Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}