Skip to content

Commit ef78543

Browse files
committed
implement streaming HMAC.update() macros
This introduces generic macros for streaming HMAC update() that solely rely on an update() function taking as inputs blocks of at most 32-bit length.
1 parent 70cd474 commit ef78543

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

Modules/hmacmodule.c

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,119 @@ typedef struct py_hmac_hacl_api {
169169
#define Py_HMAC_SSIZE_LARGER_THAN_UINT32
170170
#endif
171171

172+
/*
173+
* Assert that 'LEN' can be safely casted to uint32_t.
174+
*
175+
* The 'LEN' parameter should be convertible to Py_ssize_t.
176+
*/
177+
#ifdef Py_HMAC_SSIZE_LARGER_THAN_UINT32
178+
#define Py_CHECK_HACL_UINT32_T_LENGTH(LEN) \
179+
do { \
180+
assert((Py_ssize_t)(LEN) <= UINT32_MAX_AS_SSIZE_T); \
181+
} while (0)
182+
#else
183+
#define Py_CHECK_HACL_UINT32_T_LENGTH(LEN)
184+
#endif
185+
186+
/*
187+
* Call the HACL* HMAC-HASH update function on the given data.
188+
*
189+
* The magnitude of 'LEN' is not checked and thus 'LEN' must be
190+
* safely convertible to a uint32_t value.
191+
*/
192+
#define Py_HMAC_HACL_UPDATE_CALL(HACL_STATE, BUF, LEN) \
193+
Hacl_Streaming_HMAC_update(HACL_STATE, BUF, (uint32_t)(LEN))
194+
195+
/*
196+
* Call the HACL* HMAC-HASH update function on the given data.
197+
*
198+
* On DEBUG builds, the 'ERRACTION' statements are executed if
199+
* the update() call returned a non-successful HACL* exit code.
200+
*
201+
* The buffer 'BUF' and its length 'LEN' are left untouched.
202+
*
203+
* The formal signature of this macro is:
204+
*
205+
* (HACL_HMAC_state *, uint8_t *, uint32_t, PyObject *, (C statements))
206+
*/
207+
#ifndef NDEBUG
208+
#define Py_HMAC_HACL_UPDATE_ONCE( \
209+
HACL_STATE, BUF, LEN, \
210+
ALGORITHM, ERRACTION \
211+
) \
212+
do { \
213+
Py_CHECK_HACL_UINT32_T_LENGTH(LEN); \
214+
hacl_errno_t code = Py_HMAC_HACL_UPDATE_CALL(HACL_STATE, BUF, LEN); \
215+
if (_hacl_convert_errno(code, (ALGORITHM)) < 0) { \
216+
ERRACTION; \
217+
} \
218+
} while (0)
219+
#else
220+
#define Py_HMAC_HACL_UPDATE_ONCE( \
221+
HACL_STATE, BUF, LEN, \
222+
_ALGORITHM, _ERRACTION \
223+
) \
224+
do { \
225+
(void)Py_HMAC_HACL_UPDATE_CALL(HACL_STATE, BUF, (LEN)); \
226+
} while (0)
227+
#endif
228+
229+
/*
230+
* Repetivively call the HACL* HMAC-HASH update function on the given
231+
* data until the buffer length 'LEN' is strictly less than UINT32_MAX.
232+
*
233+
* On builds with PY_SSIZE_T_MAX <= UINT32_MAX, this is a no-op.
234+
*
235+
* The buffer 'BUF' (resp. 'LEN') is advanced (resp. decremented)
236+
* by UINT32_MAX after each update. On DEBUG builds, each update()
237+
* call is verified and the 'ERRACTION' statements are executed if
238+
* a non-successful HACL* exit code is being returned.
239+
*
240+
* In particular, 'BUF' and 'LEN' must be variable names and not
241+
* expressions on their own.
242+
*
243+
* The formal signature of this macro is:
244+
*
245+
* (HACL_HMAC_state *, uint8_t *, C integer, PyObject *, (C statements))
246+
*/
247+
#ifdef Py_HMAC_SSIZE_LARGER_THAN_UINT32
248+
#define Py_HMAC_HACL_UPDATE_LOOP( \
249+
HACL_STATE, BUF, LEN, \
250+
ALGORITHM, ERRACTION \
251+
) \
252+
do { \
253+
while ((Py_ssize_t)LEN > UINT32_MAX_AS_SSIZE_T) { \
254+
Py_HMAC_HACL_UPDATE_ONCE(HACL_STATE, BUF, UINT32_MAX, \
255+
ALGORITHM, ERRACTION); \
256+
BUF += UINT32_MAX; \
257+
LEN -= UINT32_MAX; \
258+
} \
259+
} while (0)
260+
#else
261+
#define Py_HMAC_HACL_UPDATE_LOOP( \
262+
HACL_STATE, BUF, LEN, \
263+
_ALGORITHM, _ERRACTION \
264+
)
265+
#endif
266+
267+
/*
268+
* Perform the HMAC-HASH update() operation in a streaming fashion.
269+
*
270+
* The formal signature of this macro is:
271+
*
272+
* (HACL_HMAC_state *, uint8_t *, C integer, PyObject *, (C statements))
273+
*/
274+
#define Py_HMAC_HACL_UPDATE( \
275+
HACL_STATE, BUF, LEN, \
276+
ALGORITHM, ERRACTION \
277+
) \
278+
do { \
279+
Py_HMAC_HACL_UPDATE_LOOP(HACL_STATE, BUF, LEN, \
280+
ALGORITHM, ERRACTION); \
281+
Py_HMAC_HACL_UPDATE_ONCE(HACL_STATE, BUF, LEN, \
282+
ALGORITHM, ERRACTION); \
283+
} while (0)
284+
172285
/*
173286
* HMAC underlying hash function static information.
174287
*/
@@ -265,6 +378,52 @@ class _hmac.HMAC "HMACObject *" "clinic_state()->hmac_type"
265378
#undef clinic_state
266379

267380
// --- Helpers ----------------------------------------------------------------
381+
//
382+
// The helpers have the following naming conventions:
383+
//
384+
// - Helpers with the "_hacl" prefix are thin wrappers around HACL* functions.
385+
// Buffer lengths given as inputs should fit on 32-bit integers.
386+
387+
/*
388+
* Handle the HACL* exit code.
389+
*
390+
* If 'code' represents a successful operation, this returns 0.
391+
* Otherwise, this sets an appropriate exception and returns -1.
392+
*/
393+
static int
394+
_hacl_convert_errno(hacl_errno_t code, PyObject *algorithm)
395+
{
396+
switch (code) {
397+
case Hacl_Streaming_Types_Success: {
398+
return 0;
399+
}
400+
case Hacl_Streaming_Types_InvalidAlgorithm: {
401+
// only makes sense if an algorithm is known at call time
402+
assert(algorithm != NULL);
403+
assert(PyUnicode_CheckExact(algorithm));
404+
PyErr_Format(PyExc_ValueError, "invalid algorithm: %U", algorithm);
405+
return -1;
406+
}
407+
case Hacl_Streaming_Types_InvalidLength: {
408+
PyErr_SetString(PyExc_ValueError, "invalid length");
409+
return -1;
410+
}
411+
case Hacl_Streaming_Types_MaximumLengthExceeded: {
412+
PyErr_SetString(PyExc_OverflowError, "maximum length exceeded");
413+
return -1;
414+
}
415+
case Hacl_Streaming_Types_OutOfMemory: {
416+
PyErr_NoMemory();
417+
return -1;
418+
}
419+
default: {
420+
PyErr_Format(PyExc_RuntimeError,
421+
"HACL* internal routine failed with error code: %d",
422+
code);
423+
return -1;
424+
}
425+
}
426+
}
268427

269428
/*
270429
* Free the HACL* internal state.

0 commit comments

Comments
 (0)