Line data Source code
1 : #include "fd_x509.h"
2 : #include "fd_der.h"
3 : #include "../../util/net/fd_ip4.h"
4 : #include "../secp256r1/fd_secp256r1.h"
5 : #include "../secp384r1/fd_secp384r1.h"
6 : #include "../utf8/fd_utf8.h"
7 : #include <string.h>
8 :
9 : /* Bounds on distinguished names this parser accepts: attributes per
10 : RDN, and characters per string-typed attribute value. */
11 :
12 : #define FD_X509_RDN_ATV_MAX (16UL)
13 : #define FD_X509_DN_VALUE_MAX (512UL)
14 :
15 : /* fd_x509_dn_string_width returns the code unit width of a string type
16 : the name comparator normalizes, or 0 for any other tag. */
17 :
18 : static ulong
19 348456 : fd_x509_dn_string_width( int tag ) {
20 348456 : switch( tag ) {
21 348171 : case FD_DER_TAG_UTF8_STRING:
22 348399 : case FD_DER_TAG_PRINTABLE_STR:
23 348399 : case FD_DER_TAG_TELETEX_STRING:
24 348414 : case FD_DER_TAG_IA5_STRING: return 1UL;
25 33 : case FD_DER_TAG_BMP_STRING: return 2UL;
26 9 : case FD_DER_TAG_UNIVERSAL_STRING: return 4UL;
27 0 : default: return 0UL;
28 348456 : }
29 348456 : }
30 :
31 : /* OID for algorithm IDs. */
32 :
33 : /* Ed25519: 1.3.101.112 */
34 : static uchar const oid_ed25519[] = { 0x06, 0x03, 0x2b, 0x65, 0x70 };
35 :
36 : /* ecPublicKey: 1.2.840.10045.2.1 */
37 : static uchar const oid_ec_pubkey[] = { 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 };
38 :
39 : /* prime256v1 (P-256): 1.2.840.10045.3.1.7 */
40 : static uchar const oid_prime256v1[] = { 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 };
41 :
42 : /* secp384r1 (P-384): 1.3.132.0.34 */
43 : static uchar const oid_secp384r1[] = { 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 };
44 :
45 : /* ecdsa-with-SHA256: 1.2.840.10045.4.3.2 */
46 : static uchar const oid_ecdsa_sha256[] = { 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 };
47 :
48 : /* ecdsa-with-SHA384: 1.2.840.10045.4.3.3 */
49 : static uchar const oid_ecdsa_sha384[] = { 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 };
50 :
51 : /* rsaEncryption: 1.2.840.113549.1.1.1 */
52 : static uchar const oid_rsa_encryption[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
53 :
54 : /* sha256WithRSAEncryption: 1.2.840.113549.1.1.11 */
55 : static uchar const oid_rsa_sha256[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b };
56 :
57 : /* sha384WithRSAEncryption: 1.2.840.113549.1.1.12 */
58 : static uchar const oid_rsa_sha384[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c };
59 :
60 : /* sha512WithRSAEncryption: 1.2.840.113549.1.1.13 */
61 : static uchar const oid_rsa_sha512[] = { 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d };
62 :
63 : /* DER NULL, the parameters of every RSA AlgorithmIdentifier */
64 : static uchar const der_null[] = { 0x05, 0x00 };
65 :
66 : /* subjectAltName: 2.5.29.17 */
67 : static uchar const oid_san[] = { 0x06, 0x03, 0x55, 0x1d, 0x11 };
68 :
69 : /* nameConstraints: 2.5.29.30 */
70 : static uchar const oid_name_constraints[] = { 0x06, 0x03, 0x55, 0x1d, 0x1e };
71 :
72 : /* basicConstraints: 2.5.29.19 */
73 : static uchar const oid_basic_constraints[] = { 0x06, 0x03, 0x55, 0x1d, 0x13 };
74 :
75 : /* OID 2.5.29.15 (keyUsage) */
76 : static uchar const oid_key_usage[] = { 0x06, 0x03, 0x55, 0x1d, 0x0f };
77 :
78 : /* OID 2.5.29.37 (extKeyUsage) */
79 : static uchar const oid_ext_key_usage[] = { 0x06, 0x03, 0x55, 0x1d, 0x25 };
80 :
81 : /* OID 1.3.6.1.5.5.7.3.1 (id-kp-serverAuth) */
82 : static uchar const oid_kp_server_auth[] = { 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 };
83 :
84 : /* OID 2.5.29.37.0 (anyExtendedKeyUsage) */
85 : static uchar const oid_kp_any[] = { 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00 };
86 :
87 :
88 : /* fd_x509_rsa_alg_match matches an AlgorithmIdentifier content against
89 : an RSA OID. RFC 4055 Section 5 requires the parameters to be NULL
90 : but tells implementations to accept them absent as well. */
91 :
92 : static int
93 : fd_x509_rsa_alg_match( uchar const * alg,
94 : ulong alg_len,
95 : uchar const * oid,
96 162 : ulong oid_len ) {
97 162 : if( alg_len!=oid_len && alg_len!=oid_len+sizeof(der_null) ) return 0;
98 162 : if( memcmp( alg, oid, oid_len ) ) return 0;
99 102 : return alg_len==oid_len || !memcmp( alg+oid_len, der_null, sizeof(der_null) );
100 162 : }
101 :
102 : static uchar
103 322194 : fd_x509_parse_sig_alg( uchar const * alg, ulong alg_len ) {
104 322194 : if( fd_der_oid_match( alg, alg_len, oid_ed25519, sizeof(oid_ed25519) ) )
105 319614 : return FD_X509_SIG_ED25519;
106 2580 : if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha256, sizeof(oid_ecdsa_sha256) ) )
107 2274 : return FD_X509_SIG_ECDSA_SHA256;
108 306 : if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha384, sizeof(oid_ecdsa_sha384) ) )
109 204 : return FD_X509_SIG_ECDSA_SHA384;
110 102 : if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha256, sizeof(oid_rsa_sha256) ) )
111 63 : return FD_X509_SIG_RSA_SHA256;
112 39 : if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha384, sizeof(oid_rsa_sha384) ) )
113 18 : return FD_X509_SIG_RSA_SHA384;
114 21 : if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha512, sizeof(oid_rsa_sha512) ) )
115 21 : return FD_X509_SIG_RSA_SHA512;
116 0 : return FD_X509_SIG_UNKNOWN;
117 21 : }
118 :
119 : /* fd_x509_positive_integer checks that a DER INTEGER content is a
120 : positive value in minimal two's complement encoding (RFC 8017 Appendix
121 : A.1.1 requires positive modulus and exponent). */
122 :
123 : static int
124 : fd_x509_positive_integer( uchar const * p,
125 2766 : ulong len ) {
126 2766 : if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return 0;
127 2763 : if( p[0]==0x00 ) {
128 1380 : if( FD_UNLIKELY( len==1UL || !(p[1] & 0x80U) ) ) return 0;
129 1380 : }
130 2760 : return 1;
131 2763 : }
132 :
133 : /* fd_x509_parse_rsa_pubkey parses a DER RSAPublicKey. Returns 0 on
134 : success, -1 if malformed, and 1 if well-formed but outside the range
135 : fd_rsa supports. key may be NULL to only validate. */
136 :
137 : static int
138 : fd_x509_parse_rsa_pubkey( uchar const * p,
139 : ulong len,
140 1389 : fd_rsa_pubkey_t * key ) {
141 1389 : fd_der_cursor_t c = { .p=p, .end=p+len };
142 1389 : uchar const * n; ulong n_len;
143 1389 : uchar const * e; ulong e_len;
144 1389 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
145 1386 : FD_DER_READ( c, FD_DER_TAG_INTEGER, n, n_len );
146 1386 : FD_DER_READ( c, FD_DER_TAG_INTEGER, e, e_len );
147 1386 : if( FD_UNLIKELY( !fd_x509_positive_integer( n, n_len ) ||
148 1386 : !fd_x509_positive_integer( e, e_len ) ) ) return -1;
149 1380 : FD_DER_LEAVE( c );
150 1380 : if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
151 :
152 1377 : #if FD_HAS_INT128
153 1377 : fd_rsa_pubkey_t tmp[1];
154 1377 : if( !key ) key = tmp;
155 1377 : if( FD_UNLIKELY( !fd_rsa_pubkey_init( key, n, n_len, e, e_len ) ) ) return 1;
156 1371 : return 0;
157 : #else
158 : (void)key;
159 : return 1;
160 : #endif
161 1377 : }
162 :
163 : int
164 : fd_x509_decode_rsa_pubkey( uchar const * pubkey,
165 : ulong pubkey_len,
166 573 : fd_rsa_pubkey_t * key ) {
167 573 : if( FD_UNLIKELY( !pubkey || !key ) ) return -1;
168 567 : return fd_x509_parse_rsa_pubkey( pubkey, pubkey_len, key ) ? -1 : 0;
169 573 : }
170 :
171 : static int
172 : fd_x509_parse_spki( fd_der_cursor_t * c,
173 : uchar const ** out_pk,
174 : ulong * out_pk_len,
175 322140 : uchar * out_type ) {
176 :
177 : /* algorithm AlgorithmIdentifier ::= SEQUENCE {
178 : algorithm OBJECT IDENTIFIER,
179 : parameters ANY DEFINED BY algorithm OPTIONAL } */
180 322140 : uchar const * alg_ptr; ulong alg_len;
181 322140 : FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, alg_ptr, alg_len );
182 322140 : fd_der_cursor_t alg = { .p = alg_ptr, .end = alg_ptr+alg_len };
183 :
184 322140 : uchar const * oid; ulong oid_len;
185 322140 : FD_DER_READ_RAW( alg, FD_DER_TAG_OID, oid, oid_len );
186 :
187 322137 : uchar type = FD_X509_KEY_UNKNOWN;
188 322137 : if( fd_der_oid_match( oid, oid_len, oid_ed25519, sizeof(oid_ed25519) ) ) {
189 : /* RFC 8410 Section 3: parameters MUST be absent */
190 318831 : type = FD_X509_KEY_ED25519;
191 318831 : } else if( fd_der_oid_match( oid, oid_len, oid_ec_pubkey, sizeof(oid_ec_pubkey) ) ) {
192 : /* RFC 5480 Section 2.1.1: parameters is a namedCurve OID */
193 2481 : uchar const * curve; ulong curve_len;
194 2481 : FD_DER_READ_RAW( alg, FD_DER_TAG_OID, curve, curve_len );
195 2478 : if( fd_der_oid_match( curve, curve_len, oid_prime256v1, sizeof(oid_prime256v1) ) ) type = FD_X509_KEY_ECDSA_P256;
196 39 : else if( fd_der_oid_match( curve, curve_len, oid_secp384r1, sizeof(oid_secp384r1) ) ) type = FD_X509_KEY_ECDSA_P384;
197 2478 : } else if( fd_der_oid_match( oid, oid_len, oid_rsa_encryption, sizeof(oid_rsa_encryption) ) ) {
198 : /* RFC 3279 Section 2.3.1: parameters MUST be NULL. Absent is
199 : tolerated, matching the RFC 4055 rule for the signature OIDs. */
200 822 : if( FD_DER_HAS_MORE( alg ) ) {
201 822 : uchar const * params; ulong params_len;
202 822 : FD_DER_READ( alg, FD_DER_TAG_NULL, params, params_len );
203 822 : (void)params;
204 822 : if( FD_UNLIKELY( params_len ) ) return -1;
205 822 : }
206 822 : type = FD_X509_KEY_RSA;
207 822 : } else if( FD_DER_HAS_MORE( alg ) ) {
208 0 : FD_DER_SKIP( alg );
209 0 : }
210 322134 : if( FD_UNLIKELY( FD_DER_HAS_MORE( alg ) ) ) return -1;
211 :
212 : /* subjectPublicKey BIT STRING */
213 322131 : uchar const * bits; ulong bits_len;
214 322131 : FD_DER_READ_BITS( *c, bits, bits_len );
215 :
216 322131 : switch( type ) {
217 318828 : case FD_X509_KEY_ED25519:
218 318828 : if( FD_UNLIKELY( bits_len != 32 ) ) return -1;
219 318828 : break;
220 318828 : case FD_X509_KEY_ECDSA_P256: {
221 2439 : if( FD_UNLIKELY( bits_len != 65 ) ) return -1;
222 2439 : uchar compressed[ 33 ];
223 2439 : if( FD_UNLIKELY( fd_secp256r1_public_key_compress( compressed, bits )
224 2439 : !=FD_SECP256R1_SUCCESS ) ) return -1;
225 2439 : break;
226 2439 : }
227 2439 : case FD_X509_KEY_ECDSA_P384: {
228 33 : if( FD_UNLIKELY( bits_len != 97 ) ) return -1;
229 33 : uchar compressed[ 49 ];
230 33 : if( FD_UNLIKELY( fd_secp384r1_public_key_compress( compressed, bits )
231 33 : !=FD_SECP384R1_SUCCESS ) ) return -1;
232 30 : break;
233 33 : }
234 822 : case FD_X509_KEY_RSA: {
235 822 : int err = fd_x509_parse_rsa_pubkey( bits, bits_len, NULL );
236 822 : if( FD_UNLIKELY( err<0 ) ) return -1;
237 822 : if( err ) type = FD_X509_KEY_UNKNOWN; /* e.g. RSA-1024 */
238 822 : break;
239 822 : }
240 9 : default: /* well-formed but unsupported */
241 9 : break;
242 322131 : }
243 :
244 322128 : *out_pk = bits;
245 322128 : *out_pk_len = bits_len;
246 322128 : *out_type = type;
247 322128 : return 0;
248 322131 : }
249 :
250 : static int
251 : fd_x509_parse_path_len( uchar const * p,
252 : ulong len,
253 546 : ulong * out ) {
254 : /* INTEGER is signed. pathLenConstraint is non-negative, and DER
255 : requires the shortest possible two's-complement encoding. */
256 546 : if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return -1;
257 540 : if( FD_UNLIKELY( len>1UL && p[0]==0x00 && !(p[1] & 0x80U) ) ) return -1;
258 :
259 : /* Values larger than ulong are valid ASN.1 (MAX is unbounded). Such a
260 : value cannot constrain a path capped at FD_X509_CHAIN_MAX, so saturate
261 : it instead of rejecting an otherwise valid certificate. */
262 537 : ulong path_len = 0UL;
263 1077 : for( ulong i=0UL; i<len; i++ ) {
264 540 : if( FD_UNLIKELY( path_len>(~0UL >> 8) ) ) {
265 0 : path_len = ~0UL;
266 0 : break;
267 0 : }
268 540 : path_len = (path_len << 8) | (ulong)p[i];
269 540 : }
270 537 : *out = path_len;
271 537 : return 0;
272 540 : }
273 :
274 : static int
275 : fd_x509_serial_valid( uchar const * p,
276 322197 : ulong len ) {
277 : /* RFC 5280 requires a positive serial number no longer than 20 octets.
278 : DER INTEGERs are signed and minimally encoded.
279 :
280 : Exception: serial 0 is allowed. It is in the Mozilla root store:
281 : Hellenic Academic and Research Institutions ECC RootCA 2015
282 : sha256 44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833 */
283 322197 : if( FD_UNLIKELY( !len || len>20UL || (p[0] & 0x80U) ) ) return 0;
284 322194 : if( p[0]==0x00 ) {
285 6 : if( FD_UNLIKELY( len>1UL && !(p[1] & 0x80U) ) ) return 0;
286 6 : }
287 322194 : return 1;
288 322194 : }
289 :
290 : static int
291 : fd_x509_unique_id_valid( uchar const * p,
292 0 : ulong len ) {
293 : /* UniqueIdentifier is an IMPLICIT BIT STRING. */
294 0 : if( FD_UNLIKELY( !len || p[0]>7U ) ) return 0;
295 0 : if( len==1UL ) return p[0]==0U;
296 0 : return !( p[len-1UL] & (uchar)( (1U<<p[0])-1U ) );
297 0 : }
298 :
299 : /* fd_x509_name_valid checks that [p,p+len) is one Name TLV in the shape
300 : fd_x509_name_equal and fd_x509_name_prefix compare: a SEQUENCE of
301 : non-empty SETs of SEQUENCE { OID, value }, at most FD_X509_RDN_ATV_MAX
302 : attributes per SET, string values whole in their code unit and at
303 : most FD_X509_DN_VALUE_MAX characters. Parsing rejects any other Name
304 : so that a comparison can only fail because two names differ, never
305 : because one could not be read: an excluded-subtree check that fails
306 : to compare would otherwise pass the name. */
307 :
308 : static int
309 : fd_x509_name_valid( uchar const * p,
310 644484 : ulong len ) {
311 644484 : fd_der_cursor_t c = { .p=p, .end=p+len };
312 644484 : int tag; ulong seq_len;
313 644484 : if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &seq_len ) ||
314 644484 : tag!=(int)FD_DER_TAG_SEQUENCE || c.p+seq_len!=c.end ) ) return 0;
315 976464 : while( FD_DER_HAS_MORE( c ) ) {
316 332031 : ulong set_len;
317 332031 : if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &set_len ) ||
318 332031 : tag!=(int)FD_DER_TAG_SET || !set_len ) ) return 0;
319 332022 : fd_der_cursor_t set = { .p=c.p, .end=c.p+set_len };
320 332022 : c.p += set_len;
321 332022 : ulong atv_cnt = 0UL;
322 664098 : while( FD_DER_HAS_MORE( set ) ) {
323 332118 : if( FD_UNLIKELY( ++atv_cnt>FD_X509_RDN_ATV_MAX ) ) return 0;
324 332112 : ulong atv_len;
325 332112 : if( FD_UNLIKELY( fd_der_read_tl( &set, &tag, &atv_len ) ||
326 332112 : tag!=(int)FD_DER_TAG_SEQUENCE ) ) return 0;
327 332112 : fd_der_cursor_t atv = { .p=set.p, .end=set.p+atv_len };
328 332112 : set.p += atv_len;
329 332112 : ulong oid_len;
330 332112 : if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &oid_len ) ||
331 332112 : tag!=(int)FD_DER_TAG_OID || !fd_der_oid_valid( atv.p, oid_len ) ) ) return 0;
332 332112 : atv.p += oid_len;
333 332112 : ulong val_len;
334 332112 : if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &val_len ) ) ) return 0;
335 332112 : ulong width = fd_x509_dn_string_width( tag );
336 332112 : if( width && FD_UNLIKELY( val_len%width || val_len/width>FD_X509_DN_VALUE_MAX ) ) return 0;
337 332106 : if( tag==(int)FD_DER_TAG_UTF8_STRING &&
338 332106 : FD_UNLIKELY( !fd_utf8_verify( (char const *)atv.p, val_len ) ) ) return 0;
339 4048563 : for( ulong i=0UL; width && i<val_len; i+=width ) {
340 3716481 : uint cp = 0U;
341 7433010 : for( ulong k=0UL; k<width; k++ ) cp = (cp<<8) | atv.p[i+k];
342 3716481 : if( width>1UL && FD_UNLIKELY( cp>0x10ffffU || (cp>=0xd800U && cp<=0xdfffU) ) ) return 0;
343 3716472 : if( tag==(int)FD_DER_TAG_IA5_STRING && FD_UNLIKELY( cp>0x7fU ) ) return 0;
344 3716469 : if( tag==(int)FD_DER_TAG_PRINTABLE_STR &&
345 3716469 : FD_UNLIKELY( !( (cp>='a' && cp<='z') || (cp>='A' && cp<='Z') ||
346 3716469 : (cp>='0' && cp<='9') || (cp && strchr( " '()+,-./:=?", (int)cp )) ) ) ) return 0;
347 3716469 : }
348 332082 : atv.p += val_len;
349 332082 : if( FD_UNLIKELY( atv.p!=atv.end ) ) return 0;
350 332082 : }
351 332022 : }
352 644433 : return 1;
353 644484 : }
354 :
355 : static int
356 : fd_x509_general_name_valid( int tag,
357 : uchar const * p,
358 326145 : ulong len ) {
359 326145 : switch( tag ) {
360 30 : case FD_DER_TAG_CONTEXT(0): /* otherName */
361 30 : case FD_DER_TAG_CONTEXT(3): /* x400Address */
362 30 : case FD_DER_TAG_CONTEXT(5): /* ediPartyName */
363 : /* not worth validating, we don't look at these */
364 30 : return !!len;
365 114 : case FD_DER_TAG_CONTEXT(4): /* directoryName: a Name SEQUENCE */
366 114 : return fd_x509_name_valid( p, len );
367 9 : case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
368 15 : case FD_DER_TAG_CONTEXT_PRIM(6): /* uniformResourceIdentifier */
369 15 : if( FD_UNLIKELY( !len ) ) return 0;
370 27 : for( ulong i=0UL; i<len; i++ )
371 24 : if( FD_UNLIKELY( p[i] & 0x80U ) ) return 0;
372 3 : return 1;
373 325827 : case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
374 325827 : ulong off = len>2UL && p[0]=='*' && p[1]=='.' ? 2UL : 0UL;
375 325827 : return fd_x509_dns_name_valid( (char const *)p+off, len-off );
376 3 : }
377 135 : case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress */
378 135 : return len==4UL || len==16UL;
379 6 : case FD_DER_TAG_CONTEXT_PRIM(8): /* registeredID */
380 6 : return fd_der_oid_valid( p, len );
381 18 : default:
382 18 : return 0;
383 326145 : }
384 326145 : }
385 :
386 : /* Validate GeneralSubtrees. The verifier enforces dNSName, iPAddress
387 : and directoryName bases. Other name forms are accepted here since a
388 : CA may constrain forms a cert never uses (real CA/B Forum
389 : intermediates permit a dNSName and exclude iPAddress 0.0.0.0/0 and
390 : ::/0 in the same extension); the verifier rejects any cert carrying
391 : a SAN of such a constrained form. rfc822Name is the exception: RFC
392 : 5280 Section 4.2.1.10 also applies it to subject emailAddress
393 : attributes, which this verifier does not match, so a CA constraining
394 : mail addresses is rejected outright rather than half-enforced. Web
395 : PKI issuing CAs do not carry such constraints. minimum and maximum
396 : are unsupported; minimum's DEFAULT is zero and omitted in DER. */
397 :
398 : static int
399 : fd_x509_subtrees_valid( uchar const * p,
400 315 : ulong len ) {
401 315 : fd_der_cursor_t trees = { .p=p, .end=p+len };
402 315 : if( FD_UNLIKELY( !len ) ) return -1;
403 :
404 702 : while( FD_DER_HAS_MORE( trees ) ) {
405 399 : uchar const * tree_ptr; ulong tree_len;
406 399 : FD_DER_READ( trees, FD_DER_TAG_SEQUENCE, tree_ptr, tree_len );
407 399 : fd_der_cursor_t tree = { .p=tree_ptr, .end=tree_ptr+tree_len };
408 :
409 399 : int tag; ulong base_len;
410 399 : if( FD_UNLIKELY( fd_der_read_tl( &tree, &tag, &base_len ) ) ) return -1;
411 399 : uchar const * base = tree.p;
412 399 : tree.p += base_len;
413 399 : if( FD_UNLIKELY( FD_DER_HAS_MORE( tree ) ) ) return -1;
414 :
415 399 : switch( tag ) {
416 159 : case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
417 159 : if( FD_UNLIKELY( !base_len ) ) return -1;
418 159 : ulong off = base[0]=='.';
419 159 : if( FD_UNLIKELY( !fd_x509_dns_name_valid( (char const *)base+off, base_len-off ) ) ) return -1;
420 159 : break;
421 159 : }
422 159 : case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress: address || mask */
423 90 : if( FD_UNLIKELY( base_len!=8UL && base_len!=32UL ) ) return -1;
424 90 : break;
425 90 : case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
426 12 : return -1;
427 138 : default:
428 138 : if( FD_UNLIKELY( !fd_x509_general_name_valid( tag, base, base_len ) ) ) return -1;
429 138 : break;
430 399 : }
431 399 : }
432 303 : return 0;
433 315 : }
434 :
435 : static int
436 : fd_x509_parse_extensions( fd_der_cursor_t * c,
437 321891 : fd_x509_cert_info_t * out ) {
438 :
439 321891 : uchar const * seen_oid[ FD_X509_EXT_MAX ];
440 321891 : ulong seen_len[ FD_X509_EXT_MAX ];
441 321891 : ulong seen_cnt = 0UL;
442 :
443 965691 : while( FD_DER_HAS_MORE( *c ) ) {
444 : /* Each Extension is a SEQUENCE { OID, BOOLEAN?, OCTET STRING } */
445 643992 : uchar const * ext_ptr; ulong ext_len;
446 643992 : FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, ext_ptr, ext_len );
447 :
448 643992 : fd_der_cursor_t ext = { .p = ext_ptr, .end = ext_ptr + ext_len };
449 :
450 643992 : uchar const * oid_raw; ulong oid_raw_len;
451 643992 : FD_DER_READ_RAW( ext, FD_DER_TAG_OID, oid_raw, oid_raw_len );
452 :
453 643989 : if( FD_UNLIKELY( seen_cnt==FD_X509_EXT_MAX ) ) return -1;
454 1098087 : for( ulong i=0UL; i<seen_cnt; i++ ) {
455 454119 : if( FD_UNLIKELY( fd_der_oid_match( oid_raw, oid_raw_len, seen_oid[i], seen_len[i] ) ) ) return -1;
456 454119 : }
457 643968 : seen_oid[ seen_cnt ] = oid_raw;
458 643968 : seen_len[ seen_cnt++ ] = oid_raw_len;
459 :
460 643968 : int critical = 0;
461 643968 : if( ext.p<ext.end && *ext.p==FD_DER_TAG_BOOLEAN ) {
462 629373 : uchar const * critical_ptr; ulong critical_len;
463 629373 : FD_DER_READ( ext, FD_DER_TAG_BOOLEAN, critical_ptr, critical_len );
464 : /* critical is DEFAULT FALSE, so DER permits only explicit TRUE. */
465 629373 : if( FD_UNLIKELY( critical_len!=1UL || critical_ptr[0]!=0xFF ) ) return -1;
466 629370 : critical = 1;
467 629370 : }
468 :
469 : /* OCTET STRING wrapping the extension value */
470 643965 : uchar const * val_ptr; ulong val_len;
471 643965 : FD_DER_READ( ext, FD_DER_TAG_OCTET_STRING, val_ptr, val_len );
472 643956 : if( FD_UNLIKELY( FD_DER_HAS_MORE( ext ) ) ) return -1;
473 :
474 : /* basicConstraints (2.5.29.19)
475 : BasicConstraints ::= SEQUENCE {
476 : cA BOOLEAN DEFAULT FALSE,
477 : pathLenConstraint INTEGER (0..MAX) OPTIONAL } */
478 643953 : if( fd_der_oid_match( oid_raw, oid_raw_len,
479 643953 : oid_basic_constraints, sizeof(oid_basic_constraints) ) ) {
480 : /* RFC 5280 Section 4.2 permits at most one instance of an extension. */
481 315384 : if( FD_UNLIKELY( out->has_basic_constraints ) ) return -1;
482 :
483 315384 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
484 315384 : FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
485 315381 : int bc_tag; FD_DER_PEEK_TAG_OR( val, bc_tag, 0 );
486 315381 : if( bc_tag == (int)FD_DER_TAG_BOOLEAN ) {
487 2835 : uchar const * ca_ptr; ulong ca_len;
488 2835 : FD_DER_READ( val, FD_DER_TAG_BOOLEAN, ca_ptr, ca_len );
489 : /* cA is DEFAULT FALSE, so DER permits only explicit TRUE. */
490 2835 : if( FD_UNLIKELY( ca_len!=1UL || ca_ptr[0]!=0xFF ) ) return -1;
491 2826 : if( FD_UNLIKELY( !critical ) ) return -1;
492 2823 : out->is_ca = 1;
493 2823 : }
494 315369 : int path_len_tag; FD_DER_PEEK_TAG_OR( val, path_len_tag, 0 );
495 315369 : if( path_len_tag == (int)FD_DER_TAG_INTEGER ) {
496 549 : if( FD_UNLIKELY( !out->is_ca ) ) return -1;
497 546 : uchar const * path_len_ptr; ulong path_len_len;
498 546 : FD_DER_READ( val, FD_DER_TAG_INTEGER, path_len_ptr, path_len_len );
499 546 : if( FD_UNLIKELY( fd_x509_parse_path_len( path_len_ptr, path_len_len,
500 546 : &out->path_len_constraint ) ) ) return -1;
501 537 : out->has_path_len_constraint = 1;
502 537 : }
503 315357 : FD_DER_LEAVE( val ); /* rejects unconsumed SEQUENCE content */
504 : /* Reject trailing bytes in the extension's OCTET STRING */
505 315357 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
506 315354 : out->has_basic_constraints = 1;
507 315354 : continue;
508 315357 : }
509 :
510 : /* keyUsage (2.5.29.15)
511 : KeyUsage ::= BIT STRING { digitalSignature(0) ... decipherOnly(8) } */
512 328569 : if( fd_der_oid_match( oid_raw, oid_raw_len,
513 328569 : oid_key_usage, sizeof(oid_key_usage) ) ) {
514 : /* RFC 5280 Section 4.2 permits at most one instance of an extension */
515 1341 : if( FD_UNLIKELY( out->has_key_usage ) ) return -1;
516 :
517 1341 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
518 1341 : uchar const * bs; ulong bs_len;
519 1341 : FD_DER_READ( val, FD_DER_TAG_BIT_STRING, bs, bs_len );
520 : /* Reject trailing bytes in the extension's OCTET STRING */
521 1338 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
522 :
523 : /* Content is unused_bits || bits. A 9 bit NamedBitList needs at
524 : most two octets of bits. */
525 1335 : if( FD_UNLIKELY( bs_len<2UL || bs_len>3UL ) ) return -1;
526 1320 : uint unused = bs[0];
527 1320 : if( FD_UNLIKELY( unused>7U ) ) return -1;
528 :
529 : /* DER zeroes the unused bits and trims trailing zero bits, so the
530 : final octet has no unused bits set and is itself nonzero.
531 :
532 : Exception: a trailing zero octet is allowed. Two Mozilla roots
533 : encode keyUsage as 07 06 00 instead of 01 06:
534 : Trustwave Global ECC P256 Certification Authority
535 : sha256 945bbc825ea554f489d1fd51a73ddf2ea624ac7019a05205225c22a78ccfa8b4
536 : Trustwave Global ECC P384 Certification Authority
537 : sha256 55903859c8c0c3ebb8759ece4e2557225ff5758bbd38ebd48276601e1bd58097 */
538 1317 : uchar last = bs[ bs_len-1UL ];
539 1317 : if( FD_UNLIKELY( last & (uchar)( ( 1U<<unused ) - 1U ) ) ) return -1;
540 1314 : if( FD_LIKELY( last ) ) {
541 1302 : uint canonical_unused = 0U;
542 8205 : while( !(last & (uchar)(1U<<canonical_unused)) ) canonical_unused++;
543 1302 : if( FD_UNLIKELY( unused!=canonical_unused ) ) return -1;
544 1302 : } else if( FD_UNLIKELY( bs_len<3UL || !bs[1] ) ) {
545 6 : return -1;
546 6 : }
547 1305 : out->key_usage = (ushort)( ( (uint)bs[1] << 8 ) |
548 1305 : ( bs_len>2UL ? (uint)bs[2] : 0U ) );
549 1305 : out->has_key_usage = 1;
550 1305 : continue;
551 1314 : }
552 :
553 : /* extKeyUsage (2.5.29.37)
554 : ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId */
555 327228 : if( fd_der_oid_match( oid_raw, oid_raw_len,
556 327228 : oid_ext_key_usage, sizeof(oid_ext_key_usage) ) ) {
557 2238 : if( FD_UNLIKELY( out->has_ext_key_usage ) ) return -1;
558 :
559 2238 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
560 2238 : ulong kp_cnt = 0UL;
561 2238 : FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
562 5253 : while( FD_DER_HAS_MORE( val ) ) {
563 3030 : uchar const * kp; ulong kp_len;
564 3030 : FD_DER_READ_RAW( val, FD_DER_TAG_OID, kp, kp_len );
565 3015 : kp_cnt++;
566 3015 : if( fd_der_oid_match( kp, kp_len, oid_kp_server_auth, sizeof(oid_kp_server_auth) ) )
567 1584 : out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_SERVER_AUTH );
568 1431 : else if( fd_der_oid_match( kp, kp_len, oid_kp_any, sizeof(oid_kp_any) ) )
569 15 : out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_ANY );
570 : /* Other key purposes are ignored */
571 3015 : }
572 2223 : FD_DER_LEAVE( val );
573 2223 : if( FD_UNLIKELY( !kp_cnt ) ) return -1; /* SIZE (1..MAX) */
574 2220 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
575 2217 : out->has_ext_key_usage = 1;
576 2217 : continue;
577 2220 : }
578 :
579 : /* subjectAltName (2.5.29.17) */
580 324990 : if( fd_der_oid_match( oid_raw, oid_raw_len,
581 324990 : oid_san, sizeof(oid_san) ) ) {
582 318147 : if( FD_UNLIKELY( out->has_subject_alt_name ) ) return -1;
583 318147 : if( FD_UNLIKELY( out->subject_len==2UL && !critical ) ) return -1;
584 :
585 318141 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
586 :
587 : /* SEQUENCE OF GeneralName */
588 318141 : uchar const * san_ptr; ulong san_len;
589 318141 : FD_DER_READ( val, FD_DER_TAG_SEQUENCE, san_ptr, san_len );
590 318141 : if( FD_UNLIKELY( !san_len ) ) return -1; /* SIZE (1..MAX) */
591 318138 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
592 :
593 318138 : out->san_general_names = san_ptr;
594 318138 : out->san_general_names_len = san_len;
595 318138 : out->has_subject_alt_name = 1;
596 :
597 318138 : fd_der_cursor_t san = { .p = san_ptr, .end = san_ptr + san_len };
598 641586 : while( FD_DER_HAS_MORE( san ) ) {
599 323484 : int gn_tag; ulong gn_len;
600 323484 : if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return -1;
601 323481 : if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
602 33 : return -1;
603 323448 : san.p += gn_len;
604 323448 : }
605 318102 : continue;
606 318138 : }
607 :
608 : /* nameConstraints (2.5.29.30) */
609 6843 : if( fd_der_oid_match( oid_raw, oid_raw_len,
610 6843 : oid_name_constraints, sizeof(oid_name_constraints) ) ) {
611 279 : if( FD_UNLIKELY( out->has_name_constraints || !critical ) ) return -1;
612 :
613 273 : fd_der_cursor_t val = { .p=val_ptr, .end=val_ptr+val_len };
614 273 : FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
615 273 : int last_tag = -1;
616 576 : while( FD_DER_HAS_MORE( val ) ) {
617 315 : int tag; ulong subtrees_len;
618 315 : if( FD_UNLIKELY( fd_der_read_tl( &val, &tag, &subtrees_len ) ) ) return -1;
619 315 : if( FD_UNLIKELY( (tag!=(int)FD_DER_TAG_CONTEXT(0) &&
620 315 : tag!=(int)FD_DER_TAG_CONTEXT(1)) || tag<=last_tag ) ) return -1;
621 315 : uchar const * subtrees = val.p;
622 315 : val.p += subtrees_len;
623 315 : if( FD_UNLIKELY( fd_x509_subtrees_valid( subtrees, subtrees_len ) ) ) return -1;
624 303 : if( tag==(int)FD_DER_TAG_CONTEXT(0) ) {
625 195 : out->name_constraints_permitted = subtrees;
626 195 : out->name_constraints_permitted_len = subtrees_len;
627 195 : } else {
628 108 : out->name_constraints_excluded = subtrees;
629 108 : out->name_constraints_excluded_len = subtrees_len;
630 108 : }
631 303 : last_tag = tag;
632 303 : }
633 261 : if( FD_UNLIKELY( last_tag<0 ) ) return -1;
634 258 : FD_DER_LEAVE( val );
635 258 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
636 258 : out->has_name_constraints = 1;
637 258 : continue;
638 258 : }
639 :
640 : /* Unknown extension */
641 6564 : if( FD_UNLIKELY( critical ) ) return -1;
642 6564 : }
643 :
644 321699 : return 0;
645 321891 : }
646 :
647 : /* dec2 reads a two digit decimal number, or -1 if either character is
648 : not an ASCII digit. */
649 :
650 : static int
651 4181886 : dec2( uchar const * s ) {
652 4181886 : if( FD_UNLIKELY( s[0]<'0' || s[0]>'9' || s[1]<'0' || s[1]>'9' ) ) return -1;
653 4181880 : return (s[0]-'0')*10 + (s[1]-'0');
654 4181886 : }
655 :
656 : /* days_from_civil returns the number of days between 1970-01-01 and
657 : y-m-d (proleptic Gregorian). Howard Hinnant's algorithm, valid for
658 : any year representable in a long. */
659 :
660 : static long
661 : days_from_civil( long y,
662 : long m,
663 644373 : long d ) {
664 644373 : y -= m<=2;
665 644373 : long era = (y>=0 ? y : y-399) / 400;
666 644373 : long yoe = y - era*400; /* [0, 399] */
667 644373 : long doy = (153*(m + (m>2 ? -3 : 9)) + 2)/5 + d-1; /* [0, 365] */
668 644373 : long doe = yoe*365 + yoe/4 - yoe/100 + doy; /* [0, 146096] */
669 644373 : return era*146097L + doe - 719468L;
670 644373 : }
671 :
672 : long
673 : fd_x509_time_parse( uchar tag,
674 : uchar const * s,
675 644424 : ulong s_len ) {
676 644424 : long year;
677 :
678 644424 : if( tag==FD_DER_TAG_UTC_TIME ) {
679 328950 : if( FD_UNLIKELY( s_len!=13UL ) ) return FD_X509_TIME_INVALID;
680 328935 : int yy = dec2( s );
681 328935 : if( FD_UNLIKELY( yy<0 ) ) return FD_X509_TIME_INVALID;
682 : /* RFC 5280 Section 4.1.2.5.1 */
683 328935 : year = yy>=50 ? 1900L+yy : 2000L+yy;
684 328935 : s += 2;
685 328935 : } else if( tag==FD_DER_TAG_GENERALIZED_TIME ) {
686 315471 : if( FD_UNLIKELY( s_len!=15UL ) ) return FD_X509_TIME_INVALID;
687 315468 : int hi = dec2( s );
688 315468 : int lo = dec2( s+2 );
689 315468 : if( FD_UNLIKELY( hi<0 || lo<0 ) ) return FD_X509_TIME_INVALID;
690 315468 : year = hi*100L + lo;
691 315468 : s += 4;
692 315468 : } else {
693 3 : return FD_X509_TIME_INVALID;
694 3 : }
695 :
696 644403 : int mon = dec2( s );
697 644403 : int day = dec2( s+2 );
698 644403 : int hh = dec2( s+4 );
699 644403 : int mm = dec2( s+6 );
700 644403 : int ss = dec2( s+8 );
701 644403 : if( FD_UNLIKELY( mon<0 || day<0 || hh<0 || mm<0 || ss<0 ) ) return FD_X509_TIME_INVALID;
702 644397 : if( FD_UNLIKELY( s[10]!='Z' ) ) return FD_X509_TIME_INVALID;
703 :
704 644397 : if( FD_UNLIKELY( mon<1 || mon>12 ) ) return FD_X509_TIME_INVALID;
705 :
706 644391 : static uchar const mon_days[ 12 ] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
707 644391 : int leap = ( !(year%4) && (year%100) ) || !(year%400);
708 644391 : int day_max = mon_days[ mon-1 ] + ( mon==2 && leap );
709 644391 : if( FD_UNLIKELY( day<1 || day>day_max ) ) return FD_X509_TIME_INVALID;
710 :
711 : /* Leap seconds (ss==60) are rejected: RFC 5280 does not require them */
712 644382 : if( FD_UNLIKELY( hh>23 || mm>59 || ss>59 ) ) return FD_X509_TIME_INVALID;
713 :
714 644373 : return days_from_civil( year, mon, day )*86400L + hh*3600L + mm*60L + ss;
715 644382 : }
716 :
717 : int
718 : fd_x509_cert_parse( uchar const * cert,
719 : ulong cert_sz,
720 322980 : fd_x509_cert_info_t * out ) {
721 :
722 322980 : if( FD_UNLIKELY( !cert || !out ) ) return -1;
723 :
724 322974 : fd_memset( out, 0, sizeof(fd_x509_cert_info_t) );
725 322974 : out->key_type = FD_X509_KEY_UNKNOWN;
726 322974 : out->sig_alg = FD_X509_SIG_UNKNOWN;
727 :
728 322974 : uchar const * tbs_sig_alg = NULL;
729 322974 : ulong tbs_sig_alg_len = 0UL;
730 :
731 322974 : FD_DER_CURSOR_FROM_BUF( c, cert, cert_sz );
732 :
733 322974 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
734 :
735 : /* tbsCertificate */
736 322212 : uchar const * tbs_start = c.p;
737 322212 : uchar const * tbs_ptr; ulong tbs_content_len;
738 322212 : FD_DER_READ( c, FD_DER_TAG_SEQUENCE, tbs_ptr, tbs_content_len );
739 322206 : out->tbs = tbs_start;
740 322206 : out->tbs_len = (ulong)( c.p - tbs_start );
741 :
742 322206 : {
743 322206 : fd_der_cursor_t tbs = { .p = tbs_ptr, .end = tbs_ptr + tbs_content_len };
744 :
745 : /* version [0] EXPLICIT Version DEFAULT v1 */
746 322206 : if( FD_DER_HAS_MORE( tbs ) && *tbs.p==FD_DER_TAG_CONTEXT(0) ) {
747 322206 : FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(0) );
748 322206 : uchar const * version; ulong version_len;
749 322206 : FD_DER_READ( tbs, FD_DER_TAG_INTEGER, version, version_len );
750 : /* Explicit v1 violates DEFAULT encoding. Only v1/v2/v3 exist. */
751 322203 : if( FD_UNLIKELY( version_len!=1UL || version[0]<1U || version[0]>2U ) ) return -1;
752 322200 : out->version = version[0];
753 322200 : FD_DER_LEAVE( tbs );
754 322206 : }
755 :
756 : /* serialNumber CertificateSerialNumber */
757 322200 : uchar const * serial; ulong serial_len;
758 322200 : FD_DER_READ( tbs, FD_DER_TAG_INTEGER, serial, serial_len );
759 322197 : if( FD_UNLIKELY( !fd_x509_serial_valid( serial, serial_len ) ) ) return -1;
760 :
761 : /* signature AlgorithmIdentifier SEQUENCE */
762 322194 : FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, tbs_sig_alg, tbs_sig_alg_len );
763 322194 : out->sig_alg = fd_x509_parse_sig_alg( tbs_sig_alg, tbs_sig_alg_len );
764 :
765 : /* issuer Name SEQUENCE */
766 322194 : FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->issuer, out->issuer_len );
767 322194 : if( FD_UNLIKELY( out->issuer_len==2UL ||
768 322194 : !fd_x509_name_valid( out->issuer, out->issuer_len ) ) ) return -1;
769 :
770 : /* validity SEQUENCE { notBefore, notAfter } */
771 322179 : FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
772 322179 : FD_DER_READ_TIME( tbs, out->not_before_tag, out->not_before, out->not_before_len );
773 322179 : FD_DER_READ_TIME( tbs, out->not_after_tag, out->not_after, out->not_after_len );
774 322179 : out->not_before_unix = fd_x509_time_parse( out->not_before_tag, out->not_before, out->not_before_len );
775 322179 : out->not_after_unix = fd_x509_time_parse( out->not_after_tag, out->not_after, out->not_after_len );
776 322179 : FD_DER_LEAVE( tbs );
777 :
778 : /* subject Name SEQUENCE */
779 322179 : FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->subject, out->subject_len );
780 322179 : if( FD_UNLIKELY( !fd_x509_name_valid( out->subject, out->subject_len ) ) ) return -1;
781 :
782 : /* subjectPublicKeyInfo SEQUENCE */
783 322140 : FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
784 322140 : if( FD_UNLIKELY( fd_x509_parse_spki( &tbs, &out->pubkey,
785 322140 : &out->pubkey_len,
786 322140 : &out->key_type ) ) )
787 12 : return -1;
788 322128 : FD_DER_LEAVE( tbs );
789 :
790 : /* issuerUniqueID [1]
791 : subjectUniqueID [2]
792 : extensions [3] */
793 322128 : int last_optional = 0;
794 643827 : while( FD_DER_HAS_MORE( tbs ) ) {
795 321894 : int next_tag;
796 321894 : FD_DER_PEEK_TAG( tbs, next_tag );
797 :
798 321894 : if( next_tag==(int)FD_DER_TAG_CONTEXT_PRIM(1) ||
799 321894 : next_tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) ) {
800 0 : int field = next_tag & 7;
801 0 : if( FD_UNLIKELY( out->version<1U || field<=last_optional ) ) return -1;
802 0 : int tag; ulong unique_id_len;
803 0 : if( FD_UNLIKELY( fd_der_read_tl( &tbs, &tag, &unique_id_len ) ) ) return -1;
804 0 : if( FD_UNLIKELY( !fd_x509_unique_id_valid( tbs.p, unique_id_len ) ) ) return -1;
805 0 : tbs.p += unique_id_len;
806 0 : last_optional = field;
807 321894 : } else if( next_tag == (int)FD_DER_TAG_CONTEXT(3) ) {
808 321894 : if( FD_UNLIKELY( out->version!=2U || last_optional>=3 ) ) return -1;
809 321891 : last_optional = 3;
810 321891 : FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(3) );
811 321891 : uchar const * ext_seq_ptr; ulong ext_seq_len;
812 321891 : FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, ext_seq_ptr, ext_seq_len );
813 321891 : if( FD_UNLIKELY( !ext_seq_len ) ) return -1;
814 321891 : fd_der_cursor_t ext = { .p = ext_seq_ptr, .end = ext_seq_ptr + ext_seq_len };
815 321891 : if( FD_UNLIKELY( fd_x509_parse_extensions( &ext, out ) ) ) return -1;
816 321699 : FD_DER_LEAVE( tbs );
817 321891 : } else {
818 0 : return -1;
819 0 : }
820 321894 : }
821 :
822 321933 : if( FD_UNLIKELY( out->subject_len==2UL && (out->is_ca || !out->has_subject_alt_name) ) ) return -1;
823 : /* RFC 5280 Sections 4.2.1.3, 4.2.1.9 and 4.2.1.10. Check after
824 : parsing every extension so their order cannot affect the result. */
825 321927 : if( FD_UNLIKELY( !out->is_ca &&
826 321927 : (out->has_name_constraints || (out->key_usage & FD_X509_KU_KEY_CERT_SIGN)) ) ) return -1;
827 321912 : if( FD_UNLIKELY( out->has_path_len_constraint && out->has_key_usage &&
828 321912 : !(out->key_usage & FD_X509_KU_KEY_CERT_SIGN) ) ) return -1;
829 321912 : }
830 :
831 : /* signatureAlgorithm must match the TBSCertificate field exactly. */
832 321906 : uchar const * outer_sig_alg; ulong outer_sig_alg_len;
833 321906 : FD_DER_READ( c, FD_DER_TAG_SEQUENCE, outer_sig_alg, outer_sig_alg_len );
834 321906 : if( FD_UNLIKELY( outer_sig_alg_len!=tbs_sig_alg_len ||
835 321906 : memcmp( outer_sig_alg, tbs_sig_alg, tbs_sig_alg_len ) ) ) return -1;
836 :
837 : /* signatureValue BIT STRING */
838 321903 : FD_DER_READ_BITS( c, out->sig, out->sig_len );
839 :
840 321903 : FD_DER_LEAVE( c );
841 :
842 : /* The supplied buffer is one DER Certificate, not a DER prefix. */
843 321903 : if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
844 :
845 321900 : return 0;
846 321903 : }
847 :
848 : /* Distinguished-name matching **********************************************/
849 :
850 : typedef struct {
851 : uchar const * p;
852 : ulong len;
853 : } fd_x509_der_slice_t;
854 :
855 : static int
856 : fd_x509_der_read( fd_der_cursor_t * c,
857 : int expected_tag,
858 : uchar const ** content,
859 37341 : ulong * content_len ) {
860 37341 : int tag;
861 37341 : if( FD_UNLIKELY( fd_der_read_tl( c, &tag, content_len ) || tag!=expected_tag ) ) return -1;
862 37320 : *content = c->p;
863 37320 : c->p += *content_len;
864 37320 : return 0;
865 37341 : }
866 :
867 : static int
868 : fd_x509_dn_case_ignore_oid( uchar const * oid,
869 4098 : ulong oid_len ) {
870 4098 : if( oid_len==3UL && oid[0]==0x55U && oid[1]==0x04U ) { /* 2.5.4 */
871 4083 : switch( oid[2] ) {
872 4038 : case 3U: /* commonName */
873 4038 : case 4U: /* surname */
874 4038 : case 5U: /* serialNumber */
875 4038 : case 6U: /* countryName */
876 4038 : case 7U: /* localityName */
877 4038 : case 8U: /* stateOrProvinceName */
878 4038 : case 9U: /* streetAddress */
879 4083 : case 10U: /* organizationName */
880 4083 : case 11U: /* organizationalUnitName */
881 4083 : case 12U: /* title */
882 4083 : case 13U: /* description */
883 4083 : case 15U: /* businessCategory */
884 4083 : case 17U: /* postalCode */
885 4083 : case 18U: /* postOfficeBox */
886 4083 : case 19U: /* physicalDeliveryOfficeName */
887 4083 : case 27U: /* destinationIndicator */
888 4083 : case 41U: /* name */
889 4083 : case 42U: /* givenName */
890 4083 : case 43U: /* initials */
891 4083 : case 44U: /* generationQualifier */
892 4083 : case 46U: /* dnQualifier */
893 4083 : case 51U: /* houseIdentifier */
894 4083 : case 54U: /* dmdName */
895 4083 : case 65U: /* pseudonym */
896 4083 : case 72U: /* role */
897 4083 : case 97U: /* organizationIdentifier */
898 4083 : return 1;
899 0 : default:
900 0 : return 0;
901 4083 : }
902 4083 : }
903 :
904 : /* userId 0.9.2342.19200300.100.1.1 and domainComponent ...100.1.25,
905 : both IA5String (RFC 4519: caseIgnoreIA5Match) */
906 15 : static uchar const oid_user_id[] = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x01 };
907 15 : static uchar const oid_dc[] = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x19 };
908 15 : return oid_len==sizeof(oid_user_id) &&
909 15 : ( !memcmp( oid, oid_user_id, sizeof(oid_user_id) ) || !memcmp( oid, oid_dc, sizeof(oid_dc) ) );
910 4098 : }
911 :
912 : /* Normalize a string value to UTF-8 with ASCII case folded, leading and
913 : trailing spaces trimmed, and internal runs of spaces collapsed (the
914 : RFC 5280 Section 7.1 profile of caseIgnoreMatch, as OpenSSL does it).
915 : UTF8String bytes pass through; BMPString and UniversalString code
916 : points are re-encoded, and TeletexString is read as Latin-1. out
917 : must hold FD_X509_DN_NORM_MAX bytes. Fails only on a value
918 : fd_x509_name_valid would have rejected. */
919 :
920 : #define FD_X509_DN_NORM_MAX (4UL*FD_X509_DN_VALUE_MAX)
921 :
922 : static int
923 : fd_x509_dn_string_normalize( int tag,
924 : uchar const * p,
925 : ulong len,
926 : uchar * out,
927 8172 : ulong * out_len ) {
928 8172 : ulong width = fd_x509_dn_string_width( tag );
929 8172 : if( FD_UNLIKELY( !width || len%width || len/width>FD_X509_DN_VALUE_MAX ) ) return -1;
930 8172 : int utf8_in = tag==(int)FD_DER_TAG_UTF8_STRING;
931 :
932 8172 : ulong j = 0UL;
933 8172 : int pending_space = 0;
934 136503 : for( ulong i=0UL; i<len; i+=width ) {
935 128331 : uint c = 0U;
936 256698 : for( ulong k=0UL; k<width; k++ ) c = (c<<8) | (uint)p[i+k];
937 128331 : if( c==' ' ) {
938 20634 : if( j ) pending_space = 1;
939 20634 : continue;
940 20634 : }
941 107697 : if( pending_space ) out[j++] = ' ';
942 107697 : pending_space = 0;
943 107697 : if( c>='A' && c<='Z' ) c += (uint)('a'-'A');
944 107697 : if( utf8_in || c<0x80U ) {
945 107688 : out[j++] = (uchar)c;
946 107688 : } else if( c<0x800U ) {
947 9 : out[j++] = (uchar)( 0xC0U | (c>>6) );
948 9 : out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
949 9 : } else if( c<0x10000U ) {
950 0 : out[j++] = (uchar)( 0xE0U | (c>>12) );
951 0 : out[j++] = (uchar)( 0x80U | ((c>>6) & 0x3FU) );
952 0 : out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
953 0 : } else if( c<0x110000U ) {
954 0 : out[j++] = (uchar)( 0xF0U | (c>>18) );
955 0 : out[j++] = (uchar)( 0x80U | ((c>>12) & 0x3FU) );
956 0 : out[j++] = (uchar)( 0x80U | ((c>>6) & 0x3FU) );
957 0 : out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
958 0 : } else {
959 0 : return -1;
960 0 : }
961 107697 : }
962 8172 : *out_len = j;
963 8172 : return 0;
964 8172 : }
965 :
966 : static int
967 : fd_x509_atv_equal( fd_x509_der_slice_t a,
968 4107 : fd_x509_der_slice_t b ) {
969 4107 : fd_der_cursor_t ac = { .p=a.p, .end=a.p+a.len };
970 4107 : fd_der_cursor_t bc = { .p=b.p, .end=b.p+b.len };
971 :
972 4107 : uchar const * aoid; ulong aoid_len;
973 4107 : uchar const * boid; ulong boid_len;
974 4107 : if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_OID, &aoid, &aoid_len ) ||
975 4107 : fd_x509_der_read( &bc, FD_DER_TAG_OID, &boid, &boid_len ) ) ) return 0;
976 4107 : if( aoid_len!=boid_len || memcmp( aoid, boid, aoid_len ) ) return 0;
977 :
978 4098 : int atag; ulong aval_len;
979 4098 : int btag; ulong bval_len;
980 4098 : if( FD_UNLIKELY( fd_der_read_tl( &ac, &atag, &aval_len ) ||
981 4098 : fd_der_read_tl( &bc, &btag, &bval_len ) ) ) return 0;
982 4098 : uchar const * aval = ac.p; ac.p += aval_len;
983 4098 : uchar const * bval = bc.p; bc.p += bval_len;
984 4098 : if( FD_UNLIKELY( ac.p!=ac.end || bc.p!=bc.end ) ) return 0;
985 :
986 4098 : if( fd_x509_dn_case_ignore_oid( aoid, aoid_len ) &&
987 4098 : fd_x509_dn_string_width( atag ) && fd_x509_dn_string_width( btag ) ) {
988 4086 : uchar anorm[ FD_X509_DN_NORM_MAX ]; ulong anorm_len;
989 4086 : uchar bnorm[ FD_X509_DN_NORM_MAX ]; ulong bnorm_len;
990 4086 : if( !fd_x509_dn_string_normalize( atag, aval, aval_len, anorm, &anorm_len ) &&
991 4086 : !fd_x509_dn_string_normalize( btag, bval, bval_len, bnorm, &bnorm_len ) )
992 4086 : return anorm_len==bnorm_len && !memcmp( anorm, bnorm, anorm_len );
993 4086 : }
994 :
995 12 : return atag==btag && aval_len==bval_len && !memcmp( aval, bval, aval_len );
996 4098 : }
997 :
998 : static int
999 : fd_x509_rdn_equal( uchar const * a,
1000 : ulong a_len,
1001 : uchar const * b,
1002 4107 : ulong b_len ) {
1003 4107 : fd_x509_der_slice_t aa[ FD_X509_RDN_ATV_MAX ]; ulong aa_cnt = 0UL;
1004 4107 : fd_x509_der_slice_t ba[ FD_X509_RDN_ATV_MAX ]; ulong ba_cnt = 0UL;
1005 4107 : fd_der_cursor_t ac = { .p=a, .end=a+a_len };
1006 4107 : fd_der_cursor_t bc = { .p=b, .end=b+b_len };
1007 :
1008 8214 : while( FD_DER_HAS_MORE( ac ) ) {
1009 4107 : if( FD_UNLIKELY( aa_cnt==FD_X509_RDN_ATV_MAX ||
1010 4107 : fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE,
1011 4107 : &aa[aa_cnt].p, &aa[aa_cnt].len ) ) ) return 0;
1012 4107 : aa_cnt++;
1013 4107 : }
1014 8214 : while( FD_DER_HAS_MORE( bc ) ) {
1015 4107 : if( FD_UNLIKELY( ba_cnt==FD_X509_RDN_ATV_MAX ||
1016 4107 : fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE,
1017 4107 : &ba[ba_cnt].p, &ba[ba_cnt].len ) ) ) return 0;
1018 4107 : ba_cnt++;
1019 4107 : }
1020 4107 : if( aa_cnt!=ba_cnt ) return 0;
1021 :
1022 4107 : uchar matched[ FD_X509_RDN_ATV_MAX ] = {0};
1023 4212 : for( ulong i=0UL; i<aa_cnt; i++ ) {
1024 4107 : ulong j=0UL;
1025 8109 : for( ; j<ba_cnt; j++ )
1026 4107 : if( !matched[j] && fd_x509_atv_equal( aa[i], ba[j] ) ) break;
1027 4107 : if( j==ba_cnt ) return 0;
1028 105 : matched[j] = 1U;
1029 105 : }
1030 105 : return 1;
1031 4107 : }
1032 :
1033 : /* fd_x509_name_cmp_rdns walks both Names RDN by RDN. Returns 1 if a
1034 : is exhausted (a's RDNs are a prefix of b's; with full=1 b must be
1035 : exhausted as well), 0 on a mismatch or malformed input. */
1036 :
1037 : static int
1038 : fd_x509_name_cmp_rdns( uchar const * a,
1039 : ulong a_len,
1040 : uchar const * b,
1041 : ulong b_len,
1042 6360 : int full ) {
1043 6360 : if( FD_UNLIKELY( (!a && a_len) || (!b && b_len) ) ) return 0;
1044 :
1045 6354 : FD_DER_CURSOR_FROM_BUF( ac, a, a_len );
1046 6354 : FD_DER_CURSOR_FROM_BUF( bc, b, b_len );
1047 6354 : uchar const * ap; ulong alen;
1048 6354 : uchar const * bp; ulong blen;
1049 6354 : if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE, &ap, &alen ) || ac.p!=ac.end ||
1050 6354 : fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE, &bp, &blen ) || bc.p!=bc.end ) ) return 0;
1051 :
1052 6336 : if( a_len==b_len && !memcmp( a, b, a_len ) ) return 1;
1053 :
1054 4113 : ac.p=ap; ac.end=ap+alen;
1055 4113 : bc.p=bp; bc.end=bp+blen;
1056 4218 : while( FD_DER_HAS_MORE( ac ) && FD_DER_HAS_MORE( bc ) ) {
1057 4110 : uchar const * ardn; ulong ardn_len;
1058 4110 : uchar const * brdn; ulong brdn_len;
1059 4110 : if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SET, &ardn, &ardn_len ) ||
1060 4110 : fd_x509_der_read( &bc, FD_DER_TAG_SET, &brdn, &brdn_len ) ||
1061 4110 : !fd_x509_rdn_equal( ardn, ardn_len, brdn, brdn_len ) ) ) return 0;
1062 4110 : }
1063 108 : return ac.p==ac.end && ( !full || bc.p==bc.end );
1064 4113 : }
1065 :
1066 : int
1067 : fd_x509_name_equal( uchar const * a,
1068 : ulong a_len,
1069 : uchar const * b,
1070 6306 : ulong b_len ) {
1071 6306 : return fd_x509_name_cmp_rdns( a, a_len, b, b_len, 1 );
1072 6306 : }
1073 :
1074 : int
1075 : fd_x509_name_prefix( uchar const * prefix,
1076 : ulong prefix_len,
1077 : uchar const * name,
1078 54 : ulong name_len ) {
1079 54 : return fd_x509_name_cmp_rdns( prefix, prefix_len, name, name_len, 0 );
1080 54 : }
1081 :
1082 : int
1083 : fd_x509_extract_pubkey( uchar const * cert,
1084 : ulong cert_sz,
1085 : uchar const ** out_pubkey,
1086 : ulong * out_pubkey_len,
1087 317988 : uchar * out_key_type ) {
1088 317988 : if( FD_UNLIKELY( !cert || !out_pubkey || !out_pubkey_len || !out_key_type ) ) return -1;
1089 317988 : fd_x509_cert_info_t info;
1090 317988 : if( FD_UNLIKELY( fd_x509_cert_parse( cert, cert_sz, &info ) ) ) return -1;
1091 317238 : if( FD_UNLIKELY( info.key_type==FD_X509_KEY_UNKNOWN ) ) return -1;
1092 317238 : *out_pubkey = info.pubkey;
1093 317238 : *out_pubkey_len = info.pubkey_len;
1094 317238 : *out_key_type = info.key_type;
1095 317238 : return 0;
1096 317238 : }
1097 :
1098 : /* Hostname matching (RFC 6125 Section 6.4.3) */
1099 :
1100 : int
1101 : fd_x509_dns_eq_ci( char const * a,
1102 : char const * b,
1103 1311 : ulong len ) {
1104 20193 : for( ulong i=0UL; i<len; i++ ) {
1105 18891 : char x = a[i]; if( x>='A' && x<='Z' ) x = (char)( x + ('a'-'A') );
1106 18891 : char y = b[i]; if( y>='A' && y<='Z' ) y = (char)( y + ('a'-'A') );
1107 18891 : if( x!=y ) return 0;
1108 18891 : }
1109 1302 : return 1;
1110 1311 : }
1111 :
1112 : int
1113 : fd_x509_dns_name_valid( char const * name,
1114 328731 : ulong len ) {
1115 328731 : if( len<1UL || len>253UL ) return 0;
1116 :
1117 328716 : ulong label_len = 0UL;
1118 3355116 : for( ulong i=0UL; i<len; i++ ) {
1119 3026439 : uchar c = (uchar)name[i];
1120 :
1121 3026439 : if( c=='.' ) {
1122 25227 : if( label_len<1UL || label_len>63UL ) return 0;
1123 25212 : if( name[i-1UL]=='-' ) return 0;
1124 25212 : label_len = 0UL;
1125 25212 : continue;
1126 25212 : }
1127 :
1128 3001212 : int alnum = ( c>='a' && c<='z' ) || ( c>='A' && c<='Z' ) || ( c>='0' && c<='9' );
1129 3001212 : if( !( alnum || c=='-' || c=='_' ) ) return 0;
1130 3001188 : if( c=='-' && label_len==0UL ) return 0;
1131 3001188 : label_len++;
1132 3001188 : }
1133 :
1134 328677 : if( label_len<1UL || label_len>63UL ) return 0;
1135 328671 : if( name[len-1UL]=='-' ) return 0;
1136 :
1137 328671 : return 1;
1138 328671 : }
1139 :
1140 : static int
1141 : dns_pattern_matches( char const * pattern,
1142 : ulong pattern_len,
1143 : char const * hostname,
1144 2406 : ulong hostname_len ) {
1145 :
1146 : /* Wildcard: *.example.com matches foo.example.com. The wildcard is
1147 : only recognized as the whole leftmost label, and the remainder
1148 : must span at least two labels, so *.com matches nothing. */
1149 :
1150 2406 : if( pattern_len>=2UL && pattern[0]=='*' && pattern[1]=='.' ) {
1151 27 : char const * pattern_tail = pattern + 2;
1152 27 : ulong pattern_tail_len = pattern_len - 2UL;
1153 :
1154 27 : if( !fd_x509_dns_name_valid( pattern_tail, pattern_tail_len ) ) return 0;
1155 27 : if( !memchr( pattern_tail, '.', pattern_tail_len ) ) return 0;
1156 :
1157 24 : ulong dot_pos = 0UL;
1158 102 : for( ; dot_pos<hostname_len; dot_pos++ ) {
1159 102 : if( hostname[dot_pos]=='.' ) break;
1160 102 : }
1161 :
1162 : /* The wildcard must consume a non-empty label. */
1163 :
1164 24 : if( dot_pos==0UL || dot_pos>=hostname_len ) return 0;
1165 :
1166 24 : char const * host_tail = hostname + dot_pos + 1UL;
1167 24 : ulong host_tail_len = hostname_len - dot_pos - 1UL;
1168 :
1169 24 : return host_tail_len==pattern_tail_len &&
1170 24 : fd_x509_dns_eq_ci( pattern_tail, host_tail, pattern_tail_len );
1171 24 : }
1172 :
1173 : /* Exact match (case-insensitive) */
1174 :
1175 2379 : return pattern_len==hostname_len &&
1176 2379 : fd_x509_dns_name_valid( pattern, pattern_len ) &&
1177 2379 : fd_x509_dns_eq_ci( pattern, hostname, pattern_len );
1178 2406 : }
1179 :
1180 : int
1181 : fd_x509_san_matches( fd_x509_cert_info_t const * info,
1182 : char const * hostname,
1183 1389 : ulong hostname_len ) {
1184 :
1185 1389 : if( FD_UNLIKELY( !info || !hostname ) ) return 0;
1186 :
1187 : /* Treat an absolute DNS reference name as equivalent to its relative
1188 : spelling. The root label is not part of the dNSName SAN value. */
1189 :
1190 1383 : if( hostname_len && hostname[ hostname_len-1UL ]=='.' ) hostname_len--;
1191 :
1192 1383 : if( FD_UNLIKELY( !fd_x509_dns_name_valid( hostname, hostname_len ) ) ) return 0;
1193 :
1194 : /* An IPv4 literal is matched byte-for-byte against iPAddress SANs
1195 : (RFC 6125 Section 1.7.2, RFC 5280 Section 4.2.1.6) and never
1196 : against dNSName SANs. IPv6 literals contain ':' and are rejected
1197 : by fd_x509_dns_name_valid above; the HTTP clients are IPv4-only. */
1198 :
1199 1353 : int is_ip4 = 0;
1200 1353 : uchar ip4[ 4 ];
1201 1353 : if( hostname_len<=15UL ) {
1202 1344 : char ip4_cstr[ 16 ];
1203 1344 : memcpy( ip4_cstr, hostname, hostname_len );
1204 1344 : ip4_cstr[ hostname_len ] = '\0';
1205 1344 : uint addr;
1206 1344 : if( fd_cstr_to_ip4_addr( ip4_cstr, &addr ) ) {
1207 36 : is_ip4 = 1;
1208 : /* opposite endianness of FD_IP4_ADDR */
1209 36 : ip4[0] = (uchar)( addr );
1210 36 : ip4[1] = (uchar)( addr >> 8 );
1211 36 : ip4[2] = (uchar)( addr >> 16 );
1212 36 : ip4[3] = (uchar)( addr >> 24 );
1213 36 : }
1214 1344 : }
1215 1353 : if( FD_UNLIKELY( !info->has_subject_alt_name ) ) return 0;
1216 :
1217 1350 : fd_der_cursor_t san = { .p = info->san_general_names,
1218 1350 : .end = info->san_general_names + info->san_general_names_len };
1219 1350 : int matched = 0;
1220 3834 : while( FD_DER_HAS_MORE( san ) ) {
1221 2526 : int gn_tag; ulong gn_len;
1222 2526 : if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return 0;
1223 2526 : if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
1224 42 : return 0;
1225 :
1226 2484 : if( is_ip4 ) {
1227 51 : if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(7) && gn_len==4UL &&
1228 51 : !memcmp( san.p, ip4, 4UL ) ) matched = 1;
1229 2433 : } else {
1230 2433 : if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) &&
1231 2433 : dns_pattern_matches( (char const *)san.p, gn_len, hostname, hostname_len ) ) matched = 1;
1232 2433 : }
1233 2484 : san.p += gn_len;
1234 2484 : }
1235 :
1236 1308 : return matched;
1237 1350 : }
1238 :
1239 : int
1240 : fd_x509_decode_ecdsa_sig( uchar const * der,
1241 : ulong der_len,
1242 : uchar * raw_sig,
1243 1950 : ulong scalar_sz ) {
1244 :
1245 1950 : if( FD_UNLIKELY( !der || !raw_sig ) ) return -1;
1246 :
1247 1944 : FD_DER_CURSOR_FROM_BUF( c, der, der_len );
1248 :
1249 1944 : uchar const * r_ptr; ulong r_len;
1250 1944 : uchar const * s_ptr; ulong s_len;
1251 :
1252 1944 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
1253 1941 : FD_DER_READ( c, FD_DER_TAG_INTEGER, r_ptr, r_len );
1254 1941 : FD_DER_READ( c, FD_DER_TAG_INTEGER, s_ptr, s_len );
1255 1941 : FD_DER_LEAVE( c );
1256 :
1257 1938 : if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
1258 :
1259 1935 : if( FD_UNLIKELY( fd_der_int_to_fixed( r_ptr, r_len, raw_sig, scalar_sz ) ) ) return -1;
1260 1929 : if( FD_UNLIKELY( fd_der_int_to_fixed( s_ptr, s_len, raw_sig + scalar_sz, scalar_sz ) ) ) return -1;
1261 :
1262 1929 : return 0;
1263 1929 : }
1264 :
1265 : int
1266 : fd_x509_ec_point_compress( uchar const * uncompressed,
1267 : ulong coord_sz,
1268 1959 : uchar * compressed ) {
1269 1959 : if( FD_UNLIKELY( !uncompressed || !compressed ) ) return -1;
1270 1953 : if( FD_UNLIKELY( coord_sz!=32UL && coord_sz!=48UL ) ) return -1;
1271 1941 : if( coord_sz==32UL )
1272 1770 : return fd_secp256r1_public_key_compress( compressed, uncompressed )
1273 1770 : ==FD_SECP256R1_SUCCESS ? 0 : -1;
1274 171 : else
1275 171 : return fd_secp384r1_public_key_compress( compressed, uncompressed )
1276 171 : ==FD_SECP384R1_SUCCESS ? 0 : -1;
1277 1941 : }
|