/Users/alexjokela/projects/lattice/src/crypto_ops.c
Line | Count | Source |
1 | | #include "crypto_ops.h" |
2 | | #include <stdlib.h> |
3 | | #include <string.h> |
4 | | #include <stdio.h> |
5 | | |
6 | | /* ══════════════════════════════════════════════════════════════════════ |
7 | | * SHA-256 and MD5 — require OpenSSL (EVP API) |
8 | | * ══════════════════════════════════════════════════════════════════════ */ |
9 | | |
10 | | #ifdef LATTICE_HAS_TLS |
11 | | |
12 | | #include <openssl/evp.h> |
13 | | |
14 | 12 | static char *hex_encode(const unsigned char *hash, unsigned int len) { |
15 | 12 | char *hex = malloc(len * 2 + 1); |
16 | 300 | for (unsigned int i = 0; i < len; i++) { |
17 | 288 | snprintf(hex + i * 2, 3, "%02x", hash[i]); |
18 | 288 | } |
19 | 12 | hex[len * 2] = '\0'; |
20 | 12 | return hex; |
21 | 12 | } |
22 | | |
23 | 6 | char *crypto_sha256(const char *data, size_t len, char **err) { |
24 | 6 | (void)err; |
25 | 6 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); |
26 | 6 | if (!ctx) { |
27 | 0 | *err = strdup("sha256: failed to create digest context"); |
28 | 0 | return NULL; |
29 | 0 | } |
30 | 6 | unsigned char hash[EVP_MAX_MD_SIZE]; |
31 | 6 | unsigned int hash_len = 0; |
32 | | |
33 | 6 | EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); |
34 | 6 | EVP_DigestUpdate(ctx, data, len); |
35 | 6 | EVP_DigestFinal_ex(ctx, hash, &hash_len); |
36 | 6 | EVP_MD_CTX_free(ctx); |
37 | | |
38 | 6 | return hex_encode(hash, hash_len); |
39 | 6 | } |
40 | | |
41 | 6 | char *crypto_md5(const char *data, size_t len, char **err) { |
42 | 6 | (void)err; |
43 | 6 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); |
44 | 6 | if (!ctx) { |
45 | 0 | *err = strdup("md5: failed to create digest context"); |
46 | 0 | return NULL; |
47 | 0 | } |
48 | 6 | unsigned char hash[EVP_MAX_MD_SIZE]; |
49 | 6 | unsigned int hash_len = 0; |
50 | | |
51 | 6 | EVP_DigestInit_ex(ctx, EVP_md5(), NULL); |
52 | 6 | EVP_DigestUpdate(ctx, data, len); |
53 | 6 | EVP_DigestFinal_ex(ctx, hash, &hash_len); |
54 | 6 | EVP_MD_CTX_free(ctx); |
55 | | |
56 | 6 | return hex_encode(hash, hash_len); |
57 | 6 | } |
58 | | |
59 | | #else /* !LATTICE_HAS_TLS */ |
60 | | |
61 | | char *crypto_sha256(const char *data, size_t len, char **err) { |
62 | | (void)data; (void)len; |
63 | | *err = strdup("sha256: not available (built without OpenSSL)"); |
64 | | return NULL; |
65 | | } |
66 | | |
67 | | char *crypto_md5(const char *data, size_t len, char **err) { |
68 | | (void)data; (void)len; |
69 | | *err = strdup("md5: not available (built without OpenSSL)"); |
70 | | return NULL; |
71 | | } |
72 | | |
73 | | #endif /* LATTICE_HAS_TLS */ |
74 | | |
75 | | |
76 | | /* ══════════════════════════════════════════════════════════════════════ |
77 | | * Base64 encode/decode — pure C, always available |
78 | | * ══════════════════════════════════════════════════════════════════════ */ |
79 | | |
80 | | static const char b64_table[] = |
81 | | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
82 | | |
83 | 24 | char *crypto_base64_encode(const char *data, size_t len) { |
84 | 24 | size_t out_len = 4 * ((len + 2) / 3); |
85 | 24 | char *out = malloc(out_len + 1); |
86 | 24 | size_t j = 0; |
87 | | |
88 | 66 | for (size_t i = 0; i < len; i += 3) { |
89 | 42 | unsigned int n = ((unsigned char)data[i]) << 16; |
90 | 42 | if (i + 1 < len) n |= ((unsigned char)data[i + 1]) << 8; |
91 | 42 | if (i + 2 < len) n |= ((unsigned char)data[i + 2]); |
92 | | |
93 | 42 | out[j++] = b64_table[(n >> 18) & 0x3F]; |
94 | 42 | out[j++] = b64_table[(n >> 12) & 0x3F]; |
95 | 42 | out[j++] = (i + 1 < len) ? b64_table[(n >> 6) & 0x3F] : '='; |
96 | 42 | out[j++] = (i + 2 < len) ? b64_table[n & 0x3F] : '='; |
97 | 42 | } |
98 | | |
99 | 24 | out[j] = '\0'; |
100 | 24 | return out; |
101 | 24 | } |
102 | | |
103 | | /* Decode table: maps ASCII byte -> 6-bit value, or -1 for invalid, -2 for padding */ |
104 | 108 | static int b64_decode_char(unsigned char c) { |
105 | 108 | if (c >= 'A' && c <= 'Z') return c - 'A'; |
106 | 63 | if (c >= 'a' && c <= 'z') return c - 'a' + 26; |
107 | 21 | if (c >= '0' && c <= '9') return c - '0' + 52; |
108 | 15 | if (c == '+') return 62; |
109 | 15 | if (c == '/') return 63; |
110 | 15 | if (c == '=') return -2; |
111 | 0 | return -1; |
112 | 15 | } |
113 | | |
114 | 15 | char *crypto_base64_decode(const char *data, size_t len, size_t *out_len, char **err) { |
115 | | /* Skip trailing whitespace/newlines */ |
116 | 15 | while (len > 0 && (data[len - 1] == '\n' || data[len - 1] == '\r' || |
117 | 12 | data[len - 1] == ' ' || data[len - 1] == '\t')) { |
118 | 0 | len--; |
119 | 0 | } |
120 | | |
121 | 15 | if (len == 0) { |
122 | 3 | char *out = malloc(1); |
123 | 3 | out[0] = '\0'; |
124 | 3 | *out_len = 0; |
125 | 3 | return out; |
126 | 3 | } |
127 | | |
128 | 12 | if (len % 4 != 0) { |
129 | 3 | *err = strdup("base64_decode: invalid input length (must be multiple of 4)"); |
130 | 3 | return NULL; |
131 | 3 | } |
132 | | |
133 | 9 | size_t max_out = (len / 4) * 3; |
134 | 9 | char *out = malloc(max_out + 1); |
135 | 9 | size_t j = 0; |
136 | | |
137 | 36 | for (size_t i = 0; i < len; i += 4) { |
138 | 27 | int a = b64_decode_char((unsigned char)data[i]); |
139 | 27 | int b = b64_decode_char((unsigned char)data[i + 1]); |
140 | 27 | int c = b64_decode_char((unsigned char)data[i + 2]); |
141 | 27 | int d = b64_decode_char((unsigned char)data[i + 3]); |
142 | | |
143 | | /* Check for invalid characters (but not padding) */ |
144 | 27 | if (a < 0 || b < 0 || (c < 0 && c != -2) || (d < 0 && d != -2)) { |
145 | 0 | free(out); |
146 | 0 | *err = strdup("base64_decode: invalid character in input"); |
147 | 0 | return NULL; |
148 | 0 | } |
149 | | |
150 | | /* Treat padding as 0 for the arithmetic */ |
151 | 27 | int cv = (c == -2) ? 0 : c; |
152 | 27 | int dv = (d == -2) ? 0 : d; |
153 | | |
154 | 27 | unsigned int n = ((unsigned int)a << 18) | ((unsigned int)b << 12) | |
155 | 27 | ((unsigned int)cv << 6) | (unsigned int)dv; |
156 | | |
157 | 27 | out[j++] = (char)((n >> 16) & 0xFF); |
158 | 27 | if (c != -2) out[j++] = (char)((n >> 8) & 0xFF); |
159 | 27 | if (d != -2) out[j++] = (char)(n & 0xFF); |
160 | 27 | } |
161 | | |
162 | 9 | out[j] = '\0'; |
163 | 9 | *out_len = j; |
164 | 9 | return out; |
165 | 9 | } |