forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add parsing for NSS-wrapped Ed25519 keys (hashicorp#15742)
* Add parsing for NSS-wrapped Ed25519 keys NSS wraps Ed25519 using the PKCS#8 standard structure. The Go standard library as of Go 1.18.x doesn't support parsing this key type with the OID used by NSS; it requires the 1.3.101.112/RFC 8410 format, rather than the RFC 5915-esque structure supported here. Co-authored-by: Rachel Culpepper <[email protected]> Signed-off-by: Alexander Scheel <[email protected]> * Add integration test with NSS-created wrapped key Co-authored-by: Rachel Culpepper <[email protected]> Signed-off-by: Alexander Scheel <[email protected]> * Add changelog Co-authored-by: Rachel Culpepper <[email protected]> Signed-off-by: Alexander Scheel <[email protected]> Co-authored-by: Rachel Culpepper <[email protected]>
- Loading branch information
1 parent
395356a
commit da2fd89
Showing
4 changed files
with
187 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:improvement | ||
secret/transit: Allow importing Ed25519 keys from PKCS#8 with inner RFC 5915 ECPrivateKey blobs (NSS-wrapped keys). | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package keysutil | ||
|
||
import ( | ||
"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} | ||
) | ||
|
||
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 | ||
} |