From 30fd91c0185b90a76bea19168051f2a7b83f2b27 Mon Sep 17 00:00:00 2001 From: "Anner J. Bonilla" Date: Tue, 5 Oct 2021 11:28:49 -0400 Subject: [PATCH] Add support for ed25519 (#11780) * update azure instructions Update instructions in regards to azure AD Authentication and OIDC * Initial pass of ed25519 * Fix typos on marshal function * test wip * typo * fix tests * missef changelog * fix mismatch between signature and algo * added test coverage for ed25519 * remove pkcs1 since does not exist for ed25519 * add ed25519 support to getsigner * pull request feedback Signed-off-by: Anner J. Bonilla * typo on key Signed-off-by: Anner J. Bonilla * cast mistake Signed-off-by: Anner J. Bonilla Co-authored-by: Jim Kalafut --- builtin/logical/pki/backend_test.go | 53 +++++++++- builtin/logical/pki/ca_test.go | 66 +++++++++++- builtin/logical/pki/cert_util.go | 13 +++ builtin/logical/pki/fields.go | 4 +- builtin/logical/pki/path_roles.go | 4 +- changelog/11780.txt | 3 + sdk/helper/certutil/certutil_test.go | 134 ++++++++++++++++++++++-- sdk/helper/certutil/helpers.go | 33 +++++- sdk/helper/certutil/types.go | 25 ++++- ui/app/models/pki-ca-certificate.js | 2 +- website/content/api-docs/secret/pki.mdx | 4 +- 11 files changed, 317 insertions(+), 24 deletions(-) create mode 100644 changelog/11780.txt diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index ab30d990a0ace..60833c5d20df4 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -211,6 +212,8 @@ func TestBackend_Roles(t *testing.T) { {"RSACSR", &rsaCAKey, &rsaCACert, true}, {"EC", &ecCAKey, &ecCACert, false}, {"ECCSR", &ecCAKey, &ecCACert, true}, + {"ED", &edCAKey, &edCACert, false}, + {"EDCSR", &edCAKey, &edCACert, true}, } for _, tc := range cases { @@ -309,6 +312,13 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUs if err != nil { return nil, fmt.Errorf("error parsing EC key: %s", err) } + case "ed25519": + parsedCertBundle.PrivateKeyType = certutil.Ed25519PrivateKey + parsedCertBundle.PrivateKey = key + parsedCertBundle.PrivateKeyBytes, err = x509.MarshalPKCS8PrivateKey(key.(ed25519.PrivateKey)) + if err != nil { + return nil, fmt.Errorf("error parsing Ed25519 key: %s", err) + } } } @@ -324,6 +334,8 @@ func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUs } switch { + case parsedCertBundle.PrivateKeyType == certutil.Ed25519PrivateKey && keyType != "ed25519": + fallthrough case parsedCertBundle.PrivateKeyType == certutil.RSAPrivateKey && keyType != "rsa": fallthrough case parsedCertBundle.PrivateKeyType == certutil.ECPrivateKey && keyType != "ec": @@ -707,7 +719,7 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep { generatedRSAKeys := map[int]crypto.Signer{} generatedECKeys := map[int]crypto.Signer{} - + generatedEdKeys := map[int]crypto.Signer{} /* // For the number of tests being run, a seed of 1 has been tested // to hit all of the various values below. However, for normal @@ -1017,6 +1029,13 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep { generatedECKeys[keyBits] = privKey } + case "ed25519": + privKey, ok = generatedEdKeys[keyBits] + if !ok { + _, privKey, _ = ed25519.GenerateKey(rand.Reader) + generatedEdKeys[keyBits] = privKey + } + default: panic("invalid key type: " + keyType) } @@ -3095,6 +3114,36 @@ func setCerts() { Bytes: caBytes, } rsaCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) + + _, edk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + marshaledKey, err = x509.MarshalPKCS8PrivateKey(edk) + if err != nil { + panic(err) + } + keyPEMBlock = &pem.Block{ + Type: "PRIVATE KEY", + Bytes: marshaledKey, + } + edCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) + if err != nil { + panic(err) + } + subjKeyID, err = certutil.GetSubjKeyID(edk) + if err != nil { + panic(err) + } + caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, edk.Public(), edk) + if err != nil { + panic(err) + } + caCertPEMBlock = &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + } + edCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) } func TestBackend_RevokePlusTidy_Intermediate(t *testing.T) { @@ -3281,4 +3330,6 @@ var ( rsaCACert string ecCAKey string ecCACert string + edCAKey string + edCACert string ) diff --git a/builtin/logical/pki/ca_test.go b/builtin/logical/pki/ca_test.go index 9d4e6b6c0b363..f4c4d12ea05fc 100644 --- a/builtin/logical/pki/ca_test.go +++ b/builtin/logical/pki/ca_test.go @@ -3,6 +3,7 @@ package pki import ( "context" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -49,7 +50,7 @@ func TestBackend_CA_Steps(t *testing.T) { client := cluster.Cores[0].Client // Set RSA/EC CA certificates - var rsaCAKey, rsaCACert, ecCAKey, ecCACert string + var rsaCAKey, rsaCACert, ecCAKey, ecCACert, edCAKey, edCACert string { cak, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { @@ -119,10 +120,40 @@ func TestBackend_CA_Steps(t *testing.T) { Bytes: caBytes, } rsaCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) + + _, edk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + marshaledKey, err = x509.MarshalPKCS8PrivateKey(edk) + if err != nil { + panic(err) + } + keyPEMBlock = &pem.Block{ + Type: "PRIVATE KEY", + Bytes: marshaledKey, + } + edCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) + if err != nil { + panic(err) + } + _, err = certutil.GetSubjKeyID(edk) + if err != nil { + panic(err) + } + caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, edk.Public(), edk) + if err != nil { + panic(err) + } + caCertPEMBlock = &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + } + edCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock))) } // Setup backends - var rsaRoot, rsaInt, ecRoot, ecInt *backend + var rsaRoot, rsaInt, ecRoot, ecInt, edRoot, edInt *backend { if err := client.Sys().Mount("rsaroot", &api.MountInput{ Type: "pki", @@ -167,6 +198,28 @@ func TestBackend_CA_Steps(t *testing.T) { t.Fatal(err) } ecInt = b + + if err := client.Sys().Mount("ed25519root", &api.MountInput{ + Type: "pki", + Config: api.MountConfigInput{ + DefaultLeaseTTL: "16h", + MaxLeaseTTL: "60h", + }, + }); err != nil { + t.Fatal(err) + } + edRoot = b + + if err := client.Sys().Mount("ed25519int", &api.MountInput{ + Type: "pki", + Config: api.MountConfigInput{ + DefaultLeaseTTL: "16h", + MaxLeaseTTL: "60h", + }, + }); err != nil { + t.Fatal(err) + } + edInt = b } t.Run("teststeps", func(t *testing.T) { @@ -188,6 +241,15 @@ func TestBackend_CA_Steps(t *testing.T) { subClient.SetToken(client.Token()) runSteps(t, ecRoot, ecInt, subClient, "ecroot/", "ecint/", ecCACert, ecCAKey) }) + t.Run("ed25519", func(t *testing.T) { + t.Parallel() + subClient, err := client.Clone() + if err != nil { + t.Fatal(err) + } + subClient.SetToken(client.Token()) + runSteps(t, edRoot, edInt, subClient, "ed25519root/", "ed25519int/", edCACert, edCAKey) + }) }) } diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index b129fc2834cbe..69fbb07d963a3 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -4,6 +4,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" @@ -597,6 +598,18 @@ func signCert(b *backend, pubKey.Params().BitSize)} } + case "ed25519": + // Verify that the key matches the role type + if csr.PublicKeyAlgorithm != x509.PublicKeyAlgorithm(x509.Ed25519) { + return nil, errutil.UserError{Err: fmt.Sprintf( + "role requires keys of type %s", + data.role.KeyType)} + } + _, ok := csr.PublicKey.(ed25519.PublicKey) + if !ok { + return nil, errutil.UserError{Err: "could not parse CSR's public key"} + } + case "any": // We only care about running RSA < 2048 bit checks, so if not RSA // break out diff --git a/builtin/logical/pki/fields.go b/builtin/logical/pki/fields.go index 149001aa52b95..5680a5b2d7d6d 100644 --- a/builtin/logical/pki/fields.go +++ b/builtin/logical/pki/fields.go @@ -270,8 +270,8 @@ Set to 384 for SHA384 and 512 for SHA512. Type: framework.TypeString, Default: "rsa", Description: `The type of key to use; defaults to RSA. "rsa" -and "ec" are the only valid values.`, - AllowedValues: []interface{}{"rsa", "ec"}, +"ec" and "ed25519" are the only valid values.`, + AllowedValues: []interface{}{"rsa", "ec", "ed25519"}, DisplayAttrs: &framework.DisplayAttributes{ Value: "rsa", }, diff --git a/builtin/logical/pki/path_roles.go b/builtin/logical/pki/path_roles.go index 65d0cc39081d9..4cfa666cfcc40 100644 --- a/builtin/logical/pki/path_roles.go +++ b/builtin/logical/pki/path_roles.go @@ -193,8 +193,8 @@ protection use. Defaults to false.`, Type: framework.TypeString, Default: "rsa", Description: `The type of key to use; defaults to RSA. "rsa" -and "ec" are the only valid values.`, - AllowedValues: []interface{}{"rsa", "ec"}, +"ec" and "ed25519" are the only valid values.`, + AllowedValues: []interface{}{"rsa", "ec", "ed25519"}, }, "key_bits": { diff --git a/changelog/11780.txt b/changelog/11780.txt new file mode 100644 index 0000000000000..e424054af57de --- /dev/null +++ b/changelog/11780.txt @@ -0,0 +1,3 @@ +```release-note:feature +pki: Support ed25519 as a key for the pki backend +``` diff --git a/sdk/helper/certutil/certutil_test.go b/sdk/helper/certutil/certutil_test.go index b428372141258..cedb48fec3c29 100644 --- a/sdk/helper/certutil/certutil_test.go +++ b/sdk/helper/certutil/certutil_test.go @@ -3,6 +3,7 @@ package certutil import ( "bytes" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -36,6 +37,8 @@ func TestCertBundleConversion(t *testing.T) { refreshECCertBundleWithChain(), refreshEC8CertBundle(), refreshEC8CertBundleWithChain(), + refreshEd255198CertBundle(), + refreshEd255198CertBundleWithChain(), } for i, cbut := range cbuts { @@ -75,6 +78,8 @@ func BenchmarkCertBundleParsing(b *testing.B) { refreshECCertBundleWithChain(), refreshEC8CertBundle(), refreshEC8CertBundleWithChain(), + refreshEd255198CertBundle(), + refreshEd255198CertBundleWithChain(), } for i, cbut := range cbuts { @@ -103,6 +108,8 @@ func TestCertBundleParsing(t *testing.T) { refreshECCertBundleWithChain(), refreshEC8CertBundle(), refreshEC8CertBundleWithChain(), + refreshEd255198CertBundle(), + refreshEd255198CertBundleWithChain(), } for i, cbut := range cbuts { @@ -179,6 +186,10 @@ func compareCertBundleToParsedCertBundle(cbut *CertBundle, pcbut *ParsedCertBund if pcbut.PrivateKeyType != ECPrivateKey { return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'ec' (%v)", pcbut.PrivateKeyType, ECPrivateKey) } + case privEd255198KeyPem: + if pcbut.PrivateKeyType != Ed25519PrivateKey { + return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'ed25519' (%v)", pcbut.PrivateKeyType, ECPrivateKey) + } default: return fmt.Errorf("parsed bundle has unknown private key type") } @@ -221,6 +232,10 @@ func compareCertBundleToParsedCertBundle(cbut *CertBundle, pcbut *ParsedCertBund if cb.PrivateKey != privECKeyPem && cb.PrivateKey != privEC8KeyPem { return fmt.Errorf("bundle private key does not match") } + case Ed25519PrivateKey: + if cb.PrivateKey != privEd255198KeyPem { + return fmt.Errorf("bundle private key does not match") + } default: return fmt.Errorf("certBundle has unknown private key type") } @@ -245,6 +260,7 @@ func TestCSRBundleConversion(t *testing.T) { csrbuts := []*CSRBundle{ refreshRSACSRBundle(), refreshECCSRBundle(), + refreshEd25519CSRBundle(), } for _, csrbut := range csrbuts { @@ -294,6 +310,10 @@ func compareCSRBundleToParsedCSRBundle(csrbut *CSRBundle, pcsrbut *ParsedCSRBund if pcsrbut.PrivateKeyType != ECPrivateKey { return fmt.Errorf("parsed bundle has wrong private key type") } + case privEd255198KeyPem: + if pcsrbut.PrivateKeyType != Ed25519PrivateKey { + return fmt.Errorf("parsed bundle has wrong private key type") + } default: return fmt.Errorf("parsed bundle has unknown private key type") } @@ -325,6 +345,13 @@ func compareCSRBundleToParsedCSRBundle(csrbut *CSRBundle, pcsrbut *ParsedCSRBund if csrb.PrivateKey != privECKeyPem { return fmt.Errorf("bundle ec private key does not match") } + case "ed25519": + if pcsrbut.PrivateKeyType != Ed25519PrivateKey { + return fmt.Errorf("bundle has wrong private key type") + } + if csrb.PrivateKey != privEd255198KeyPem { + return fmt.Errorf("bundle ed25519 private key does not match") + } default: return fmt.Errorf("bundle has unknown private key type") } @@ -476,6 +503,30 @@ func refreshECCertBundleWithChain() *CertBundle { return ret } +func refreshEd255198CertBundle() *CertBundle { + initTest.Do(setCerts) + return &CertBundle{ + Certificate: certEd25519Pem, + PrivateKey: privEd255198KeyPem, + CAChain: []string{issuingCaChainPem[0]}, + } +} + +func refreshEd255198CertBundleWithChain() *CertBundle { + initTest.Do(setCerts) + ret := refreshEd255198CertBundle() + ret.CAChain = issuingCaChainPem + return ret +} + +func refreshEd25519CSRBundle() *CSRBundle { + initTest.Do(setCerts) + return &CSRBundle{ + CSR: csrEd25519Pem, + PrivateKey: privEd255198KeyPem, + } +} + func refreshRSACSRBundle() *CSRBundle { initTest.Do(setCerts) return &CSRBundle{ @@ -714,18 +765,81 @@ func setCerts() { privRSA8KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) } + // Ed25519 generation + { + pubkey, privkey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + subjKeyID, err := GetSubjKeyID(privkey) + if err != nil { + panic(err) + } + certTemplate := &x509.Certificate{ + Subject: pkix.Name{ + CommonName: "localhost", + }, + SubjectKeyId: subjKeyID, + DNSNames: []string{"localhost"}, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement, + SerialNumber: big.NewInt(mathrand.Int63()), + NotBefore: time.Now().Add(-30 * time.Second), + NotAfter: time.Now().Add(262980 * time.Hour), + } + csrTemplate := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "localhost", + }, + DNSNames: []string{"localhost"}, + } + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privkey) + if err != nil { + panic(err) + } + csrPEMBlock := &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: csrBytes, + } + csrEd25519Pem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock))) + certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, pubkey, intKey) + if err != nil { + panic(err) + } + certPEMBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + } + certEd25519Pem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock))) + marshaledKey, err := x509.MarshalPKCS8PrivateKey(privkey) + if err != nil { + panic(err) + } + keyPEMBlock := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: marshaledKey, + } + privEd255198KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock))) + } + issuingCaChainPem = []string{intCertPEM, caCertPEM} } var ( - initTest sync.Once - privRSA8KeyPem string - privRSAKeyPem string - csrRSAPem string - certRSAPem string - privECKeyPem string - csrECPem string - privEC8KeyPem string - certECPem string - issuingCaChainPem []string + initTest sync.Once + privRSA8KeyPem string + privRSAKeyPem string + csrRSAPem string + certRSAPem string + privEd255198KeyPem string + csrEd25519Pem string + certEd25519Pem string + privECKeyPem string + csrECPem string + privEC8KeyPem string + certECPem string + issuingCaChainPem []string ) diff --git a/sdk/helper/certutil/helpers.go b/sdk/helper/certutil/helpers.go index 863ec12847e38..a88e39723002d 100644 --- a/sdk/helper/certutil/helpers.go +++ b/sdk/helper/certutil/helpers.go @@ -164,6 +164,10 @@ func ParsePEMBundle(pemBundle string) (*ParsedCertBundle, error) { parsedBundle.PrivateKey = signer parsedBundle.PrivateKeyType = ECPrivateKey parsedBundle.PrivateKeyBytes = pemBlock.Bytes + case ed25519.PrivateKey: + parsedBundle.PrivateKey = signer + parsedBundle.PrivateKeyType = Ed25519PrivateKey + parsedBundle.PrivateKeyBytes = pemBlock.Bytes } } else if certificates, err := x509.ParseCertificates(pemBlock.Bytes); err == nil { certPath = append(certPath, &CertBlock{ @@ -246,6 +250,16 @@ func generatePrivateKey(keyType string, keyBits int, container ParsedPrivateKeyC if err != nil { return errutil.InternalError{Err: fmt.Sprintf("error marshalling EC private key: %v", err)} } + case "ed25519": + privateKeyType = Ed25519PrivateKey + _, privateKey, err = ed25519.GenerateKey(randReader) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error generating ed25519 private key: %v", err)} + } + privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey.(ed25519.PrivateKey)) + if err != nil { + return errutil.InternalError{Err: fmt.Sprintf("error marshalling Ed25519 private key: %v", err)} + } default: return errutil.UserError{Err: fmt.Sprintf("unknown key type: %s", keyType)} } @@ -309,7 +323,16 @@ func ComparePublicKeys(key1Iface, key2Iface crypto.PublicKey) (bool, error) { return false, nil } return true, nil - + case ed25519.PublicKey: + key1 := key1Iface.(ed25519.PublicKey) + key2, ok := key2Iface.(ed25519.PublicKey) + if !ok { + return false, fmt.Errorf("key types do not match: %T and %T", key1Iface, key2Iface) + } + if !key1.Equal(key2) { + return false, nil + } + return true, nil default: return false, fmt.Errorf("cannot compare key with type %T", key1Iface) } @@ -521,7 +544,7 @@ func ValidateKeyTypeLength(keyType string, keyBits int) error { default: return fmt.Errorf("unsupported bit length for EC key: %d", keyBits) } - case "any": + case "any", "ed25519": default: return fmt.Errorf("unknown key type %s", keyType) } @@ -617,6 +640,8 @@ func createCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertB case 512: certTemplate.SignatureAlgorithm = x509.SHA512WithRSA } + case Ed25519PrivateKey: + certTemplate.SignatureAlgorithm = x509.PureEd25519 case ECPrivateKey: switch data.Params.SignatureBits { case 256: @@ -651,6 +676,8 @@ func createCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertB case 512: certTemplate.SignatureAlgorithm = x509.SHA512WithRSA } + case "ed25519": + certTemplate.SignatureAlgorithm = x509.PureEd25519 case "ec": switch data.Params.SignatureBits { case 256: @@ -754,6 +781,8 @@ func createCSR(data *CreationBundle, addBasicConstraints bool, randReader io.Rea csrTemplate.SignatureAlgorithm = x509.SHA256WithRSA case "ec": csrTemplate.SignatureAlgorithm = x509.ECDSAWithSHA256 + case "ed25519": + csrTemplate.SignatureAlgorithm = x509.PureEd25519 } csr, err := x509.CreateCertificateRequest(randReader, csrTemplate, result.PrivateKey) diff --git a/sdk/helper/certutil/types.go b/sdk/helper/certutil/types.go index be10c19f43050..a557b5d73db5e 100644 --- a/sdk/helper/certutil/types.go +++ b/sdk/helper/certutil/types.go @@ -12,6 +12,7 @@ import ( "bytes" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "crypto/tls" "crypto/x509" @@ -56,6 +57,7 @@ const ( UnknownPrivateKey PrivateKeyType = "" RSAPrivateKey PrivateKeyType = "rsa" ECPrivateKey PrivateKeyType = "ec" + Ed25519PrivateKey PrivateKeyType = "ed25519" ) // TLSUsage controls whether the intended usage of a *tls.Config @@ -185,6 +187,8 @@ func (c *CertBundle) ToParsedCertBundle() (*ParsedCertBundle, error) { c.PrivateKeyType = ECPrivateKey case RSAPrivateKey: c.PrivateKeyType = RSAPrivateKey + case Ed25519PrivateKey: + c.PrivateKeyType = Ed25519PrivateKey } default: return nil, errutil.UserError{Err: fmt.Sprintf("Unsupported key block type: %s", pemBlock.Type)} @@ -290,6 +294,8 @@ func (p *ParsedCertBundle) ToCertBundle() (*CertBundle, error) { block.Type = string(ECBlock) case RSAPrivateKey: block.Type = string(PKCS1Block) + case Ed25519PrivateKey: + block.Type = string(PKCS8Block) } } @@ -380,7 +386,7 @@ func (p *ParsedCertBundle) getSigner() (crypto.Signer, error) { case PKCS8Block: if k, err := x509.ParsePKCS8PrivateKey(p.PrivateKeyBytes); err == nil { switch k := k.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey: + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: return k.(crypto.Signer), nil default: return nil, errutil.UserError{Err: "Found unknown private key type in pkcs#8 wrapping"} @@ -411,6 +417,8 @@ func getPKCS8Type(bs []byte) (PrivateKeyType, error) { return ECPrivateKey, nil case *rsa.PrivateKey: return RSAPrivateKey, nil + case ed25519.PrivateKey: + return Ed25519PrivateKey, nil default: return UnknownPrivateKey, errutil.UserError{Err: "Found unknown private key type in pkcs#8 wrapping"} } @@ -443,6 +451,9 @@ func (c *CSRBundle) ToParsedCSRBundle() (*ParsedCSRBundle, error) { } else if _, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes); err == nil { result.PrivateKeyType = RSAPrivateKey c.PrivateKeyType = "rsa" + } else if _, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes); err == nil { + result.PrivateKeyType = Ed25519PrivateKey + c.PrivateKeyType = "ed25519" } else { return nil, errutil.UserError{Err: fmt.Sprintf("Unknown private key type in bundle: %s", c.PrivateKeyType)} } @@ -491,6 +502,9 @@ func (p *ParsedCSRBundle) ToCSRBundle() (*CSRBundle, error) { case ECPrivateKey: result.PrivateKeyType = "ec" block.Type = "EC PRIVATE KEY" + case Ed25519PrivateKey: + result.PrivateKeyType = "ed25519" + block.Type = "PRIVATE KEY" default: return nil, errutil.InternalError{Err: "Could not determine private key type when creating block"} } @@ -525,8 +539,15 @@ func (p *ParsedCSRBundle) getSigner() (crypto.Signer, error) { return nil, errutil.UserError{Err: fmt.Sprintf("Unable to parse CA's private RSA key: %s", err)} } + case Ed25519PrivateKey: + signerd, err := x509.ParsePKCS8PrivateKey(p.PrivateKeyBytes) + signer = signerd.(ed25519.PrivateKey) + if err != nil { + return nil, errutil.UserError{Err: fmt.Sprintf("Unable to parse CA's private Ed25519 key: %s", err)} + } + default: - return nil, errutil.UserError{Err: "Unable to determine type of private key; only RSA and EC are supported"} + return nil, errutil.UserError{Err: "Unable to determine type of private key; only RSA, Ed25519 and EC are supported"} } return signer, nil } diff --git a/ui/app/models/pki-ca-certificate.js b/ui/app/models/pki-ca-certificate.js index 3821e04ff81b9..8217165630e8e 100644 --- a/ui/app/models/pki-ca-certificate.js +++ b/ui/app/models/pki-ca-certificate.js @@ -128,7 +128,7 @@ export default Certificate.extend({ }), keyType: attr('string', { - possibleValues: ['rsa', 'ec'], + possibleValues: ['rsa', 'ec','ed25519'], defaultValue: 'rsa', }), keyBits: attr('number', { diff --git a/website/content/api-docs/secret/pki.mdx b/website/content/api-docs/secret/pki.mdx index d5a3921d6b5d3..e3d19a3b21058 100644 --- a/website/content/api-docs/secret/pki.mdx +++ b/website/content/api-docs/secret/pki.mdx @@ -463,7 +463,7 @@ can be set in a CSR are supported. PEM-encoded DER, depending on the value of `format`. The other option is `pkcs8` which will return the key marshalled as PEM-encoded PKCS8. -- `key_type` `(string: "rsa")` – Specifies the desired key type; must be `rsa` +- `key_type` `(string: "rsa")` – Specifies the desired key type; must be `rsa`, `ed25519` or `ec`. - `key_bits` `(int: 2048)` – Specifies the number of bits to use. This must be @@ -1084,7 +1084,7 @@ overwrite the existing cert/key with new values. PEM-encoded DER, depending on the value of `format`. The other option is `pkcs8` which will return the key marshalled as PEM-encoded PKCS8. -- `key_type` `(string: "rsa")` – Specifies the desired key type; must be `rsa` +- `key_type` `(string: "rsa")` – Specifies the desired key type; must be `rsa`, `ed25519` or `ec`. - `key_bits` `(int: 2048)` – Specifies the number of bits to use. This must be