| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package keysutil |
| |
| import ( |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/asn1" |
| "errors" |
| "fmt" |
| |
| "golang.org/x/crypto/ed25519" |
| ) |
| |
| // pkcs8 reflects an ASN.1, PKCS #8 PrivateKey. See |
| // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn |
| // and RFC 5208. |
| // |
| // Copied from Go: https://github.com/golang/go/blob/master/src/crypto/x509/pkcs8.go#L17-L80 |
| type pkcs8 struct { |
| Version int |
| Algo pkix.AlgorithmIdentifier |
| PrivateKey []byte |
| // optional attributes omitted. |
| } |
| |
| // ecPrivateKey reflects an ASN.1 Elliptic Curve Private Key Structure. |
| // References: |
| // |
| // RFC 5915 |
| // SEC1 - http://www.secg.org/sec1-v2.pdf |
| // |
| // Per RFC 5915 the NamedCurveOID is marked as ASN.1 OPTIONAL, however in |
| // most cases it is not. |
| // |
| // Copied from Go: https://github.com/golang/go/blob/master/src/crypto/x509/sec1.go#L18-L31 |
| type ecPrivateKey struct { |
| Version int |
| PrivateKey []byte |
| NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` |
| |
| // Because the PKCS8/RFC 5915 encoding of the Ed25519 key uses the |
| // RFC 8032 Ed25519 seed format, we can ignore the public key parameter |
| // and infer it later. |
| } |
| |
| var ( |
| // See crypto/x509/x509.go in the Go toolchain source distribution. |
| oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} |
| |
| // NSS encodes Ed25519 private keys with the OID 1.3.6.1.4.1.11591.15.1 |
| // from https://tools.ietf.org/html/draft-josefsson-pkix-newcurves-01. |
| // See https://github.com/nss-dev/nss/blob/NSS_3_79_BRANCH/lib/util/secoid.c#L600-L603. |
| oidNSSPKIXEd25519 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11591, 15, 1} |
| |
| // Other implementations may use the OID 1.3.101.110 from |
| // https://datatracker.ietf.org/doc/html/rfc8410. |
| oidRFC8410Ed25519 = asn1.ObjectIdentifier{1, 3, 101, 110} |
| |
| // See crypto/x509/x509.go in the Go toolchain source distribution. |
| oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} |
| ) |
| |
| func isEd25519OID(oid asn1.ObjectIdentifier) bool { |
| return oidNSSPKIXEd25519.Equal(oid) || oidRFC8410Ed25519.Equal(oid) |
| } |
| |
| // ParsePKCS8PrivateKey parses an unencrypted private key in PKCS #8, ASN.1 DER form. |
| // |
| // It returns a *rsa.PrivateKey, a *ecdsa.PrivateKey, or a ed25519.PrivateKey. |
| // More types might be supported in the future. |
| // |
| // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". |
| func ParsePKCS8Ed25519PrivateKey(der []byte) (key interface{}, err error) { |
| var privKey pkcs8 |
| var ed25519Key ecPrivateKey |
| |
| var checkedOID bool |
| |
| // If this err is nil, we assume we directly have a ECPrivateKey structure |
| // with explicit OID; ignore this error for now and return the latter err |
| // instead if neither parse correctly. |
| if _, err := asn1.Unmarshal(der, &privKey); err == nil { |
| switch { |
| case privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA): |
| bytes := privKey.Algo.Parameters.FullBytes |
| namedCurveOID := new(asn1.ObjectIdentifier) |
| if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil { |
| namedCurveOID = nil |
| } |
| |
| if namedCurveOID == nil || !isEd25519OID(*namedCurveOID) { |
| return nil, errors.New("keysutil: failed to parse private key (invalid, non-ed25519 curve parameter OID)") |
| } |
| |
| der = privKey.PrivateKey |
| checkedOID = true |
| default: |
| // The Go standard library already parses RFC 8410 keys; the |
| // inclusion of the OID here is in case it is used with the |
| // regular ECDSA PrivateKey structure, rather than the struct |
| // recognized by the Go standard library. |
| return nil, errors.New("keysutil: failed to parse key as ed25519 private key") |
| } |
| } |
| |
| _, err = asn1.Unmarshal(der, &ed25519Key) |
| if err != nil { |
| return nil, fmt.Errorf("keysutil: failed to parse private key (inner Ed25519 ECPrivateKey format was incorrect): %v", err) |
| } |
| |
| if !checkedOID && !isEd25519OID(ed25519Key.NamedCurveOID) { |
| return nil, errors.New("keysutil: failed to parse private key (invalid, non-ed25519 curve parameter OID)") |
| } |
| |
| if len(ed25519Key.PrivateKey) != 32 { |
| return nil, fmt.Errorf("keysutil: failed to parse private key as ed25519 private key: got %v bytes but expected %v byte RFC 8032 seed", len(ed25519Key.PrivateKey), ed25519.SeedSize) |
| } |
| |
| return ed25519.NewKeyFromSeed(ed25519Key.PrivateKey), nil |
| } |
| |
| // ParsePKCS8PrivateKey parses an unencrypted private key in PKCS #8, ASN.1 DER form. |
| // |
| // This helper only supports RSA/PSS keys (with OID 1.2.840.113549.1.1.10). |
| // |
| // It returns a *rsa.PrivateKey, a *ecdsa.PrivateKey, or a ed25519.PrivateKey. |
| // More types might be supported in the future. |
| // |
| // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". |
| func ParsePKCS8RSAPSSPrivateKey(der []byte) (key interface{}, err error) { |
| var privKey pkcs8 |
| if _, err := asn1.Unmarshal(der, &privKey); err == nil { |
| switch { |
| case privKey.Algo.Algorithm.Equal(oidSignatureRSAPSS): |
| // Fall through; there's no parameters here unlike ECDSA |
| // containers, so we can go to parsing the inner rsaPrivateKey |
| // object. |
| default: |
| return nil, errors.New("keysutil: failed to parse key as RSA PSS private key") |
| } |
| } |
| |
| key, err = x509.ParsePKCS1PrivateKey(privKey.PrivateKey) |
| if err != nil { |
| return nil, fmt.Errorf("keysutil: failed to parse inner RSA PSS private key: %w", err) |
| } |
| |
| return key, nil |
| } |