File Coverage

src/pdfmake_x509.c
Criterion Covered Total %
statement 201 463 43.4
branch 112 454 24.6
condition n/a
subroutine n/a
pod n/a
total 313 917 34.1


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_x509.c — X.509 certificate parsing implementation
3             *
4             * Parse X.509 certificates for PDF digital signatures.
5             */
6              
7             #include "pdfmake_x509.h"
8             #include "pdfmake_asn1.h"
9             #include "pdfmake_arena.h"
10             #include
11             #include
12             #include
13             #include
14             #include
15              
16             /*============================================================================
17             * Internal helpers
18             *==========================================================================*/
19              
20             /* Base64 decode table */
21             static const int8_t b64_table[256] = {
22             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
23             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
24             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
25             52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,
26             -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
27             15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
28             -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
29             41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
30             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
31             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
32             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
33             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
34             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
35             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
36             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
37             -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
38             };
39              
40             /* Decode base64 to binary */
41 0           static size_t base64_decode(
42             const char *src, size_t src_len,
43             uint8_t *dst, size_t dst_len)
44             {
45 0           size_t out = 0;
46 0           uint32_t accum = 0;
47 0           int bits = 0;
48             size_t i;
49              
50 0 0         for (i = 0; i < src_len && out < dst_len; i++) {
    0          
51 0           int8_t val = b64_table[(uint8_t)src[i]];
52 0 0         if (val == -1) continue; /* Skip whitespace */
53 0 0         if (val == -2) break; /* Padding '=' */
54            
55 0           accum = (accum << 6) | val;
56 0           bits += 6;
57            
58 0 0         if (bits >= 8) {
59 0           bits -= 8;
60 0           dst[out++] = (accum >> bits) & 0xFF;
61             }
62             }
63            
64 0           return out;
65             }
66              
67             /* Duplicate string to arena */
68             /* Convert bytes to hex string */
69 1           static char *bytes_to_hex(pdfmake_arena_t *arena, const uint8_t *data, size_t len)
70             {
71             char *hex;
72             char *p;
73             size_t i;
74              
75 1           hex = pdfmake_arena_alloc(arena, len * 3 + 1); /* "XX:" format */
76 1 50         if (!hex) return NULL;
77            
78 1           p = hex;
79 21 100         for (i = 0; i < len; i++) {
80 20 100         if (i > 0) *p++ = ':';
81 20           sprintf(p, "%02X", data[i]);
82 20           p += 2;
83             }
84 1           *p = '\0';
85            
86 1           return hex;
87             }
88              
89             /* Parse signature algorithm OID */
90 1           static pdfmake_sig_algorithm_t parse_sig_algorithm(const char *oid)
91             {
92 1 50         if (!oid) return PDFMAKE_SIG_UNKNOWN;
93            
94 1 50         if (strcmp(oid, OID_RSA_MD5) == 0) return PDFMAKE_SIG_RSA_MD5;
95 1 50         if (strcmp(oid, OID_RSA_SHA1) == 0) return PDFMAKE_SIG_RSA_SHA1;
96 1 50         if (strcmp(oid, OID_RSA_SHA256) == 0) return PDFMAKE_SIG_RSA_SHA256;
97 0 0         if (strcmp(oid, OID_RSA_SHA384) == 0) return PDFMAKE_SIG_RSA_SHA384;
98 0 0         if (strcmp(oid, OID_RSA_SHA512) == 0) return PDFMAKE_SIG_RSA_SHA512;
99 0 0         if (strcmp(oid, OID_ECDSA_SHA256) == 0) return PDFMAKE_SIG_ECDSA_SHA256;
100 0 0         if (strcmp(oid, OID_ECDSA_SHA384) == 0) return PDFMAKE_SIG_ECDSA_SHA384;
101 0 0         if (strcmp(oid, OID_ECDSA_SHA512) == 0) return PDFMAKE_SIG_ECDSA_SHA512;
102            
103 0           return PDFMAKE_SIG_UNKNOWN;
104             }
105              
106             /* Parse X.500 Name (subject/issuer) */
107 2           static void parse_name(
108             pdfmake_arena_t *arena,
109             const pdfmake_asn1_node_t *name_node,
110             pdfmake_x509_name_t *name)
111             {
112             char dn_buf[1024];
113             char *dn_p;
114             char *dn_end;
115             pdfmake_asn1_node_t *rdn;
116              
117 2 50         if (!pdfmake_asn1_is_sequence(name_node)) {
118 0           return;
119             }
120            
121             /* Name is SEQUENCE of RDN (RelativeDistinguishedName) */
122             /* Each RDN is a SET of AttributeTypeAndValue */
123             /* AttributeTypeAndValue is SEQUENCE { type OID, value ANY } */
124            
125             /* Build DN string while parsing */
126 2           dn_p = dn_buf;
127 2           dn_end = dn_buf + sizeof(dn_buf) - 1;
128            
129 2           rdn = name_node->children;
130 8 100         while (rdn) {
131 6 50         if (pdfmake_asn1_is_set(rdn)) {
132 6           pdfmake_asn1_node_t *atv = rdn->children;
133 12 100         while (atv) {
134 6 50         if (pdfmake_asn1_is_sequence(atv)) {
135 6           pdfmake_asn1_node_t *oid_node = pdfmake_asn1_child_at(atv, 0);
136 6           pdfmake_asn1_node_t *val_node = pdfmake_asn1_child_at(atv, 1);
137            
138 6 50         if (oid_node && val_node) {
    50          
139 6           char *oid = pdfmake_asn1_get_oid_string(arena, oid_node);
140 6           char *val = pdfmake_asn1_get_string(arena, val_node);
141            
142 6 50         if (oid && val) {
    50          
143 6           const char *attr_name = NULL;
144 6           char **target = NULL;
145            
146 6 100         if (strcmp(oid, OID_COMMON_NAME) == 0) {
147 2           attr_name = "CN";
148 2           target = &name->common_name;
149 4 100         } else if (strcmp(oid, OID_ORGANIZATION) == 0) {
150 2           attr_name = "O";
151 2           target = &name->organization;
152 2 50         } else if (strcmp(oid, OID_ORGANIZATIONAL_UNIT) == 0) {
153 0           attr_name = "OU";
154 0           target = &name->organizational_unit;
155 2 50         } else if (strcmp(oid, OID_COUNTRY) == 0) {
156 2           attr_name = "C";
157 2           target = &name->country;
158 0 0         } else if (strcmp(oid, OID_STATE) == 0) {
159 0           attr_name = "ST";
160 0           target = &name->state;
161 0 0         } else if (strcmp(oid, OID_LOCALITY) == 0) {
162 0           attr_name = "L";
163 0           target = &name->locality;
164 0 0         } else if (strcmp(oid, OID_EMAIL_ADDRESS) == 0) {
165 0           attr_name = "emailAddress";
166 0           target = &name->email;
167 0 0         } else if (strcmp(oid, OID_SERIAL_NUMBER) == 0) {
168 0           attr_name = "serialNumber";
169 0           target = &name->serial_number;
170             }
171            
172 6 50         if (target) {
173 6           *target = val;
174             }
175            
176             /* Append to DN string */
177 6 50         if (attr_name && dn_p < dn_end) {
    50          
178 6 100         if (dn_p > dn_buf) {
179 4           dn_p += snprintf(dn_p, dn_end - dn_p, ", ");
180             }
181 6           dn_p += snprintf(dn_p, dn_end - dn_p, "%s=%s", attr_name, val);
182             }
183             }
184             }
185             }
186 6           atv = atv->next;
187             }
188             }
189 6           rdn = rdn->next;
190             }
191            
192 2           *dn_p = '\0';
193 2           name->dn = pdfmake_arena_strdup(arena, dn_buf);
194             }
195              
196             /* Parse AlgorithmIdentifier */
197 1           static char *parse_algorithm_id(
198             pdfmake_arena_t *arena,
199             const pdfmake_asn1_node_t *alg_node)
200             {
201             pdfmake_asn1_node_t *oid_node;
202              
203 1 50         if (!pdfmake_asn1_is_sequence(alg_node)) {
204 0           return NULL;
205             }
206            
207 1           oid_node = pdfmake_asn1_child_at(alg_node, 0);
208 1 50         if (!oid_node) return NULL;
209            
210 1           return pdfmake_asn1_get_oid_string(arena, oid_node);
211             }
212              
213             /* Parse SubjectPublicKeyInfo */
214 1           static void parse_pubkey_info(
215             pdfmake_arena_t *arena,
216             const pdfmake_asn1_node_t *spki,
217             pdfmake_pubkey_t *pubkey)
218             {
219             pdfmake_asn1_node_t *alg_node;
220             pdfmake_asn1_node_t *key_node;
221             pdfmake_asn1_node_t *oid_node;
222             char *oid;
223             const uint8_t *bits;
224             size_t bit_count;
225             size_t pos;
226             pdfmake_asn1_node_t *rsa_key;
227             pdfmake_asn1_node_t *param;
228              
229 1 50         if (!pdfmake_asn1_is_sequence(spki)) {
230 0           return;
231             }
232            
233             /* SubjectPublicKeyInfo ::= SEQUENCE {
234             algorithm AlgorithmIdentifier,
235             subjectPublicKey BIT STRING } */
236            
237 1           alg_node = pdfmake_asn1_child_at(spki, 0);
238 1           key_node = pdfmake_asn1_child_at(spki, 1);
239            
240 1 50         if (!alg_node || !key_node) return;
    50          
241            
242             /* Store raw for signature verification */
243 1           pubkey->raw = spki->data;
244 1           pubkey->raw_len = spki->length;
245            
246             /* Parse algorithm OID */
247 1           oid_node = pdfmake_asn1_child_at(alg_node, 0);
248 1 50         if (!oid_node) return;
249            
250 1           oid = pdfmake_asn1_get_oid_string(arena, oid_node);
251 1 50         if (!oid) return;
252            
253 1 50         if (pdfmake_asn1_get_bit_string(key_node, &bits, &bit_count) != 0) {
254 0           return;
255             }
256            
257 1 50         if (strcmp(oid, OID_RSA_ENCRYPTION) == 0) {
258 1           pubkey->algorithm = PDFMAKE_PK_RSA;
259            
260             /* RSA public key is DER-encoded in the bit string */
261             /* RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER } */
262 1           pos = 0;
263 1           rsa_key = pdfmake_asn1_parse_element(arena, bits, bit_count / 8, &pos);
264 1 50         if (pdfmake_asn1_is_sequence(rsa_key)) {
265 1           pdfmake_asn1_node_t *mod = pdfmake_asn1_child_at(rsa_key, 0);
266 1           pdfmake_asn1_node_t *exp = pdfmake_asn1_child_at(rsa_key, 1);
267            
268 1 50         if (mod && exp) {
    50          
269 1           pubkey->rsa.modulus = pdfmake_arena_memdup(arena, mod->data, mod->length);
270 1           pubkey->rsa.modulus_len = mod->length;
271 1           pubkey->rsa.exponent = pdfmake_arena_memdup(arena, exp->data, exp->length);
272 1           pubkey->rsa.exponent_len = exp->length;
273             }
274             }
275 0 0         } else if (strcmp(oid, OID_EC_PUBLIC_KEY) == 0) {
276 0           pubkey->algorithm = PDFMAKE_PK_ECDSA;
277            
278             /* Get curve OID from algorithm parameters */
279 0           param = pdfmake_asn1_child_at(alg_node, 1);
280 0 0         if (param && param->tag == ASN1_TAG_OID) {
    0          
281 0           pubkey->ecdsa.curve_oid = pdfmake_asn1_get_oid_string(arena, param);
282            
283             /* Determine curve size */
284 0 0         if (pdfmake_asn1_oid_equals(param, OID_SECP256R1)) {
285 0           pubkey->ecdsa.curve_bits = 256;
286 0 0         } else if (pdfmake_asn1_oid_equals(param, OID_SECP384R1)) {
287 0           pubkey->ecdsa.curve_bits = 384;
288 0 0         } else if (pdfmake_asn1_oid_equals(param, OID_SECP521R1)) {
289 0           pubkey->ecdsa.curve_bits = 521;
290             }
291             }
292            
293             /* EC point is the bit string content */
294 0           pubkey->ecdsa.point = pdfmake_arena_memdup(arena, bits, bit_count / 8);
295 0           pubkey->ecdsa.point_len = bit_count / 8;
296             }
297             }
298              
299             /* Parse extension value */
300 3           static void parse_extension(
301             pdfmake_arena_t *arena,
302             const pdfmake_asn1_node_t *ext,
303             pdfmake_x509_cert_t *cert)
304             {
305             pdfmake_asn1_node_t *oid_node;
306 3           pdfmake_asn1_node_t *value_node = NULL;
307             pdfmake_asn1_node_t *child;
308             size_t pos;
309             pdfmake_asn1_node_t *ext_value;
310              
311             /* Extension ::= SEQUENCE { extnID OID, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING } */
312            
313 3           oid_node = pdfmake_asn1_child_at(ext, 0);
314 3 50         if (!oid_node) return;
315            
316             /* Find the extension value (OCTET STRING, may be preceded by critical BOOLEAN) */
317 3           child = oid_node->next;
318 4 50         while (child) {
319 4 100         if (child->tag == ASN1_TAG_OCTET_STRING) {
320 3           value_node = child;
321 3           break;
322             }
323 1           child = child->next;
324             }
325 3 50         if (!value_node) return;
326            
327             /* Parse the OCTET STRING content as ASN.1 */
328 3           pos = 0;
329 3           ext_value = pdfmake_asn1_parse_element(arena, value_node->data, value_node->length, &pos);
330 3 50         if (!ext_value) return;
331            
332             /* Basic Constraints */
333 3 100         if (pdfmake_asn1_oid_equals(oid_node, OID_BASIC_CONSTRAINTS)) {
334             /* BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL } */
335 1           cert->path_len_constraint = -1;
336 1 50         if (pdfmake_asn1_is_sequence(ext_value)) {
337 1           pdfmake_asn1_node_t *ca_node = pdfmake_asn1_find_tag(ext_value, ASN1_TAG_BOOLEAN);
338 1           pdfmake_asn1_node_t *path_node = pdfmake_asn1_find_tag(ext_value, ASN1_TAG_INTEGER);
339            
340 1 50         if (ca_node) {
341             int is_ca;
342 1 50         if (pdfmake_asn1_get_bool(ca_node, &is_ca) == 0) {
343 1           cert->is_ca = is_ca;
344             }
345             }
346 1 50         if (path_node) {
347             int64_t path_len;
348 0 0         if (pdfmake_asn1_get_int64(path_node, &path_len) == 0) {
349 0           cert->path_len_constraint = (int)path_len;
350             }
351             }
352             }
353             }
354             /* Key Usage */
355 2 50         else if (pdfmake_asn1_oid_equals(oid_node, OID_KEY_USAGE)) {
356             /* KeyUsage ::= BIT STRING */
357 0 0         if (ext_value->tag == ASN1_TAG_BIT_STRING) {
358             const uint8_t *bits;
359             size_t bit_count;
360 0 0         if (pdfmake_asn1_get_bit_string(ext_value, &bits, &bit_count) == 0 && bit_count > 0) {
    0          
361             /* Key usage bits are in order from MSB */
362 0           uint16_t ku = 0;
363 0 0         if (bit_count > 0) ku |= (bits[0] & 0x80) ? PDFMAKE_KU_DIGITAL_SIGNATURE : 0;
364 0 0         if (bit_count > 1) ku |= (bits[0] & 0x40) ? PDFMAKE_KU_NON_REPUDIATION : 0;
365 0 0         if (bit_count > 2) ku |= (bits[0] & 0x20) ? PDFMAKE_KU_KEY_ENCIPHERMENT : 0;
366 0 0         if (bit_count > 3) ku |= (bits[0] & 0x10) ? PDFMAKE_KU_DATA_ENCIPHERMENT : 0;
367 0 0         if (bit_count > 4) ku |= (bits[0] & 0x08) ? PDFMAKE_KU_KEY_AGREEMENT : 0;
368 0 0         if (bit_count > 5) ku |= (bits[0] & 0x04) ? PDFMAKE_KU_KEY_CERT_SIGN : 0;
369 0 0         if (bit_count > 6) ku |= (bits[0] & 0x02) ? PDFMAKE_KU_CRL_SIGN : 0;
370 0 0         if (bit_count > 7) ku |= (bits[0] & 0x01) ? PDFMAKE_KU_ENCIPHER_ONLY : 0;
371 0           cert->key_usage = ku;
372             }
373             }
374             }
375             /* Extended Key Usage */
376 2 50         else if (pdfmake_asn1_oid_equals(oid_node, OID_EXT_KEY_USAGE)) {
377             /* ExtKeyUsageSyntax ::= SEQUENCE OF KeyPurposeId */
378 0 0         if (pdfmake_asn1_is_sequence(ext_value)) {
379 0           pdfmake_asn1_node_t *eku = ext_value->children;
380 0 0         while (eku) {
381 0 0         if (eku->tag == ASN1_TAG_OID) {
382 0 0         if (pdfmake_asn1_oid_equals(eku, OID_EKU_SERVER_AUTH)) {
383 0           cert->ext_key_usage |= PDFMAKE_EKU_SERVER_AUTH;
384 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_CLIENT_AUTH)) {
385 0           cert->ext_key_usage |= PDFMAKE_EKU_CLIENT_AUTH;
386 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_CODE_SIGNING)) {
387 0           cert->ext_key_usage |= PDFMAKE_EKU_CODE_SIGNING;
388 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_EMAIL_PROTECTION)) {
389 0           cert->ext_key_usage |= PDFMAKE_EKU_EMAIL_PROTECTION;
390 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_TIME_STAMPING)) {
391 0           cert->ext_key_usage |= PDFMAKE_EKU_TIME_STAMPING;
392 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_OCSP_SIGNING)) {
393 0           cert->ext_key_usage |= PDFMAKE_EKU_OCSP_SIGNING;
394 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_PDF_SIGNING)) {
395 0           cert->ext_key_usage |= PDFMAKE_EKU_PDF_SIGNING;
396 0 0         } else if (pdfmake_asn1_oid_equals(eku, OID_EKU_DOCUMENT_SIGNING)) {
397 0           cert->ext_key_usage |= PDFMAKE_EKU_DOCUMENT_SIGNING;
398             }
399             }
400 0           eku = eku->next;
401             }
402             }
403             }
404             /* Subject Key Identifier */
405 2 100         else if (pdfmake_asn1_oid_equals(oid_node, OID_SUBJECT_KEY_ID)) {
406             /* SubjectKeyIdentifier ::= OCTET STRING */
407 1 50         if (ext_value->tag == ASN1_TAG_OCTET_STRING) {
408 1           cert->subject_key_id = pdfmake_arena_memdup(arena, ext_value->data, ext_value->length);
409 1           cert->subject_key_id_len = ext_value->length;
410             }
411             }
412             /* Authority Key Identifier */
413 1 50         else if (pdfmake_asn1_oid_equals(oid_node, OID_AUTHORITY_KEY_ID)) {
414             /* AuthorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] IMPLICIT OCTET STRING OPTIONAL, ... } */
415 1 50         if (pdfmake_asn1_is_sequence(ext_value)) {
416 1           pdfmake_asn1_node_t *kid = ext_value->children;
417 1 50         while (kid) {
418 1 50         if ((kid->tag & 0x1F) == 0 && (kid->tag & ASN1_CLASS_MASK) == ASN1_CLASS_CONTEXT) {
    50          
419             /* [0] keyIdentifier */
420 1           cert->authority_key_id = pdfmake_arena_memdup(arena, kid->data, kid->length);
421 1           cert->authority_key_id_len = kid->length;
422 1           break;
423             }
424 0           kid = kid->next;
425             }
426             }
427             }
428             /* Authority Info Access */
429 0 0         else if (pdfmake_asn1_oid_equals(oid_node, OID_AUTHORITY_INFO_ACCESS)) {
430             /* AuthorityInfoAccessSyntax ::= SEQUENCE OF AccessDescription
431             AccessDescription ::= SEQUENCE { accessMethod OID, accessLocation GeneralName } */
432 0 0         if (pdfmake_asn1_is_sequence(ext_value)) {
433 0           pdfmake_asn1_node_t *ad = ext_value->children;
434 0 0         while (ad) {
435 0 0         if (pdfmake_asn1_is_sequence(ad)) {
436 0           pdfmake_asn1_node_t *method = pdfmake_asn1_child_at(ad, 0);
437 0           pdfmake_asn1_node_t *location = pdfmake_asn1_child_at(ad, 1);
438            
439 0 0         if (method && location && pdfmake_asn1_oid_equals(method, OID_OCSP)) {
    0          
    0          
440             /* OCSP responder URL in [6] uniformResourceIdentifier */
441 0 0         if ((location->tag & 0x1F) == 6 &&
442 0 0         (location->tag & ASN1_CLASS_MASK) == ASN1_CLASS_CONTEXT) {
443 0           char *url = pdfmake_arena_alloc(arena, location->length + 1);
444 0 0         if (url) {
445 0           memcpy(url, location->data, location->length);
446 0           url[location->length] = '\0';
447 0           cert->ocsp_responder = url;
448             }
449             }
450             }
451             }
452 0           ad = ad->next;
453             }
454             }
455             }
456             /* CRL Distribution Points */
457 0 0         else if (pdfmake_asn1_oid_equals(oid_node, OID_CRL_DISTRIBUTION)) {
458             /* CRLDistributionPoints ::= SEQUENCE OF DistributionPoint */
459 0 0         if (pdfmake_asn1_is_sequence(ext_value)) {
460 0           pdfmake_asn1_node_t *dp = ext_value->children;
461 0 0         while (dp && !cert->crl_distribution) {
    0          
462 0 0         if (pdfmake_asn1_is_sequence(dp)) {
463             /* DistributionPoint has [0] distributionPoint CHOICE */
464 0           pdfmake_asn1_node_t *dpname = dp->children;
465 0 0         while (dpname && !cert->crl_distribution) {
    0          
466 0 0         if ((dpname->tag & 0x1F) == 0 &&
467 0 0         (dpname->tag & ASN1_CLASS_MASK) == ASN1_CLASS_CONTEXT &&
468 0 0         (dpname->tag & ASN1_CONSTRUCTED)) {
469             /* [0] fullName GeneralNames */
470 0           pdfmake_asn1_node_t *gn = dpname->children;
471 0 0         while (gn && !cert->crl_distribution) {
    0          
472             /* GeneralName: [6] uniformResourceIdentifier */
473 0 0         if ((gn->tag & 0x1F) == 6 &&
474 0 0         (gn->tag & ASN1_CLASS_MASK) == ASN1_CLASS_CONTEXT) {
475 0           char *url = pdfmake_arena_alloc(arena, gn->length + 1);
476 0 0         if (url) {
477 0           memcpy(url, gn->data, gn->length);
478 0           url[gn->length] = '\0';
479 0           cert->crl_distribution = url;
480             }
481             }
482 0           gn = gn->next;
483             }
484             }
485 0           dpname = dpname->next;
486             }
487             }
488 0           dp = dp->next;
489             }
490             }
491             }
492             }
493              
494             /* Parse extensions */
495 1           static void parse_extensions(
496             pdfmake_arena_t *arena,
497             const pdfmake_asn1_node_t *extensions,
498             pdfmake_x509_cert_t *cert)
499             {
500             pdfmake_asn1_node_t *ext;
501              
502 1 50         if (!extensions) return;
503            
504             /* Extensions is SEQUENCE of Extension */
505 1           ext = extensions->children;
506 4 100         while (ext) {
507 3 50         if (pdfmake_asn1_is_sequence(ext)) {
508 3           parse_extension(arena, ext, cert);
509             }
510 3           ext = ext->next;
511             }
512             }
513              
514             /*============================================================================
515             * Public API
516             *==========================================================================*/
517              
518 1           pdfmake_x509_cert_t *pdfmake_x509_parse_der(
519             pdfmake_arena_t *arena,
520             const uint8_t *der,
521             size_t len)
522             {
523             pdfmake_asn1_node_t *root;
524             pdfmake_asn1_node_t *tbs;
525             pdfmake_asn1_node_t *sig_alg;
526             pdfmake_asn1_node_t *sig_val;
527             pdfmake_x509_cert_t *cert;
528             size_t tbs_offset;
529             const uint8_t *sig_bits;
530             size_t sig_bit_count;
531             pdfmake_asn1_node_t *field;
532              
533 1 50         if (!arena || !der || len == 0) return NULL;
    50          
    50          
534            
535             /* Parse the certificate ASN.1 */
536 1           root = pdfmake_asn1_parse(arena, der, len);
537 1 50         if (!pdfmake_asn1_is_sequence(root)) {
538 0           return NULL;
539             }
540            
541             /* Certificate ::= SEQUENCE {
542             tbsCertificate TBSCertificate,
543             signatureAlgorithm AlgorithmIdentifier,
544             signatureValue BIT STRING } */
545            
546 1           tbs = pdfmake_asn1_child_at(root, 0);
547 1           sig_alg = pdfmake_asn1_child_at(root, 1);
548 1           sig_val = pdfmake_asn1_child_at(root, 2);
549            
550 1 50         if (!tbs || !sig_alg || !sig_val) {
    50          
    50          
551 0           return NULL;
552             }
553            
554             /* Allocate certificate */
555 1           cert = pdfmake_arena_alloc(arena, sizeof(pdfmake_x509_cert_t));
556 1 50         if (!cert) return NULL;
557 1           memset(cert, 0, sizeof(pdfmake_x509_cert_t));
558            
559 1           cert->arena = arena;
560 1           cert->der = der;
561 1           cert->der_len = len;
562 1           cert->path_len_constraint = -1;
563            
564             /* Store TBS certificate for signature verification */
565             /* We need to find the raw bytes of the TBS certificate */
566 1           cert->tbs_certificate = tbs->data - 1; /* Include tag byte (approximate) */
567 2 50         cert->tbs_certificate_len = tbs->length + 2 + (tbs->length < 0x80 ? 0 :
568 1 50         tbs->length < 0x100 ? 1 :
569 1 50         tbs->length < 0x10000 ? 2 : 3);
570            
571             /* Actually, we need to recalculate properly - get offset from start */
572             /* For now, use the first child's data pointer minus some offset */
573 1 50         tbs_offset = (tbs->children ? tbs->children->data : tbs->data) - der;
574             /* Back up to find the SEQUENCE tag */
575 6 50         while (tbs_offset > 0 && der[tbs_offset - 1] != (ASN1_TAG_SEQUENCE | ASN1_CONSTRUCTED)) {
    100          
576 5           tbs_offset--;
577             }
578 1 50         if (tbs_offset > 0) tbs_offset--;
579 1           cert->tbs_certificate = der + tbs_offset;
580             /* Find length by looking at sig_alg position */
581 1           cert->tbs_certificate_len = (sig_alg->data - cert->tbs_certificate - 1);
582            
583             /* Parse signature algorithm */
584 1           cert->sig_algorithm_oid = parse_algorithm_id(arena, sig_alg);
585 1           cert->sig_algorithm = parse_sig_algorithm(cert->sig_algorithm_oid);
586            
587             /* Parse signature value */
588 1 50         if (pdfmake_asn1_get_bit_string(sig_val, &sig_bits, &sig_bit_count) == 0) {
589 1           cert->signature = sig_bits;
590 1           cert->signature_len = sig_bit_count / 8;
591             }
592            
593             /* Parse TBSCertificate */
594             /* TBSCertificate ::= SEQUENCE {
595             version [0] EXPLICIT Version DEFAULT v1,
596             serialNumber CertificateSerialNumber,
597             signature AlgorithmIdentifier,
598             issuer Name,
599             validity Validity,
600             subject Name,
601             subjectPublicKeyInfo SubjectPublicKeyInfo,
602             ... extensions [3] EXPLICIT Extensions OPTIONAL } */
603            
604 1 50         if (!pdfmake_asn1_is_sequence(tbs)) {
605 0           return NULL;
606             }
607            
608 1           field = tbs->children;
609            
610             /* Version (optional, [0] EXPLICIT) */
611 1 50         if (field && (field->tag & ASN1_CLASS_MASK) == ASN1_CLASS_CONTEXT &&
    50          
612 1 50         (field->tag & 0x1F) == 0) {
613 1           pdfmake_asn1_node_t *ver = field->children;
614 1 50         if (ver) {
615             int64_t v;
616 1 50         if (pdfmake_asn1_get_int64(ver, &v) == 0) {
617 1           cert->version = (int)v;
618             }
619             }
620 1           field = field->next;
621             }
622            
623             /* Serial number */
624 1 50         if (field && field->tag == ASN1_TAG_INTEGER) {
    50          
625 1           cert->serial = pdfmake_arena_memdup(arena, field->data, field->length);
626 1           cert->serial_len = field->length;
627 1           cert->serial_hex = bytes_to_hex(arena, field->data, field->length);
628 1           field = field->next;
629             }
630            
631             /* Signature algorithm (skip, same as outer) */
632 1 50         if (field) {
633 1           field = field->next;
634             }
635            
636             /* Issuer */
637 1 50         if (field) {
638 1           parse_name(arena, field, &cert->issuer);
639 1           field = field->next;
640             }
641            
642             /* Validity */
643 1 50         if (pdfmake_asn1_is_sequence(field)) {
644 1           pdfmake_asn1_node_t *not_before = pdfmake_asn1_child_at(field, 0);
645 1           pdfmake_asn1_node_t *not_after = pdfmake_asn1_child_at(field, 1);
646            
647 1 50         if (not_before) pdfmake_asn1_get_time(not_before, &cert->not_before);
648 1 50         if (not_after) pdfmake_asn1_get_time(not_after, &cert->not_after);
649            
650 1           field = field->next;
651             }
652            
653             /* Subject */
654 1 50         if (field) {
655 1           parse_name(arena, field, &cert->subject);
656 1           field = field->next;
657             }
658            
659             /* Subject Public Key Info */
660 1 50         if (field) {
661 1           parse_pubkey_info(arena, field, &cert->pubkey);
662 1           field = field->next;
663             }
664            
665             /* Skip issuerUniqueID [1] and subjectUniqueID [2] if present */
666 2 100         while (field && (field->tag & ASN1_CLASS_MASK) == ASN1_CLASS_CONTEXT) {
    50          
667 1 50         if ((field->tag & 0x1F) == 3) {
668             /* Extensions [3] */
669 1 50         if (field->children) {
670 1           parse_extensions(arena, field->children, cert);
671             }
672             }
673 1           field = field->next;
674             }
675            
676             /* Check if self-signed */
677 1 50         if (cert->issuer.dn && cert->subject.dn &&
    50          
678 1 50         strcmp(cert->issuer.dn, cert->subject.dn) == 0) {
679 1           cert->is_self_signed = 1;
680             }
681            
682 1           return cert;
683             }
684              
685 0           pdfmake_x509_cert_t *pdfmake_x509_parse_pem(
686             pdfmake_arena_t *arena,
687             const char *pem,
688             size_t len)
689             {
690             const char *begin;
691             const char *end;
692             size_t b64_len;
693             size_t max_der_len;
694             uint8_t *der;
695             size_t der_len;
696              
697 0 0         if (!arena || !pem || len == 0) return NULL;
    0          
    0          
698            
699             /* Find BEGIN marker */
700 0           begin = strstr(pem, "-----BEGIN CERTIFICATE-----");
701 0 0         if (!begin) return NULL;
702 0           begin += 27; /* Skip marker */
703            
704             /* Find END marker */
705 0           end = strstr(begin, "-----END CERTIFICATE-----");
706 0 0         if (!end) return NULL;
707            
708             /* Allocate buffer for decoded data */
709 0           b64_len = end - begin;
710 0           max_der_len = (b64_len * 3) / 4 + 4;
711 0           der = pdfmake_arena_alloc(arena, max_der_len);
712 0 0         if (!der) return NULL;
713            
714             /* Decode base64 */
715 0           der_len = base64_decode(begin, b64_len, der, max_der_len);
716 0 0         if (der_len == 0) return NULL;
717            
718 0           return pdfmake_x509_parse_der(arena, der, der_len);
719             }
720              
721 0           pdfmake_cert_chain_t *pdfmake_x509_parse_pem_chain(
722             pdfmake_arena_t *arena,
723             const char *pem,
724             size_t len)
725             {
726             pdfmake_cert_chain_t *chain;
727             const char *p;
728             const char *end;
729             pdfmake_x509_cert_t *last;
730             const char *begin;
731             const char *cert_end;
732             pdfmake_x509_cert_t *cert;
733              
734 0 0         if (!arena || !pem || len == 0) return NULL;
    0          
    0          
735            
736 0           chain = pdfmake_arena_alloc(arena, sizeof(pdfmake_cert_chain_t));
737 0 0         if (!chain) return NULL;
738 0           memset(chain, 0, sizeof(pdfmake_cert_chain_t));
739 0           chain->arena = arena;
740            
741 0           p = pem;
742 0           end = pem + len;
743 0           last = NULL;
744            
745 0 0         while (p < end) {
746 0           begin = strstr(p, "-----BEGIN CERTIFICATE-----");
747 0 0         if (!begin || begin >= end) break;
    0          
748            
749 0           cert_end = strstr(begin, "-----END CERTIFICATE-----");
750 0 0         if (!cert_end || cert_end >= end) break;
    0          
751 0           cert_end += 25; /* Include end marker */
752            
753 0           cert = pdfmake_x509_parse_pem(arena, begin, cert_end - begin);
754 0 0         if (cert) {
755 0 0         if (last) {
756 0           last->next = cert;
757             } else {
758 0           chain->certs = cert;
759             }
760 0           last = cert;
761 0           chain->count++;
762             }
763            
764 0           p = cert_end;
765             }
766            
767 0           return chain;
768             }
769              
770 0           pdfmake_x509_cert_t *pdfmake_x509_load_file(
771             pdfmake_arena_t *arena,
772             const char *path)
773             {
774             FILE *f;
775             long size;
776             uint8_t *data;
777              
778 0 0         if (!arena || !path) return NULL;
    0          
779            
780 0           f = fopen(path, "rb");
781 0 0         if (!f) return NULL;
782            
783             /* Get file size */
784 0           fseek(f, 0, SEEK_END);
785 0           size = ftell(f);
786 0           fseek(f, 0, SEEK_SET);
787            
788 0 0         if (size <= 0 || size > 1024 * 1024) { /* Max 1MB */
    0          
789 0           fclose(f);
790 0           return NULL;
791             }
792            
793             /* Read file */
794 0           data = pdfmake_arena_alloc(arena, size);
795 0 0         if (!data) {
796 0           fclose(f);
797 0           return NULL;
798             }
799            
800 0 0         if (fread(data, 1, size, f) != (size_t)size) {
801 0           fclose(f);
802 0           return NULL;
803             }
804 0           fclose(f);
805            
806             /* Check for PEM format */
807 0 0         if (size > 27 && memcmp(data, "-----BEGIN", 10) == 0) {
    0          
808 0           return pdfmake_x509_parse_pem(arena, (const char *)data, size);
809             }
810            
811             /* Assume DER */
812 0           return pdfmake_x509_parse_der(arena, data, size);
813             }
814              
815 0           int pdfmake_x509_is_valid(
816             const pdfmake_x509_cert_t *cert,
817             int64_t check_time)
818             {
819 0 0         if (!cert) return 0;
820            
821 0 0         if (check_time == 0) {
822 0           check_time = time(NULL);
823             }
824            
825 0 0         return (check_time >= cert->not_before && check_time <= cert->not_after);
    0          
826             }
827              
828 1           int pdfmake_x509_can_sign_documents(const pdfmake_x509_cert_t *cert)
829             {
830 1 50         if (!cert) return 0;
831            
832             /* Check key usage if present */
833 1 50         if (cert->key_usage != 0) {
834             /* Need digitalSignature or nonRepudiation */
835 0 0         if (!(cert->key_usage & (PDFMAKE_KU_DIGITAL_SIGNATURE | PDFMAKE_KU_NON_REPUDIATION))) {
836 0           return 0;
837             }
838             }
839            
840             /* Check extended key usage if present */
841 1 50         if (cert->ext_key_usage != 0) {
842             /* Need one of: document signing, PDF signing, email protection, or code signing */
843 0           uint32_t signing_ekus = PDFMAKE_EKU_DOCUMENT_SIGNING | PDFMAKE_EKU_PDF_SIGNING |
844             PDFMAKE_EKU_EMAIL_PROTECTION | PDFMAKE_EKU_CODE_SIGNING;
845 0 0         if (!(cert->ext_key_usage & signing_ekus)) {
846 0           return 0;
847             }
848             }
849            
850 1           return 1;
851             }
852              
853 0           char *pdfmake_x509_format_name(
854             pdfmake_arena_t *arena,
855             const pdfmake_x509_name_t *name)
856             {
857             char buf[512];
858             char *p;
859             char *end;
860              
861 0 0         if (!arena || !name) return NULL;
    0          
862            
863             /* Already have DN string */
864 0 0         if (name->dn) {
865 0           return pdfmake_arena_strdup(arena, name->dn);
866             }
867            
868             /* Build from components */
869 0           p = buf;
870 0           end = buf + sizeof(buf) - 1;
871            
872 0 0         if (name->common_name) {
873 0           p += snprintf(p, end - p, "CN=%s", name->common_name);
874             }
875 0 0         if (name->organization && p < end) {
    0          
876 0 0         if (p > buf) p += snprintf(p, end - p, ", ");
877 0           p += snprintf(p, end - p, "O=%s", name->organization);
878             }
879 0 0         if (name->organizational_unit && p < end) {
    0          
880 0 0         if (p > buf) p += snprintf(p, end - p, ", ");
881 0           p += snprintf(p, end - p, "OU=%s", name->organizational_unit);
882             }
883 0 0         if (name->locality && p < end) {
    0          
884 0 0         if (p > buf) p += snprintf(p, end - p, ", ");
885 0           p += snprintf(p, end - p, "L=%s", name->locality);
886             }
887 0 0         if (name->state && p < end) {
    0          
888 0 0         if (p > buf) p += snprintf(p, end - p, ", ");
889 0           p += snprintf(p, end - p, "ST=%s", name->state);
890             }
891 0 0         if (name->country && p < end) {
    0          
892 0 0         if (p > buf) p += snprintf(p, end - p, ", ");
893 0           p += snprintf(p, end - p, "C=%s", name->country);
894             }
895            
896 0           *p = '\0';
897 0           return pdfmake_arena_strdup(arena, buf);
898             }
899              
900 0           const char *pdfmake_x509_sig_algorithm_name(pdfmake_sig_algorithm_t alg)
901             {
902 0           switch (alg) {
903 0           case PDFMAKE_SIG_RSA_MD5: return "RSA-MD5";
904 0           case PDFMAKE_SIG_RSA_SHA1: return "RSA-SHA1";
905 0           case PDFMAKE_SIG_RSA_SHA256: return "RSA-SHA256";
906 0           case PDFMAKE_SIG_RSA_SHA384: return "RSA-SHA384";
907 0           case PDFMAKE_SIG_RSA_SHA512: return "RSA-SHA512";
908 0           case PDFMAKE_SIG_ECDSA_SHA256: return "ECDSA-SHA256";
909 0           case PDFMAKE_SIG_ECDSA_SHA384: return "ECDSA-SHA384";
910 0           case PDFMAKE_SIG_ECDSA_SHA512: return "ECDSA-SHA512";
911 0           case PDFMAKE_SIG_ED25519: return "Ed25519";
912 0           case PDFMAKE_SIG_ED448: return "Ed448";
913 0           default: return "Unknown";
914             }
915             }
916              
917 0           const char *pdfmake_x509_pk_algorithm_name(pdfmake_pk_algorithm_t alg)
918             {
919 0           switch (alg) {
920 0           case PDFMAKE_PK_RSA: return "RSA";
921 0           case PDFMAKE_PK_DSA: return "DSA";
922 0           case PDFMAKE_PK_ECDSA: return "ECDSA";
923 0           case PDFMAKE_PK_ED25519: return "Ed25519";
924 0           case PDFMAKE_PK_ED448: return "Ed448";
925 0           default: return "Unknown";
926             }
927             }
928              
929 0           pdfmake_err_t pdfmake_x509_verify_signature(
930             const pdfmake_x509_cert_t *cert,
931             const pdfmake_x509_cert_t *issuer)
932             {
933             /* TODO: Implement actual cryptographic verification */
934             /* This requires RSA/ECDSA signature verification with the issuer's public key */
935             /* For now, just return success for self-signed certs with matching issuer/subject */
936            
937 0 0         if (!cert) return PDFMAKE_EINVAL;
938            
939 0 0         if (!issuer && cert->is_self_signed) {
    0          
940             /* Self-signed certificate - would verify with own public key */
941 0           return PDFMAKE_OK;
942             }
943            
944 0 0         if (issuer) {
945             /* Would verify cert->signature against cert->tbs_certificate
946             * using issuer->pubkey */
947 0           return PDFMAKE_OK;
948             }
949            
950 0           return PDFMAKE_EINVAL;
951             }
952              
953 0           pdfmake_err_t pdfmake_x509_verify_chain(
954             const pdfmake_cert_chain_t *chain,
955             const pdfmake_cert_chain_t *trust_anchors)
956             {
957             pdfmake_x509_cert_t *cert;
958              
959             /* TODO: Implement full chain verification */
960             /* 1. Verify each cert is signed by the next in chain */
961             /* 2. Verify the last cert is in trust_anchors or is self-signed */
962             /* 3. Check validity periods */
963             /* 4. Check basic constraints (CA flag) */
964            
965 0 0         if (!chain || chain->count == 0) return PDFMAKE_EINVAL;
    0          
966             (void)trust_anchors;
967            
968             /* For now, basic validity check */
969 0           cert = chain->certs;
970 0 0         while (cert) {
971 0 0         if (!pdfmake_x509_is_valid(cert, 0)) {
972 0           return PDFMAKE_EINVAL; /* Certificate expired */
973             }
974 0           cert = cert->next;
975             }
976            
977 0           return PDFMAKE_OK;
978             }
979              
980 0           void pdfmake_x509_cert_free(pdfmake_x509_cert_t *cert)
981             {
982             /* If allocated from arena, arena cleanup handles this */
983             (void)cert;
984 0           }
985              
986 0           void pdfmake_cert_chain_free(pdfmake_cert_chain_t *chain)
987             {
988             /* If allocated from arena, arena cleanup handles this */
989             (void)chain;
990 0           }