1/*
2 * Copyright (c) 2021 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "fido.h"
9
10static int
11aes256_cbc(const fido_blob_t *key, const u_char *iv, const fido_blob_t *in,
12    fido_blob_t *out, int encrypt)
13{
14	EVP_CIPHER_CTX *ctx = NULL;
15	const EVP_CIPHER *cipher;
16	int ok = -1;
17
18	memset(out, 0, sizeof(*out));
19
20	if (key->len != 32) {
21		fido_log_debug("%s: invalid key len %zu", __func__, key->len);
22		goto fail;
23	}
24	if (in->len > UINT_MAX || in->len % 16 || in->len == 0) {
25		fido_log_debug("%s: invalid input len %zu", __func__, in->len);
26		goto fail;
27	}
28	out->len = in->len;
29	if ((out->ptr = calloc(1, out->len)) == NULL) {
30		fido_log_debug("%s: calloc", __func__);
31		goto fail;
32	}
33	if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
34	    (cipher = EVP_aes_256_cbc()) == NULL) {
35		fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__);
36		goto fail;
37	}
38	if (EVP_CipherInit(ctx, cipher, key->ptr, iv, encrypt) == 0 ||
39	    EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)out->len) < 0) {
40		fido_log_debug("%s: EVP_Cipher", __func__);
41		goto fail;
42	}
43
44	ok = 0;
45fail:
46	if (ctx != NULL)
47		EVP_CIPHER_CTX_free(ctx);
48	if (ok < 0)
49		fido_blob_reset(out);
50
51	return ok;
52}
53
54static int
55aes256_cbc_proto1(const fido_blob_t *key, const fido_blob_t *in,
56    fido_blob_t *out, int encrypt)
57{
58	u_char iv[16];
59
60	memset(&iv, 0, sizeof(iv));
61
62	return aes256_cbc(key, iv, in, out, encrypt);
63}
64
65static int
66aes256_cbc_fips(const fido_blob_t *secret, const fido_blob_t *in,
67    fido_blob_t *out, int encrypt)
68{
69	fido_blob_t key, cin, cout;
70	u_char iv[16];
71
72	memset(out, 0, sizeof(*out));
73
74	if (secret->len != 64) {
75		fido_log_debug("%s: invalid secret len %zu", __func__,
76		    secret->len);
77		return -1;
78	}
79	if (in->len < sizeof(iv)) {
80		fido_log_debug("%s: invalid input len %zu", __func__, in->len);
81		return -1;
82	}
83	if (encrypt) {
84		if (fido_get_random(iv, sizeof(iv)) < 0) {
85			fido_log_debug("%s: fido_get_random", __func__);
86			return -1;
87		}
88		cin = *in;
89	} else {
90		memcpy(iv, in->ptr, sizeof(iv));
91		cin.ptr = in->ptr + sizeof(iv);
92		cin.len = in->len - sizeof(iv);
93	}
94	key.ptr = secret->ptr + 32;
95	key.len = secret->len - 32;
96	if (aes256_cbc(&key, iv, &cin, &cout, encrypt) < 0)
97		return -1;
98	if (encrypt) {
99		if (cout.len > SIZE_MAX - sizeof(iv) ||
100		    (out->ptr = calloc(1, sizeof(iv) + cout.len)) == NULL) {
101			fido_blob_reset(&cout);
102			return -1;
103		}
104		out->len = sizeof(iv) + cout.len;
105		memcpy(out->ptr, iv, sizeof(iv));
106		memcpy(out->ptr + sizeof(iv), cout.ptr, cout.len);
107		fido_blob_reset(&cout);
108	} else
109		*out = cout;
110
111	return 0;
112}
113
114static int
115aes256_gcm(const fido_blob_t *key, const fido_blob_t *nonce,
116    const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out,
117    int encrypt)
118{
119	EVP_CIPHER_CTX *ctx = NULL;
120	const EVP_CIPHER *cipher;
121	size_t textlen;
122	int ok = -1;
123
124	memset(out, 0, sizeof(*out));
125
126	if (nonce->len != 12 || key->len != 32 || aad->len > UINT_MAX) {
127		fido_log_debug("%s: invalid params %zu, %zu, %zu", __func__,
128		    nonce->len, key->len, aad->len);
129		goto fail;
130	}
131	if (in->len > UINT_MAX || in->len > SIZE_MAX - 16 || in->len < 16) {
132		fido_log_debug("%s: invalid input len %zu", __func__, in->len);
133		goto fail;
134	}
135	/* add tag to (on encrypt) or trim tag from the output (on decrypt) */
136	out->len = encrypt ? in->len + 16 : in->len - 16;
137	if ((out->ptr = calloc(1, out->len)) == NULL) {
138		fido_log_debug("%s: calloc", __func__);
139		goto fail;
140	}
141	if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
142	    (cipher = EVP_aes_256_gcm()) == NULL) {
143		fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__);
144		goto fail;
145	}
146	if (EVP_CipherInit(ctx, cipher, key->ptr, nonce->ptr, encrypt) == 0) {
147		fido_log_debug("%s: EVP_CipherInit", __func__);
148		goto fail;
149	}
150
151	if (encrypt)
152		textlen = in->len;
153	else {
154		textlen = in->len - 16;
155		/* point openssl at the mac tag */
156		if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
157		    in->ptr + in->len - 16) == 0) {
158			fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__);
159			goto fail;
160		}
161	}
162	/* the last EVP_Cipher() will either compute or verify the mac tag */
163	if (EVP_Cipher(ctx, NULL, aad->ptr, (u_int)aad->len) < 0 ||
164	    EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)textlen) < 0 ||
165	    EVP_Cipher(ctx, NULL, NULL, 0) < 0) {
166		fido_log_debug("%s: EVP_Cipher", __func__);
167		goto fail;
168	}
169	if (encrypt) {
170		/* append the mac tag */
171		if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16,
172		    out->ptr + out->len - 16) == 0) {
173			fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__);
174			goto fail;
175		}
176	}
177
178	ok = 0;
179fail:
180	if (ctx != NULL)
181		EVP_CIPHER_CTX_free(ctx);
182	if (ok < 0)
183		fido_blob_reset(out);
184
185	return ok;
186}
187
188int
189aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *secret,
190    const fido_blob_t *in, fido_blob_t *out)
191{
192	return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret,
193	    in, out, 1) : aes256_cbc_proto1(secret, in, out, 1);
194}
195
196int
197aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *secret,
198    const fido_blob_t *in, fido_blob_t *out)
199{
200	return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret,
201	    in, out, 0) : aes256_cbc_proto1(secret, in, out, 0);
202}
203
204int
205aes256_gcm_enc(const fido_blob_t *key, const fido_blob_t *nonce,
206    const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out)
207{
208	return aes256_gcm(key, nonce, aad, in, out, 1);
209}
210
211int
212aes256_gcm_dec(const fido_blob_t *key, const fido_blob_t *nonce,
213    const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out)
214{
215	return aes256_gcm(key, nonce, aad, in, out, 0);
216}
217