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 0 : fd_x509_dn_string_width( int tag ) {
20 0 : switch( tag ) {
21 0 : case FD_DER_TAG_UTF8_STRING:
22 0 : case FD_DER_TAG_PRINTABLE_STR:
23 0 : case FD_DER_TAG_TELETEX_STRING:
24 0 : case FD_DER_TAG_IA5_STRING: return 1UL;
25 0 : case FD_DER_TAG_BMP_STRING: return 2UL;
26 0 : case FD_DER_TAG_UNIVERSAL_STRING: return 4UL;
27 0 : default: return 0UL;
28 0 : }
29 0 : }
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 0 : ulong oid_len ) {
97 0 : if( alg_len!=oid_len && alg_len!=oid_len+sizeof(der_null) ) return 0;
98 0 : if( memcmp( alg, oid, oid_len ) ) return 0;
99 0 : return alg_len==oid_len || !memcmp( alg+oid_len, der_null, sizeof(der_null) );
100 0 : }
101 :
102 : static uchar
103 0 : fd_x509_parse_sig_alg( uchar const * alg, ulong alg_len ) {
104 0 : if( fd_der_oid_match( alg, alg_len, oid_ed25519, sizeof(oid_ed25519) ) )
105 0 : return FD_X509_SIG_ED25519;
106 0 : if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha256, sizeof(oid_ecdsa_sha256) ) )
107 0 : return FD_X509_SIG_ECDSA_SHA256;
108 0 : if( fd_der_oid_match( alg, alg_len, oid_ecdsa_sha384, sizeof(oid_ecdsa_sha384) ) )
109 0 : return FD_X509_SIG_ECDSA_SHA384;
110 0 : if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha256, sizeof(oid_rsa_sha256) ) )
111 0 : return FD_X509_SIG_RSA_SHA256;
112 0 : if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha384, sizeof(oid_rsa_sha384) ) )
113 0 : return FD_X509_SIG_RSA_SHA384;
114 0 : if( fd_x509_rsa_alg_match( alg, alg_len, oid_rsa_sha512, sizeof(oid_rsa_sha512) ) )
115 0 : return FD_X509_SIG_RSA_SHA512;
116 0 : return FD_X509_SIG_UNKNOWN;
117 0 : }
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 0 : ulong len ) {
126 0 : if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return 0;
127 0 : if( p[0]==0x00 ) {
128 0 : if( FD_UNLIKELY( len==1UL || !(p[1] & 0x80U) ) ) return 0;
129 0 : }
130 0 : return 1;
131 0 : }
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 0 : fd_rsa_pubkey_t * key ) {
141 0 : fd_der_cursor_t c = { .p=p, .end=p+len };
142 0 : uchar const * n; ulong n_len;
143 0 : uchar const * e; ulong e_len;
144 0 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
145 0 : FD_DER_READ( c, FD_DER_TAG_INTEGER, n, n_len );
146 0 : FD_DER_READ( c, FD_DER_TAG_INTEGER, e, e_len );
147 0 : if( FD_UNLIKELY( !fd_x509_positive_integer( n, n_len ) ||
148 0 : !fd_x509_positive_integer( e, e_len ) ) ) return -1;
149 0 : FD_DER_LEAVE( c );
150 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
151 :
152 0 : #if FD_HAS_INT128
153 0 : fd_rsa_pubkey_t tmp[1];
154 0 : if( !key ) key = tmp;
155 0 : if( FD_UNLIKELY( !fd_rsa_pubkey_init( key, n, n_len, e, e_len ) ) ) return 1;
156 0 : return 0;
157 : #else
158 : (void)key;
159 : return 1;
160 : #endif
161 0 : }
162 :
163 : int
164 : fd_x509_decode_rsa_pubkey( uchar const * pubkey,
165 : ulong pubkey_len,
166 0 : fd_rsa_pubkey_t * key ) {
167 0 : if( FD_UNLIKELY( !pubkey || !key ) ) return -1;
168 0 : return fd_x509_parse_rsa_pubkey( pubkey, pubkey_len, key ) ? -1 : 0;
169 0 : }
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 0 : uchar * out_type ) {
176 :
177 : /* algorithm AlgorithmIdentifier ::= SEQUENCE {
178 : algorithm OBJECT IDENTIFIER,
179 : parameters ANY DEFINED BY algorithm OPTIONAL } */
180 0 : uchar const * alg_ptr; ulong alg_len;
181 0 : FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, alg_ptr, alg_len );
182 0 : fd_der_cursor_t alg = { .p = alg_ptr, .end = alg_ptr+alg_len };
183 :
184 0 : uchar const * oid; ulong oid_len;
185 0 : FD_DER_READ_RAW( alg, FD_DER_TAG_OID, oid, oid_len );
186 :
187 0 : uchar type = FD_X509_KEY_UNKNOWN;
188 0 : if( fd_der_oid_match( oid, oid_len, oid_ed25519, sizeof(oid_ed25519) ) ) {
189 : /* RFC 8410 Section 3: parameters MUST be absent */
190 0 : type = FD_X509_KEY_ED25519;
191 0 : } 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 0 : uchar const * curve; ulong curve_len;
194 0 : FD_DER_READ_RAW( alg, FD_DER_TAG_OID, curve, curve_len );
195 0 : if( fd_der_oid_match( curve, curve_len, oid_prime256v1, sizeof(oid_prime256v1) ) ) type = FD_X509_KEY_ECDSA_P256;
196 0 : else if( fd_der_oid_match( curve, curve_len, oid_secp384r1, sizeof(oid_secp384r1) ) ) type = FD_X509_KEY_ECDSA_P384;
197 0 : } 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 0 : if( FD_DER_HAS_MORE( alg ) ) {
201 0 : uchar const * params; ulong params_len;
202 0 : FD_DER_READ( alg, FD_DER_TAG_NULL, params, params_len );
203 0 : (void)params;
204 0 : if( FD_UNLIKELY( params_len ) ) return -1;
205 0 : }
206 0 : type = FD_X509_KEY_RSA;
207 0 : } else if( FD_DER_HAS_MORE( alg ) ) {
208 0 : FD_DER_SKIP( alg );
209 0 : }
210 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( alg ) ) ) return -1;
211 :
212 : /* subjectPublicKey BIT STRING */
213 0 : uchar const * bits; ulong bits_len;
214 0 : FD_DER_READ_BITS( *c, bits, bits_len );
215 :
216 0 : switch( type ) {
217 0 : case FD_X509_KEY_ED25519:
218 0 : if( FD_UNLIKELY( bits_len != 32 ) ) return -1;
219 0 : break;
220 0 : case FD_X509_KEY_ECDSA_P256: {
221 0 : if( FD_UNLIKELY( bits_len != 65 ) ) return -1;
222 0 : uchar compressed[ 33 ];
223 0 : if( FD_UNLIKELY( fd_secp256r1_public_key_compress( compressed, bits )
224 0 : !=FD_SECP256R1_SUCCESS ) ) return -1;
225 0 : break;
226 0 : }
227 0 : case FD_X509_KEY_ECDSA_P384: {
228 0 : if( FD_UNLIKELY( bits_len != 97 ) ) return -1;
229 0 : uchar compressed[ 49 ];
230 0 : if( FD_UNLIKELY( fd_secp384r1_public_key_compress( compressed, bits )
231 0 : !=FD_SECP384R1_SUCCESS ) ) return -1;
232 0 : break;
233 0 : }
234 0 : case FD_X509_KEY_RSA: {
235 0 : int err = fd_x509_parse_rsa_pubkey( bits, bits_len, NULL );
236 0 : if( FD_UNLIKELY( err<0 ) ) return -1;
237 0 : if( err ) type = FD_X509_KEY_UNKNOWN; /* e.g. RSA-1024 */
238 0 : break;
239 0 : }
240 0 : default: /* well-formed but unsupported */
241 0 : break;
242 0 : }
243 :
244 0 : *out_pk = bits;
245 0 : *out_pk_len = bits_len;
246 0 : *out_type = type;
247 0 : return 0;
248 0 : }
249 :
250 : static int
251 : fd_x509_parse_path_len( uchar const * p,
252 : ulong len,
253 0 : ulong * out ) {
254 : /* INTEGER is signed. pathLenConstraint is non-negative, and DER
255 : requires the shortest possible two's-complement encoding. */
256 0 : if( FD_UNLIKELY( !len || (p[0] & 0x80U) ) ) return -1;
257 0 : 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 0 : ulong path_len = 0UL;
263 0 : for( ulong i=0UL; i<len; i++ ) {
264 0 : if( FD_UNLIKELY( path_len>(~0UL >> 8) ) ) {
265 0 : path_len = ~0UL;
266 0 : break;
267 0 : }
268 0 : path_len = (path_len << 8) | (ulong)p[i];
269 0 : }
270 0 : *out = path_len;
271 0 : return 0;
272 0 : }
273 :
274 : static int
275 : fd_x509_serial_valid( uchar const * p,
276 0 : 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 0 : if( FD_UNLIKELY( !len || len>20UL || (p[0] & 0x80U) ) ) return 0;
284 0 : if( p[0]==0x00 ) {
285 0 : if( FD_UNLIKELY( len>1UL && !(p[1] & 0x80U) ) ) return 0;
286 0 : }
287 0 : return 1;
288 0 : }
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 0 : ulong len ) {
311 0 : fd_der_cursor_t c = { .p=p, .end=p+len };
312 0 : int tag; ulong seq_len;
313 0 : if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &seq_len ) ||
314 0 : tag!=(int)FD_DER_TAG_SEQUENCE || c.p+seq_len!=c.end ) ) return 0;
315 0 : while( FD_DER_HAS_MORE( c ) ) {
316 0 : ulong set_len;
317 0 : if( FD_UNLIKELY( fd_der_read_tl( &c, &tag, &set_len ) ||
318 0 : tag!=(int)FD_DER_TAG_SET || !set_len ) ) return 0;
319 0 : fd_der_cursor_t set = { .p=c.p, .end=c.p+set_len };
320 0 : c.p += set_len;
321 0 : ulong atv_cnt = 0UL;
322 0 : while( FD_DER_HAS_MORE( set ) ) {
323 0 : if( FD_UNLIKELY( ++atv_cnt>FD_X509_RDN_ATV_MAX ) ) return 0;
324 0 : ulong atv_len;
325 0 : if( FD_UNLIKELY( fd_der_read_tl( &set, &tag, &atv_len ) ||
326 0 : tag!=(int)FD_DER_TAG_SEQUENCE ) ) return 0;
327 0 : fd_der_cursor_t atv = { .p=set.p, .end=set.p+atv_len };
328 0 : set.p += atv_len;
329 0 : ulong oid_len;
330 0 : if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &oid_len ) ||
331 0 : tag!=(int)FD_DER_TAG_OID || !fd_der_oid_valid( atv.p, oid_len ) ) ) return 0;
332 0 : atv.p += oid_len;
333 0 : ulong val_len;
334 0 : if( FD_UNLIKELY( fd_der_read_tl( &atv, &tag, &val_len ) ) ) return 0;
335 0 : ulong width = fd_x509_dn_string_width( tag );
336 0 : if( width && FD_UNLIKELY( val_len%width || val_len/width>FD_X509_DN_VALUE_MAX ) ) return 0;
337 0 : if( tag==(int)FD_DER_TAG_UTF8_STRING &&
338 0 : FD_UNLIKELY( !fd_utf8_verify( (char const *)atv.p, val_len ) ) ) return 0;
339 0 : for( ulong i=0UL; width && i<val_len; i+=width ) {
340 0 : uint cp = 0U;
341 0 : for( ulong k=0UL; k<width; k++ ) cp = (cp<<8) | atv.p[i+k];
342 0 : if( width>1UL && FD_UNLIKELY( cp>0x10ffffU || (cp>=0xd800U && cp<=0xdfffU) ) ) return 0;
343 0 : if( tag==(int)FD_DER_TAG_IA5_STRING && FD_UNLIKELY( cp>0x7fU ) ) return 0;
344 0 : if( tag==(int)FD_DER_TAG_PRINTABLE_STR &&
345 0 : FD_UNLIKELY( !( (cp>='a' && cp<='z') || (cp>='A' && cp<='Z') ||
346 0 : (cp>='0' && cp<='9') || (cp && strchr( " '()+,-./:=?", (int)cp )) ) ) ) return 0;
347 0 : }
348 0 : atv.p += val_len;
349 0 : if( FD_UNLIKELY( atv.p!=atv.end ) ) return 0;
350 0 : }
351 0 : }
352 0 : return 1;
353 0 : }
354 :
355 : static int
356 : fd_x509_general_name_valid( int tag,
357 : uchar const * p,
358 0 : ulong len ) {
359 0 : switch( tag ) {
360 0 : case FD_DER_TAG_CONTEXT(0): /* otherName */
361 0 : case FD_DER_TAG_CONTEXT(3): /* x400Address */
362 0 : case FD_DER_TAG_CONTEXT(5): /* ediPartyName */
363 : /* not worth validating, we don't look at these */
364 0 : return !!len;
365 0 : case FD_DER_TAG_CONTEXT(4): /* directoryName: a Name SEQUENCE */
366 0 : return fd_x509_name_valid( p, len );
367 0 : case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
368 0 : case FD_DER_TAG_CONTEXT_PRIM(6): /* uniformResourceIdentifier */
369 0 : if( FD_UNLIKELY( !len ) ) return 0;
370 0 : for( ulong i=0UL; i<len; i++ )
371 0 : if( FD_UNLIKELY( p[i] & 0x80U ) ) return 0;
372 0 : return 1;
373 0 : case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
374 0 : ulong off = len>2UL && p[0]=='*' && p[1]=='.' ? 2UL : 0UL;
375 0 : return fd_x509_dns_name_valid( (char const *)p+off, len-off );
376 0 : }
377 0 : case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress */
378 0 : return len==4UL || len==16UL;
379 0 : case FD_DER_TAG_CONTEXT_PRIM(8): /* registeredID */
380 0 : return fd_der_oid_valid( p, len );
381 0 : default:
382 0 : return 0;
383 0 : }
384 0 : }
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 0 : ulong len ) {
401 0 : fd_der_cursor_t trees = { .p=p, .end=p+len };
402 0 : if( FD_UNLIKELY( !len ) ) return -1;
403 :
404 0 : while( FD_DER_HAS_MORE( trees ) ) {
405 0 : uchar const * tree_ptr; ulong tree_len;
406 0 : FD_DER_READ( trees, FD_DER_TAG_SEQUENCE, tree_ptr, tree_len );
407 0 : fd_der_cursor_t tree = { .p=tree_ptr, .end=tree_ptr+tree_len };
408 :
409 0 : int tag; ulong base_len;
410 0 : if( FD_UNLIKELY( fd_der_read_tl( &tree, &tag, &base_len ) ) ) return -1;
411 0 : uchar const * base = tree.p;
412 0 : tree.p += base_len;
413 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( tree ) ) ) return -1;
414 :
415 0 : switch( tag ) {
416 0 : case FD_DER_TAG_CONTEXT_PRIM(2): { /* dNSName */
417 0 : if( FD_UNLIKELY( !base_len ) ) return -1;
418 0 : ulong off = base[0]=='.';
419 0 : if( FD_UNLIKELY( !fd_x509_dns_name_valid( (char const *)base+off, base_len-off ) ) ) return -1;
420 0 : break;
421 0 : }
422 0 : case FD_DER_TAG_CONTEXT_PRIM(7): /* iPAddress: address || mask */
423 0 : if( FD_UNLIKELY( base_len!=8UL && base_len!=32UL ) ) return -1;
424 0 : break;
425 0 : case FD_DER_TAG_CONTEXT_PRIM(1): /* rfc822Name */
426 0 : return -1;
427 0 : default:
428 0 : if( FD_UNLIKELY( !fd_x509_general_name_valid( tag, base, base_len ) ) ) return -1;
429 0 : break;
430 0 : }
431 0 : }
432 0 : return 0;
433 0 : }
434 :
435 : static int
436 : fd_x509_parse_extensions( fd_der_cursor_t * c,
437 0 : fd_x509_cert_info_t * out ) {
438 :
439 0 : uchar const * seen_oid[ FD_X509_EXT_MAX ];
440 0 : ulong seen_len[ FD_X509_EXT_MAX ];
441 0 : ulong seen_cnt = 0UL;
442 :
443 0 : while( FD_DER_HAS_MORE( *c ) ) {
444 : /* Each Extension is a SEQUENCE { OID, BOOLEAN?, OCTET STRING } */
445 0 : uchar const * ext_ptr; ulong ext_len;
446 0 : FD_DER_READ( *c, FD_DER_TAG_SEQUENCE, ext_ptr, ext_len );
447 :
448 0 : fd_der_cursor_t ext = { .p = ext_ptr, .end = ext_ptr + ext_len };
449 :
450 0 : uchar const * oid_raw; ulong oid_raw_len;
451 0 : FD_DER_READ_RAW( ext, FD_DER_TAG_OID, oid_raw, oid_raw_len );
452 :
453 0 : if( FD_UNLIKELY( seen_cnt==FD_X509_EXT_MAX ) ) return -1;
454 0 : for( ulong i=0UL; i<seen_cnt; i++ ) {
455 0 : if( FD_UNLIKELY( fd_der_oid_match( oid_raw, oid_raw_len, seen_oid[i], seen_len[i] ) ) ) return -1;
456 0 : }
457 0 : seen_oid[ seen_cnt ] = oid_raw;
458 0 : seen_len[ seen_cnt++ ] = oid_raw_len;
459 :
460 0 : int critical = 0;
461 0 : if( ext.p<ext.end && *ext.p==FD_DER_TAG_BOOLEAN ) {
462 0 : uchar const * critical_ptr; ulong critical_len;
463 0 : FD_DER_READ( ext, FD_DER_TAG_BOOLEAN, critical_ptr, critical_len );
464 : /* critical is DEFAULT FALSE, so DER permits only explicit TRUE. */
465 0 : if( FD_UNLIKELY( critical_len!=1UL || critical_ptr[0]!=0xFF ) ) return -1;
466 0 : critical = 1;
467 0 : }
468 :
469 : /* OCTET STRING wrapping the extension value */
470 0 : uchar const * val_ptr; ulong val_len;
471 0 : FD_DER_READ( ext, FD_DER_TAG_OCTET_STRING, val_ptr, val_len );
472 0 : 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 0 : if( fd_der_oid_match( oid_raw, oid_raw_len,
479 0 : oid_basic_constraints, sizeof(oid_basic_constraints) ) ) {
480 : /* RFC 5280 Section 4.2 permits at most one instance of an extension. */
481 0 : if( FD_UNLIKELY( out->has_basic_constraints ) ) return -1;
482 :
483 0 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
484 0 : FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
485 0 : int bc_tag; FD_DER_PEEK_TAG_OR( val, bc_tag, 0 );
486 0 : if( bc_tag == (int)FD_DER_TAG_BOOLEAN ) {
487 0 : uchar const * ca_ptr; ulong ca_len;
488 0 : FD_DER_READ( val, FD_DER_TAG_BOOLEAN, ca_ptr, ca_len );
489 : /* cA is DEFAULT FALSE, so DER permits only explicit TRUE. */
490 0 : if( FD_UNLIKELY( ca_len!=1UL || ca_ptr[0]!=0xFF ) ) return -1;
491 0 : if( FD_UNLIKELY( !critical ) ) return -1;
492 0 : out->is_ca = 1;
493 0 : }
494 0 : int path_len_tag; FD_DER_PEEK_TAG_OR( val, path_len_tag, 0 );
495 0 : if( path_len_tag == (int)FD_DER_TAG_INTEGER ) {
496 0 : if( FD_UNLIKELY( !out->is_ca ) ) return -1;
497 0 : uchar const * path_len_ptr; ulong path_len_len;
498 0 : FD_DER_READ( val, FD_DER_TAG_INTEGER, path_len_ptr, path_len_len );
499 0 : if( FD_UNLIKELY( fd_x509_parse_path_len( path_len_ptr, path_len_len,
500 0 : &out->path_len_constraint ) ) ) return -1;
501 0 : out->has_path_len_constraint = 1;
502 0 : }
503 0 : FD_DER_LEAVE( val ); /* rejects unconsumed SEQUENCE content */
504 : /* Reject trailing bytes in the extension's OCTET STRING */
505 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
506 0 : out->has_basic_constraints = 1;
507 0 : continue;
508 0 : }
509 :
510 : /* keyUsage (2.5.29.15)
511 : KeyUsage ::= BIT STRING { digitalSignature(0) ... decipherOnly(8) } */
512 0 : if( fd_der_oid_match( oid_raw, oid_raw_len,
513 0 : oid_key_usage, sizeof(oid_key_usage) ) ) {
514 : /* RFC 5280 Section 4.2 permits at most one instance of an extension */
515 0 : if( FD_UNLIKELY( out->has_key_usage ) ) return -1;
516 :
517 0 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
518 0 : uchar const * bs; ulong bs_len;
519 0 : FD_DER_READ( val, FD_DER_TAG_BIT_STRING, bs, bs_len );
520 : /* Reject trailing bytes in the extension's OCTET STRING */
521 0 : 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 0 : if( FD_UNLIKELY( bs_len<2UL || bs_len>3UL ) ) return -1;
526 0 : uint unused = bs[0];
527 0 : 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 0 : uchar last = bs[ bs_len-1UL ];
539 0 : if( FD_UNLIKELY( last & (uchar)( ( 1U<<unused ) - 1U ) ) ) return -1;
540 0 : if( FD_LIKELY( last ) ) {
541 0 : uint canonical_unused = 0U;
542 0 : while( !(last & (uchar)(1U<<canonical_unused)) ) canonical_unused++;
543 0 : if( FD_UNLIKELY( unused!=canonical_unused ) ) return -1;
544 0 : } else if( FD_UNLIKELY( bs_len<3UL || !bs[1] ) ) {
545 0 : return -1;
546 0 : }
547 0 : out->key_usage = (ushort)( ( (uint)bs[1] << 8 ) |
548 0 : ( bs_len>2UL ? (uint)bs[2] : 0U ) );
549 0 : out->has_key_usage = 1;
550 0 : continue;
551 0 : }
552 :
553 : /* extKeyUsage (2.5.29.37)
554 : ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId */
555 0 : if( fd_der_oid_match( oid_raw, oid_raw_len,
556 0 : oid_ext_key_usage, sizeof(oid_ext_key_usage) ) ) {
557 0 : if( FD_UNLIKELY( out->has_ext_key_usage ) ) return -1;
558 :
559 0 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
560 0 : ulong kp_cnt = 0UL;
561 0 : FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
562 0 : while( FD_DER_HAS_MORE( val ) ) {
563 0 : uchar const * kp; ulong kp_len;
564 0 : FD_DER_READ_RAW( val, FD_DER_TAG_OID, kp, kp_len );
565 0 : kp_cnt++;
566 0 : if( fd_der_oid_match( kp, kp_len, oid_kp_server_auth, sizeof(oid_kp_server_auth) ) )
567 0 : out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_SERVER_AUTH );
568 0 : else if( fd_der_oid_match( kp, kp_len, oid_kp_any, sizeof(oid_kp_any) ) )
569 0 : out->ext_key_usage = (ushort)( out->ext_key_usage | FD_X509_EKU_ANY );
570 : /* Other key purposes are ignored */
571 0 : }
572 0 : FD_DER_LEAVE( val );
573 0 : if( FD_UNLIKELY( !kp_cnt ) ) return -1; /* SIZE (1..MAX) */
574 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
575 0 : out->has_ext_key_usage = 1;
576 0 : continue;
577 0 : }
578 :
579 : /* subjectAltName (2.5.29.17) */
580 0 : if( fd_der_oid_match( oid_raw, oid_raw_len,
581 0 : oid_san, sizeof(oid_san) ) ) {
582 0 : if( FD_UNLIKELY( out->has_subject_alt_name ) ) return -1;
583 0 : if( FD_UNLIKELY( out->subject_len==2UL && !critical ) ) return -1;
584 :
585 0 : fd_der_cursor_t val = { .p = val_ptr, .end = val_ptr + val_len };
586 :
587 : /* SEQUENCE OF GeneralName */
588 0 : uchar const * san_ptr; ulong san_len;
589 0 : FD_DER_READ( val, FD_DER_TAG_SEQUENCE, san_ptr, san_len );
590 0 : if( FD_UNLIKELY( !san_len ) ) return -1; /* SIZE (1..MAX) */
591 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
592 :
593 0 : out->san_general_names = san_ptr;
594 0 : out->san_general_names_len = san_len;
595 0 : out->has_subject_alt_name = 1;
596 :
597 0 : fd_der_cursor_t san = { .p = san_ptr, .end = san_ptr + san_len };
598 0 : while( FD_DER_HAS_MORE( san ) ) {
599 0 : int gn_tag; ulong gn_len;
600 0 : if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return -1;
601 0 : if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
602 0 : return -1;
603 0 : san.p += gn_len;
604 0 : }
605 0 : continue;
606 0 : }
607 :
608 : /* nameConstraints (2.5.29.30) */
609 0 : if( fd_der_oid_match( oid_raw, oid_raw_len,
610 0 : oid_name_constraints, sizeof(oid_name_constraints) ) ) {
611 0 : if( FD_UNLIKELY( out->has_name_constraints || !critical ) ) return -1;
612 :
613 0 : fd_der_cursor_t val = { .p=val_ptr, .end=val_ptr+val_len };
614 0 : FD_DER_ENTER( val, FD_DER_TAG_SEQUENCE );
615 0 : int last_tag = -1;
616 0 : while( FD_DER_HAS_MORE( val ) ) {
617 0 : int tag; ulong subtrees_len;
618 0 : if( FD_UNLIKELY( fd_der_read_tl( &val, &tag, &subtrees_len ) ) ) return -1;
619 0 : if( FD_UNLIKELY( (tag!=(int)FD_DER_TAG_CONTEXT(0) &&
620 0 : tag!=(int)FD_DER_TAG_CONTEXT(1)) || tag<=last_tag ) ) return -1;
621 0 : uchar const * subtrees = val.p;
622 0 : val.p += subtrees_len;
623 0 : if( FD_UNLIKELY( fd_x509_subtrees_valid( subtrees, subtrees_len ) ) ) return -1;
624 0 : if( tag==(int)FD_DER_TAG_CONTEXT(0) ) {
625 0 : out->name_constraints_permitted = subtrees;
626 0 : out->name_constraints_permitted_len = subtrees_len;
627 0 : } else {
628 0 : out->name_constraints_excluded = subtrees;
629 0 : out->name_constraints_excluded_len = subtrees_len;
630 0 : }
631 0 : last_tag = tag;
632 0 : }
633 0 : if( FD_UNLIKELY( last_tag<0 ) ) return -1;
634 0 : FD_DER_LEAVE( val );
635 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( val ) ) ) return -1;
636 0 : out->has_name_constraints = 1;
637 0 : continue;
638 0 : }
639 :
640 : /* Unknown extension */
641 0 : if( FD_UNLIKELY( critical ) ) return -1;
642 0 : }
643 :
644 0 : return 0;
645 0 : }
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 0 : dec2( uchar const * s ) {
652 0 : if( FD_UNLIKELY( s[0]<'0' || s[0]>'9' || s[1]<'0' || s[1]>'9' ) ) return -1;
653 0 : return (s[0]-'0')*10 + (s[1]-'0');
654 0 : }
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 0 : long d ) {
664 0 : y -= m<=2;
665 0 : long era = (y>=0 ? y : y-399) / 400;
666 0 : long yoe = y - era*400; /* [0, 399] */
667 0 : long doy = (153*(m + (m>2 ? -3 : 9)) + 2)/5 + d-1; /* [0, 365] */
668 0 : long doe = yoe*365 + yoe/4 - yoe/100 + doy; /* [0, 146096] */
669 0 : return era*146097L + doe - 719468L;
670 0 : }
671 :
672 : long
673 : fd_x509_time_parse( uchar tag,
674 : uchar const * s,
675 0 : ulong s_len ) {
676 0 : long year;
677 :
678 0 : if( tag==FD_DER_TAG_UTC_TIME ) {
679 0 : if( FD_UNLIKELY( s_len!=13UL ) ) return FD_X509_TIME_INVALID;
680 0 : int yy = dec2( s );
681 0 : if( FD_UNLIKELY( yy<0 ) ) return FD_X509_TIME_INVALID;
682 : /* RFC 5280 Section 4.1.2.5.1 */
683 0 : year = yy>=50 ? 1900L+yy : 2000L+yy;
684 0 : s += 2;
685 0 : } else if( tag==FD_DER_TAG_GENERALIZED_TIME ) {
686 0 : if( FD_UNLIKELY( s_len!=15UL ) ) return FD_X509_TIME_INVALID;
687 0 : int hi = dec2( s );
688 0 : int lo = dec2( s+2 );
689 0 : if( FD_UNLIKELY( hi<0 || lo<0 ) ) return FD_X509_TIME_INVALID;
690 0 : year = hi*100L + lo;
691 0 : s += 4;
692 0 : } else {
693 0 : return FD_X509_TIME_INVALID;
694 0 : }
695 :
696 0 : int mon = dec2( s );
697 0 : int day = dec2( s+2 );
698 0 : int hh = dec2( s+4 );
699 0 : int mm = dec2( s+6 );
700 0 : int ss = dec2( s+8 );
701 0 : if( FD_UNLIKELY( mon<0 || day<0 || hh<0 || mm<0 || ss<0 ) ) return FD_X509_TIME_INVALID;
702 0 : if( FD_UNLIKELY( s[10]!='Z' ) ) return FD_X509_TIME_INVALID;
703 :
704 0 : if( FD_UNLIKELY( mon<1 || mon>12 ) ) return FD_X509_TIME_INVALID;
705 :
706 0 : static uchar const mon_days[ 12 ] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
707 0 : int leap = ( !(year%4) && (year%100) ) || !(year%400);
708 0 : int day_max = mon_days[ mon-1 ] + ( mon==2 && leap );
709 0 : 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 0 : if( FD_UNLIKELY( hh>23 || mm>59 || ss>59 ) ) return FD_X509_TIME_INVALID;
713 :
714 0 : return days_from_civil( year, mon, day )*86400L + hh*3600L + mm*60L + ss;
715 0 : }
716 :
717 : int
718 : fd_x509_cert_parse( uchar const * cert,
719 : ulong cert_sz,
720 0 : fd_x509_cert_info_t * out ) {
721 :
722 0 : if( FD_UNLIKELY( !cert || !out ) ) return -1;
723 :
724 0 : fd_memset( out, 0, sizeof(fd_x509_cert_info_t) );
725 0 : out->key_type = FD_X509_KEY_UNKNOWN;
726 0 : out->sig_alg = FD_X509_SIG_UNKNOWN;
727 :
728 0 : uchar const * tbs_sig_alg = NULL;
729 0 : ulong tbs_sig_alg_len = 0UL;
730 :
731 0 : FD_DER_CURSOR_FROM_BUF( c, cert, cert_sz );
732 :
733 0 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
734 :
735 : /* tbsCertificate */
736 0 : uchar const * tbs_start = c.p;
737 0 : uchar const * tbs_ptr; ulong tbs_content_len;
738 0 : FD_DER_READ( c, FD_DER_TAG_SEQUENCE, tbs_ptr, tbs_content_len );
739 0 : out->tbs = tbs_start;
740 0 : out->tbs_len = (ulong)( c.p - tbs_start );
741 :
742 0 : {
743 0 : fd_der_cursor_t tbs = { .p = tbs_ptr, .end = tbs_ptr + tbs_content_len };
744 :
745 : /* version [0] EXPLICIT Version DEFAULT v1 */
746 0 : if( FD_DER_HAS_MORE( tbs ) && *tbs.p==FD_DER_TAG_CONTEXT(0) ) {
747 0 : FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(0) );
748 0 : uchar const * version; ulong version_len;
749 0 : FD_DER_READ( tbs, FD_DER_TAG_INTEGER, version, version_len );
750 : /* Explicit v1 violates DEFAULT encoding. Only v1/v2/v3 exist. */
751 0 : if( FD_UNLIKELY( version_len!=1UL || version[0]<1U || version[0]>2U ) ) return -1;
752 0 : out->version = version[0];
753 0 : FD_DER_LEAVE( tbs );
754 0 : }
755 :
756 : /* serialNumber CertificateSerialNumber */
757 0 : uchar const * serial; ulong serial_len;
758 0 : FD_DER_READ( tbs, FD_DER_TAG_INTEGER, serial, serial_len );
759 0 : if( FD_UNLIKELY( !fd_x509_serial_valid( serial, serial_len ) ) ) return -1;
760 :
761 : /* signature AlgorithmIdentifier SEQUENCE */
762 0 : FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, tbs_sig_alg, tbs_sig_alg_len );
763 0 : out->sig_alg = fd_x509_parse_sig_alg( tbs_sig_alg, tbs_sig_alg_len );
764 :
765 : /* issuer Name SEQUENCE */
766 0 : FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->issuer, out->issuer_len );
767 0 : if( FD_UNLIKELY( out->issuer_len==2UL ||
768 0 : !fd_x509_name_valid( out->issuer, out->issuer_len ) ) ) return -1;
769 :
770 : /* validity SEQUENCE { notBefore, notAfter } */
771 0 : FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
772 0 : FD_DER_READ_TIME( tbs, out->not_before_tag, out->not_before, out->not_before_len );
773 0 : FD_DER_READ_TIME( tbs, out->not_after_tag, out->not_after, out->not_after_len );
774 0 : out->not_before_unix = fd_x509_time_parse( out->not_before_tag, out->not_before, out->not_before_len );
775 0 : out->not_after_unix = fd_x509_time_parse( out->not_after_tag, out->not_after, out->not_after_len );
776 0 : FD_DER_LEAVE( tbs );
777 :
778 : /* subject Name SEQUENCE */
779 0 : FD_DER_READ_RAW( tbs, FD_DER_TAG_SEQUENCE, out->subject, out->subject_len );
780 0 : if( FD_UNLIKELY( !fd_x509_name_valid( out->subject, out->subject_len ) ) ) return -1;
781 :
782 : /* subjectPublicKeyInfo SEQUENCE */
783 0 : FD_DER_ENTER( tbs, FD_DER_TAG_SEQUENCE );
784 0 : if( FD_UNLIKELY( fd_x509_parse_spki( &tbs, &out->pubkey,
785 0 : &out->pubkey_len,
786 0 : &out->key_type ) ) )
787 0 : return -1;
788 0 : FD_DER_LEAVE( tbs );
789 :
790 : /* issuerUniqueID [1]
791 : subjectUniqueID [2]
792 : extensions [3] */
793 0 : int last_optional = 0;
794 0 : while( FD_DER_HAS_MORE( tbs ) ) {
795 0 : int next_tag;
796 0 : FD_DER_PEEK_TAG( tbs, next_tag );
797 :
798 0 : if( next_tag==(int)FD_DER_TAG_CONTEXT_PRIM(1) ||
799 0 : 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 0 : } else if( next_tag == (int)FD_DER_TAG_CONTEXT(3) ) {
808 0 : if( FD_UNLIKELY( out->version!=2U || last_optional>=3 ) ) return -1;
809 0 : last_optional = 3;
810 0 : FD_DER_ENTER( tbs, FD_DER_TAG_CONTEXT(3) );
811 0 : uchar const * ext_seq_ptr; ulong ext_seq_len;
812 0 : FD_DER_READ( tbs, FD_DER_TAG_SEQUENCE, ext_seq_ptr, ext_seq_len );
813 0 : if( FD_UNLIKELY( !ext_seq_len ) ) return -1;
814 0 : fd_der_cursor_t ext = { .p = ext_seq_ptr, .end = ext_seq_ptr + ext_seq_len };
815 0 : if( FD_UNLIKELY( fd_x509_parse_extensions( &ext, out ) ) ) return -1;
816 0 : FD_DER_LEAVE( tbs );
817 0 : } else {
818 0 : return -1;
819 0 : }
820 0 : }
821 :
822 0 : 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 0 : if( FD_UNLIKELY( !out->is_ca &&
826 0 : (out->has_name_constraints || (out->key_usage & FD_X509_KU_KEY_CERT_SIGN)) ) ) return -1;
827 0 : if( FD_UNLIKELY( out->has_path_len_constraint && out->has_key_usage &&
828 0 : !(out->key_usage & FD_X509_KU_KEY_CERT_SIGN) ) ) return -1;
829 0 : }
830 :
831 : /* signatureAlgorithm must match the TBSCertificate field exactly. */
832 0 : uchar const * outer_sig_alg; ulong outer_sig_alg_len;
833 0 : FD_DER_READ( c, FD_DER_TAG_SEQUENCE, outer_sig_alg, outer_sig_alg_len );
834 0 : if( FD_UNLIKELY( outer_sig_alg_len!=tbs_sig_alg_len ||
835 0 : memcmp( outer_sig_alg, tbs_sig_alg, tbs_sig_alg_len ) ) ) return -1;
836 :
837 : /* signatureValue BIT STRING */
838 0 : FD_DER_READ_BITS( c, out->sig, out->sig_len );
839 :
840 0 : FD_DER_LEAVE( c );
841 :
842 : /* The supplied buffer is one DER Certificate, not a DER prefix. */
843 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
844 :
845 0 : return 0;
846 0 : }
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 0 : ulong * content_len ) {
860 0 : int tag;
861 0 : if( FD_UNLIKELY( fd_der_read_tl( c, &tag, content_len ) || tag!=expected_tag ) ) return -1;
862 0 : *content = c->p;
863 0 : c->p += *content_len;
864 0 : return 0;
865 0 : }
866 :
867 : static int
868 : fd_x509_dn_case_ignore_oid( uchar const * oid,
869 0 : ulong oid_len ) {
870 0 : if( oid_len==3UL && oid[0]==0x55U && oid[1]==0x04U ) { /* 2.5.4 */
871 0 : switch( oid[2] ) {
872 0 : case 3U: /* commonName */
873 0 : case 4U: /* surname */
874 0 : case 5U: /* serialNumber */
875 0 : case 6U: /* countryName */
876 0 : case 7U: /* localityName */
877 0 : case 8U: /* stateOrProvinceName */
878 0 : case 9U: /* streetAddress */
879 0 : case 10U: /* organizationName */
880 0 : case 11U: /* organizationalUnitName */
881 0 : case 12U: /* title */
882 0 : case 13U: /* description */
883 0 : case 15U: /* businessCategory */
884 0 : case 17U: /* postalCode */
885 0 : case 18U: /* postOfficeBox */
886 0 : case 19U: /* physicalDeliveryOfficeName */
887 0 : case 27U: /* destinationIndicator */
888 0 : case 41U: /* name */
889 0 : case 42U: /* givenName */
890 0 : case 43U: /* initials */
891 0 : case 44U: /* generationQualifier */
892 0 : case 46U: /* dnQualifier */
893 0 : case 51U: /* houseIdentifier */
894 0 : case 54U: /* dmdName */
895 0 : case 65U: /* pseudonym */
896 0 : case 72U: /* role */
897 0 : case 97U: /* organizationIdentifier */
898 0 : return 1;
899 0 : default:
900 0 : return 0;
901 0 : }
902 0 : }
903 :
904 : /* userId 0.9.2342.19200300.100.1.1 and domainComponent ...100.1.25,
905 : both IA5String (RFC 4519: caseIgnoreIA5Match) */
906 0 : static uchar const oid_user_id[] = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x01 };
907 0 : static uchar const oid_dc[] = { 0x09,0x92,0x26,0x89,0x93,0xf2,0x2c,0x64,0x01,0x19 };
908 0 : return oid_len==sizeof(oid_user_id) &&
909 0 : ( !memcmp( oid, oid_user_id, sizeof(oid_user_id) ) || !memcmp( oid, oid_dc, sizeof(oid_dc) ) );
910 0 : }
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 0 : ulong * out_len ) {
928 0 : ulong width = fd_x509_dn_string_width( tag );
929 0 : if( FD_UNLIKELY( !width || len%width || len/width>FD_X509_DN_VALUE_MAX ) ) return -1;
930 0 : int utf8_in = tag==(int)FD_DER_TAG_UTF8_STRING;
931 :
932 0 : ulong j = 0UL;
933 0 : int pending_space = 0;
934 0 : for( ulong i=0UL; i<len; i+=width ) {
935 0 : uint c = 0U;
936 0 : for( ulong k=0UL; k<width; k++ ) c = (c<<8) | (uint)p[i+k];
937 0 : if( c==' ' ) {
938 0 : if( j ) pending_space = 1;
939 0 : continue;
940 0 : }
941 0 : if( pending_space ) out[j++] = ' ';
942 0 : pending_space = 0;
943 0 : if( c>='A' && c<='Z' ) c += (uint)('a'-'A');
944 0 : if( utf8_in || c<0x80U ) {
945 0 : out[j++] = (uchar)c;
946 0 : } else if( c<0x800U ) {
947 0 : out[j++] = (uchar)( 0xC0U | (c>>6) );
948 0 : out[j++] = (uchar)( 0x80U | (c & 0x3FU) );
949 0 : } 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 0 : }
962 0 : *out_len = j;
963 0 : return 0;
964 0 : }
965 :
966 : static int
967 : fd_x509_atv_equal( fd_x509_der_slice_t a,
968 0 : fd_x509_der_slice_t b ) {
969 0 : fd_der_cursor_t ac = { .p=a.p, .end=a.p+a.len };
970 0 : fd_der_cursor_t bc = { .p=b.p, .end=b.p+b.len };
971 :
972 0 : uchar const * aoid; ulong aoid_len;
973 0 : uchar const * boid; ulong boid_len;
974 0 : if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_OID, &aoid, &aoid_len ) ||
975 0 : fd_x509_der_read( &bc, FD_DER_TAG_OID, &boid, &boid_len ) ) ) return 0;
976 0 : if( aoid_len!=boid_len || memcmp( aoid, boid, aoid_len ) ) return 0;
977 :
978 0 : int atag; ulong aval_len;
979 0 : int btag; ulong bval_len;
980 0 : if( FD_UNLIKELY( fd_der_read_tl( &ac, &atag, &aval_len ) ||
981 0 : fd_der_read_tl( &bc, &btag, &bval_len ) ) ) return 0;
982 0 : uchar const * aval = ac.p; ac.p += aval_len;
983 0 : uchar const * bval = bc.p; bc.p += bval_len;
984 0 : if( FD_UNLIKELY( ac.p!=ac.end || bc.p!=bc.end ) ) return 0;
985 :
986 0 : if( fd_x509_dn_case_ignore_oid( aoid, aoid_len ) &&
987 0 : fd_x509_dn_string_width( atag ) && fd_x509_dn_string_width( btag ) ) {
988 0 : uchar anorm[ FD_X509_DN_NORM_MAX ]; ulong anorm_len;
989 0 : uchar bnorm[ FD_X509_DN_NORM_MAX ]; ulong bnorm_len;
990 0 : if( !fd_x509_dn_string_normalize( atag, aval, aval_len, anorm, &anorm_len ) &&
991 0 : !fd_x509_dn_string_normalize( btag, bval, bval_len, bnorm, &bnorm_len ) )
992 0 : return anorm_len==bnorm_len && !memcmp( anorm, bnorm, anorm_len );
993 0 : }
994 :
995 0 : return atag==btag && aval_len==bval_len && !memcmp( aval, bval, aval_len );
996 0 : }
997 :
998 : static int
999 : fd_x509_rdn_equal( uchar const * a,
1000 : ulong a_len,
1001 : uchar const * b,
1002 0 : ulong b_len ) {
1003 0 : fd_x509_der_slice_t aa[ FD_X509_RDN_ATV_MAX ]; ulong aa_cnt = 0UL;
1004 0 : fd_x509_der_slice_t ba[ FD_X509_RDN_ATV_MAX ]; ulong ba_cnt = 0UL;
1005 0 : fd_der_cursor_t ac = { .p=a, .end=a+a_len };
1006 0 : fd_der_cursor_t bc = { .p=b, .end=b+b_len };
1007 :
1008 0 : while( FD_DER_HAS_MORE( ac ) ) {
1009 0 : if( FD_UNLIKELY( aa_cnt==FD_X509_RDN_ATV_MAX ||
1010 0 : fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE,
1011 0 : &aa[aa_cnt].p, &aa[aa_cnt].len ) ) ) return 0;
1012 0 : aa_cnt++;
1013 0 : }
1014 0 : while( FD_DER_HAS_MORE( bc ) ) {
1015 0 : if( FD_UNLIKELY( ba_cnt==FD_X509_RDN_ATV_MAX ||
1016 0 : fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE,
1017 0 : &ba[ba_cnt].p, &ba[ba_cnt].len ) ) ) return 0;
1018 0 : ba_cnt++;
1019 0 : }
1020 0 : if( aa_cnt!=ba_cnt ) return 0;
1021 :
1022 0 : uchar matched[ FD_X509_RDN_ATV_MAX ] = {0};
1023 0 : for( ulong i=0UL; i<aa_cnt; i++ ) {
1024 0 : ulong j=0UL;
1025 0 : for( ; j<ba_cnt; j++ )
1026 0 : if( !matched[j] && fd_x509_atv_equal( aa[i], ba[j] ) ) break;
1027 0 : if( j==ba_cnt ) return 0;
1028 0 : matched[j] = 1U;
1029 0 : }
1030 0 : return 1;
1031 0 : }
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 0 : int full ) {
1043 0 : if( FD_UNLIKELY( (!a && a_len) || (!b && b_len) ) ) return 0;
1044 :
1045 0 : FD_DER_CURSOR_FROM_BUF( ac, a, a_len );
1046 0 : FD_DER_CURSOR_FROM_BUF( bc, b, b_len );
1047 0 : uchar const * ap; ulong alen;
1048 0 : uchar const * bp; ulong blen;
1049 0 : if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SEQUENCE, &ap, &alen ) || ac.p!=ac.end ||
1050 0 : fd_x509_der_read( &bc, FD_DER_TAG_SEQUENCE, &bp, &blen ) || bc.p!=bc.end ) ) return 0;
1051 :
1052 0 : if( a_len==b_len && !memcmp( a, b, a_len ) ) return 1;
1053 :
1054 0 : ac.p=ap; ac.end=ap+alen;
1055 0 : bc.p=bp; bc.end=bp+blen;
1056 0 : while( FD_DER_HAS_MORE( ac ) && FD_DER_HAS_MORE( bc ) ) {
1057 0 : uchar const * ardn; ulong ardn_len;
1058 0 : uchar const * brdn; ulong brdn_len;
1059 0 : if( FD_UNLIKELY( fd_x509_der_read( &ac, FD_DER_TAG_SET, &ardn, &ardn_len ) ||
1060 0 : fd_x509_der_read( &bc, FD_DER_TAG_SET, &brdn, &brdn_len ) ||
1061 0 : !fd_x509_rdn_equal( ardn, ardn_len, brdn, brdn_len ) ) ) return 0;
1062 0 : }
1063 0 : return ac.p==ac.end && ( !full || bc.p==bc.end );
1064 0 : }
1065 :
1066 : int
1067 : fd_x509_name_equal( uchar const * a,
1068 : ulong a_len,
1069 : uchar const * b,
1070 0 : ulong b_len ) {
1071 0 : return fd_x509_name_cmp_rdns( a, a_len, b, b_len, 1 );
1072 0 : }
1073 :
1074 : int
1075 : fd_x509_name_prefix( uchar const * prefix,
1076 : ulong prefix_len,
1077 : uchar const * name,
1078 0 : ulong name_len ) {
1079 0 : return fd_x509_name_cmp_rdns( prefix, prefix_len, name, name_len, 0 );
1080 0 : }
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 0 : uchar * out_key_type ) {
1088 0 : if( FD_UNLIKELY( !cert || !out_pubkey || !out_pubkey_len || !out_key_type ) ) return -1;
1089 0 : fd_x509_cert_info_t info;
1090 0 : if( FD_UNLIKELY( fd_x509_cert_parse( cert, cert_sz, &info ) ) ) return -1;
1091 0 : if( FD_UNLIKELY( info.key_type==FD_X509_KEY_UNKNOWN ) ) return -1;
1092 0 : *out_pubkey = info.pubkey;
1093 0 : *out_pubkey_len = info.pubkey_len;
1094 0 : *out_key_type = info.key_type;
1095 0 : return 0;
1096 0 : }
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 0 : ulong len ) {
1104 0 : for( ulong i=0UL; i<len; i++ ) {
1105 0 : char x = a[i]; if( x>='A' && x<='Z' ) x = (char)( x + ('a'-'A') );
1106 0 : char y = b[i]; if( y>='A' && y<='Z' ) y = (char)( y + ('a'-'A') );
1107 0 : if( x!=y ) return 0;
1108 0 : }
1109 0 : return 1;
1110 0 : }
1111 :
1112 : int
1113 : fd_x509_dns_name_valid( char const * name,
1114 0 : ulong len ) {
1115 0 : if( len<1UL || len>253UL ) return 0;
1116 :
1117 0 : ulong label_len = 0UL;
1118 0 : for( ulong i=0UL; i<len; i++ ) {
1119 0 : uchar c = (uchar)name[i];
1120 :
1121 0 : if( c=='.' ) {
1122 0 : if( label_len<1UL || label_len>63UL ) return 0;
1123 0 : if( name[i-1UL]=='-' ) return 0;
1124 0 : label_len = 0UL;
1125 0 : continue;
1126 0 : }
1127 :
1128 0 : int alnum = ( c>='a' && c<='z' ) || ( c>='A' && c<='Z' ) || ( c>='0' && c<='9' );
1129 0 : if( !( alnum || c=='-' || c=='_' ) ) return 0;
1130 0 : if( c=='-' && label_len==0UL ) return 0;
1131 0 : label_len++;
1132 0 : }
1133 :
1134 0 : if( label_len<1UL || label_len>63UL ) return 0;
1135 0 : if( name[len-1UL]=='-' ) return 0;
1136 :
1137 0 : return 1;
1138 0 : }
1139 :
1140 : static int
1141 : dns_pattern_matches( char const * pattern,
1142 : ulong pattern_len,
1143 : char const * hostname,
1144 0 : 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 0 : if( pattern_len>=2UL && pattern[0]=='*' && pattern[1]=='.' ) {
1151 0 : char const * pattern_tail = pattern + 2;
1152 0 : ulong pattern_tail_len = pattern_len - 2UL;
1153 :
1154 0 : if( !fd_x509_dns_name_valid( pattern_tail, pattern_tail_len ) ) return 0;
1155 0 : if( !memchr( pattern_tail, '.', pattern_tail_len ) ) return 0;
1156 :
1157 0 : ulong dot_pos = 0UL;
1158 0 : for( ; dot_pos<hostname_len; dot_pos++ ) {
1159 0 : if( hostname[dot_pos]=='.' ) break;
1160 0 : }
1161 :
1162 : /* The wildcard must consume a non-empty label. */
1163 :
1164 0 : if( dot_pos==0UL || dot_pos>=hostname_len ) return 0;
1165 :
1166 0 : char const * host_tail = hostname + dot_pos + 1UL;
1167 0 : ulong host_tail_len = hostname_len - dot_pos - 1UL;
1168 :
1169 0 : return host_tail_len==pattern_tail_len &&
1170 0 : fd_x509_dns_eq_ci( pattern_tail, host_tail, pattern_tail_len );
1171 0 : }
1172 :
1173 : /* Exact match (case-insensitive) */
1174 :
1175 0 : return pattern_len==hostname_len &&
1176 0 : fd_x509_dns_name_valid( pattern, pattern_len ) &&
1177 0 : fd_x509_dns_eq_ci( pattern, hostname, pattern_len );
1178 0 : }
1179 :
1180 : int
1181 : fd_x509_san_matches( fd_x509_cert_info_t const * info,
1182 : char const * hostname,
1183 0 : ulong hostname_len ) {
1184 :
1185 0 : 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 0 : if( hostname_len && hostname[ hostname_len-1UL ]=='.' ) hostname_len--;
1191 :
1192 0 : 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 0 : int is_ip4 = 0;
1200 0 : uchar ip4[ 4 ];
1201 0 : if( hostname_len<=15UL ) {
1202 0 : char ip4_cstr[ 16 ];
1203 0 : memcpy( ip4_cstr, hostname, hostname_len );
1204 0 : ip4_cstr[ hostname_len ] = '\0';
1205 0 : uint addr;
1206 0 : if( fd_cstr_to_ip4_addr( ip4_cstr, &addr ) ) {
1207 0 : is_ip4 = 1;
1208 : /* opposite endianness of FD_IP4_ADDR */
1209 0 : ip4[0] = (uchar)( addr );
1210 0 : ip4[1] = (uchar)( addr >> 8 );
1211 0 : ip4[2] = (uchar)( addr >> 16 );
1212 0 : ip4[3] = (uchar)( addr >> 24 );
1213 0 : }
1214 0 : }
1215 0 : if( FD_UNLIKELY( !info->has_subject_alt_name ) ) return 0;
1216 :
1217 0 : fd_der_cursor_t san = { .p = info->san_general_names,
1218 0 : .end = info->san_general_names + info->san_general_names_len };
1219 0 : int matched = 0;
1220 0 : while( FD_DER_HAS_MORE( san ) ) {
1221 0 : int gn_tag; ulong gn_len;
1222 0 : if( FD_UNLIKELY( fd_der_read_tl( &san, &gn_tag, &gn_len ) ) ) return 0;
1223 0 : if( FD_UNLIKELY( !fd_x509_general_name_valid( gn_tag, san.p, gn_len ) ) )
1224 0 : return 0;
1225 :
1226 0 : if( is_ip4 ) {
1227 0 : if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(7) && gn_len==4UL &&
1228 0 : !memcmp( san.p, ip4, 4UL ) ) matched = 1;
1229 0 : } else {
1230 0 : if( gn_tag==(int)FD_DER_TAG_CONTEXT_PRIM(2) &&
1231 0 : dns_pattern_matches( (char const *)san.p, gn_len, hostname, hostname_len ) ) matched = 1;
1232 0 : }
1233 0 : san.p += gn_len;
1234 0 : }
1235 :
1236 0 : return matched;
1237 0 : }
1238 :
1239 : int
1240 : fd_x509_decode_ecdsa_sig( uchar const * der,
1241 : ulong der_len,
1242 : uchar * raw_sig,
1243 0 : ulong scalar_sz ) {
1244 :
1245 0 : if( FD_UNLIKELY( !der || !raw_sig ) ) return -1;
1246 :
1247 0 : FD_DER_CURSOR_FROM_BUF( c, der, der_len );
1248 :
1249 0 : uchar const * r_ptr; ulong r_len;
1250 0 : uchar const * s_ptr; ulong s_len;
1251 :
1252 0 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
1253 0 : FD_DER_READ( c, FD_DER_TAG_INTEGER, r_ptr, r_len );
1254 0 : FD_DER_READ( c, FD_DER_TAG_INTEGER, s_ptr, s_len );
1255 0 : FD_DER_LEAVE( c );
1256 :
1257 0 : if( FD_UNLIKELY( FD_DER_HAS_MORE( c ) ) ) return -1;
1258 :
1259 0 : if( FD_UNLIKELY( fd_der_int_to_fixed( r_ptr, r_len, raw_sig, scalar_sz ) ) ) return -1;
1260 0 : if( FD_UNLIKELY( fd_der_int_to_fixed( s_ptr, s_len, raw_sig + scalar_sz, scalar_sz ) ) ) return -1;
1261 :
1262 0 : return 0;
1263 0 : }
1264 :
1265 : int
1266 : fd_x509_ec_point_compress( uchar const * uncompressed,
1267 : ulong coord_sz,
1268 0 : uchar * compressed ) {
1269 0 : if( FD_UNLIKELY( !uncompressed || !compressed ) ) return -1;
1270 0 : if( FD_UNLIKELY( coord_sz!=32UL && coord_sz!=48UL ) ) return -1;
1271 0 : if( coord_sz==32UL )
1272 0 : return fd_secp256r1_public_key_compress( compressed, uncompressed )
1273 0 : ==FD_SECP256R1_SUCCESS ? 0 : -1;
1274 0 : else
1275 0 : return fd_secp384r1_public_key_compress( compressed, uncompressed )
1276 0 : ==FD_SECP384R1_SUCCESS ? 0 : -1;
1277 0 : }
|