diff options
Diffstat (limited to 'src/noscryptutil.c')
-rw-r--r-- | src/noscryptutil.c | 418 |
1 files changed, 418 insertions, 0 deletions
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 <noscryptutil.h> +#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 <stdlib.h> +#include <math.h> + +#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); +} |