Skip to content

Commit

Permalink
Add support for generating ed25519 keys and certs (#1061)
Browse files Browse the repository at this point in the history
Co-authored-by: Luke Valenta <lvalenta@cloudflare.com>
Co-authored-by: Sofía Celi <cherenkovd69@gmail.com>
  • Loading branch information
3 people committed Jun 12, 2023
1 parent c21e85d commit 9a0778d
Show file tree
Hide file tree
Showing 42 changed files with 2,549 additions and 23 deletions.
7 changes: 7 additions & 0 deletions bundler/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bundler
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
Expand All @@ -13,6 +14,7 @@ import (
"time"

"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/helpers/derhelpers"
)

// A Bundle contains a certificate and its trust chain. It is intended
Expand Down Expand Up @@ -108,6 +110,8 @@ func (b *Bundle) MarshalJSON() ([]byte, error) {
keyType = fmt.Sprintf("%d-bit RSA", keyLength)
case x509.DSA:
keyType = "DSA"
case x509.Ed25519:
keyType = "Ed25519"
default:
keyType = "Unknown"
}
Expand All @@ -119,6 +123,9 @@ func (b *Bundle) MarshalJSON() ([]byte, error) {
case *ecdsa.PrivateKey:
keyBytes, _ = x509.MarshalECPrivateKey(key)
keyString = PemBlockToString(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
case ed25519.PrivateKey:
keyBytes, _ = derhelpers.MarshalEd25519PrivateKey(key)
keyString = PemBlockToString(&pem.Block{Type: "Ed25519 PRIVATE KEY", Bytes: keyBytes})
case fmt.Stringer:
keyString = key.String()
}
Expand Down
4 changes: 2 additions & 2 deletions bundler/bundle_from_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,12 @@ var fileTests = []fileTest{
},

// DSA is NOT supported.
// Keyless bundling, expect private key error "NotRSAOrECC"
// Keyless bundling, expect private key error "NotRSAOrECCOrEd25519"
{
cert: certDSA2048,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2200,`, `"message":"Private key algorithm is not RSA or ECC"`}),
errorCallback: ExpectErrorMessages([]string{`"code":2200,`, `"message":"Private key algorithm is not RSA or ECC or Ed25519"`}),
},
// Bundling with DSA private key, expect error "Failed to parse private key"
{
Expand Down
17 changes: 13 additions & 4 deletions bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
Expand Down Expand Up @@ -555,7 +556,7 @@ func (b *Bundler) fetchIntermediates(certs []*x509.Certificate) (err error) {

// Bundle takes an X509 certificate (already in the
// Certificate structure), a private key as crypto.Signer in one of the appropriate
// formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to
// formats (i.e. *rsa.PrivateKey, *ecdsa.PrivateKey or ed25519.PrivateKey, or even a opaque key), using them to
// build a certificate bundle.
func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) {
log.Infof("bundling certificate for %+v", certs[0].Subject)
Expand All @@ -576,7 +577,6 @@ func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor Bu
if key != nil {
switch {
case cert.PublicKeyAlgorithm == x509.RSA:

var rsaPublicKey *rsa.PublicKey
if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
Expand All @@ -592,15 +592,24 @@ func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor Bu
if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
case cert.PublicKeyAlgorithm == x509.Ed25519:
var ed25519PublicKey ed25519.PublicKey
if ed25519PublicKey, ok = key.Public().(ed25519.PublicKey); !ok {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
if !(bytes.Equal(cert.PublicKey.(ed25519.PublicKey), ed25519PublicKey)) {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
default:
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECCOrEd25519)
}
} else {
switch {
case cert.PublicKeyAlgorithm == x509.RSA:
case cert.PublicKeyAlgorithm == x509.ECDSA:
case cert.PublicKeyAlgorithm == x509.Ed25519:
default:
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECCOrEd25519)
}
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/multirootca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"errors"
"flag"
Expand All @@ -25,7 +26,7 @@ import (
func parseSigner(root *config.Root) (signer.Signer, error) {
privateKey := root.PrivateKey
switch priv := privateKey.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey:
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
s, err := local.NewSigner(priv, root.Certificate, signer.DefaultSigAlgo(priv), nil)
if err != nil {
return nil, err
Expand Down
24 changes: 23 additions & 1 deletion csr/csr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package csr
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
Expand All @@ -13,6 +14,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"net"
"net/mail"
"net/url"
Expand All @@ -21,6 +23,7 @@ import (

cferr "github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/helpers/derhelpers"
"github.com/cloudflare/cfssl/log"
)

Expand Down Expand Up @@ -64,7 +67,7 @@ func (kr *KeyRequest) Size() int {
}

// Generate generates a key as specified in the request. Currently,
// only ECDSA and RSA are supported.
// only ECDSA, RSA and ed25519 algorithms are supported.
func (kr *KeyRequest) Generate() (crypto.PrivateKey, error) {
log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo(), kr.Size())
switch kr.Algo() {
Expand All @@ -89,6 +92,12 @@ func (kr *KeyRequest) Generate() (crypto.PrivateKey, error) {
return nil, errors.New("invalid curve")
}
return ecdsa.GenerateKey(curve, rand.Reader)
case "ed25519":
seed := make([]byte, ed25519.SeedSize)
if _, err := io.ReadFull(rand.Reader, seed); err != nil {
return nil, err
}
return ed25519.NewKeyFromSeed(seed), nil
default:
return nil, errors.New("invalid algorithm")
}
Expand Down Expand Up @@ -120,6 +129,8 @@ func (kr *KeyRequest) SigAlgo() x509.SignatureAlgorithm {
default:
return x509.ECDSAWithSHA1
}
case "ed25519":
return x509.PureEd25519
default:
return x509.UnknownSignatureAlgorithm
}
Expand Down Expand Up @@ -249,6 +260,17 @@ func ParseRequest(req *CertificateRequest) (csr, key []byte, err error) {
Bytes: key,
}
key = pem.EncodeToMemory(&block)
case ed25519.PrivateKey:
key, err = derhelpers.MarshalEd25519PrivateKey(priv)
if err != nil {
err = cferr.Wrap(cferr.PrivateKeyError, cferr.Unknown, err)
return
}
block := pem.Block{
Type: "Ed25519 PRIVATE KEY",
Bytes: key,
}
key = pem.EncodeToMemory(&block)
default:
panic("Generate should have failed to produce a valid key.")
}
Expand Down
47 changes: 47 additions & 0 deletions csr/csr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package csr
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
Expand Down Expand Up @@ -42,6 +43,10 @@ func TestKeyRequest(t *testing.T) {
if kr.Algo() != "ecdsa" {
t.Fatal("ECDSA key generated, but expected", kr.Algo())
}
case ed25519.PrivateKey:
if kr.Algo() != "ed25519" {
t.Fatal("Ed25519 key generated, but expected", kr.Algo())
}
}
}

Expand Down Expand Up @@ -311,6 +316,21 @@ func TestECGeneration(t *testing.T) {
}
}

func TestED25519Generation(t *testing.T) {
kr := &KeyRequest{A: "ed25519"}
priv, err := kr.Generate()
if err != nil {
t.Fatalf("%v", err)
}
_, ok := priv.(ed25519.PrivateKey)
if !ok {
t.Fatal("Expected ed25519 key")
}
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
t.Fatal("Invalid signature algorithm!")
}
}

func TestRSAKeyGeneration(t *testing.T) {
var rsakey *rsa.PrivateKey

Expand Down Expand Up @@ -404,6 +424,10 @@ func TestDefaultKeyRequest(t *testing.T) {
if DefaultKeyRequest.Algo() != "ecdsa" {
t.Fatal("Invalid default key request.")
}
case "Ed25519 PRIVATE KEY":
if DefaultKeyRequest.Algo() != "ed25519" {
t.Fatal("Invalid default key request.")
}
}
}

Expand All @@ -430,6 +454,29 @@ func TestRSACertRequest(t *testing.T) {
}
}

// TestED25519CertRequest validates parsing a certificate request with an
// ED25519 key.
func TestED25519CertRequest(t *testing.T) {
var req = &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com", "www.cloudflare.com", "jdoe@example.com", "https://www.cloudflare.com"},
KeyRequest: &KeyRequest{A: "ed25519"},
}
_, _, err := ParseRequest(req)
if err != nil {
t.Fatalf("%v", err)
}
}

// TestBadCertRequest checks for failure conditions of ParseRequest.
func TestBadCertRequest(t *testing.T) {
var req = &CertificateRequest{
Expand Down
8 changes: 4 additions & 4 deletions errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ const (
// these keys.
Encrypted Reason = 100 * (iota + 1) //21XX

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

// KeyMismatch indicates that the private key does not match
// the public key or certificate being presented with the key.
Expand Down Expand Up @@ -273,8 +273,8 @@ func New(category Category, reason Reason) *Error {
msg = "Failed to parse private key"
case Encrypted:
msg = "Private key is encrypted."
case NotRSAOrECC:
msg = "Private key algorithm is not RSA or ECC"
case NotRSAOrECCOrEd25519:
msg = "Private key algorithm is not RSA or ECC or Ed25519"
case KeyMismatch:
msg = "Private key does not match public key"
case GenerationFailed:
Expand Down
2 changes: 1 addition & 1 deletion errors/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestNew(t *testing.T) {
if code != 2100 {
t.Fatal("Improper error code")
}
code = New(PrivateKeyError, NotRSAOrECC).ErrorCode
code = New(PrivateKeyError, NotRSAOrECCOrEd25519).ErrorCode
if code != 2200 {
t.Fatal("Improper error code")
}
Expand Down
13 changes: 11 additions & 2 deletions helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/tls"
Expand Down Expand Up @@ -61,7 +62,7 @@ var Jul2012 = InclusiveDate(2012, time.July, 01)
// issuing certificates valid for more than 39 months.
var Apr2015 = InclusiveDate(2015, time.April, 01)

// KeyLength returns the bit size of ECDSA or RSA PublicKey
// KeyLength returns the bit size of ECDSA, RSA or Ed25519 PublicKey
func KeyLength(key interface{}) int {
if key == nil {
return 0
Expand All @@ -70,6 +71,8 @@ func KeyLength(key interface{}) int {
return ecdsaKey.Curve.Params().BitSize
} else if rsaKey, ok := key.(*rsa.PublicKey); ok {
return rsaKey.N.BitLen()
} else if _, ok := key.(ed25519.PublicKey); ok {
return ed25519.PublicKeySize
}

return 0
Expand Down Expand Up @@ -154,12 +157,14 @@ func SignatureString(alg x509.SignatureAlgorithm) string {
return "ECDSAWithSHA384"
case x509.ECDSAWithSHA512:
return "ECDSAWithSHA512"
case x509.PureEd25519:
return "Ed25519"
default:
return "Unknown Signature"
}
}

// HashAlgoString returns the hash algorithm name contains in the signature
// HashAlgoString returns the hash algorithm name contained in the signature
// method.
func HashAlgoString(alg x509.SignatureAlgorithm) string {
switch alg {
Expand Down Expand Up @@ -187,6 +192,8 @@ func HashAlgoString(alg x509.SignatureAlgorithm) string {
return "SHA384"
case x509.ECDSAWithSHA512:
return "SHA512"
case x509.PureEd25519:
return "Ed25519"
default:
return "Unknown Hash Algorithm"
}
Expand Down Expand Up @@ -479,6 +486,8 @@ func SignerAlgo(priv crypto.Signer) x509.SignatureAlgorithm {
default:
return x509.ECDSAWithSHA1
}
case ed25519.PublicKey:
return x509.PureEd25519
default:
return x509.UnknownSignatureAlgorithm
}
Expand Down
Loading

0 comments on commit 9a0778d

Please sign in to comment.