From e1cb7efbf1692663cc45a097d4de12c627473b66 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 12 Feb 2026 13:51:20 -0800 Subject: [PATCH] DisplayCSR and MatchKeysCSR. --- CHANGELOG | 6 +++++ certlib/dump/dump.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ certlib/keymatch.go | 45 ++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9da4527..eaaeb6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ CHANGELOG +v1.19.0 - 2026-02-12 + +Added: +- certlib/dump: DisplayCSR function for dumping Certificate Signing Request information. +- certlib: MatchKeysCSR function for testing if a CSR's public key matches a private key. + v1.18.0 - 2025-11-21 Changed: diff --git a/certlib/dump/dump.go b/certlib/dump/dump.go index 1eafef2..86cae3d 100644 --- a/certlib/dump/dump.go +++ b/certlib/dump/dump.go @@ -165,6 +165,28 @@ func certPublic(cert *x509.Certificate) string { } } +func csrPublic(csr *x509.CertificateRequest) string { + switch pub := csr.PublicKey.(type) { + case *rsa.PublicKey: + return fmt.Sprintf("RSA-%d", pub.N.BitLen()) + case *ecdsa.PublicKey: + switch pub.Curve { + case elliptic.P256(): + return "ECDSA-prime256v1" + case elliptic.P384(): + return "ECDSA-secp384r1" + case elliptic.P521(): + return "ECDSA-secp521r1" + default: + return "ECDSA (unknown curve)" + } + case *dsa.PublicKey: + return "DSA" + default: + return "Unknown" + } +} + func DisplayName(name pkix.Name) string { var ns []string @@ -333,3 +355,36 @@ func DisplayCert(w io.Writer, cert *x509.Certificate, showHash bool) { } } } + +func DisplayCSR(w io.Writer, csr *x509.CertificateRequest, showHash bool) { + fmt.Fprintln(w, "CERTIFICATE REQUEST") + if showHash { + fmt.Fprintln(w, wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(csr.Raw)), 0)) + } + + fmt.Fprintln(w, wrap("Subject: "+DisplayName(csr.Subject), 0)) + fmt.Fprintf(w, "\tSignature algorithm: %s / %s\n", sigAlgoPK(csr.SignatureAlgorithm), + sigAlgoHash(csr.SignatureAlgorithm)) + fmt.Fprintln(w, "Details:") + wrapPrint("Public key: "+csrPublic(csr), 1) + + validNames := make([]string, 0, len(csr.DNSNames)+len(csr.EmailAddresses)+len(csr.IPAddresses)+len(csr.URIs)) + for i := range csr.DNSNames { + validNames = append(validNames, "dns:"+csr.DNSNames[i]) + } + + for i := range csr.EmailAddresses { + validNames = append(validNames, "email:"+csr.EmailAddresses[i]) + } + + for i := range csr.IPAddresses { + validNames = append(validNames, "ip:"+csr.IPAddresses[i].String()) + } + + for i := range csr.URIs { + validNames = append(validNames, "uri:"+csr.URIs[i].String()) + } + + sans := fmt.Sprintf("SANs (%d): %s\n", len(validNames), strings.Join(validNames, ", ")) + wrapPrint(sans, 1) +} diff --git a/certlib/keymatch.go b/certlib/keymatch.go index 19f00d7..08f25bf 100644 --- a/certlib/keymatch.go +++ b/certlib/keymatch.go @@ -133,3 +133,48 @@ func MatchKeys(cert *x509.Certificate, priv crypto.Signer) (bool, string) { return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public()) } } + +// MatchKeysCSR determines whether the CSR's public key matches the given private key. +// It returns true if they match; otherwise, it returns false and a human-friendly reason. +func MatchKeysCSR(csr *x509.CertificateRequest, priv crypto.Signer) (bool, string) { + switch keyPub := priv.Public().(type) { + case *rsa.PublicKey: + switch csrPub := csr.PublicKey.(type) { + case *rsa.PublicKey: + if matchRSA(csrPub, keyPub) { + return true, "" + } + return false, "public keys don't match" + case *ecdsa.PublicKey: + return false, "RSA private key, EC public key" + default: + return false, fmt.Sprintf("unsupported CSR public key type: %T", csr.PublicKey) + } + case *ecdsa.PublicKey: + switch csrPub := csr.PublicKey.(type) { + case *ecdsa.PublicKey: + if matchECDSA(csrPub, keyPub) { + return true, "" + } + // Determine a more precise reason + kc := getECCurve(keyPub) + cc := getECCurve(csrPub) + if kc == curveInvalid { + return false, "invalid private key curve" + } + if cc == curveRSA { + return false, "private key is EC, CSR is RSA" + } + if kc != cc { + return false, "EC curves don't match" + } + return false, "public keys don't match" + case *rsa.PublicKey: + return false, "private key is EC, CSR is RSA" + default: + return false, fmt.Sprintf("unsupported CSR public key type: %T", csr.PublicKey) + } + default: + return false, fmt.Sprintf("unrecognised private key type: %T", priv.Public()) + } +}