diff --git a/api/api_test.go b/api/api_test.go index 3f99dd68..88110314 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -24,6 +24,8 @@ import ( "time" "github.com/go-chi/chi" + "github.com/pkg/errors" + "github.com/smallstep/assert" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/logging" @@ -150,7 +152,7 @@ func parseCertificate(data string) *x509.Certificate { } func parseCertificateRequest(data string) *x509.CertificateRequest { - block, _ := pem.Decode([]byte(csrPEM)) + block, _ := pem.Decode([]byte(data)) if block == nil { panic("failed to parse certificate request PEM") } @@ -385,13 +387,13 @@ func TestSignRequest_Validate(t *testing.T) { NotAfter time.Time } tests := []struct { - name string - fields fields - wantErr bool + name string + fields fields + err error }{ - {"missing csr", fields{CertificateRequest{}, "foobarzar", time.Time{}, time.Time{}}, true}, - {"invalid csr", fields{CertificateRequest{bad}, "foobarzar", time.Time{}, time.Time{}}, true}, - {"missing ott", fields{CertificateRequest{csr}, "", time.Time{}, time.Time{}}, true}, + {"missing csr", fields{CertificateRequest{}, "foobarzar", time.Time{}, time.Time{}}, errors.New("missing csr")}, + {"invalid csr", fields{CertificateRequest{bad}, "foobarzar", time.Time{}, time.Time{}}, errors.New("invalid csr")}, + {"missing ott", fields{CertificateRequest{csr}, "", time.Time{}, time.Time{}}, errors.New("missing ott")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -401,8 +403,12 @@ func TestSignRequest_Validate(t *testing.T) { NotAfter: NewTimeDuration(tt.fields.NotAfter), NotBefore: NewTimeDuration(tt.fields.NotBefore), } - if err := s.Validate(); (err != nil) != tt.wantErr { - t.Errorf("SignRequest.Validate() error = %v, wantErr %v", err, tt.wantErr) + if err := s.Validate(); err != nil { + if assert.NotNil(t, tt.err) { + assert.HasPrefix(t, err.Error(), tt.err.Error()) + } + } else { + assert.Nil(t, tt.err) } }) } diff --git a/authority/authorize_test.go b/authority/authorize_test.go index 03a1139f..27a15513 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -446,7 +446,7 @@ func TestAuthority_AuthorizeSign(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - assert.Len(t, 7, got) + assert.Len(t, 8, got) } } }) @@ -538,7 +538,7 @@ func TestAuthority_Authorize(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - assert.Len(t, 7, got) + assert.Len(t, 8, got) } } }) diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 8b288e3d..421b4af8 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -287,6 +287,7 @@ func (p *AWS) AuthorizeSign(token string) ([]SignOption, error) { } return append(so, + defaultPublicKeyValidator{}, commonNameValidator(payload.Claims.Subject), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID), diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index 41793848..98c885fa 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -326,11 +326,11 @@ func TestAWS_AuthorizeSign(t *testing.T) { wantLen int wantErr bool }{ - {"ok", p1, args{t1}, 4, false}, - {"ok", p2, args{t2}, 6, false}, - {"ok", p2, args{t2Hostname}, 6, false}, - {"ok", p2, args{t2PrivateIP}, 6, false}, - {"ok", p1, args{t4}, 4, false}, + {"ok", p1, args{t1}, 5, false}, + {"ok", p2, args{t2}, 7, false}, + {"ok", p2, args{t2Hostname}, 7, false}, + {"ok", p2, args{t2PrivateIP}, 7, false}, + {"ok", p1, args{t4}, 5, false}, {"fail account", p3, args{t3}, 0, true}, {"fail token", p1, args{"token"}, 0, true}, {"fail subject", p1, args{failSubject}, 0, true}, diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index ee7fb744..40d14c9c 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -275,6 +275,7 @@ func (p *Azure) AuthorizeSign(token string) ([]SignOption, error) { } return append(so, + defaultPublicKeyValidator{}, profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID), newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index c370682a..8fbc9b20 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -281,9 +281,9 @@ func TestAzure_AuthorizeSign(t *testing.T) { wantLen int wantErr bool }{ - {"ok", p1, args{t1}, 3, false}, - {"ok", p2, args{t2}, 5, false}, - {"ok", p1, args{t11}, 3, false}, + {"ok", p1, args{t1}, 4, false}, + {"ok", p2, args{t2}, 6, false}, + {"ok", p1, args{t11}, 4, false}, {"fail tenant", p3, args{t3}, 0, true}, {"fail resource group", p4, args{t4}, 0, true}, {"fail token", p1, args{"token"}, 0, true}, diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 234b00fe..5ee92237 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -228,6 +228,7 @@ func (p *GCP) AuthorizeSign(token string) ([]SignOption, error) { } return append(so, + defaultPublicKeyValidator{}, profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName), newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), diff --git a/authority/provisioner/gcp_test.go b/authority/provisioner/gcp_test.go index ce935526..04dffd95 100644 --- a/authority/provisioner/gcp_test.go +++ b/authority/provisioner/gcp_test.go @@ -311,9 +311,9 @@ func TestGCP_AuthorizeSign(t *testing.T) { wantLen int wantErr bool }{ - {"ok", p1, args{t1}, 3, false}, - {"ok", p2, args{t2}, 5, false}, - {"ok", p3, args{t3}, 3, false}, + {"ok", p1, args{t1}, 4, false}, + {"ok", p2, args{t2}, 6, false}, + {"ok", p3, args{t3}, 4, false}, {"fail token", p1, args{"token"}, 0, true}, {"fail key", p1, args{failKey}, 0, true}, {"fail iss", p1, args{failIss}, 0, true}, diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index d0b08fa1..a4bc1137 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -143,6 +143,7 @@ func (p *JWK) AuthorizeSign(token string) ([]SignOption, error) { dnsNames, ips, emails := x509util.SplitSANs(claims.SANs) return []SignOption{ + defaultPublicKeyValidator{}, commonNameValidator(claims.Subject), dnsNamesValidator(dnsNames), ipAddressesValidator(ips), diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 0f45744a..5e3ad5f7 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -265,14 +265,14 @@ func TestJWK_AuthorizeSign(t *testing.T) { } } else { if assert.NotNil(t, got) { - assert.Len(t, 7, got) + assert.Len(t, 8, got) - _cnv := got[0] + _cnv := got[1] cnv, ok := _cnv.(commonNameValidator) assert.True(t, ok) assert.Equals(t, string(cnv), "subject") - _dnv := got[1] + _dnv := got[2] dnv, ok := _dnv.(dnsNamesValidator) assert.True(t, ok) if tt.name == "ok-sans" { diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 0b2e2700..66e8b9b7 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -264,21 +264,19 @@ func (o *OIDC) AuthorizeSign(token string) ([]SignOption, error) { if err != nil { return nil, err } - // Admins should be able to authorize any SAN - if o.IsAdmin(claims.Email) { - return []SignOption{ - profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), - newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), - newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), - }, nil - } - return []SignOption{ - emailOnlyIdentity(claims.Email), + so := []SignOption{ + defaultPublicKeyValidator{}, profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), - }, nil + } + // Admins should be able to authorize any SAN + if o.IsAdmin(claims.Email) { + return so, nil + } + + return append(so, emailOnlyIdentity(claims.Email)), nil } // AuthorizeRenewal returns an error if the renewal is disabled. diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index 80920f41..431bb7f8 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -286,9 +286,9 @@ func TestOIDC_AuthorizeSign(t *testing.T) { } else { assert.NotNil(t, got) if tt.name == "admin" { - assert.Len(t, 3, got) - } else { assert.Len(t, 4, got) + } else { + assert.Len(t, 5, got) } } }) diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index a106e9de..d535dfb6 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -1,6 +1,8 @@ package provisioner import ( + "crypto/ecdsa" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -10,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/cli/crypto/x509util" + "golang.org/x/crypto/ed25519" ) // Options contains the options that can be passed to the Sign method. @@ -78,7 +81,7 @@ func (e emailOnlyIdentity) Valid(req *x509.CertificateRequest) error { case len(req.EmailAddresses) == 0: return errors.New("certificate request does not contain any email address") case len(req.EmailAddresses) > 1: - return errors.New("certificate request does not contain too many email addresses") + return errors.New("certificate request contains too many email addresses") case req.EmailAddresses[0] == "": return errors.New("certificate request cannot contain an empty email address") case req.EmailAddresses[0] != string(e): @@ -88,6 +91,23 @@ func (e emailOnlyIdentity) Valid(req *x509.CertificateRequest) error { } } +// defaultPublicKeyValidator validates the public key of a certificate request. +type defaultPublicKeyValidator struct{} + +// Valid checks that certificate request common name matches the one configured. +func (v defaultPublicKeyValidator) Valid(req *x509.CertificateRequest) error { + switch k := req.PublicKey.(type) { + case *rsa.PublicKey: + if k.Size() < 256 { + return errors.New("rsa key in CSR must be at least 2048 bits (256 bytes)") + } + case *ecdsa.PublicKey, ed25519.PublicKey: + default: + return errors.Errorf("unrecognized public key of type '%T' in CSR", k) + } + return nil +} + // commonNameValidator validates the common name of a certificate request. type commonNameValidator string diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index bc6d7414..38f1d5e6 100644 --- a/authority/provisioner/sign_options_test.go +++ b/authority/provisioner/sign_options_test.go @@ -7,6 +7,12 @@ import ( "net/url" "testing" "time" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/cli/crypto/pemutil" + "github.com/smallstep/cli/crypto/x509util" + stepx509 "github.com/smallstep/cli/pkg/x509" ) func Test_emailOnlyIdentity_Valid(t *testing.T) { @@ -41,6 +47,72 @@ func Test_emailOnlyIdentity_Valid(t *testing.T) { } } +func Test_defaultPublicKeyValidator_Valid(t *testing.T) { + _shortRSA, err := pemutil.Read("./testdata/short-rsa.csr") + assert.FatalError(t, err) + shortRSA, ok := _shortRSA.(*x509.CertificateRequest) + assert.Fatal(t, ok) + + _rsa, err := pemutil.Read("./testdata/rsa.csr") + assert.FatalError(t, err) + rsaCSR, ok := _rsa.(*x509.CertificateRequest) + assert.Fatal(t, ok) + + _ecdsa, err := pemutil.Read("./testdata/ecdsa.csr") + assert.FatalError(t, err) + ecdsaCSR, ok := _ecdsa.(*x509.CertificateRequest) + assert.Fatal(t, ok) + + _ed25519, err := pemutil.Read("./testdata/ed25519.csr", pemutil.WithStepCrypto()) + assert.FatalError(t, err) + ed25519CSR, ok := _ed25519.(*stepx509.CertificateRequest) + assert.Fatal(t, ok) + + v := defaultPublicKeyValidator{} + tests := []struct { + name string + csr *x509.CertificateRequest + err error + }{ + { + "fail/unrecognized-key-type", + &x509.CertificateRequest{PublicKey: "foo"}, + errors.New("unrecognized public key of type 'string' in CSR"), + }, + { + "fail/rsa/too-short", + shortRSA, + errors.New("rsa key in CSR must be at least 2048 bits (256 bytes)"), + }, + { + "ok/rsa", + rsaCSR, + nil, + }, + { + "ok/ecdsa", + ecdsaCSR, + nil, + }, + { + "ok/ed25519", + x509util.ToX509CertificateRequest(ed25519CSR), + nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := v.Valid(tt.csr); err != nil { + if assert.NotNil(t, tt.err) { + assert.HasPrefix(t, err.Error(), tt.err.Error()) + } + } else { + assert.Nil(t, tt.err) + } + }) + } +} + func Test_commonNameValidator_Valid(t *testing.T) { type args struct { req *x509.CertificateRequest diff --git a/authority/provisioner/testdata/ecdsa.csr b/authority/provisioner/testdata/ecdsa.csr new file mode 100644 index 00000000..5fe5be03 --- /dev/null +++ b/authority/provisioner/testdata/ecdsa.csr @@ -0,0 +1,7 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHqMIGRAgEAMA4xDDAKBgNVBAMTA2ZvbzBZMBMGByqGSM49AgEGCCqGSM49AwEH +A0IABKdDjTb7XIYCWC4QUq1xn5hgf3J4WpfWbd3C5frKrA4/VdQ+XfpHQIxDoHqh +jcWke0SEETc9i6HDDtWv8bXSETegITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQH +MAWCA2ZvbzAKBggqhkjOPQQDAgNIADBFAiEA1pFLT8p/YogG0o6NEEmdxzwbOzJA +A+C+DvoT91c1OcQCIGUjP3s+k6Xwdf/VukUZXTfG1lobmkZhO3vYxAjPkwA7 +-----END CERTIFICATE REQUEST----- diff --git a/authority/provisioner/testdata/ecdsa.key b/authority/provisioner/testdata/ecdsa.key new file mode 100644 index 00000000..37c04f0f --- /dev/null +++ b/authority/provisioner/testdata/ecdsa.key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,54abd40e525b255542ee6161ec438721 + +fJvmEc5n0IG4t4FKF+ekKhpog4ods2nZjBR5KLkGH5oSGAOEADSXIRBK76Jnm/nz +Kv8ZwGqxNnoJUQyeTMlyg5OnOUAQPyNBPvoItOlD2DP32WJXgQ+NSHB2h9pcBGYG +yLWrCtzl9/P9REWskanPO4RujP27Ht62omcMO7SxxNI= +-----END EC PRIVATE KEY----- diff --git a/authority/provisioner/testdata/ed25519.csr b/authority/provisioner/testdata/ed25519.csr new file mode 100644 index 00000000..c5ca07a5 --- /dev/null +++ b/authority/provisioner/testdata/ed25519.csr @@ -0,0 +1,6 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIGuMGICAQAwDjEMMAoGA1UEAxMDZm9vMCowBQYDK2VwAyEA3yF/Igqb5UTp6XOq +yj+cZL9nIfjDKrUT0fMzDAHtIqqgITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQH +MAWCA2ZvbzAFBgMrZXADQQAIAx7N6ezi4NL8n0oJU8v3AmVSi0XvTuIHXUtcLGoU +OZtlO3zjWI+DgcT/ADeEKn+T8OazDxcCbTBbHiM2hIsA +-----END CERTIFICATE REQUEST----- diff --git a/authority/provisioner/testdata/ed25519.key b/authority/provisioner/testdata/ed25519.key new file mode 100644 index 00000000..38de1972 --- /dev/null +++ b/authority/provisioner/testdata/ed25519.key @@ -0,0 +1,6 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGkMGAGCSqGSIb3DQEFDTBTMDIGCSqGSIb3DQEFDDAlBBDJ0vCXdpPyUiLlbge5 +1g0jAgMBhqAwDAYIKoZIhvcNAgkAADAdBglghkgBZQMEASoEENtOknzU2eS2mlxl +73Yo/IoEQEyJS2EEx3+oYaKlFIB90e1Zkmi8da7d3r2iUlfc7faRAiKChcEvtEas +vYF2l9LEZ9DXv1Rm1uyNuSpXuddHScE= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/authority/provisioner/testdata/rsa.csr b/authority/provisioner/testdata/rsa.csr new file mode 100644 index 00000000..3352b48c --- /dev/null +++ b/authority/provisioner/testdata/rsa.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICdDCCAVwCAQAwDjEMMAoGA1UEAxMDZm9vMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA86h3t/KJylE0/aPxvF9JqPaOwSsGexuDWqDVJSOWBJi/ZqUA +Ea2Gy05ZIJkQ5GOy0bUs2JCNCVXVkfPrUkX6IvIlXpTjutjMDYyYGdgQjzpKPnOA +v3mO2a7mLMzJunws7pvrUPP7z5KDCKSAPf6VAcu/na8rGDWn1TUYR8hINK1rLQQf +OcyNWrr7yLkR84jSsrw/Qgc8NS//F4ccca1NfZecPEtxgcHjKdDQZ3SYRAfb6Dc0 +jRuvoByAd3q9okOOr70gpMXgpoFVArDynaHMPK9xJ1w2p3s2/NhOYgY9f9rtcWTo +afoAcHK1jy5iQCogFUKt1bUCz5IsaYkRt+D+HQIDAQABoCEwHwYJKoZIhvcNAQkO +MRIwEDAOBgNVHREEBzAFggNmb28wDQYJKoZIhvcNAQELBQADggEBAOsv1UKwEbcY +8Fj2Pl55BjkqQG4PqSQdWJZfK0ol/GRty5XFaTgOUZyTeXOag84OGw0qM0E7kkUa +O5QwDOpnmIgg01Ywr4QM166l1iED+eOUscXJMonBAsS3JNYF1JxcDyKzIl/dt9+w +JXQ64uquuD57amOs8++ROfKW988HzXm0OnoHj8LZ1Mq2yUmxvnnfVnmMpZWo43sA +8NQs4v9dT5wLByFvBjcaWiGVZwZiwT4Q/Msskv9L0o1On0fgCJ6PjLYdblTwMHDZ +syH+X8SsUqeEmyvtiRc1XUeFbxS2hnPXJCXeyfljqwsBNGaVhBXcsV2Lg7IaloBF +/RyWqQZ44eE= +-----END CERTIFICATE REQUEST----- diff --git a/authority/provisioner/testdata/rsa.key b/authority/provisioner/testdata/rsa.key new file mode 100644 index 00000000..b5cf51dc --- /dev/null +++ b/authority/provisioner/testdata/rsa.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,e77ed7e2d2572b5a246a1c4b994190bb + +29o7UA/L7OF7inTPKkwBrtd8CJdXVQs9R3oJPitmFk8SZbrLHEiEhF1C0uB3xK3s +GWQ9O7bjERM3uAvQkd7MSkUUBpyPXS9GFacd85e85d1Ubl6miTAwkFrQnT9yn6n/ +Fak5JtkmdB6ObfVTioOwT1jdtGTifKg1bIhYISgwqCWhgV2fUFk6HQAAIMXTTRc+ +ZK1WbunT7LimYrnN3gQ4ylm/4C8nQl3JCGpvWZaRoH91q1LLD6IwWmX0D09F37dU +X3KoKv/GvuDlV3H1dUBwxhU+GI5/lItPp9OcdLZnnr67Gs+X+do3MFT1h675TM4N +c9QEIJB6RYatLBKHCS7j8W7EbuJAFZ+MCCapP92ERmVVVsWPY+V7CDVQxM2v4X/w +7C7JYx8b4xuQbdvu9KVU5irsXg8hBx7kDb/mtWjT4+8+sseLKA4oOmI6XwVMdbow +MciGilAIaNtWwQHe0EK9E9tiQfc9OyzxdrfplRckAAehHuPGU7+iMCsigCLT3aiV +CDHmnLdTXKIvGe8faTQoJphrb9F8bobGo5D4ZqX5f6gKuPIJsfd/r0GD8VNSF/Q7 +SJQMhkVyaixFB0gQbmea7sTyScdW+Qne7nLpam3ISgo+G4CAH8W88wLnuHMLmvoC +ZE3HvArSeQZ0WPHgB86AfoNRIxd6Emgb+dFyA6wPJC29nZkB8PFSrAHp0zp7KilF +fe9K2dVAUBZFhQthQIYAjJmYLCukLhxUALiqdSmQZrt6DSE33K8s5ed2KJu/60G6 +lZwIzQHPXesRhwmwbkfPB8CyWM+L6osdWv8QyMdM8Wb+66zkhKWBNbm+ccMfP6Zf +1ynF/a/DRX8bf81w+nvLsCGTdxVuEVEpuzS1NclKTmYQu58Ol0RgQe2JSxL89n+A +JAHUu9g9LcTg2jNPjxeA/vusSXMZRrPqrUCYhHhcgR4mE13uyyFI/9frk0gPpKXp +/FislMydWov2JRp1ixzypMBqlFR/zF6j6m3P1g7gchwScWzrZQHD58xdRin4Udiv +OR4huswh5v2i/0KozBoUAwbvPGERnMlTaGoBMPJ5Xe/jkBJw3uC3Dhi74uyUCjqU +hMQW4RJKmuiZVfAIX0RdgeUWXPs+8pf2pXrpIiVHCAHDrxXNMC7X6/9EcBN15B88 +W5/KIRngDeB2oVYrn1GfO7iLu1Rd8VFXyaVItOXq7WrL2pwm8ANhWcFDdnXf6jHW +BcKss1j8rZxOchksf+ZPXhn3QkdooD9iVONky1zLIsV5GPwMe8+yXwXznzJSbHH7 +dOfhK93fZqUwx4gFULwCuWIwLTfNmQ3VzdKioGt39RFDVQb+pbR7p9jv899VjsVO +TBBpRa00fvbK1H2CMVHnwwIf82M4XypSNGR/tSD3AImZPb5RfZnznoXXCMEfYVsd +8/Ry4GHusA+zxCjCxHFtXVkb9sklewJtnUmN5mUzo/81szuigLB5IADR21IOyVBq +A4kz96Ta885Z5owhonfZp1HD53pDEbxCuuIy+fgYfjfDSAj3L/QT3ZKrdcIdYQap +PhrNRW3j38koAatTLd3+E9KqBO5BiY+T5h+Q3XesWnaXInfu5WKiiEm5hHiejA0C +-----END RSA PRIVATE KEY----- diff --git a/authority/provisioner/testdata/short-rsa.csr b/authority/provisioner/testdata/short-rsa.csr new file mode 100644 index 00000000..abbd2ba9 --- /dev/null +++ b/authority/provisioner/testdata/short-rsa.csr @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBdDCB2wIBADAOMQwwCgYDVQQDEwNmb28wgaIwDQYJKoZIhvcNAQEBBQADgZAA +MIGMAoGEAK8dks7oV6kcIFEaWna7CDGYPAE8IL7rNi+ruQ1dIYz+JtxT7OPjbCn/ +t5iqni96+35iS/8CvMtEuquOMTMSWOWwlurrbTbLqCazuz/g233o8udxSxhny3cY +wHogp4cXCX6cFll6DeUnoCEuTTSIu8IBHbK48VfNw4V4gGz6cp/H93HrAgMBAAGg +ITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQHMAWCA2ZvbzANBgkqhkiG9w0BAQsF +AAOBhABCZsYM+Kgje68Z9Fjl2+cBwtQHvZDarh+cz6W1SchinZ1T0aNQvSj/otOe +ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG +2FbarLt9jN2aJLAYQPwtSeGTAZ74tLOPRPnTP6aMfFNg4XCR0uveHA== +-----END CERTIFICATE REQUEST----- diff --git a/authority/tls_test.go b/authority/tls_test.go index 142eedde..5e3a3746 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -7,6 +7,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/base64" + "encoding/pem" "fmt" "net/http" "reflect" @@ -203,6 +204,33 @@ func TestSign(t *testing.T) { }, } }, + "fail rsa key too short": func(t *testing.T) *signTest { + shortRSAKeyPEM := `-----BEGIN CERTIFICATE REQUEST----- +MIIBdDCB2wIBADAOMQwwCgYDVQQDEwNmb28wgaIwDQYJKoZIhvcNAQEBBQADgZAA +MIGMAoGEAK8dks7oV6kcIFEaWna7CDGYPAE8IL7rNi+ruQ1dIYz+JtxT7OPjbCn/ +t5iqni96+35iS/8CvMtEuquOMTMSWOWwlurrbTbLqCazuz/g233o8udxSxhny3cY +wHogp4cXCX6cFll6DeUnoCEuTTSIu8IBHbK48VfNw4V4gGz6cp/H93HrAgMBAAGg +ITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQHMAWCA2ZvbzANBgkqhkiG9w0BAQsF +AAOBhABCZsYM+Kgje68Z9Fjl2+cBwtQHvZDarh+cz6W1SchinZ1T0aNQvSj/otOe +ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG +2FbarLt9jN2aJLAYQPwtSeGTAZ74tLOPRPnTP6aMfFNg4XCR0uveHA== +-----END CERTIFICATE REQUEST-----` + block, _ := pem.Decode([]byte(shortRSAKeyPEM)) + assert.FatalError(t, err) + csr, err := x509.ParseCertificateRequest(block.Bytes) + assert.FatalError(t, err) + + return &signTest{ + auth: a, + csr: csr, + extraOpts: extraOpts, + signOpts: signOpts, + err: &apiError{errors.New("sign: rsa key in CSR must be at least 2048 bits (256 bytes)"), + http.StatusUnauthorized, + context{"csr": csr, "signOptions": signOpts}, + }, + } + }, "fail store cert in db": func(t *testing.T) *signTest { csr := getCSR(t, priv) _a := testAuthority(t)