|
|
@@ -5,6 +5,8 @@ import (
|
|
|
"encoding/pem"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
+ "slices"
|
|
|
+ "strings"
|
|
|
|
|
|
"golang.org/x/crypto/ed25519"
|
|
|
)
|
|
|
@@ -74,23 +76,11 @@ const ( //key-agreement-key banners
|
|
|
P256PublicKeyBanner = "NEBULA P256 PUBLIC KEY"
|
|
|
)
|
|
|
|
|
|
-// Version 3 keys are composite: the classical key followed by the post-quantum key, an ML-KEM-1024 seed or
|
|
|
-// encapsulation key for hosts and an ML-DSA-87 seed or public key for CAs.
|
|
|
-const ( //post-quantum key-agreement-key banners
|
|
|
- X25519MLKEM1024PrivateKeyBanner = "NEBULA X25519 MLKEM1024 PRIVATE KEY"
|
|
|
- X25519MLKEM1024PublicKeyBanner = "NEBULA X25519 MLKEM1024 PUBLIC KEY"
|
|
|
- P256MLKEM1024PrivateKeyBanner = "NEBULA P256 MLKEM1024 PRIVATE KEY"
|
|
|
- P256MLKEM1024PublicKeyBanner = "NEBULA P256 MLKEM1024 PUBLIC KEY"
|
|
|
-)
|
|
|
-
|
|
|
-const ( //post-quantum signing key banners
|
|
|
- EncryptedECDSAP256MLDSA87PrivateKeyBanner = "NEBULA ECDSA P256 MLDSA87 ENCRYPTED PRIVATE KEY"
|
|
|
- ECDSAP256MLDSA87PrivateKeyBanner = "NEBULA ECDSA P256 MLDSA87 PRIVATE KEY"
|
|
|
- ECDSAP256MLDSA87PublicKeyBanner = "NEBULA ECDSA P256 MLDSA87 PUBLIC KEY"
|
|
|
- EncryptedEd25519MLDSA87PrivateKeyBanner = "NEBULA ED25519 MLDSA87 ENCRYPTED PRIVATE KEY"
|
|
|
- Ed25519MLDSA87PrivateKeyBanner = "NEBULA ED25519 MLDSA87 PRIVATE KEY"
|
|
|
- Ed25519MLDSA87PublicKeyBanner = "NEBULA ED25519 MLDSA87 PUBLIC KEY"
|
|
|
-)
|
|
|
+// Version 3 keys are composite: the classical key followed by the post-quantum key of the certificate's PQSuite, a
|
|
|
+// KEM seed or public key for hosts and a signature algorithm's seed or public key for CAs. Their banners name both
|
|
|
+// halves, "NEBULA X25519 MLKEM1024 PRIVATE KEY" or "NEBULA ED25519 MLDSA87 PUBLIC KEY" for example, because the
|
|
|
+// post-quantum algorithm can not be told from the key's size: ML-KEM and ML-DSA seeds are the same size at every
|
|
|
+// strength. The banners name algorithms, not suites, so suites sharing an algorithm share its banners too.
|
|
|
|
|
|
// Classical key sizes, the composite keys of version 3 add the post-quantum key to these.
|
|
|
const (
|
|
|
@@ -102,12 +92,91 @@ const (
|
|
|
ecdsaP256PublicKeySize = 65 // uncompressed
|
|
|
)
|
|
|
|
|
|
-// signingPrivateKeySize returns the size of a classical signing private key.
|
|
|
-func signingPrivateKeySize(curve Curve) int {
|
|
|
- if curve == Curve_P256 {
|
|
|
- return p256PrivateKeySize
|
|
|
+// ClassicalPrivateKey returns the classical half of a host private key: all of a classical key, or the classical half
|
|
|
+// of a composite one, whatever its suite.
|
|
|
+func ClassicalPrivateKey(key []byte) []byte {
|
|
|
+ // X25519 and P256 private keys are the same size.
|
|
|
+ if len(key) > x25519KeySize {
|
|
|
+ return key[:x25519KeySize]
|
|
|
+ }
|
|
|
+ return key
|
|
|
+}
|
|
|
+
|
|
|
+// pqKeyKind is what a composite key banner holds.
|
|
|
+type pqKeyKind int
|
|
|
+
|
|
|
+const (
|
|
|
+ pqPrivateKey pqKeyKind = iota
|
|
|
+ pqPublicKey
|
|
|
+ pqSigningPrivateKey
|
|
|
+ pqSigningPublicKey
|
|
|
+ pqEncryptedSigningPrivateKey
|
|
|
+)
|
|
|
+
|
|
|
+// pqKeyBanner is what a composite key banner says about the key under it.
|
|
|
+type pqKeyBanner struct {
|
|
|
+ kind pqKeyKind
|
|
|
+ curve Curve
|
|
|
+ size int // of the whole composite key; for an encrypted key, once decrypted
|
|
|
+}
|
|
|
+
|
|
|
+var pqKeyKinds = []pqKeyKind{pqPrivateKey, pqPublicKey, pqSigningPrivateKey, pqSigningPublicKey, pqEncryptedSigningPrivateKey}
|
|
|
+
|
|
|
+// pqKeyBanners maps every composite key banner of every suite to what it holds, see registerPQSuite.
|
|
|
+var pqKeyBanners = map[string]pqKeyBanner{}
|
|
|
+
|
|
|
+// pqBannerName returns the banner for a composite key.
|
|
|
+func pqBannerName(kind pqKeyKind, curve Curve, suite *pqSuite) string {
|
|
|
+ classical := map[Curve]string{Curve_CURVE25519: "X25519", Curve_P256: "P256"}[curve]
|
|
|
+ alg := suite.kem.name
|
|
|
+ if kind != pqPrivateKey && kind != pqPublicKey {
|
|
|
+ classical = map[Curve]string{Curve_CURVE25519: "ED25519", Curve_P256: "ECDSA P256"}[curve]
|
|
|
+ alg = suite.dsa.name
|
|
|
+ }
|
|
|
+ return "NEBULA " + classical + " " + alg + " " + map[pqKeyKind]string{
|
|
|
+ pqPrivateKey: "PRIVATE KEY",
|
|
|
+ pqPublicKey: "PUBLIC KEY",
|
|
|
+ pqSigningPrivateKey: "PRIVATE KEY",
|
|
|
+ pqSigningPublicKey: "PUBLIC KEY",
|
|
|
+ pqEncryptedSigningPrivateKey: "ENCRYPTED PRIVATE KEY",
|
|
|
+ }[kind]
|
|
|
+}
|
|
|
+
|
|
|
+// pqKeySize returns the size of a composite key.
|
|
|
+func pqKeySize(kind pqKeyKind, curve Curve, suite *pqSuite) int {
|
|
|
+ p256 := curve == Curve_P256
|
|
|
+ switch kind {
|
|
|
+ case pqPrivateKey:
|
|
|
+ return x25519KeySize + suite.kem.privateKeySize // a P256 private key is the same size
|
|
|
+ case pqPublicKey:
|
|
|
+ return map[bool]int{false: x25519KeySize, true: p256PublicKeySize}[p256] + suite.kem.publicKeySize
|
|
|
+ case pqSigningPublicKey:
|
|
|
+ return map[bool]int{false: ed25519PublicKeySize, true: ecdsaP256PublicKeySize}[p256] + suite.dsa.publicKeySize
|
|
|
+ default:
|
|
|
+ return map[bool]int{false: ed25519PrivateKeySize, true: p256PrivateKeySize}[p256] + suite.dsa.privateKeySize
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// marshalPQKeyToPEM PEM encodes a composite key of suite, nil for an unknown curve or suite or a key of the wrong size.
|
|
|
+func marshalPQKeyToPEM(kind pqKeyKind, curve Curve, suite PQSuite, b []byte) []byte {
|
|
|
+ p := pqSuites[suite]
|
|
|
+ if p == nil || (curve != Curve_CURVE25519 && curve != Curve_P256) || len(b) != pqKeySize(kind, curve, p) {
|
|
|
+ return nil
|
|
|
}
|
|
|
- return ed25519PrivateKeySize
|
|
|
+ return pem.EncodeToMemory(&pem.Block{Type: pqBannerName(kind, curve, p), Bytes: b})
|
|
|
+}
|
|
|
+
|
|
|
+// unmarshalPQKeyBlock checks a PEM block that may hold a composite key of one of kinds. ok is false when the banner
|
|
|
+// is not a composite key banner of those kinds at all.
|
|
|
+func unmarshalPQKeyBlock(k *pem.Block, kinds ...pqKeyKind) (curve Curve, ok bool, err error) {
|
|
|
+ b, found := pqKeyBanners[k.Type]
|
|
|
+ if !found || !slices.Contains(kinds, b.kind) {
|
|
|
+ return 0, false, nil
|
|
|
+ }
|
|
|
+ if b.kind != pqEncryptedSigningPrivateKey && len(k.Bytes) != b.size {
|
|
|
+ return b.curve, true, fmt.Errorf("key was not %d bytes, is invalid %s", b.size, strings.ToLower(strings.TrimPrefix(k.Type, "NEBULA ")))
|
|
|
+ }
|
|
|
+ return b.curve, true, nil
|
|
|
}
|
|
|
|
|
|
/* including "ECDSA" in the P256 banners is a clue that these keys should be used only for signing */
|
|
|
@@ -154,6 +223,12 @@ func unmarshalCertificateBlock(block *pem.Block) (Certificate, error) {
|
|
|
}
|
|
|
|
|
|
func marshalCertPublicKeyToPEM(c Certificate) []byte {
|
|
|
+ if suite := PQSuiteOf(c); suite != PQSuiteUnknown {
|
|
|
+ if c.IsCA() {
|
|
|
+ return MarshalPQSigningPublicKeyToPEM(c.Curve(), suite, c.PublicKey())
|
|
|
+ }
|
|
|
+ return MarshalPQPublicKeyToPEM(c.Curve(), suite, c.PublicKey())
|
|
|
+ }
|
|
|
if c.IsCA() {
|
|
|
return MarshalSigningPublicKeyToPEM(c.Curve(), c.PublicKey())
|
|
|
} else {
|
|
|
@@ -164,13 +239,6 @@ func marshalCertPublicKeyToPEM(c Certificate) []byte {
|
|
|
// MarshalPublicKeyToPEM returns a PEM representation of a public key used for ECDH.
|
|
|
// if your public key came from a certificate, prefer Certificate.PublicKeyPEM() if possible, to avoid mistakes!
|
|
|
func MarshalPublicKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
- pq := len(b) > MLKEM1024PublicKeySize
|
|
|
- switch {
|
|
|
- case pq && curve == Curve_CURVE25519:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: X25519MLKEM1024PublicKeyBanner, Bytes: b})
|
|
|
- case pq && curve == Curve_P256:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: P256MLKEM1024PublicKeyBanner, Bytes: b})
|
|
|
- }
|
|
|
switch curve {
|
|
|
case Curve_CURVE25519:
|
|
|
return pem.EncodeToMemory(&pem.Block{Type: X25519PublicKeyBanner, Bytes: b})
|
|
|
@@ -181,16 +249,15 @@ func MarshalPublicKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// MarshalPQPublicKeyToPEM returns a PEM representation of a composite host public key of suite, the classical ECDH
|
|
|
+// key followed by the suite's KEM public key, or nil if b is not one.
|
|
|
+func MarshalPQPublicKeyToPEM(curve Curve, suite PQSuite, b []byte) []byte {
|
|
|
+ return marshalPQKeyToPEM(pqPublicKey, curve, suite, b)
|
|
|
+}
|
|
|
+
|
|
|
// MarshalSigningPublicKeyToPEM returns a PEM representation of a public key used for signing.
|
|
|
// if your public key came from a certificate, prefer Certificate.PublicKeyPEM() if possible, to avoid mistakes!
|
|
|
func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
- pq := len(b) > MLDSA87PublicKeySize
|
|
|
- switch {
|
|
|
- case pq && curve == Curve_CURVE25519:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: Ed25519MLDSA87PublicKeyBanner, Bytes: b})
|
|
|
- case pq && curve == Curve_P256:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256MLDSA87PublicKeyBanner, Bytes: b})
|
|
|
- }
|
|
|
switch curve {
|
|
|
case Curve_CURVE25519:
|
|
|
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})
|
|
|
@@ -201,6 +268,12 @@ func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// MarshalPQSigningPublicKeyToPEM returns a PEM representation of a composite CA public key of suite, the classical
|
|
|
+// signing key followed by the public key of the suite's signature algorithm, or nil if b is not one.
|
|
|
+func MarshalPQSigningPublicKeyToPEM(curve Curve, suite PQSuite, b []byte) []byte {
|
|
|
+ return marshalPQKeyToPEM(pqSigningPublicKey, curve, suite, b)
|
|
|
+}
|
|
|
+
|
|
|
// UnmarshalPublicKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
|
|
|
// consumed data or an error on failure. Only key-agreement (ECDH) public key banners are accepted.
|
|
|
// Use UnmarshalSigningPublicKeyFromPEM for Ed25519/ECDSA banners.
|
|
|
@@ -209,6 +282,12 @@ func UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
if k == nil {
|
|
|
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
|
|
|
}
|
|
|
+ if curve, ok, err := unmarshalPQKeyBlock(k, pqPublicKey); ok {
|
|
|
+ if err != nil {
|
|
|
+ return nil, r, 0, err
|
|
|
+ }
|
|
|
+ return k.Bytes, r, curve, nil
|
|
|
+ }
|
|
|
var expectedLen int
|
|
|
var curve Curve
|
|
|
switch k.Type {
|
|
|
@@ -219,12 +298,6 @@ func UnmarshalPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
// Uncompressed
|
|
|
expectedLen = 65
|
|
|
curve = Curve_P256
|
|
|
- case X25519MLKEM1024PublicKeyBanner:
|
|
|
- expectedLen = x25519KeySize + MLKEM1024PublicKeySize
|
|
|
- curve = Curve_CURVE25519
|
|
|
- case P256MLKEM1024PublicKeyBanner:
|
|
|
- expectedLen = p256PublicKeySize + MLKEM1024PublicKeySize
|
|
|
- curve = Curve_P256
|
|
|
default:
|
|
|
return nil, r, 0, fmt.Errorf("bytes did not contain a proper public key banner")
|
|
|
}
|
|
|
@@ -242,6 +315,12 @@ func UnmarshalSigningPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
if k == nil {
|
|
|
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
|
|
|
}
|
|
|
+ if curve, ok, err := unmarshalPQKeyBlock(k, pqSigningPublicKey); ok {
|
|
|
+ if err != nil {
|
|
|
+ return nil, r, 0, err
|
|
|
+ }
|
|
|
+ return k.Bytes, r, curve, nil
|
|
|
+ }
|
|
|
var expectedLen int
|
|
|
var curve Curve
|
|
|
switch k.Type {
|
|
|
@@ -252,12 +331,6 @@ func UnmarshalSigningPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
// Uncompressed
|
|
|
expectedLen = 65
|
|
|
curve = Curve_P256
|
|
|
- case Ed25519MLDSA87PublicKeyBanner:
|
|
|
- expectedLen = ed25519PublicKeySize + MLDSA87PublicKeySize
|
|
|
- curve = Curve_CURVE25519
|
|
|
- case ECDSAP256MLDSA87PublicKeyBanner:
|
|
|
- expectedLen = ecdsaP256PublicKeySize + MLDSA87PublicKeySize
|
|
|
- curve = Curve_P256
|
|
|
default:
|
|
|
return nil, r, 0, fmt.Errorf("bytes did not contain a proper Ed25519/ECDSA public key banner")
|
|
|
}
|
|
|
@@ -268,13 +341,6 @@ func UnmarshalSigningPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
}
|
|
|
|
|
|
func MarshalPrivateKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
- pq := len(b) > MLKEM1024PrivateKeySize
|
|
|
- switch {
|
|
|
- case pq && curve == Curve_CURVE25519:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: X25519MLKEM1024PrivateKeyBanner, Bytes: b})
|
|
|
- case pq && curve == Curve_P256:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: P256MLKEM1024PrivateKeyBanner, Bytes: b})
|
|
|
- }
|
|
|
switch curve {
|
|
|
case Curve_CURVE25519:
|
|
|
return pem.EncodeToMemory(&pem.Block{Type: X25519PrivateKeyBanner, Bytes: b})
|
|
|
@@ -285,14 +351,19 @@ func MarshalPrivateKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// MarshalPQPrivateKeyToPEM returns a PEM representation of a composite host private key of suite, the classical ECDH
|
|
|
+// key followed by the seed of the suite's KEM, or nil if b is not one.
|
|
|
+func MarshalPQPrivateKeyToPEM(curve Curve, suite PQSuite, b []byte) []byte {
|
|
|
+ return marshalPQKeyToPEM(pqPrivateKey, curve, suite, b)
|
|
|
+}
|
|
|
+
|
|
|
+// MarshalPQSigningPrivateKeyToPEM returns a PEM representation of a composite CA private key of suite, the classical
|
|
|
+// signing key followed by the seed of the suite's signature algorithm, or nil if b is not one.
|
|
|
+func MarshalPQSigningPrivateKeyToPEM(curve Curve, suite PQSuite, b []byte) []byte {
|
|
|
+ return marshalPQKeyToPEM(pqSigningPrivateKey, curve, suite, b)
|
|
|
+}
|
|
|
+
|
|
|
func MarshalSigningPrivateKeyToPEM(curve Curve, b []byte) []byte {
|
|
|
- pq := len(b) == signingPrivateKeySize(curve)+MLDSA87PrivateKeySize
|
|
|
- switch {
|
|
|
- case pq && curve == Curve_CURVE25519:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: Ed25519MLDSA87PrivateKeyBanner, Bytes: b})
|
|
|
- case pq && curve == Curve_P256:
|
|
|
- return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256MLDSA87PrivateKeyBanner, Bytes: b})
|
|
|
- }
|
|
|
switch curve {
|
|
|
case Curve_CURVE25519:
|
|
|
return pem.EncodeToMemory(&pem.Block{Type: Ed25519PrivateKeyBanner, Bytes: b})
|
|
|
@@ -310,6 +381,12 @@ func UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
if k == nil {
|
|
|
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
|
|
|
}
|
|
|
+ if curve, ok, err := unmarshalPQKeyBlock(k, pqPrivateKey); ok {
|
|
|
+ if err != nil {
|
|
|
+ return nil, r, 0, err
|
|
|
+ }
|
|
|
+ return k.Bytes, r, curve, nil
|
|
|
+ }
|
|
|
var expectedLen int
|
|
|
var curve Curve
|
|
|
switch k.Type {
|
|
|
@@ -319,12 +396,6 @@ func UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
|
|
|
case P256PrivateKeyBanner:
|
|
|
expectedLen = 32
|
|
|
curve = Curve_P256
|
|
|
- case X25519MLKEM1024PrivateKeyBanner:
|
|
|
- expectedLen = x25519KeySize + MLKEM1024PrivateKeySize
|
|
|
- curve = Curve_CURVE25519
|
|
|
- case P256MLKEM1024PrivateKeyBanner:
|
|
|
- expectedLen = p256PrivateKeySize + MLKEM1024PrivateKeySize
|
|
|
- curve = Curve_P256
|
|
|
default:
|
|
|
return nil, r, 0, fmt.Errorf("bytes did not contain a proper private key banner")
|
|
|
}
|
|
|
@@ -339,24 +410,21 @@ func UnmarshalSigningPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error)
|
|
|
if k == nil {
|
|
|
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
|
|
|
}
|
|
|
+ if curve, ok, err := unmarshalPQKeyBlock(k, pqSigningPrivateKey, pqEncryptedSigningPrivateKey); ok {
|
|
|
+ switch {
|
|
|
+ case err != nil:
|
|
|
+ return nil, r, 0, err
|
|
|
+ case pqKeyBanners[k.Type].kind == pqEncryptedSigningPrivateKey:
|
|
|
+ return nil, nil, curve, ErrPrivateKeyEncrypted
|
|
|
+ }
|
|
|
+ return k.Bytes, r, curve, nil
|
|
|
+ }
|
|
|
var curve Curve
|
|
|
switch k.Type {
|
|
|
case EncryptedEd25519PrivateKeyBanner:
|
|
|
return nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted
|
|
|
- case EncryptedECDSAP256PrivateKeyBanner, EncryptedECDSAP256MLDSA87PrivateKeyBanner:
|
|
|
+ case EncryptedECDSAP256PrivateKeyBanner:
|
|
|
return nil, nil, Curve_P256, ErrPrivateKeyEncrypted
|
|
|
- case EncryptedEd25519MLDSA87PrivateKeyBanner:
|
|
|
- return nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted
|
|
|
- case Ed25519MLDSA87PrivateKeyBanner:
|
|
|
- curve = Curve_CURVE25519
|
|
|
- if len(k.Bytes) != ed25519PrivateKeySize+MLDSA87PrivateKeySize {
|
|
|
- return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid Ed25519 ML-DSA-87 private key", ed25519PrivateKeySize+MLDSA87PrivateKeySize)
|
|
|
- }
|
|
|
- case ECDSAP256MLDSA87PrivateKeyBanner:
|
|
|
- curve = Curve_P256
|
|
|
- if len(k.Bytes) != p256PrivateKeySize+MLDSA87PrivateKeySize {
|
|
|
- return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid ECDSA P256 ML-DSA-87 private key", p256PrivateKeySize+MLDSA87PrivateKeySize)
|
|
|
- }
|
|
|
case Ed25519PrivateKeyBanner:
|
|
|
curve = Curve_CURVE25519
|
|
|
if len(k.Bytes) != ed25519.PrivateKeySize {
|