aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-07-12 22:14:00 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-07-12 22:14:00 -0400
commit5dfafbc5a9214587533ec8b1dae2a962118d3650 (patch)
tree03bee9bd074962a65924b63e66612b95fb9e2546
parent8df8c5aed4ac626171b451b5422c3b207e88000b (diff)
feat: add decryption functionality to public api
-rw-r--r--include/noscryptutil.h26
-rw-r--r--src/nc-util.h14
-rw-r--r--src/noscrypt.c6
-rw-r--r--src/noscryptutil.c354
4 files changed, 357 insertions, 43 deletions
diff --git a/include/noscryptutil.h b/include/noscryptutil.h
index 7db5738..20384c4 100644
--- a/include/noscryptutil.h
+++ b/include/noscryptutil.h
@@ -34,11 +34,16 @@ extern "C" {
#include "noscrypt.h"
-#define E_OUT_OF_MEMORY -10
+#define E_OUT_OF_MEMORY -10
+
+#define E_CIPHER_INVALID_FORMAT -11
+#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
/*
* The encryption context structure. This structure is used to store the state
@@ -76,11 +81,11 @@ NC_EXPORT NCResult NC_CC NCUtilGetEncryptionBufferSize(uint32_t encVersion, uint
NC_EXPORT NCUtilCipherContext* NC_CC NCUtilCipherAlloc(uint32_t encVersion, uint32_t flags);
/*
-* 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
+* Initializes the cipher context with the input data and size. This function will
+ internally allocate a the required output buffer for the cipher 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 inputData A pointer to the input data for the Cipher
* @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
@@ -131,7 +136,7 @@ NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput(
* @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(
+NC_EXPORT NCResult NC_CC NCUtilCipherSetProperty(
NCUtilCipherContext* ctx,
uint32_t property,
uint8_t* value,
@@ -139,7 +144,16 @@ NC_EXPORT NCResult NCUtilCipherSetProperty(
);
/*
-* Performs the desired ciper option once. This may either cause an encryption
+* Gets the flags set on the encryption context during initialization.
+* @param ctx A valid pointer to an encryption context
+* @return The flags set on the encryption context cast to a NCResult, or
+* an error code if the context is invalid. Use NCParseErrorCode to get the error code
+* and positional argument that caused the error.
+*/
+NC_EXPORT NCResult NC_CC NCUtilCipherGetFlags(const NCUtilCipherContext* ctx);
+
+/*
+* Performs the desired Cipher 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
diff --git a/src/nc-util.h b/src/nc-util.h
index e94a222..d0afe28 100644
--- a/src/nc-util.h
+++ b/src/nc-util.h
@@ -151,4 +151,18 @@ static _nc_fn_inline span_t ncSpanSlice(span_t span, uint32_t offset, uint32_t s
return slice;
}
+static _nc_fn_inline cspan_t ncSpanSliceC(cspan_t span, uint32_t offset, uint32_t size)
+{
+ cspan_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 */
+ ncSpanInitC(&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 01ec136..f3c28cb 100644
--- a/src/noscrypt.c
+++ b/src/noscrypt.c
@@ -843,8 +843,6 @@ NC_EXPORT NCResult NC_CC NCEncrypt(
CHECK_INVALID_ARG(args->inputData, 3)
CHECK_INVALID_ARG(args->outputData, 3)
CHECK_INVALID_ARG(args->nonceData, 3)
- CHECK_INVALID_ARG(args->keyData, 3)
- CHECK_ARG_RANGE(args->dataSize, NIP44_MIN_ENC_MESSAGE_SIZE, NIP44_MAX_ENC_MESSAGE_SIZE, 3)
result = E_OPERATION_FAILED;
@@ -852,6 +850,10 @@ NC_EXPORT NCResult NC_CC NCEncrypt(
{
case NC_ENC_VERSION_NIP44:
{
+ /* Mac key output is only needed for nip44 */
+ CHECK_INVALID_ARG(args->keyData, 3)
+ CHECK_ARG_RANGE(args->dataSize, NIP44_MIN_ENC_MESSAGE_SIZE, NIP44_MAX_ENC_MESSAGE_SIZE, 3)
+
/* Compute the shared point */
if ((result = _computeSharedSecret(ctx, sk, pk, &sharedSecret)) != NC_SUCCESS)
{
diff --git a/src/noscryptutil.c b/src/noscryptutil.c
index c0eb036..09b7c92 100644
--- a/src/noscryptutil.c
+++ b/src/noscryptutil.c
@@ -60,7 +60,12 @@
#define NIP44_VERSION_SIZE 0x01u
#define NIP44_PT_LEN_SIZE sizeof(uint16_t)
-#define NC_ENC_FLAG_MODE_MASK 0x01ui32
+/*
+* 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)
+
/* Currently were on nip44 version 2 */
@@ -171,7 +176,7 @@ static _nc_fn_inline uint32_t _calcNip44TotalOutSize(uint32_t inputSize)
return bufferSize;
}
-static _nc_fn_inline span_t _nip44GetMacData(span_t payload)
+static _nc_fn_inline cspan_t _nip44GetMacData(cspan_t payload)
{
DEBUG_ASSERT(payload.size > NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE);
@@ -189,7 +194,7 @@ static _nc_fn_inline span_t _nip44GetMacData(span_t payload)
* macData = ct.size - version.size + mac.size
*/
- return ncSpanSlice(
+ return ncSpanSliceC(
payload,
NIP44_VERSION_SIZE,
payload.size - (NIP44_VERSION_SIZE + NC_ENCRYPTION_MAC_SIZE)
@@ -210,6 +215,47 @@ 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);
+
+ /*
+ * Mac is the final 32 bytes of the ciphertext buffer
+ */
+ return 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);
+
+ /* slice after the nonce and before the mac segments */
+ return ncSpanSliceC(
+ payload,
+ NIP44_VERSION_SIZE + NC_ENCRYPTION_NONCE_SIZE,
+ payload.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);
+
+ /* slice after the version and before the mac segments */
+ nonceData = ncSpanSliceC(
+ payload,
+ NIP44_VERSION_SIZE,
+ NC_ENCRYPTION_NONCE_SIZE
+ );
+
+ return nonceData;
+}
static NCResult _nip44EncryptCompleteCore(
const NCContext* libContext,
@@ -222,20 +268,23 @@ static NCResult _nip44EncryptCompleteCore(
{
NCResult result;
- span_t macData, macOutput;
+ cspan_t macData, cPayload;
+ span_t macOutput;
uint32_t outPos, paddedCtSize;
- uint8_t ptSize[2];
+ uint8_t ptSize[NIP44_PT_LEN_SIZE];
uint8_t hmacKeyOut[NC_ENCRYPTION_MAC_SIZE];
outPos = 0;
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, 0x01);
+ ncSpanAppend(payload, &outPos, Nip44VersionValue, sizeof(Nip44VersionValue));
/* next is nonce data */
ncSpanAppend(payload, &outPos, encArgs.nonceData, NC_ENCRYPTION_NONCE_SIZE);
@@ -320,7 +369,7 @@ static NCResult _nip44EncryptCompleteCore(
this helper captures that data segment into a span
*/
- macData = _nip44GetMacData(payload);
+ macData = _nip44GetMacData(cPayload);
macOutput = _nip44GetMacOutput(payload);
result = NCComputeMac(
@@ -347,6 +396,107 @@ static NCResult _nip44EncryptCompleteCore(
return NC_SUCCESS;
}
+static NCResult _nip44DecryptCompleteCore(
+ const NCContext* libContext,
+ const NCSecretKey* recvKey,
+ const NCPublicKey* sendKey,
+ const NCUtilCipherContext* cipher
+)
+{
+ NCResult result;
+ NCMacVerifyArgs macArgs;
+ NCEncryptionArgs encArgs;
+ cspan_t macData, macValue, cipherText, nonce;
+
+ DEBUG_ASSERT(libContext && recvKey && sendKey && cipher);
+ DEBUG_ASSERT(cipher->encArgs.version == NC_ENC_VERSION_NIP44);
+
+ /* ensure decryption mode */
+ DEBUG_ASSERT(cipher->_flags & NC_UTIL_CIPHER_MODE_DECRYPT);
+
+ /* store local stack copy for safe mutation */
+ encArgs = cipher->encArgs;
+
+ nonce = _nip44ParseNonce(cipher->cipherInput);
+
+ /* 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
+ */
+
+ macData = _nip44GetMacData(cipher->cipherInput);
+ macValue = _nip44ParseMac(cipher->cipherInput);
+
+ DEBUG_ASSERT(macValue.size == NC_ENCRYPTION_MAC_SIZE);
+ DEBUG_ASSERT(macData.size > NC_ENCRYPTION_NONCE_SIZE + 0x20);
+
+ /* Assign the mac data to the mac verify args */
+ macArgs.mac32 = macValue.data;
+ macArgs.nonce32 = nonce.data;
+
+ /* payload for verifying a mac in nip44 is the nonce+ciphertext */
+ macArgs.payload = macData.data;
+ macArgs.payloadSize = macData.size;
+
+ /* Verify the mac */
+ result = NCVerifyMac(libContext, recvKey, sendKey, &macArgs);
+
+ /* When the mac is invlaid */
+ if (result == E_OPERATION_FAILED)
+ {
+ return E_CIPHER_MAC_INVALID;
+ }
+ /* argument errors */
+ else if (result != NC_SUCCESS)
+ {
+ return result;
+ }
+ }
+
+ cipherText = _nip44ParseCipherText(cipher->cipherInput);
+
+ DEBUG_ASSERT2(cipherText.size > 0x20, "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.
+ */
+
+ DEBUG_ASSERT(cipher->cipherOutput.size >= cipherText.size);
+
+ result = NCSetEncryptionData(
+ &encArgs,
+ cipherText.data,
+ cipher->cipherOutput.data,
+ cipherText.size
+ );
+
+ if (result != NC_SUCCESS)
+ {
+ return result;
+ }
+
+ /*
+ * If decryption was successful, the data should be written
+ * directly to the output buffer
+ */
+ result = NCDecrypt(libContext, recvKey, sendKey, &encArgs);
+
+ return result;
+}
NC_EXPORT NCResult NC_CC NCUtilGetEncryptionPaddedSize(uint32_t encVersion, uint32_t plaintextSize)
{
@@ -436,23 +586,65 @@ 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
- */
+ /* The output state must not have alraedy been allocated */
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, inputSize);
+ if ((encCtx->_flags & NC_UTIL_CIPHER_MODE_DECRYPT) > 0)
+ {
+ /*
+ * Validate the input data for proper format for
+ * the current cipher version
+ */
+ switch (encCtx->encArgs.version)
+ {
+ case NC_ENC_VERSION_NIP44:
+ {
+ if (inputSize < NIP44_MIN_PAYLOAD_SIZE)
+ {
+ return E_CIPHER_INVALID_FORMAT;
+ }
+
+ if (inputSize > NIP44_MAX_ENC_MESSAGE_SIZE)
+ {
+ return E_CIPHER_INVALID_FORMAT;
+ }
+
+ /* Ensure the first byte is a valid version */
+ if (inputData[0] != Nip44VersionValue[0])
+ {
+ return E_VERSION_NOT_SUPPORTED;
+ }
+
+ break;
+ }
+ default:
+ return E_VERSION_NOT_SUPPORTED;
+ }
+
+ /*
+ * Alloc a the output buffer to be the same size as the input
+ * data for decryption because the output will always be equal
+ * or smaller than the input data. This is an over-alloc but
+ * that should be fine
+ */
+
+ outputSize = inputSize;
+ }
+ else
+ {
+ /*
+ * Calculate the correct output size to store the encryption
+ * data for the given cipher version
+ */
+ outputSize = NCUtilGetEncryptionBufferSize(encCtx->encArgs.version, inputSize);
+ }
if (outputSize <= 0)
{
return outputSize;
}
- /*Alloc output buffer within the struct */
+ /* Alloc output buffer within the struct */
encCtx->cipherOutput = _ncUtilAllocSpan((uint32_t)outputSize, sizeof(uint8_t));
if (!encCtx->cipherOutput.data)
@@ -461,14 +653,60 @@ NC_EXPORT NCResult NC_CC NCUtilCipherInit(
}
ncSpanInitC(&encCtx->cipherInput, inputData, inputSize);
-
+
return NC_SUCCESS;
}
+NC_EXPORT NCResult NC_CC NCUtilCipherGetFlags(const NCUtilCipherContext* ctx)
+{
+ CHECK_NULL_ARG(ctx, 0);
+
+ return (NCResult)(ctx->_flags);
+}
+
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)
+ {
+
+ /* 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 (NCResult)(encCtx->cipherOutput.size);
}
@@ -478,21 +716,55 @@ NC_EXPORT NCResult NC_CC NCUtilCipherReadOutput(
uint32_t outputSize
)
{
+ NCResult result;
+
CHECK_NULL_ARG(encCtx, 0)
CHECK_NULL_ARG(output, 1)
- if (outputSize < encCtx->cipherOutput.size)
+ /*
+ * Again if in nip44 decrypt mode we only want the
+ * actual plaintext data
+ */
+
+ if (encCtx->encArgs.version == NC_ENC_VERSION_NIP44
+ && (encCtx->_flags & NC_UTIL_CIPHER_MODE_DECRYPT) > 0)
{
- return E_OPERATION_FAILED;
+ 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;
}
+ else
+ {
+ CHECK_ARG_RANGE(outputSize, encCtx->cipherOutput.size, UINT32_MAX, 2);
- MEMMOV(
- output,
- encCtx->cipherOutput.data,
- encCtx->cipherOutput.size
- );
+ MEMMOV(
+ output,
+ encCtx->cipherOutput.data,
+ encCtx->cipherOutput.size
+ );
- return (NCResult)encCtx->cipherOutput.size;
+ return (NCResult)encCtx->cipherOutput.size;
+ }
}
NC_EXPORT NCResult NCUtilCipherSetProperty(
@@ -501,8 +773,7 @@ NC_EXPORT NCResult NCUtilCipherSetProperty(
uint8_t* value,
uint32_t valueLen
)
-{
-
+{
CHECK_NULL_ARG(ctx, 0)
/* All other arguments are verified */
@@ -521,20 +792,37 @@ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate(
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;
+ /* Make sure input & output buffers have been assigned/allocated */
+ if (encCtx->cipherOutput.data == NULL)
+ {
+ return E_INVALID_CONTEXT;
+ }
+ if (encCtx->cipherInput.data == NULL)
+ {
+ return E_INVALID_CONTEXT;
+ }
switch (encCtx->encArgs.version)
{
case NC_ENC_VERSION_NIP44:
- if (mode == NC_UTIL_CIPHER_MODE_ENCRYPT)
+
+ if ((encCtx->_flags & NC_UTIL_CIPHER_MODE_DECRYPT) > 0)
{
+ return _nip44DecryptCompleteCore(libContext, sk, pk, encCtx);
+ }
+ else
+ {
+ /* Ensure the user manually specified a nonce buffer for encryption mode */
+ if (!encCtx->encArgs.nonceData)
+ {
+ return E_CIPHER_BAD_NONCE;
+ }
+
return _nip44EncryptCompleteCore(
libContext,
sk,
@@ -544,10 +832,6 @@ NC_EXPORT NCResult NC_CC NCUtilCipherUpdate(
encCtx->cipherOutput
);
}
- else
- {
- return E_VERSION_NOT_SUPPORTED;
- }
default:
return E_VERSION_NOT_SUPPORTED;