package pki import ( "context" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "testing" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" "github.com/stretchr/testify/require" ) func TestPKI_PathManageKeys_GenerateInternalKeys(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) tests := []struct { name string keyType string keyBits []int wantLogicalErr bool }{ {"all-defaults", "", []int{0}, false}, {"rsa", "rsa", []int{0, 2048, 3072, 4096}, false}, {"ec", "ec", []int{0, 224, 256, 384, 521}, false}, {"ed25519", "ed25519", []int{0}, false}, {"error-rsa", "rsa", []int{-1, 343444}, true}, {"error-ec", "ec", []int{-1, 3434324}, true}, {"error-bad-type", "dskjfkdsfjdkf", []int{0}, true}, } for _, tt := range tests { tt := tt for _, keyBitParam := range tt.keyBits { keyName := fmt.Sprintf("%s-%d", tt.name, keyBitParam) t.Run(keyName, func(t *testing.T) { data := make(map[string]interface{}) if tt.keyType != "" { data["key_type"] = tt.keyType } if keyBitParam != 0 { data["key_bits"] = keyBitParam } keyName = genUuid() + "-" + tt.keyType + "-key-name" data["key_name"] = keyName resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/generate/internal", Storage: s, Data: data, MountPoint: "pki/", }) require.NoError(t, err, "Failed generating key with values key_type:%s key_bits:%d key_name:%s", tt.keyType, keyBitParam, keyName) require.NotNil(t, resp, "Got nil response generating key with values key_type:%s key_bits:%d key_name:%s", tt.keyType, keyBitParam, keyName) if tt.wantLogicalErr { require.True(t, resp.IsError(), "expected logical error but the request passed:\n%#v", resp) } else { require.False(t, resp.IsError(), "Got logical error response when not expecting one, "+ "generating key with values key_type:%s key_bits:%d key_name:%s\n%s", tt.keyType, keyBitParam, keyName, resp.Error()) // Special case our all-defaults if tt.keyType == "" { tt.keyType = "rsa" } require.Equal(t, tt.keyType, resp.Data["key_type"], "key_type field contained an invalid type") require.NotEmpty(t, resp.Data["key_id"], "returned an empty key_id field, should never happen") require.Equal(t, keyName, resp.Data["key_name"], "key name was not processed correctly") require.Nil(t, resp.Data["private_key"], "private_key field should not appear in internal generation type.") } }) } } } func TestPKI_PathManageKeys_GenerateExportedKeys(t *testing.T) { t.Parallel() // We tested a lot of the logic above within the internal test, so just make sure we honor the exported contract b, s := createBackendWithStorage(t) resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/generate/exported", Storage: s, Data: map[string]interface{}{ "key_type": "ec", "key_bits": 224, }, MountPoint: "pki/", }) require.NoError(t, err, "Failed generating exported key") require.NotNil(t, resp, "Got nil response generating exported key") require.Equal(t, "ec", resp.Data["key_type"], "key_type field contained an invalid type") require.NotEmpty(t, resp.Data["key_id"], "returned an empty key_id field, should never happen") require.Empty(t, resp.Data["key_name"], "key name should have been empty but was not") require.NotEmpty(t, resp.Data["private_key"], "private_key field should not be empty in exported generation type.") // Make sure we can decode our private key as expected keyData := resp.Data["private_key"].(string) block, rest := pem.Decode([]byte(keyData)) require.Empty(t, rest, "should not have had any trailing data") require.NotEmpty(t, block, "failed decoding pem block") key, err := x509.ParseECPrivateKey(block.Bytes) require.NoError(t, err, "failed parsing pem block as ec private key") require.Equal(t, elliptic.P224(), key.Curve, "got unexpected curve value in returned private key") } func TestPKI_PathManageKeys_ImportKeyBundle(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) bundle1, err := certutil.CreateKeyBundle("ec", 224, rand.Reader) require.NoError(t, err, "failed generating an ec key bundle") bundle2, err := certutil.CreateKeyBundle("rsa", 2048, rand.Reader) require.NoError(t, err, "failed generating an rsa key bundle") pem1, err := bundle1.ToPrivateKeyPemString() require.NoError(t, err, "failed converting ec key to pem") pem2, err := bundle2.ToPrivateKeyPemString() require.NoError(t, err, "failed converting rsa key to pem") resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-ec-key", "pem_bundle": pem1, }, MountPoint: "pki/", }) require.NoError(t, err, "Failed importing ec key") require.NotNil(t, resp, "Got nil response importing ec key") require.False(t, resp.IsError(), "received an error response: %v", resp.Error()) require.NotEmpty(t, resp.Data["key_id"], "key id for ec import response was empty") require.Equal(t, "my-ec-key", resp.Data["key_name"], "key_name was incorrect for ec key") require.Equal(t, certutil.ECPrivateKey, resp.Data["key_type"]) keyId1 := resp.Data["key_id"].(keyID) resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-rsa-key", "pem_bundle": pem2, }, MountPoint: "pki/", }) require.NoError(t, err, "Failed importing rsa key") require.NotNil(t, resp, "Got nil response importing rsa key") require.False(t, resp.IsError(), "received an error response: %v", resp.Error()) require.NotEmpty(t, resp.Data["key_id"], "key id for rsa import response was empty") require.Equal(t, "my-rsa-key", resp.Data["key_name"], "key_name was incorrect for ec key") require.Equal(t, certutil.RSAPrivateKey, resp.Data["key_type"]) keyId2 := resp.Data["key_id"].(keyID) require.NotEqual(t, keyId1, keyId2) // Attempt to reimport the same key with a different name. resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-new-ec-key", "pem_bundle": pem1, }, MountPoint: "pki/", }) require.NoError(t, err, "Failed importing the same ec key") require.NotNil(t, resp, "Got nil response importing the same ec key") require.False(t, resp.IsError(), "received an error response: %v", resp.Error()) require.NotEmpty(t, resp.Data["key_id"], "key id for ec import response was empty") // Note we should receive back the original name, not the new updated name. require.Equal(t, "my-ec-key", resp.Data["key_name"], "key_name was incorrect for ec key") require.Equal(t, certutil.ECPrivateKey, resp.Data["key_type"]) keyIdReimport := resp.Data["key_id"] require.Equal(t, keyId1, keyIdReimport, "the re-imported key did not return the same key id") // Make sure we can not reuse an existing name across different keys. bundle3, err := certutil.CreateKeyBundle("ec", 224, rand.Reader) require.NoError(t, err, "failed generating an ec key bundle") pem3, err := bundle3.ToPrivateKeyPemString() require.NoError(t, err, "failed converting rsa key to pem") resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-ec-key", "pem_bundle": pem3, }, MountPoint: "pki/", }) require.NoError(t, err, "Failed importing the same ec key") require.NotNil(t, resp, "Got nil response importing the same ec key") require.True(t, resp.IsError(), "should have received an error response importing a key with a re-used name") // Delete the key to make sure re-importing gets another ID resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.DeleteOperation, Path: "key/" + keyId2.String(), Storage: s, MountPoint: "pki/", }) require.NoError(t, err, "failed deleting keyId 2") require.Nil(t, resp, "Got non-nil response deleting the key: %#v", resp) // Deleting a non-existent key should be okay... resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.DeleteOperation, Path: "key/" + keyId2.String(), Storage: s, MountPoint: "pki/", }) require.NoError(t, err, "failed deleting keyId 2") require.Nil(t, resp, "Got non-nil response deleting the key: %#v", resp) // Let's reimport key 2 post-deletion to make sure we re-generate a new key id resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-rsa-key", "pem_bundle": pem2, }, MountPoint: "pki/", }) require.NoError(t, err, "Failed importing rsa key") require.NotNil(t, resp, "Got nil response importing rsa key") require.False(t, resp.IsError(), "received an error response: %v", resp.Error()) require.NotEmpty(t, resp.Data["key_id"], "key id for rsa import response was empty") require.Equal(t, "my-rsa-key", resp.Data["key_name"], "key_name was incorrect for ec key") require.Equal(t, certutil.RSAPrivateKey, resp.Data["key_type"]) keyId2Reimport := resp.Data["key_id"].(keyID) require.NotEqual(t, keyId2, keyId2Reimport, "re-importing key 2 did not generate a new key id") } func TestPKI_PathManageKeys_DeleteDefaultKeyWarns(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/generate/internal", Storage: s, Data: map[string]interface{}{"key_type": "ec"}, MountPoint: "pki/", }) require.NoError(t, err, "Failed generating key") require.NotNil(t, resp, "Got nil response generating key") require.False(t, resp.IsError(), "resp contained errors generating key: %#v", resp.Error()) keyId := resp.Data["key_id"].(keyID) resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.DeleteOperation, Path: "key/" + keyId.String(), Storage: s, MountPoint: "pki/", }) require.NoError(t, err, "failed deleting default key") require.NotNil(t, resp, "Got nil response deleting the default key") require.False(t, resp.IsError(), "expected no errors deleting default key: %#v", resp.Error()) require.NotEmpty(t, resp.Warnings, "expected warnings to be populated on deleting default key") } func TestPKI_PathManageKeys_DeleteUsedKeyFails(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "issuers/generate/root/internal", Storage: s, Data: map[string]interface{}{"common_name": "test.com"}, MountPoint: "pki/", }) require.NoError(t, err, "Failed generating issuer") require.NotNil(t, resp, "Got nil response generating issuer") require.False(t, resp.IsError(), "resp contained errors generating issuer: %#v", resp.Error()) keyId := resp.Data["key_id"].(keyID) resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.DeleteOperation, Path: "key/" + keyId.String(), Storage: s, MountPoint: "pki/", }) require.NoError(t, err, "failed deleting key used by an issuer") require.NotNil(t, resp, "Got nil response deleting key used by an issuer") require.True(t, resp.IsError(), "expected an error deleting key used by an issuer") } func TestPKI_PathManageKeys_UpdateKeyDetails(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/generate/internal", Storage: s, Data: map[string]interface{}{"key_type": "ec"}, MountPoint: "pki/", }) require.NoError(t, err, "Failed generating key") require.NotNil(t, resp, "Got nil response generating key") require.False(t, resp.IsError(), "resp contained errors generating key: %#v", resp.Error()) keyId := resp.Data["key_id"].(keyID) resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "key/" + keyId.String(), Storage: s, Data: map[string]interface{}{"key_name": "new-name"}, MountPoint: "pki/", }) require.NoError(t, err, "failed updating key with new name") require.NotNil(t, resp, "Got nil response updating key with new name") require.False(t, resp.IsError(), "unexpected error updating key with new name: %#v", resp.Error()) resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.ReadOperation, Path: "key/" + keyId.String(), Storage: s, MountPoint: "pki/", }) require.NoError(t, err, "failed reading key after name update") require.NotNil(t, resp, "Got nil response reading key after name update") require.False(t, resp.IsError(), "unexpected error reading key: %#v", resp.Error()) keyName := resp.Data["key_name"].(string) require.Equal(t, "new-name", keyName, "failed to update key_name expected: new-name was: %s", keyName) // Make sure we do not allow updates to invalid name values resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "key/" + keyId.String(), Storage: s, Data: map[string]interface{}{"key_name": "a-bad\\-name"}, MountPoint: "pki/", }) require.NoError(t, err, "failed updating key with a bad name") require.NotNil(t, resp, "Got nil response updating key with a bad name") require.True(t, resp.IsError(), "expected an error updating key with a bad name, but did not get one.") } func TestPKI_PathManageKeys_ImportKeyBundleBadData(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-ec-key", "pem_bundle": "this-is-not-a-pem-bundle", }, MountPoint: "pki/", }) require.NoError(t, err, "got a 500 error type response from a bad pem bundle") require.NotNil(t, resp, "Got nil response importing a bad pem bundle") require.True(t, resp.IsError(), "should have received an error response importing a bad pem bundle") // Make sure we also bomb on a proper certificate bundle := genCertBundle(t, b, s) resp, err = b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "pem_bundle": bundle.Certificate, }, MountPoint: "pki/", }) require.NoError(t, err, "got a 500 error type response from a certificate pem bundle") require.NotNil(t, resp, "Got nil response importing a certificate bundle") require.True(t, resp.IsError(), "should have received an error response importing a certificate pem bundle") } func TestPKI_PathManageKeys_ImportKeyRejectsMultipleKeys(t *testing.T) { t.Parallel() b, s := createBackendWithStorage(t) bundle1, err := certutil.CreateKeyBundle("ec", 224, rand.Reader) require.NoError(t, err, "failed generating an ec key bundle") bundle2, err := certutil.CreateKeyBundle("rsa", 2048, rand.Reader) require.NoError(t, err, "failed generating an rsa key bundle") pem1, err := bundle1.ToPrivateKeyPemString() require.NoError(t, err, "failed converting ec key to pem") pem2, err := bundle2.ToPrivateKeyPemString() require.NoError(t, err, "failed converting rsa key to pem") importPem := pem1 + "\n" + pem2 resp, err := b.HandleRequest(context.Background(), &logical.Request{ Operation: logical.UpdateOperation, Path: "keys/import", Storage: s, Data: map[string]interface{}{ "key_name": "my-ec-key", "pem_bundle": importPem, }, MountPoint: "pki/", }) require.NoError(t, err, "got a 500 error type response from a bad pem bundle") require.NotNil(t, resp, "Got nil response importing a bad pem bundle") require.True(t, resp.IsError(), "should have received an error response importing a pem bundle with more than 1 key") ctx := context.Background() sc := b.makeStorageContext(ctx, s) keys, _ := sc.listKeys() for _, keyId := range keys { id, _ := sc.fetchKeyById(keyId) t.Logf("%s:%s", id.ID, id.Name) } }