Skip to content

Commit 57214b4

Browse files
authored
mtpki: use ML-DSA (#8803)
genmtpki now outputs a ML-DSA certificate and private key. Our various loading functions had some type switches that accounted for RSA and ECDSA, but errored on ML-DSA keys. Factored out those functions into versions for go1.27 and for !go1.27. As a quality of life improvement, add a `-output-dir` flag for genmtpki to allow writing it in different places while iterating. Fixes #8787
1 parent b72b108 commit 57214b4

12 files changed

Lines changed: 255 additions & 78 deletions

File tree

cmd/boulder-ca/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func main() {
194194
}
195195

196196
issuer, err := issuance.LoadIssuer(issuerConfig, clk)
197-
cmd.FailOnError(err, "Loading issuer")
197+
cmd.FailOnError(err, fmt.Sprintf("Loading issuer %q", issuerConfig.Location.CertFile))
198198
issuers = append(issuers, issuer)
199199
}
200200

issuance/issuer.go

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ package issuance
22

33
import (
44
"crypto"
5-
"crypto/ecdsa"
6-
"crypto/elliptic"
7-
"crypto/rsa"
85
"crypto/x509"
96
"encoding/json"
107
"errors"
@@ -209,24 +206,9 @@ type Issuer struct {
209206
// newIssuer constructs a new Issuer from the in-memory certificate and signer.
210207
// It exists as a helper for LoadIssuer to make testing simpler.
211208
func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk clock.Clock) (*Issuer, error) {
212-
var keyAlg x509.PublicKeyAlgorithm
213-
var sigAlg x509.SignatureAlgorithm
214-
switch k := cert.PublicKey.(type) {
215-
case *rsa.PublicKey:
216-
keyAlg = x509.RSA
217-
sigAlg = x509.SHA256WithRSA
218-
case *ecdsa.PublicKey:
219-
keyAlg = x509.ECDSA
220-
switch k.Curve {
221-
case elliptic.P256():
222-
sigAlg = x509.ECDSAWithSHA256
223-
case elliptic.P384():
224-
sigAlg = x509.ECDSAWithSHA384
225-
default:
226-
return nil, fmt.Errorf("unsupported ECDSA curve: %q", k.Curve.Params().Name)
227-
}
228-
default:
229-
return nil, errors.New("unsupported issuer key type")
209+
keyAlg, sigAlg, err := pubkeyParams(cert.PublicKey)
210+
if err != nil {
211+
return nil, err
230212
}
231213

232214
if config.IssuerURL == "" {
@@ -274,8 +256,8 @@ func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk
274256
return i, nil
275257
}
276258

277-
// KeyType returns either x509.RSA or x509.ECDSA, depending on whether the
278-
// issuer has an RSA or ECDSA keypair. This is useful for determining which
259+
// KeyType returns x509.RSA, x509.ECDSA, or x509.MLDSA depending on the
260+
// keypair of the issuer. This is useful for determining which
279261
// issuance requests should be routed to this issuer.
280262
func (i *Issuer) KeyType() x509.PublicKeyAlgorithm {
281263
return i.keyAlg

issuance/pubkeyparams.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//go:build !go1.27
2+
3+
package issuance
4+
5+
import (
6+
"crypto/ecdsa"
7+
"crypto/elliptic"
8+
"crypto/rsa"
9+
"crypto/x509"
10+
"errors"
11+
"fmt"
12+
)
13+
14+
func pubkeyParams(pubkey any) (x509.PublicKeyAlgorithm, x509.SignatureAlgorithm, error) {
15+
switch k := pubkey.(type) {
16+
case *rsa.PublicKey:
17+
return x509.RSA, x509.SHA256WithRSA, nil
18+
case *ecdsa.PublicKey:
19+
switch k.Curve {
20+
case elliptic.P256():
21+
return x509.ECDSA, x509.ECDSAWithSHA256, nil
22+
case elliptic.P384():
23+
return x509.ECDSA, x509.ECDSAWithSHA384, nil
24+
default:
25+
return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm,
26+
fmt.Errorf("unsupported ECDSA curve: %q", k.Curve.Params().Name)
27+
}
28+
default:
29+
return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm,
30+
errors.New("unsupported issuer key type")
31+
}
32+
}

issuance/pubkeyparams_go127.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//go:build go1.27
2+
3+
package issuance
4+
5+
import (
6+
"crypto/ecdsa"
7+
"crypto/elliptic"
8+
"crypto/mldsa"
9+
"crypto/rsa"
10+
"crypto/x509"
11+
"errors"
12+
"fmt"
13+
)
14+
15+
// pubkeyParams returns a PublicKeyAlgorithm and SignatureAlgorithm for the input pubkey.
16+
// TODO(#8812): Move this back to issuer.go.
17+
func pubkeyParams(pubkey any) (x509.PublicKeyAlgorithm, x509.SignatureAlgorithm, error) {
18+
switch k := pubkey.(type) {
19+
case *mldsa.PublicKey:
20+
return x509.MLDSA, x509.MLDSA44, nil
21+
case *rsa.PublicKey:
22+
return x509.RSA, x509.SHA256WithRSA, nil
23+
case *ecdsa.PublicKey:
24+
switch k.Curve {
25+
case elliptic.P256():
26+
return x509.ECDSA, x509.ECDSAWithSHA256, nil
27+
case elliptic.P384():
28+
return x509.ECDSA, x509.ECDSAWithSHA384, nil
29+
default:
30+
return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm,
31+
fmt.Errorf("unsupported ECDSA curve: %q", k.Curve.Params().Name)
32+
}
33+
default:
34+
return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm,
35+
errors.New("unsupported issuer key type")
36+
}
37+
}

linter/linter.go

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package linter
33
import (
44
"bytes"
55
"crypto"
6-
"crypto/ecdsa"
76
"crypto/rand"
8-
"crypto/rsa"
97
"crypto/x509"
108
"fmt"
119
"strings"
@@ -136,26 +134,6 @@ func (l *Linter) CheckCRL(tbs *x509.RevocationList, reg lint.Registry) error {
136134
return ProcessResultSet(lintRes)
137135
}
138136

139-
func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
140-
var lintSigner crypto.Signer
141-
var err error
142-
switch k := realSigner.Public().(type) {
143-
case *rsa.PublicKey:
144-
lintSigner, err = rsa.GenerateKey(rand.Reader, k.Size()*8)
145-
if err != nil {
146-
return nil, fmt.Errorf("failed to create RSA lint signer: %w", err)
147-
}
148-
case *ecdsa.PublicKey:
149-
lintSigner, err = ecdsa.GenerateKey(k.Curve, rand.Reader)
150-
if err != nil {
151-
return nil, fmt.Errorf("failed to create ECDSA lint signer: %w", err)
152-
}
153-
default:
154-
return nil, fmt.Errorf("unsupported lint signer type: %T", k)
155-
}
156-
return lintSigner, nil
157-
}
158-
159137
func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.Certificate, error) {
160138
lintIssuerTBS := &x509.Certificate{
161139
// This is nearly the full list of attributes that

linter/makesigner.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:build !go1.27
2+
3+
package linter
4+
5+
import (
6+
"crypto"
7+
"crypto/ecdsa"
8+
"crypto/rand"
9+
"crypto/rsa"
10+
"fmt"
11+
)
12+
13+
func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
14+
var lintSigner crypto.Signer
15+
var err error
16+
switch k := realSigner.Public().(type) {
17+
case *rsa.PublicKey:
18+
lintSigner, err = rsa.GenerateKey(rand.Reader, k.Size()*8)
19+
if err != nil {
20+
return nil, fmt.Errorf("failed to create RSA lint signer: %w", err)
21+
}
22+
case *ecdsa.PublicKey:
23+
lintSigner, err = ecdsa.GenerateKey(k.Curve, rand.Reader)
24+
if err != nil {
25+
return nil, fmt.Errorf("failed to create ECDSA lint signer: %w", err)
26+
}
27+
default:
28+
return nil, fmt.Errorf("unsupported lint signer type: %T", k)
29+
}
30+
return lintSigner, nil
31+
}

linter/makesigner_go127.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//go:build go1.27
2+
3+
package linter
4+
5+
import (
6+
"crypto"
7+
"crypto/ecdsa"
8+
"crypto/mldsa"
9+
"crypto/rand"
10+
"crypto/rsa"
11+
"fmt"
12+
)
13+
14+
// makeSigner makes a signer with a throwaway key that matches `realSigner`'s type.
15+
//
16+
// TODO(#8812): Move this back to linter.go, above makeIssuer.
17+
func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
18+
var lintSigner crypto.Signer
19+
var err error
20+
switch k := realSigner.Public().(type) {
21+
case *rsa.PublicKey:
22+
lintSigner, err = rsa.GenerateKey(rand.Reader, k.Size()*8)
23+
if err != nil {
24+
return nil, fmt.Errorf("failed to create RSA lint signer: %w", err)
25+
}
26+
case *ecdsa.PublicKey:
27+
lintSigner, err = ecdsa.GenerateKey(k.Curve, rand.Reader)
28+
if err != nil {
29+
return nil, fmt.Errorf("failed to create ECDSA lint signer: %w", err)
30+
}
31+
case *mldsa.PublicKey:
32+
lintSigner, err = mldsa.GenerateKey(k.Parameters())
33+
if err != nil {
34+
return nil, fmt.Errorf("failed to create ML-DSA lint signer: %w", err)
35+
}
36+
default:
37+
return nil, fmt.Errorf("unsupported lint signer type: %T", k)
38+
}
39+
return lintSigner, nil
40+
}

privatekey/privatekey.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,29 +57,6 @@ func verifyECDSA(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey, msgHash has
5757
return privKey, privKey.Public(), nil
5858
}
5959

60-
// verify ensures that the embedded PublicKey of the provided privateKey is
61-
// actually a match for the private key. For an example of private keys
62-
// embedding a mismatched public key, see:
63-
// https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html.
64-
func verify(privateKey crypto.Signer) (crypto.Signer, crypto.PublicKey, error) {
65-
verifyHash, err := makeVerifyHash()
66-
if err != nil {
67-
return nil, nil, err
68-
}
69-
70-
switch k := privateKey.(type) {
71-
case *rsa.PrivateKey:
72-
return verifyRSA(k, &k.PublicKey, verifyHash)
73-
74-
case *ecdsa.PrivateKey:
75-
return verifyECDSA(k, &k.PublicKey, verifyHash)
76-
77-
default:
78-
// This should never happen.
79-
return nil, nil, errors.New("the provided private key could not be asserted to ECDSA or RSA")
80-
}
81-
}
82-
8360
// Load decodes and parses a private key from the provided file path and returns
8461
// the private key as crypto.Signer. keyPath is expected to be a PEM formatted
8562
// RSA or ECDSA private key in a PKCS #1, PKCS# 8, or SEC 1 container. The

privatekey/verify.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//go:build !go1.27
2+
3+
package privatekey
4+
5+
import (
6+
"crypto"
7+
"crypto/ecdsa"
8+
"crypto/rsa"
9+
"errors"
10+
)
11+
12+
// verify ensures that the embedded PublicKey of the provided privateKey is
13+
// actually a match for the private key. For an example of private keys
14+
// embedding a mismatched public key, see:
15+
// https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html.
16+
func verify(privateKey crypto.Signer) (crypto.Signer, crypto.PublicKey, error) {
17+
verifyHash, err := makeVerifyHash()
18+
if err != nil {
19+
return nil, nil, err
20+
}
21+
22+
switch k := privateKey.(type) {
23+
case *rsa.PrivateKey:
24+
return verifyRSA(k, &k.PublicKey, verifyHash)
25+
26+
case *ecdsa.PrivateKey:
27+
return verifyECDSA(k, &k.PublicKey, verifyHash)
28+
29+
default:
30+
// This should never happen.
31+
return nil, nil, errors.New("the provided private key could not be asserted to ECDSA or RSA")
32+
}
33+
}

privatekey/verify_go127.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//go:build go1.27
2+
3+
package privatekey
4+
5+
import (
6+
"crypto"
7+
"crypto/ecdsa"
8+
"crypto/mldsa"
9+
"crypto/rsa"
10+
"errors"
11+
"fmt"
12+
"hash"
13+
)
14+
15+
// verify ensures that the embedded PublicKey of the provided privateKey is
16+
// actually a match for the private key. For an example of private keys
17+
// embedding a mismatched public key, see:
18+
// https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html.
19+
//
20+
// TODO(#8812): move this back to privatekey.go, above Load().
21+
func verify(privateKey crypto.Signer) (crypto.Signer, crypto.PublicKey, error) {
22+
verifyHash, err := makeVerifyHash()
23+
if err != nil {
24+
return nil, nil, err
25+
}
26+
27+
switch k := privateKey.(type) {
28+
case *rsa.PrivateKey:
29+
return verifyRSA(k, &k.PublicKey, verifyHash)
30+
31+
case *ecdsa.PrivateKey:
32+
return verifyECDSA(k, &k.PublicKey, verifyHash)
33+
34+
case *mldsa.PrivateKey:
35+
return verifyMLDSA(k, k.PublicKey(), verifyHash)
36+
37+
default:
38+
// This should never happen.
39+
return nil, nil, errors.New("the provided private key was not *rsa.PrivateKey, *ecdsa.PrivateKey, or *mldsa.PrivateKey")
40+
}
41+
}
42+
43+
// verifyMLDSA verifies ML-DSA private keys.
44+
func verifyMLDSA(privKey *mldsa.PrivateKey, pubKey *mldsa.PublicKey, msgHash hash.Hash) (crypto.Signer, crypto.PublicKey, error) {
45+
sig, err := privKey.Sign(nil, msgHash.Sum(nil), nil)
46+
if err != nil {
47+
return nil, nil, fmt.Errorf("failed to sign using the provided ML-DSA private key: %s", err)
48+
}
49+
50+
err = mldsa.Verify(pubKey, msgHash.Sum(nil), sig, nil)
51+
if err != nil {
52+
return nil, nil, fmt.Errorf("the provided ML-DSA private key failed signature verification: %s", err)
53+
}
54+
return privKey, privKey.Public(), nil
55+
}

0 commit comments

Comments
 (0)