OpenVPN 3 Core Library
Loading...
Searching...
No Matches
cipheraead.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12// Wrap the OpenSSL GCM API.
13
14#pragma once
15
16#include <string>
17
18#include <openssl/evp.h>
19#include <openssl/objects.h>
20#include <openssl/opensslv.h>
21
29
30namespace openvpn::OpenSSLCrypto {
32{
33 /* In OpenSSL 3.0 the method that returns EVP_CIPHER, the cipher needs to be
34 * freed afterwards, thus needing a non-const type. In contrast, OpenSSL 1.1.1
35 * and lower returns a const type, needing a const type */
36#if OPENSSL_VERSION_NUMBER < 0x30000000L
37 using evp_cipher_type = const EVP_CIPHER;
38#else
39 using evp_cipher_type = EVP_CIPHER;
40#endif
41 using CIPHER_unique_ptr = std::unique_ptr<evp_cipher_type, decltype(&::EVP_CIPHER_free)>;
42
43 public:
46
48 : ctx(std::exchange(other.ctx, nullptr)), aead_usage_limit_(other.aead_usage_limit_)
49 {
50 }
51
53 {
54 CipherContextAEAD temp(std::move(other));
55 std::swap(ctx, temp.ctx);
56 std::swap(aead_usage_limit_, other.aead_usage_limit_);
57 return *this;
58 }
59
60 OPENVPN_EXCEPTION(openssl_gcm_error);
61
62 // mode parameter for constructor
63 enum
64 {
67 DECRYPT = 0
68 };
69
70 // OpenSSL cipher constants
71 enum : size_t
72 {
73 IV_LEN = 12,
74 AUTH_TAG_LEN = 16
75 };
76
77 bool constexpr requires_authtag_at_end()
78 {
79 return false;
80 }
81
82 CipherContextAEAD() = default;
83
88
89 void init(SSLLib::Ctx libctx,
90 const CryptoAlgs::Type alg,
91 const unsigned char *key,
92 const unsigned int keysize,
93 const int mode)
94 {
96 unsigned int ckeysz = 0;
97 const CIPHER_unique_ptr ciph(cipher_type(libctx, alg, ckeysz), EVP_CIPHER_free);
98
99 if (!ciph)
100 OPENVPN_THROW(openssl_gcm_error, CryptoAlgs::name(alg) << ": not usable");
101
102 if (ckeysz > keysize)
103 throw openssl_gcm_error("insufficient key material");
104 ctx = EVP_CIPHER_CTX_new();
105 EVP_CIPHER_CTX_reset(ctx);
106 switch (mode)
107 {
108 case ENCRYPT:
109 if (!EVP_EncryptInit_ex(ctx, ciph.get(), nullptr, key, nullptr))
110 {
113 throw openssl_gcm_error("EVP_EncryptInit_ex (init)");
114 }
115 break;
116 case DECRYPT:
117 if (!EVP_DecryptInit_ex(ctx, ciph.get(), nullptr, key, nullptr))
118 {
121 throw openssl_gcm_error("EVP_DecryptInit_ex (init)");
122 }
123 break;
124 default:
125 throw openssl_gcm_error("bad mode");
126 }
127 if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, nullptr) != 1)
128 {
131 throw openssl_gcm_error("EVP_CIPHER_CTX_ctrl set IV len");
132 }
133 aead_usage_limit_ = {alg};
134 }
135
136 void encrypt(const unsigned char *input,
137 unsigned char *output,
138 size_t length,
139 const unsigned char *iv,
140 unsigned char *tag,
141 const unsigned char *ad,
142 size_t ad_len)
143 {
144 int len;
145 int ciphertext_len;
146
148 if (!EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, iv))
149 {
151 throw openssl_gcm_error("EVP_EncryptInit_ex (reset)");
152 }
153 if (!EVP_EncryptUpdate(ctx, nullptr, &len, ad, int(ad_len)))
154 {
156 throw openssl_gcm_error("EVP_EncryptUpdate AD");
157 }
158 if (!EVP_EncryptUpdate(ctx, output, &len, input, int(length)))
159 {
161 throw openssl_gcm_error("EVP_EncryptUpdate data");
162 }
163 ciphertext_len = len;
164 if (!EVP_EncryptFinal_ex(ctx, output + len, &len))
165 {
167 throw openssl_gcm_error("EVP_EncryptFinal_ex");
168 }
169 ciphertext_len += len;
170 if ((size_t)ciphertext_len != length)
171 {
172 throw openssl_gcm_error("encrypt size inconsistency");
173 }
174 if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTH_TAG_LEN, tag))
175 {
177 throw openssl_gcm_error("EVP_CIPHER_CTX_ctrl get tag");
178 }
179 aead_usage_limit_.update(length + ad_len);
180 }
181
182
185 {
186 return aead_usage_limit_;
187 }
188
189
203 bool decrypt(const unsigned char *input,
204 unsigned char *output,
205 size_t length,
206 const unsigned char *iv,
207 const unsigned char *tag,
208 const unsigned char *ad,
209 size_t ad_len)
210 {
211 if (!tag)
212 {
213 /* Tag is at the end of input, check that input is large enough to hold the tag */
214 if (length < AUTH_TAG_LEN)
215 {
216 throw openssl_gcm_error("decrypt input length too short");
217 }
218
219 length = length - AUTH_TAG_LEN;
220 tag = input + length;
221 }
222
224 if (!EVP_DecryptInit_ex(ctx, nullptr, nullptr, nullptr, iv))
225 {
227 throw openssl_gcm_error("EVP_DecryptInit_ex (reset)");
228 }
229
230 int len;
231 if (!EVP_DecryptUpdate(ctx, nullptr, &len, ad, int(ad_len)))
232 {
234 throw openssl_gcm_error("EVP_DecryptUpdate AD");
235 }
236 if (!EVP_DecryptUpdate(ctx, output, &len, input, int(length)))
237 {
239 throw openssl_gcm_error("EVP_DecryptUpdate data");
240 }
241
242 int plaintext_len = len;
245 if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, AUTH_TAG_LEN, const_cast<unsigned char *>(tag)))
246 {
248 throw openssl_gcm_error("EVP_CIPHER_CTX_ctrl set tag");
249 }
250 if (!EVP_DecryptFinal_ex(ctx, output + len, &len))
251 {
253 return false;
254 }
255 plaintext_len += len;
256 if (static_cast<size_t>(plaintext_len) != length)
257 {
258 throw openssl_gcm_error("decrypt size inconsistency");
259 }
260 return true;
261 }
262
263 bool is_initialized() const
264 {
265 return ctx != nullptr;
266 }
267
268 static bool is_supported(SSLLib::Ctx libctx, const CryptoAlgs::Type alg)
269 {
270 unsigned int keysize = 0;
271 const CIPHER_unique_ptr cipher(cipher_type(libctx, alg, keysize), EVP_CIPHER_free);
272 return (bool)cipher;
273 }
274
275
276 private:
278 const CryptoAlgs::Type alg,
279 unsigned int &keysize)
280 {
281 switch (alg)
282 {
284 keysize = 16;
285 return EVP_CIPHER_fetch(libctx, "AES-128-GCM", nullptr);
287 keysize = 24;
288 return EVP_CIPHER_fetch(libctx, "AES-192-GCM", nullptr);
290 keysize = 32;
291 return EVP_CIPHER_fetch(libctx, "AES-256-GCM", nullptr);
293 keysize = 32;
294 return EVP_CIPHER_fetch(libctx, "CHACHA20-POLY1305", nullptr);
295 default:
296 keysize = 0;
297 return nullptr;
298 }
299 }
300
302 {
303 EVP_CIPHER_CTX_free(ctx);
304 ctx = nullptr;
305 }
306
307 void check_initialized() const
308 {
309#ifdef OPENVPN_ENABLE_ASSERT
310 if (!ctx)
311 throw openssl_gcm_error("uninitialized");
312#endif
313 }
314
315 EVP_CIPHER_CTX *ctx = nullptr;
317};
318} // namespace openvpn::OpenSSLCrypto
void update(const std::size_t outlen)
static evp_cipher_type * cipher_type(SSLLib::Ctx libctx, const CryptoAlgs::Type alg, unsigned int &keysize)
const Crypto::AEADUsageLimit & get_usage_limit()
static bool is_supported(SSLLib::Ctx libctx, const CryptoAlgs::Type alg)
void init(SSLLib::Ctx libctx, const CryptoAlgs::Type alg, const unsigned char *key, const unsigned int keysize, const int mode)
CipherContextAEAD(CipherContextAEAD &&other) noexcept
bool decrypt(const unsigned char *input, unsigned char *output, size_t length, const unsigned char *iv, const unsigned char *tag, const unsigned char *ad, size_t ad_len)
std::unique_ptr< evp_cipher_type, decltype(&::EVP_CIPHER_free)> CIPHER_unique_ptr
CipherContextAEAD(const CipherContextAEAD &)=delete
CipherContextAEAD & operator=(const CipherContextAEAD &)=delete
CipherContextAEAD & operator=(CipherContextAEAD &&other)
void encrypt(const unsigned char *input, unsigned char *output, size_t length, const unsigned char *iv, unsigned char *tag, const unsigned char *ad, size_t ad_len)
static void EVP_CIPHER_free(const EVP_CIPHER *cipher)
Definition compat.hpp:85
static const EVP_CIPHER * EVP_CIPHER_fetch(void *ctx, const char *algorithm, const char *properties)
Definition compat.hpp:56
#define OPENVPN_THROW(exc, stuff)
const char * name(const KeyDerivation kd)
void openssl_clear_error_stack()
Definition error.hpp:239
static const char * input[]