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