// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package transit import ( "context" "testing" "github.com/hashicorp/vault/sdk/logical" ) func TestTransit_BYOKExportImport(t *testing.T) { // Test encryption/decryption after a restore for supported keys testBYOKExportImport(t, "aes128-gcm96", "encrypt-decrypt") testBYOKExportImport(t, "aes256-gcm96", "encrypt-decrypt") testBYOKExportImport(t, "chacha20-poly1305", "encrypt-decrypt") testBYOKExportImport(t, "rsa-2048", "encrypt-decrypt") testBYOKExportImport(t, "rsa-3072", "encrypt-decrypt") testBYOKExportImport(t, "rsa-4096", "encrypt-decrypt") // Test signing/verification after a restore for supported keys testBYOKExportImport(t, "ecdsa-p256", "sign-verify") testBYOKExportImport(t, "ecdsa-p384", "sign-verify") testBYOKExportImport(t, "ecdsa-p521", "sign-verify") testBYOKExportImport(t, "ed25519", "sign-verify") testBYOKExportImport(t, "rsa-2048", "sign-verify") testBYOKExportImport(t, "rsa-3072", "sign-verify") testBYOKExportImport(t, "rsa-4096", "sign-verify") // Test HMAC sign/verify after a restore for supported keys. testBYOKExportImport(t, "hmac", "hmac-verify") } func testBYOKExportImport(t *testing.T, keyType, feature string) { var resp *logical.Response var err error b, s := createBackendWithStorage(t) // Create a key keyReq := &logical.Request{ Path: "keys/test-source", Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "type": keyType, "exportable": true, }, } if keyType == "hmac" { keyReq.Data["key_size"] = 32 } resp, err = b.HandleRequest(context.Background(), keyReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } // Read the wrapping key. wrapKeyReq := &logical.Request{ Path: "wrapping_key", Operation: logical.ReadOperation, Storage: s, } resp, err = b.HandleRequest(context.Background(), wrapKeyReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } // Import the wrapping key. wrapKeyImportReq := &logical.Request{ Path: "keys/wrapper/import", Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "public_key": resp.Data["public_key"], "type": "rsa-4096", }, } resp, err = b.HandleRequest(context.Background(), wrapKeyImportReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } // Export the key backupReq := &logical.Request{ Path: "byok-export/wrapper/test-source", Operation: logical.ReadOperation, Storage: s, } resp, err = b.HandleRequest(context.Background(), backupReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } keys := resp.Data["keys"].(map[string]string) // Import the key to a new name. restoreReq := &logical.Request{ Path: "keys/test/import", Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "ciphertext": keys["1"], "type": keyType, }, } resp, err = b.HandleRequest(context.Background(), restoreReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } plaintextB64 := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox" // Perform encryption, signing or hmac-ing based on the set 'feature' var encryptReq, signReq, hmacReq *logical.Request var ciphertext, signature, hmac string switch feature { case "encrypt-decrypt": encryptReq = &logical.Request{ Path: "encrypt/test-source", Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "plaintext": plaintextB64, }, } resp, err = b.HandleRequest(context.Background(), encryptReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } ciphertext = resp.Data["ciphertext"].(string) case "sign-verify": signReq = &logical.Request{ Path: "sign/test-source", Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "input": plaintextB64, }, } resp, err = b.HandleRequest(context.Background(), signReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } signature = resp.Data["signature"].(string) case "hmac-verify": hmacReq = &logical.Request{ Path: "hmac/test-source", Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "input": plaintextB64, }, } resp, err = b.HandleRequest(context.Background(), hmacReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } hmac = resp.Data["hmac"].(string) } // validationFunc verifies the ciphertext, signature or hmac based on the // set 'feature' validationFunc := func(keyName string) { var decryptReq *logical.Request var verifyReq *logical.Request switch feature { case "encrypt-decrypt": decryptReq = &logical.Request{ Path: "decrypt/" + keyName, Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "ciphertext": ciphertext, }, } resp, err = b.HandleRequest(context.Background(), decryptReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } if resp.Data["plaintext"].(string) != plaintextB64 { t.Fatalf("bad: plaintext; expected: %q, actual: %q", plaintextB64, resp.Data["plaintext"].(string)) } case "sign-verify": verifyReq = &logical.Request{ Path: "verify/" + keyName, Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "signature": signature, "input": plaintextB64, }, } resp, err = b.HandleRequest(context.Background(), verifyReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } if resp.Data["valid"].(bool) != true { t.Fatalf("bad: signature verification failed for key type %q", keyType) } case "hmac-verify": verifyReq = &logical.Request{ Path: "verify/" + keyName, Operation: logical.UpdateOperation, Storage: s, Data: map[string]interface{}{ "hmac": hmac, "input": plaintextB64, }, } resp, err = b.HandleRequest(context.Background(), verifyReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("resp: %#v\nerr: %v", resp, err) } if resp.Data["valid"].(bool) != true { t.Fatalf("bad: HMAC verification failed for key type %q", keyType) } } } // Ensure that the restored key is functional validationFunc("test") // Ensure the original key is functional validationFunc("test-source") }