From 74454715ec994a14953c07f0679a7c5585076a07 Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Thu, 13 Mar 2025 10:19:33 +1000 Subject: [PATCH] ECH: generate multiple configs and rotate echConfigs Change wolfSSL_CTX_GenerateEchConfig to generate multiple configs, add functions to rotate the server's echConfigs. --- src/ssl.c | 483 ++++++++++++++++++++++++++------------------- tests/api.c | 20 ++ wolfssl/internal.h | 4 + wolfssl/ssl.h | 6 + 4 files changed, 313 insertions(+), 200 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index ca2a52c1b..38e6e073a 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -364,6 +364,8 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, { int ret = 0; word16 encLen = DHKEM_X25519_ENC_LEN; + WOLFSSL_EchConfig* newConfig; + WOLFSSL_EchConfig* parentConfig; #ifdef WOLFSSL_SMALL_STACK Hpke* hpke = NULL; WC_RNG* rng; @@ -388,16 +390,16 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, return ret; } - ctx->echConfigs = (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), + newConfig = (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); - if (ctx->echConfigs == NULL) + if (newConfig == NULL) ret = MEMORY_E; else - XMEMSET(ctx->echConfigs, 0, sizeof(WOLFSSL_EchConfig)); + XMEMSET(newConfig, 0, sizeof(WOLFSSL_EchConfig)); /* set random config id */ if (ret == 0) - ret = wc_RNG_GenerateByte(rng, &ctx->echConfigs->configId); + ret = wc_RNG_GenerateByte(rng, &newConfig->configId); /* if 0 is selected for algorithms use default, may change with draft */ if (kemId == 0) @@ -411,19 +413,20 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, if (ret == 0) { /* set the kem id */ - ctx->echConfigs->kemId = kemId; + newConfig->kemId = kemId; /* set the cipher suite, only 1 for now */ - ctx->echConfigs->numCipherSuites = 1; - ctx->echConfigs->cipherSuites = (EchCipherSuite*)XMALLOC( - sizeof(EchCipherSuite), ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + newConfig->numCipherSuites = 1; + newConfig->cipherSuites = + (EchCipherSuite*)XMALLOC(sizeof(EchCipherSuite), ctx->heap, + DYNAMIC_TYPE_TMP_BUFFER); - if (ctx->echConfigs->cipherSuites == NULL) { + if (newConfig->cipherSuites == NULL) { ret = MEMORY_E; } else { - ctx->echConfigs->cipherSuites[0].kdfId = kdfId; - ctx->echConfigs->cipherSuites[0].aeadId = aeadId; + newConfig->cipherSuites[0].kdfId = kdfId; + newConfig->cipherSuites[0].aeadId = aeadId; } } @@ -440,38 +443,47 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, /* generate the receiver private key */ if (ret == 0) - ret = wc_HpkeGenerateKeyPair(hpke, &ctx->echConfigs->receiverPrivkey, - rng); + ret = wc_HpkeGenerateKeyPair(hpke, &newConfig->receiverPrivkey, rng); /* done with RNG */ wc_FreeRng(rng); /* serialize the receiver key */ if (ret == 0) - ret = wc_HpkeSerializePublicKey(hpke, ctx->echConfigs->receiverPrivkey, - ctx->echConfigs->receiverPubkey, &encLen); + ret = wc_HpkeSerializePublicKey(hpke, newConfig->receiverPrivkey, + newConfig->receiverPubkey, &encLen); if (ret == 0) { - ctx->echConfigs->publicName = (char*)XMALLOC(XSTRLEN(publicName) + 1, + newConfig->publicName = (char*)XMALLOC(XSTRLEN(publicName) + 1, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); - if (ctx->echConfigs->publicName == NULL) { + if (newConfig->publicName == NULL) { ret = MEMORY_E; } else { - XMEMCPY(ctx->echConfigs->publicName, publicName, + XMEMCPY(newConfig->publicName, publicName, XSTRLEN(publicName) + 1); } } if (ret != 0) { - if (ctx->echConfigs) { - XFREE(ctx->echConfigs->cipherSuites, ctx->heap, - DYNAMIC_TYPE_TMP_BUFFER); - XFREE(ctx->echConfigs->publicName, ctx->heap, - DYNAMIC_TYPE_TMP_BUFFER); - XFREE(ctx->echConfigs, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); - /* set to null to avoid double free in cleanup */ - ctx->echConfigs = NULL; + if (newConfig) { + XFREE(newConfig->cipherSuites, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(newConfig->publicName, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(newConfig, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + } + else { + parentConfig = ctx->echConfigs; + + if (parentConfig == NULL) { + ctx->echConfigs = newConfig; + } + else { + while (parentConfig->next != NULL) { + parentConfig = parentConfig->next; + } + + parentConfig->next = newConfig; } } @@ -486,6 +498,59 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, return ret; } +int wolfSSL_CTX_SetEchConfigsBase64(WOLFSSL_CTX* ctx, const char* echConfigs64, + word32 echConfigs64Len) +{ + int ret = 0; + word32 decodedLen = echConfigs64Len * 3 / 4 + 1; + byte* decodedConfigs; + + if (ctx == NULL || echConfigs64 == NULL || echConfigs64Len == 0) + return BAD_FUNC_ARG; + + decodedConfigs = (byte*)XMALLOC(decodedLen, ctx->heap, + DYNAMIC_TYPE_TMP_BUFFER); + + if (decodedConfigs == NULL) + return MEMORY_E; + + decodedConfigs[decodedLen - 1] = 0; + + /* decode the echConfigs */ + ret = Base64_Decode((const byte*)echConfigs64, echConfigs64Len, + decodedConfigs, &decodedLen); + + if (ret != 0) { + XFREE(decodedConfigs, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + return ret; + } + + ret = wolfSSL_CTX_SetEchConfigs(ctx, decodedConfigs, decodedLen); + + XFREE(decodedConfigs, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +int wolfSSL_CTX_SetEchConfigs(WOLFSSL_CTX* ctx, const byte* echConfigs, + word32 echConfigsLen) +{ + int ret; + + if (ctx == NULL || echConfigs == NULL || echConfigsLen == 0) + return BAD_FUNC_ARG; + + FreeEchConfigs(ctx->echConfigs, ctx->heap); + ctx->echConfigs = NULL; + ret = SetEchConfigsEx(&ctx->echConfigs, ctx->heap, echConfigs, + echConfigsLen); + + if (ret == 0) + return WOLFSSL_SUCCESS; + + return ret; +} + /* get the ech configs that the server context is using */ int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output, word32* outputLen) { @@ -493,9 +558,8 @@ int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output, return BAD_FUNC_ARG; /* if we don't have ech configs */ - if (ctx->echConfigs == NULL) { + if (ctx->echConfigs == NULL) return WOLFSSL_FATAL_ERROR; - } return GetEchConfigsEx(ctx->echConfigs, output, outputLen); } @@ -558,19 +622,7 @@ int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, word32 echConfigsLen) { - int ret = 0; - int i; - int j; - word16 totalLength; - word16 version; - word16 length; - word16 hpkePubkeyLen; - word16 cipherSuitesLen; - word16 publicNameLen; - WOLFSSL_EchConfig* configList = NULL; - WOLFSSL_EchConfig* workingConfig = NULL; - WOLFSSL_EchConfig* lastConfig = NULL; - byte* echConfig = NULL; + int ret; if (ssl == NULL || echConfigs == NULL || echConfigsLen == 0) return BAD_FUNC_ARG; @@ -580,171 +632,15 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, return WOLFSSL_FATAL_ERROR; } - /* check that the total length is well formed */ - ato16(echConfigs, &totalLength); - - if (totalLength != echConfigsLen - 2) { - return WOLFSSL_FATAL_ERROR; - } - - /* skip the total length uint16_t */ - i = 2; - - do { - echConfig = (byte*)echConfigs + i; - ato16(echConfig, &version); - ato16(echConfig + 2, &length); - - /* if the version does not match */ - if (version != TLSX_ECH) { - /* we hit the end of the configs */ - if ( (word32)i + 2 >= echConfigsLen ) { - break; - } - - /* skip this config, +4 for version and length */ - i += length + 4; - continue; - } - - /* check if the length will overrun the buffer */ - if ((word32)i + length + 4 > echConfigsLen) { - break; - } - - if (workingConfig == NULL) { - workingConfig = - (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - configList = workingConfig; - if (workingConfig != NULL) { - workingConfig->next = NULL; - } - } - else { - lastConfig = workingConfig; - workingConfig->next = - (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - workingConfig = workingConfig->next; - } - - if (workingConfig == NULL) { - ret = MEMORY_E; - break; - } - - XMEMSET(workingConfig, 0, sizeof(WOLFSSL_EchConfig)); - - /* rawLen */ - workingConfig->rawLen = length + 4; - - /* raw body */ - workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen, - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - if (workingConfig->raw == NULL) { - ret = MEMORY_E; - break; - } - - XMEMCPY(workingConfig->raw, echConfig, workingConfig->rawLen); - - /* skip over version and length */ - echConfig += 4; - - /* configId, 1 byte */ - workingConfig->configId = *(echConfig); - echConfig++; - /* kemId, 2 bytes */ - ato16(echConfig, &workingConfig->kemId); - echConfig += 2; - /* hpke public_key length, 2 bytes */ - ato16(echConfig, &hpkePubkeyLen); - echConfig += 2; - /* hpke public_key */ - XMEMCPY(workingConfig->receiverPubkey, echConfig, hpkePubkeyLen); - echConfig += hpkePubkeyLen; - /* cipherSuitesLen */ - ato16(echConfig, &cipherSuitesLen); - - workingConfig->cipherSuites = (EchCipherSuite*)XMALLOC(cipherSuitesLen, - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - if (workingConfig->cipherSuites == NULL) { - ret = MEMORY_E; - break; - } - - echConfig += 2; - workingConfig->numCipherSuites = cipherSuitesLen / 4; - /* cipherSuites */ - for (j = 0; j < workingConfig->numCipherSuites; j++) { - ato16(echConfig + j * 4, &workingConfig->cipherSuites[j].kdfId); - ato16(echConfig + j * 4 + 2, - &workingConfig->cipherSuites[j].aeadId); - } - echConfig += cipherSuitesLen; - /* ignore the maximum name length */ - echConfig++; - /* publicNameLen */ - publicNameLen = *(echConfig); - workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1, - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - if (workingConfig->publicName == NULL) { - ret = MEMORY_E; - break; - } - echConfig++; - /* publicName */ - XMEMCPY(workingConfig->publicName, echConfig, publicNameLen); - /* null terminated */ - workingConfig->publicName[publicNameLen] = 0; - - /* add length to go to next config, +4 for version and length */ - i += length + 4; - - /* check that we support this config */ - for (j = 0; j < HPKE_SUPPORTED_KEM_LEN; j++) { - if (hpkeSupportedKem[j] == workingConfig->kemId) - break; - } - - /* if we don't support the kem or at least one cipher suite */ - if (j >= HPKE_SUPPORTED_KEM_LEN || - EchConfigGetSupportedCipherSuite(workingConfig) < 0) - { - XFREE(workingConfig->cipherSuites, ssl->heap, - DYNAMIC_TYPE_TMP_BUFFER); - XFREE(workingConfig->publicName, ssl->heap, - DYNAMIC_TYPE_TMP_BUFFER); - XFREE(workingConfig->raw, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - workingConfig = lastConfig; - } - } while ((word32)i < echConfigsLen); + ret = SetEchConfigsEx(&ssl->echConfigs, ssl->heap, echConfigs, + echConfigsLen); /* if we found valid configs */ - if (ret == 0 && configList != NULL) { + if (ret == 0) { ssl->options.useEch = 1; - ssl->echConfigs = configList; - return WOLFSSL_SUCCESS; } - workingConfig = configList; - - while (workingConfig != NULL) { - lastConfig = workingConfig; - workingConfig = workingConfig->next; - - XFREE(lastConfig->cipherSuites, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(lastConfig->publicName, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(lastConfig->raw, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - - XFREE(lastConfig, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - } - - if (ret == 0) - return WOLFSSL_FATAL_ERROR; - return ret; } @@ -918,6 +814,193 @@ void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable) } } +int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, + const byte* echConfigs, word32 echConfigsLen) +{ + int ret = 0; + int i; + int j; + word16 totalLength; + word16 version; + word16 length; + word16 hpkePubkeyLen; + word16 cipherSuitesLen; + word16 publicNameLen; + WOLFSSL_EchConfig* configList = NULL; + WOLFSSL_EchConfig* workingConfig = NULL; + WOLFSSL_EchConfig* lastConfig = NULL; + byte* echConfig = NULL; + + if (outputConfigs == NULL || echConfigs == NULL || echConfigsLen == 0) + return BAD_FUNC_ARG; + + /* check that the total length is well formed */ + ato16(echConfigs, &totalLength); + + if (totalLength != echConfigsLen - 2) { + return WOLFSSL_FATAL_ERROR; + } + + /* skip the total length uint16_t */ + i = 2; + + do { + echConfig = (byte*)echConfigs + i; + ato16(echConfig, &version); + ato16(echConfig + 2, &length); + + /* if the version does not match */ + if (version != TLSX_ECH) { + /* we hit the end of the configs */ + if ( (word32)i + 2 >= echConfigsLen ) { + break; + } + + /* skip this config, +4 for version and length */ + i += length + 4; + continue; + } + + /* check if the length will overrun the buffer */ + if ((word32)i + length + 4 > echConfigsLen) { + break; + } + + if (workingConfig == NULL) { + workingConfig = + (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), heap, + DYNAMIC_TYPE_TMP_BUFFER); + configList = workingConfig; + if (workingConfig != NULL) { + workingConfig->next = NULL; + } + } + else { + lastConfig = workingConfig; + workingConfig->next = + (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), + heap, DYNAMIC_TYPE_TMP_BUFFER); + workingConfig = workingConfig->next; + } + + if (workingConfig == NULL) { + ret = MEMORY_E; + break; + } + + XMEMSET(workingConfig, 0, sizeof(WOLFSSL_EchConfig)); + + /* rawLen */ + workingConfig->rawLen = length + 4; + + /* raw body */ + workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen, + heap, DYNAMIC_TYPE_TMP_BUFFER); + if (workingConfig->raw == NULL) { + ret = MEMORY_E; + break; + } + + XMEMCPY(workingConfig->raw, echConfig, workingConfig->rawLen); + + /* skip over version and length */ + echConfig += 4; + + /* configId, 1 byte */ + workingConfig->configId = *(echConfig); + echConfig++; + /* kemId, 2 bytes */ + ato16(echConfig, &workingConfig->kemId); + echConfig += 2; + /* hpke public_key length, 2 bytes */ + ato16(echConfig, &hpkePubkeyLen); + echConfig += 2; + /* hpke public_key */ + XMEMCPY(workingConfig->receiverPubkey, echConfig, hpkePubkeyLen); + echConfig += hpkePubkeyLen; + /* cipherSuitesLen */ + ato16(echConfig, &cipherSuitesLen); + + workingConfig->cipherSuites = (EchCipherSuite*)XMALLOC(cipherSuitesLen, + heap, DYNAMIC_TYPE_TMP_BUFFER); + if (workingConfig->cipherSuites == NULL) { + ret = MEMORY_E; + break; + } + + echConfig += 2; + workingConfig->numCipherSuites = cipherSuitesLen / 4; + /* cipherSuites */ + for (j = 0; j < workingConfig->numCipherSuites; j++) { + ato16(echConfig + j * 4, &workingConfig->cipherSuites[j].kdfId); + ato16(echConfig + j * 4 + 2, + &workingConfig->cipherSuites[j].aeadId); + } + echConfig += cipherSuitesLen; + /* ignore the maximum name length */ + echConfig++; + /* publicNameLen */ + publicNameLen = *(echConfig); + workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1, + heap, DYNAMIC_TYPE_TMP_BUFFER); + if (workingConfig->publicName == NULL) { + ret = MEMORY_E; + break; + } + echConfig++; + /* publicName */ + XMEMCPY(workingConfig->publicName, echConfig, publicNameLen); + /* null terminated */ + workingConfig->publicName[publicNameLen] = 0; + + /* add length to go to next config, +4 for version and length */ + i += length + 4; + + /* check that we support this config */ + for (j = 0; j < HPKE_SUPPORTED_KEM_LEN; j++) { + if (hpkeSupportedKem[j] == workingConfig->kemId) + break; + } + + /* if we don't support the kem or at least one cipher suite */ + if (j >= HPKE_SUPPORTED_KEM_LEN || + EchConfigGetSupportedCipherSuite(workingConfig) < 0) + { + XFREE(workingConfig->cipherSuites, heap, + DYNAMIC_TYPE_TMP_BUFFER); + XFREE(workingConfig->publicName, heap, + DYNAMIC_TYPE_TMP_BUFFER); + XFREE(workingConfig->raw, heap, DYNAMIC_TYPE_TMP_BUFFER); + workingConfig = lastConfig; + } + } while ((word32)i < echConfigsLen); + + /* if we found valid configs */ + if (ret == 0 && configList != NULL) { + *outputConfigs = configList; + + return ret; + } + + workingConfig = configList; + + while (workingConfig != NULL) { + lastConfig = workingConfig; + workingConfig = workingConfig->next; + + XFREE(lastConfig->cipherSuites, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(lastConfig->publicName, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(lastConfig->raw, heap, DYNAMIC_TYPE_TMP_BUFFER); + + XFREE(lastConfig, heap, DYNAMIC_TYPE_TMP_BUFFER); + } + + if (ret == 0) + return WOLFSSL_FATAL_ERROR; + + return ret; +} + /* get the raw ech configs from our linked list of ech config structs */ int GetEchConfigsEx(WOLFSSL_EchConfig* configs, byte* output, word32* outputLen) { diff --git a/tests/api.c b/tests/api.c index 1383537be..ece94c471 100644 --- a/tests/api.c +++ b/tests/api.c @@ -50103,6 +50103,26 @@ static int test_wolfSSL_Tls13_ECH_params(void) ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, "ech-public-name.com", 1000, 1000, 1000)); + /* invalid ctx */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(NULL, + (char*)testBuf, sizeof(testBuf))); + /* invalid base64 configs */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + NULL, sizeof(testBuf))); + /* invalid length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + (char*)testBuf, 0)); + + /* invalid ctx */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(NULL, + testBuf, sizeof(testBuf))); + /* invalid configs */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, + NULL, sizeof(testBuf))); + /* invalid length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, + testBuf, 0)); + /* invalid ctx */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, NULL, &outputLen)); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index e24077365..645febed1 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3176,6 +3176,10 @@ WOLFSSL_LOCAL int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config); WOLFSSL_LOCAL int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen); + +WOLFSSL_LOCAL int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, + const byte* echConfigs, word32 echConfigsLen); + WOLFSSL_LOCAL int GetEchConfig(WOLFSSL_EchConfig* config, byte* output, word32* outputLen); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 7260799fe..beef3cfe8 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1181,6 +1181,12 @@ WOLFSSL_API WOLFSSL_METHOD *wolfSSLv23_method(void); WOLFSSL_API int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, word16 kemId, word16 kdfId, word16 aeadId); +WOLFSSL_API int wolfSSL_CTX_SetEchConfigsBase64(WOLFSSL_CTX* ctx, + const char* echConfigs64, word32 echConfigs64Len); + +WOLFSSL_API int wolfSSL_CTX_SetEchConfigs(WOLFSSL_CTX* ctx, + const byte* echConfigs, word32 echConfigsLen); + WOLFSSL_API int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output, word32* outputLen);