1package transit 2 3import ( 4 "context" 5 "testing" 6 7 "github.com/hashicorp/vault/sdk/logical" 8) 9 10func TestTransit_BackupRestore(t *testing.T) { 11 // Test encryption/decryption after a restore for supported keys 12 testBackupRestore(t, "aes256-gcm96", "encrypt-decrypt") 13 testBackupRestore(t, "chacha20-poly1305", "encrypt-decrypt") 14 testBackupRestore(t, "rsa-2048", "encrypt-decrypt") 15 testBackupRestore(t, "rsa-4096", "encrypt-decrypt") 16 17 // Test signing/verification after a restore for supported keys 18 testBackupRestore(t, "ecdsa-p256", "sign-verify") 19 testBackupRestore(t, "ed25519", "sign-verify") 20 testBackupRestore(t, "rsa-2048", "sign-verify") 21 testBackupRestore(t, "rsa-4096", "sign-verify") 22 23 // Test HMAC/verification after a restore for all key types 24 testBackupRestore(t, "aes256-gcm96", "hmac-verify") 25 testBackupRestore(t, "chacha20-poly1305", "hmac-verify") 26 testBackupRestore(t, "ecdsa-p256", "hmac-verify") 27 testBackupRestore(t, "ed25519", "hmac-verify") 28 testBackupRestore(t, "rsa-2048", "hmac-verify") 29 testBackupRestore(t, "rsa-4096", "hmac-verify") 30} 31 32func testBackupRestore(t *testing.T, keyType, feature string) { 33 var resp *logical.Response 34 var err error 35 36 b, s := createBackendWithStorage(t) 37 38 // Create a key 39 keyReq := &logical.Request{ 40 Path: "keys/test", 41 Operation: logical.UpdateOperation, 42 Storage: s, 43 Data: map[string]interface{}{ 44 "type": keyType, 45 "exportable": true, 46 }, 47 } 48 resp, err = b.HandleRequest(context.Background(), keyReq) 49 if err != nil || (resp != nil && resp.IsError()) { 50 t.Fatalf("resp: %#v\nerr: %v", resp, err) 51 } 52 53 // Configure the key to allow its deletion 54 configReq := &logical.Request{ 55 Path: "keys/test/config", 56 Operation: logical.UpdateOperation, 57 Storage: s, 58 Data: map[string]interface{}{ 59 "deletion_allowed": true, 60 "allow_plaintext_backup": true, 61 }, 62 } 63 resp, err = b.HandleRequest(context.Background(), configReq) 64 if err != nil || (resp != nil && resp.IsError()) { 65 t.Fatalf("resp: %#v\nerr: %v", resp, err) 66 } 67 68 // Take a backup of the key 69 backupReq := &logical.Request{ 70 Path: "backup/test", 71 Operation: logical.ReadOperation, 72 Storage: s, 73 } 74 resp, err = b.HandleRequest(context.Background(), backupReq) 75 if err != nil || (resp != nil && resp.IsError()) { 76 t.Fatalf("resp: %#v\nerr: %v", resp, err) 77 } 78 backup := resp.Data["backup"] 79 80 // Try to restore the key without deleting it. Expect error due to 81 // conflicting key names. 82 restoreReq := &logical.Request{ 83 Path: "restore", 84 Operation: logical.UpdateOperation, 85 Storage: s, 86 Data: map[string]interface{}{ 87 "backup": backup, 88 }, 89 } 90 resp, err = b.HandleRequest(context.Background(), restoreReq) 91 if resp != nil && resp.IsError() { 92 t.Fatalf("resp: %#v\nerr: %v", resp, err) 93 } 94 if err == nil { 95 t.Fatalf("expected an error") 96 } 97 98 plaintextB64 := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox" 99 100 // Perform encryption, signing or hmac-ing based on the set 'feature' 101 var encryptReq, signReq, hmacReq *logical.Request 102 var ciphertext, signature, hmac string 103 switch feature { 104 case "encrypt-decrypt": 105 encryptReq = &logical.Request{ 106 Path: "encrypt/test", 107 Operation: logical.UpdateOperation, 108 Storage: s, 109 Data: map[string]interface{}{ 110 "plaintext": plaintextB64, 111 }, 112 } 113 resp, err = b.HandleRequest(context.Background(), encryptReq) 114 if err != nil || (resp != nil && resp.IsError()) { 115 t.Fatalf("resp: %#v\nerr: %v", resp, err) 116 } 117 ciphertext = resp.Data["ciphertext"].(string) 118 119 case "sign-verify": 120 signReq = &logical.Request{ 121 Path: "sign/test", 122 Operation: logical.UpdateOperation, 123 Storage: s, 124 Data: map[string]interface{}{ 125 "input": plaintextB64, 126 }, 127 } 128 resp, err = b.HandleRequest(context.Background(), signReq) 129 if err != nil || (resp != nil && resp.IsError()) { 130 t.Fatalf("resp: %#v\nerr: %v", resp, err) 131 } 132 signature = resp.Data["signature"].(string) 133 134 case "hmac-verify": 135 hmacReq = &logical.Request{ 136 Path: "hmac/test", 137 Operation: logical.UpdateOperation, 138 Storage: s, 139 Data: map[string]interface{}{ 140 "input": plaintextB64, 141 }, 142 } 143 resp, err = b.HandleRequest(context.Background(), hmacReq) 144 if err != nil || (resp != nil && resp.IsError()) { 145 t.Fatalf("resp: %#v\nerr: %v", resp, err) 146 } 147 hmac = resp.Data["hmac"].(string) 148 } 149 150 // Delete the key 151 keyReq.Operation = logical.DeleteOperation 152 resp, err = b.HandleRequest(context.Background(), keyReq) 153 if err != nil || (resp != nil && resp.IsError()) { 154 t.Fatalf("resp: %#v\nerr: %v", resp, err) 155 } 156 157 // Restore the key from the backup 158 resp, err = b.HandleRequest(context.Background(), restoreReq) 159 if err != nil || (resp != nil && resp.IsError()) { 160 t.Fatalf("resp: %#v\nerr: %v", resp, err) 161 } 162 163 // validationFunc verifies the ciphertext, signature or hmac based on the 164 // set 'feature' 165 validationFunc := func(keyName string) { 166 var decryptReq *logical.Request 167 var verifyReq *logical.Request 168 switch feature { 169 case "encrypt-decrypt": 170 decryptReq = &logical.Request{ 171 Path: "decrypt/" + keyName, 172 Operation: logical.UpdateOperation, 173 Storage: s, 174 Data: map[string]interface{}{ 175 "ciphertext": ciphertext, 176 }, 177 } 178 resp, err = b.HandleRequest(context.Background(), decryptReq) 179 if err != nil || (resp != nil && resp.IsError()) { 180 t.Fatalf("resp: %#v\nerr: %v", resp, err) 181 } 182 183 if resp.Data["plaintext"].(string) != plaintextB64 { 184 t.Fatalf("bad: plaintext; expected: %q, actual: %q", plaintextB64, resp.Data["plaintext"].(string)) 185 } 186 case "sign-verify": 187 verifyReq = &logical.Request{ 188 Path: "verify/" + keyName, 189 Operation: logical.UpdateOperation, 190 Storage: s, 191 Data: map[string]interface{}{ 192 "signature": signature, 193 "input": plaintextB64, 194 }, 195 } 196 resp, err = b.HandleRequest(context.Background(), verifyReq) 197 if err != nil || (resp != nil && resp.IsError()) { 198 t.Fatalf("resp: %#v\nerr: %v", resp, err) 199 } 200 if resp.Data["valid"].(bool) != true { 201 t.Fatalf("bad: signature verification failed for key type %q", keyType) 202 } 203 204 case "hmac-verify": 205 verifyReq = &logical.Request{ 206 Path: "verify/" + keyName, 207 Operation: logical.UpdateOperation, 208 Storage: s, 209 Data: map[string]interface{}{ 210 "hmac": hmac, 211 "input": plaintextB64, 212 }, 213 } 214 resp, err = b.HandleRequest(context.Background(), verifyReq) 215 if err != nil || (resp != nil && resp.IsError()) { 216 t.Fatalf("resp: %#v\nerr: %v", resp, err) 217 } 218 if resp.Data["valid"].(bool) != true { 219 t.Fatalf("bad: HMAC verification failed for key type %q", keyType) 220 } 221 } 222 } 223 224 // Ensure that the restored key is functional 225 validationFunc("test") 226 227 // Delete the key again 228 resp, err = b.HandleRequest(context.Background(), keyReq) 229 if err != nil || (resp != nil && resp.IsError()) { 230 t.Fatalf("resp: %#v\nerr: %v", resp, err) 231 } 232 233 // Restore the key under a different name 234 restoreReq.Path = "restore/test1" 235 resp, err = b.HandleRequest(context.Background(), restoreReq) 236 if err != nil || (resp != nil && resp.IsError()) { 237 t.Fatalf("resp: %#v\nerr: %v", resp, err) 238 } 239 240 // Ensure that the restored key is functional 241 validationFunc("test1") 242} 243