From dc71f861df8929deee300368b88ef47d45560695 Mon Sep 17 00:00:00 2001 From: vnugent Date: Mon, 1 Jul 2024 15:05:34 -0400 Subject: fix: #7 fix confusing inline functions --- tests/test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/test.c b/tests/test.c index 6e3851e..5feae79 100644 --- a/tests/test.c +++ b/tests/test.c @@ -378,7 +378,7 @@ static int TestPublicApiArgumentValidation() TEST(NCValidateSecretKey(NULL, &secKey), ARG_ERROR_POS_0) TEST(NCValidateSecretKey(ctx, NULL), ARG_ERROR_POS_1) /* Should fail with a zero key */ - TEST(NCValidateSecretKey(ctx, NCToSecKey(zero32)), E_OPERATION_FAILED) + TEST(NCValidateSecretKey(ctx, NCByteCastToSecretKey(zero32)), E_OPERATION_FAILED) /*Verify sig64 args test*/ TEST(NCVerifyDigest(NULL, &pubKey, zero32, sig64), ARG_ERROR_POS_0) @@ -502,10 +502,10 @@ static int TestKnownKeys(const NCContext* context) pubKey2 = FromHexString("421181660af5d39eb95e48a0a66c41ae393ba94ffeca94703ef81afbed724e5a", sizeof(NCPublicKey)); /*Test known keys*/ - TEST(NCValidateSecretKey(context, NCToSecKey(secKey1->data)), NC_SUCCESS); + TEST(NCValidateSecretKey(context, NCByteCastToSecretKey(secKey1->data)), NC_SUCCESS); /* Recover a public key from secret key 1 */ - TEST(NCGetPublicKey(context, NCToSecKey(secKey1->data), &pubKey), NC_SUCCESS); + TEST(NCGetPublicKey(context, NCByteCastToSecretKey(secKey1->data), &pubKey), NC_SUCCESS); /* Ensure the public key matches the known public key value */ TEST(memcmp(pubKey1->data, &pubKey, sizeof(pubKey)), 0); -- cgit From 23fe6e8c8596333c2183f0f4389817087442c551 Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 5 Jul 2024 00:03:48 -0400 Subject: push latest utils and changes --- CMakeLists.txt | 14 ++ CMakePresets.json | 24 +-- include/noscrypt.h | 2 +- include/noscryptutil.h | 76 +++++++++ src/hkdf.c | 15 +- src/hkdf.h | 4 +- src/nc-util.h | 46 ++++++ src/noscrypt.c | 15 +- src/noscryptutil.c | 418 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/test.c | 53 ++++++- 10 files changed, 630 insertions(+), 37 deletions(-) create mode 100644 include/noscryptutil.h create mode 100644 src/noscryptutil.c (limited to 'tests') diff --git a/CMakeLists.txt b/CMakeLists.txt index 53fbe29..493b18a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ project( set(_NC_PROJ_NAME "noscrypt") option(NC_BUILD_TESTS "Build tests" OFF) +option(NC_ENABLE_UTILS "Enables the sidecar utility library" OFF) option(NC_DISABLE_INPUT_VALIDATION "Disables public function input validation" OFF) option(NC_FETCH_MBEDTLS "Fetch Mbed-TLS from it's source repository locally" OFF) option(NC_FETCH_SECP256K1 "Fetch and locally build secp256k1 source code" ON) @@ -94,6 +95,17 @@ set(NOSCRYPT_HEADERS "src/nc-crypto.h" ) +#if utils are enabled, add the source files +if(NC_ENABLE_UTILS) + list(APPEND NOSCRYPT_SRCS "src/noscryptutil.c") + list(APPEND NOSCRYPT_HEADERS "include/noscryptutil.h") + + #notify the project that utils are enabled + list(APPEND NC_PROJ_DEFINTIONS NC_ENABLE_UTILS) + + message(STATUS "Utilities libraries are enabled") +endif() + #static/shared library add_library(${_NC_PROJ_NAME} SHARED ${NOSCRYPT_SRCS} ${NOSCRYPT_HEADERS}) add_library(${_NC_PROJ_NAME}_static STATIC ${NOSCRYPT_SRCS} ${NOSCRYPT_HEADERS}) @@ -366,11 +378,13 @@ if(NC_BUILD_TESTS) #add test executable and link to shared library for more realistic usage add_executable(nctest tests/test.c) target_link_libraries(nctest ${_NC_PROJ_NAME}) + target_include_directories(nctest PRIVATE include) target_include_directories(nctest PRIVATE src) #allow access to internal headers #enable c11 for testing target_compile_features(nctest PRIVATE c_std_11) + target_compile_definitions(nctest PRIVATE ${NC_PROJ_DEFINTIONS}) enable_testing() diff --git a/CMakePresets.json b/CMakePresets.json index 63ccfec..1b6d907 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -15,7 +15,7 @@ "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" - } + }, }, { "name": "x64-debug", @@ -27,7 +27,8 @@ }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", - "NC_BUILD_TESTS": true + "NC_BUILD_TESTS": true, + "NC_ENABLE_UTILS": true } }, { @@ -44,13 +45,18 @@ "CRYPTO_LIB": "openssl" } }, - { - "name": "x64-release", - "displayName": "x64 Release", - "inherits": "x64-debug", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NC_ENABLE_UTILS": true } + } ] } diff --git a/include/noscrypt.h b/include/noscrypt.h index bdfaa9f..8b39f17 100644 --- a/include/noscrypt.h +++ b/include/noscrypt.h @@ -228,7 +228,7 @@ NC_EXPORT NCResult NC_CC NCResultWithArgPosition(NCResult err, uint8_t argPositi * Parses an error code and returns the error code and the argument position that caused the error. * @param result The error code to parse -* @param argPositionOut A pointer to the argument position to write to +* @param argPositionOut A pointer to the argument position to write to (optionall, set to NULL of unobserved) * @return The error code */ NC_EXPORT int NC_CC NCParseErrorCode(NCResult result, uint8_t* argPositionOut); diff --git a/include/noscryptutil.h b/include/noscryptutil.h new file mode 100644 index 0000000..1a98698 --- /dev/null +++ b/include/noscryptutil.h @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Package: noscrypt +* File: noscryptutil.h +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public License +* as published by the Free Software Foundation; either version 2.1 +* of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with noscrypt. If not, see http://www.gnu.org/licenses/. +*/ + +/* +* noscrypt is a an open-source, strict C89 library that performs the basic +* cryptographic operations found in the Nostr protocol. It is designed to be +* portable and easy to use in any C89 compatible environment. It is also designed +*/ + +#pragma once + +#ifndef NOSCRYPTUTIL_H +#define NOSCRYPTUTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "noscrypt.h" + +#define E_OUT_OF_MEMORY -10 + +typedef struct nc_util_enc_struct NCUtilEncryptionContext; + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize); + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, int32_t plaintextSize); + +NC_EXPORT NCUtilEncryptionContext* NC_CC NCUtilAllocEncryptionContext(uint32_t encVersion); + +NC_EXPORT NCResult NC_CC NCUtilInitEncryptionContext( + NCUtilEncryptionContext* encCtx, + const uint8_t* plainText, + uint32_t plainTextSize +); + +NC_EXPORT void NC_CC NCUtilFreeEncryptionContext(NCUtilEncryptionContext* encCtx); + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptedSize(const NCUtilEncryptionContext* encCtx); + +NC_EXPORT NCResult NC_CC NCUtilReadEncryptedData( + const NCUtilEncryptionContext* encCtx, + uint8_t* output, + uint32_t outputSize +); + +NC_EXPORT NCResult NCUtilSetEncryptionProperty( + NCUtilEncryptionContext* ctx, + uint32_t property, + uint8_t* value, + uint32_t valueLen +); + +#ifdef __cplusplus +} +#endif + +#endif /* NOSCRYPTUTIL_H */ \ No newline at end of file diff --git a/src/hkdf.c b/src/hkdf.c index 0d91d14..cff7d60 100644 --- a/src/hkdf.c +++ b/src/hkdf.c @@ -21,23 +21,10 @@ #include "hkdf.h" -/* Include string for memmove */ -#include - #define HKDF_MIN(a, b) (a < b ? a : b) STATIC_ASSERT(HKDF_IN_BUF_SIZE > SHA256_DIGEST_SIZE, "HDK Buffer must be at least the size of the underlying hashing alg output") -static _nc_fn_inline void ncWriteSpanS(span_t* span, uint32_t offset, const uint8_t* data, uint32_t size) -{ - DEBUG_ASSERT2(span != NULL, "Expected span to be non-null") - DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") - DEBUG_ASSERT2(offset + size <= span->size, "Expected offset + size to be less than span size") - - /* Copy data to span */ - memmove(span->data + offset, data, size); -} - static _nc_fn_inline void debugValidateHandler(const struct nc_hkdf_fn_cb_struct* handler) { DEBUG_ASSERT(handler != NULL) @@ -114,7 +101,7 @@ cstatus_t hkdfExpandProcess( DEBUG_ASSERT(tLen <= sizeof(t)); /* write the T buffer back to okm */ - ncWriteSpanS(okm, okmOffset, t, tLen); + ncSpanWrite(*okm, okmOffset, t, tLen); /* shift base okm pointer by T */ okmOffset += tLen; diff --git a/src/hkdf.h b/src/hkdf.h index 460e203..2e3a55e 100644 --- a/src/hkdf.h +++ b/src/hkdf.h @@ -42,12 +42,12 @@ /* typedefs for hdkf callback functions */ -typedef cstatus_t (*hmac_hash_func)(void* ctx, const cspan_t* data); +typedef cstatus_t (*hmac_hash_fn)(void* ctx, const cspan_t* data); typedef cstatus_t (*hmac_finish_fn)(void* ctx, sha256_t hmacOut32); struct nc_hkdf_fn_cb_struct { - hmac_hash_func update; + hmac_hash_fn update; hmac_finish_fn finish; }; diff --git a/src/nc-util.h b/src/nc-util.h index dd319c7..0647f4c 100644 --- a/src/nc-util.h +++ b/src/nc-util.h @@ -68,6 +68,28 @@ #define _overflow_check(x) #endif +#ifdef NC_EXTREME_COMPAT + + void _nc_memmove(void* dst, const void* src, uint32_t size) + { + uint32_t i; + + for (i = 0; i < size; i++) + { + ((uint8_t*)dst)[i] = ((uint8_t*)src)[i]; + } + } + + #define MEMMOV _nc_memmove + +#else + + /* Include string for memmove */ + #include + #define MEMMOV(dst, src, size) memmove(dst, src, size) + +#endif /* NC_EXTREME_COMPAT */ + typedef struct memory_span_struct { uint8_t* data; @@ -92,4 +114,28 @@ static _nc_fn_inline void ncSpanInit(span_t* span, uint8_t* data, uint32_t size) span->size = size; } +static _nc_fn_inline void ncSpanWrite(span_t span, uint32_t offset, const uint8_t* data, uint32_t size) +{ + DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null") + DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") + DEBUG_ASSERT2(offset + size <= span.size, "Expected offset + size to be less than span size") + + /* Copy data to span */ + MEMMOV(span.data + offset, data, size); +} + +static _nc_fn_inline void ncSpanAppend(span_t span, uint32_t* offset, const uint8_t* data, uint32_t size) +{ + DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null") + DEBUG_ASSERT2(offset != NULL, "Expected offset to be non-null") + DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") + DEBUG_ASSERT2(*offset + size <= span.size, "Expected offset + size to be less than span size") + + /* Copy data to span */ + MEMMOV(span.data + *offset, data, size); + + /* Increment offset */ + *offset += size; +} + #endif /* !_NC_UTIL_H */ \ No newline at end of file diff --git a/src/noscrypt.c b/src/noscrypt.c index c523262..910f559 100644 --- a/src/noscrypt.c +++ b/src/noscrypt.c @@ -32,10 +32,6 @@ */ #define ZERO_FILL(x, size) ncCryptoSecureZero(x, size) -/* Include string for memmove */ -#include -#define MEMMOV(dst, src, size) memmove(dst, src, size) - /* * Validation macros */ @@ -44,7 +40,6 @@ #define CHECK_INVALID_ARG(x, argPos) if(x == NULL) return NCResultWithArgPosition(E_INVALID_ARG, argPos); #define CHECK_NULL_ARG(x, argPos) if(x == NULL) return NCResultWithArgPosition(E_NULL_PTR, argPos); #define CHECK_ARG_RANGE(x, min, max, argPos) if(x < min || x > max) return NCResultWithArgPosition(E_ARGUMENT_OUT_OF_RANGE, argPos); - #define CHECK_CONTEXT_STATE(ctx, argPos) CHECK_INVALID_ARG(ctx->secpCtx, argPos) #else /* empty macros */ #define CHECK_INVALID_ARG(x) @@ -52,6 +47,8 @@ #define CHECK_ARG_RANGE(x, min, max, argPos) #endif /* !NC_DISABLE_INPUT_VALIDATION */ +#define CHECK_CONTEXT_STATE(ctx, argPos) CHECK_INVALID_ARG(ctx->secpCtx, argPos) + /* * Actual, private defintion of the NCContext structure * to allow for future development and ABI backords @@ -449,7 +446,6 @@ NC_EXPORT NCResult NC_CC NCResultWithArgPosition(NCResult err, uint8_t argPositi return -(((NCResult)argPosition << NC_ARG_POSITION_OFFSET) | -err); } - NC_EXPORT int NC_CC NCParseErrorCode(NCResult result, uint8_t* argPositionOut) { NCResult asPositive; @@ -460,7 +456,12 @@ NC_EXPORT int NC_CC NCParseErrorCode(NCResult result, uint8_t* argPositionOut) /* Get the error code from the lower 8 bits and the argument position from the upper 8 bits*/ code = -(asPositive & NC_ERROR_CODE_MASK); - *argPositionOut = (asPositive >> NC_ARG_POSITION_OFFSET) & 0xFF; + + /* Allow argument position assignment to be null */ + if (argPositionOut) + { + *argPositionOut = (asPositive >> NC_ARG_POSITION_OFFSET) & 0xFF; + } return code; } diff --git a/src/noscryptutil.c b/src/noscryptutil.c new file mode 100644 index 0000000..b7723cb --- /dev/null +++ b/src/noscryptutil.c @@ -0,0 +1,418 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Package: noscrypt +* File: noscryptutil.h +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public License +* as published by the Free Software Foundation; either version 2.1 +* of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with noscrypt. If not, see http://www.gnu.org/licenses/. +*/ + + +#include +#include "nc-util.h" +#include "nc-crypto.h" + +/* +* Validation macros +*/ + +#ifdef NC_EXTREME_COMPAT + #error "Utilities library must be disabled when using extreme compat mode" +#endif /* NC_EXTREME_COMPAT */ + +#include +#include + +#define _nc_mem_free(x) if(x != NULL) { free(x); x = NULL; } +#define _nc_mem_alloc(elements, size) calloc(elements, size); + +#ifndef NC_INPUT_VALIDATION_OFF + #define CHECK_INVALID_ARG(x, argPos) if(x == NULL) return NCResultWithArgPosition(E_INVALID_ARG, argPos); + #define CHECK_NULL_ARG(x, argPos) if(x == NULL) return NCResultWithArgPosition(E_NULL_PTR, argPos); + #define CHECK_ARG_RANGE(x, min, max, argPos) if(x < min || x > max) return NCResultWithArgPosition(E_ARGUMENT_OUT_OF_RANGE, argPos); +#else + /* empty macros */ + #define CHECK_INVALID_ARG(x) + #define CHECK_NULL_ARG(x, argPos) + #define CHECK_ARG_RANGE(x, min, max, argPos) +#endif /* !NC_DISABLE_INPUT_VALIDATION */ + + +/* performs a log2 on integer types */ +#define _math_int_log2(x) (int32_t)log2((double)x) + +#define MIN_PADDING_SIZE 0x20 +#define NIP44_VERSION_SIZE 0x01 +#define NIP44_PT_LEN_SIZE 0x02 + +/* Currently were on nip44 version 2 */ +const static uint8_t Nip44VersionValue = 0x02; + +typedef struct nc_util_enc_buffer_state +{ + uint8_t* ciphertext; + uint32_t ciphertextSize; + +} NCCipherTextOutState; + +struct nc_util_enc_struct { + + /* Dynamically allocated during initialization */ + NCCipherTextOutState* outState; + + const uint8_t* plaintext; + + uint32_t plaintextSize; + + NCEncryptionArgs encArgs; +}; + +static _nc_fn_inline int32_t _calcNip44PtPadding(int32_t plaintextSize) +{ + int32_t chunk, nextPower, factor; + + /* + * Taken from https://github.com/nostr-protocol/nips/blob/master/44.md + * + * I believe the idea is to add consisten padding for some better + * disgusing of the plainText data. + */ + + if (plaintextSize <= MIN_PADDING_SIZE) + { + return MIN_PADDING_SIZE; + } + + nextPower = _math_int_log2(plaintextSize - 1); + + nextPower += 1; + + nextPower = 1 << nextPower; + + if (nextPower <= 256) + { + chunk = 32; + } + else + { + chunk = nextPower / 8; + } + + factor = plaintextSize - 1; + + factor /= chunk; + + factor += 1; + + return chunk * factor; +} + +static _nc_fn_inline int32_t _calcNip44TotalOutSize(int32_t inputSize) +{ + int32_t bufferSize; + + /* + * Buffer size for nip44 is calculated as follows: + * 1 byte for the version + * 32 bytes for the nonce + * 2 bytes for the length of the plainText + * ... padding size + * 32 bytes for the MAC + */ + + bufferSize = NIP44_VERSION_SIZE; + + bufferSize += NC_ENCRYPTION_NONCE_SIZE; + + bufferSize += NIP44_PT_LEN_SIZE; + + bufferSize += _calcNip44PtPadding(inputSize); + + bufferSize += NC_ENCRYPTION_MAC_SIZE; + + return bufferSize; +} + +static NCResult _nip44EncryptCompleteCore( + const NCContext* libContext, + const NCSecretKey* sk, + const NCPublicKey* pk, + NCEncryptionArgs encArgs, + span_t cipherText, + span_t plainText +) +{ + + NCResult result; + uint32_t outPos, paddedCtSize; + uint16_t ptSize; + + outPos = 0; + + DEBUG_ASSERT(encArgs.version == NC_ENC_VERSION_NIP44); + + /* Padded size is required to know how large the CT buffer is for encryption */ + paddedCtSize = (int32_t)_calcNip44PtPadding((int32_t)plainText.size); + + /* Start by appending the version number */ + ncSpanAppend(cipherText, &outPos, &Nip44VersionValue, 0x01); + + /* next is nonce data */ + ncSpanAppend(cipherText, &outPos, encArgs.nonceData, NC_ENCRYPTION_NONCE_SIZE); + DEBUG_ASSERT(outPos == 1 + NC_ENCRYPTION_NONCE_SIZE); + + /* + * So this is the tricky part. The encryption operation appens directly + * on the ciphertext segment + * + * All current implementations allow overlapping input and output buffers + * so we can assign the pt segment on the encryption args + */ + + /* + * Since the message size and padding bytes will get encrypted, + * the buffer should currently point to the start of the encryption segment + * + * The size of the data to encrypt is the padded size plus the size of the + * plainText size field. + */ + + encArgs.inputData = (cipherText.data + outPos); + encArgs.outputData = (cipherText.data + outPos); + encArgs.dataSize = paddedCtSize + sizeof(uint16_t); /* Plaintext + pt size must be encrypted */ + + ptSize = (uint16_t)plainText.size; + + /* Can write the plainText size to buffer now */ + ncSpanAppend(cipherText, &outPos, &ptSize, sizeof(uint16_t)); + + /* concat plainText */ + ncSpanAppend(cipherText, &outPos, plainText.data, plainText.size); + + /* Time to perform encryption operation */ + result = NCEncrypt(libContext, sk, pk, &encArgs); + + if (result == NC_SUCCESS) + { + + } +} + +static NCResult _nip44EncryptCompleteCore( + NCUtilEncryptionContext* encCtx, + const NCContext* libContext, + const NCSecretKey* sk, + const NCPublicKey* pk +) +{ + span_t cipherText, plainText; + + /* Set up spans */ + ncSpanInit( + &cipherText, + encCtx->outState->ciphertext, + encCtx->outState->ciphertextSize + ); + + ncSpanInit( + &plainText, + encCtx->plaintext, + encCtx->plaintextSize + ); + + return _nip44EncryptCompleteCore( + libContext, + sk, + pk, + encCtx->encArgs, + cipherText, + plainText + ); +} + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize) +{ + int32_t paddingSize; + + CHECK_ARG_RANGE(plaintextSize, 0, INT32_MAX, 1) + + switch (encVersion) + { + default: + return E_VERSION_NOT_SUPPORTED; + + case NC_ENC_VERSION_NIP04: + return plaintextSize; + + case NC_ENC_VERSION_NIP44: + paddingSize = _calcNip44PtPadding(plaintextSize); + + DEBUG_ASSERT(paddingSize > 0) + + return (NCResult)(paddingSize); + } +} + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, int32_t plaintextSize) +{ + int32_t totalSize; + + CHECK_ARG_RANGE(plaintextSize, 0, INT32_MAX, 1) + + switch (encVersion) + { + default: + return E_VERSION_NOT_SUPPORTED; + + /* + * NIP-04 simply uses AES to 1:1 encrypt the plainText + * to ciphertext. + */ + case NC_ENC_VERSION_NIP04: + return plaintextSize; + + case NC_ENC_VERSION_NIP44: + totalSize = _calcNip44TotalOutSize(plaintextSize); + + DEBUG_ASSERT(totalSize > 0) + + return (NCResult)(totalSize); + + } +} + + +NC_EXPORT NCUtilEncryptionContext* NC_CC NCUtilAllocEncryptionContext(uint32_t encVersion) +{ + NCUtilEncryptionContext* encCtx; + + /* + * Alloc context on heap + */ + encCtx = (NCUtilEncryptionContext*)_nc_mem_alloc(1, sizeof(NCUtilEncryptionContext)); + + if (encCtx != NULL) + { + encCtx->encArgs.version = encVersion; + } + + return encCtx; +} + +NC_EXPORT void NC_CC NCUtilFreeEncryptionContext(NCUtilEncryptionContext* encCtx) +{ + if (!encCtx) + { + return; + } + + /* Free output buffers */ + _nc_mem_free(encCtx->outState); + + /* context can be released */ + _nc_mem_free(encCtx); +} + +NC_EXPORT NCResult NC_CC NCUtilInitEncryptionContext( + NCUtilEncryptionContext* encCtx, + const uint8_t* plainText, + uint32_t plainTextSize +) +{ + + NCResult outputSize; + NCCipherTextOutState* output; + + CHECK_NULL_ARG(encCtx, 0) + CHECK_NULL_ARG(plainText, 1) + CHECK_ARG_RANGE(plainTextSize, 0, INT32_MAX, 2) + + /* + * The output state must not have alraedy been allocated + */ + if (encCtx->outState) + { + return E_INVALID_ARG; + } + + /* + * Calculate the correct output size to store the encryption + * data for the given cipher version + */ + outputSize = NCUtilGetEncryptionBufferSize(encCtx->encArgs.version, plainTextSize); + + if (outputSize <= 0) + { + return outputSize; + } + + /*Alloc output buffer within the struct */ + output = (NCCipherTextOutState*)_nc_mem_alloc(sizeof(NCCipherTextOutState) + (int)outputSize, 1); + + if (!output) + { + return E_OUT_OF_MEMORY; + } + + /* set cipertext buffer to end of the structure memory */ + output->ciphertext = (uint8_t*)(output + 1); + output->ciphertextSize = outputSize; + + encCtx->outState = output; + encCtx->plaintext = plainText; + encCtx->plaintextSize = plainTextSize; + + return NC_SUCCESS; +} + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptedSize(const NCUtilEncryptionContext* encCtx) +{ + CHECK_NULL_ARG(encCtx, 0); + + return (NCResult)(encCtx->outState->ciphertextSize); +} + +NC_EXPORT NCResult NC_CC NCUtilReadEncryptedData( + const NCUtilEncryptionContext* encCtx, + uint8_t* output, + uint32_t outputSize +) +{ + CHECK_NULL_ARG(encCtx, 0) + CHECK_NULL_ARG(output, 1) + CHECK_ARG_RANGE(outputSize, 0, INT32_MAX, 2) + + if (outputSize < encCtx->outState->ciphertextSize) + { + return E_OPERATION_FAILED; + } + + MEMMOV(output, encCtx->outState->ciphertext, encCtx->outState->ciphertextSize); + + return (NCResult)encCtx->outState->ciphertextSize; +} + +NC_EXPORT NCResult NCUtilSetEncryptionProperty( + NCUtilEncryptionContext* ctx, + uint32_t property, + uint8_t* value, + uint32_t valueLen +) +{ + + CHECK_NULL_ARG(ctx, 0) + + /* All other arguments are verified */ + return NCSetEncryptionPropertyEx(&ctx->encArgs, property, value, valueLen); +} diff --git a/tests/test.c b/tests/test.c index 5feae79..3833e7b 100644 --- a/tests/test.c +++ b/tests/test.c @@ -101,6 +101,10 @@ static int InitKepair(const NCContext* context, NCSecretKey* secKey, NCPublicKey static int TestKnownKeys(const NCContext* context); static int TestCorrectEncryption(const NCContext* context); +#ifdef NC_ENABLE_UTILS +static int TestUtilFunctions(void); +#endif + #ifndef NC_INPUT_VALIDATION_OFF static int TestPublicApiArgumentValidation(void); #endif @@ -167,6 +171,13 @@ static int RunTests(void) return 1; } +#ifdef NC_ENABLE_UTILS + if (TestUtilFunctions() != 0) + { + return 1; + } +#endif + TEST(NCDestroyContext(ctx), NC_SUCCESS) PRINTL("\nSUCCESS All tests passed") @@ -277,6 +288,8 @@ static int TestPublicApiArgumentValidation() NCEncryptionArgs cryptoData; + PRINTL("TEST: Public API argument validation tests") + { /* * Test arguments for encryption properties @@ -332,6 +345,7 @@ static int TestPublicApiArgumentValidation() TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, NC_NIP04_AES_KEY_SIZE - 1), ARG_RANGE_ERROR_POS_3) } + /* Prep the crypto structure for proper usage */ ENSURE(NCSetEncryptionProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44) == NC_SUCCESS); ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, sizeof(nonce)) == NC_SUCCESS); ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, sizeof(hmacKeyOut)) == NC_SUCCESS); @@ -339,14 +353,13 @@ static int TestPublicApiArgumentValidation() /* Assign the encryption material */ ENSURE(NCSetEncryptionData(&cryptoData, zero32, sig64, sizeof(zero32)) == NC_SUCCESS); - PRINTL("TEST: Public API argument validation tests") FillRandomData(ctxRandom, 32); FillRandomData(nonce, sizeof(nonce)); /* * Alloc context structure on the heap before use. - * THIS WILL LEAK IN THE CURRENT CONFIG ALWAYS FEE UNDER NORMAL CONDITIONS + * THIS WILL LEAK IN THE CURRENT CONFIG ALWAYS FREE UNDER NORMAL CONDITIONS */ ctx = (NCContext*)malloc(NCGetContextStructSize()); TASSERT(ctx != NULL) @@ -540,6 +553,8 @@ static int TestCorrectEncryption(const NCContext* context) NCEncryptionArgs cryptoData; NCMacVerifyArgs macVerifyArgs; + PRINTL("TEST: Correct encryption") + ENSURE(NCSetEncryptionProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44) == NC_SUCCESS); ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, sizeof(nonce)) == NC_SUCCESS); ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, NC_HMAC_KEY_SIZE) == NC_SUCCESS); @@ -552,8 +567,6 @@ static int TestCorrectEncryption(const NCContext* context) macVerifyArgs.payload = cipherText; macVerifyArgs.payloadSize = TEST_ENC_DATA_SIZE; - PRINTL("TEST: Correct encryption") - /* init a sending and receiving key */ FillRandomData(&secKey1, sizeof(NCSecretKey)); FillRandomData(&secKey2, sizeof(NCSecretKey)); @@ -590,6 +603,38 @@ static int TestCorrectEncryption(const NCContext* context) return 0; } +#ifdef NC_ENABLE_UTILS + +#include + +/* +* This function is not currently public, but we can access it for testing +* purposes because it is used to calculate the output buffer size for encryption +*/ +extern NCResult NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize); + +/* Padding tests taken from the nip44 repo vectors.json file */ +const int32_t _padTestActual[24] = { 16, 32, 33, 37, 45, 49, 64, 65, 100, 111, 200, 250, 320, 383, 384, 400, 500, 512, 515, 700, 800, 900, 1020, 65536 }; +const int32_t _padTestExpected[24] = { 32, 32, 64, 64, 64, 64, 64, 96, 128, 128, 224, 256, 320, 384, 384, 448, 512, 512, 640, 768, 896, 1024, 1024, 65536 }; + +static int TestUtilFunctions(void) +{ + PRINTL("TEST: Util functions") + + for (int i = 0; i < 24; i++) + { + int32_t totalSize = _padTestExpected[i] + 67; + + TEST(NCUtilGetEncryptionPaddedSize(NC_ENC_VERSION_NIP44, _padTestActual[i]), _padTestExpected[i]); + TEST(NCUtilGetEncryptionBufferSize(NC_ENC_VERSION_NIP44, _padTestActual[i]), totalSize); + } + + PRINTL("PASSED: Util functions tests completed") + return 0; +} + +#endif + static void FillRandomData(void* pbBuffer, size_t length) { -- cgit From 8df8c5aed4ac626171b451b5422c3b207e88000b Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 11 Jul 2024 21:39:39 -0400 Subject: feat: Update sidecar utils library --- include/noscryptutil.h | 125 ++++++++++++-- src/nc-util.h | 19 ++- src/noscrypt.c | 168 ++++++++++--------- src/noscryptutil.c | 433 ++++++++++++++++++++++++++++++++----------------- tests/hex.h | 51 +++--- tests/test.c | 107 +++++++++--- 6 files changed, 600 insertions(+), 303 deletions(-) (limited to 'tests') diff --git a/include/noscryptutil.h b/include/noscryptutil.h index 1a98698..7db5738 100644 --- a/include/noscryptutil.h +++ b/include/noscryptutil.h @@ -19,9 +19,8 @@ */ /* -* noscrypt is a an open-source, strict C89 library that performs the basic -* cryptographic operations found in the Nostr protocol. It is designed to be -* portable and easy to use in any C89 compatible environment. It is also designed +* This header includes some optional high-level nostr crypto utility functions +* for much easer app development. */ #pragma once @@ -33,42 +32,132 @@ extern "C" { #endif -#include #include "noscrypt.h" #define E_OUT_OF_MEMORY -10 -typedef struct nc_util_enc_struct NCUtilEncryptionContext; +#define NC_UTIL_CIPHER_MODE_ENCRYPT 0x00ui32 +#define NC_UTIL_CIPHER_MODE_DECRYPT 0x01ui32 +#define NC_UTIL_CIPHER_ZERO_ON_FREE 0x02ui32 -NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize); +/* +* The encryption context structure. This structure is used to store the state +* of the encryption operation. The structure is opaque and should not be accessed +* directly. +*/ +typedef struct nc_util_enc_struct NCUtilCipherContext; + +/* +* Gets the size of the padded buffer required for an encryption operation. +* @param encVersion The encryption specification version to use +* @param plaintextSize The size of the plaintext buffer in bytes +* @return The size of the padded buffer in bytes +*/ +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, uint32_t plaintextSize); -NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, int32_t plaintextSize); +/* +* Gets the size of the payload buffer required for an encryption operation. +* @param encVersion The encryption specification version to use +* @param plaintextSize The size of the plaintext buffer in bytes +* @return The size of the payload buffer in bytes +* @note The payload buffer is the final buffer to be sent to a nostr user. For nip04 this +* is a raw AES message, for nip44 this is a mucher lager buffer. See the nostr specifications +* for more information. +*/ +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, uint32_t plaintextSize); -NC_EXPORT NCUtilEncryptionContext* NC_CC NCUtilAllocEncryptionContext(uint32_t encVersion); +/* +* Allocates a new encryption context and sets the encryption version and flags. The encryption context +* must be freed with NCUtilCipherFree when it is no longer needed. +* @param encVersion The encryption specification version to use +* @param flags The flags to set on the encryption context +* @return A valid pointer to an encryption context or NULL if the operation failed +*/ +NC_EXPORT NCUtilCipherContext* NC_CC NCUtilCipherAlloc(uint32_t encVersion, uint32_t flags); -NC_EXPORT NCResult NC_CC NCUtilInitEncryptionContext( - NCUtilEncryptionContext* encCtx, - const uint8_t* plainText, - uint32_t plainTextSize +/* +* Initializes the encryption context with the input data and size. This function will + internally allocate a the required output buffer for the ciper operation. You may only call + this function once. +* @param encCtx A valid pointer to an allocated encryption context +* @param inputData A pointer to the input data for the ciper +* @param inputSize The size of the input data +* @return NC_SUCCESS if the operation was successful, otherwise an error code. Use NCParseErrorCode to +the error code and positional argument that caused the error +*/ +NC_EXPORT NCResult NC_CC NCUtilCipherInit( + NCUtilCipherContext* encCtx, + const uint8_t* inputData, + uint32_t inputSize ); -NC_EXPORT void NC_CC NCUtilFreeEncryptionContext(NCUtilEncryptionContext* encCtx); +/* +* Frees the encryption context and clears the memory if the NC_UTIL_CIPHER_ZERO_ON_FREE +* flag is set. +* @param encCtx A valid pointer to an allocated encryption context to free +*/ +NC_EXPORT void NC_CC NCUtilCipherFree(NCUtilCipherContext* encCtx); -NC_EXPORT NCResult NC_CC NCUtilGetEncryptedSize(const NCUtilEncryptionContext* encCtx); +/* +* Gets the output size of the encryption context. This function will return the size of +* the output buffer that will be written to when calling NCUtilCipherReadOutput. +* @param encCtx A valid pointer to an allocated encryption context +* @return The size of the output buffer in bytes +*/ +NC_EXPORT NCResult NC_CC NCUtilCipherGetOutputSize(const NCUtilCipherContext* encCtx); -NC_EXPORT NCResult NC_CC NCUtilReadEncryptedData( - const NCUtilEncryptionContext* encCtx, +/* +* Reads the output buffer from the encryption context. This function will copy the output +* buffer to the output buffer provided. The output buffer must be at least the size of the +* output buffer returned by NCUtilCipherGetOutputSize. +* @param encCtx A valid pointer to an initialized encryption context +* @param output A pointer to the output buffer to copy the output to +* @param outputSize The size of the output buffer in bytes +* @returns The number of bytes written to the output buffer or an error code. Use NCParseErrorCode +* to get the error code and positional argument that caused the error +*/ +NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput( + const NCUtilCipherContext* encCtx, uint8_t* output, uint32_t outputSize ); -NC_EXPORT NCResult NCUtilSetEncryptionProperty( - NCUtilEncryptionContext* ctx, +/* +* Sets a property on the encryption context. Equivalent to calling NCSetEncryptionPropertyEx +* @param ctx A valid pointer to an encryption context +* @param property The property to set +* @param value A pointer to the value to set +* @param valueLen The length of the value +* @return NC_SUCCESS if the operation was successful, otherwise an error code. Use NCParseErrorCode to +* get the error code and positional argument that caused the error +*/ +NC_EXPORT NCResult NCUtilCipherSetProperty( + NCUtilCipherContext* ctx, uint32_t property, uint8_t* value, uint32_t valueLen ); +/* +* Performs the desired ciper option once. This may either cause an encryption +* or decryption operation to be performed. Regardless of the operation, input data +* is consumed and output data is produced. +* @param encCtx A valid pointer to an initialized encryption context +* @param libContext A valid pointer to an NCContext structure +* @param sk A valid pointer to the sender's private key +* @param pk A valid pointer to the receivers public key +* @return NC_SUCCESS if the operation was successful, otherwise an error code. Use NCParseErrorCode to +* get the error code and positional argument that caused the error. +* @note This function should only be called once. However it is indempotent and deterministic +* so the exact same operation should happen if called again. +*/ +NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( + const NCUtilCipherContext* encCtx, + const NCContext* libContext, + const NCSecretKey* sk, + const NCPublicKey* pk +); + #ifdef __cplusplus } #endif diff --git a/src/nc-util.h b/src/nc-util.h index 0647f4c..e94a222 100644 --- a/src/nc-util.h +++ b/src/nc-util.h @@ -126,9 +126,9 @@ static _nc_fn_inline void ncSpanWrite(span_t span, uint32_t offset, const uint8_ static _nc_fn_inline void ncSpanAppend(span_t span, uint32_t* offset, const uint8_t* data, uint32_t size) { - DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null") - DEBUG_ASSERT2(offset != NULL, "Expected offset to be non-null") - DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") + DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null") + DEBUG_ASSERT2(offset != NULL, "Expected offset to be non-null") + DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") DEBUG_ASSERT2(*offset + size <= span.size, "Expected offset + size to be less than span size") /* Copy data to span */ @@ -138,4 +138,17 @@ static _nc_fn_inline void ncSpanAppend(span_t span, uint32_t* offset, const uint *offset += size; } +static _nc_fn_inline span_t ncSpanSlice(span_t span, uint32_t offset, uint32_t size) +{ + span_t slice; + + DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null"); + DEBUG_ASSERT2(offset + size <= span.size, "Expected offset + size to be less than span size") + + /* Initialize slice, offset input data by the specified offset */ + ncSpanInit(&slice, span.data + offset, size); + + return slice; +} + #endif /* !_NC_UTIL_H */ \ No newline at end of file diff --git a/src/noscrypt.c b/src/noscrypt.c index 910f559..01ec136 100644 --- a/src/noscrypt.c +++ b/src/noscrypt.c @@ -272,14 +272,13 @@ static cstatus_t _chachaEncipher(const struct nc_expand_keys* keys, NCEncryption static _nc_fn_inline cstatus_t _getMessageKey( const struct conversation_key* converstationKey, - const cspan_t* nonce, + cspan_t nonce, struct message_key* messageKey ) { cspan_t prkSpan; span_t okmSpan; - DEBUG_ASSERT2(nonce != NULL, "Expected valid nonce buffer") DEBUG_ASSERT2(converstationKey != NULL, "Expected valid conversation key") DEBUG_ASSERT2(messageKey != NULL, "Expected valid message key buffer") @@ -287,7 +286,7 @@ static _nc_fn_inline cstatus_t _getMessageKey( ncSpanInit(&okmSpan, messageKey->value, sizeof(struct message_key)); /* Output produces a message key (write it directly to struct memory) */ /* Nonce is the info */ - return ncCryptoSha256HkdfExpand(&prkSpan, nonce, &okmSpan); + return ncCryptoSha256HkdfExpand(&prkSpan, &nonce, &okmSpan); } static _nc_fn_inline NCResult _encryptNip44Ex( @@ -312,7 +311,7 @@ static _nc_fn_inline NCResult _encryptNip44Ex( ncSpanInitC(&nonceSpan, args->nonceData, NC_ENCRYPTION_NONCE_SIZE); /* Message key will be derrived on every encryption call */ - if (_getMessageKey(ck, &nonceSpan, &messageKey) != CSTATUS_OK) + if (_getMessageKey(ck, nonceSpan, &messageKey) != CSTATUS_OK) { result = E_OPERATION_FAILED; goto Cleanup; @@ -351,7 +350,7 @@ static _nc_fn_inline NCResult _decryptNip44Ex(const NCContext* ctx, const struct ncSpanInitC(&nonceSpan, args->nonceData, NC_ENCRYPTION_NONCE_SIZE); - if (_getMessageKey(ck, &nonceSpan, &messageKey) != CSTATUS_OK) + if (_getMessageKey(ck, nonceSpan, &messageKey) != CSTATUS_OK) { result = E_OPERATION_FAILED; goto Cleanup; @@ -372,17 +371,16 @@ Cleanup: return result; } -static _nc_fn_inline cstatus_t _computeHmac(const uint8_t key[NC_HMAC_KEY_SIZE], const cspan_t* payload, sha256_t hmacOut) +static _nc_fn_inline cstatus_t _computeHmac(const uint8_t key[NC_HMAC_KEY_SIZE], cspan_t payload, sha256_t hmacOut) { cspan_t keySpan; DEBUG_ASSERT2(key != NULL, "Expected valid hmac key") - DEBUG_ASSERT2(payload != NULL, "Expected valid mac verification args") DEBUG_ASSERT2(hmacOut != NULL, "Expected valid hmac output buffer") ncSpanInitC(&keySpan, key, NC_HMAC_KEY_SIZE); - return ncCryptoHmacSha256(&keySpan, payload, hmacOut); + return ncCryptoHmacSha256(&keySpan, &payload, hmacOut); } static NCResult _verifyMacEx( @@ -408,7 +406,7 @@ static NCResult _verifyMacEx( * Message key is again required for the hmac verification */ - if (_getMessageKey((struct conversation_key*)conversationKey, &nonceSpan, &messageKey) != CSTATUS_OK) + if (_getMessageKey((struct conversation_key*)conversationKey, nonceSpan, &messageKey) != CSTATUS_OK) { result = E_OPERATION_FAILED; goto Cleanup; @@ -420,7 +418,7 @@ static NCResult _verifyMacEx( /* * Compute the hmac of the data using the computed hmac key */ - if (_computeHmac(keys->hmac_key, &payloadSpan, hmacOut) != CSTATUS_OK) + if (_computeHmac(keys->hmac_key, payloadSpan, hmacOut) != CSTATUS_OK) { result = E_OPERATION_FAILED; goto Cleanup; @@ -888,8 +886,8 @@ Cleanup: } NC_EXPORT NCResult NC_CC NCDecryptEx( - const NCContext* ctx, - const uint8_t conversationKey[NC_CONV_KEY_SIZE], + const NCContext* ctx, + const uint8_t conversationKey[NC_CONV_KEY_SIZE], NCEncryptionArgs* args ) { @@ -906,12 +904,12 @@ NC_EXPORT NCResult NC_CC NCDecryptEx( switch (args->version) { - case NC_ENC_VERSION_NIP44: - return _decryptNip44Ex(ctx, (struct conversation_key*)conversationKey, args); + case NC_ENC_VERSION_NIP44: + return _decryptNip44Ex(ctx, (struct conversation_key*)conversationKey, args); - case NC_ENC_VERSION_NIP04: - default: - return E_VERSION_NOT_SUPPORTED; + case NC_ENC_VERSION_NIP04: + default: + return E_VERSION_NOT_SUPPORTED; } } @@ -942,26 +940,26 @@ NC_EXPORT NCResult NC_CC NCDecrypt( switch (args->version) { - case NC_ENC_VERSION_NIP44: + case NC_ENC_VERSION_NIP44: + { + if ((result = _computeSharedSecret(ctx, sk, pk, &sharedSecret)) != NC_SUCCESS) { - if ((result = _computeSharedSecret(ctx, sk, pk, &sharedSecret)) != NC_SUCCESS) - { - goto Cleanup; - } - - if ((result = _computeConversationKey(ctx, &sharedSecret, &conversationKey)) != NC_SUCCESS) - { - goto Cleanup; - } + goto Cleanup; + } - result = _decryptNip44Ex(ctx, &conversationKey, args); + if ((result = _computeConversationKey(ctx, &sharedSecret, &conversationKey)) != NC_SUCCESS) + { + goto Cleanup; } - break; - case NC_ENC_VERSION_NIP04: - default: - result = E_VERSION_NOT_SUPPORTED; - break; + result = _decryptNip44Ex(ctx, &conversationKey, args); + } + break; + + case NC_ENC_VERSION_NIP04: + default: + result = E_VERSION_NOT_SUPPORTED; + break; } Cleanup: @@ -994,7 +992,7 @@ NC_EXPORT NCResult NCComputeMac( /* * Compute the hmac of the data using the supplied hmac key */ - return _computeHmac(hmacKey, &payloadSpan, hmacOut) == CSTATUS_OK ? NC_SUCCESS : E_OPERATION_FAILED; + return _computeHmac(hmacKey, payloadSpan, hmacOut) == CSTATUS_OK ? NC_SUCCESS : E_OPERATION_FAILED; } @@ -1075,74 +1073,74 @@ NC_EXPORT NCResult NCSetEncryptionPropertyEx( switch (property) { - case NC_ENC_SET_VERSION: - - /* Ensure version is proper length */ - CHECK_ARG_RANGE(valueLen, sizeof(uint32_t), sizeof(uint32_t), 2) + case NC_ENC_SET_VERSION: + + /* Ensure version is proper length */ + CHECK_ARG_RANGE(valueLen, sizeof(uint32_t), sizeof(uint32_t), 2) + + args->version = *((uint32_t*)value); + + return NC_SUCCESS; + + case NC_ENC_SET_NIP04_IV: + /* + * The safest way to store the nip04 IV is in the nonce + * field. An IV is essentially a nonce. A secure random + * number used to encrypt the first block of a CBC chain. + */ + + CHECK_ARG_RANGE(valueLen, AES_IV_SIZE, UINT32_MAX, 3) + + ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP04) + + args->nonceData = value; - args->version = *((uint32_t*)value); - - return NC_SUCCESS; + return NC_SUCCESS; - case NC_ENC_SET_NIP04_IV: - /* - * The safest way to store the nip04 IV is in the nonce - * field. An IV is essentially a nonce. A secure random - * number used to encrypt the first block of a CBC chain. - */ - - CHECK_ARG_RANGE(valueLen, AES_IV_SIZE, UINT32_MAX, 3) - ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP04) + case NC_ENC_SET_NIP04_KEY: + /* + * The AES key is stored in the hmac key field, since + * it won't be used for the operating and should be the same size + * as the hmac key. + */ - args->nonceData = value; + CHECK_ARG_RANGE(valueLen, AES_KEY_SIZE, UINT32_MAX, 3) - return NC_SUCCESS; - + ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP04) - case NC_ENC_SET_NIP04_KEY: - /* - * The AES key is stored in the hmac key field, since - * it won't be used for the operating and should be the same size - * as the hmac key. - */ - - CHECK_ARG_RANGE(valueLen, AES_KEY_SIZE, UINT32_MAX, 3) + args->keyData = value; - ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP04) + return NC_SUCCESS; - args->keyData = value; + case NC_ENC_SET_NIP44_NONCE: - return NC_SUCCESS; + /* Nonce buffer must be at least the size, max doesnt matter */ + CHECK_ARG_RANGE(valueLen, NC_ENCRYPTION_NONCE_SIZE, UINT32_MAX, 3) - case NC_ENC_SET_NIP44_NONCE: - - /* Nonce buffer must be at least the size, max doesnt matter */ - CHECK_ARG_RANGE(valueLen, NC_ENCRYPTION_NONCE_SIZE, UINT32_MAX, 3) + /* Nonce is only used in nip44 mode */ + ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP44) - /* Nonce is only used in nip44 mode */ - ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP44) + args->nonceData = value; - args->nonceData = value; + return NC_SUCCESS; - return NC_SUCCESS; + case NC_ENC_SET_NIP44_MAC_KEY: - case NC_ENC_SET_NIP44_MAC_KEY: - - /* The maximum size of the buffer doesn't matter as long as its larger than the key size */ - CHECK_ARG_RANGE(valueLen, NC_HMAC_KEY_SIZE, UINT32_MAX, 3) + /* The maximum size of the buffer doesn't matter as long as its larger than the key size */ + CHECK_ARG_RANGE(valueLen, NC_HMAC_KEY_SIZE, UINT32_MAX, 3) - /* Mac key is only used in nip44 mode */ - ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP44) + /* Mac key is only used in nip44 mode */ + ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP44) - /* - * During encryption the key data buffer is used - * to write the hmac hey used for MAC computation - * operations. - */ - args->keyData = value; + /* + * During encryption the key data buffer is used + * to write the hmac hey used for MAC computation + * operations. + */ + args->keyData = value; - return NC_SUCCESS; + return NC_SUCCESS; } return E_INVALID_ARG; diff --git a/src/noscryptutil.c b/src/noscryptutil.c index b7723cb..c0eb036 100644 --- a/src/noscryptutil.c +++ b/src/noscryptutil.c @@ -19,10 +19,14 @@ */ -#include +#include +#include + #include "nc-util.h" #include "nc-crypto.h" +#include + /* * Validation macros */ @@ -31,56 +35,78 @@ #error "Utilities library must be disabled when using extreme compat mode" #endif /* NC_EXTREME_COMPAT */ -#include -#include - #define _nc_mem_free(x) if(x != NULL) { free(x); x = NULL; } #define _nc_mem_alloc(elements, size) calloc(elements, size); +#define ZERO_FILL ncCryptoSecureZero #ifndef NC_INPUT_VALIDATION_OFF #define CHECK_INVALID_ARG(x, argPos) if(x == NULL) return NCResultWithArgPosition(E_INVALID_ARG, argPos); #define CHECK_NULL_ARG(x, argPos) if(x == NULL) return NCResultWithArgPosition(E_NULL_PTR, argPos); #define CHECK_ARG_RANGE(x, min, max, argPos) if(x < min || x > max) return NCResultWithArgPosition(E_ARGUMENT_OUT_OF_RANGE, argPos); + #define CHECK_ARG_IS(exp, argPos) if(!(exp)) return NCResultWithArgPosition(E_INVALID_ARG, argPos); #else /* empty macros */ #define CHECK_INVALID_ARG(x) #define CHECK_NULL_ARG(x, argPos) #define CHECK_ARG_RANGE(x, min, max, argPos) + #define CHECK_ARG_IS(is, expected, argPos) #endif /* !NC_DISABLE_INPUT_VALIDATION */ /* performs a log2 on integer types */ #define _math_int_log2(x) (int32_t)log2((double)x) -#define MIN_PADDING_SIZE 0x20 -#define NIP44_VERSION_SIZE 0x01 -#define NIP44_PT_LEN_SIZE 0x02 +#define MIN_PADDING_SIZE 0x20u +#define NIP44_VERSION_SIZE 0x01u +#define NIP44_PT_LEN_SIZE sizeof(uint16_t) -/* Currently were on nip44 version 2 */ -const static uint8_t Nip44VersionValue = 0x02; +#define NC_ENC_FLAG_MODE_MASK 0x01ui32 -typedef struct nc_util_enc_buffer_state -{ - uint8_t* ciphertext; - uint32_t ciphertextSize; -} NCCipherTextOutState; +/* Currently were on nip44 version 2 */ +const static uint8_t Nip44VersionValue[1] = { 0x02u }; struct nc_util_enc_struct { - - /* Dynamically allocated during initialization */ - NCCipherTextOutState* outState; - const uint8_t* plaintext; - - uint32_t plaintextSize; + uint32_t _flags; + + cspan_t cipherInput; + + /* + The data this span points to is allocated during initialization + */ + span_t cipherOutput; NCEncryptionArgs encArgs; }; -static _nc_fn_inline int32_t _calcNip44PtPadding(int32_t plaintextSize) +static _nc_fn_inline span_t _ncUtilAllocSpan(uint32_t count, size_t size) { - int32_t chunk, nextPower, factor; + span_t span; + +#if SIZE_MAX < UINT32_MAX + + if (count > SIZE_MAX) + { + return span; + } + +#endif + + span.data = _nc_mem_alloc((size_t)count, size); + span.size = (uint32_t)count; + + return span; +} + +static _nc_fn_inline void _ncUtilFreeSpan(span_t span) +{ + _nc_mem_free(span.data); +} + +static _nc_fn_inline uint32_t _calcNip44PtPadding(uint32_t plaintextSize) +{ + uint32_t chunk, nextPower, factor; /* * Taken from https://github.com/nostr-protocol/nips/blob/master/44.md @@ -94,19 +120,20 @@ static _nc_fn_inline int32_t _calcNip44PtPadding(int32_t plaintextSize) return MIN_PADDING_SIZE; } + /* Safe to subtract because pt > 0 */ nextPower = _math_int_log2(plaintextSize - 1); - nextPower += 1; + nextPower += 1u; nextPower = 1 << nextPower; - if (nextPower <= 256) + if (nextPower <= 256u) { - chunk = 32; + chunk = 32u; } else { - chunk = nextPower / 8; + chunk = nextPower / 8u; } factor = plaintextSize - 1; @@ -118,9 +145,9 @@ static _nc_fn_inline int32_t _calcNip44PtPadding(int32_t plaintextSize) return chunk * factor; } -static _nc_fn_inline int32_t _calcNip44TotalOutSize(int32_t inputSize) +static _nc_fn_inline uint32_t _calcNip44TotalOutSize(uint32_t inputSize) { - int32_t bufferSize; + uint32_t bufferSize; /* * Buffer size for nip44 is calculated as follows: @@ -144,213 +171,281 @@ static _nc_fn_inline int32_t _calcNip44TotalOutSize(int32_t inputSize) return bufferSize; } +static _nc_fn_inline span_t _nip44GetMacData(span_t payload) +{ + DEBUG_ASSERT(payload.size > NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE); + + /* + * The nip44 mac is computed over the nonce+encrypted ciphertext + * + * the ciphertext is the entire message buffer, so it includes + * version, nonce, data, padding, and mac space available. + * + * This function will return a span that points to the nonce+data + * segment of the buffer for mac computation. + * + * The nonce sits directly after the version byte, ct is after, + * and the remaining 32 bytes are for the mac. So that means + * macData = ct.size - version.size + mac.size + */ + + return ncSpanSlice( + payload, + NIP44_VERSION_SIZE, + payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE) + ); +} + +static _nc_fn_inline span_t _nip44GetMacOutput(span_t payload) +{ + DEBUG_ASSERT(payload.size > NC_ENCRYPTION_MAC_SIZE); + + /* + * Mac is the final 32 bytes of the ciphertext buffer + */ + return ncSpanSlice( + payload, + payload.size - NC_ENCRYPTION_MAC_SIZE, + NC_ENCRYPTION_MAC_SIZE + ); +} + + static NCResult _nip44EncryptCompleteCore( const NCContext* libContext, const NCSecretKey* sk, const NCPublicKey* pk, NCEncryptionArgs encArgs, - span_t cipherText, - span_t plainText + cspan_t plainText, + span_t payload ) { NCResult result; + span_t macData, macOutput; uint32_t outPos, paddedCtSize; - uint16_t ptSize; - + uint8_t ptSize[2]; + uint8_t hmacKeyOut[NC_ENCRYPTION_MAC_SIZE]; + outPos = 0; - + DEBUG_ASSERT(encArgs.version == NC_ENC_VERSION_NIP44); /* Padded size is required to know how large the CT buffer is for encryption */ - paddedCtSize = (int32_t)_calcNip44PtPadding((int32_t)plainText.size); + paddedCtSize = _calcNip44PtPadding(plainText.size); /* Start by appending the version number */ - ncSpanAppend(cipherText, &outPos, &Nip44VersionValue, 0x01); + ncSpanAppend(payload, &outPos, Nip44VersionValue, 0x01); /* next is nonce data */ - ncSpanAppend(cipherText, &outPos, encArgs.nonceData, NC_ENCRYPTION_NONCE_SIZE); + ncSpanAppend(payload, &outPos, encArgs.nonceData, NC_ENCRYPTION_NONCE_SIZE); DEBUG_ASSERT(outPos == 1 + NC_ENCRYPTION_NONCE_SIZE); /* - * So this is the tricky part. The encryption operation appens directly + * Assign the hmac key from the stack buffer. Since the args structure + * is copied, it won't leak the address to the stack buffer. + * + * Should always return success for nip44 because all properties are valid + * addresses. + */ + + result = NCSetEncryptionPropertyEx( + &encArgs, + NC_ENC_SET_NIP44_MAC_KEY, + hmacKeyOut, + sizeof(hmacKeyOut) + ); + + DEBUG_ASSERT(result == NC_SUCCESS); + + /* + * So this is the tricky part. The encryption operation appens directly * on the ciphertext segment - * + * * All current implementations allow overlapping input and output buffers * so we can assign the pt segment on the encryption args */ /* - * Since the message size and padding bytes will get encrypted, + * Since the message size and padding bytes will get encrypted, * the buffer should currently point to the start of the encryption segment - * + * * The size of the data to encrypt is the padded size plus the size of the - * plainText size field. + * plainText size field. */ - encArgs.inputData = (cipherText.data + outPos); - encArgs.outputData = (cipherText.data + outPos); - encArgs.dataSize = paddedCtSize + sizeof(uint16_t); /* Plaintext + pt size must be encrypted */ + result = NCSetEncryptionData( + &encArgs, + (payload.data + outPos), + (payload.data + outPos), + paddedCtSize + NIP44_PT_LEN_SIZE /* Plaintext + pt size must be encrypted */ + ); + + DEBUG_ASSERT(result == NC_SUCCESS); - ptSize = (uint16_t)plainText.size; + /* big endian plaintext size */ + ptSize[0] = (uint8_t)(plainText.size >> 8); + ptSize[1] = (uint8_t)(plainText.size & 0xFF); - /* Can write the plainText size to buffer now */ - ncSpanAppend(cipherText, &outPos, &ptSize, sizeof(uint16_t)); + /* + * Written position must point to the end of the padded ciphertext + * area which the plaintext is written to. + * + * The plaintext data will be encrypted in place. The encrypted + * data is the entired padded region containing the leading byte count + * the plaintext data, followed by zero padding. + */ - /* concat plainText */ - ncSpanAppend(cipherText, &outPos, plainText.data, plainText.size); + ncSpanWrite(payload, outPos, ptSize, NIP44_PT_LEN_SIZE); + + ncSpanWrite( + payload, + outPos + NIP44_PT_LEN_SIZE, /* write pt directly after length */ + plainText.data, + plainText.size + ); + + /* Move position pointer directly after final padding bytes */ + outPos += encArgs.dataSize; - /* Time to perform encryption operation */ result = NCEncrypt(libContext, sk, pk, &encArgs); - if (result == NC_SUCCESS) + if (result != NC_SUCCESS) { - + return result; } -} -static NCResult _nip44EncryptCompleteCore( - NCUtilEncryptionContext* encCtx, - const NCContext* libContext, - const NCSecretKey* sk, - const NCPublicKey* pk -) -{ - span_t cipherText, plainText; + /* + MAC is computed over the nonce+encrypted data + this helper captures that data segment into a span + */ - /* Set up spans */ - ncSpanInit( - &cipherText, - encCtx->outState->ciphertext, - encCtx->outState->ciphertextSize - ); + macData = _nip44GetMacData(payload); + macOutput = _nip44GetMacOutput(payload); - ncSpanInit( - &plainText, - encCtx->plaintext, - encCtx->plaintextSize + result = NCComputeMac( + libContext, + hmacKeyOut, + macData.data, + macData.size, + macOutput.data ); - return _nip44EncryptCompleteCore( - libContext, - sk, - pk, - encCtx->encArgs, - cipherText, - plainText - ); -} + if (result != NC_SUCCESS) + { + return result; + } -NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize) -{ - int32_t paddingSize; + outPos += NC_ENCRYPTION_MAC_SIZE; + + DEBUG_ASSERT2(outPos == payload.size, "Buffer under/overflow detected"); + + /* zero hmac key before returning */ + ZERO_FILL(hmacKeyOut, sizeof(hmacKeyOut)); - CHECK_ARG_RANGE(plaintextSize, 0, INT32_MAX, 1) + /* Notify the caller how many bytes were written */ + return NC_SUCCESS; +} + +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, uint32_t plaintextSize) +{ switch (encVersion) { - default: - return E_VERSION_NOT_SUPPORTED; - - case NC_ENC_VERSION_NIP04: - return plaintextSize; + default: + return E_VERSION_NOT_SUPPORTED; - case NC_ENC_VERSION_NIP44: - paddingSize = _calcNip44PtPadding(plaintextSize); + case NC_ENC_VERSION_NIP04: + return plaintextSize; - DEBUG_ASSERT(paddingSize > 0) + case NC_ENC_VERSION_NIP44: - return (NCResult)(paddingSize); + return (NCResult)(_calcNip44PtPadding(plaintextSize)); } } -NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, int32_t plaintextSize) +NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, uint32_t plaintextSize) { - int32_t totalSize; - - CHECK_ARG_RANGE(plaintextSize, 0, INT32_MAX, 1) switch (encVersion) { - default: - return E_VERSION_NOT_SUPPORTED; + default: + return E_VERSION_NOT_SUPPORTED; /* * NIP-04 simply uses AES to 1:1 encrypt the plainText * to ciphertext. */ - case NC_ENC_VERSION_NIP04: - return plaintextSize; - - case NC_ENC_VERSION_NIP44: - totalSize = _calcNip44TotalOutSize(plaintextSize); - - DEBUG_ASSERT(totalSize > 0) - - return (NCResult)(totalSize); + case NC_ENC_VERSION_NIP04: + return plaintextSize; + case NC_ENC_VERSION_NIP44: + return (NCResult)(_calcNip44TotalOutSize(plaintextSize)); } } -NC_EXPORT NCUtilEncryptionContext* NC_CC NCUtilAllocEncryptionContext(uint32_t encVersion) +NC_EXPORT NCUtilCipherContext* NC_CC NCUtilCipherAlloc(uint32_t encVersion, uint32_t flags) { - NCUtilEncryptionContext* encCtx; + NCUtilCipherContext* encCtx; /* * Alloc context on heap */ - encCtx = (NCUtilEncryptionContext*)_nc_mem_alloc(1, sizeof(NCUtilEncryptionContext)); + encCtx = (NCUtilCipherContext*)_nc_mem_alloc(1, sizeof(NCUtilCipherContext)); if (encCtx != NULL) { encCtx->encArgs.version = encVersion; + encCtx->_flags = flags; } return encCtx; } -NC_EXPORT void NC_CC NCUtilFreeEncryptionContext(NCUtilEncryptionContext* encCtx) +NC_EXPORT void NC_CC NCUtilCipherFree(NCUtilCipherContext* encCtx) { if (!encCtx) { return; } + /* + * If zero on free flag is set, we can zero all output memory + * before returning the buffer back to the heap + */ + if ((encCtx->_flags & NC_UTIL_CIPHER_ZERO_ON_FREE) > 0 && encCtx->cipherOutput.data) + { + ZERO_FILL(encCtx->cipherOutput.data, encCtx->cipherOutput.size); + } + /* Free output buffers */ - _nc_mem_free(encCtx->outState); + _ncUtilFreeSpan(encCtx->cipherOutput); /* context can be released */ _nc_mem_free(encCtx); } -NC_EXPORT NCResult NC_CC NCUtilInitEncryptionContext( - NCUtilEncryptionContext* encCtx, - const uint8_t* plainText, - uint32_t plainTextSize +NC_EXPORT NCResult NC_CC NCUtilCipherInit( + NCUtilCipherContext* encCtx, + const uint8_t* inputData, + uint32_t inputSize ) { - NCResult outputSize; - NCCipherTextOutState* output; - - CHECK_NULL_ARG(encCtx, 0) - CHECK_NULL_ARG(plainText, 1) - CHECK_ARG_RANGE(plainTextSize, 0, INT32_MAX, 2) + CHECK_NULL_ARG(encCtx, 0); + CHECK_NULL_ARG(inputData, 1); /* * The output state must not have alraedy been allocated */ - if (encCtx->outState) - { - return E_INVALID_ARG; - } + CHECK_ARG_IS(encCtx->cipherOutput.data == NULL, 0); /* * Calculate the correct output size to store the encryption * data for the given cipher version */ - outputSize = NCUtilGetEncryptionBufferSize(encCtx->encArgs.version, plainTextSize); + outputSize = NCUtilGetEncryptionBufferSize(encCtx->encArgs.version, inputSize); if (outputSize <= 0) { @@ -358,53 +453,50 @@ NC_EXPORT NCResult NC_CC NCUtilInitEncryptionContext( } /*Alloc output buffer within the struct */ - output = (NCCipherTextOutState*)_nc_mem_alloc(sizeof(NCCipherTextOutState) + (int)outputSize, 1); + encCtx->cipherOutput = _ncUtilAllocSpan((uint32_t)outputSize, sizeof(uint8_t)); - if (!output) + if (!encCtx->cipherOutput.data) { return E_OUT_OF_MEMORY; } - /* set cipertext buffer to end of the structure memory */ - output->ciphertext = (uint8_t*)(output + 1); - output->ciphertextSize = outputSize; - - encCtx->outState = output; - encCtx->plaintext = plainText; - encCtx->plaintextSize = plainTextSize; + ncSpanInitC(&encCtx->cipherInput, inputData, inputSize); return NC_SUCCESS; } -NC_EXPORT NCResult NC_CC NCUtilGetEncryptedSize(const NCUtilEncryptionContext* encCtx) +NC_EXPORT NCResult NC_CC NCUtilCipherGetOutputSize(const NCUtilCipherContext* encCtx) { CHECK_NULL_ARG(encCtx, 0); - return (NCResult)(encCtx->outState->ciphertextSize); + return (NCResult)(encCtx->cipherOutput.size); } -NC_EXPORT NCResult NC_CC NCUtilReadEncryptedData( - const NCUtilEncryptionContext* encCtx, +NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput( + const NCUtilCipherContext* encCtx, uint8_t* output, uint32_t outputSize ) { CHECK_NULL_ARG(encCtx, 0) CHECK_NULL_ARG(output, 1) - CHECK_ARG_RANGE(outputSize, 0, INT32_MAX, 2) - if (outputSize < encCtx->outState->ciphertextSize) + if (outputSize < encCtx->cipherOutput.size) { return E_OPERATION_FAILED; } - MEMMOV(output, encCtx->outState->ciphertext, encCtx->outState->ciphertextSize); + MEMMOV( + output, + encCtx->cipherOutput.data, + encCtx->cipherOutput.size + ); - return (NCResult)encCtx->outState->ciphertextSize; + return (NCResult)encCtx->cipherOutput.size; } -NC_EXPORT NCResult NCUtilSetEncryptionProperty( - NCUtilEncryptionContext* ctx, +NC_EXPORT NCResult NCUtilCipherSetProperty( + NCUtilCipherContext* ctx, uint32_t property, uint8_t* value, uint32_t valueLen @@ -414,5 +506,50 @@ NC_EXPORT NCResult NCUtilSetEncryptionProperty( CHECK_NULL_ARG(ctx, 0) /* All other arguments are verified */ - return NCSetEncryptionPropertyEx(&ctx->encArgs, property, value, valueLen); + return NCSetEncryptionPropertyEx( + &ctx->encArgs, + property, + value, + valueLen + ); +} + +NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( + const NCUtilCipherContext* encCtx, + const NCContext* libContext, + const NCSecretKey* sk, + const NCPublicKey* pk +) +{ + uint32_t mode; + + CHECK_NULL_ARG(encCtx, 0); + CHECK_NULL_ARG(libContext, 1); + CHECK_NULL_ARG(sk, 2); + CHECK_NULL_ARG(pk, 3); + + mode = encCtx->_flags & NC_ENC_FLAG_MODE_MASK; + + switch (encCtx->encArgs.version) + { + case NC_ENC_VERSION_NIP44: + if (mode == NC_UTIL_CIPHER_MODE_ENCRYPT) + { + return _nip44EncryptCompleteCore( + libContext, + sk, + pk, + encCtx->encArgs, + encCtx->cipherInput, + encCtx->cipherOutput + ); + } + else + { + return E_VERSION_NOT_SUPPORTED; + } + + default: + return E_VERSION_NOT_SUPPORTED; + } } diff --git a/tests/hex.h b/tests/hex.h index 5e90ce9..e6a2a07 100644 --- a/tests/hex.h +++ b/tests/hex.h @@ -28,18 +28,12 @@ #include -typedef struct hexBytes -{ - uint8_t* data; - size_t length; -} HexBytes; - -/* Deferred list of HexBytes to be freed on exit */ -static HexBytes* _hdeferList[10]; +/* Deferred list of span_t to be freed on exit */ +static span_t _hdeferList[20]; static size_t _hdeferListIndex = 0; /* - Allocates a HexBytes and decodes the hexadecimal string into it's binary + Allocates a span_t and decodes the hexadecimal string into it's binary representation. The string must be a valid hexadecimal string and the length and may not be NULL. The length may be known at compile time and can be used to assert the length of the string literal. @@ -48,34 +42,33 @@ static size_t _hdeferListIndex = 0; */ #define FromHexString(str, len) _fromHexString(str, sizeof(str) - 1); STATIC_ASSERT(sizeof(str)/2 == len && len > 0, "Invalid length hex string literal"); -static HexBytes* __allocHexBytes(size_t length) +static span_t __allocHexBytes(size_t length) { - HexBytes* hexBytes; + span_t hexBytes; length /= 2; - hexBytes = (HexBytes*)malloc(length + sizeof(HexBytes)); - if(!hexBytes) + hexBytes.data = malloc(length); + + if(!hexBytes.data) { - return NULL; + return hexBytes; } - hexBytes->length = length; - /* data starts after the structure size */ - hexBytes-> data = ((uint8_t*)hexBytes) + sizeof(HexBytes); + hexBytes.size = length; /* add new value to deferred cleanup list */ _hdeferList[_hdeferListIndex++] = hexBytes; return hexBytes; } -static HexBytes* _fromHexString(const char* hexLiteral, size_t strLen) +static span_t _fromHexString(const char* hexLiteral, uint32_t strLen) { - HexBytes* hexBytes; + span_t hexBytes; size_t i; if(!hexLiteral) { - return NULL; + return hexBytes; } /* alloc the raw bytes */ @@ -90,14 +83,14 @@ static HexBytes* _fromHexString(const char* hexLiteral, size_t strLen) byteString[0] = hexLiteral[i]; byteString[1] = hexLiteral[i + 1]; - hexBytes->data[i / 2] = (uint8_t)strtol(byteString, NULL, 16); + hexBytes.data[i / 2] = (uint8_t)strtol(byteString, NULL, 16); } return hexBytes; } /* - Frees all the HexBytes that were allocated by the + Frees all the span_t that were allocated by the FromHexString function. To be called at the end of the program. */ @@ -105,8 +98,8 @@ static void FreeHexBytes(void) { while(_hdeferListIndex > 0) { - free(_hdeferList[--_hdeferListIndex]); - _hdeferList[_hdeferListIndex] = NULL; + free(_hdeferList[--_hdeferListIndex].data); + memset(&_hdeferList[_hdeferListIndex], 0, sizeof(span_t)); } } @@ -127,18 +120,18 @@ static void PrintHexRaw(void* bytes, size_t len) } /* -* Prints the value of the HexBytes as a hexadecimal string -* @param hexBytes A pointer to the HexBytes structure to print the value of +* Prints the value of the span_t as a hexadecimal string +* @param hexBytes A pointer to the span_t structure to print the value of */ -static void PrintHexBytes(HexBytes* hexBytes) +static void PrintHexBytes(span_t hexBytes) { - if (!hexBytes) + if (!hexBytes.data) { puts("NULL"); } else { - PrintHexRaw(hexBytes->data, hexBytes->length); + PrintHexRaw(hexBytes.data, hexBytes.size); } } diff --git a/tests/test.c b/tests/test.c index 3833e7b..c917f52 100644 --- a/tests/test.c +++ b/tests/test.c @@ -102,7 +102,7 @@ static int TestKnownKeys(const NCContext* context); static int TestCorrectEncryption(const NCContext* context); #ifdef NC_ENABLE_UTILS -static int TestUtilFunctions(void); +static int TestUtilFunctions(const NCContext * libCtx); #endif #ifndef NC_INPUT_VALIDATION_OFF @@ -172,7 +172,7 @@ static int RunTests(void) } #ifdef NC_ENABLE_UTILS - if (TestUtilFunctions() != 0) + if (TestUtilFunctions(ctx) != 0) { return 1; } @@ -213,7 +213,7 @@ static int TestEcdsa(const NCContext* context, NCSecretKey* secKey, NCPublicKey* uint8_t sigEntropy[32]; uint8_t invalidSig[64]; - HexBytes* digestHex; + span_t digestHex; PRINTL("TEST: Ecdsa") @@ -227,8 +227,8 @@ static int TestEcdsa(const NCContext* context, NCSecretKey* secKey, NCPublicKey* /* Test signing just the message digest */ { uint8_t sig[64]; - TEST(NCSignDigest(context, secKey, sigEntropy, digestHex->data, sig), NC_SUCCESS); - TEST(NCVerifyDigest(context, pubKey, digestHex->data, sig), NC_SUCCESS); + TEST(NCSignDigest(context, secKey, sigEntropy, digestHex.data, sig), NC_SUCCESS); + TEST(NCVerifyDigest(context, pubKey, digestHex.data, sig), NC_SUCCESS); } /* Sign and verify the raw message */ @@ -245,7 +245,7 @@ static int TestEcdsa(const NCContext* context, NCSecretKey* secKey, NCPublicKey* /* Ensure operations succeed but dont print them as test cases */ ENSURE(NCSignData(context, secKey, sigEntropy, (uint8_t*)message, strlen32(message), sig1) == NC_SUCCESS); - ENSURE(NCSignDigest(context, secKey, sigEntropy, digestHex->data, sig2) == NC_SUCCESS); + ENSURE(NCSignDigest(context, secKey, sigEntropy, digestHex.data, sig2) == NC_SUCCESS); /* Perform test */ TEST(memcmp(sig1, sig2, 64), 0); @@ -256,18 +256,18 @@ static int TestEcdsa(const NCContext* context, NCSecretKey* secKey, NCPublicKey* uint8_t sig[64]; ENSURE(NCSignData(context, secKey, sigEntropy, (uint8_t*)message, strlen32(message), sig) == NC_SUCCESS); - TEST(NCVerifyDigest(context, pubKey, digestHex->data, sig), NC_SUCCESS); + TEST(NCVerifyDigest(context, pubKey, digestHex.data, sig), NC_SUCCESS); /* Now invert test, zero signature to ensure its overwritten */ ZERO_FILL(sig, sizeof(sig)); - ENSURE(NCSignDigest(context, secKey, sigEntropy, digestHex->data, sig) == NC_SUCCESS); + ENSURE(NCSignDigest(context, secKey, sigEntropy, digestHex.data, sig) == NC_SUCCESS); TEST(NCVerifyData(context, pubKey, (uint8_t*)message, strlen32(message), sig), NC_SUCCESS); } /* test verification of invalid signature */ { - TEST(NCVerifyDigest(context, pubKey, digestHex->data, invalidSig), E_INVALID_ARG); + TEST(NCVerifyDigest(context, pubKey, digestHex.data, invalidSig), E_INVALID_ARG); } PRINTL("\nPASSED: Ecdsa tests completed") @@ -504,7 +504,7 @@ static int TestPublicApiArgumentValidation() static int TestKnownKeys(const NCContext* context) { NCPublicKey pubKey; - HexBytes* secKey1, * pubKey1, * secKey2, * pubKey2; + span_t secKey1, pubKey1, secKey2, pubKey2; PRINTL("TEST: Known keys") @@ -515,18 +515,18 @@ static int TestKnownKeys(const NCContext* context) pubKey2 = FromHexString("421181660af5d39eb95e48a0a66c41ae393ba94ffeca94703ef81afbed724e5a", sizeof(NCPublicKey)); /*Test known keys*/ - TEST(NCValidateSecretKey(context, NCByteCastToSecretKey(secKey1->data)), NC_SUCCESS); + TEST(NCValidateSecretKey(context, NCByteCastToSecretKey(secKey1.data)), NC_SUCCESS); /* Recover a public key from secret key 1 */ - TEST(NCGetPublicKey(context, NCByteCastToSecretKey(secKey1->data), &pubKey), NC_SUCCESS); + TEST(NCGetPublicKey(context, NCByteCastToSecretKey(secKey1.data), &pubKey), NC_SUCCESS); /* Ensure the public key matches the known public key value */ - TEST(memcmp(pubKey1->data, &pubKey, sizeof(pubKey)), 0); + TEST(memcmp(pubKey1.data, &pubKey, sizeof(pubKey)), 0); /* Repeat with second key */ - TEST(NCValidateSecretKey(context, (NCSecretKey*)secKey2->data), NC_SUCCESS); - TEST(NCGetPublicKey(context, (NCSecretKey*)secKey2->data, &pubKey), NC_SUCCESS); - TEST(memcmp(pubKey2->data, &pubKey, sizeof(pubKey)), 0); + TEST(NCValidateSecretKey(context, NCByteCastToSecretKey(secKey2.data)), NC_SUCCESS); + TEST(NCGetPublicKey(context, NCByteCastToSecretKey(secKey2.data), &pubKey), NC_SUCCESS); + TEST(memcmp(pubKey2.data, &pubKey, sizeof(pubKey)), 0); PRINTL("\nPASSED: Known keys tests completed") return 0; @@ -614,10 +614,62 @@ static int TestCorrectEncryption(const NCContext* context) extern NCResult NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize); /* Padding tests taken from the nip44 repo vectors.json file */ -const int32_t _padTestActual[24] = { 16, 32, 33, 37, 45, 49, 64, 65, 100, 111, 200, 250, 320, 383, 384, 400, 500, 512, 515, 700, 800, 900, 1020, 65536 }; -const int32_t _padTestExpected[24] = { 32, 32, 64, 64, 64, 64, 64, 96, 128, 128, 224, 256, 320, 384, 384, 448, 512, 512, 640, 768, 896, 1024, 1024, 65536 }; +const uint32_t _padTestActual[24] = { 16, 32, 33, 37, 45, 49, 64, 65, 100, 111, 200, 250, 320, 383, 384, 400, 500, 512, 515, 700, 800, 900, 1020, 65536 }; +const uint32_t _padTestExpected[24] = { 32, 32, 64, 64, 64, 64, 64, 96, 128, 128, 224, 256, 320, 384, 384, 448, 512, 512, 640, 768, 896, 1024, 1024, 65536 }; + +static int TestUtilNip44Encryption( + const NCContext* libCtx, + span_t sendKey, + span_t recvKey, + span_t nonce, + span_t expected, + const char* plainText +) +{ + NCPublicKey recvPubKey; + span_t outData; + + ENSURE(NCValidateSecretKey(libCtx, NCByteCastToSecretKey(sendKey.data)) == NC_SUCCESS); + ENSURE(NCGetPublicKey(libCtx, NCByteCastToSecretKey(recvKey.data), &recvPubKey) == NC_SUCCESS); + + /* Alloc cipher in nip44 encryption mode */ + NCUtilCipherContext* ctx = NCUtilCipherAlloc( + NC_ENC_VERSION_NIP44, + NC_UTIL_CIPHER_MODE_ENCRYPT | NC_UTIL_CIPHER_ZERO_ON_FREE + ); + + ENSURE(ctx != NULL); + + TEST(NCUtilCipherInit(ctx, (const uint8_t*)plainText, strlen(plainText)), NC_SUCCESS); + + /* Nonce is required for nip44 encryption */ + TEST(NCUtilCipherSetProperty(ctx, NC_ENC_SET_NIP44_NONCE, nonce.data, nonce.size), NC_SUCCESS); + + /* Ciper update should return the */ + TEST(NCUtilCipherUpdate(ctx, libCtx, NCByteCastToSecretKey(sendKey.data), &recvPubKey), NC_SUCCESS); + + NCResult cipherOutputSize = NCUtilCipherGetOutputSize(ctx); + + TEST(cipherOutputSize, expected.size); -static int TestUtilFunctions(void) + outData.data = (uint8_t*)malloc(cipherOutputSize); + outData.size = (uint32_t)cipherOutputSize; + + TASSERT(outData.data != NULL); + + /* Read the encrypted payload to test */ + TEST(NCUtilCipherReadOutput(ctx, outData.data, cipherOutputSize), cipherOutputSize); + + /* Ensure encrypted payload matches */ + TEST(memcmp(outData.data, expected.data, cipherOutputSize), 0); + + free(outData.data); + + /* Free encryption memory */ + NCUtilCipherFree(ctx); +} + +static int TestUtilFunctions(const NCContext* libCtx) { PRINTL("TEST: Util functions") @@ -628,8 +680,23 @@ static int TestUtilFunctions(void) TEST(NCUtilGetEncryptionPaddedSize(NC_ENC_VERSION_NIP44, _padTestActual[i]), _padTestExpected[i]); TEST(NCUtilGetEncryptionBufferSize(NC_ENC_VERSION_NIP44, _padTestActual[i]), totalSize); } + { + PRINTL("TEST: NIP-44 util encryption") + + /* From the nip44 vectors file */ + span_t sendKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", sizeof(NCSecretKey)); + span_t recvKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000002", sizeof(NCSecretKey)); + span_t nonce = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", NC_ENCRYPTION_NONCE_SIZE); + span_t payload = FromHexString("02000000000000000000000000000000000000000000000000000000000000000179ed06e5548ad3ff58ca920e6c0b4329f6040230f7e6e5641f20741780f0adc35a09794259929a02bb06ad8e8cf709ee4ccc567e9d514cdf5781af27a3e905e55b1b", 99); + const char* plainText = "a"; + + if (TestUtilNip44Encryption(libCtx, sendKey, recvKey, nonce, payload, plainText) != 0) + { + return 1; + } + } - PRINTL("PASSED: Util functions tests completed") + PRINTL("\nPASSED: Util functions tests completed") return 0; } -- cgit From ffe42b6858f112a00405be4f0605ab1163063749 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 13 Jul 2024 22:13:13 -0400 Subject: test: Add decryption test cases and fixes --- include/noscryptutil.h | 8 ++--- src/noscryptutil.c | 50 +++++++++++++++++++---------- tests/test.c | 86 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 108 insertions(+), 36 deletions(-) (limited to 'tests') diff --git a/include/noscryptutil.h b/include/noscryptutil.h index 20384c4..13b9231 100644 --- a/include/noscryptutil.h +++ b/include/noscryptutil.h @@ -40,10 +40,10 @@ extern "C" { #define E_CIPHER_BAD_NONCE -12 #define E_CIPHER_MAC_INVALID -13 -#define NC_UTIL_CIPHER_MODE_ENCRYPT 0x00ui32 -#define NC_UTIL_CIPHER_MODE_DECRYPT 0x01ui32 -#define NC_UTIL_CIPHER_ZERO_ON_FREE 0x02ui32 -#define NC_UTIL_CIPHER_MAC_NO_VERIFY 0x04ui32 +#define NC_UTIL_CIPHER_MODE_ENCRYPT 0x00u +#define NC_UTIL_CIPHER_MODE_DECRYPT 0x01u +#define NC_UTIL_CIPHER_ZERO_ON_FREE 0x02u +#define NC_UTIL_CIPHER_MAC_NO_VERIFY 0x04u /* * The encryption context structure. This structure is used to store the state diff --git a/src/noscryptutil.c b/src/noscryptutil.c index 09b7c92..97526d9 100644 --- a/src/noscryptutil.c +++ b/src/noscryptutil.c @@ -20,7 +20,6 @@ #include -#include #include "nc-util.h" #include "nc-crypto.h" @@ -53,8 +52,26 @@ #endif /* !NC_DISABLE_INPUT_VALIDATION */ -/* performs a log2 on integer types */ -#define _math_int_log2(x) (int32_t)log2((double)x) +#ifdef _NC_IS_WINDOWS + + #include + + /* performs a log2 on integer types */ + #define _math_int_log2(x) (uint32_t)log2((double)x) + +#else + /* + * GCC/clang does not expose log2 so we can use the __builtin_clz + * to find leading zeros of an integer and subtract that from 31 + * (bit positions) for int32 + */ + static _nc_fn_inline uint32_t _math_int_log2(uint32_t val) + { + DEBUG_ASSERT(val < UINT32_MAX); + + return 31 - __builtin_clz(val); + } +#endif #define MIN_PADDING_SIZE 0x20u #define NIP44_VERSION_SIZE 0x01u @@ -66,10 +83,15 @@ */ #define NIP44_MIN_PAYLOAD_SIZE (NIP44_VERSION_SIZE + 0x20 + 0x02 + 0x20 + 0x02) +/* +* The minimum ciphertext size is the minimum padded size + the minimum +* size of the plaintext length field +*/ +#define NIP44_MIN_CIPHERTEXT_SIZE (MIN_PADDING_SIZE + NIP44_PT_LEN_SIZE) /* Currently were on nip44 version 2 */ -const static uint8_t Nip44VersionValue[1] = { 0x02u }; +static const uint8_t Nip44VersionValue[1] = { 0x02u }; struct nc_util_enc_struct { @@ -217,7 +239,7 @@ static _nc_fn_inline span_t _nip44GetMacOutput(span_t payload) static _nc_fn_inline cspan_t _nip44ParseMac(cspan_t payload) { - DEBUG_ASSERT(payload.size > NC_ENCRYPTION_MAC_SIZE); + DEBUG_ASSERT(payload.size >= NIP44_MIN_PAYLOAD_SIZE); /* * Mac is the final 32 bytes of the ciphertext buffer @@ -231,30 +253,26 @@ static _nc_fn_inline cspan_t _nip44ParseMac(cspan_t payload) static _nc_fn_inline cspan_t _nip44ParseCipherText(cspan_t payload) { - DEBUG_ASSERT(payload.size < NIP44_MIN_PAYLOAD_SIZE); + DEBUG_ASSERT(payload.size >= NIP44_MIN_PAYLOAD_SIZE); - /* slice after the nonce and before the mac segments */ + /* ct is all of the data after the nonce and before the mac segment */ return ncSpanSliceC( payload, NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE, - payload.size - NC_ENCRYPTION_MAC_SIZE + payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE) ); } static _nc_fn_inline cspan_t _nip44ParseNonce(cspan_t payload) { - cspan_t nonceData; - - DEBUG_ASSERT(payload.size > NIP44_MIN_PAYLOAD_SIZE); + DEBUG_ASSERT(payload.size >= NIP44_MIN_PAYLOAD_SIZE); /* slice after the version and before the mac segments */ - nonceData = ncSpanSliceC( + return ncSpanSliceC( payload, NIP44_VERSION_SIZE, NC_ENCRYPTION_NONCE_SIZE ); - - return nonceData; } static NCResult _nip44EncryptCompleteCore( @@ -325,7 +343,7 @@ static NCResult _nip44EncryptCompleteCore( result = NCSetEncryptionData( &encArgs, - (payload.data + outPos), + (payload.data + outPos), /* in place encryption */ (payload.data + outPos), paddedCtSize + NIP44_PT_LEN_SIZE /* Plaintext + pt size must be encrypted */ ); @@ -458,7 +476,7 @@ static NCResult _nip44DecryptCompleteCore( cipherText = _nip44ParseCipherText(cipher->cipherInput); - DEBUG_ASSERT2(cipherText.size > 0x20, "Cipertext segment was parsed incorrectly. Too small"); + DEBUG_ASSERT2(cipherText.size >= MIN_PADDING_SIZE, "Cipertext segment was parsed incorrectly. Too small"); /* manually sign nonce */ encArgs.nonceData = nonce.data; diff --git a/tests/test.c b/tests/test.c index c917f52..80b8704 100644 --- a/tests/test.c +++ b/tests/test.c @@ -607,15 +607,9 @@ static int TestCorrectEncryption(const NCContext* context) #include -/* -* This function is not currently public, but we can access it for testing -* purposes because it is used to calculate the output buffer size for encryption -*/ -extern NCResult NCUtilGetEncryptionPaddedSize(uint32_t encVersion, int32_t plaintextSize); - /* Padding tests taken from the nip44 repo vectors.json file */ -const uint32_t _padTestActual[24] = { 16, 32, 33, 37, 45, 49, 64, 65, 100, 111, 200, 250, 320, 383, 384, 400, 500, 512, 515, 700, 800, 900, 1020, 65536 }; -const uint32_t _padTestExpected[24] = { 32, 32, 64, 64, 64, 64, 64, 96, 128, 128, 224, 256, 320, 384, 384, 448, 512, 512, 640, 768, 896, 1024, 1024, 65536 }; +static const uint32_t _padTestActual[24] = { 16, 32, 33, 37, 45, 49, 64, 65, 100, 111, 200, 250, 320, 383, 384, 400, 500, 512, 515, 700, 800, 900, 1020, 65536 }; +static const uint32_t _padTestExpected[24] = { 32, 32, 64, 64, 64, 64, 64, 96, 128, 128, 224, 256, 320, 384, 384, 448, 512, 512, 640, 768, 896, 1024, 1024, 65536 }; static int TestUtilNip44Encryption( const NCContext* libCtx, @@ -627,7 +621,7 @@ static int TestUtilNip44Encryption( ) { NCPublicKey recvPubKey; - span_t outData; + uint8_t* outData; ENSURE(NCValidateSecretKey(libCtx, NCByteCastToSecretKey(sendKey.data)) == NC_SUCCESS); ENSURE(NCGetPublicKey(libCtx, NCByteCastToSecretKey(recvKey.data), &recvPubKey) == NC_SUCCESS); @@ -645,25 +639,70 @@ static int TestUtilNip44Encryption( /* Nonce is required for nip44 encryption */ TEST(NCUtilCipherSetProperty(ctx, NC_ENC_SET_NIP44_NONCE, nonce.data, nonce.size), NC_SUCCESS); - /* Ciper update should return the */ + /* Cipher update should return the */ TEST(NCUtilCipherUpdate(ctx, libCtx, NCByteCastToSecretKey(sendKey.data), &recvPubKey), NC_SUCCESS); NCResult cipherOutputSize = NCUtilCipherGetOutputSize(ctx); TEST(cipherOutputSize, expected.size); - outData.data = (uint8_t*)malloc(cipherOutputSize); - outData.size = (uint32_t)cipherOutputSize; + outData = (uint8_t*)malloc(cipherOutputSize); + TASSERT(outData != NULL); + + /* Read the encrypted payload to test */ + TEST(NCUtilCipherReadOutput(ctx, outData, cipherOutputSize), cipherOutputSize); + + /* Ensure encrypted payload matches */ + TEST(memcmp(outData, expected.data, cipherOutputSize), 0); + + free(outData); + + /* Free encryption memory */ + NCUtilCipherFree(ctx); +} + +static int TestUtilNip44Decryption( + const NCContext* libCtx, + span_t sendKey, + span_t recvKey, + span_t payload, + const char* expectedPt +) +{ + NCPublicKey recvPubKey; + uint8_t* outData; - TASSERT(outData.data != NULL); + ENSURE(NCValidateSecretKey(libCtx, NCByteCastToSecretKey(sendKey.data)) == NC_SUCCESS); + ENSURE(NCGetPublicKey(libCtx, NCByteCastToSecretKey(recvKey.data), &recvPubKey) == NC_SUCCESS); + + /* Alloc cipher in nip44 decryption mode */ + NCUtilCipherContext* ctx = NCUtilCipherAlloc( + NC_ENC_VERSION_NIP44, + NC_UTIL_CIPHER_MODE_DECRYPT | NC_UTIL_CIPHER_ZERO_ON_FREE + ); + + ENSURE(ctx != NULL); + + /* submit encrypted payload for ciphertext */ + TEST(NCUtilCipherInit(ctx, payload.data, payload.size), NC_SUCCESS); + + TEST(NCUtilCipherUpdate(ctx, libCtx, NCByteCastToSecretKey(sendKey.data), &recvPubKey), NC_SUCCESS); + + NCResult plaintextSize = NCUtilCipherGetOutputSize(ctx); + + TEST(plaintextSize, strlen(expectedPt)); + + outData = (uint8_t*)malloc(plaintextSize); + + TASSERT(outData != NULL); /* Read the encrypted payload to test */ - TEST(NCUtilCipherReadOutput(ctx, outData.data, cipherOutputSize), cipherOutputSize); + TEST(NCUtilCipherReadOutput(ctx, outData, plaintextSize), plaintextSize); /* Ensure encrypted payload matches */ - TEST(memcmp(outData.data, expected.data, cipherOutputSize), 0); + TEST(memcmp(outData, expectedPt, plaintextSize), 0); - free(outData.data); + free(outData); /* Free encryption memory */ NCUtilCipherFree(ctx); @@ -695,6 +734,21 @@ static int TestUtilFunctions(const NCContext* libCtx) return 1; } } + { + PRINTL("TEST: NIP-44 util decryption"); + + /* From the nip44 vectors file */ + span_t sendKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", sizeof(NCSecretKey)); + span_t recvKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000002", sizeof(NCSecretKey)); + span_t nonce = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", NC_ENCRYPTION_NONCE_SIZE); + span_t payload = FromHexString("02000000000000000000000000000000000000000000000000000000000000000179ed06e5548ad3ff58ca920e6c0b4329f6040230f7e6e5641f20741780f0adc35a09794259929a02bb06ad8e8cf709ee4ccc567e9d514cdf5781af27a3e905e55b1b", 99); + const char* plainText = "a"; + + if (TestUtilNip44Decryption(libCtx, sendKey, recvKey, payload, plainText) != 0) + { + return 1; + } + } PRINTL("\nPASSED: Util functions tests completed") return 0; -- cgit From 12feb33dba2061415d6f39fa59dec16fafcda2a0 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 21 Jul 2024 17:51:04 -0400 Subject: Push latest changes, patches, and internal upgrades --- CMakeLists.txt | 31 ++-- Taskfile.yaml | 53 ++++-- include/noscrypt.h | 2 +- include/noscryptutil.h | 4 +- src/hkdf.c | 30 ++-- src/hkdf.h | 6 +- src/nc-crypto.c | 34 ++-- src/nc-crypto.h | 8 +- src/nc-util.h | 78 ++++++++- src/noscrypt.c | 12 +- src/noscryptutil.c | 451 ++++++++++++++++++++++++++---------------------- src/providers/bcrypt.c | 20 +-- src/providers/mbedtls.c | 42 ++--- tests/hex.h | 7 +- tests/test.c | 19 +- 15 files changed, 469 insertions(+), 328 deletions(-) (limited to 'tests') diff --git a/CMakeLists.txt b/CMakeLists.txt index 493b18a..6dad383 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,21 +143,6 @@ target_include_directories(${_NC_PROJ_NAME}_static SYSTEM PUBLIC vendor/secp256k # ############################################# -#try to load openssl quietly in order to check for its availability -find_package(OpenSSL QUIET) - -#setup default linking to crypto libraries for certain plaftorms. -#Windows defaults to bcrypt, openssl otherwise if installed -if(CRYPTO_LIB STREQUAL "") - if(MSVC) - set(CRYPTO_LIB "bcrypt") - elseif(OPENSSL_FOUND) - set(CRYPTO_LIB "openssl") - endif() - - message(STATUS "No crypto library was specified, defaulting to ${CRYPTO_LIB}") -endif() - #Include mbedtls if enabled if(NC_FETCH_MBEDTLS) @@ -186,6 +171,22 @@ if(NC_FETCH_MBEDTLS) endif() +#try to load openssl quietly in order to check for its availability +find_package(OpenSSL QUIET) + +#setup default linking to crypto libraries for certain plaftorms. +#Windows defaults to bcrypt, openssl otherwise if installed +if(CRYPTO_LIB STREQUAL "") + if(MSVC) + set(CRYPTO_LIB "bcrypt") + elseif(OPENSSL_FOUND) + set(CRYPTO_LIB "openssl") + endif() + + message(STATUS "No crypto library was specified, defaulting to ${CRYPTO_LIB}") +endif() + + #if mbedtls linking is enabled target the library if(CRYPTO_LIB STREQUAL "mbedtls") diff --git a/Taskfile.yaml b/Taskfile.yaml index a79921c..93ea182 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -14,6 +14,7 @@ version: '3' vars: CMAKE_BUILD_DIR: 'build/{{ OS }}' + TEST_EXE_NAME: 'nctest' tasks: @@ -29,14 +30,6 @@ tasks: - task: build-internal vars: { CMAKE_TEST_STATUS: 'ON', BUILD_CONFIG: 'Debug' } - build-internal: - internal: true - cmds: - - cmake -S . -B{{.CMAKE_BUILD_DIR}} -DCMAKE_BUILD_TYPE={{.BUILD_CONFIG}} -DNC_BUILD_TESTS={{ .CMAKE_TEST_STATUS }} {{.CLI_ARGS}} - - cmake --build {{.CMAKE_BUILD_DIR}} --config {{.BUILD_CONFIG}} - - cmd: echo "Build complete. Your files can be found in the {{.CMAKE_BUILD_DIR}} directory" - silent: true - #available to users and vnbuild runner test: desc: "Builds a local copy of the library in a debug configuration, then runs the test executable" @@ -44,6 +37,37 @@ tasks: - task: build-debug - cmd: cd {{.CMAKE_BUILD_DIR}} && ctest -C Debug --verbose + test-mbedtls: + desc: "Builds and runs tests for noscrypt using the mbedtls crypto library for the current platform" + cmds: + - task: build-internal + vars: + CMAKE_TEST_STATUS: 'ON' + BUILD_CONFIG: 'Debug' + CLI_ARGS: '-DNC_FETCH_MBEDTLS=ON {{.CLI_ARGS}}' #fetches and enabled medtls + - cmd: cd {{.CMAKE_BUILD_DIR}} && ctest -C Debug --verbose + + test-dev: + desc: "Re-runs compilation phase and test execution" + cmds: + - task: compile + vars: { BUILD_CONFIG: 'Debug' } + - cmd: cd {{.CMAKE_BUILD_DIR}} && ctest -C Debug --verbose --output-on-failure + + compile: + internal: true + cmds: + - cmake --build {{.CMAKE_BUILD_DIR}} --config {{.BUILD_CONFIG}} + + build-internal: + internal: true + cmds: + - cmake -S . -B{{.CMAKE_BUILD_DIR}} -DCMAKE_BUILD_TYPE={{.BUILD_CONFIG}} -DNC_BUILD_TESTS={{ .CMAKE_TEST_STATUS }} {{.CLI_ARGS}} + - task: compile + vars: { BUILD_CONFIG: '{{.BUILD_CONFIG}}' } + - cmd: echo "Build complete. Your files can be found in the {{.CMAKE_BUILD_DIR}} directory" + silent: true + install: desc: "Uses cmake to install the library on your system" cmds: @@ -115,17 +139,26 @@ tasks: desc: "Packs up the project source code and creates a tarball in the builds binary directory" vars: TARGET_SOURCE: '{{.PROJECT_DIR}}/{{.BINARY_DIR}}/{{.PROJECT_NAME}}-src.tgz' - SOURCE_FILES: 'CMakeLists.txt src include license tests vendor readme.md Taskfile.yaml' + SOURCE_FILES: [ CMakeLists.txt, Taskfile.yaml, src, include, license, tests, vendor, readme.md, CMakePresets.json ] cmds: #tar up the source - - tar -czf "{{.TARGET_SOURCE}}" {{.SOURCE_FILES}} + - tar -czf "{{.TARGET_SOURCE}}" {{ .SOURCE_FILES | join " " }} ################################# # # DEV TASKS # ################################# + + dev-gdb-test: + platforms: [ linux ] + desc: "Starts a new gdb session on the test executable" + interactive: true + cmds: + - task: compile + vars: { BUILD_CONFIG: 'Debug' } + - cmd: gdb '{{.CMAKE_BUILD_DIR}}/{{.TEST_EXE_NAME}}' dev-update-deps: desc: "Updates vendored projects files (headers mostly) from their source repositories to the latest version" diff --git a/include/noscrypt.h b/include/noscrypt.h index 8b39f17..b91bc2b 100644 --- a/include/noscrypt.h +++ b/include/noscrypt.h @@ -58,7 +58,7 @@ extern "C" { #ifdef _NC_IS_WINDOWS #define NC_EXPORT __declspec(dllimport) #else - #define NC_EXPORT + #define NC_EXPORT extern #endif /* _NC_IS_WINDOWS */ #endif /* !NOSCRYPT_EXPORTING */ #endif /* !NC_EXPORT */ diff --git a/include/noscryptutil.h b/include/noscryptutil.h index 13b9231..a5e460f 100644 --- a/include/noscryptutil.h +++ b/include/noscryptutil.h @@ -39,11 +39,13 @@ extern "C" { #define E_CIPHER_INVALID_FORMAT -11 #define E_CIPHER_BAD_NONCE -12 #define E_CIPHER_MAC_INVALID -13 +#define E_CIPHER_NO_OUTPUT -14 #define NC_UTIL_CIPHER_MODE_ENCRYPT 0x00u #define NC_UTIL_CIPHER_MODE_DECRYPT 0x01u #define NC_UTIL_CIPHER_ZERO_ON_FREE 0x02u #define NC_UTIL_CIPHER_MAC_NO_VERIFY 0x04u +#define NC_UTIL_CIPHER_REUSEABLE 0x08u /* * The encryption context structure. This structure is used to store the state @@ -166,7 +168,7 @@ NC_EXPORT NCResult NC_CC NCUtilCipherGetFlags(const NCUtilCipherContext* ctx); * so the exact same operation should happen if called again. */ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( - const NCUtilCipherContext* encCtx, + NCUtilCipherContext* encCtx, const NCContext* libContext, const NCSecretKey* sk, const NCPublicKey* pk diff --git a/src/hkdf.c b/src/hkdf.c index cff7d60..ab661de 100644 --- a/src/hkdf.c +++ b/src/hkdf.c @@ -42,16 +42,15 @@ static _nc_fn_inline void debugValidateHandler(const struct nc_hkdf_fn_cb_struct cstatus_t hkdfExpandProcess( const struct nc_hkdf_fn_cb_struct* handler, void* ctx, - const cspan_t* info, - span_t* okm + cspan_t info, + span_t okm ) { cstatus_t result; - - uint8_t counter; + cspan_t tSpan, counterSpan; uint32_t tLen, okmOffset; + uint8_t counter[1]; uint8_t t[HKDF_IN_BUF_SIZE]; - cspan_t tSpan, counterSpan; debugValidateHandler(handler); @@ -59,18 +58,18 @@ cstatus_t hkdfExpandProcess( tLen = 0; /* T(0) is an empty string(zero length) */ okmOffset = 0; - counter = 1; /* counter is offset by 1 for init */ + counter[0] = 1; /* counter is offset by 1 for init */ result = CSTATUS_FAIL; /* Start in fail state */ /* counter as a span */ - ncSpanInitC(&counterSpan, &counter, sizeof(counter)); + ncSpanInitC(&counterSpan, counter, sizeof(counter)); /* Compute T(N) = HMAC(prk, T(n-1) | info | n) */ - while (okmOffset < okm->size) + while (okmOffset < okm.size) { ncSpanInitC(&tSpan, t, tLen); - if (handler->update(ctx, &tSpan) != CSTATUS_OK) + if (handler->update(ctx, tSpan) != CSTATUS_OK) { goto Exit; } @@ -80,7 +79,7 @@ cstatus_t hkdfExpandProcess( goto Exit; } - if (handler->update(ctx, &counterSpan) != CSTATUS_OK) + if (handler->update(ctx, counterSpan) != CSTATUS_OK) { goto Exit; } @@ -96,18 +95,15 @@ cstatus_t hkdfExpandProcess( } /* tlen becomes the hash size or remaining okm size */ - tLen = HKDF_MIN(okm->size - okmOffset, SHA256_DIGEST_SIZE); + tLen = HKDF_MIN(okm.size - okmOffset, SHA256_DIGEST_SIZE); DEBUG_ASSERT(tLen <= sizeof(t)); - /* write the T buffer back to okm */ - ncSpanWrite(*okm, okmOffset, t, tLen); - - /* shift base okm pointer by T */ - okmOffset += tLen; + /* write the T buffer back to okm and advance okmOffset by tLen */ + ncSpanAppend(okm, &okmOffset, t, tLen); /* increment counter */ - counter++; + (*counter)++; } result = CSTATUS_OK; /* HMAC operation completed, so set success */ diff --git a/src/hkdf.h b/src/hkdf.h index 2e3a55e..50ee6e8 100644 --- a/src/hkdf.h +++ b/src/hkdf.h @@ -42,7 +42,7 @@ /* typedefs for hdkf callback functions */ -typedef cstatus_t (*hmac_hash_fn)(void* ctx, const cspan_t* data); +typedef cstatus_t (*hmac_hash_fn)(void* ctx, cspan_t data); typedef cstatus_t (*hmac_finish_fn)(void* ctx, sha256_t hmacOut32); struct nc_hkdf_fn_cb_struct @@ -54,8 +54,8 @@ struct nc_hkdf_fn_cb_struct cstatus_t hkdfExpandProcess( const struct nc_hkdf_fn_cb_struct* handler, void* ctx, - const cspan_t* info, - span_t* okm + cspan_t info, + span_t okm ); #endif /* !_NC_HKDF_H */ diff --git a/src/nc-crypto.c b/src/nc-crypto.c index 99c072d..752c9b0 100644 --- a/src/nc-crypto.c +++ b/src/nc-crypto.c @@ -134,7 +134,7 @@ _IMPLSTB cstatus_t _dummyAesFunc( #define _IMPL_CRYPTO_SHA256_HKDF_EXTRACT _fallbackHkdfExtract - _IMPLSTB cstatus_t _fallbackHkdfExtract(const cspan_t* salt, const cspan_t* ikm, sha256_t prk) + _IMPLSTB cstatus_t _fallbackHkdfExtract(cspan_t salt, cspan_t ikm, sha256_t prk) { return _IMPL_CRYPTO_SHA256_HMAC(salt, ikm, prk); } @@ -217,11 +217,11 @@ uint32_t ncCryptoFixedTimeComp(const uint8_t* a, const uint8_t* b, uint32_t size return _IMPL_CRYPTO_FIXED_TIME_COMPARE(a, b, size); } -cstatus_t ncCryptoDigestSha256(const cspan_t* data, sha256_t digestOut32) +cstatus_t ncCryptoDigestSha256(cspan_t data, sha256_t digestOut32) { /* Debug arg validate */ - DEBUG_ASSERT2(data != NULL && data->data != NULL, "Expected data to be non-null") - DEBUG_ASSERT2(digestOut32 != NULL, "Expected digestOut32 to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(data), "Expected data to be non-null") + DEBUG_ASSERT2(digestOut32 != NULL, "Expected digestOut32 to be non-null") #ifndef _IMPL_CRYPTO_SHA256_DIGEST #error "No SHA256 implementation defined" @@ -230,12 +230,12 @@ cstatus_t ncCryptoDigestSha256(const cspan_t* data, sha256_t digestOut32) return _IMPL_CRYPTO_SHA256_DIGEST(data, digestOut32); } -cstatus_t ncCryptoHmacSha256(const cspan_t* key, const cspan_t* data, sha256_t hmacOut32) +cstatus_t ncCryptoHmacSha256(cspan_t key, cspan_t data, sha256_t hmacOut32) { /* Debug arg validate */ - DEBUG_ASSERT2(key != NULL && key->data != NULL, "Expected key to be non-null") - DEBUG_ASSERT2(data != NULL && data->data != NULL, "Expected data to be non-null") - DEBUG_ASSERT2(hmacOut32 != NULL && data->data != NULL, "Expected hmacOut32 to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(key), "Expected key to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(data), "Expected data to be non-null") + DEBUG_ASSERT2(hmacOut32 != NULL, "Expected hmacOut32 to be non-null") #ifndef _IMPL_CRYPTO_SHA256_HMAC #error "No SHA256 HMAC implementation defined" @@ -244,12 +244,12 @@ cstatus_t ncCryptoHmacSha256(const cspan_t* key, const cspan_t* data, sha256_t h return _IMPL_CRYPTO_SHA256_HMAC(key, data, hmacOut32); } -cstatus_t ncCryptoSha256HkdfExpand(const cspan_t* prk, const cspan_t* info, span_t* okm) +cstatus_t ncCryptoSha256HkdfExpand(cspan_t prk, cspan_t info, span_t okm) { /* Debug arg validate */ - DEBUG_ASSERT2(prk != NULL && prk->data != NULL, "Expected prk to be non-null") - DEBUG_ASSERT2(info != NULL && info->data != NULL, "Expected info to be non-null") - DEBUG_ASSERT2(okm != NULL && okm->data != NULL, "Expected okm to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(prk), "Expected prk to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(info), "Expected info to be non-null") + DEBUG_ASSERT2(ncSpanIsValid(okm), "Expected okm to be non-null") /* * RFC 5869: 2.3 @@ -258,7 +258,7 @@ cstatus_t ncCryptoSha256HkdfExpand(const cspan_t* prk, const cspan_t* info, span * important as the counter is 1 byte, so it cannot overflow */ - if(okm->size > (uint32_t)(0xFFu * SHA256_DIGEST_SIZE)) + if(okm.size > (uint32_t)(0xFFu * SHA256_DIGEST_SIZE)) { return CSTATUS_FAIL; } @@ -270,12 +270,12 @@ cstatus_t ncCryptoSha256HkdfExpand(const cspan_t* prk, const cspan_t* info, span return _IMPL_CRYPTO_SHA256_HKDF_EXPAND(prk, info, okm); } -cstatus_t ncCryptoSha256HkdfExtract(const cspan_t* salt, const cspan_t* ikm, sha256_t prk) +cstatus_t ncCryptoSha256HkdfExtract(cspan_t salt, cspan_t ikm, sha256_t prk) { /* Debug arg validate */ - DEBUG_ASSERT2(salt != NULL, "Expected salt to be non-null") - DEBUG_ASSERT2(ikm != NULL, "Expected ikm to be non-null") - DEBUG_ASSERT2(prk != NULL, "Expected prk to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(salt), "Expected salt to be non-null") + DEBUG_ASSERT2(ncSpanIsValidC(ikm), "Expected ikm to be non-null") + DEBUG_ASSERT2(prk != NULL, "Expected prk to be non-null") #ifndef _IMPL_CRYPTO_SHA256_HKDF_EXTRACT #error "No SHA256 HKDF extract implementation defined" diff --git a/src/nc-crypto.h b/src/nc-crypto.h index 11da6d3..a1545de 100644 --- a/src/nc-crypto.h +++ b/src/nc-crypto.h @@ -42,13 +42,13 @@ uint32_t ncCryptoFixedTimeComp(const uint8_t* a, const uint8_t* b, uint32_t size void ncCryptoSecureZero(void* ptr, uint32_t size); -cstatus_t ncCryptoDigestSha256(const cspan_t* data, sha256_t digestOut32); +cstatus_t ncCryptoDigestSha256(cspan_t data, sha256_t digestOut32); -cstatus_t ncCryptoHmacSha256(const cspan_t* key, const cspan_t* data, sha256_t hmacOut32); +cstatus_t ncCryptoHmacSha256(cspan_t key, cspan_t data, sha256_t hmacOut32); -cstatus_t ncCryptoSha256HkdfExpand(const cspan_t* prk, const cspan_t* info, span_t* okm); +cstatus_t ncCryptoSha256HkdfExpand(cspan_t prk, cspan_t info, span_t okm); -cstatus_t ncCryptoSha256HkdfExtract(const cspan_t* salt, const cspan_t* ikm, sha256_t prk); +cstatus_t ncCryptoSha256HkdfExtract(cspan_t salt, cspan_t ikm, sha256_t prk); cstatus_t ncCryptoChacha20( const uint8_t key[CHACHA_KEY_SIZE], diff --git a/src/nc-util.h b/src/nc-util.h index d0afe28..2ddfd3f 100644 --- a/src/nc-util.h +++ b/src/nc-util.h @@ -102,6 +102,26 @@ typedef struct read_only_memory_span_struct uint32_t size; } cspan_t; +static _nc_fn_inline int ncSpanIsValid(span_t span) +{ + return span.data != NULL; +} + +static _nc_fn_inline int ncSpanIsValidC(cspan_t span) +{ + return span.data != NULL; +} + +static _nc_fn_inline int ncSpanIsValidRange(span_t span, uint32_t offset, uint32_t size) +{ + return ncSpanIsValid(span) && offset + size <= span.size; +} + +static _nc_fn_inline int ncSpanIsValidRangeC(cspan_t span, uint32_t offset, uint32_t size) +{ + return ncSpanIsValidC(span) && offset + size <= span.size; +} + static _nc_fn_inline void ncSpanInitC(cspan_t* span, const uint8_t* data, uint32_t size) { span->data = data; @@ -114,9 +134,25 @@ static _nc_fn_inline void ncSpanInit(span_t* span, uint8_t* data, uint32_t size) span->size = size; } +static _nc_fn_inline const uint8_t* ncSpanGetOffsetC(cspan_t span, uint32_t offset) +{ + DEBUG_ASSERT2(ncSpanIsValidC(span), "Expected span to be non-null"); + DEBUG_ASSERT2(offset < span.size, "Expected offset to be less than span size"); + + return span.data + offset; +} + +static _nc_fn_inline uint8_t* ncSpanGetOffset(span_t span, uint32_t offset) +{ + DEBUG_ASSERT2(ncSpanIsValid(span), "Expected span to be non-null"); + DEBUG_ASSERT2(offset < span.size, "Expected offset to be less than span size"); + + return span.data + offset; +} + static _nc_fn_inline void ncSpanWrite(span_t span, uint32_t offset, const uint8_t* data, uint32_t size) { - DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null") + DEBUG_ASSERT2(ncSpanIsValid(span), "Expected span to be non-null") DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") DEBUG_ASSERT2(offset + size <= span.size, "Expected offset + size to be less than span size") @@ -126,7 +162,7 @@ static _nc_fn_inline void ncSpanWrite(span_t span, uint32_t offset, const uint8_ static _nc_fn_inline void ncSpanAppend(span_t span, uint32_t* offset, const uint8_t* data, uint32_t size) { - DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null") + DEBUG_ASSERT2(ncSpanIsValid(span), "Expected span to be non-null") DEBUG_ASSERT2(offset != NULL, "Expected offset to be non-null") DEBUG_ASSERT2(data != NULL, "Expected data to be non-null") DEBUG_ASSERT2(*offset + size <= span.size, "Expected offset + size to be less than span size") @@ -142,7 +178,7 @@ static _nc_fn_inline span_t ncSpanSlice(span_t span, uint32_t offset, uint32_t s { span_t slice; - DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null"); + DEBUG_ASSERT2(ncSpanIsValid(span), "Expected span to be non-null"); DEBUG_ASSERT2(offset + size <= span.size, "Expected offset + size to be less than span size") /* Initialize slice, offset input data by the specified offset */ @@ -155,7 +191,7 @@ static _nc_fn_inline cspan_t ncSpanSliceC(cspan_t span, uint32_t offset, uint32_ { cspan_t slice; - DEBUG_ASSERT2(span.data != NULL, "Expected span to be non-null"); + DEBUG_ASSERT2(ncSpanIsValidC(span), "Expected span to be non-null"); DEBUG_ASSERT2(offset + size <= span.size, "Expected offset + size to be less than span size") /* Initialize slice, offset input data by the specified offset */ @@ -164,5 +200,39 @@ static _nc_fn_inline cspan_t ncSpanSliceC(cspan_t span, uint32_t offset, uint32_ return slice; } +static _nc_fn_inline void ncSpanCopyC(cspan_t src, span_t dest) +{ + DEBUG_ASSERT2(ncSpanIsValidC(src), "Expected span to be non-null"); + DEBUG_ASSERT2(ncSpanIsValid(dest), "Expected offset + size to be less than span size"); + DEBUG_ASSERT2(dest.size >= src.size, "Output buffer too small. Overrun detected"); + + /* Copy data to span */ + MEMMOV(dest.data, src.data, src.size); +} + +static _nc_fn_inline void ncSpanCopy(span_t src, span_t dest) +{ + cspan_t csrc; + + ncSpanInitC(&csrc, src.data, src.size); + ncSpanCopyC(csrc, dest); +} + +static _nc_fn_inline void ncSpanReadC(cspan_t src, uint8_t* dest, uint32_t size) +{ + span_t dsts; + + ncSpanInit(&dsts, dest, size); + ncSpanCopyC(src, dsts); +} + +static _nc_fn_inline void ncSpanRead(span_t src, uint8_t* dest, uint32_t size) +{ + cspan_t srcs; + + ncSpanInitC(&srcs, src.data, src.size); + ncSpanReadC(srcs, dest, size); +} + #endif /* !_NC_UTIL_H */ \ No newline at end of file diff --git a/src/noscrypt.c b/src/noscrypt.c index f3c28cb..46b3d65 100644 --- a/src/noscrypt.c +++ b/src/noscrypt.c @@ -244,7 +244,9 @@ static _nc_fn_inline NCResult _computeConversationKey( ncSpanInitC(&saltSpan, Nip44ConstantSalt, sizeof(Nip44ConstantSalt)); ncSpanInitC(&ikmSpan, sharedSecret->value, NC_SHARED_SEC_SIZE); - return ncCryptoSha256HkdfExtract(&saltSpan, &ikmSpan, ck->value) == CSTATUS_OK ? NC_SUCCESS : E_OPERATION_FAILED; + return ncCryptoSha256HkdfExtract(saltSpan, ikmSpan, ck->value) == CSTATUS_OK + ? NC_SUCCESS + : E_OPERATION_FAILED; } @@ -286,7 +288,7 @@ static _nc_fn_inline cstatus_t _getMessageKey( ncSpanInit(&okmSpan, messageKey->value, sizeof(struct message_key)); /* Output produces a message key (write it directly to struct memory) */ /* Nonce is the info */ - return ncCryptoSha256HkdfExpand(&prkSpan, &nonce, &okmSpan); + return ncCryptoSha256HkdfExpand(prkSpan, nonce, okmSpan); } static _nc_fn_inline NCResult _encryptNip44Ex( @@ -380,7 +382,7 @@ static _nc_fn_inline cstatus_t _computeHmac(const uint8_t key[NC_HMAC_KEY_SIZE], ncSpanInitC(&keySpan, key, NC_HMAC_KEY_SIZE); - return ncCryptoHmacSha256(&keySpan, &payload, hmacOut); + return ncCryptoHmacSha256(keySpan, payload, hmacOut); } static NCResult _verifyMacEx( @@ -650,7 +652,7 @@ NC_EXPORT NCResult NC_CC NCSignData( ncSpanInitC(&dataSpan, data, dataSize); /* Compute sha256 of the data before signing */ - if(ncCryptoDigestSha256(&dataSpan, digest) != CSTATUS_OK) + if(ncCryptoDigestSha256(dataSpan, digest) != CSTATUS_OK) { return E_INVALID_ARG; } @@ -708,7 +710,7 @@ NC_EXPORT NCResult NC_CC NCVerifyData( ncSpanInitC(&dataSpan, data, dataSize); /* Compute sha256 of the data before verifying */ - if (ncCryptoDigestSha256(&dataSpan, digest) != CSTATUS_OK) + if (ncCryptoDigestSha256(dataSpan, digest) != CSTATUS_OK) { return E_INVALID_ARG; } diff --git a/src/noscryptutil.c b/src/noscryptutil.c index 97526d9..56acb1b 100644 --- a/src/noscryptutil.c +++ b/src/noscryptutil.c @@ -34,6 +34,23 @@ #error "Utilities library must be disabled when using extreme compat mode" #endif /* NC_EXTREME_COMPAT */ +#define MIN_PADDING_SIZE 0x20u +#define NIP44_VERSION_SIZE 0x01u +#define NIP44_PT_LEN_SIZE sizeof(uint16_t) + +/* +* minimum size for a valid nip44 payload +* 1 byte version + 32 byte nonce + 32 byte mac + 2 byte ptSize + 32bytes minimum length +*/ +#define NIP44_MIN_PAYLOAD_SIZE (NIP44_VERSION_SIZE + 0x20 + 0x02 + 0x20 + 0x02) + +/* +* The minimum ciphertext size is the minimum padded size + the minimum +* size of the plaintext length field +*/ +#define NIP44_MIN_CIPHERTEXT_SIZE (MIN_PADDING_SIZE + NIP44_PT_LEN_SIZE) + + #define _nc_mem_free(x) if(x != NULL) { free(x); x = NULL; } #define _nc_mem_alloc(elements, size) calloc(elements, size); #define ZERO_FILL ncCryptoSecureZero @@ -51,7 +68,6 @@ #define CHECK_ARG_IS(is, expected, argPos) #endif /* !NC_DISABLE_INPUT_VALIDATION */ - #ifdef _NC_IS_WINDOWS #include @@ -73,38 +89,24 @@ } #endif -#define MIN_PADDING_SIZE 0x20u -#define NIP44_VERSION_SIZE 0x01u -#define NIP44_PT_LEN_SIZE sizeof(uint16_t) - -/* -* minimum size for a valid nip44 payload -* 1 byte version + 32 byte nonce + 32 byte mac + 2 byte ptSize + 32bytes minimum length -*/ -#define NIP44_MIN_PAYLOAD_SIZE (NIP44_VERSION_SIZE + 0x20 + 0x02 + 0x20 + 0x02) - -/* -* The minimum ciphertext size is the minimum padded size + the minimum -* size of the plaintext length field -*/ -#define NIP44_MIN_CIPHERTEXT_SIZE (MIN_PADDING_SIZE + NIP44_PT_LEN_SIZE) - - /* Currently were on nip44 version 2 */ static const uint8_t Nip44VersionValue[1] = { 0x02u }; -struct nc_util_enc_struct { +struct cipher_buffer_state { + + cspan_t input; + span_t output; - uint32_t _flags; + cspan_t actualOutput; +}; - cspan_t cipherInput; +struct nc_util_enc_struct { - /* - The data this span points to is allocated during initialization - */ - span_t cipherOutput; + uint32_t _flags; NCEncryptionArgs encArgs; + + struct cipher_buffer_state buffer; }; static _nc_fn_inline span_t _ncUtilAllocSpan(uint32_t count, size_t size) @@ -126,6 +128,11 @@ static _nc_fn_inline span_t _ncUtilAllocSpan(uint32_t count, size_t size) return span; } +static _nc_fn_inline void _ncUtilZeroSpan(span_t span) +{ + ZERO_FILL(span.data, span.size); +} + static _nc_fn_inline void _ncUtilFreeSpan(span_t span) { _nc_mem_free(span.data); @@ -198,7 +205,7 @@ static _nc_fn_inline uint32_t _calcNip44TotalOutSize(uint32_t inputSize) return bufferSize; } -static _nc_fn_inline cspan_t _nip44GetMacData(cspan_t payload) +static _nc_fn_inline span_t _nip44GetMacData(span_t payload) { DEBUG_ASSERT(payload.size > NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE); @@ -216,7 +223,7 @@ static _nc_fn_inline cspan_t _nip44GetMacData(cspan_t payload) * macData = ct.size - version.size + mac.size */ - return ncSpanSliceC( + return ncSpanSlice( payload, NIP44_VERSION_SIZE, payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE) @@ -237,70 +244,105 @@ static _nc_fn_inline span_t _nip44GetMacOutput(span_t payload) ); } -static _nc_fn_inline cspan_t _nip44ParseMac(cspan_t payload) +static _nc_fn_inline int _nip44ParseSegments( + cspan_t payload, + cspan_t* nonce, + cspan_t* mac, + cspan_t* macData, + cspan_t* cipherText +) { - DEBUG_ASSERT(payload.size >= NIP44_MIN_PAYLOAD_SIZE); + if (payload.size < NIP44_MIN_PAYLOAD_SIZE) + { + return 0; + } + + /* slice after the version and before the mac segments */ + *nonce = ncSpanSliceC( + payload, + NIP44_VERSION_SIZE, + NC_ENCRYPTION_NONCE_SIZE + ); /* * Mac is the final 32 bytes of the ciphertext buffer */ - return ncSpanSliceC( + *mac = ncSpanSliceC( payload, payload.size - NC_ENCRYPTION_MAC_SIZE, NC_ENCRYPTION_MAC_SIZE ); -} -static _nc_fn_inline cspan_t _nip44ParseCipherText(cspan_t payload) -{ - DEBUG_ASSERT(payload.size >= NIP44_MIN_PAYLOAD_SIZE); + /* + * The mac data is the nonce+ct segment of the buffer for mac computation. + */ + *macData = ncSpanSliceC( + payload, + NIP44_VERSION_SIZE, + payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE) + ); - /* ct is all of the data after the nonce and before the mac segment */ - return ncSpanSliceC( + /* + * Ciphertext is after the nonce segment and before the mac segment + */ + *cipherText = ncSpanSliceC( payload, NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE, payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE) ); + + return 1; } -static _nc_fn_inline cspan_t _nip44ParseNonce(cspan_t payload) + +static _nc_fn_inline void _cipherPublishOutput(NCUtilCipherContext* buffer, uint32_t offset, uint32_t size) { - DEBUG_ASSERT(payload.size >= NIP44_MIN_PAYLOAD_SIZE); + span_t slice; - /* slice after the version and before the mac segments */ - return ncSpanSliceC( - payload, - NIP44_VERSION_SIZE, - NC_ENCRYPTION_NONCE_SIZE - ); + DEBUG_ASSERT(ncSpanIsValid(buffer->buffer.output)); + + if (size == 0) + { + ncSpanInitC(&buffer->buffer.actualOutput, NULL, 0); + } + else + { + /* use slice for debug guards */ + slice = ncSpanSlice(buffer->buffer.output, offset, size); + ncSpanInitC(&buffer->buffer.actualOutput, slice.data, slice.size); + } } +/* +* I want the encryption/decyption functions to be indempodent +* meaning all mutations that happen can be repeated without +* side effects. IE no perminent state changes that can't be +* undone. +*/ + static NCResult _nip44EncryptCompleteCore( const NCContext* libContext, const NCSecretKey* sk, const NCPublicKey* pk, - NCEncryptionArgs encArgs, - cspan_t plainText, - span_t payload + NCUtilCipherContext* state ) { NCResult result; - cspan_t macData, cPayload; - span_t macOutput; - uint32_t outPos, paddedCtSize; + cspan_t plainText; + span_t macData, macOutput, payload; + uint32_t outPos; uint8_t ptSize[NIP44_PT_LEN_SIZE]; uint8_t hmacKeyOut[NC_ENCRYPTION_MAC_SIZE]; + NCEncryptionArgs encArgs; outPos = 0; + encArgs = state->encArgs; + payload = state->buffer.output; + plainText = state->buffer.input; DEBUG_ASSERT(encArgs.version == NC_ENC_VERSION_NIP44); - ncSpanInitC(&cPayload, payload.data, payload.size); - - /* Padded size is required to know how large the CT buffer is for encryption */ - paddedCtSize = _calcNip44PtPadding(plainText.size); - /* Start by appending the version number */ ncSpanAppend(payload, &outPos, Nip44VersionValue, sizeof(Nip44VersionValue)); @@ -343,9 +385,9 @@ static NCResult _nip44EncryptCompleteCore( result = NCSetEncryptionData( &encArgs, - (payload.data + outPos), /* in place encryption */ - (payload.data + outPos), - paddedCtSize + NIP44_PT_LEN_SIZE /* Plaintext + pt size must be encrypted */ + ncSpanGetOffset(payload, outPos), /* in place encryption */ + ncSpanGetOffset(payload, outPos), + NIP44_PT_LEN_SIZE + _calcNip44PtPadding(plainText.size) /* Plaintext + pt size must be encrypted */ ); DEBUG_ASSERT(result == NC_SUCCESS); @@ -363,7 +405,7 @@ static NCResult _nip44EncryptCompleteCore( * the plaintext data, followed by zero padding. */ - ncSpanWrite(payload, outPos, ptSize, NIP44_PT_LEN_SIZE); + ncSpanWrite(payload, outPos, ptSize, sizeof(ptSize)); ncSpanWrite( payload, @@ -387,15 +429,15 @@ static NCResult _nip44EncryptCompleteCore( this helper captures that data segment into a span */ - macData = _nip44GetMacData(cPayload); + macData = _nip44GetMacData(payload); macOutput = _nip44GetMacOutput(payload); result = NCComputeMac( libContext, hmacKeyOut, - macData.data, + ncSpanGetOffset(macData, 0), macData.size, - macOutput.data + ncSpanGetOffset(macOutput, 0) ); if (result != NC_SUCCESS) @@ -407,6 +449,9 @@ static NCResult _nip44EncryptCompleteCore( DEBUG_ASSERT2(outPos == payload.size, "Buffer under/overflow detected"); + /* publish all payload bytes to output */ + _cipherPublishOutput(state, 0, outPos); + /* zero hmac key before returning */ ZERO_FILL(hmacKeyOut, sizeof(hmacKeyOut)); @@ -418,45 +463,55 @@ static NCResult _nip44DecryptCompleteCore( const NCContext* libContext, const NCSecretKey* recvKey, const NCPublicKey* sendKey, - const NCUtilCipherContext* cipher + NCUtilCipherContext* state ) { NCResult result; NCMacVerifyArgs macArgs; NCEncryptionArgs encArgs; - cspan_t macData, macValue, cipherText, nonce; + cspan_t macData, macValue, nonce, payload, cipherText; + span_t output; + uint16_t ptSize; - DEBUG_ASSERT(libContext && recvKey && sendKey && cipher); - DEBUG_ASSERT(cipher->encArgs.version == NC_ENC_VERSION_NIP44); + DEBUG_ASSERT(libContext && recvKey && sendKey && state); + DEBUG_ASSERT(state->encArgs.version == NC_ENC_VERSION_NIP44); + DEBUG_ASSERT(state->buffer.input.size >= NIP44_MIN_PAYLOAD_SIZE); /* ensure decryption mode */ - DEBUG_ASSERT(cipher->_flags & NC_UTIL_CIPHER_MODE_DECRYPT); + DEBUG_ASSERT(state->_flags & NC_UTIL_CIPHER_MODE_DECRYPT); /* store local stack copy for safe mutation */ - encArgs = cipher->encArgs; + encArgs = state->encArgs; + payload = state->buffer.input; + output = state->buffer.output; - nonce = _nip44ParseNonce(cipher->cipherInput); + /* + * Copy the input buffer to the output buffer because the + * decryption happens in-place and needs a writable buffer + * + * After the operation is complete, we will assign the actual plaintext + * data to the actual output buffer + */ - /* Verify mac if the user allowed it */ - if ((cipher->_flags & NC_UTIL_CIPHER_MAC_NO_VERIFY) == 0) - { - /* - * The mac data to verify against is the nonce+ciphertext data - * from within the nip44 message payload - */ + DEBUG_ASSERT2(ncSpanIsValid(output), "Output buffer was not allocated"); - macData = _nip44GetMacData(cipher->cipherInput); - macValue = _nip44ParseMac(cipher->cipherInput); + if (!_nip44ParseSegments(payload, &nonce, &macValue, &macData, &cipherText)) + { + return E_CIPHER_INVALID_FORMAT; + } + /* Verify mac if the user allowed it */ + if ((state->_flags & NC_UTIL_CIPHER_MAC_NO_VERIFY) == 0) + { DEBUG_ASSERT(macValue.size == NC_ENCRYPTION_MAC_SIZE); - DEBUG_ASSERT(macData.size > NC_ENCRYPTION_NONCE_SIZE + 0x20); + DEBUG_ASSERT(macData.size > NC_ENCRYPTION_NONCE_SIZE + MIN_PADDING_SIZE); /* Assign the mac data to the mac verify args */ - macArgs.mac32 = macValue.data; - macArgs.nonce32 = nonce.data; + macArgs.mac32 = ncSpanGetOffsetC(macValue, 0); + macArgs.nonce32 = ncSpanGetOffsetC(nonce, 0); /* payload for verifying a mac in nip44 is the nonce+ciphertext */ - macArgs.payload = macData.data; + macArgs.payload = ncSpanGetOffsetC(macData, 0); macArgs.payloadSize = macData.size; /* Verify the mac */ @@ -474,44 +529,60 @@ static NCResult _nip44DecryptCompleteCore( } } - cipherText = _nip44ParseCipherText(cipher->cipherInput); - - DEBUG_ASSERT2(cipherText.size >= MIN_PADDING_SIZE, "Cipertext segment was parsed incorrectly. Too small"); - - /* manually sign nonce */ - encArgs.nonceData = nonce.data; - - /* - * Remember the decryption operation is symmetric, it reads the input bytes and writes - * directly to the output. - * - * The decryption is performed on the ciphertext segment and we can write the output - * directly the output buffer. - * - * The leading 2 bytes will be the encoded plaintext size, followed by the plaintext data - * and padding. That's okay. The user will call NCUtilCipherGetOutputSize to get the - * actual size of the plaintext, which will exlcude the leading 2 bytes and padding. + /* + * manually assign nonce because it's a constant pointer which + * is not allowed when calling setproperty */ + encArgs.nonceData = ncSpanGetOffsetC(nonce, 0); - DEBUG_ASSERT(cipher->cipherOutput.size >= cipherText.size); - + DEBUG_ASSERT2(cipherText.size >= MIN_PADDING_SIZE, "Cipertext segment was parsed incorrectly. Too small"); + result = NCSetEncryptionData( &encArgs, - cipherText.data, - cipher->cipherOutput.data, + ncSpanGetOffsetC(cipherText, 0), + ncSpanGetOffset(output, 0), /*decrypt ciphertext and write directly to the output buffer */ cipherText.size ); + DEBUG_ASSERT(result == NC_SUCCESS); + + /* + * If decryption was successful, the data should be written + * directly to the output buffer + */ + result = NCDecrypt(libContext, recvKey, sendKey, &encArgs); + if (result != NC_SUCCESS) { return result; } /* - * If decryption was successful, the data should be written - * directly to the output buffer + * Parse CT length and assign the output buffer. + * + * PT size is stored at the beginning of the ciphertext + * segment and is 2 bytes in size, big endian. */ - result = NCDecrypt(libContext, recvKey, sendKey, &encArgs); + + ptSize = (uint16_t)(output.data[0] << 8 | output.data[1]); + + /* + * If the PT is corrupted or set maliciously, it can overrun + * the current buffer. The PT size must be less than the + * ciphertext size. + */ + if (!ncSpanIsValidRange(output, NIP44_PT_LEN_SIZE, ptSize)) + { + return E_OPERATION_FAILED; + } + + /* + * actual output span should now point to the decrypted plaintext + * data segment + */ + _cipherPublishOutput(state, NIP44_PT_LEN_SIZE, ptSize); + + DEBUG_ASSERT(state->buffer.actualOutput.size < cipherText.size); return result; } @@ -582,13 +653,13 @@ NC_EXPORT void NC_CC NCUtilCipherFree(NCUtilCipherContext* encCtx) * If zero on free flag is set, we can zero all output memory * before returning the buffer back to the heap */ - if ((encCtx->_flags & NC_UTIL_CIPHER_ZERO_ON_FREE) > 0 && encCtx->cipherOutput.data) + if ((encCtx->_flags & NC_UTIL_CIPHER_ZERO_ON_FREE) > 0 && ncSpanIsValid(encCtx->buffer.output)) { - ZERO_FILL(encCtx->cipherOutput.data, encCtx->cipherOutput.size); + _ncUtilZeroSpan(encCtx->buffer.output); } - /* Free output buffers */ - _ncUtilFreeSpan(encCtx->cipherOutput); + /* Free output buffers (null buffers are allowed) */ + _ncUtilFreeSpan(encCtx->buffer.output); /* context can be released */ _nc_mem_free(encCtx); @@ -604,14 +675,12 @@ NC_EXPORT NCResult NC_CC NCUtilCipherInit( CHECK_NULL_ARG(encCtx, 0); CHECK_NULL_ARG(inputData, 1); - /* The output state must not have alraedy been allocated */ - CHECK_ARG_IS(encCtx->cipherOutput.data == NULL, 0); if ((encCtx->_flags & NC_UTIL_CIPHER_MODE_DECRYPT) > 0) { /* * Validate the input data for proper format for - * the current cipher version + * the current state version */ switch (encCtx->encArgs.version) { @@ -652,7 +721,7 @@ NC_EXPORT NCResult NC_CC NCUtilCipherInit( { /* * Calculate the correct output size to store the encryption - * data for the given cipher version + * data for the given state version */ outputSize = NCUtilGetEncryptionBufferSize(encCtx->encArgs.version, inputSize); } @@ -662,15 +731,48 @@ NC_EXPORT NCResult NC_CC NCUtilCipherInit( return outputSize; } + /* + * If the buffer was previously allocated, the reuseable flag + * must be set to allow the buffer to be re-used for another + * operation. + */ + + if (ncSpanIsValid(encCtx->buffer.output)) + { + CHECK_ARG_IS((encCtx->_flags & NC_UTIL_CIPHER_REUSEABLE) > 0, 0); + + /* + * if the existing buffer is large enough to hold the new + * data reuse it, otherwise free it and allocate a new buffer + */ + + if (outputSize <= encCtx->buffer.output.size) + { + _ncUtilZeroSpan(encCtx->buffer.output); + + goto AssignInputAndExit; + } + else + { + _ncUtilFreeSpan(encCtx->buffer.output); + } + } + /* Alloc output buffer within the struct */ - encCtx->cipherOutput = _ncUtilAllocSpan((uint32_t)outputSize, sizeof(uint8_t)); + encCtx->buffer.output = _ncUtilAllocSpan((uint32_t)outputSize, sizeof(uint8_t)); - if (!encCtx->cipherOutput.data) + if (!ncSpanIsValid(encCtx->buffer.output)) { return E_OUT_OF_MEMORY; } - ncSpanInitC(&encCtx->cipherInput, inputData, inputSize); +AssignInputAndExit: + + /* Confirm output was allocated */ + DEBUG_ASSERT(ncSpanIsValid(encCtx->buffer.output)); + + /* Assign the input data span to point to the assigned input data */ + ncSpanInitC(&encCtx->buffer.input, inputData, inputSize); return NC_SUCCESS; } @@ -684,48 +786,14 @@ NC_EXPORT NCResult NC_CC NCUtilCipherGetFlags(const NCUtilCipherContext* ctx) NC_EXPORT NCResult NC_CC NCUtilCipherGetOutputSize(const NCUtilCipherContext* encCtx) { - uint16_t nip44PtSize; - CHECK_NULL_ARG(encCtx, 0); - /* - * if nip44 decryption is desired, the output buffer will be - * overallocated. It will also contain some padding bytes - * so we need to parse the plaintext size from the buffer - * and return that as the output size. - */ - if (encCtx->encArgs.version == NC_ENC_VERSION_NIP44 - && (encCtx->_flags & NC_UTIL_CIPHER_MODE_DECRYPT) > 0) + if (!ncSpanIsValidC(encCtx->buffer.actualOutput)) { - - /* ensure the output has been allocated correctly */ - if (encCtx->cipherOutput.size < NIP44_PT_LEN_SIZE) - { - return E_INVALID_CONTEXT; - } - - /* - * If a decryption operation was performed the leading 2 bytes will - * be the big-endian encoded plaintext size. This function should - * return the size of the plaintext data, not the entire buffer. - */ - - nip44PtSize = (encCtx->cipherOutput.data[0] << 8) | encCtx->cipherOutput.data[1]; - - /* - * If improperly decryption/formatted, the pt size may be some really large - * number when decoded, so make sure it doesn't point to a location outside - * the buffer, that would be invalid - */ - if (nip44PtSize > (encCtx->cipherOutput.size - NIP44_PT_LEN_SIZE)) - { - return E_CIPHER_INVALID_FORMAT; - } - - return (NCResult)nip44PtSize; + return E_CIPHER_NO_OUTPUT; } - return (NCResult)(encCtx->cipherOutput.size); + return (NCResult)(encCtx->buffer.actualOutput.size); } NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput( @@ -734,55 +802,24 @@ NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput( uint32_t outputSize ) { - NCResult result; - - CHECK_NULL_ARG(encCtx, 0) - CHECK_NULL_ARG(output, 1) - - /* - * Again if in nip44 decrypt mode we only want the - * actual plaintext data - */ + CHECK_NULL_ARG(encCtx, 0); + CHECK_NULL_ARG(output, 1); - if (encCtx->encArgs.version == NC_ENC_VERSION_NIP44 - && (encCtx->_flags & NC_UTIL_CIPHER_MODE_DECRYPT) > 0) + if (!ncSpanIsValidC(encCtx->buffer.actualOutput)) { - result = NCUtilCipherGetOutputSize(encCtx); - - if (result < 0) - { - return result; - } - - DEBUG_ASSERT((result + NIP44_PT_LEN_SIZE) < encCtx->cipherOutput.size); - - /* Make sure the output buffer is large enough */ - CHECK_ARG_RANGE(outputSize, result, UINT32_MAX, 2); - - /* - * Plaintext data sits directly after the length bytes - * and up to the length of the plaintext size - */ - MEMMOV( - output, - encCtx->cipherOutput.data + NIP44_PT_LEN_SIZE, - (uint32_t)result - ); - - return result; + return E_CIPHER_NO_OUTPUT; } - else - { - CHECK_ARG_RANGE(outputSize, encCtx->cipherOutput.size, UINT32_MAX, 2); - MEMMOV( - output, - encCtx->cipherOutput.data, - encCtx->cipherOutput.size - ); + /* Buffer must be as large as the output data */ + CHECK_ARG_RANGE(outputSize, encCtx->buffer.actualOutput.size, UINT32_MAX, 2); - return (NCResult)encCtx->cipherOutput.size; - } + ncSpanReadC( + encCtx->buffer.actualOutput, + output, + outputSize + ); + + return (NCResult)encCtx->buffer.actualOutput.size; } NC_EXPORT NCResult NCUtilCipherSetProperty( @@ -804,7 +841,7 @@ NC_EXPORT NCResult NCUtilCipherSetProperty( } NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( - const NCUtilCipherContext* encCtx, + NCUtilCipherContext* encCtx, const NCContext* libContext, const NCSecretKey* sk, const NCPublicKey* pk @@ -816,15 +853,18 @@ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( CHECK_NULL_ARG(pk, 3); /* Make sure input & output buffers have been assigned/allocated */ - if (encCtx->cipherOutput.data == NULL) + if (!ncSpanIsValid(encCtx->buffer.output)) { return E_INVALID_CONTEXT; } - if (encCtx->cipherInput.data == NULL) + if (!ncSpanIsValidC(encCtx->buffer.input)) { return E_INVALID_CONTEXT; } + /* Reset output data pointer incase it has been moved */ + _cipherPublishOutput(encCtx, 0, 0); + switch (encCtx->encArgs.version) { case NC_ENC_VERSION_NIP44: @@ -841,14 +881,7 @@ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( return E_CIPHER_BAD_NONCE; } - return _nip44EncryptCompleteCore( - libContext, - sk, - pk, - encCtx->encArgs, - encCtx->cipherInput, - encCtx->cipherOutput - ); + return _nip44EncryptCompleteCore(libContext, sk, pk, encCtx); } default: diff --git a/src/providers/bcrypt.c b/src/providers/bcrypt.c index d1b9aa5..67ae695 100644 --- a/src/providers/bcrypt.c +++ b/src/providers/bcrypt.c @@ -63,7 +63,7 @@ _IMPLSTB NTSTATUS _bcInitSha256(struct _bcrypt_ctx* ctx, DWORD flags) return result; } -_IMPLSTB NTSTATUS _bcCreateHmac(struct _bcrypt_ctx* ctx, const cspan_t* key) +_IMPLSTB NTSTATUS _bcCreateHmac(struct _bcrypt_ctx* ctx, cspan_t key) { /* * NOTE: @@ -79,8 +79,8 @@ _IMPLSTB NTSTATUS _bcCreateHmac(struct _bcrypt_ctx* ctx, const cspan_t* key) &ctx->hHash, NULL, 0, - (uint8_t*)key->data, - key->size, + (uint8_t*)key.data, + key.size, BCRYPT_HASH_REUSABLE_FLAG /* Enable reusable for expand function */ ); } @@ -92,7 +92,7 @@ _IMPLSTB NTSTATUS _bcCreate(struct _bcrypt_ctx* ctx) /* Zero out key span for 0 size and NULL data ptr */ SecureZeroMemory(&key, sizeof(cspan_t)); - return _bcCreateHmac(ctx, &key); + return _bcCreateHmac(ctx, key); } _IMPLSTB NTSTATUS _bcHashDataRaw(const struct _bcrypt_ctx* ctx, const uint8_t* data, uint32_t len) @@ -100,9 +100,9 @@ _IMPLSTB NTSTATUS _bcHashDataRaw(const struct _bcrypt_ctx* ctx, const uint8_t* d return BCryptHashData(ctx->hHash, (uint8_t*)data, len, 0); } -_IMPLSTB NTSTATUS _bcHashData(const struct _bcrypt_ctx* ctx, const cspan_t* data) +_IMPLSTB NTSTATUS _bcHashData(const struct _bcrypt_ctx* ctx, cspan_t data) { - return _bcHashDataRaw(ctx, data->data, data->size); + return _bcHashDataRaw(ctx, data.data, data.size); } _IMPLSTB NTSTATUS _bcFinishHash(const struct _bcrypt_ctx* ctx, sha256_t digestOut32) @@ -146,7 +146,7 @@ _IMPLSTB void _bcDestroyCtx(struct _bcrypt_ctx* ctx) /* Export function fallack */ #define _IMPL_CRYPTO_SHA256_DIGEST _bcrypt_sha256_digest - _IMPLSTB cstatus_t _bcrypt_sha256_digest(const cspan_t* data, sha256_t digestOut32) + _IMPLSTB cstatus_t _bcrypt_sha256_digest(cspan_t data, sha256_t digestOut32) { cstatus_t result; struct _bcrypt_ctx ctx; @@ -177,7 +177,7 @@ _IMPLSTB void _bcDestroyCtx(struct _bcrypt_ctx* ctx) /* Export function */ #define _IMPL_CRYPTO_SHA256_HMAC _bcrypt_hmac_sha256 - _IMPLSTB cstatus_t _bcrypt_hmac_sha256(const cspan_t* key, const cspan_t* data, sha256_t hmacOut32) + _IMPLSTB cstatus_t _bcrypt_hmac_sha256(cspan_t key, cspan_t data, sha256_t hmacOut32) { cstatus_t result; struct _bcrypt_ctx ctx; @@ -213,7 +213,7 @@ _IMPLSTB void _bcDestroyCtx(struct _bcrypt_ctx* ctx) #define _IMPL_CRYPTO_SHA256_HKDF_EXPAND _bcrypt_fallback_hkdf_expand - cstatus_t _bcrypt_hkdf_update(void* ctx, const cspan_t* data) + cstatus_t _bcrypt_hkdf_update(void* ctx, cspan_t data) { DEBUG_ASSERT(ctx != NULL) @@ -229,7 +229,7 @@ _IMPLSTB void _bcDestroyCtx(struct _bcrypt_ctx* ctx) return CSTATUS_OK; } - _IMPLSTB cstatus_t _bcrypt_fallback_hkdf_expand(const cspan_t* prk, const cspan_t* info, span_t* okm) + _IMPLSTB cstatus_t _bcrypt_fallback_hkdf_expand(cspan_t prk, cspan_t info, span_t okm) { cstatus_t result; struct _bcrypt_ctx ctx; diff --git a/src/providers/mbedtls.c b/src/providers/mbedtls.c index df5201f..8479380 100644 --- a/src/providers/mbedtls.c +++ b/src/providers/mbedtls.c @@ -95,13 +95,13 @@ _IMPLSTB const mbedtls_md_info_t* _mbed_sha256_alg(void) #define _IMPL_CRYPTO_SHA256_DIGEST _mbed_sha256_digest - _IMPLSTB cstatus_t _mbed_sha256_digest(const cspan_t* data, sha256_t digestOut32) + _IMPLSTB cstatus_t _mbed_sha256_digest(cspan_t data, sha256_t digestOut32) { - _overflow_check(data->size) + _overflow_check(data.size) return mbedtls_sha256( - data->data, - data->size, + data.data, + data.size, digestOut32, 0 /* Set 0 for sha256 mode */ ) == 0 ? CSTATUS_OK : CSTATUS_FAIL; @@ -114,19 +114,19 @@ _IMPLSTB const mbedtls_md_info_t* _mbed_sha256_alg(void) #define _IMPL_CRYPTO_SHA256_HMAC _mbed_sha256_hmac - _IMPLSTB cstatus_t _mbed_sha256_hmac(const cspan_t* key, const cspan_t* data, sha256_t hmacOut32) + _IMPLSTB cstatus_t _mbed_sha256_hmac(cspan_t key, cspan_t data, sha256_t hmacOut32) { - _overflow_check(data->size) + _overflow_check(data.size) /* Keys should never be large enough for this to matter, but sanity check. */ - DEBUG_ASSERT2(key->size < SIZE_MAX, "Expected key size to be less than SIZE_MAX") + DEBUG_ASSERT2(key.size < SIZE_MAX, "Expected key size to be less than SIZE_MAX") return mbedtls_md_hmac( _mbed_sha256_alg(), - key->data, - key->size, - data->data, - data->size, + key.data, + key.size, + data.data, + data.size, hmacOut32 ) == 0 ? CSTATUS_OK : CSTATUS_FAIL; } @@ -137,21 +137,21 @@ _IMPLSTB const mbedtls_md_info_t* _mbed_sha256_alg(void) #define _IMPL_CRYPTO_SHA256_HKDF_EXPAND _mbed_sha256_hkdf_expand - _IMPLSTB cstatus_t _mbed_sha256_hkdf_expand(const cspan_t* prk, const cspan_t* info, span_t* okm) + _IMPLSTB cstatus_t _mbed_sha256_hkdf_expand(cspan_t prk, cspan_t info, span_t okm) { /* These sizes should never be large enough to overflow on <64bit platforms, but sanity check */ - DEBUG_ASSERT(okm->size < SIZE_MAX) - DEBUG_ASSERT(prk->size < SIZE_MAX) - DEBUG_ASSERT(info->size < SIZE_MAX) + DEBUG_ASSERT(okm.size < SIZE_MAX) + DEBUG_ASSERT(prk.size < SIZE_MAX) + DEBUG_ASSERT(info.size < SIZE_MAX) return mbedtls_hkdf_expand( _mbed_sha256_alg(), - prk->data, - prk->size, - info->data, - info->size, - okm->data, - okm->size + prk.data, + prk.size, + info.data, + info.size, + okm.data, + okm.size ) == 0 ? CSTATUS_OK : CSTATUS_FAIL; } diff --git a/tests/hex.h b/tests/hex.h index e6a2a07..3cfe559 100644 --- a/tests/hex.h +++ b/tests/hex.h @@ -68,6 +68,7 @@ static span_t _fromHexString(const char* hexLiteral, uint32_t strLen) if(!hexLiteral) { + ncSpanInit(&hexBytes, NULL, 0); return hexBytes; } @@ -125,13 +126,13 @@ static void PrintHexRaw(void* bytes, size_t len) */ static void PrintHexBytes(span_t hexBytes) { - if (!hexBytes.data) + if (ncSpanIsValid(hexBytes)) { - puts("NULL"); + PrintHexRaw(hexBytes.data, hexBytes.size); } else { - PrintHexRaw(hexBytes.data, hexBytes.size); + puts("NULL"); } } diff --git a/tests/test.c b/tests/test.c index 80b8704..b4cdef1 100644 --- a/tests/test.c +++ b/tests/test.c @@ -617,7 +617,7 @@ static int TestUtilNip44Encryption( span_t recvKey, span_t nonce, span_t expected, - const char* plainText + span_t plainText ) { NCPublicKey recvPubKey; @@ -634,7 +634,7 @@ static int TestUtilNip44Encryption( ENSURE(ctx != NULL); - TEST(NCUtilCipherInit(ctx, (const uint8_t*)plainText, strlen(plainText)), NC_SUCCESS); + TEST(NCUtilCipherInit(ctx, plainText.data, plainText.size), NC_SUCCESS); /* Nonce is required for nip44 encryption */ TEST(NCUtilCipherSetProperty(ctx, NC_ENC_SET_NIP44_NONCE, nonce.data, nonce.size), NC_SUCCESS); @@ -659,6 +659,8 @@ static int TestUtilNip44Encryption( /* Free encryption memory */ NCUtilCipherFree(ctx); + + return 0; } static int TestUtilNip44Decryption( @@ -666,7 +668,7 @@ static int TestUtilNip44Decryption( span_t sendKey, span_t recvKey, span_t payload, - const char* expectedPt + span_t expectedPt ) { NCPublicKey recvPubKey; @@ -690,7 +692,7 @@ static int TestUtilNip44Decryption( NCResult plaintextSize = NCUtilCipherGetOutputSize(ctx); - TEST(plaintextSize, strlen(expectedPt)); + TEST(plaintextSize, expectedPt.size); outData = (uint8_t*)malloc(plaintextSize); @@ -700,12 +702,14 @@ static int TestUtilNip44Decryption( TEST(NCUtilCipherReadOutput(ctx, outData, plaintextSize), plaintextSize); /* Ensure encrypted payload matches */ - TEST(memcmp(outData, expectedPt, plaintextSize), 0); + TEST(memcmp(outData, expectedPt.data, plaintextSize), 0); free(outData); /* Free encryption memory */ NCUtilCipherFree(ctx); + + return 0; } static int TestUtilFunctions(const NCContext* libCtx) @@ -727,7 +731,7 @@ static int TestUtilFunctions(const NCContext* libCtx) span_t recvKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000002", sizeof(NCSecretKey)); span_t nonce = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", NC_ENCRYPTION_NONCE_SIZE); span_t payload = FromHexString("02000000000000000000000000000000000000000000000000000000000000000179ed06e5548ad3ff58ca920e6c0b4329f6040230f7e6e5641f20741780f0adc35a09794259929a02bb06ad8e8cf709ee4ccc567e9d514cdf5781af27a3e905e55b1b", 99); - const char* plainText = "a"; + span_t plainText = FromHexString("61", 1); if (TestUtilNip44Encryption(libCtx, sendKey, recvKey, nonce, payload, plainText) != 0) { @@ -740,9 +744,8 @@ static int TestUtilFunctions(const NCContext* libCtx) /* From the nip44 vectors file */ span_t sendKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", sizeof(NCSecretKey)); span_t recvKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000002", sizeof(NCSecretKey)); - span_t nonce = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", NC_ENCRYPTION_NONCE_SIZE); span_t payload = FromHexString("02000000000000000000000000000000000000000000000000000000000000000179ed06e5548ad3ff58ca920e6c0b4329f6040230f7e6e5641f20741780f0adc35a09794259929a02bb06ad8e8cf709ee4ccc567e9d514cdf5781af27a3e905e55b1b", 99); - const char* plainText = "a"; + span_t plainText = FromHexString("61", 1); if (TestUtilNip44Decryption(libCtx, sendKey, recvKey, payload, plainText) != 0) { -- cgit From 7c8f910e5be9a1d86af5bdcb7e51e8092cc06cf6 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 7 Aug 2024 15:11:19 -0400 Subject: feat: added NCEncryptionGetIvSize() function and helpers --- include/noscrypt.h | 20 +++++++---- include/noscryptutil.h | 11 +++++- src/noscrypt.c | 71 +++++++++++++++++++----------------- src/noscryptutil.c | 47 ++++++++++++++++++------ tests/test.c | 97 +++++++++++++++++++++++++++----------------------- 5 files changed, 149 insertions(+), 97 deletions(-) (limited to 'tests') diff --git a/include/noscrypt.h b/include/noscrypt.h index faad1f1..574cef9 100644 --- a/include/noscrypt.h +++ b/include/noscrypt.h @@ -68,7 +68,6 @@ extern "C" { */ #define BIP340_PUBKEY_HEADER_BYTE 0x02 #define NIP44_MESSAGE_KEY_SIZE 0x4c /*32 + 12 + 32 = 76 */ -#define NC_ENCRYPTION_NONCE_SIZE 0x20 #define NC_SEC_KEY_SIZE 0x20 #define NC_PUBKEY_SIZE 0x20 #define NC_CONTEXT_ENTROPY_SIZE 0x20 @@ -77,8 +76,9 @@ extern "C" { #define NC_HMAC_KEY_SIZE 0x20 #define NC_ENCRYPTION_MAC_SIZE 0x20 #define NC_MESSAGE_KEY_SIZE NIP44_MESSAGE_KEY_SIZE -#define NC_NIP04_AES_IV_SIZE 0x10 /* AES IV size is 16 bytes (aka cipher block size) */ #define NC_NIP04_AES_KEY_SIZE 0x20 /* AES 256 key size */ +#define NC_NIP44_IV_SIZE 0x20 /* 32 bytes */ +#define NC_NIP04_IV_SIZE 0x10 /* 16 bytes */ /* * From spec @@ -121,10 +121,9 @@ extern "C" { */ #define NC_ENC_SET_VERSION 0x01 -#define NC_ENC_SET_NIP44_NONCE 0x02 +#define NC_ENC_SET_IV 0x02 #define NC_ENC_SET_NIP44_MAC_KEY 0x03 #define NC_ENC_SET_NIP04_KEY 0x04 -#define NC_ENC_SET_NIP04_IV 0x05 /* A compressed resul/return value, negative values @@ -578,7 +577,7 @@ NC_EXPORT NCResult NCComputeMac( * @return NC_SUCCESS if the operation was successful, otherwise an error code. Use NCParseErrorCode to * the error code and positional argument that caused the error. */ -NC_EXPORT NCResult NCSetEncryptionProperty( +NC_EXPORT NCResult NCEncryptionSetProperty( NCEncryptionArgs* args, uint32_t property, uint32_t value @@ -595,7 +594,7 @@ NC_EXPORT NCResult NCSetEncryptionProperty( * @return NC_SUCCESS if the operation was successful, otherwise an error code. Use NCParseErrorCode to * the error code and positional argument that caused the error. */ -NC_EXPORT NCResult NCSetEncryptionPropertyEx( +NC_EXPORT NCResult NCEncryptionSetPropertyEx( NCEncryptionArgs* args, uint32_t property, uint8_t* value, @@ -612,13 +611,20 @@ NC_EXPORT NCResult NCSetEncryptionPropertyEx( * @return NC_SUCCESS if the operation was successful, otherwise an error code. Use NCParseErrorCode to * the error code and positional argument that caused the error. */ -NC_EXPORT NCResult NCSetEncryptionData( +NC_EXPORT NCResult NCEncryptionSetData( NCEncryptionArgs* args, const uint8_t* input, uint8_t* output, uint32_t dataSize ); +/* +* Gets the size of the encryption nonce (iv) for the given encryption version +* @param version The encryption version to get the nonce size for +* @return The size of the nonce in bytes, or 0 if the version is not supported +*/ +NC_EXPORT uint32_t NCEncryptionGetIvSize(uint32_t version); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/include/noscryptutil.h b/include/noscryptutil.h index bd60c79..709f607 100644 --- a/include/noscryptutil.h +++ b/include/noscryptutil.h @@ -132,7 +132,7 @@ NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput( ); /* -* Sets a property on the encryption context. Equivalent to calling NCSetEncryptionPropertyEx +* Sets a property on the encryption context. Equivalent to calling NCEncryptionSetPropertyEx * @param ctx A valid pointer to an encryption context * @param property The property to set * @param value A pointer to the value to set @@ -176,6 +176,15 @@ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( const NCPublicKey* pk ); +/* +* Gets the size of the IV(nonce) required for the encryption context. +* @param encCtx A valid pointer to an initialized encryption context +* @return The size of the IV in bytes, or a negative error code if the context +* is invalid, or the version is not supported. Use NCParseErrorCode to get the error code +* and positional argument that caused the error. +*/ +NC_EXPORT NCResult NC_CC NCUtilCipherGetIvSize(const NCUtilCipherContext* encCtx); + #ifdef __cplusplus } #endif diff --git a/src/noscrypt.c b/src/noscrypt.c index deadca6..fededaf 100644 --- a/src/noscrypt.c +++ b/src/noscrypt.c @@ -310,7 +310,7 @@ static _nc_fn_inline NCResult _encryptNip44Ex( result = NC_SUCCESS; - ncSpanInitC(&nonceSpan, args->nonceData, NC_ENCRYPTION_NONCE_SIZE); + ncSpanInitC(&nonceSpan, args->nonceData, NC_NIP44_IV_SIZE); /* Message key will be derrived on every encryption call */ if (_getMessageKey(ck, nonceSpan, &messageKey) != CSTATUS_OK) @@ -344,13 +344,14 @@ static _nc_fn_inline NCResult _decryptNip44Ex(const NCContext* ctx, const struct struct message_key messageKey; const struct nc_expand_keys* cipherKeys; - DEBUG_ASSERT2(ctx != NULL, "Expected valid context") - DEBUG_ASSERT2(ck != NULL, "Expected valid conversation key") - DEBUG_ASSERT2(args != NULL, "Expected valid encryption args") + DEBUG_ASSERT2(ctx != NULL, "Expected valid context"); + DEBUG_ASSERT2(ck != NULL, "Expected valid conversation key"); + DEBUG_ASSERT2(args != NULL, "Expected valid encryption args"); + DEBUG_ASSERT(args->version == NC_ENC_VERSION_NIP44); result = NC_SUCCESS; - ncSpanInitC(&nonceSpan, args->nonceData, NC_ENCRYPTION_NONCE_SIZE); + ncSpanInitC(&nonceSpan, args->nonceData, NC_NIP44_IV_SIZE); if (_getMessageKey(ck, nonceSpan, &messageKey) != CSTATUS_OK) { @@ -401,7 +402,7 @@ static NCResult _verifyMacEx( DEBUG_ASSERT2(conversationKey != NULL, "Expected valid conversation key") DEBUG_ASSERT2(args != NULL, "Expected valid mac verification args") - ncSpanInitC(&nonceSpan, args->nonce32, NC_ENCRYPTION_NONCE_SIZE); + ncSpanInitC(&nonceSpan, args->nonce32, NC_NIP44_IV_SIZE); ncSpanInitC(&payloadSpan, args->payload, args->payloadSize); /* @@ -979,7 +980,7 @@ Cleanup: return result; } -NC_EXPORT NCResult NCComputeMac( +NC_EXPORT NCResult NC_CC NCComputeMac( const NCContext* ctx, const uint8_t hmacKey[NC_HMAC_KEY_SIZE], const uint8_t* payload, @@ -1069,13 +1070,14 @@ Cleanup: #define ENSURE_ENC_MODE(args, mode) if(args->version != mode) return E_VERSION_NOT_SUPPORTED; -NC_EXPORT NCResult NCSetEncryptionPropertyEx( +NC_EXPORT NCResult NC_CC NCEncryptionSetPropertyEx( NCEncryptionArgs* args, uint32_t property, uint8_t* value, uint32_t valueLen ) { + uint32_t ivSize; CHECK_NULL_ARG(args, 0) CHECK_NULL_ARG(value, 2) @@ -1091,22 +1093,6 @@ NC_EXPORT NCResult NCSetEncryptionPropertyEx( return NC_SUCCESS; - case NC_ENC_SET_NIP04_IV: - /* - * The safest way to store the nip04 IV is in the nonce - * field. An IV is essentially a nonce. A secure random - * number used to encrypt the first block of a CBC chain. - */ - - CHECK_ARG_RANGE(valueLen, AES_IV_SIZE, UINT32_MAX, 3) - - ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP04) - - args->nonceData = value; - - return NC_SUCCESS; - - case NC_ENC_SET_NIP04_KEY: /* * The AES key is stored in the hmac key field, since @@ -1122,13 +1108,17 @@ NC_EXPORT NCResult NCSetEncryptionPropertyEx( return NC_SUCCESS; - case NC_ENC_SET_NIP44_NONCE: + case NC_ENC_SET_IV: + + ivSize = NCEncryptionGetIvSize(args->version); - /* Nonce buffer must be at least the size, max doesnt matter */ - CHECK_ARG_RANGE(valueLen, NC_ENCRYPTION_NONCE_SIZE, UINT32_MAX, 3) + /* Gaurd invalid version */ + if (ivSize == 0) + { + return E_VERSION_NOT_SUPPORTED; + } - /* Nonce is only used in nip44 mode */ - ENSURE_ENC_MODE(args, NC_ENC_VERSION_NIP44) + CHECK_ARG_RANGE(valueLen, ivSize, ivSize, 3) args->nonceData = value; @@ -1155,13 +1145,13 @@ NC_EXPORT NCResult NCSetEncryptionPropertyEx( return E_INVALID_ARG; } -NC_EXPORT NCResult NCSetEncryptionProperty( +NC_EXPORT NCResult NC_CC NCEncryptionSetProperty( NCEncryptionArgs* args, uint32_t property, uint32_t value ) { - return NCSetEncryptionPropertyEx( + return NCEncryptionSetPropertyEx( args, property, (uint8_t*)&value, @@ -1169,7 +1159,7 @@ NC_EXPORT NCResult NCSetEncryptionProperty( ); } -NC_EXPORT NCResult NCSetEncryptionData( +NC_EXPORT NCResult NC_CC NCEncryptionSetData( NCEncryptionArgs* args, const uint8_t* input, uint8_t* output, @@ -1186,4 +1176,19 @@ NC_EXPORT NCResult NCSetEncryptionData( args->dataSize = dataSize; return NC_SUCCESS; -} \ No newline at end of file +} + +NC_EXPORT uint32_t NC_CC NCEncryptionGetIvSize(uint32_t version) +{ + switch (version) + { + case NC_ENC_VERSION_NIP04: + return NC_NIP04_IV_SIZE; + + case NC_ENC_VERSION_NIP44: + return NC_NIP44_IV_SIZE; + + default: + return 0; + } +} diff --git a/src/noscryptutil.c b/src/noscryptutil.c index 4cee2c3..0d7a55c 100644 --- a/src/noscryptutil.c +++ b/src/noscryptutil.c @@ -37,6 +37,7 @@ #define MIN_PADDING_SIZE 0x20u #define NIP44_VERSION_SIZE 0x01u #define NIP44_PT_LEN_SIZE sizeof(uint16_t) +#define NIP44_NONCE_SIZE NC_NIP44_IV_SIZE /* * minimum size for a valid nip44 payload @@ -200,7 +201,7 @@ static _nc_fn_inline uint32_t _calcNip44TotalOutSize(uint32_t inputSize) bufferSize = NIP44_VERSION_SIZE; - bufferSize += NC_ENCRYPTION_NONCE_SIZE; + bufferSize += NIP44_NONCE_SIZE; bufferSize += NIP44_PT_LEN_SIZE; @@ -267,7 +268,7 @@ static _nc_fn_inline int _nip44ParseSegments( *nonce = ncSpanSliceC( payload, NIP44_VERSION_SIZE, - NC_ENCRYPTION_NONCE_SIZE + NIP44_NONCE_SIZE ); /* @@ -293,8 +294,8 @@ static _nc_fn_inline int _nip44ParseSegments( */ *cipherText = ncSpanSliceC( payload, - NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE, - payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE) + NIP44_VERSION_SIZE + NIP44_NONCE_SIZE, + payload.size - (NIP44_VERSION_SIZE + NIP44_NONCE_SIZE + NC_ENCRYPTION_MAC_SIZE) ); return 1; @@ -357,12 +358,23 @@ static NCResult _nip44EncryptCompleteCore( ZERO_FILL(hmacKeyOut, sizeof(hmacKeyOut)); + /* Get the nonce/iv size so we know how much nonce data to write */ + result = NCUtilCipherGetIvSize(state); + DEBUG_ASSERT(result > 0); + /* Start by appending the version number */ ncSpanAppend(message, &outPos, Nip44VersionValue, sizeof(Nip44VersionValue)); /* next is nonce data */ - ncSpanAppend(message, &outPos, encArgs.nonceData, NC_ENCRYPTION_NONCE_SIZE); - DEBUG_ASSERT(outPos == 1 + NC_ENCRYPTION_NONCE_SIZE); + ncSpanAppend(message, &outPos, encArgs.nonceData, (uint32_t)result); + + /* + * Assert the output points to the end of the nonce segment + * for nip44 this is exactly 33 bytes. This assert also doubles + * to check the output of NCUtilCipherGetIvSize() to ensure + * it's returning the correct size for nip44 + */ + DEBUG_ASSERT(outPos == 1 + NIP44_NONCE_SIZE); /* * Assign the hmac key from the stack buffer. Since the args structure @@ -372,7 +384,7 @@ static NCResult _nip44EncryptCompleteCore( * addresses. */ - result = NCSetEncryptionPropertyEx( + result = NCEncryptionSetPropertyEx( &encArgs, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, @@ -397,7 +409,7 @@ static NCResult _nip44EncryptCompleteCore( * plainText size field. */ - result = NCSetEncryptionData( + result = NCEncryptionSetData( &encArgs, ncSpanGetOffset(message, outPos), /* in place encryption */ ncSpanGetOffset(message, outPos), @@ -517,7 +529,7 @@ static NCResult _nip44DecryptCompleteCore( if ((state->_flags & NC_UTIL_CIPHER_MAC_NO_VERIFY) == 0) { DEBUG_ASSERT(ncSpanGetSizeC(macValue) == NC_ENCRYPTION_MAC_SIZE); - DEBUG_ASSERT(ncSpanGetSizeC(macData) > NC_ENCRYPTION_NONCE_SIZE + MIN_PADDING_SIZE); + DEBUG_ASSERT(ncSpanGetSizeC(macData) > NIP44_NONCE_SIZE + MIN_PADDING_SIZE); /* Assign the mac data to the mac verify args */ macArgs.mac32 = ncSpanGetOffsetC(macValue, 0); @@ -550,7 +562,7 @@ static NCResult _nip44DecryptCompleteCore( DEBUG_ASSERT2(cipherText.size >= MIN_PADDING_SIZE, "Cipertext segment was parsed incorrectly. Too small"); - result = NCSetEncryptionData( + result = NCEncryptionSetData( &encArgs, ncSpanGetOffsetC(cipherText, 0), ncSpanGetOffset(output, 0), /*decrypt ciphertext and write directly to the output buffer */ @@ -852,7 +864,7 @@ NC_EXPORT NCResult NCUtilCipherSetProperty( CHECK_NULL_ARG(ctx, 0) /* All other arguments are verified */ - return NCSetEncryptionPropertyEx( + return NCEncryptionSetPropertyEx( &ctx->encArgs, property, value, @@ -908,3 +920,16 @@ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate( return E_VERSION_NOT_SUPPORTED; } } + +NC_EXPORT NCResult NC_CC NCUtilCipherGetIvSize(const NCUtilCipherContext* encCtx) +{ + uint32_t ivSize; + + CHECK_NULL_ARG(encCtx, 0); + + ivSize = NCEncryptionGetIvSize(encCtx->encArgs.version); + + return ivSize == 0 + ? E_VERSION_NOT_SUPPORTED + : (NCResult)ivSize; +} diff --git a/tests/test.c b/tests/test.c index b4cdef1..e8b064b 100644 --- a/tests/test.c +++ b/tests/test.c @@ -284,74 +284,78 @@ static int TestPublicApiArgumentValidation() NCSecretKey secKey; NCPublicKey pubKey; uint8_t hmacKeyOut[NC_HMAC_KEY_SIZE]; - uint8_t nonce[NC_ENCRYPTION_NONCE_SIZE]; + uint8_t nonce[NC_NIP44_IV_SIZE]; NCEncryptionArgs cryptoData; PRINTL("TEST: Public API argument validation tests") { + TEST(NCEncryptionGetIvSize(NC_ENC_VERSION_NIP44), sizeof(nonce)); + /* * Test arguments for encryption properties */ uint8_t testBuff32[32]; - TEST(NCSetEncryptionProperty(NULL, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44), ARG_ERROR_POS_0) - TEST(NCSetEncryptionProperty(&cryptoData, 0, 1), E_INVALID_ARG) + TEST(NCEncryptionSetProperty(NULL, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP04), ARG_ERROR_POS_0) + TEST(NCEncryptionSetProperty(&cryptoData, 0, 1), E_INVALID_ARG) - TEST(NCSetEncryptionData(NULL, zero32, sig64, sizeof(zero32)), ARG_ERROR_POS_0) - TEST(NCSetEncryptionData(&cryptoData, NULL, sig64, sizeof(zero32)), ARG_ERROR_POS_1) - TEST(NCSetEncryptionData(&cryptoData, zero32, NULL, sizeof(zero32)), ARG_ERROR_POS_2) - TEST(NCSetEncryptionData(&cryptoData, zero32, sig64, 0), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetData(NULL, zero32, sig64, sizeof(zero32)), ARG_ERROR_POS_0) + TEST(NCEncryptionSetData(&cryptoData, NULL, sig64, sizeof(zero32)), ARG_ERROR_POS_1) + TEST(NCEncryptionSetData(&cryptoData, zero32, NULL, sizeof(zero32)), ARG_ERROR_POS_2) + TEST(NCEncryptionSetData(&cryptoData, zero32, sig64, 0), ARG_RANGE_ERROR_POS_3) - /* Setting any version specific value should fail */ - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, sizeof(nonce)), E_VERSION_NOT_SUPPORTED) + /* Setting the IV should fail because a version is not set*/ + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, nonce, sizeof(nonce)), E_VERSION_NOT_SUPPORTED); /* Set to nip44 to continue nip44 tests */ - TEST(NCSetEncryptionProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44), NC_SUCCESS) - - TEST(NCSetEncryptionPropertyEx(&cryptoData, 0, nonce, sizeof(nonce)), E_INVALID_ARG) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, NULL, sizeof(nonce)), ARG_ERROR_POS_2) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, 0), ARG_RANGE_ERROR_POS_3) - /* Nonce size should fail if smaller than the required nonce size */ - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, NC_ENCRYPTION_NONCE_SIZE - 1), ARG_RANGE_ERROR_POS_3) - - TEST(NCSetEncryptionPropertyEx(&cryptoData, 0, hmacKeyOut, sizeof(hmacKeyOut)), E_INVALID_ARG) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, NULL, sizeof(hmacKeyOut)), ARG_ERROR_POS_2) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, 0), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44), NC_SUCCESS) + + TEST(NCEncryptionSetPropertyEx(&cryptoData, 0, nonce, sizeof(nonce)), E_INVALID_ARG) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, NULL, sizeof(nonce)), ARG_ERROR_POS_2) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, nonce, 0), ARG_RANGE_ERROR_POS_3) + /* Nonce size should fail if not exactly the required nonce size */ + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, nonce, NC_NIP44_IV_SIZE - 1), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, nonce, NC_NIP44_IV_SIZE + 1), ARG_RANGE_ERROR_POS_3) + + TEST(NCEncryptionSetPropertyEx(&cryptoData, 0, hmacKeyOut, sizeof(hmacKeyOut)), E_INVALID_ARG) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, NULL, sizeof(hmacKeyOut)), ARG_ERROR_POS_2) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, 0), ARG_RANGE_ERROR_POS_3) /* Key size should fail if smaller than the required nip44 key size */ - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, NC_HMAC_KEY_SIZE - 1), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, NC_HMAC_KEY_SIZE - 1), ARG_RANGE_ERROR_POS_3) + /* Test for nip04 */ /* Any nip04 specific properties should fail since nip44 has already been set */ - - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_IV, testBuff32, sizeof(testBuff32)), E_VERSION_NOT_SUPPORTED) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, sizeof(testBuff32)), E_VERSION_NOT_SUPPORTED) + + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, sizeof(testBuff32)), E_VERSION_NOT_SUPPORTED) /* Set to nip04 to continue nip04 tests */ - ENSURE(NCSetEncryptionProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP04) == NC_SUCCESS) + ENSURE(NCEncryptionSetProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP04) == NC_SUCCESS) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_IV, NULL, sizeof(testBuff32)), ARG_ERROR_POS_2) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_IV, testBuff32, 0), ARG_RANGE_ERROR_POS_3) - /* IV size should fail if smaller than IV */ - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_IV, testBuff32, NC_NIP04_AES_IV_SIZE - 1), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, NULL, sizeof(testBuff32)), ARG_ERROR_POS_2) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, testBuff32, 0), ARG_RANGE_ERROR_POS_3) + /* IV size should fail if not exact size IV for the version */ + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, testBuff32, NC_NIP04_IV_SIZE - 1), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, testBuff32, NC_NIP04_IV_SIZE + 1), ARG_RANGE_ERROR_POS_3) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, NULL, sizeof(testBuff32)), ARG_ERROR_POS_2) - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, 0), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, NULL, sizeof(testBuff32)), ARG_ERROR_POS_2) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, 0), ARG_RANGE_ERROR_POS_3) /* Key size should fail if smaller than the required nip04 key size */ - TEST(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, NC_NIP04_AES_KEY_SIZE - 1), ARG_RANGE_ERROR_POS_3) + TEST(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP04_KEY, testBuff32, NC_NIP04_AES_KEY_SIZE - 1), ARG_RANGE_ERROR_POS_3) } /* Prep the crypto structure for proper usage */ - ENSURE(NCSetEncryptionProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44) == NC_SUCCESS); - ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, sizeof(nonce)) == NC_SUCCESS); - ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, sizeof(hmacKeyOut)) == NC_SUCCESS); + ENSURE(NCEncryptionSetProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44) == NC_SUCCESS); + ENSURE(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, nonce, sizeof(nonce)) == NC_SUCCESS); + ENSURE(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, sizeof(hmacKeyOut)) == NC_SUCCESS); /* Assign the encryption material */ - ENSURE(NCSetEncryptionData(&cryptoData, zero32, sig64, sizeof(zero32)) == NC_SUCCESS); + ENSURE(NCEncryptionSetData(&cryptoData, zero32, sig64, sizeof(zero32)) == NC_SUCCESS); FillRandomData(ctxRandom, 32); @@ -543,7 +547,7 @@ static int TestCorrectEncryption(const NCContext* context) NCPublicKey pubKey2; uint8_t hmacKeyOut[NC_HMAC_KEY_SIZE]; - uint8_t nonce[NC_ENCRYPTION_NONCE_SIZE]; + uint8_t nonce[NC_NIP44_IV_SIZE]; //nonce is set by cipher spec, shoud use NCEncryptionGetIvSize() in production uint8_t mac[NC_ENCRYPTION_MAC_SIZE]; uint8_t plainText[TEST_ENC_DATA_SIZE]; @@ -555,12 +559,13 @@ static int TestCorrectEncryption(const NCContext* context) PRINTL("TEST: Correct encryption") - ENSURE(NCSetEncryptionProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44) == NC_SUCCESS); - ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_NONCE, nonce, sizeof(nonce)) == NC_SUCCESS); - ENSURE(NCSetEncryptionPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, NC_HMAC_KEY_SIZE) == NC_SUCCESS); + ENSURE(NCEncryptionGetIvSize(NC_ENC_VERSION_NIP44) == (uint32_t)sizeof(nonce)); + ENSURE(NCEncryptionSetProperty(&cryptoData, NC_ENC_SET_VERSION, NC_ENC_VERSION_NIP44) == NC_SUCCESS); + ENSURE(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_IV, nonce, sizeof(nonce)) == NC_SUCCESS); + ENSURE(NCEncryptionSetPropertyEx(&cryptoData, NC_ENC_SET_NIP44_MAC_KEY, hmacKeyOut, NC_HMAC_KEY_SIZE) == NC_SUCCESS); /* Assign the encryption material */ - ENSURE(NCSetEncryptionData(&cryptoData, plainText, cipherText, TEST_ENC_DATA_SIZE) == NC_SUCCESS); + ENSURE(NCEncryptionSetData(&cryptoData, plainText, cipherText, TEST_ENC_DATA_SIZE) == NC_SUCCESS); macVerifyArgs.nonce32 = nonce; /* nonce is shared */ macVerifyArgs.mac32 = mac; @@ -634,10 +639,12 @@ static int TestUtilNip44Encryption( ENSURE(ctx != NULL); + TEST(ncSpanGetSize(nonce), (uint32_t)NCUtilCipherGetIvSize(ctx)); + TEST(NCUtilCipherInit(ctx, plainText.data, plainText.size), NC_SUCCESS); /* Nonce is required for nip44 encryption */ - TEST(NCUtilCipherSetProperty(ctx, NC_ENC_SET_NIP44_NONCE, nonce.data, nonce.size), NC_SUCCESS); + TEST(NCUtilCipherSetProperty(ctx, NC_ENC_SET_IV, nonce.data, nonce.size), NC_SUCCESS); /* Cipher update should return the */ TEST(NCUtilCipherUpdate(ctx, libCtx, NCByteCastToSecretKey(sendKey.data), &recvPubKey), NC_SUCCESS); @@ -650,7 +657,7 @@ static int TestUtilNip44Encryption( TASSERT(outData != NULL); /* Read the encrypted payload to test */ - TEST(NCUtilCipherReadOutput(ctx, outData, cipherOutputSize), cipherOutputSize); + TEST(NCUtilCipherReadOutput(ctx, outData, (uint32_t)cipherOutputSize), cipherOutputSize); /* Ensure encrypted payload matches */ TEST(memcmp(outData, expected.data, cipherOutputSize), 0); @@ -699,7 +706,7 @@ static int TestUtilNip44Decryption( TASSERT(outData != NULL); /* Read the encrypted payload to test */ - TEST(NCUtilCipherReadOutput(ctx, outData, plaintextSize), plaintextSize); + TEST(NCUtilCipherReadOutput(ctx, outData, (uint32_t)plaintextSize), plaintextSize); /* Ensure encrypted payload matches */ TEST(memcmp(outData, expectedPt.data, plaintextSize), 0); @@ -729,7 +736,7 @@ static int TestUtilFunctions(const NCContext* libCtx) /* From the nip44 vectors file */ span_t sendKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", sizeof(NCSecretKey)); span_t recvKey = FromHexString("0000000000000000000000000000000000000000000000000000000000000002", sizeof(NCSecretKey)); - span_t nonce = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", NC_ENCRYPTION_NONCE_SIZE); + span_t nonce = FromHexString("0000000000000000000000000000000000000000000000000000000000000001", NC_NIP44_IV_SIZE); span_t payload = FromHexString("02000000000000000000000000000000000000000000000000000000000000000179ed06e5548ad3ff58ca920e6c0b4329f6040230f7e6e5641f20741780f0adc35a09794259929a02bb06ad8e8cf709ee4ccc567e9d514cdf5781af27a3e905e55b1b", 99); span_t plainText = FromHexString("61", 1); -- cgit