Skip to content

Commit

Permalink
Added support for generating Ed25519 certs (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsaarni authored Oct 18, 2024
1 parent 21e465d commit 207c1ff
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 3 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ Writing state: certs.state
| --- | ----------- | -------- |
| subject | Distinguished name for the certificate. `subject` is the only mandatory field and it must be unique. | `CN=Joe` |
| sans | List of values for x509 Subject Alternative Name extension. | `DNS:www.example.com`, `IP:1.2.3.4`, `URI:https://www.example.com` |
| key_type | Certificate key algorithm. Default value is `EC` (elliptic curve). | `EC` or `RSA` |
| key_size | The key length in bits. Default value is 256 if `key_size` is not defined. | For key_type EC: `256`, `384`, `521`. For key_type RSA: `1024`, `2048`, `4096` |
| key_type | Certificate key algorithm. Default value is `EC` (elliptic curve). | `EC`, `RSA` or `ED25519` |
| key_size | The key length in bits. Default value is 256 if `key_size` is not defined. | For key_type EC: `256`, `384`, `521`. For key_type RSA: `1024`, `2048`, `4096`. For key_type ED25519: `256`. |
| expires | Certificate NotAfter field is calculated by adding duration defined in `expires` to current time. Default value is 8760h (one year) if `expires` is not defined. `not_after` takes precedence over `expires`. | `1s`, `10m`, `1h` |
| key_usages | List of values for x509 key usage extension. If `key_usages` is not defined, `CertSign` and `CRLSign` are set for CA certificates, `KeyEncipherment` and `DigitalSignature` are set for end-entity certificates. | `DigitalSignature`, `ContentCommitment`, `KeyEncipherment`, `DataEncipherment`, `KeyAgreement`, `CertSign`, `CRLSign`, `EncipherOnly`, `DecipherOnly` |
| ext_key_usages | List of values for x509 extended key usage extension. Not set by default. | `Any`, `ServerAuth`, `ClientAuth`, `CodeSigning`, `EmailProtection`, `IPSECEndSystem`, `IPSECTunnel`, `IPSECUser`. `TimeStamping`, `OCSPSigning`, `MicrosoftServerGatedCrypto`, `NetscapeServerGatedCrypto`, `MicrosoftCommercialCodeSigning`, `MicrosoftKernelCodeSigning` |
Expand Down
11 changes: 10 additions & 1 deletion certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
Expand Down Expand Up @@ -54,7 +55,7 @@ type Certificate struct {

// KeySize defines the key length in bits.
// Default value is 256 (EC) or 2048 (RSA) if KeySize is undefined (when value is 0).
// Examples: For key_type EC: 256, 384, 521. For key_type RSA: 1024, 2048, 4096.
// Examples: For key_type EC: 256, 384, 521. For key_type RSA: 1024, 2048, 4096. For key_type ED25519: 256.
KeySize int `json:"key_size"`

// Expires automatically defines certificate's NotAfter field by adding duration defined in Expires to the current time.
Expand Down Expand Up @@ -106,6 +107,7 @@ type KeyType uint
const (
KeyTypeEC = iota
KeyTypeRSA
KeyTypeEd25519
)

// TLSCertificate returns the Certificate as tls.Certificate.
Expand Down Expand Up @@ -221,6 +223,8 @@ func (c *Certificate) defaults() error {
c.KeySize = 256
} else if c.KeyType == KeyTypeRSA {
c.KeySize = 2048
} else if c.KeyType == KeyTypeEd25519 {
c.KeySize = 256
}
}

Expand Down Expand Up @@ -300,6 +304,11 @@ func (c *Certificate) Generate() error {
key, err = ecdsa.GenerateKey(curve, rand.Reader)
} else if c.KeyType == KeyTypeRSA {
key, err = rsa.GenerateKey(rand.Reader, c.KeySize)
} else if c.KeyType == KeyTypeEd25519 {
if c.KeySize != 256 {
return fmt.Errorf("invalid Ed25519 key size: %d (valid: 256)", c.KeySize)
}
_, key, err = ed25519.GenerateKey(rand.Reader)
}
if err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ func TestInvalidKeySize(t *testing.T) {
input = Certificate{Subject: "CN=Joe", KeyType: KeyTypeRSA, KeySize: 1}
_, err = input.X509Certificate()
assert.NotNil(t, err)

input = Certificate{Subject: "CN=Joe", KeyType: KeyTypeEd25519, KeySize: 1}
_, err = input.X509Certificate()
assert.NotNil(t, err)
}

func TestPEM(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions internal/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ func (m *Manifest) processCertificate(c *CertificateManifest) error {
c.KeyType = api.KeyTypeEC
case "RSA":
c.KeyType = api.KeyTypeRSA
case "ED25519":
c.KeyType = api.KeyTypeEd25519
default:
return fmt.Errorf("key_type contains invalid value: %s", c.KeyTypeAsString)
}
Expand Down
10 changes: 10 additions & 0 deletions internal/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ func TestParsingAllCertificateFields(t *testing.T) {
assert.Empty(t, got.IPAddresses)

assert.Equal(t, big.NewInt(123), got.SerialNumber)

// Check fields Ee25519 end-entity cert.
tlsCert, err = tls.LoadX509KeyPair(path.Join(dir, "ed25519-cert.pem"), path.Join(dir, "ed25519-cert-key.pem"))
assert.Nil(t, err)
got, err = x509.ParseCertificate(tlsCert.Certificate[0])
assert.Nil(t, err)

assert.Equal(t, "ed25519-cert", got.Issuer.CommonName)
assert.Equal(t, "ed25519-cert", got.Subject.CommonName)
assert.Equal(t, x509.Ed25519, got.PublicKeyAlgorithm)
}

func TestRevocation(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions internal/manifest/testdata/certs-test-all-fields.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ key_size: 256
not_before: 2020-01-01T09:00:00Z
expires: 1h
serial: 123
---
subject: cn=ed25519-cert
key_type: ED25519

0 comments on commit 207c1ff

Please sign in to comment.