diff --git a/acceptance/openstack/kms/v1/keys_test.go b/acceptance/openstack/kms/v1/keys_test.go index 6c9b136f5..afae532da 100644 --- a/acceptance/openstack/kms/v1/keys_test.go +++ b/acceptance/openstack/kms/v1/keys_test.go @@ -1,6 +1,11 @@ package v1 import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "regexp" "testing" "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" @@ -84,3 +89,64 @@ func TestKmsEncryptDataLifecycle(t *testing.T) { tools.PrintResource(t, kmsDecrypt) } + +// Test requries an external kms key in `pending import` state +// and can't be run more than once for the key or an error will occur: +// `The plain key value must be same with imported before when try to re-import a deleted key material` +func TestKmsCMKImportLifecycle(t *testing.T) { + kmsID := clients.EnvOS.GetEnv("KMS_ID") + if kmsID == "" { + t.Skip("OS_KMS_ID env var is missing but KMSv1 grant test requires") + } + + client, err := clients.NewKMSV1Client() + th.AssertNoErr(t, err) + + getOpts := keys.GetCMKImportOpts{ + KeyId: kmsID, + WrappingAlgorithm: "RSAES_PKCS1_V1_5", + } + + getResp, err := keys.GetCMKImport(client, getOpts) + th.AssertNoErr(t, err) + + publicKeyBytes, err := base64.StdEncoding.DecodeString(getResp.PublicKey) + th.AssertNoErr(t, err) + + pubKey, err := x509.ParsePKIXPublicKey(publicKeyBytes) + th.AssertNoErr(t, err) + + rsaPublicKey, ok := pubKey.(*rsa.PublicKey) + th.AssertEquals(t, ok, true) + + keyMaterial := make([]byte, 32) + _, err = rand.Read(keyMaterial) + th.AssertNoErr(t, err) + + encryptedKeyMaterial, err := rsa.EncryptPKCS1v15( + rand.Reader, + rsaPublicKey, + keyMaterial, + ) + th.AssertNoErr(t, err) + + publicMaterial := base64.StdEncoding.EncodeToString(encryptedKeyMaterial) + + matched, err := regexp.MatchString("^[0-9a-zA-Z+/=]{344,360}$", publicMaterial) + th.AssertNoErr(t, err) + th.AssertEquals(t, matched, true) + + kmsOpts := keys.ImportCMKOpts{ + KeyId: kmsID, + ImportToken: getResp.ImportToken, + EncryptedKeyMaterial: publicMaterial, + } + + err = keys.ImportCMKMaterial(client, kmsOpts) + th.AssertNoErr(t, err) + + err = keys.DeleteCMKImport(client, keys.DeleteCMKImportOpts{ + KeyId: kmsID, + }) + th.AssertNoErr(t, err) +} diff --git a/openstack/kms/v1/keys/Create.go b/openstack/kms/v1/keys/Create.go index 82e9ed86f..9b72c6c60 100644 --- a/openstack/kms/v1/keys/Create.go +++ b/openstack/kms/v1/keys/Create.go @@ -15,6 +15,8 @@ type CreateOpts struct { Realm string `json:"realm,omitempty"` // Purpose of a CMK (The default value is Encrypt_Decrypt) KeyUsage string `json:"key_usage,omitempty"` + // Origin of a CMK. The default value is kms. Possible values: `kms` / `external` + Origin string `json:"origin,omitempty"` } func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*Key, error) { diff --git a/openstack/kms/v1/keys/DeleteCMKImport.go b/openstack/kms/v1/keys/DeleteCMKImport.go new file mode 100644 index 000000000..ba35cfa8d --- /dev/null +++ b/openstack/kms/v1/keys/DeleteCMKImport.go @@ -0,0 +1,23 @@ +package keys + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type DeleteCMKImportOpts struct { + KeyId string `json:"key_id" required:"true"` + Sequence string `json:"sequence,omitempty"` +} + +func DeleteCMKImport(client *golangsdk.ServiceClient, opts DeleteCMKImportOpts) error { + b, err := build.RequestBody(opts, "") + if err != nil { + return err + } + + _, err = client.Post(client.ServiceURL("kms", "delete-imported-key-material"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return err +} diff --git a/openstack/kms/v1/keys/GetCMKImport.go b/openstack/kms/v1/keys/GetCMKImport.go new file mode 100644 index 000000000..8ea185e40 --- /dev/null +++ b/openstack/kms/v1/keys/GetCMKImport.go @@ -0,0 +1,39 @@ +package keys + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +type GetCMKImportOpts struct { + KeyId string `json:"key_id" required:"true"` + WrappingAlgorithm string `json:"wrapping_algorithm" required:"true"` + Sequence string `json:"sequence,omitempty"` +} + +func GetCMKImport(client *golangsdk.ServiceClient, opts GetCMKImportOpts) (*CMKImport, error) { + b, err := build.RequestBody(opts, "") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("kms", "get-parameters-for-import"), &b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res CMKImport + + err = extract.Into(raw.Body, &res) + return &res, err +} + +type CMKImport struct { + KeyId string `json:"key_id"` + ImportToken string `json:"import_token"` + ExpirationTime int `json:"expiration_time"` + PublicKey string `json:"public_key"` +} diff --git a/openstack/kms/v1/keys/ImportCMKMaterial.go b/openstack/kms/v1/keys/ImportCMKMaterial.go new file mode 100644 index 000000000..9589f36fb --- /dev/null +++ b/openstack/kms/v1/keys/ImportCMKMaterial.go @@ -0,0 +1,27 @@ +package keys + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type ImportCMKOpts struct { + KeyId string `json:"key_id" required:"true"` + ImportToken string `json:"import_token" required:"true"` + EncryptedKeyMaterial string `json:"encrypted_key_material" required:"true"` + ExpirationTime string `json:"expiration_time,omitempty"` + Sequence string `json:"sequence,omitempty"` +} + +func ImportCMKMaterial(client *golangsdk.ServiceClient, opts ImportCMKOpts) error { + b, err := build.RequestBody(opts, "") + if err != nil { + return err + } + + _, err = client.Post(client.ServiceURL("kms", "import-key-material"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + + return err +}