From 02fb85aec036b240138cc3e23fca78690dbf9b43 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 19 Nov 2025 14:46:17 -0800 Subject: [PATCH] certlib: update FileKind with algo information. Additionally, key algo wasn't being set on PEM files. --- certlib/certlib.go | 132 +++++++++++++++++++++++++------ certlib/certlib_test.go | 33 ++++++++ certlib/testdata/ec-ca-cert.csr | 12 +++ certlib/testdata/ec-ca-cert.pem | 18 +++++ certlib/testdata/ec-ca-priv.pem | 8 ++ certlib/testdata/ec-ca.yaml | 13 +++ certlib/testdata/rsa-ca-cert.csr | 8 ++ certlib/testdata/rsa-ca-cert.pem | 14 ++++ certlib/testdata/rsa-ca-priv.pem | 3 + certlib/testdata/rsa-ca.yaml | 13 +++ 10 files changed, 231 insertions(+), 23 deletions(-) create mode 100644 certlib/testdata/ec-ca-cert.csr create mode 100644 certlib/testdata/ec-ca-cert.pem create mode 100644 certlib/testdata/ec-ca-priv.pem create mode 100644 certlib/testdata/ec-ca.yaml create mode 100644 certlib/testdata/rsa-ca-cert.csr create mode 100644 certlib/testdata/rsa-ca-cert.pem create mode 100644 certlib/testdata/rsa-ca-priv.pem create mode 100644 certlib/testdata/rsa-ca.yaml diff --git a/certlib/certlib.go b/certlib/certlib.go index 15a5421..93edb08 100644 --- a/certlib/certlib.go +++ b/certlib/certlib.go @@ -3,6 +3,11 @@ package certlib import ( "bytes" "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" "crypto/x509" "encoding/pem" "errors" @@ -155,16 +160,102 @@ func (f FileFormat) String() string { } } +type KeyAlgo struct { + Type x509.PublicKeyAlgorithm + Size int + curve elliptic.Curve +} + +func (ka KeyAlgo) String() string { + switch ka.Type { + case x509.RSA: + return fmt.Sprintf("RSA-%d", ka.Size) + case x509.ECDSA: + return fmt.Sprintf("ECDSA-%s", ka.curve.Params().Name) + case x509.Ed25519: + return "Ed25519" + case x509.DSA: + return "DSA" + default: + return "unknown" + } +} + +func publicKeyAlgoFromPublicKey(key crypto.PublicKey) KeyAlgo { + switch key := key.(type) { + case *rsa.PublicKey: + return KeyAlgo{ + Type: x509.RSA, + Size: key.N.BitLen(), + } + case *ecdsa.PublicKey: + return KeyAlgo{ + Type: x509.ECDSA, + curve: key.Curve, + Size: key.Params().BitSize, + } + case *ed25519.PublicKey: + return KeyAlgo{ + Type: x509.Ed25519, + } + case *dsa.PublicKey: + return KeyAlgo{ + Type: x509.DSA, + } + default: + return KeyAlgo{ + Type: x509.UnknownPublicKeyAlgorithm, + } + } +} + +func publicKeyAlgoFromKey(key crypto.PrivateKey) KeyAlgo { + switch key := key.(type) { + case *rsa.PrivateKey: + return KeyAlgo{ + Type: x509.RSA, + Size: key.PublicKey.N.BitLen(), + } + case *ecdsa.PrivateKey: + return KeyAlgo{ + Type: x509.ECDSA, + curve: key.PublicKey.Curve, + Size: key.Params().BitSize, + } + case *ed25519.PrivateKey: + return KeyAlgo{ + Type: x509.Ed25519, + } + case *dsa.PrivateKey: + return KeyAlgo{ + Type: x509.DSA, + } + default: + return KeyAlgo{ + Type: x509.UnknownPublicKeyAlgorithm, + } + } +} + +func publicKeyAlgoFromCert(cert *x509.Certificate) KeyAlgo { + return publicKeyAlgoFromPublicKey(cert.PublicKey) +} + +func publicKeyAlgoFromCSR(csr *x509.CertificateRequest) KeyAlgo { + return publicKeyAlgoFromPublicKey(csr.PublicKeyAlgorithm) +} + type FileType struct { Format FileFormat Type string + Algo KeyAlgo } func (ft FileType) String() string { if ft.Type == "" { return ft.Format.String() } - return fmt.Sprintf("%s (%s)", ft.Type, ft.Format) + return fmt.Sprintf("%s %s (%s)", ft.Algo, ft.Type, ft.Format) } // FileKind returns the file type of the given file. @@ -174,36 +265,31 @@ func FileKind(path string) (*FileType, error) { return nil, err } + ft := &FileType{Format: FormatDER} + block, _ := pem.Decode(data) if block != nil { - return &FileType{ - Format: FormatPEM, - Type: strings.ToLower(strings.TrimSpace(block.Type)), - }, nil + data = block.Bytes + ft.Type = strings.ToLower(block.Type) + ft.Format = FormatPEM + } + + cert, err := x509.ParseCertificate(data) + if err == nil { + ft.Algo = publicKeyAlgoFromCert(cert) + return ft, nil } - _, err = x509.ParseCertificate(data) + csr, err := x509.ParseCertificateRequest(data) if err == nil { - return &FileType{ - Format: FormatDER, - Type: strings.ToLower(pemTypeCertificate), - }, nil + ft.Algo = publicKeyAlgoFromCSR(csr) + return ft, nil } - _, err = x509.ParseCertificateRequest(data) + priv, err := x509.ParsePKCS8PrivateKey(data) if err == nil { - return &FileType{ - Format: FormatDER, - Type: strings.ToLower(pemTypeCertificateRequest), - }, nil - } - - _, err = x509.ParsePKCS8PrivateKey(data) - if err == nil { - return &FileType{ - Format: FormatDER, - Type: strings.ToLower(pemTypePrivateKey), - }, nil + ft.Algo = publicKeyAlgoFromKey(priv) + return ft, nil } return nil, errors.New("certlib; unknown file type") diff --git a/certlib/certlib_test.go b/certlib/certlib_test.go index d588eab..16bb858 100644 --- a/certlib/certlib_test.go +++ b/certlib/certlib_test.go @@ -2,7 +2,10 @@ package certlib import ( + "crypto/elliptic" + "crypto/x509" "fmt" + "strings" "testing" "git.wntrmute.dev/kyle/goutils/assert" @@ -138,3 +141,33 @@ func TestReadCertificates(t *testing.T) { assert.BoolT(t, cert != nil, "lib: expected an actual certificate to have been returned") } } + +var ( + ecTestCACert = "testdata/ec-ca-cert.pem" + ecTestCAPriv = "testdata/ec-ca-priv.pem" + ecTestCAReq = "testdata/ec-ca-cert.csr" +) + +func TestFileTypeEC(t *testing.T) { + ft, err := FileKind(ecTestCAPriv) + assert.NoErrorT(t, err) + + if ft.Format != FormatPEM { + t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format) + } + + if ft.Type != strings.ToLower(pemTypePrivateKey) { + t.Errorf("certlib: expected type '%s', got '%s'", + strings.ToLower(pemTypePrivateKey), ft.Type) + } + + expectedAlgo := KeyAlgo{ + Type: x509.ECDSA, + Size: 521, + curve: elliptic.P521(), + } + + if ft.Algo.String() != expectedAlgo.String() { + t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo) + } +} diff --git a/certlib/testdata/ec-ca-cert.csr b/certlib/testdata/ec-ca-cert.csr new file mode 100644 index 0000000..d37bee5 --- /dev/null +++ b/certlib/testdata/ec-ca-cert.csr @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBzTCCAS4CAQAwgYgxCzAJBgNVBAYTAlVTMQkwBwYDVQQIEwAxCTAHBgNVBAcT +ADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNUUklFUzEfMB0GA1UECxMW +Q1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEeMBwGA1UEAxMVV05UUk1VVEUgVEVTVCBF +QyBDQSAxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAQxmTxzo1XOK0HDrtn92b +exC4sXr8GnU+oATiXied3e1AWVOux9XtaWduY+a+r6Kb1rxMVyebn9KqtwNw+9KS +XaEB1IN9QzfdxEcJgRIAVtFplOqCip5xKK0B+woo3wXm3ndq2kJts86aONqQ0m2g +RrsmAKAX4pwmMnAHFF7veBcpsqugADAKBggqhkjOPQQDBAOBjAAwgYgCQgDG8Hdu +FkC3z0u0MU01+Bi/2MorcVTvdkurLm6Rh2Zf65aaXK8PDdV/cPZ98qx7NoLDSvwF +83gJuUI/3nVB/Ith7wJCAb6SAkXroT7y41XHayyTYb6+RKSlxxb9e5rtVCp/nG23 +s59r23vUC/wDb4VWJE5jKi5vmXfjY+RAL9FOnpr2wsX0 +-----END CERTIFICATE REQUEST----- diff --git a/certlib/testdata/ec-ca-cert.pem b/certlib/testdata/ec-ca-cert.pem new file mode 100644 index 0000000..1cde823 --- /dev/null +++ b/certlib/testdata/ec-ca-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC4TCCAkKgAwIBAgIUSnrCuvU8kj0nxNzmTgibiPLrQ8QwCgYIKoZIzj0EAwQw +gYgxCzAJBgNVBAYTAlVTMQkwBwYDVQQIEwAxCTAHBgNVBAcTADEiMCAGA1UEChMZ +V05UUk1VVEUgSEVBVlkgSU5EVVNUUklFUzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJ +QyBTRVJWSUNFUzEeMBwGA1UEAxMVV05UUk1VVEUgVEVTVCBFQyBDQSAxMB4XDTI1 +MTExOTIwNTgwMVoXDTQ1MTExNDIxNTgwMVowgYgxCzAJBgNVBAYTAlVTMQkwBwYD +VQQIEwAxCTAHBgNVBAcTADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNU +UklFUzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEeMBwGA1UEAxMV +V05UUk1VVEUgVEVTVCBFQyBDQSAxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA +QxmTxzo1XOK0HDrtn92bexC4sXr8GnU+oATiXied3e1AWVOux9XtaWduY+a+r6Kb +1rxMVyebn9KqtwNw+9KSXaEB1IN9QzfdxEcJgRIAVtFplOqCip5xKK0B+woo3wXm +3ndq2kJts86aONqQ0m2gRrsmAKAX4pwmMnAHFF7veBcpsqujRTBDMA4GA1UdDwEB +/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/AgEDMB0GA1UdDgQWBBSNqRkvwUgIHGa2 +jKmA2Q3w6Ju/FzAKBggqhkjOPQQDBAOBjAAwgYgCQgCckIFCjzJExxbV9dqm92nr +safC3kqhCxjmilf0IYWVj5f1kymoFr3jPpmy0iFcteUk0QTcqpnUT4i140lxtyK8 +NAJCAVxbicZgVns9rgp6hu14l81j0XMpNgzy0QxscjMpWS/17iDJ4Y5vCWpwekrr +F1cmmRpsodONacAvTml4ehKE2ekx +-----END CERTIFICATE----- diff --git a/certlib/testdata/ec-ca-priv.pem b/certlib/testdata/ec-ca-priv.pem new file mode 100644 index 0000000..391d351 --- /dev/null +++ b/certlib/testdata/ec-ca-priv.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAzkf/rvLGJBTVHHHr +lUhzsRJZgkyzSY5YE3KBReDyFWc+OB48C1gdYB1u7+PxgyfwYACjPx2y1AxN8fJh +XonY39mhgYkDgYYABABDGZPHOjVc4rQcOu2f3Zt7ELixevwadT6gBOJeJ53d7UBZ +U67H1e1pZ25j5r6vopvWvExXJ5uf0qq3A3D70pJdoQHUg31DN93ERwmBEgBW0WmU +6oKKnnEorQH7CijfBebed2raQm2zzpo42pDSbaBGuyYAoBfinCYycAcUXu94Fymy +qw== +-----END PRIVATE KEY----- diff --git a/certlib/testdata/ec-ca.yaml b/certlib/testdata/ec-ca.yaml new file mode 100644 index 0000000..e9f0f45 --- /dev/null +++ b/certlib/testdata/ec-ca.yaml @@ -0,0 +1,13 @@ +key: + algorithm: ecdsa + size: 521 +subject: + common_name: WNTRMUTE TEST EC CA 1 + country: US + organization: WNTRMUTE HEAVY INDUSTRIES + organizational_unit: CRYPTOGRAPHIC SERVICES +profile: + is_ca: true + path_len: 3 + key_uses: cert sign + expiry: 20y diff --git a/certlib/testdata/rsa-ca-cert.csr b/certlib/testdata/rsa-ca-cert.csr new file mode 100644 index 0000000..3c5041a --- /dev/null +++ b/certlib/testdata/rsa-ca-cert.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBCjCBvQIBADCBiTELMAkGA1UEBhMCVVMxCTAHBgNVBAgTADEJMAcGA1UEBxMA +MSIwIAYDVQQKExlXTlRSTVVURSBIRUFWWSBJTkRVU1RSSUVTMR8wHQYDVQQLExZD +UllQVE9HUkFQSElDIFNFUlZJQ0VTMR8wHQYDVQQDExZXTlRSTVVURSBURVNUIFJT +QSBDQSAxMCowBQYDK2VwAyEA1Lai2WChuUH2kq4LWddp6TlcmpuuBz6G43e9efsZ +GBqgADAFBgMrZXADQQDbBl1gW07c0g9UQmK2g8QkVIXzr2TLrOjXVAptlcW/3rPO +M3iQM2mGwZWMwv7t6C4C7xBaLcUkcqT3b4S+MaUK +-----END CERTIFICATE REQUEST----- diff --git a/certlib/testdata/rsa-ca-cert.pem b/certlib/testdata/rsa-ca-cert.pem new file mode 100644 index 0000000..616eaaf --- /dev/null +++ b/certlib/testdata/rsa-ca-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHDCCAc6gAwIBAgIVAN1AKHhLNsqcBEKYCqgjEMG65hhvMAUGAytlcDCBiTEL +MAkGA1UEBhMCVVMxCTAHBgNVBAgTADEJMAcGA1UEBxMAMSIwIAYDVQQKExlXTlRS +TVVURSBIRUFWWSBJTkRVU1RSSUVTMR8wHQYDVQQLExZDUllQVE9HUkFQSElDIFNF +UlZJQ0VTMR8wHQYDVQQDExZXTlRSTVVURSBURVNUIFJTQSBDQSAxMB4XDTI1MTEx +OTIxMDQyNVoXDTQ1MTExNDIyMDQyNVowgYkxCzAJBgNVBAYTAlVTMQkwBwYDVQQI +EwAxCTAHBgNVBAcTADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNUUklF +UzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEfMB0GA1UEAxMWV05U +Uk1VVEUgVEVTVCBSU0EgQ0EgMTAqMAUGAytlcAMhANS2otlgoblB9pKuC1nXaek5 +XJqbrgc+huN3vXn7GRgao0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgw +BgEB/wIBAzAdBgNVHQ4EFgQUetUgY5rlFq+OCeYe0Eqmp8Ek488wBQYDK2VwA0EA +LIFZo6FQL+8q8h66Bm7favIh2AlqsXA45DpRUN2LpjNm/7NbTPDw52y8cLegUUMc +UhDyk20fGg5g6cLywC0mDA== +-----END CERTIFICATE----- diff --git a/certlib/testdata/rsa-ca-priv.pem b/certlib/testdata/rsa-ca-priv.pem new file mode 100644 index 0000000..7b2ff35 --- /dev/null +++ b/certlib/testdata/rsa-ca-priv.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIDDkYbIZKArACSevxtX2Rr8MQSeJ4Jz0qJEe/YgHfjzo +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/certlib/testdata/rsa-ca.yaml b/certlib/testdata/rsa-ca.yaml new file mode 100644 index 0000000..7bf1a26 --- /dev/null +++ b/certlib/testdata/rsa-ca.yaml @@ -0,0 +1,13 @@ +key: + algorithm: ed25519 + size: 4096 +subject: + common_name: WNTRMUTE TEST RSA CA 1 + country: US + organization: WNTRMUTE HEAVY INDUSTRIES + organizational_unit: CRYPTOGRAPHIC SERVICES +profile: + is_ca: true + path_len: 3 + key_uses: cert sign + expiry: 20y