Skip to content

Commit 9a0778d

Browse files
izolightlukevalentaclaucece
authored
Add support for generating ed25519 keys and certs (#1061)
Co-authored-by: Luke Valenta <[email protected]> Co-authored-by: Sofía Celi <[email protected]>
1 parent c21e85d commit 9a0778d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2549
-23
lines changed

bundler/bundle.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bundler
33
import (
44
"bytes"
55
"crypto/ecdsa"
6+
"crypto/ed25519"
67
"crypto/rsa"
78
"crypto/x509"
89
"crypto/x509/pkix"
@@ -13,6 +14,7 @@ import (
1314
"time"
1415

1516
"github.com/cloudflare/cfssl/helpers"
17+
"github.com/cloudflare/cfssl/helpers/derhelpers"
1618
)
1719

1820
// A Bundle contains a certificate and its trust chain. It is intended
@@ -108,6 +110,8 @@ func (b *Bundle) MarshalJSON() ([]byte, error) {
108110
keyType = fmt.Sprintf("%d-bit RSA", keyLength)
109111
case x509.DSA:
110112
keyType = "DSA"
113+
case x509.Ed25519:
114+
keyType = "Ed25519"
111115
default:
112116
keyType = "Unknown"
113117
}
@@ -119,6 +123,9 @@ func (b *Bundle) MarshalJSON() ([]byte, error) {
119123
case *ecdsa.PrivateKey:
120124
keyBytes, _ = x509.MarshalECPrivateKey(key)
121125
keyString = PemBlockToString(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
126+
case ed25519.PrivateKey:
127+
keyBytes, _ = derhelpers.MarshalEd25519PrivateKey(key)
128+
keyString = PemBlockToString(&pem.Block{Type: "Ed25519 PRIVATE KEY", Bytes: keyBytes})
122129
case fmt.Stringer:
123130
keyString = key.String()
124131
}

bundler/bundle_from_file_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,12 @@ var fileTests = []fileTest{
260260
},
261261

262262
// DSA is NOT supported.
263-
// Keyless bundling, expect private key error "NotRSAOrECC"
263+
// Keyless bundling, expect private key error "NotRSAOrECCOrEd25519"
264264
{
265265
cert: certDSA2048,
266266
caBundleFile: testCFSSLRootBundle,
267267
intBundleFile: testCFSSLIntBundle,
268-
errorCallback: ExpectErrorMessages([]string{`"code":2200,`, `"message":"Private key algorithm is not RSA or ECC"`}),
268+
errorCallback: ExpectErrorMessages([]string{`"code":2200,`, `"message":"Private key algorithm is not RSA or ECC or Ed25519"`}),
269269
},
270270
// Bundling with DSA private key, expect error "Failed to parse private key"
271271
{

bundler/bundler.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"bytes"
77
"crypto"
88
"crypto/ecdsa"
9+
"crypto/ed25519"
910
"crypto/rsa"
1011
"crypto/tls"
1112
"crypto/x509"
@@ -555,7 +556,7 @@ func (b *Bundler) fetchIntermediates(certs []*x509.Certificate) (err error) {
555556

556557
// Bundle takes an X509 certificate (already in the
557558
// Certificate structure), a private key as crypto.Signer in one of the appropriate
558-
// formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to
559+
// formats (i.e. *rsa.PrivateKey, *ecdsa.PrivateKey or ed25519.PrivateKey, or even a opaque key), using them to
559560
// build a certificate bundle.
560561
func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) {
561562
log.Infof("bundling certificate for %+v", certs[0].Subject)
@@ -576,7 +577,6 @@ func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor Bu
576577
if key != nil {
577578
switch {
578579
case cert.PublicKeyAlgorithm == x509.RSA:
579-
580580
var rsaPublicKey *rsa.PublicKey
581581
if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok {
582582
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
@@ -592,15 +592,24 @@ func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor Bu
592592
if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 {
593593
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
594594
}
595+
case cert.PublicKeyAlgorithm == x509.Ed25519:
596+
var ed25519PublicKey ed25519.PublicKey
597+
if ed25519PublicKey, ok = key.Public().(ed25519.PublicKey); !ok {
598+
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
599+
}
600+
if !(bytes.Equal(cert.PublicKey.(ed25519.PublicKey), ed25519PublicKey)) {
601+
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
602+
}
595603
default:
596-
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
604+
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECCOrEd25519)
597605
}
598606
} else {
599607
switch {
600608
case cert.PublicKeyAlgorithm == x509.RSA:
601609
case cert.PublicKeyAlgorithm == x509.ECDSA:
610+
case cert.PublicKeyAlgorithm == x509.Ed25519:
602611
default:
603-
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
612+
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECCOrEd25519)
604613
}
605614
}
606615

cmd/multirootca/ca.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"crypto/ecdsa"
5+
"crypto/ed25519"
56
"crypto/rsa"
67
"errors"
78
"flag"
@@ -25,7 +26,7 @@ import (
2526
func parseSigner(root *config.Root) (signer.Signer, error) {
2627
privateKey := root.PrivateKey
2728
switch priv := privateKey.(type) {
28-
case *rsa.PrivateKey, *ecdsa.PrivateKey:
29+
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
2930
s, err := local.NewSigner(priv, root.Certificate, signer.DefaultSigAlgo(priv), nil)
3031
if err != nil {
3132
return nil, err

csr/csr.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package csr
44
import (
55
"crypto"
66
"crypto/ecdsa"
7+
"crypto/ed25519"
78
"crypto/elliptic"
89
"crypto/rand"
910
"crypto/rsa"
@@ -13,6 +14,7 @@ import (
1314
"encoding/pem"
1415
"errors"
1516
"fmt"
17+
"io"
1618
"net"
1719
"net/mail"
1820
"net/url"
@@ -21,6 +23,7 @@ import (
2123

2224
cferr "github.com/cloudflare/cfssl/errors"
2325
"github.com/cloudflare/cfssl/helpers"
26+
"github.com/cloudflare/cfssl/helpers/derhelpers"
2427
"github.com/cloudflare/cfssl/log"
2528
)
2629

@@ -64,7 +67,7 @@ func (kr *KeyRequest) Size() int {
6467
}
6568

6669
// Generate generates a key as specified in the request. Currently,
67-
// only ECDSA and RSA are supported.
70+
// only ECDSA, RSA and ed25519 algorithms are supported.
6871
func (kr *KeyRequest) Generate() (crypto.PrivateKey, error) {
6972
log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo(), kr.Size())
7073
switch kr.Algo() {
@@ -89,6 +92,12 @@ func (kr *KeyRequest) Generate() (crypto.PrivateKey, error) {
8992
return nil, errors.New("invalid curve")
9093
}
9194
return ecdsa.GenerateKey(curve, rand.Reader)
95+
case "ed25519":
96+
seed := make([]byte, ed25519.SeedSize)
97+
if _, err := io.ReadFull(rand.Reader, seed); err != nil {
98+
return nil, err
99+
}
100+
return ed25519.NewKeyFromSeed(seed), nil
92101
default:
93102
return nil, errors.New("invalid algorithm")
94103
}
@@ -120,6 +129,8 @@ func (kr *KeyRequest) SigAlgo() x509.SignatureAlgorithm {
120129
default:
121130
return x509.ECDSAWithSHA1
122131
}
132+
case "ed25519":
133+
return x509.PureEd25519
123134
default:
124135
return x509.UnknownSignatureAlgorithm
125136
}
@@ -249,6 +260,17 @@ func ParseRequest(req *CertificateRequest) (csr, key []byte, err error) {
249260
Bytes: key,
250261
}
251262
key = pem.EncodeToMemory(&block)
263+
case ed25519.PrivateKey:
264+
key, err = derhelpers.MarshalEd25519PrivateKey(priv)
265+
if err != nil {
266+
err = cferr.Wrap(cferr.PrivateKeyError, cferr.Unknown, err)
267+
return
268+
}
269+
block := pem.Block{
270+
Type: "Ed25519 PRIVATE KEY",
271+
Bytes: key,
272+
}
273+
key = pem.EncodeToMemory(&block)
252274
default:
253275
panic("Generate should have failed to produce a valid key.")
254276
}

csr/csr_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package csr
33
import (
44
"crypto"
55
"crypto/ecdsa"
6+
"crypto/ed25519"
67
"crypto/elliptic"
78
"crypto/rsa"
89
"crypto/x509"
@@ -42,6 +43,10 @@ func TestKeyRequest(t *testing.T) {
4243
if kr.Algo() != "ecdsa" {
4344
t.Fatal("ECDSA key generated, but expected", kr.Algo())
4445
}
46+
case ed25519.PrivateKey:
47+
if kr.Algo() != "ed25519" {
48+
t.Fatal("Ed25519 key generated, but expected", kr.Algo())
49+
}
4550
}
4651
}
4752

@@ -311,6 +316,21 @@ func TestECGeneration(t *testing.T) {
311316
}
312317
}
313318

319+
func TestED25519Generation(t *testing.T) {
320+
kr := &KeyRequest{A: "ed25519"}
321+
priv, err := kr.Generate()
322+
if err != nil {
323+
t.Fatalf("%v", err)
324+
}
325+
_, ok := priv.(ed25519.PrivateKey)
326+
if !ok {
327+
t.Fatal("Expected ed25519 key")
328+
}
329+
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
330+
t.Fatal("Invalid signature algorithm!")
331+
}
332+
}
333+
314334
func TestRSAKeyGeneration(t *testing.T) {
315335
var rsakey *rsa.PrivateKey
316336

@@ -404,6 +424,10 @@ func TestDefaultKeyRequest(t *testing.T) {
404424
if DefaultKeyRequest.Algo() != "ecdsa" {
405425
t.Fatal("Invalid default key request.")
406426
}
427+
case "Ed25519 PRIVATE KEY":
428+
if DefaultKeyRequest.Algo() != "ed25519" {
429+
t.Fatal("Invalid default key request.")
430+
}
407431
}
408432
}
409433

@@ -430,6 +454,29 @@ func TestRSACertRequest(t *testing.T) {
430454
}
431455
}
432456

457+
// TestED25519CertRequest validates parsing a certificate request with an
458+
// ED25519 key.
459+
func TestED25519CertRequest(t *testing.T) {
460+
var req = &CertificateRequest{
461+
Names: []Name{
462+
{
463+
C: "US",
464+
ST: "California",
465+
L: "San Francisco",
466+
O: "CloudFlare",
467+
OU: "Systems Engineering",
468+
},
469+
},
470+
CN: "cloudflare.com",
471+
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "[email protected]", "https://www.cloudflare.com"},
472+
KeyRequest: &KeyRequest{A: "ed25519"},
473+
}
474+
_, _, err := ParseRequest(req)
475+
if err != nil {
476+
t.Fatalf("%v", err)
477+
}
478+
}
479+
433480
// TestBadCertRequest checks for failure conditions of ParseRequest.
434481
func TestBadCertRequest(t *testing.T) {
435482
var req = &CertificateRequest{

errors/error.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ const (
113113
// these keys.
114114
Encrypted Reason = 100 * (iota + 1) //21XX
115115

116-
// NotRSAOrECC indicates that they key is not an RSA or ECC
116+
// NotRSAOrECCOrEd25519 indicates that they key is not an RSA or ECC or Ed25519
117117
// private key; these are the only two private key types supported
118118
// at this time by CFSSL.
119-
NotRSAOrECC //22XX
119+
NotRSAOrECCOrEd25519 //22XX
120120

121121
// KeyMismatch indicates that the private key does not match
122122
// the public key or certificate being presented with the key.
@@ -273,8 +273,8 @@ func New(category Category, reason Reason) *Error {
273273
msg = "Failed to parse private key"
274274
case Encrypted:
275275
msg = "Private key is encrypted."
276-
case NotRSAOrECC:
277-
msg = "Private key algorithm is not RSA or ECC"
276+
case NotRSAOrECCOrEd25519:
277+
msg = "Private key algorithm is not RSA or ECC or Ed25519"
278278
case KeyMismatch:
279279
msg = "Private key does not match public key"
280280
case GenerationFailed:

errors/error_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func TestNew(t *testing.T) {
8787
if code != 2100 {
8888
t.Fatal("Improper error code")
8989
}
90-
code = New(PrivateKeyError, NotRSAOrECC).ErrorCode
90+
code = New(PrivateKeyError, NotRSAOrECCOrEd25519).ErrorCode
9191
if code != 2200 {
9292
t.Fatal("Improper error code")
9393
}

helpers/helpers.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"bytes"
77
"crypto"
88
"crypto/ecdsa"
9+
"crypto/ed25519"
910
"crypto/elliptic"
1011
"crypto/rsa"
1112
"crypto/tls"
@@ -61,7 +62,7 @@ var Jul2012 = InclusiveDate(2012, time.July, 01)
6162
// issuing certificates valid for more than 39 months.
6263
var Apr2015 = InclusiveDate(2015, time.April, 01)
6364

64-
// KeyLength returns the bit size of ECDSA or RSA PublicKey
65+
// KeyLength returns the bit size of ECDSA, RSA or Ed25519 PublicKey
6566
func KeyLength(key interface{}) int {
6667
if key == nil {
6768
return 0
@@ -70,6 +71,8 @@ func KeyLength(key interface{}) int {
7071
return ecdsaKey.Curve.Params().BitSize
7172
} else if rsaKey, ok := key.(*rsa.PublicKey); ok {
7273
return rsaKey.N.BitLen()
74+
} else if _, ok := key.(ed25519.PublicKey); ok {
75+
return ed25519.PublicKeySize
7376
}
7477

7578
return 0
@@ -154,12 +157,14 @@ func SignatureString(alg x509.SignatureAlgorithm) string {
154157
return "ECDSAWithSHA384"
155158
case x509.ECDSAWithSHA512:
156159
return "ECDSAWithSHA512"
160+
case x509.PureEd25519:
161+
return "Ed25519"
157162
default:
158163
return "Unknown Signature"
159164
}
160165
}
161166

162-
// HashAlgoString returns the hash algorithm name contains in the signature
167+
// HashAlgoString returns the hash algorithm name contained in the signature
163168
// method.
164169
func HashAlgoString(alg x509.SignatureAlgorithm) string {
165170
switch alg {
@@ -187,6 +192,8 @@ func HashAlgoString(alg x509.SignatureAlgorithm) string {
187192
return "SHA384"
188193
case x509.ECDSAWithSHA512:
189194
return "SHA512"
195+
case x509.PureEd25519:
196+
return "Ed25519"
190197
default:
191198
return "Unknown Hash Algorithm"
192199
}
@@ -479,6 +486,8 @@ func SignerAlgo(priv crypto.Signer) x509.SignatureAlgorithm {
479486
default:
480487
return x509.ECDSAWithSHA1
481488
}
489+
case ed25519.PublicKey:
490+
return x509.PureEd25519
482491
default:
483492
return x509.UnknownSignatureAlgorithm
484493
}

0 commit comments

Comments
 (0)