open-vault/sdk/helper/keysutil/util.go
Alexander Scheel ea6452757f
Add parsing for NSS-wrapped Ed25519 keys (#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 <84159930+rculpepper@users.noreply.github.com>
Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add integration test with NSS-created wrapped key

Co-authored-by: Rachel Culpepper <84159930+rculpepper@users.noreply.github.com>
Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add changelog

Co-authored-by: Rachel Culpepper <84159930+rculpepper@users.noreply.github.com>
Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

Co-authored-by: Rachel Culpepper <84159930+rculpepper@users.noreply.github.com>
2022-06-06 18:09:21 -04:00

116 lines
3.9 KiB
Go

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
}