Allow signing self issued certs with a different public key algorithm. (#12514)
* WIP: Unset the certificate's SignatureAlgorithm to allown cross-signing of different key types * Allow signing self issued certs with a different public key algorithm * Remove cruft * Remove stale import * changelog * eliminate errwrap * Add a test to cover the lack of opt-in flag * Better comment Co-authored-by: catsby <clint@ctshryock.com>
This commit is contained in:
parent
89271bf0ca
commit
6f18a9b6be
|
@ -2103,22 +2103,6 @@ func TestBackend_SignSelfIssued(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelfSigned := func(subject, issuer *x509.Certificate) (string, *x509.Certificate) {
|
|
||||||
selfSigned, err := x509.CreateCertificate(rand.Reader, subject, issuer, key.Public(), key)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(selfSigned)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pemSS := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: selfSigned,
|
|
||||||
})))
|
|
||||||
return pemSS, cert
|
|
||||||
}
|
|
||||||
|
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: "foo.bar.com",
|
CommonName: "foo.bar.com",
|
||||||
|
@ -2128,7 +2112,7 @@ func TestBackend_SignSelfIssued(t *testing.T) {
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
ss, _ := getSelfSigned(template, template)
|
ss, _ := getSelfSigned(t, template, template, key)
|
||||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
Operation: logical.UpdateOperation,
|
Operation: logical.UpdateOperation,
|
||||||
Path: "root/sign-self-issued",
|
Path: "root/sign-self-issued",
|
||||||
|
@ -2158,7 +2142,7 @@ func TestBackend_SignSelfIssued(t *testing.T) {
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
ss, ssCert := getSelfSigned(template, issuer)
|
ss, ssCert := getSelfSigned(t, template, issuer, key)
|
||||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
Operation: logical.UpdateOperation,
|
Operation: logical.UpdateOperation,
|
||||||
Path: "root/sign-self-issued",
|
Path: "root/sign-self-issued",
|
||||||
|
@ -2177,7 +2161,7 @@ func TestBackend_SignSelfIssued(t *testing.T) {
|
||||||
t.Fatalf("expected error due to different issuer; cert info is\nIssuer\n%#v\nSubject\n%#v\n", ssCert.Issuer, ssCert.Subject)
|
t.Fatalf("expected error due to different issuer; cert info is\nIssuer\n%#v\nSubject\n%#v\n", ssCert.Issuer, ssCert.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
ss, ssCert = getSelfSigned(template, template)
|
ss, ssCert = getSelfSigned(t, template, template, key)
|
||||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
Operation: logical.UpdateOperation,
|
Operation: logical.UpdateOperation,
|
||||||
Path: "root/sign-self-issued",
|
Path: "root/sign-self-issued",
|
||||||
|
@ -2224,6 +2208,185 @@ func TestBackend_SignSelfIssued(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBackend_SignSelfIssued_DifferentTypes is a copy of
|
||||||
|
// TestBackend_SignSelfIssued, but uses a different key type for the internal
|
||||||
|
// root (EC instead of RSA). This verifies that we can cross-sign CAs that are
|
||||||
|
// different key types, at the cost of verifying the algorithm used
|
||||||
|
func TestBackend_SignSelfIssued_DifferentTypes(t *testing.T) {
|
||||||
|
// create the backend
|
||||||
|
config := logical.TestBackendConfig()
|
||||||
|
storage := &logical.InmemStorage{}
|
||||||
|
config.StorageView = storage
|
||||||
|
|
||||||
|
b := Backend(config)
|
||||||
|
err := b.Setup(context.Background(), config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate root
|
||||||
|
rootData := map[string]interface{}{
|
||||||
|
"common_name": "test.com",
|
||||||
|
"ttl": "172800",
|
||||||
|
"key_type": "ec",
|
||||||
|
"key_bits": "521",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := b.HandleRequest(context.Background(), &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "root/generate/internal",
|
||||||
|
Storage: storage,
|
||||||
|
Data: rootData,
|
||||||
|
})
|
||||||
|
if resp != nil && resp.IsError() {
|
||||||
|
t.Fatalf("failed to generate root, %#v", *resp)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
template := &x509.Certificate{
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "foo.bar.com",
|
||||||
|
},
|
||||||
|
SerialNumber: big.NewInt(1234),
|
||||||
|
IsCA: false,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ss, _ := getSelfSigned(t, template, template, key)
|
||||||
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "root/sign-self-issued",
|
||||||
|
Storage: storage,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"certificate": ss,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("got nil response")
|
||||||
|
}
|
||||||
|
if !resp.IsError() {
|
||||||
|
t.Fatalf("expected error due to non-CA; got: %#v", *resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set CA to true, but leave issuer alone
|
||||||
|
template.IsCA = true
|
||||||
|
|
||||||
|
issuer := &x509.Certificate{
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "bar.foo.com",
|
||||||
|
},
|
||||||
|
SerialNumber: big.NewInt(2345),
|
||||||
|
IsCA: true,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ss, ssCert := getSelfSigned(t, template, issuer, key)
|
||||||
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "root/sign-self-issued",
|
||||||
|
Storage: storage,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"certificate": ss,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("got nil response")
|
||||||
|
}
|
||||||
|
if !resp.IsError() {
|
||||||
|
t.Fatalf("expected error due to different issuer; cert info is\nIssuer\n%#v\nSubject\n%#v\n", ssCert.Issuer, ssCert.Subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
ss, ssCert = getSelfSigned(t, template, template, key)
|
||||||
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "root/sign-self-issued",
|
||||||
|
Storage: storage,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"certificate": ss,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error due to different signature algo but not opted-in")
|
||||||
|
}
|
||||||
|
|
||||||
|
ss, ssCert = getSelfSigned(t, template, template, key)
|
||||||
|
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "root/sign-self-issued",
|
||||||
|
Storage: storage,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"certificate": ss,
|
||||||
|
"allow_different_signature_algorithm": "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("got nil response")
|
||||||
|
}
|
||||||
|
if resp.IsError() {
|
||||||
|
t.Fatalf("error in response: %s", resp.Error().Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
newCertString := resp.Data["certificate"].(string)
|
||||||
|
block, _ := pem.Decode([]byte(newCertString))
|
||||||
|
newCert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signingBundle, err := fetchCAInfo(context.Background(), &logical.Request{Storage: storage})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(newCert.Subject, newCert.Issuer) {
|
||||||
|
t.Fatal("expected different subject/issuer")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(newCert.Issuer, signingBundle.Certificate.Subject) {
|
||||||
|
t.Fatalf("expected matching issuer/CA subject\n\nIssuer:\n%#v\nSubject:\n%#v\n", newCert.Issuer, signingBundle.Certificate.Subject)
|
||||||
|
}
|
||||||
|
if bytes.Equal(newCert.AuthorityKeyId, newCert.SubjectKeyId) {
|
||||||
|
t.Fatal("expected different authority/subject")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(newCert.AuthorityKeyId, signingBundle.Certificate.SubjectKeyId) {
|
||||||
|
t.Fatal("expected authority on new cert to be same as signing subject")
|
||||||
|
}
|
||||||
|
if newCert.Subject.CommonName != "foo.bar.com" {
|
||||||
|
t.Fatalf("unexpected common name on new cert: %s", newCert.Subject.CommonName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSelfSigned(t *testing.T, subject, issuer *x509.Certificate, key *rsa.PrivateKey) (string, *x509.Certificate) {
|
||||||
|
t.Helper()
|
||||||
|
selfSigned, err := x509.CreateCertificate(rand.Reader, subject, issuer, key.Public(), key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(selfSigned)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pemSS := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: selfSigned,
|
||||||
|
})))
|
||||||
|
return pemSS, cert
|
||||||
|
}
|
||||||
|
|
||||||
// This is a really tricky test because the Go stdlib asn1 package is incapable
|
// This is a really tricky test because the Go stdlib asn1 package is incapable
|
||||||
// of doing the right thing with custom OID SANs (see comments in the package,
|
// of doing the right thing with custom OID SANs (see comments in the package,
|
||||||
// it's readily admitted that it's too magic) but that means that any
|
// it's readily admitted that it's too magic) but that means that any
|
||||||
|
|
|
@ -2,11 +2,17 @@ package pki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -103,6 +109,10 @@ func pathSignSelfIssued(b *backend) *framework.Path {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: `PEM-format self-issued certificate to be signed.`,
|
Description: `PEM-format self-issued certificate to be signed.`,
|
||||||
},
|
},
|
||||||
|
"allow_different_signature_algorithm": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeBool,
|
||||||
|
Description: `If true, allow the public key type of the signer to differ from the self issued certificate.`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
HelpSynopsis: pathSignSelfIssuedHelpSyn,
|
HelpSynopsis: pathSignSelfIssuedHelpSyn,
|
||||||
|
@ -428,6 +438,25 @@ func (b *backend) pathCASignSelfIssued(ctx context.Context, req *logical.Request
|
||||||
cert.CRLDistributionPoints = urls.CRLDistributionPoints
|
cert.CRLDistributionPoints = urls.CRLDistributionPoints
|
||||||
cert.OCSPServer = urls.OCSPServers
|
cert.OCSPServer = urls.OCSPServers
|
||||||
|
|
||||||
|
// If the requested signature algorithm isn't the same as the signing certificate, and
|
||||||
|
// the user has requested a cross-algorithm signature, reset the template's signing algorithm
|
||||||
|
// to that of the signing key
|
||||||
|
signingPubType, signingAlgorithm, err := publicKeyType(signingBundle.Certificate.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error determining signing certificate algorithm type: %e", err)
|
||||||
|
}
|
||||||
|
certPubType, _, err := publicKeyType(cert.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error determining template algorithm type: %e", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if signingPubType != certPubType {
|
||||||
|
b, ok := data.GetOk("allow_different_signature_algorithm")
|
||||||
|
if ok && b.(bool) {
|
||||||
|
cert.SignatureAlgorithm = signingAlgorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newCert, err := x509.CreateCertificate(rand.Reader, cert, signingBundle.Certificate, cert.PublicKey, signingBundle.PrivateKey)
|
newCert, err := x509.CreateCertificate(rand.Reader, cert, signingBundle.Certificate, cert.PublicKey, signingBundle.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error signing self-issued certificate: %w", err)
|
return nil, fmt.Errorf("error signing self-issued certificate: %w", err)
|
||||||
|
@ -448,6 +477,34 @@ func (b *backend) pathCASignSelfIssued(ctx context.Context, req *logical.Request
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adapted from similar code in https://github.com/golang/go/blob/4a4221e8187189adcc6463d2d96fe2e8da290132/src/crypto/x509/x509.go#L1342,
|
||||||
|
// may need to be updated in the future.
|
||||||
|
func publicKeyType(pub crypto.PublicKey) (pubType x509.PublicKeyAlgorithm, sigAlgo x509.SignatureAlgorithm, err error) {
|
||||||
|
switch pub := pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
pubType = x509.RSA
|
||||||
|
sigAlgo = x509.SHA256WithRSA
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
pubType = x509.ECDSA
|
||||||
|
switch pub.Curve {
|
||||||
|
case elliptic.P224(), elliptic.P256():
|
||||||
|
sigAlgo = x509.ECDSAWithSHA256
|
||||||
|
case elliptic.P384():
|
||||||
|
sigAlgo = x509.ECDSAWithSHA384
|
||||||
|
case elliptic.P521():
|
||||||
|
sigAlgo = x509.ECDSAWithSHA512
|
||||||
|
default:
|
||||||
|
err = errors.New("x509: unknown elliptic curve")
|
||||||
|
}
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
pubType = x509.Ed25519
|
||||||
|
sigAlgo = x509.PureEd25519
|
||||||
|
default:
|
||||||
|
err = errors.New("x509: only RSA, ECDSA and Ed25519 keys supported")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const pathGenerateRootHelpSyn = `
|
const pathGenerateRootHelpSyn = `
|
||||||
Generate a new CA certificate and private key used for signing.
|
Generate a new CA certificate and private key used for signing.
|
||||||
`
|
`
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
secrets/pki: Allow signing of self-issued certs with a different signature algorithm.
|
||||||
|
```
|
Loading…
Reference in New Issue