From 9e13dff54c558f7f73f416e2d60d1b74b910e286 Mon Sep 17 00:00:00 2001 From: Laura Date: Fri, 20 Jun 2025 23:37:53 +0200 Subject: [PATCH] prefetch cert --- client/certificates.go | 62 ++++++++++++++++++++++++++++++++---------- client/main.go | 4 +++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/client/certificates.go b/client/certificates.go index cb28bf8..b9060bf 100644 --- a/client/certificates.go +++ b/client/certificates.go @@ -135,6 +135,51 @@ func CertificateFingerprint(certificate *x509.Certificate) string { return fmt.Sprintf("%s-%s", algo, hex.EncodeToString(sum[:])) } +func PreFetchServerCertificate(store *CertificateStore, addr string) error { + conn, err := tls.DialWithDialer(&net.Dialer{ + Timeout: 5 * time.Second, + }, "tcp", addr, &tls.Config{ + InsecureSkipVerify: true, + }) + + if err != nil { + return err + } + + defer conn.Close() + + state := conn.ConnectionState() + if len(state.PeerCertificates) == 0 { + return fmt.Errorf("no peer certificates") + } + + certificate := state.PeerCertificates[0] + + if certificate.Subject.CommonName != "up" { + return errors.New("invalid certificate subject") + } + + name := state.ServerName + fingerprint := CertificateFingerprint(certificate) + + if store.IsPinned(name, fingerprint) { + return nil + } + + log.Printf("Server fingerprint for %s: %s\n", name, fingerprint) + log.Print("Accept? [y/N]: ") + + var confirm string + + fmt.Scanln(&confirm) + + if strings.ToLower(strings.TrimSpace(confirm)) != "y" { + return errors.New("certificate rejected") + } + + return store.Pin(name, fingerprint) +} + func NewPinnedClient(store *CertificateStore) *http.Client { config := &tls.Config{ InsecureSkipVerify: true, @@ -154,22 +199,11 @@ func NewPinnedClient(store *CertificateStore) *http.Client { name := cs.ServerName fingerprint := CertificateFingerprint(certificate) - if store.IsPinned(name, fingerprint) { - return nil + if !store.IsPinned(name, fingerprint) { + return errors.New("unknown certificate") } - log.Printf("Server fingerprint for %s: %s\n", name, fingerprint) - log.Print("Accept? [y/N]: ") - - var confirm string - - fmt.Scanln(&confirm) - - if strings.ToLower(strings.TrimSpace(confirm)) != "y" { - return errors.New("certificate rejected") - } - - return store.Pin(name, fingerprint) + return nil } return &http.Client{ diff --git a/client/main.go b/client/main.go index c67c1d2..7b93432 100644 --- a/client/main.go +++ b/client/main.go @@ -141,6 +141,10 @@ func run(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("failed to load certificate store: %v", err) } + if err = PreFetchServerCertificate(store, hostname); err != nil { + return err + } + client := NewPinnedClient(store) log.Println("Requesting challenge...")