1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stddef.h>
6#include <stdint.h>
7#include <string.h>
8
9#include <crypto/aead.h>
10#include <crypto/bytes.h>
11#include <fbl/algorithm.h>
12#include <fbl/auto_call.h>
13#include <fbl/unique_ptr.h>
14#include <lib/fdio/debug.h>
15#include <openssl/aead.h>
16#include <zircon/errors.h>
17#include <zircon/types.h>
18
19#include "error.h"
20
21#define ZXDEBUG 0
22
23namespace crypto {
24
25// The previously opaque crypto implementation context.  Guaranteed to clean up on destruction.
26struct AEAD::Context {
27    Context() {}
28
29    ~Context() { EVP_AEAD_CTX_cleanup(&impl); }
30
31    EVP_AEAD_CTX impl;
32};
33
34namespace {
35
36// Get the aead for the given |version|.
37zx_status_t GetAEAD(AEAD::Algorithm aead, const EVP_AEAD** out) {
38    switch (aead) {
39    case AEAD::kUninitialized:
40        xprintf("not initialized\n");
41        return ZX_ERR_INVALID_ARGS;
42
43    case AEAD::kAES128_GCM:
44        *out = EVP_aead_aes_128_gcm();
45        return ZX_OK;
46
47    case AEAD::kAES128_GCM_SIV:
48        *out = EVP_aead_aes_128_gcm_siv();
49        return ZX_OK;
50
51    default:
52        xprintf("invalid aead = %u\n", aead);
53        return ZX_ERR_NOT_SUPPORTED;
54    }
55}
56
57} // namespace
58
59// Public methods
60
61zx_status_t AEAD::GetKeyLen(Algorithm algo, size_t* out) {
62    zx_status_t rc;
63
64    if (!out) {
65        xprintf("missing output pointer\n");
66        return ZX_ERR_INVALID_ARGS;
67    }
68    const EVP_AEAD* aead;
69    if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
70        return rc;
71    }
72    *out = EVP_AEAD_key_length(aead);
73
74    return ZX_OK;
75}
76
77zx_status_t AEAD::GetIVLen(Algorithm algo, size_t* out) {
78    zx_status_t rc;
79
80    if (!out) {
81        xprintf("missing output pointer\n");
82        return ZX_ERR_INVALID_ARGS;
83    }
84    const EVP_AEAD* aead;
85    if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
86        return rc;
87    }
88    *out = EVP_AEAD_nonce_length(aead);
89
90    return ZX_OK;
91}
92
93zx_status_t AEAD::GetTagLen(Algorithm algo, size_t* out) {
94    zx_status_t rc;
95
96    if (!out) {
97        xprintf("missing output pointer\n");
98        return ZX_ERR_INVALID_ARGS;
99    }
100    const EVP_AEAD* aead;
101    if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
102        return rc;
103    }
104    *out = EVP_AEAD_max_tag_len(aead);
105
106    return ZX_OK;
107}
108
109AEAD::AEAD()
110    : ctx_(nullptr), direction_(Cipher::kUnset), tag_len_(0) {}
111
112AEAD::~AEAD() {}
113
114zx_status_t AEAD::Seal(const Bytes& ptext, const uint8_t* aad, size_t aad_len, uint64_t* out_nonce,
115                       Bytes* out_ctext) {
116
117    zx_status_t rc;
118
119    if (direction_ != Cipher::kEncrypt) {
120        xprintf("not configured to encrypt\n");
121        return ZX_ERR_BAD_STATE;
122    }
123
124    size_t ptext_len = ptext.len();
125    if (!out_nonce || !out_ctext) {
126        xprintf("bad parameter(s): out_nonce=%p, ctext=%p\n", out_nonce, out_ctext);
127        return ZX_ERR_INVALID_ARGS;
128    }
129
130    // If the caller recycles the |Bytes| used for|ctext|, this becomes a no-op.
131    size_t ctext_len = ptext_len + tag_len_;
132    if ((rc = out_ctext->Resize(ctext_len)) != ZX_OK) {
133        return rc;
134    }
135
136    uint8_t* iv8 = reinterpret_cast<uint8_t*>(iv_.get());
137    size_t out_len;
138    if (EVP_AEAD_CTX_seal(&ctx_->impl, out_ctext->get(), &out_len, ctext_len, iv8, iv_len_,
139                          ptext.get(), ptext_len, aad, aad_len) != 1) {
140        xprintf_crypto_errors(&rc);
141        return rc;
142    }
143    if (out_len != ctext_len) {
144        xprintf("length mismatch: expected %zu, got %zu\n", ptext_len, out_len);
145        return ZX_ERR_INTERNAL;
146    }
147
148    // Increment nonce
149    uint64_t nonce = iv_[0];
150    iv_[0] += 1;
151    if (iv_[0] == iv0_) {
152        xprintf("exceeded maximum operations with this key\n");
153        return ZX_ERR_BAD_STATE;
154    }
155
156    *out_nonce = nonce;
157    return ZX_OK;
158}
159
160zx_status_t AEAD::Open(uint64_t nonce, const Bytes& ctext, const uint8_t* aad, size_t aad_len, Bytes* out_ptext) {
161    zx_status_t rc;
162
163    if (direction_ != Cipher::kDecrypt) {
164        xprintf("not configured to decrypt\n");
165        return ZX_ERR_BAD_STATE;
166    }
167
168    size_t ctext_len = ctext.len();
169    if (ctext_len < tag_len_ || !out_ptext) {
170        xprintf("bad parameter(s): ctext.len=%zu, ptext=%p\n", ctext_len, out_ptext);
171        return ZX_ERR_INVALID_ARGS;
172    }
173
174    size_t ptext_len = ctext_len - tag_len_;
175    if ((rc = out_ptext->Resize(ptext_len)) != ZX_OK) {
176        return rc;
177    }
178
179    // Inject nonce
180    iv_[0] = nonce;
181    uint8_t* iv8 = reinterpret_cast<uint8_t*>(iv_.get());
182    size_t out_len;
183    if (EVP_AEAD_CTX_open(&ctx_->impl, out_ptext->get(), &out_len, ptext_len, iv8, iv_len_,
184                          ctext.get(), ctext_len, aad, aad_len) != 1) {
185        xprintf_crypto_errors(&rc);
186        return rc;
187    }
188    if (out_len != ptext_len) {
189        xprintf("length mismatch: expected %zu, got %zu\n", ptext_len, out_len);
190        return ZX_ERR_INTERNAL;
191    }
192
193    return ZX_OK;
194}
195
196void AEAD::Reset() {
197    ctx_.reset();
198    direction_ = Cipher::kUnset;
199    iv_len_ = 0;
200    tag_len_ = 0;
201}
202
203// Private methods
204
205zx_status_t AEAD::Init(Algorithm algo, const Secret& key, const Bytes& iv,
206                       Cipher::Direction direction) {
207    zx_status_t rc;
208
209    Reset();
210    auto cleanup = fbl::MakeAutoCall([&] { Reset(); });
211
212    // Look up specific algorithm
213    const EVP_AEAD* aead;
214    if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
215        return rc;
216    }
217    size_t key_len = EVP_AEAD_key_length(aead);
218    iv_len_ = EVP_AEAD_nonce_length(aead);
219    tag_len_ = EVP_AEAD_max_tag_len(aead);
220
221    // Check parameters
222    if (key.len() != key_len) {
223        xprintf("wrong key length; have %zu, need %zu\n", key.len(), key_len);
224        return ZX_ERR_INVALID_ARGS;
225    }
226    if (iv.len() != iv_len_) {
227        xprintf("wrong IV length; have %zu, need %zu\n", iv.len(), iv_len_);
228        return ZX_ERR_INVALID_ARGS;
229    }
230
231    // Allocate context
232    fbl::AllocChecker ac;
233    ctx_.reset(new (&ac) Context());
234    if (!ac.check()) {
235        xprintf("allocation failed: %zu bytes\n", sizeof(Context));
236        return ZX_ERR_NO_MEMORY;
237    }
238
239    // Initialize context
240    if (EVP_AEAD_CTX_init(&ctx_->impl, aead, key.get(), key.len(), EVP_AEAD_DEFAULT_TAG_LENGTH,
241                          nullptr) != 1) {
242        xprintf_crypto_errors(&rc);
243        return rc;
244    }
245    direction_ = direction;
246
247    // Reserve space for IV
248    size_t n = fbl::round_up(iv_len_, sizeof(uint64_t)) / sizeof(uint64_t);
249    iv_.reset(new (&ac) uint64_t[n]{0});
250    if (!ac.check()) {
251        xprintf("failed to allocate %zu bytes\n", n * sizeof(uint64_t));
252        return ZX_ERR_NO_MEMORY;
253    }
254    memcpy(iv_.get(), iv.get(), iv_len_);
255
256    cleanup.cancel();
257    return ZX_OK;
258}
259
260} // namespace crypto
261