Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating ed25519 keys and certs #1061

Merged
merged 39 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e340753
add support for generating ed25519 keys and certs
izolight Nov 23, 2019
c639e67
add 5min ca testfiles
izolight Nov 24, 2019
e4cf3d8
only build for go1.13 as the ed25519 package is now part of std library
izolight Nov 24, 2019
bb83c8c
remove dependency on golang.org/x/crypto/ed25519
izolight Nov 24, 2019
9a17a34
add support for generating ed25519 keys and certs
izolight Nov 23, 2019
cf7fb43
add 5min ca testfiles
izolight Nov 24, 2019
79fa3b0
only build for go1.13 as the ed25519 package is now part of std library
izolight Nov 24, 2019
221d4ec
remove dependency on golang.org/x/crypto/ed25519
izolight Nov 24, 2019
626680c
Change string domain and format
claucece Apr 21, 2020
1d9e618
typo
izolight Apr 23, 2020
751bf89
compare key and cert
izolight Apr 23, 2020
6624bf1
update comment and error to indicate support for ed25519
izolight Apr 23, 2020
b43f9a2
update comments
izolight Apr 23, 2020
7f6b294
Change to use circl ed25519. This will fail due to issue cloudflare/c…
claucece Apr 30, 2020
b8f9375
Use circl only for signing
claucece May 1, 2020
f1bf794
Update vendor
claucece May 1, 2020
259b11c
Update vendor to mod
claucece May 1, 2020
35d039a
Remove go 1.12
claucece May 1, 2020
887b6fc
Only use c25519 for generation
claucece May 6, 2020
7dc5eca
Fix style
claucece May 6, 2020
4685a06
Use circl library for all ed25519 key generation
claucece May 12, 2020
b4a68f6
Run the tests with expired data
claucece May 13, 2020
7aff46b
Consistent naming
claucece May 14, 2020
2725cfc
Not using pointers
claucece May 16, 2020
ad00b62
Fix 5min files
claucece May 16, 2020
7d2779f
Use 25519 from stdlib
claucece May 23, 2020
acff4d8
This package is vendored
claucece May 23, 2020
21fb139
Re add error line and mod tidy
claucece Jul 22, 2020
bca3df9
Merge branch 'claucece-ed25519cert' into ed25519
izolight May 26, 2023
c1cfc4c
Merge remote-tracking branch 'origin' into ed25519
izolight May 26, 2023
58fc165
remove unneeded todo as per https://github.com/cloudflare/cfssl/pull/…
izolight May 26, 2023
fcceb3d
ignore key size for ed25519 as all keys are 256bit
izolight May 26, 2023
dfcdcd0
remove duplicate case from merge
izolight May 26, 2023
27e825e
add Ed25519 to error for supported keys
izolight May 26, 2023
ed80075
Update errors/error.go
izolight Jun 2, 2023
2b14670
remove duplicate switch case
izolight Jun 2, 2023
6fdd6b4
correctly remove size param from ed25519 instead of ecdsa
izolight Jun 2, 2023
0c7066a
remove unneeded test for ed25519 size
izolight Jun 2, 2023
ecc0574
print config instead of error if test fails
izolight Jun 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
izolight marked this conversation as resolved.
Show resolved Hide resolved
}

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