cert: Name the post-quantum algorithms of a version 3 certificate with a PQSuite, like its curve, and pick the hybrid subtype by the suite's KEM

This commit is contained in:
JackDoan committed 2026-10-14 14:40:52 -05:00
1 parent 84e28c62ea
commit 656164b259
29 files changed
+1124 -365

No files matched your search

+22 -8
View File
@@ -37,6 +37,7 @@ type caFlags struct {
argonParallelism *uint
encryption *bool
version *uint
pqSuite *string
curve *string
p11url *string
@@ -71,7 +72,8 @@ func newCaFlags() *caFlags {
cf := caFlags{set: flag.NewFlagSet("ca", flag.ContinueOnError)}
cf.set.Usage = func() {}
cf.name = cf.set.String("name", "", "Required: name of the certificate authority")
cf.version = cf.set.Uint("version", uint(cert.Version2), "Optional: version of the certificate format to use. Version 3 adds an ML-DSA-87 key, for post-quantum certificates")
cf.version = cf.set.Uint("version", uint(cert.Version2), "Optional: version of the certificate format to use. Version 3 adds a post-quantum key, for post-quantum certificates")
cf.pqSuite = pqSuiteFlag(cf.set, "Optional: post-quantum suite of a version 3 CA, which the certificates it signs share")
cf.duration = cf.set.Duration("duration", time.Duration(time.Hour*8760), "Optional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"")
cf.outKeyPath = cf.set.String("out-key", "ca.key", "Optional: path to write the private key to")
cf.outCertPath = cf.set.String("out-crt", "ca.crt", "Optional: path to write the certificate to")
@@ -164,7 +166,11 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
return newHelpErrorf("-version must be %v, %v or %v", cert.Version1, cert.Version2, cert.Version3)
}
if version == cert.Version3 && isP11 {
return newHelpErrorf("-version 3 can not be used with -pkcs11, the ML-DSA-87 key has to be in out-key")
return newHelpErrorf("-version 3 can not be used with -pkcs11, the post-quantum key has to be in out-key")
}
suite, err := parsePQSuiteFlag(cf.set, *cf.pqSuite, version == cert.Version3, "is only for -version 3")
if err != nil {
return err
}
var networks []netip.Prefix
@@ -300,7 +306,7 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
}
if version == cert.Version3 {
pub, rawPriv, err = appendPQSigningKey(pub, rawPriv)
pub, rawPriv, err = appendPQSigningKey(suite, pub, rawPriv)
if err != nil {
return err
}
@@ -319,6 +325,9 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
IsCA: true,
Curve: curve,
}
if version == cert.Version3 {
t.PQSuite = suite
}
if !isP11 && !isStdio(*cf.outKeyPath) {
if _, err := os.Stat(*cf.outKeyPath); err == nil {
@@ -346,14 +355,19 @@ func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error
return fmt.Errorf("error while signing: %s", err)
}
if *cf.encryption {
switch {
case *cf.encryption && version == cert.Version3:
b, err = cert.EncryptAndMarshalPQSigningPrivateKey(curve, suite, rawPriv, passphrase, kdfParams)
case *cf.encryption:
b, err = cert.EncryptAndMarshalSigningPrivateKey(curve, rawPriv, passphrase, kdfParams)
if err != nil {
return fmt.Errorf("error while encrypting out-key: %s", err)
}
} else {
case version == cert.Version3:
b = cert.MarshalPQSigningPrivateKeyToPEM(curve, suite, rawPriv)
default:
b = cert.MarshalSigningPrivateKeyToPEM(curve, rawPriv)
}
if err != nil {
return fmt.Errorf("error while encrypting out-key: %s", err)
}
err = writeOutput(*cf.outKeyPath, b, 0600, out)
if err != nil {
+3 -1
View File
@@ -68,12 +68,14 @@ func Test_caHelp(t *testing.T) {
" -out-qr string\n"+
" \tOptional: output a qr code image (png) of the certificate\n"+
optionalPkcs11String(" -pkcs11 string\n \tOptional: PKCS#11 URI to an existing private key\n")+
" -pq-suite string\n"+
" \tOptional: post-quantum suite of a version 3 CA, which the certificates it signs share (MLKEM1024_MLDSA87) (default \"MLKEM1024_MLDSA87\")\n"+
" -subnets string\n"+
" \tDeprecated, see -unsafe-networks\n"+
" -unsafe-networks string\n"+
" \tOptional: comma separated list of ip address and network in CIDR notation. This will limit which ip addresses and networks subordinate certs can use in unsafe networks\n"+
" -version uint\n"+
" \tOptional: version of the certificate format to use. Version 3 adds an ML-DSA-87 key, for post-quantum certificates (default 2)\n",
" \tOptional: version of the certificate format to use. Version 3 adds a post-quantum key, for post-quantum certificates (default 2)\n",
ob.String(),
)
}
+20 -6
View File
@@ -20,6 +20,7 @@ type keygenFlags struct {
inKeyPath *string
curve *string
version *uint
pqSuite *string
p11url *string
}
@@ -29,8 +30,9 @@ func newKeygenFlags() *keygenFlags {
cf.outPubPath = cf.set.String("out-pub", "", "Required: path to write the public key to")
cf.outKeyPath = cf.set.String("out-key", "", "Required: path to write the private key to")
cf.curve = cf.set.String("curve", defaultCurve(), "ECDH Curve (25519, P256)")
cf.version = cf.set.Uint("version", 0, "Optional: 3 adds an ML-KEM-1024 key, for version 3 certificates")
cf.inKeyPath = cf.set.String("in-key", "", "Optional: path to an existing private key to add an ML-KEM-1024 key to, keeping the existing key. Implies -version 3")
cf.version = cf.set.Uint("version", 0, "Optional: 3 adds a post-quantum key, for version 3 certificates")
cf.inKeyPath = cf.set.String("in-key", "", "Optional: path to an existing private key to add a post-quantum key to, keeping the existing key. Implies -version 3")
cf.pqSuite = pqSuiteFlag(cf.set, "Optional: post-quantum suite of a version 3 key, which must be the suite of the CA that signs it")
cf.p11url = p11Flag(cf.set)
return &cf
}
@@ -63,7 +65,11 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error {
return newHelpErrorf("-version must be %v, or left out for keys that work with any other version", cert.Version3)
}
if version == cert.Version3 && isP11 {
return newHelpErrorf("-version 3 can not be used with -pkcs11, the ML-KEM-1024 key has to be in out-key")
return newHelpErrorf("-version 3 can not be used with -pkcs11, the post-quantum key has to be in out-key")
}
suite, err := parsePQSuiteFlag(cf.set, *cf.pqSuite, version == cert.Version3, "is only for -version 3")
if err != nil {
return err
}
var claims ioClaims
@@ -113,7 +119,7 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error {
}
if version == cert.Version3 {
pub, rawPriv, err = appendPQKey(pub, rawPriv)
pub, rawPriv, err = appendPQKey(suite, pub, rawPriv)
if err != nil {
return err
}
@@ -139,12 +145,20 @@ func keygen(args []string, out io.Writer, errOut io.Writer) error {
return fmt.Errorf("error while getting public key: %w", err)
}
} else {
err = writeOutput(*cf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600, out)
b := cert.MarshalPrivateKeyToPEM(curve, rawPriv)
if version == cert.Version3 {
b = cert.MarshalPQPrivateKeyToPEM(curve, suite, rawPriv)
}
err = writeOutput(*cf.outKeyPath, b, 0600, out)
if err != nil {
return fmt.Errorf("error while writing out-key: %s", err)
}
}
err = writeOutput(*cf.outPubPath, cert.MarshalPublicKeyToPEM(curve, pub), 0600, out)
b := cert.MarshalPublicKeyToPEM(curve, pub)
if version == cert.Version3 {
b = cert.MarshalPQPublicKeyToPEM(curve, suite, pub)
}
err = writeOutput(*cf.outPubPath, b, 0600, out)
if err != nil {
return fmt.Errorf("error while writing out-pub: %s", err)
}
+4 -2
View File
@@ -24,14 +24,16 @@ func Test_keygenHelp(t *testing.T) {
" -curve string\n"+
" \tECDH Curve (25519, P256) (default \"25519\")\n"+
" -in-key string\n"+
" \tOptional: path to an existing private key to add an ML-KEM-1024 key to, keeping the existing key. Implies -version 3\n"+
" \tOptional: path to an existing private key to add a post-quantum key to, keeping the existing key. Implies -version 3\n"+
" -out-key string\n"+
" \tRequired: path to write the private key to\n"+
" -out-pub string\n"+
" \tRequired: path to write the public key to\n"+
optionalPkcs11String(" -pkcs11 string\n \tOptional: PKCS#11 URI to an existing private key\n")+
" -pq-suite string\n"+
" \tOptional: post-quantum suite of a version 3 key, which must be the suite of the CA that signs it (MLKEM1024_MLDSA87) (default \"MLKEM1024_MLDSA87\")\n"+
" -version uint\n"+
" \tOptional: 3 adds an ML-KEM-1024 key, for version 3 certificates\n",
" \tOptional: 3 adds a post-quantum key, for version 3 certificates\n",
ob.String(),
)
}
+68 -29
View File
@@ -3,33 +3,60 @@ package main
import (
"bytes"
"crypto/ecdh"
"crypto/mldsa"
"crypto/mlkem"
"encoding/pem"
"flag"
"fmt"
"io"
"os"
"strings"
"github.com/slackhq/nebula/cert"
)
// appendPQSigningKey makes a classical CA keypair into a version 3 composite one with a fresh ML-DSA-87 key.
func appendPQSigningKey(pub, priv []byte) ([]byte, []byte, error) {
sk, err := mldsa.GenerateKey(mldsa.MLDSA87())
if err != nil {
return nil, nil, fmt.Errorf("error while generating ML-DSA-87 key: %s", err)
// pqSuiteFlag adds -pq-suite to a flag set.
func pqSuiteFlag(set *flag.FlagSet, usage string) *string {
var names []string
for _, s := range cert.PQSuites() {
names = append(names, s.String())
}
return append(pub, sk.PublicKey().Bytes()...), append(priv, sk.Bytes()...), nil
return set.String("pq-suite", cert.DefaultPQSuite.String(), usage+" ("+strings.Join(names, ", ")+")")
}
// appendPQKey makes a classical host keypair into a version 3 composite one with a fresh ML-KEM-1024 key.
func appendPQKey(pub, priv []byte) ([]byte, []byte, error) {
dk, err := mlkem.GenerateKey1024()
if err != nil {
return nil, nil, fmt.Errorf("error while generating ML-KEM-1024 key: %s", err)
// parsePQSuiteFlag returns the suite -pq-suite names. Setting it is an error unless allowed, when the suite has no use
// or comes from elsewhere.
func parsePQSuiteFlag(set *flag.FlagSet, value string, allowed bool, why string) (cert.PQSuite, error) {
given := false
set.Visit(func(f *flag.Flag) {
if f.Name == "pq-suite" {
given = true
}
})
if given && !allowed {
return cert.PQSuiteUnknown, newHelpErrorf("-pq-suite %s", why)
}
return append(pub, dk.EncapsulationKey().Bytes()...), append(priv, dk.Bytes()...), nil
s, err := cert.ParsePQSuite(value)
if err != nil {
return cert.PQSuiteUnknown, newHelpErrorf("-pq-suite: %s", err)
}
return s, nil
}
// appendPQSigningKey makes a classical CA keypair into a version 3 composite one with a fresh key of suite.
func appendPQSigningKey(suite cert.PQSuite, pub, priv []byte) ([]byte, []byte, error) {
pqPub, pqPriv, err := suite.GenerateKey(true)
if err != nil {
return nil, nil, fmt.Errorf("error while generating %v CA key: %s", suite, err)
}
return append(pub, pqPub...), append(priv, pqPriv...), nil
}
// appendPQKey makes a classical host keypair into a version 3 composite one with a fresh key of suite.
func appendPQKey(suite cert.PQSuite, pub, priv []byte) ([]byte, []byte, error) {
pqPub, pqPriv, err := suite.GenerateKey(false)
if err != nil {
return nil, nil, fmt.Errorf("error while generating %v key: %s", suite, err)
}
return append(pub, pqPub...), append(priv, pqPriv...), nil
}
// publicKeyFor returns the public key of a classical host private key.
@@ -45,7 +72,7 @@ func publicKeyFor(curve cert.Curve, priv []byte) ([]byte, error) {
}
k, err := c.NewPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("not a classical %s private key, it may already have an ML-KEM-1024 key", curve)
return nil, fmt.Errorf("not a classical %s private key, it may already have a post-quantum key", curve)
}
return k.PublicKey().Bytes(), nil
}
@@ -58,6 +85,7 @@ type pqExtendFlags struct {
inPubPath *string
outCertPath *string
outKeyPath *string
pqSuite *string
}
func newPQExtendFlags() *pqExtendFlags {
@@ -69,6 +97,7 @@ func newPQExtendFlags() *pqExtendFlags {
pf.caCertPath = pf.set.String("ca-crt", "", "Required for a host in-crt: path to the version 3 form of the CA that issued it")
pf.inPubPath = pf.set.String("in-pub", "", "Required for a host in-crt: path to the host's public key from keygen -in-key")
pf.outKeyPath = pf.set.String("out-key", "", "Required for a CA in-crt: path to write the CA's version 3 key to, encrypted again if ca-key was")
pf.pqSuite = pqSuiteFlag(pf.set, "Optional, for a CA in-crt: post-quantum suite of the version 3 CA. A host takes its CA's suite")
return &pf
}
@@ -134,9 +163,14 @@ func pqExtend(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return err
}
suite, err := parsePQSuiteFlag(pf.set, *pf.pqSuite, c.IsCA(), "is for CA certificates, a host takes its CA's suite")
if err != nil {
return err
}
var c3 cert.Certificate
if c.IsCA() {
c3, err = extendCA(pf, c, curve, caKey, rawCAKey, passphrase, out)
c3, err = extendCA(pf, c, suite, curve, caKey, rawCAKey, passphrase, out)
} else {
c3, err = extendHost(pf, c, curve, caKey, &claims)
}
@@ -154,8 +188,8 @@ func pqExtend(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
return nil
}
// extendCA gives a v2 CA an ML-DSA-87 key and writes out the composite key.
func extendCA(pf *pqExtendFlags, ca cert.Certificate, curve cert.Curve, key, rawKey, passphrase []byte, out io.Writer) (cert.Certificate, error) {
// extendCA gives a v2 CA a post-quantum key of suite and writes out the composite key.
func extendCA(pf *pqExtendFlags, ca cert.Certificate, suite cert.PQSuite, curve cert.Curve, key, rawKey, passphrase []byte, out io.Writer) (cert.Certificate, error) {
if *pf.caCertPath != "" || *pf.inPubPath != "" {
return nil, newHelpErrorf("-ca-crt and -in-pub are for host certificates, a CA extends itself")
}
@@ -166,15 +200,15 @@ func extendCA(pf *pqExtendFlags, ca cert.Certificate, curve cert.Curve, key, raw
return nil, fmt.Errorf("refusing to extend, the CA certificate does not match ca-key")
}
pub, priv, err := appendPQSigningKey(nil, append([]byte(nil), key...))
pub, priv, err := appendPQSigningKey(suite, nil, append([]byte(nil), key...))
if err != nil {
return nil, err
}
signPQ, err := cert.SignPQ(priv[len(key):])
signPQ, err := cert.SignPQ(suite, priv[len(key):])
if err != nil {
return nil, err
}
c3, err := cert.ExtendToV3(ca, pub, nil, signPQ)
c3, err := cert.ExtendToV3(ca, suite, pub, nil, signPQ)
if err != nil {
return nil, fmt.Errorf("error while extending: %s", err)
}
@@ -192,12 +226,12 @@ func extendCA(pf *pqExtendFlags, ca cert.Certificate, curve cert.Curve, key, raw
}
a := ned.EncryptionMetadata.Argon2Parameters
kdf := cert.NewArgon2Parameters(a.Memory, a.Parallelism, a.Iterations)
b, err = cert.EncryptAndMarshalSigningPrivateKey(curve, priv, passphrase, kdf)
b, err = cert.EncryptAndMarshalPQSigningPrivateKey(curve, suite, priv, passphrase, kdf)
if err != nil {
return nil, fmt.Errorf("error while encrypting out-key: %s", err)
}
} else {
b = cert.MarshalSigningPrivateKeyToPEM(curve, priv)
b = cert.MarshalPQSigningPrivateKeyToPEM(curve, suite, priv)
}
if err := writeOutput(*pf.outKeyPath, b, 0600, out); err != nil {
return nil, fmt.Errorf("error while writing out-key: %s", err)
@@ -205,7 +239,7 @@ func extendCA(pf *pqExtendFlags, ca cert.Certificate, curve cert.Curve, key, raw
return c3, nil
}
// extendHost adds a host's ML-KEM-1024 key to its certificate, signed by the v3 form of its CA.
// extendHost adds a host's post-quantum key to its certificate, signed by the v3 form of its CA, in the CA's suite.
func extendHost(pf *pqExtendFlags, c cert.Certificate, curve cert.Curve, caKey []byte, claims *ioClaims) (cert.Certificate, error) {
if *pf.outKeyPath != "" {
return nil, newHelpErrorf("-out-key is for CA certificates, a host's key comes from keygen -in-key")
@@ -240,19 +274,24 @@ func extendHost(pf *pqExtendFlags, c cert.Certificate, curve cert.Curve, caKey [
if err != nil {
return nil, fmt.Errorf("error while parsing in-pub: %s", err)
}
if pubCurve != c.Curve() || len(pub) <= cert.MLKEM1024PublicKeySize {
return nil, fmt.Errorf("in-pub must be the host's key from keygen -in-key")
suite := cert.PQSuiteOf(ca)
classical, pq, err := suite.SplitPublicKey(false, pub)
if pubCurve != c.Curve() || err != nil {
return nil, fmt.Errorf("in-pub must be the host's key from keygen -in-key -pq-suite %v", suite)
}
classical, pq := pub[:len(pub)-cert.MLKEM1024PublicKeySize], pub[len(pub)-cert.MLKEM1024PublicKeySize:]
if !bytes.Equal(classical, c.PublicKey()) {
return nil, fmt.Errorf("in-pub does not extend the key in in-crt")
}
signPQ, err := cert.SignPQ(caKey[len(caKey)-cert.MLDSA87PrivateKeySize:])
_, pqCAKey, err := suite.SplitPrivateKey(true, caKey)
if err != nil {
return nil, err
}
c3, err := cert.ExtendToV3(c, pq, ca, signPQ)
signPQ, err := cert.SignPQ(suite, pqCAKey)
if err != nil {
return nil, err
}
c3, err := cert.ExtendToV3(c, suite, pq, ca, signPQ)
if err != nil {
return nil, fmt.Errorf("error while extending: %s", err)
}
+34 -3
View File
@@ -100,9 +100,40 @@ func TestPQ_SignErrors(t *testing.T) {
require.NoError(t, ca([]string{"-version", "3", "-name", "v3 ca", "-out-crt", p("ca3.crt"), "-out-key", p("ca3.key")}, ob, eb, nopw))
require.ErrorContains(t, signCert([]string{"-ca-crt", p("ca3.crt"), "-ca-key", p("ca3.key"), "-name", "h", "-networks", "10.1.0.1/16", "-in-pub", p("k.pub"), "-out-crt", p("h3.crt")}, ob, eb, nopw), "need an in-pub from keygen -version 3")
// keygen -in-key refuses a key that already has an ML-KEM key.
// keygen -in-key refuses a key that already has a post-quantum key.
require.NoError(t, keygen([]string{"-version", "3", "-out-key", p("k3.key"), "-out-pub", p("k3.pub")}, ob, eb))
require.ErrorContains(t, keygen([]string{"-in-key", p("k3.key"), "-out-key", p("k33.key"), "-out-pub", p("k33.pub")}, ob, eb), "already have an ML-KEM-1024 key")
require.ErrorContains(t, keygen([]string{"-in-key", p("k3.key"), "-out-key", p("k33.key"), "-out-pub", p("k33.pub")}, ob, eb), "already have a post-quantum key")
}
func TestPQ_SuiteFlag(t *testing.T) {
dir := t.TempDir()
ob, eb := &bytes.Buffer{}, &bytes.Buffer{}
nopw := &StubPasswordReader{}
p := func(n string) string { return filepath.Join(dir, n) }
// The suite can be named, case aside, and the certificates a CA signs share its suite.
require.NoError(t, ca([]string{"-version", "3", "-pq-suite", "mlkem1024_mldsa87", "-name", "ca", "-out-crt", p("ca.crt"), "-out-key", p("ca.key")}, ob, eb, nopw))
assert.Equal(t, cert.PQSuite_MLKEM1024_MLDSA87, cert.PQSuiteOf(readCert(t, p("ca.crt"))))
require.NoError(t, keygen([]string{"-version", "3", "-pq-suite", "MLKEM1024_MLDSA87", "-out-key", p("k.key"), "-out-pub", p("k.pub")}, ob, eb))
require.NoError(t, signCert([]string{"-ca-crt", p("ca.crt"), "-ca-key", p("ca.key"), "-name", "h", "-networks", "10.1.0.1/16", "-in-pub", p("k.pub"), "-out-crt", p("h.crt")}, ob, eb, nopw))
assert.Equal(t, cert.PQSuite_MLKEM1024_MLDSA87, cert.PQSuiteOf(readCert(t, p("h.crt"))))
// Printing shows it.
ob.Reset()
require.NoError(t, printCert([]string{"-json", "-path", p("h.crt")}, ob, eb))
assert.Contains(t, ob.String(), `"pqSuite":"MLKEM1024_MLDSA87"`)
// Unknown suites, and suites where there is no post-quantum key, are refused.
require.ErrorContains(t, ca([]string{"-version", "3", "-pq-suite", "mlkem768_mldsa65", "-name", "x", "-out-crt", p("x.crt"), "-out-key", p("x.key")}, ob, eb, nopw), "unknown post-quantum suite")
require.ErrorContains(t, ca([]string{"-pq-suite", "MLKEM1024_MLDSA87", "-name", "x", "-out-crt", p("x.crt"), "-out-key", p("x.key")}, ob, eb, nopw), "only for -version 3")
require.ErrorContains(t, keygen([]string{"-pq-suite", "MLKEM1024_MLDSA87", "-out-key", p("x.key"), "-out-pub", p("x.pub")}, ob, eb), "only for -version 3")
// A host takes its CA's suite when extended.
require.NoError(t, ca([]string{"-name", "ca2", "-out-crt", p("ca2.crt"), "-out-key", p("ca2.key")}, ob, eb, nopw))
require.NoError(t, signCert([]string{"-ca-crt", p("ca2.crt"), "-ca-key", p("ca2.key"), "-name", "h2", "-networks", "10.1.0.2/16", "-out-crt", p("h2.crt"), "-out-key", p("h2.key")}, ob, eb, nopw))
require.NoError(t, pqExtend([]string{"-in-crt", p("ca2.crt"), "-ca-key", p("ca2.key"), "-pq-suite", "MLKEM1024_MLDSA87", "-out-crt", p("ca23.crt"), "-out-key", p("ca23.key")}, ob, eb, nopw))
require.NoError(t, keygen([]string{"-in-key", p("h2.key"), "-out-key", p("h23.key"), "-out-pub", p("h23.pub")}, ob, eb))
require.ErrorContains(t, pqExtend([]string{"-in-crt", p("h2.crt"), "-in-pub", p("h23.pub"), "-ca-crt", p("ca23.crt"), "-ca-key", p("ca23.key"), "-pq-suite", "MLKEM1024_MLDSA87", "-out-crt", p("h23.crt")}, ob, eb, nopw), "a host takes its CA's suite")
}
// TestPQ_Extend upgrades a v2 CA and a host in place, the way a running network migrates.
@@ -145,7 +176,7 @@ func TestPQ_Extend(t *testing.T) {
require.ErrorIs(t, err, cert.ErrPrivateKeyEncrypted, "an encrypted ca-key stays encrypted")
}
// The host gets an ML-KEM key on top of the one it has, and the CA signs it.
// The host gets a post-quantum key on top of the one it has, and the CA signs it.
require.NoError(t, keygen([]string{"-in-key", p("host.key"), "-out-key", p("host3.key"), "-out-pub", p("host3.pub")}, ob, eb))
require.NoError(t, pqExtend([]string{"-in-crt", p("host.crt"), "-in-pub", p("host3.pub"), "-ca-crt", p("ca3.crt"), "-ca-key", p("ca3.key"), "-out-crt", p("host3.crt")}, ob, eb, pw))
host3 := readCert(t, p("host3.crt"))
+19 -6
View File
@@ -169,16 +169,20 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
version = caCert.Version()
}
// A version 3 certificate is in the suite of its CA.
suite := cert.PQSuiteOf(caCert)
switch {
case version == cert.Version3 && caCert.Version() != cert.Version3:
return newHelpErrorf("version 3 certificates must be signed by a version 3 CA")
case version == cert.Version3 && isP11:
return newHelpErrorf("version 3 certificates can not be signed with -pkcs11, the ML-DSA-87 key has to be in ca-key")
return newHelpErrorf("version 3 certificates can not be signed with -pkcs11, the post-quantum key has to be in ca-key")
case version != cert.Version3 && caCert.Version() == cert.Version3:
// A v3 CA signs anything older as its v2 form, with the classical half of its key.
caCert = cert.V2Form(caCert)
if caKey != nil {
caKey = caKey[:len(caKey)-cert.MLDSA87PrivateKeySize]
if caKey, _, err = suite.SplitPrivateKey(true, caKey); err != nil {
return fmt.Errorf("error while parsing ca-key: %s", err)
}
}
}
@@ -272,12 +276,16 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
if pubCurve != curve {
return fmt.Errorf("curve of in-pub does not match ca")
}
if isPQ := len(pub) > cert.MLKEM1024PublicKeySize; isPQ != (version == cert.Version3) {
// Composite keys are longer than any classical one, an uncompressed P256 key being the longest.
if isPQ := len(pub) > 65; isPQ != (version == cert.Version3) {
if isPQ {
return fmt.Errorf("in-pub has an ML-KEM-1024 key, which only version 3 certificates carry")
return fmt.Errorf("in-pub has a post-quantum key, which only version 3 certificates carry")
}
return fmt.Errorf("version 3 certificates need an in-pub from keygen -version 3")
}
if _, _, err := suite.SplitPublicKey(false, pub); version == cert.Version3 && err != nil {
return fmt.Errorf("in-pub is not a key for the CA's post-quantum suite, %v", suite)
}
} else if isP11 {
pub, err = p11Client.GetPubKey()
if err != nil {
@@ -286,7 +294,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
} else {
pub, rawPriv = newKeypair(curve)
if version == cert.Version3 {
pub, rawPriv, err = appendPQKey(pub, rawPriv)
pub, rawPriv, err = appendPQKey(suite, pub, rawPriv)
if err != nil {
return err
}
@@ -388,6 +396,7 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
PublicKey: pub,
IsCA: false,
Curve: curve,
PQSuite: suite,
}
nc, err := t.Sign(caCert, curve, caKey)
@@ -408,7 +417,11 @@ func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader)
}
}
err = writeOutput(*sf.outKeyPath, cert.MarshalPrivateKeyToPEM(curve, rawPriv), 0600, out)
b := cert.MarshalPrivateKeyToPEM(curve, rawPriv)
if version == cert.Version3 {
b = cert.MarshalPQPrivateKeyToPEM(curve, suite, rawPriv)
}
err = writeOutput(*sf.outKeyPath, b, 0600, out)
if err != nil {
return fmt.Errorf("error while writing out-key: %s", err)
}