diff --git a/CMakeLists.txt b/CMakeLists.txt index b443528..85b2586 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES src/string.c src/vector.c src/utility.c + src/crypto.c ) if ((${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")) diff --git a/docs/crypto.md b/docs/crypto.md new file mode 100644 index 0000000..cbcb855 --- /dev/null +++ b/docs/crypto.md @@ -0,0 +1,24 @@ +# crypto + +Cryptographic library + +## Functions + +### b64_encode + +Encodes an `unsigned char *` into base64. User is responsible for freeing the +`char *` that is returned. + +```c +char *b64_encode(const unsigned char *s, size_t sz); +``` + +### b64_decode + +Decodes a base64 encoded set of bytes into an `unsigned char *`. User is responsible +for freeing the `unsigned char *` that is returned. `decode_sz` is an optional argument +in case you need to know the size of the decoded data, otherwise you can just pass `NULL`. + +```c +unsigned char *b64_decode(const char *s, size_t sz, size_t *decode_sz); +``` diff --git a/include/lfcrypto.h b/include/lfcrypto.h new file mode 100644 index 0000000..a124336 --- /dev/null +++ b/include/lfcrypto.h @@ -0,0 +1,9 @@ +#ifndef LIBFLINT_CRYPTO_H +#define LIBFLINT_CRYPTO_H + +#include + +char *b64_encode(const unsigned char *s, size_t sz); +unsigned char *b64_decode(const char *s, size_t sz, size_t *decode_sz); + +#endif // LIBFLINT_CRYPTO_H diff --git a/mkdocs.yml b/mkdocs.yml index 38d8c8c..7717dd6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,7 @@ nav: - 'Modules': - 'Binary Tree': 'binarytree.md' - 'Boolean': 'bool.md' + - 'Crypto': 'crypto.md' - 'Input': 'input.md' - 'Linked List': 'linkedlist.md' - 'Math': 'math.md' diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..b7e5892 --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,202 @@ +#include +#include +#include + +#include "lfcrypto.h" + +#define B64_BUF_SZ (1024 * 64) + +static const char b64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +typedef struct { + char *ptr; + int count; +} b64_buf; + +static int new_b64_buf(b64_buf *b) { + b->ptr = malloc(B64_BUF_SZ); + if (b->ptr == NULL) { + return 1; + } + b->count = 1; + return 0; +} + +static int resize_b64_buf(b64_buf *b, size_t sz) { + if (sz > b->count * B64_BUF_SZ) { + while (sz > b->count * B64_BUF_SZ) { + b->count++; + } + b->ptr = realloc(b->ptr, B64_BUF_SZ * b->count); + if (b->ptr == NULL) { + return 1; + } + } + return 0; +} + +char *b64_encode(const unsigned char *s, size_t sz) { + int i = 0; + b64_buf encbuf; + size_t size = 0; + unsigned char buf[4]; + unsigned char tmp[3]; + + if (new_b64_buf(&encbuf) != 0) { + return NULL; + } + + while (sz--) { + tmp[i++] = *(s++); + if (i == 3) { + buf[0] = (tmp[0] & 0xfc) >> 2; + buf[1] = ((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4); + buf[2] = ((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6); + buf[3] = tmp[2] & 0x3f; + + if (resize_b64_buf(&encbuf, size + 4) != 0) { + free(encbuf.ptr); + return NULL; + } + + for (i = 0; i < 4; ++i) { + encbuf.ptr[size++] = b64_table[buf[i]]; + } + + i = 0; + } + } + + if (i > 0) { + for (int j = i; j < 3; ++j) { + tmp[j] = '\0'; + } + + buf[0] = (tmp[0] & 0xfc) >> 2; + buf[1] = ((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4); + buf[2] = ((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6); + buf[3] = tmp[2] & 0x3f; + + for (int j = 0; (j < i + 1); ++j) { + if (resize_b64_buf(&encbuf, size + 1) != 0) { + free(encbuf.ptr); + return NULL; + } + encbuf.ptr[size++] = b64_table[buf[j]]; + } + + while (i++ < 3) { + if (resize_b64_buf(&encbuf, size + 1) != 0) { + free(encbuf.ptr); + return NULL; + } + encbuf.ptr[size++] = '='; + } + } + + if (resize_b64_buf(&encbuf, size + 1) != 0) { + free(encbuf.ptr); + return NULL; + } + encbuf.ptr[size] = '\0'; + + return encbuf.ptr; +} + +unsigned char *b64_decode(const char *s, size_t sz, size_t *decode_sz) { + int i = 0, j = 0, l = 0; + size_t size = 0; + b64_buf decbuf; + unsigned char buf[3]; + unsigned char tmp[4]; + + if (new_b64_buf(&decbuf) != 0) { + return NULL; + } + + while (sz--) { + if (s[j] == '=') { + break; + } + // Not base64 characters + if (!isalnum(s[j]) || s[j] == '+' || s[j] == '/') { + break; + } + + tmp[i++] = s[j++]; + + if (i == 4) { + for (i = 0; i < 4; ++i) { + for (l = 0; l < 64; ++l) { + if (tmp[i] == b64_table[l]) { + tmp[i] = l; + break; + } + } + } + + buf[0] = (tmp[0] << 2) + ((tmp[1] & 0x30) >> 4); + buf[1] = ((tmp[1] & 0xf) << 4) + ((tmp[2] & 0x3c) >> 2); + buf[2] = ((tmp[2] & 0x3) << 6) + tmp[3]; + + if (resize_b64_buf(&decbuf, size + 3) != 0) { + free(decbuf.ptr); + return NULL; + } + + for (i = 0; i < 3; ++i) { + decbuf.ptr[size++] = buf[i]; + } + + i = 0; + } + } + + if (i > 0) { + for (j = i; j < 4; ++j) { + tmp[j] = '\0'; + } + + for (j = 0; j < 4; ++j) { + for (l = 0; l < 64; ++l) { + if (tmp[j] == b64_table[l]) { + tmp[j] = l; + break; + } + } + } + + buf[0] = (tmp[0] << 2) + ((tmp[1] & 0x30) >> 4); + buf[1] = ((tmp[1] & 0xf) << 4) + ((tmp[2] & 0x3c) >> 2); + buf[2] = ((tmp[2] & 0x3) << 6) + tmp[3]; + + if (resize_b64_buf(&decbuf, size + (i - 1)) != 0) { + free(decbuf.ptr); + return NULL; + } + for (j = 0; (j < i - 1); ++j) { + decbuf.ptr[size++] = buf[j]; + } + } + + if (resize_b64_buf(&decbuf, size + 1) != 0) { + free(decbuf.ptr); + return NULL; + } + decbuf.ptr[size] = '\0'; + + if (decode_sz != NULL) { + *decode_sz = size; + } + + return (unsigned char*)decbuf.ptr; +} diff --git a/tests/tests.c b/tests/tests.c index 04c2b95..a7164f6 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -10,6 +10,7 @@ #include "lfvector.h" #include "lfmath.h" #include "lfstring.h" +#include "lfcrypto.h" #if defined(__APPLE__) || defined(__MACH__) #include "lfmacos.h" @@ -308,6 +309,25 @@ void test_string() { printf("Passes all string tests\n"); } +void test_crypto() { + char *in = "BUTT"; + char *s = b64_encode(in, strlen(in)); + assert(strcmp(s, "QlVUVA==") == 0); + free(s); + + char *in2 = "a longer base64 test, apparently"; + s = b64_encode(in2, strlen(in2)); + assert(strcmp(s, "YSBsb25nZXIgYmFzZTY0IHRlc3QsIGFwcGFyZW50bHk=") == 0); + free(s); + + char *out2 = "YSBsb25nZXIgYmFzZTY0IHRlc3QsIGFwcGFyZW50bHk="; + size_t s_sz = 0; + s = (char *)b64_decode(out2, strlen(out2), &s_sz); + assert(strcmp(s, "a longer base64 test, apparently") == 0); + assert(strlen(s) == s_sz); + free(s); +} + #if defined(__APPLE__) || defined(__MACH__) void test_macos() { printf("\n--- macOS TEST ---\n"); @@ -331,6 +351,7 @@ int main() { test_math(); test_vector(); test_string(); + test_crypto(); #if defined(__APPLE__) || defined(__MACH__) test_macos();