Skip to content

Commit 06ce5e7

Browse files
committed
aes: add aes_cbc_with_ecdh_key for ephemeral request/response encryption
1 parent 375983c commit 06ce5e7

File tree

12 files changed

+352
-0
lines changed

12 files changed

+352
-0
lines changed

include/wally.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,19 @@ inline int aes_cbc_get_maximum_length(const KEY& key, const IV& iv, const BYTES&
401401
return ret;
402402
}
403403

404+
template <class PRIV_KEY, class IV, class BYTES, class PUB_KEY, class LABEL, class BYTES_OUT>
405+
inline int aes_cbc_with_ecdh_key(const PRIV_KEY& priv_key, const IV& iv, const BYTES& bytes, const PUB_KEY& pub_key, const LABEL& label, uint32_t flags, BYTES_OUT& bytes_out, size_t* written = 0) {
406+
size_t n;
407+
int ret = ::wally_aes_cbc_with_ecdh_key(priv_key.data(), priv_key.size(), iv.data(), iv.size(), bytes.data(), bytes.size(), pub_key.data(), pub_key.size(), label.data(), label.size(), flags, bytes_out.data(), bytes_out.size(), written ? written : &n);
408+
return written || ret != WALLY_OK ? ret : n == static_cast<size_t>(bytes_out.size()) ? WALLY_OK : WALLY_EINVAL;
409+
}
410+
411+
template <class PRIV_KEY, class IV, class BYTES, class PUB_KEY, class LABEL>
412+
inline int aes_cbc_with_ecdh_key_get_maximum_length(const PRIV_KEY& priv_key, const IV& iv, const BYTES& bytes, const PUB_KEY& pub_key, const LABEL& label, uint32_t flags, size_t* written) {
413+
int ret = ::wally_aes_cbc_with_ecdh_key_get_maximum_length(priv_key.data(), priv_key.size(), iv.data(), iv.size(), bytes.data(), bytes.size(), pub_key.data(), pub_key.size(), label.data(), label.size(), flags, written);
414+
return ret;
415+
}
416+
404417
template <class KEY, class BYTES>
405418
inline int aes_len(const KEY& key, const BYTES& bytes, uint32_t flags, size_t* written) {
406419
int ret = ::wally_aes_len(key.data(), key.size(), bytes.data(), bytes.size(), flags, written);

include/wally_crypto.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,81 @@ WALLY_CORE_API int wally_s2c_commitment_verify(
891891
size_t s2c_opening_len,
892892
uint32_t flags);
893893

894+
/**
895+
* Get the maximum length of data encrypted/decrypted using `wally_aes_cbc_with_ecdh_key`.
896+
*
897+
* :param priv_key: The callers private key used for Diffie-Helman exchange.
898+
* :param priv_key_len: The length of ``priv_key`` in bytes. Must be `EC_PRIVATE_KEY_LEN`.
899+
* :param iv: Initialisation vector. Only required when encrypting, otherwise pass NULL.
900+
* :param iv_len: Length of ``iv`` in bytes. Must be `AES_BLOCK_LEN`.
901+
* :param bytes: Bytes to encrypt/decrypt.
902+
* :param bytes_len: Length of ``bytes`` in bytes.
903+
* :param pub_key: The other parties public key used for Diffie-Helman exchange.
904+
* :param pub_key_len: Length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN`.
905+
* :param label: A non-empty array of bytes for internal key generation. Must
906+
*| be the same (fixed) value when encrypting and decrypting.
907+
* :param label_len: Length of ``label`` in bytes.
908+
* :param flags: :ref:`aes-operation-flag` indicating the desired behavior.
909+
* :param written: Destination for the maximum length of the encrypted/decrypted data.
910+
*/
911+
WALLY_CORE_API int wally_aes_cbc_with_ecdh_key_get_maximum_length(
912+
const unsigned char *priv_key,
913+
size_t priv_key_len,
914+
const unsigned char *iv,
915+
size_t iv_len,
916+
const unsigned char *bytes,
917+
size_t bytes_len,
918+
const unsigned char *pub_key,
919+
size_t pub_key_len,
920+
const unsigned char *label,
921+
size_t label_len,
922+
uint32_t flags,
923+
size_t *written);
924+
925+
/**
926+
* Encrypt/decrypt data using AES-256 (CBC mode, PKCS#7 padding) and a shared Diffie-Helman secret.
927+
*
928+
* :param priv_key: The callers private key used for Diffie-Helman exchange.
929+
* :param priv_key_len: The length of ``priv_key`` in bytes. Must be `EC_PRIVATE_KEY_LEN`.
930+
* :param iv: Initialisation vector. Only required when encrypting, otherwise pass NULL.
931+
* :param iv_len: Length of ``iv`` in bytes. Must be `AES_BLOCK_LEN` if encrypting otherwise 0.
932+
* :param bytes: Bytes to encrypt/decrypt.
933+
* :param bytes_len: Length of ``bytes`` in bytes.
934+
* :param pub_key: The other parties public key used for Diffie-Helman exchange.
935+
* :param pub_key_len: Length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN`.
936+
* :param label: A non-empty array of bytes for internal key generation. Must
937+
*| be the same (fixed) value when encrypting and decrypting.
938+
* :param label_len: Length of ``label`` in bytes.
939+
* :param flags: :ref:`aes-operation-flag` indicating the desired behavior.
940+
* :param bytes_out: Destination for the encrypted/decrypted data.
941+
* :param len: The length of ``bytes_out`` in bytes.
942+
* :param written: Destination for the number of bytes written to ``bytes_out``.
943+
*
944+
* This function implements a scheme for sharing data using a derived secret.
945+
* Alice creates an ephemeral key pair and sends her public key to Bob along
946+
* with any request details. Bob creates an ephemeral key pair and calls this
947+
* function with his private key and Alices public key to encrypt ``bytes``
948+
* (the request payload). Bob returns his public key and the encrypted data to
949+
* Alice, who calls this function with her private key and Bobs public key
950+
* to decrypt and authenticate the payload. The ``label`` parameter must be
951+
* be the same for both Alice and Bob for a given request/response.
952+
*/
953+
WALLY_CORE_API int wally_aes_cbc_with_ecdh_key(
954+
const unsigned char *priv_key,
955+
size_t priv_key_len,
956+
const unsigned char *iv,
957+
size_t iv_len,
958+
const unsigned char *bytes,
959+
size_t bytes_len,
960+
const unsigned char *pub_key,
961+
size_t pub_key_len,
962+
const unsigned char *label,
963+
size_t label_len,
964+
uint32_t flags,
965+
unsigned char *bytes_out,
966+
size_t len,
967+
size_t *written);
968+
894969
#ifdef __cplusplus
895970
}
896971
#endif

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ endif
319319

320320
if USE_SWIG_PYTHON
321321
check-swig-python: $(SWIG_PYTHON_TEST_DEPS)
322+
$(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/aes.py
322323
$(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/bip32.py
323324
$(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/coinselection.py
324325
$(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/descriptor.py

src/aes.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "internal.h"
22
#include <include/wally_crypto.h>
33

4+
#include "ccan/ccan/build_assert/build_assert.h"
45
#include "ctaes/ctaes.h"
56
#include "ctaes/ctaes.c"
67

@@ -208,3 +209,132 @@ int wally_aes_cbc(const unsigned char *key, size_t key_len,
208209
wally_clear_2(buf, sizeof(buf), &ctx, sizeof(ctx));
209210
return WALLY_OK;
210211
}
212+
213+
int wally_aes_cbc_with_ecdh_key_get_maximum_length(
214+
const unsigned char *priv_key, size_t priv_key_len,
215+
const unsigned char *iv, size_t iv_len,
216+
const unsigned char *bytes, size_t bytes_len,
217+
const unsigned char *pub_key, size_t pub_key_len,
218+
const unsigned char *label, size_t label_len, uint32_t flags,
219+
size_t *written)
220+
{
221+
if (written)
222+
*written = 0;
223+
224+
if (!priv_key || priv_key_len != EC_PRIVATE_KEY_LEN || !bytes ||
225+
!pub_key || pub_key_len != EC_PUBLIC_KEY_LEN || !label || !label_len ||
226+
(flags != AES_FLAG_ENCRYPT && flags != AES_FLAG_DECRYPT) || !written)
227+
return WALLY_EINVAL;
228+
229+
if (flags & AES_FLAG_ENCRYPT) {
230+
/* Must provide IV + minimum 1 byte of payload for encryption */
231+
if (!iv || iv_len != AES_BLOCK_LEN || !bytes_len)
232+
return WALLY_EINVAL;
233+
/* Output is IV + encrypted payload + HMAC */
234+
*written = AES_BLOCK_LEN + HMAC_SHA256_LEN
235+
+ ((bytes_len / AES_BLOCK_LEN) + 1) * AES_BLOCK_LEN;
236+
} else {
237+
/* Must not provide an IV for decryption */
238+
if (iv || iv_len)
239+
return WALLY_EINVAL;
240+
/* Payload must contain IV, payload and the HMAC for decryption */
241+
if (bytes_len < AES_BLOCK_LEN + AES_BLOCK_LEN + HMAC_SHA256_LEN)
242+
return WALLY_EINVAL;
243+
/* Output is the decrypted payload without the IV and HMAC */
244+
bytes_len = bytes_len - AES_BLOCK_LEN - HMAC_SHA256_LEN;
245+
if (bytes_len % AES_BLOCK_LEN)
246+
return WALLY_EINVAL; /* Payload isn't a block size multiple */
247+
/* Actual bytes written may be less due to padding, but the
248+
* caller must pass a buffer of the padded size. */
249+
*written = bytes_len;
250+
}
251+
return WALLY_OK;
252+
}
253+
254+
int wally_aes_cbc_with_ecdh_key(
255+
const unsigned char *priv_key, size_t priv_key_len,
256+
const unsigned char *iv, size_t iv_len,
257+
const unsigned char *bytes, size_t bytes_len,
258+
const unsigned char *pub_key, size_t pub_key_len,
259+
const unsigned char *label, size_t label_len, uint32_t flags,
260+
unsigned char *bytes_out, size_t len, size_t *written)
261+
{
262+
unsigned char secret[SHA256_LEN], keys[HMAC_SHA512_LEN];
263+
const unsigned char *enc_key = keys, *hmac_key = keys + AES_KEY_LEN_256;
264+
size_t expected_len;
265+
const bool is_encrypt = flags & AES_FLAG_ENCRYPT;
266+
int ret;
267+
268+
/* Derived key sizes must match the derived keys buffer */
269+
BUILD_ASSERT(sizeof(keys) == AES_KEY_LEN_256 + SHA256_LEN);
270+
271+
if (written)
272+
*written = 0;
273+
274+
if (!bytes_out || !len || !written)
275+
return WALLY_EINVAL;
276+
ret = wally_aes_cbc_with_ecdh_key_get_maximum_length(
277+
priv_key, priv_key_len, iv, iv_len, bytes, bytes_len,
278+
pub_key, pub_key_len, label, label_len, flags,
279+
&expected_len);
280+
if (ret == WALLY_OK && expected_len > len) {
281+
*written = expected_len;
282+
return ret; /* Tell the caller how much space is needed */
283+
}
284+
if (ret != WALLY_OK)
285+
return ret;
286+
287+
if (is_encrypt) {
288+
/* Copy the IV to the start of the encrypted output */
289+
memcpy(bytes_out, iv, iv_len);
290+
} else {
291+
/* The IV is the first AES_BLOCK_LEN bytes of the payload */
292+
iv = bytes;
293+
iv_len = AES_BLOCK_LEN;
294+
}
295+
296+
/* Shared secret is ECDH(their pubkey, our private key) */
297+
ret = wally_ecdh(pub_key, pub_key_len, priv_key, priv_key_len,
298+
secret, sizeof(secret));
299+
/* Generate encryption/HMAC keys using the shared secret and label */
300+
if (ret == WALLY_OK)
301+
ret = wally_hmac_sha512(secret, sizeof(secret), label, label_len, keys, sizeof(keys));
302+
if (ret == WALLY_OK && !is_encrypt) {
303+
/* Verify the IV + encrypted data's HMAC */
304+
unsigned char hmac[HMAC_SHA256_LEN];
305+
ret = wally_hmac_sha256(hmac_key, SHA256_LEN,
306+
bytes, bytes_len - sizeof(hmac),
307+
hmac, sizeof(hmac));
308+
if (ret == WALLY_OK &&
309+
memcmp(hmac, bytes + bytes_len - sizeof(hmac), sizeof(hmac)))
310+
ret = WALLY_EINVAL; /* Invalid HMAC */
311+
}
312+
313+
/* Encrypt/decrypt the payload */
314+
if (ret == WALLY_OK) {
315+
/* Trim our output length to a block size multiple for aes_cbc */
316+
size_t out_len = (len - (is_encrypt ? (iv_len + HMAC_SHA256_LEN) : 0)) & ~((size_t)0xf);
317+
if (is_encrypt)
318+
ret = wally_aes_cbc(enc_key, AES_KEY_LEN_256, iv, iv_len,
319+
bytes, bytes_len, flags,
320+
bytes_out + iv_len, out_len, written);
321+
else
322+
ret = wally_aes_cbc(enc_key, AES_KEY_LEN_256, iv, iv_len,
323+
bytes + iv_len, bytes_len - iv_len - HMAC_SHA256_LEN,
324+
flags, bytes_out, out_len, written);
325+
}
326+
327+
if (ret == WALLY_OK && is_encrypt) {
328+
/* append the HMAC of the IV + encrypted data */
329+
*written += AES_BLOCK_LEN; /* Include the IV */
330+
ret = wally_hmac_sha256(hmac_key, SHA256_LEN,
331+
bytes_out, *written,
332+
bytes_out + *written, HMAC_SHA256_LEN);
333+
*written = ret == WALLY_OK ? *written + HMAC_SHA256_LEN : 0;
334+
}
335+
336+
if (ret == WALLY_OK && *written > expected_len)
337+
ret = WALLY_ERROR; /* Should never happen! */
338+
wally_clear_2(secret, sizeof(secret), keys, sizeof(keys));
339+
return ret;
340+
}

src/swig_java/swig.i

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,8 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
523523
%returns_size_t(wally_aes_len);
524524
%returns_size_t(wally_aes_cbc);
525525
%returns_size_t(wally_aes_cbc_get_maximum_length);
526+
%returns_size_t(wally_aes_cbc_with_ecdh_key);
527+
%returns_size_t(wally_aes_cbc_with_ecdh_key_get_maximum_length);
526528
%returns_array_(wally_asset_final_vbf, 8, 9, ASSET_TAG_LEN);
527529
%returns_array_(wally_asset_generator_from_bytes, 5, 6, ASSET_GENERATOR_LEN);
528530
%returns_size_t(wally_asset_rangeproof_get_maximum_len);

src/swig_python/contrib/aes.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import unittest
2+
from wallycore import init, cleanup, \
3+
ec_public_key_from_private_key, aes_cbc_with_ecdh_key, \
4+
AES_FLAG_ENCRYPT, AES_FLAG_DECRYPT
5+
6+
7+
class AESTests(unittest.TestCase):
8+
9+
def test_aes_cbc_ecdh(self):
10+
"""Test python wrappers for aes_cbc_with_ecdh_key """
11+
# Alice generates an ephemeral keypair for her request.
12+
alice_priv = bytes.fromhex('1c6a837d1ac663fdc7f1002327ca38452766eaf4fe3b80ce620bf7cd3f584cf6')
13+
alice_pub = ec_public_key_from_private_key(alice_priv)
14+
# Bob generates an ephemeral keypair for his response.
15+
bob_priv = bytes.fromhex('0b6b3dc90d203d854100110788ac87d43aa00620c9cdb361b281b09022ef4b53')
16+
bob_pub = ec_public_key_from_private_key(bob_priv)
17+
# Bob also generates a secure random IV for encrypting the response.
18+
iv = bytes.fromhex('bd5d4724243880738e7e8b0c02658700')
19+
# Both parties must agree on a shared label to use.
20+
label = 'a sample label'.encode()
21+
# This is the example payload we are using.
22+
payload = 'This is an example response/payload to encrypt'.encode()
23+
24+
# Test we handle messages up to and over AES_BLOCK_LEN boundaries
25+
for i in range(1, len(payload) + 1):
26+
# The protocol:
27+
# 1) Alice requests some data using her pubkey.
28+
# 2) Bob encrypts the response (payload) with his private key,
29+
# a random IV, a shared label and Alice's pubkey.
30+
encrypted = aes_cbc_with_ecdh_key(bob_priv, iv, payload[0:i],
31+
alice_pub, label, AES_FLAG_ENCRYPT)
32+
# 3) Bob sends the encrypted data and his pubkey to Alice.
33+
# 4) Alice decrypts the payload with her private key and Bobs pubkey.
34+
decrypted = aes_cbc_with_ecdh_key(alice_priv, None, encrypted,
35+
bob_pub, label, AES_FLAG_DECRYPT)
36+
# Alice now has the unencrypted payload.
37+
self.assertEqual(decrypted.hex(), payload[0:i].hex())
38+
39+
40+
if __name__ == '__main__':
41+
init(0)
42+
unittest.main()
43+
cleanup(0)

src/swig_python/python_extra.py_in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ ae_sig_from_bytes = _wrap_bin(ae_sig_from_bytes, EC_SIGNATURE_LEN)
116116
ae_signer_commit_from_bytes = _wrap_bin(ae_signer_commit_from_bytes, WALLY_S2C_OPENING_LEN)
117117
aes = _wrap_bin(aes, aes_len)
118118
aes_cbc = _wrap_bin(aes_cbc, aes_cbc_get_maximum_length, resize=True)
119+
aes_cbc_with_ecdh_key = _wrap_bin(aes_cbc_with_ecdh_key, aes_cbc_with_ecdh_key_get_maximum_length, resize=True)
119120
base58_n_to_bytes = _wrap_bin(base58_n_to_bytes, base58_n_to_bytes_len, resize=True)
120121
base58_to_bytes = _wrap_bin(base58_to_bytes, base58_to_bytes_len, resize=True)
121122
base64_to_bytes = _wrap_bin(base64_to_bytes, base64_get_maximum_length, resize=True)

src/test/test_aes.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,85 @@ def test_aes_cbc(self):
9292
self.assertEqual((ret, written), (0, len(o)))
9393
self.assertEqual(h(out_buf), h(o))
9494

95+
def test_aes_cbc_with_ecdh_key(self):
96+
ENCRYPT, DECRYPT, PUBKEY_LEN, _ = 1, 2, 33, True
97+
a_priv = make_cbuffer('1c6a837d1ac663fdc7f1002327ca38452766eaf4fe3b80ce620bf7cd3f584cf6')[0]
98+
a_pub = make_cbuffer('03e581be89d1ef8ce11d60746d08e4f8aedf934d1d861dd436042ee2e3b16db918')[0]
99+
b_priv = make_cbuffer('0b6b3dc90d203d854100110788ac87d43aa00620c9cdb361b281b09022ef4b53')[0]
100+
b_pub = make_cbuffer('03ff06999ad61c0f3a733b93fc1e6b75ecfb1439b326e840de590a56454f0eeb0d')[0]
101+
iv = make_cbuffer('bd5d4724243880738e7e8b0c02658700')[0]
102+
label = 'a sample label'.encode()
103+
payload = 'This is an example response/payload to encrypt'.encode()
104+
buf = make_cbuffer('00' * 256)[0]
105+
106+
# Encryption
107+
good_args = [b_priv, len(b_priv), iv, len(iv), payload, len(payload),
108+
a_pub, len(a_pub), label, len(label), ENCRYPT, buf, len(buf)]
109+
110+
ret, written = wally_aes_cbc_with_ecdh_key(*good_args)
111+
self.assertEqual(ret, WALLY_OK) # Make sure good args work
112+
encrypted = make_cbuffer(buf[:written].hex())[0]
113+
114+
invalid_cases = [
115+
(None, _, _, _, _, _, _, _, _, _, _, _, _), # NULL privkey
116+
(_, 0, _, _, _, _, _, _, _, _, _, _, _), # Empty privkey
117+
(_, 9, _, _, _, _, _, _, _, _, _, _, _), # Wrong privkey length
118+
(_, _, None, _, _, _, _, _, _, _, _, _, _), # NULL IV (enc)
119+
(_, _, _, 0, _, _, _, _, _, _, _, _, _), # Empty IV (enc)
120+
(_, _, _, 9, _, _, _, _, _, _, _, _, _), # Wrong IV length (enc)
121+
(_, _, _, _, None, _, _, _, _, _, _, _, _), # NULL payload
122+
(_, _, _, _, _, 0, _, _, _, _, _, _, _), # Empty payload
123+
(_, _, _, _, _, _, None, _, _, _, _, _, _), # NULL pubkey
124+
(_, _, _, _, _, _, _, 0, _, _, _, _, _), # Empty pubkey
125+
(_, _, _, _, _, _, _, 9, _, _, _, _, _), # Wrong pubkey length
126+
(_, _, _, _, _, _, _, _, None, _, _, _, _), # NULL label
127+
(_, _, _, _, _, _, _, _, _, 0, _, _, _), # Empty label
128+
(_, _, _, _, _, _, _, _, _, _, 3, _, _), # Encrypt+Decrypt flags
129+
(_, _, _, _, _, _, _, _, _, _, 5, _, _), # Unknown flag
130+
(_, _, _, _, _, _, _, _, _, _, _, None, _), # NULL output
131+
(_, _, _, _, _, _, _, _, _, _, _, _, 0), # Zero-length output
132+
]
133+
for case in invalid_cases:
134+
args = [good_args[i] if a == _ else a for i, a in enumerate(case)]
135+
self.assertEqual(wally_aes_cbc_with_ecdh_key(*args), (WALLY_EINVAL, 0))
136+
137+
# Test writing up to/beyond the output buffer size
138+
for out_len in range(1, len(encrypted) + 16):
139+
args = [a for a in good_args]
140+
args[-1] = out_len
141+
ret = wally_aes_cbc_with_ecdh_key(*args)
142+
self.assertEqual(ret, (WALLY_OK, written)) # returns required length
143+
144+
# Decryption
145+
good_args = [a_priv, len(a_priv), None, 0, encrypted, len(encrypted),
146+
b_pub, len(b_pub), label, len(label), DECRYPT, buf, len(buf)]
147+
148+
ret, written = wally_aes_cbc_with_ecdh_key(*good_args)
149+
self.assertEqual(ret, WALLY_OK) # Make sure good args work
150+
self.assertEqual(buf[:written], payload)
151+
152+
bad = make_cbuffer((encrypted[:-1] + b'?').hex())[0] # Corrupt the HMAC
153+
bad_len = len(encrypted) - 1
154+
invalid_cases = [
155+
(_, _, iv, _, _, _, _, _, _, _, _, _, _), # Non-NULL IV (dec)
156+
(_, _, _, len(iv), _, _, _, _, _, _, _, _, _), # Non-zero IV length (dec)
157+
(_, _, _, _, bad, _, _, _, _, _, _, _, _), # Corrupt HMAC
158+
(_, _, _, _, _, bad_len, _, _, _, _, _, _, _), # Truncated encrypted data
159+
]
160+
for case in invalid_cases:
161+
args = [good_args[i] if a == _ else a for i, a in enumerate(case)]
162+
self.assertEqual(wally_aes_cbc_with_ecdh_key(*args), (WALLY_EINVAL, 0))
163+
164+
# Test writing up to/beyond the output buffer size
165+
for out_len in range(1, len(payload) + 16):
166+
args = [a for a in good_args]
167+
args[-1] = out_len
168+
ret, written = wally_aes_cbc_with_ecdh_key(*args)
169+
self.assertEqual(ret, WALLY_OK)
170+
# The output size required includes final padding which is
171+
# stripped if the payload isn't a multiple of the AES block size.
172+
self.assertLessEqual(len(payload), written)
173+
95174

96175
if __name__ == '__main__':
97176
unittest.main()

0 commit comments

Comments
 (0)