diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 4a88fbd748..7561b73ac6 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -5630,6 +5630,209 @@ func TestBackend_VerifyIssuerUpdateDefaultsMatchCreation(t *testing.T) { "most likely we have a different set of field parameters across create and update of issuers.") } +func TestBackend_VerifyPSSKeysIssuersFailImport(t *testing.T) { + t.Parallel() + b, s := createBackendWithStorage(t) + + // PKCS8 parsing fails on this key due to rsaPSS OID + rsaOIDKey := ` +-----BEGIN PRIVATE KEY----- +MIIEugIBADALBgkqhkiG9w0BAQoEggSmMIIEogIBAAKCAQEAtN0/NPuJHLuyEdBr +tUikXoXOV741XZcNvLAIVBIqDA0ege2gXt9A15FGUI4X3u6kT16Fl6MRdtUZ/qNS +Vs15nK9A1PI/AVekMgTVFTnoCzs550CKN8iRk9Om+lwHimpyXxKkFW69v8fsXwKE +Bsz69jjT7HV9VZQ7fQhmE79brAMuwKP1fUQKdHq5OBKtQ7Cl3Gmipp0izCsVuQIE +kBHvT3UUgyaSp2n+FONpOiyuBoYUH5tVEv9sZzBqSsrYBJYF+GvfnFy9AcTdqRe2 +VX2SjjWjDF84T30OBA798gIFIPwu9R4OjWOlPeh2bo2kGeo3AITjwFZ28m7kS7kc +OtvHpwIDAQABAoIBAFQxmjbj0RQbG+3HBBzD0CBgUYnu9ZC3vKFVoMriGci6YrVB +FSKU8u5mpkDhpKMWnE6GRdItCvgyg4NSLAZUaIRT4O5ARqwtTDYsobTb2/U+gNnx +5WXKbFpQcK6jIK+ClfNEDjYb8yDPxG0GEsfHrBvqoFy25L1t37N4sWwH7HjJyZIe +Hbqx4NVDur9qgqaUwkfSeufn4ycHqFtkzKNzCUarDkST9cxE6/1AKfhl09PPuMEa +lAY2JLiEplQL5sh9cxG5FObJbutJo5EIhR2OdM0VcPf0MTD9LXKRoGR3SNlG7IlS +llJzBjlh4J1ByMX32btKMHzEvlhyrMI90E1SEGECgYEAx1yDQWe4/b1MBqCxA3d0 +20dDmUHSRQFhkd/Mzkl5dPzRkG42W3ryNbMKdeuL0ZgK9AhfaLCjcj1i+44O7dHb +qBTVwfRrer2uoQVCqqJ6z8PGxPJJxTaqh9QuJxkoQ0i43ZNPcjc2M2sWLn+lkkdE +MaGMiyrmjIQEC6tmgCtZ1VUCgYEA6D9xoT9VuAnQjDvW2tO5N2U2H/8ZyRd1pC3z +H1CzjwShhxsP4YOUaVdw59K95JL4SMxSmpRrhthlW3cRaiT/exBcXLEvz0Qu0OhW +a6155ZFjK3UaLDKlwvmtuoAsuAFqX084LO0B1oxvUJESgyPncQ36fv2lZGV7A66z +Uo+BKQsCgYB2yGBMMAjA5nDN4iCV+C7gF+3m+pjWFKSVzcqxfoWndptGeuRYTUDT +TgIFkHqWPwkHrZVrQxOflYPMbi/m8wr1crSKA5+mWi4aMpAuKvERqYxc/B+IKbIh +jAKTuSGMNWAwZP0JCGx65mso+VUleuDe0Wpz4PPM9TuT2GQSKcI0oQKBgHAHcouC +npmo+lU65DgoWzaydrpWdpy+2Tt6AsW/Su4ZIMWoMy/oJaXuzQK2cG0ay/NpxArW +v0uLhNDrDZZzBF3blYIM4nALhr205UMJqjwntnuXACoDwFvdzoShIXEdFa+l6gYZ +yYIxudxWLmTd491wDb5GIgrcvMsY8V1I5dfjAoGAM9g2LtdqgPgK33dCDtZpBm8m +y4ri9PqHxnpps9WJ1dO6MW/YbW+a7vbsmNczdJ6XNLEfy2NWho1dw3xe7ztFVDjF +cWNUzs1+/6aFsi41UX7EFn3zAFhQUPxT59hXspuWuKbRAWc5fMnxbCfI/Cr8wTLJ +E/0kiZ4swUMyI4tYSbM= +-----END PRIVATE KEY----- +` + _, err := CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ + "pem_bundle": rsaOIDKey, + }) + require.Error(t, err, "expected error importing PKCS8 rsaPSS OID key") + + _, err = CBWrite(b, s, "keys/import", map[string]interface{}{ + "key": rsaOIDKey, + }) + require.Error(t, err, "expected error importing PKCS8 rsaPSS OID key") + + // Importing a cert with rsaPSS OID should also fail + rsaOIDCert := ` +-----BEGIN CERTIFICATE----- +MIIDfjCCAjGgAwIBAgIBATBCBgkqhkiG9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUA +oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogQCAgDeMBMxETAPBgNVBAMM +CHJvb3Qtb2xkMB4XDTIyMDkxNjE0MDEwM1oXDTIzMDkyNjE0MDEwM1owEzERMA8G +A1UEAwwIcm9vdC1vbGQwggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKCAQEAtN0/ +NPuJHLuyEdBrtUikXoXOV741XZcNvLAIVBIqDA0ege2gXt9A15FGUI4X3u6kT16F +l6MRdtUZ/qNSVs15nK9A1PI/AVekMgTVFTnoCzs550CKN8iRk9Om+lwHimpyXxKk +FW69v8fsXwKEBsz69jjT7HV9VZQ7fQhmE79brAMuwKP1fUQKdHq5OBKtQ7Cl3Gmi +pp0izCsVuQIEkBHvT3UUgyaSp2n+FONpOiyuBoYUH5tVEv9sZzBqSsrYBJYF+Gvf +nFy9AcTdqRe2VX2SjjWjDF84T30OBA798gIFIPwu9R4OjWOlPeh2bo2kGeo3AITj +wFZ28m7kS7kcOtvHpwIDAQABo3UwczAdBgNVHQ4EFgQUVGkTAUJ8inxIVGBlfxf4 +cDhRSnowHwYDVR0jBBgwFoAUVGkTAUJ8inxIVGBlfxf4cDhRSnowDAYDVR0TBAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwEwQgYJKoZI +hvcNAQEKMDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgB +ZQMEAgEFAKIEAgIA3gOCAQEAQZ3iQ3NjvS4FYJ5WG41huZI0dkvNFNan+ZYWlYHJ +MIQhbFogb/UQB0rlsuldG0+HF1RDXoYNuThfzt5hiBWYEtMBNurezvnOn4DF0hrl +Uk3sBVnvTalVXg+UVjqh9hBGB75JYJl6a5Oa2Zrq++4qGNwjd0FqgnoXzqS5UGuB +TJL8nlnXPuOIK3VHoXEy7l9GtvEzKcys0xa7g1PYpaJ5D2kpbBJmuQGmU6CDcbP+ +m0hI4QDfVfHtnBp2VMCvhj0yzowtwF4BFIhv4EXZBU10mzxVj0zyKKft9++X8auH +nebuK22ZwzbPe4NhOvAdfNDElkrrtGvTnzkDB7ezPYjelA== +-----END CERTIFICATE----- +` + _, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ + "pem_bundle": rsaOIDCert, + }) + require.Error(t, err, "expected error importing PKCS8 rsaPSS OID cert") + + _, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ + "pem_bundle": rsaOIDKey + "\n" + rsaOIDCert, + }) + require.Error(t, err, "expected error importing PKCS8 rsaPSS OID key+cert") + + _, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ + "pem_bundle": rsaOIDCert + "\n" + rsaOIDKey, + }) + require.Error(t, err, "expected error importing PKCS8 rsaPSS OID cert+key") + + // After all these errors, we should have zero issuers and keys. + resp, err := CBList(b, s, "issuers") + require.NoError(t, err) + require.Equal(t, nil, resp.Data["keys"]) + + resp, err = CBList(b, s, "keys") + require.NoError(t, err) + require.Equal(t, nil, resp.Data["keys"]) + + // If we create a new PSS root, we should be able to issue an intermediate + // under it. + resp, err = CBWrite(b, s, "root/generate/exported", map[string]interface{}{ + "use_pss": "true", + "common_name": "root x1 - pss", + "key_type": "ec", + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + require.NotEmpty(t, resp.Data["certificate"]) + require.NotEmpty(t, resp.Data["private_key"]) + + resp, err = CBWrite(b, s, "intermediate/generate/exported", map[string]interface{}{ + "use_pss": "true", + "common_name": "int x1 - pss", + "key_type": "ec", + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + require.NotEmpty(t, resp.Data["csr"]) + require.NotEmpty(t, resp.Data["private_key"]) + + resp, err = CBWrite(b, s, "issuer/default/sign-intermediate", map[string]interface{}{ + "use_pss": "true", + "common_name": "int x1 - pss", + "csr": resp.Data["csr"].(string), + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + require.NotEmpty(t, resp.Data["certificate"]) + + resp, err = CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ + "pem_bundle": resp.Data["certificate"].(string), + }) + require.NoError(t, err) + + // Finally, if we were to take an rsaPSS OID'd CSR and use it against this + // mount, it will fail. + _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ + "allow_any_name": true, + "ttl": "85s", + "key_type": "any", + }) + require.NoError(t, err) + + // Issuing a leaf from a CSR with rsaPSS OID should fail... + rsaOIDCSR := `-----BEGIN CERTIFICATE REQUEST----- +MIICkTCCAUQCAQAwGTEXMBUGA1UEAwwOcmFuY2hlci5teS5vcmcwggEgMAsGCSqG +SIb3DQEBCgOCAQ8AMIIBCgKCAQEAtzHuGEUK55lXI08yp9DXoye9yCZbkJZO+Hej +1TWGEkbX4hzauRJeNp2+wn8xU5y8ITjWSIXEVDHeezosLCSy0Y2QT7/V45zWPUYY +ld0oUnPiwsb9CPFlBRFnX3dO9SS5MONIrNCJGKXmLdF3lgSl8zPT6J/hWM+JBjHO +hBzK6L8IYwmcEujrQfnOnOztzgMEBJtWG8rnI8roz1adpczTddDKGymh2QevjhlL +X9CLeYSSQZInOMsgaDYl98Hn00K5x0CBp8ADzzXtaPSQ9nsnihN8VvZ/wHw6YbBS +BSHa6OD+MrYnw3Sao6/YgBRNT2glIX85uro4ARW9zGB9/748dwIDAQABoAAwQgYJ +KoZIhvcNAQEKMDWgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglg +hkgBZQMEAgEFAKIEAgIA3gOCAQEARGAa0HiwzWCpvAdLOVc4/srEyOYFZPLbtv+Y +ezZIaUBNaWhOvkunqpa48avmcbGlji7r6fxJ5sT28lHt7ODWcJfn1XPAnqesXErm +EBuOIhCv6WiwVyGeTVynuHYkHyw3rIL/zU7N8+zIFV2G2M1UAv5D/eyh/74cr9Of ++nvm9jAbkHix8UwOBCFY2LLNl6bXvbIeJEdDOEtA9UmDXs8QGBg4lngyqcE2Z7rz ++5N/x4guMk2FqblbFGiCc5fLB0Gp6lFFOqhX9Q8nLJ6HteV42xGJUUtsFpppNCRm +82dGIH2PTbXZ0k7iAAwLaPjzOv1v58Wq90o35d4iEsOfJ8v98Q== +-----END CERTIFICATE REQUEST-----` + + _, err = CBWrite(b, s, "issuer/default/sign/testing", map[string]interface{}{ + "common_name": "example.com", + "csr": rsaOIDCSR, + }) + require.Error(t, err) + + _, err = CBWrite(b, s, "issuer/default/sign-verbatim", map[string]interface{}{ + "common_name": "example.com", + "use_pss": true, + "csr": rsaOIDCSR, + }) + require.Error(t, err) + + _, err = CBWrite(b, s, "issuer/default/sign-intermediate", map[string]interface{}{ + "common_name": "faulty x1 - pss", + "use_pss": true, + "csr": rsaOIDCSR, + }) + require.Error(t, err) + + // Vault has a weird API for signing self-signed certificates. Ensure + // that doesn't accept rsaPSS OID'd certificates either. + _, err = CBWrite(b, s, "issuer/default/sign-self-issued", map[string]interface{}{ + "use_pss": true, + "certificate": rsaOIDCert, + }) + require.Error(t, err) + + // Issuing a regular leaf should succeed. + _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ + "allow_any_name": true, + "ttl": "85s", + "key_type": "rsa", + "use_pss": "true", + }) + require.NoError(t, err) + + resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ + "common_name": "example.com", + "use_pss": "true", + }) + requireSuccessNonNilResponse(t, resp, err, "failed to issue PSS leaf") +} + var ( initTest sync.Once rsaCAKey string diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index 9d6ce410b2..5608310637 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -765,6 +765,10 @@ func signCert(b *backend, return nil, nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)} } + if csr.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm || csr.PublicKey == nil { + return nil, nil, errutil.UserError{Err: "Refusing to sign CSR with empty PublicKey. This usually means the SubjectPublicKeyInfo field has an OID not recognized by Go, such as 1.2.840.113549.1.1.10 for rsaPSS."} + } + // This switch validates that the CSR key type matches the role and sets // the value in the actualKeyType/actualKeyBits values. actualKeyType := "" diff --git a/builtin/logical/pki/crl_test.go b/builtin/logical/pki/crl_test.go index 903921bae0..962cd650f9 100644 --- a/builtin/logical/pki/crl_test.go +++ b/builtin/logical/pki/crl_test.go @@ -18,6 +18,7 @@ import ( ) func TestBackend_CRL_EnableDisableRoot(t *testing.T) { + t.Parallel() b, s := createBackendWithStorage(t) resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ @@ -33,6 +34,7 @@ func TestBackend_CRL_EnableDisableRoot(t *testing.T) { } func TestBackend_CRLConfigUpdate(t *testing.T) { + t.Parallel() b, s := createBackendWithStorage(t) // Write a legacy config to storage. @@ -137,6 +139,8 @@ func TestBackend_CRLConfig(t *testing.T) { } func TestBackend_CRL_AllKeyTypeSigAlgos(t *testing.T) { + t.Parallel() + type testCase struct { KeyType string KeyBits int @@ -192,10 +196,12 @@ func TestBackend_CRL_AllKeyTypeSigAlgos(t *testing.T) { } func TestBackend_CRL_EnableDisableIntermediateWithRoot(t *testing.T) { + t.Parallel() crlEnableDisableIntermediateTestForBackend(t, true) } func TestBackend_CRL_EnableDisableIntermediateWithoutRoot(t *testing.T) { + t.Parallel() crlEnableDisableIntermediateTestForBackend(t, false) } @@ -354,6 +360,7 @@ func crlEnableDisableTestForBackend(t *testing.T, b *backend, s logical.Storage, } func TestBackend_Secondary_CRL_Rebuilding(t *testing.T) { + t.Parallel() ctx := context.Background() b, s := createBackendWithStorage(t) sc := b.makeStorageContext(ctx, s) @@ -378,6 +385,7 @@ func TestBackend_Secondary_CRL_Rebuilding(t *testing.T) { } func TestCrlRebuilder(t *testing.T) { + t.Parallel() ctx := context.Background() b, s := createBackendWithStorage(t) sc := b.makeStorageContext(ctx, s) diff --git a/builtin/logical/pki/storage.go b/builtin/logical/pki/storage.go index 64bf9a268e..dd4b4174ba 100644 --- a/builtin/logical/pki/storage.go +++ b/builtin/logical/pki/storage.go @@ -702,6 +702,12 @@ func (sc *storageContext) importIssuer(certValue string, issuerName string) (*is return nil, false, errutil.UserError{Err: "Refusing to import non-CA certificate"} } + // Ensure this certificate has a parsed public key. Otherwise, we've + // likely been given a bad certificate. + if issuerCert.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm || issuerCert.PublicKey == nil { + return nil, false, errutil.UserError{Err: "Refusing to import CA certificate with empty PublicKey. This usually means the SubjectPublicKeyInfo field has an OID not recognized by Go, such as 1.2.840.113549.1.1.10 for rsaPSS."} + } + // Before we can import a known issuer, we first need to know if the issuer // exists in storage already. This means iterating through all known // issuers and comparing their private value against this value. diff --git a/website/content/docs/secrets/pki/considerations.mdx b/website/content/docs/secrets/pki/considerations.mdx index 7cf5f99eb0..c0d6f22471 100644 --- a/website/content/docs/secrets/pki/considerations.mdx +++ b/website/content/docs/secrets/pki/considerations.mdx @@ -35,6 +35,7 @@ generating the CA to use with this secrets engine. - [Role-Based Access](#role-based-access) - [Replicated DataSets](#replicated-datasets) - [Cluster Scalability](#cluster-scalability) + - [PSS Support](#pss-support) ## Be Careful with Root CAs @@ -564,6 +565,31 @@ set to false. If `generate_lease` is true the lease creation will be forwarded t the active node; if `no_store` is false the entire request will be forwarded to the active node. +## PSS Support + +Go lacks support for PSS certificates, keys, and CSRs using the `rsaPSS` OID +(`1.2.840.113549.1.1.10`). It requires all RSA certificates, keys, and CSRs +to use the alternative `rsaEncryption` OID (`1.2.840.113549.1.1.1`). + +When using OpenSSL to generate CAs or CSRs from PKCS8-encoded PSS keys, the +resulting CAs and CSRs will have the `rsaPSS` OID. Go and Vault will reject +them. Instead, use OpenSSL to generate or convert to a PKCS#1v1.5 private +key file and use this to generate the CSR. Vault will, depending on the role +and the signing mechanism, still use a PSS signature despite the +`rsaEncryption` OID on the request as the SubjectPublicKeyInfo and +SignatureAlgorithm fields are orthogonal. When creating an external CA and +importing it into Vault, ensure that the `rsaEncryption` OID is present on +the SubjectPublicKeyInfo field even if the SignatureAlgorithm is PSS-based. + +These certificates generated by Go (with `rsaEncryption` OID but PSS-based +signatures) are otherwise compatible with the fully PSS-based certificates. +OpenSSL and NSS support parsing and verifying chains using this type of +certificate. Note that some TLS implementations may not support these types +of certificates if they do not support `rsa_pss_rsae_*` signature schemes. +Additionally, some implementations allow rsaPSS OID certificates to contain +restrictions on signature parameters allowed by this certificate, but Go and +Vault do not support adding such restrictions. + ## Tutorial Refer to the [Build Your Own Certificate Authority (CA)](https://learn.hashicorp.com/vault/secrets-management/sm-pki-engine)