Line data Source code
1 : #ifndef HEADER_fd_src_ballet_x509_fd_der_h
2 : #define HEADER_fd_src_ballet_x509_fd_der_h
3 :
4 : /* fd_der.h provides a parser for DER (Distinguished Encoding Rules)
5 : parsing for ASN.1 structures.
6 :
7 : All reads are bounded by the cursor's [p, end) window.
8 : ENTER narrows end to the content of the current TLV. LEAVE checks
9 : that exactly the right number of bytes were consumed and restores the
10 : outer window.
11 :
12 : All macros assume the enclosing function returns int, with the semantics
13 : success=0 & failure=nonzero. On any parse error, the macro evaluates
14 : to "return -1".
15 :
16 : An example of parsing an ECDSA DER signature:
17 :
18 : FD_DER_CURSOR_FROM_BUF( c, der, der_len );
19 : FD_DER_ENTER( c, FD_DER_TAG_SEQUENCE );
20 : FD_DER_READ( c, FD_DER_TAG_INTEGER, r_ptr, r_len );
21 : FD_DER_READ( c, FD_DER_TAG_INTEGER, s_ptr, s_len );
22 : FD_DER_LEAVE( c );
23 :
24 : */
25 :
26 : #include "../../util/fd_util_base.h"
27 :
28 : /* DER tags */
29 :
30 959838 : #define FD_DER_TAG_BOOLEAN ((uchar)0x01)
31 316437 : #define FD_DER_TAG_INTEGER ((uchar)0x02)
32 2127 : #define FD_DER_TAG_BIT_STRING ((uchar)0x03)
33 1161 : #define FD_DER_TAG_OCTET_STRING ((uchar)0x04)
34 15 : #define FD_DER_TAG_NULL ((uchar)0x05)
35 3 : #define FD_DER_TAG_OID ((uchar)0x06)
36 1020714 : #define FD_DER_TAG_UTF8_STRING ((uchar)0x0c)
37 348399 : #define FD_DER_TAG_TELETEX_STRING ((uchar)0x14)
38 10509 : #define FD_DER_TAG_SEQUENCE ((uchar)0x30)
39 216 : #define FD_DER_TAG_SET ((uchar)0x31)
40 7781352 : #define FD_DER_TAG_PRINTABLE_STR ((uchar)0x13)
41 7781367 : #define FD_DER_TAG_IA5_STRING ((uchar)0x16)
42 645561 : #define FD_DER_TAG_UTC_TIME ((uchar)0x17)
43 316524 : #define FD_DER_TAG_GENERALIZED_TIME ((uchar)0x18)
44 18 : #define FD_DER_TAG_UNIVERSAL_STRING ((uchar)0x1c)
45 48 : #define FD_DER_TAG_BMP_STRING ((uchar)0x1e)
46 :
47 : /* Context-specific tags [0]..[7] (EXPLICIT, constructed) */
48 :
49 646926 : #define FD_DER_TAG_CONTEXT(n) ((uchar)(0xA0 | (n)))
50 :
51 : /* Context-specific tags [0]..[7] (IMPLICIT, primitive) */
52 :
53 1297449 : #define FD_DER_TAG_CONTEXT_PRIM(n) ((uchar)(0x80 | (n)))
54 :
55 : /* fd_der_cursor_t is the read cursor into a DER buffer.
56 : p points to the next byte to read.
57 : end points one past the last readable byte.
58 : Always: p <= end. */
59 :
60 : typedef struct {
61 : uchar const * p;
62 : uchar const * end;
63 : } fd_der_cursor_t;
64 :
65 : FD_PROTOTYPES_BEGIN
66 :
67 : /* fd_der_read_tl reads a DER tag-length prefix from cursor c.
68 :
69 : On success, out_tag is the tag byte, out_len is the content length,
70 : c->p is advanced past the tag-length bytes, returns 0.
71 :
72 : On failure, c is in an undefined state, returns -1. */
73 :
74 : static inline int
75 : fd_der_read_tl( fd_der_cursor_t * c,
76 : int * out_tag,
77 11686179 : ulong * out_len ) {
78 :
79 11686179 : uchar const * p = c->p;
80 11686179 : uchar const * end = c->end;
81 :
82 : /* Tag byte */
83 11686179 : if( FD_UNLIKELY( p == end ) ) return -1;
84 11686146 : if( FD_UNLIKELY( (*p & 0x1fU)==0x1fU ) ) return -1; /* unsupported high-tag-number form */
85 11686137 : *out_tag = (int)*p++;
86 :
87 : /* Length byte */
88 11686137 : if( FD_UNLIKELY( p == end ) ) return -1;
89 11686125 : ulong len = *p++;
90 :
91 11686125 : if( len & 0x80 ) {
92 649980 : uint n_bytes = (uint)( len & 0x7F );
93 649980 : if( FD_UNLIKELY( n_bytes > 4 || n_bytes == 0 ) ) return -1;
94 649971 : if( FD_UNLIKELY( p == end || !p[0] ) ) return -1; /* no leading zero length octet */
95 649962 : len = 0;
96 1313610 : for( uint i = 0; i < n_bytes; i++ ) {
97 663651 : if( FD_UNLIKELY( p == end ) ) return -1;
98 663648 : len = ( len << 8 ) | *p++;
99 663648 : }
100 649959 : if( FD_UNLIKELY( len<128UL ) ) return -1; /* long form must be necessary */
101 649959 : }
102 :
103 11686098 : if( FD_UNLIKELY( (ulong)( end - p ) < len ) ) return -1;
104 :
105 11685339 : *out_len = len;
106 11685339 : c->p = p;
107 11685339 : return 0;
108 11686098 : }
109 :
110 : /* fd_der_int_to_fixed strips DER INTEGER padding and right-justifies
111 : into a fixed-size output buffer.
112 :
113 : p points to the INTEGER content bytes, len is the content length.
114 : out is zero-filled and receives the value right-justified into out_sz
115 : bytes.
116 :
117 : Returns 0 on success, -1 on failure. */
118 :
119 : static inline int
120 : fd_der_int_to_fixed( uchar const * p,
121 : ulong len,
122 : uchar * out,
123 3894 : ulong out_sz ) {
124 :
125 3894 : if( FD_UNLIKELY( !len ) ) return -1;
126 :
127 : /* ECDSA scalars are positive INTEGERs. DER uses a leading zero only
128 : when it is needed to keep the value positive. */
129 3891 : if( p[0]==0x00 ) {
130 1936 : if( FD_UNLIKELY( len==1UL || !(p[1] & 0x80U) ) ) return -1;
131 1927 : p++;
132 1927 : len--;
133 2085 : } else if( FD_UNLIKELY( p[0] & 0x80U ) ) return -1;
134 :
135 3876 : if( FD_UNLIKELY( len>out_sz ) ) return -1;
136 :
137 3870 : fd_memset( out, 0, out_sz );
138 3870 : fd_memcpy( out + ( out_sz - len ), p, len );
139 3870 : return 0;
140 3876 : }
141 :
142 : /* fd_der_oid_valid checks that p,len is a canonical DER OBJECT IDENTIFIER
143 : content encoding. Each subidentifier uses base 128, with bit 7 marking
144 : continuation. DER forbids leading zero base-128 digits. */
145 :
146 : static inline int
147 : fd_der_oid_valid( uchar const * p,
148 1303785 : ulong len ) {
149 1303785 : if( FD_UNLIKELY( !len ) ) return 0;
150 :
151 1303776 : int at_subidentifier_start = 1;
152 5257272 : for( ulong i=0UL; i<len; i++ ) {
153 3953517 : uchar octet = p[i];
154 3953517 : if( FD_UNLIKELY( at_subidentifier_start && octet==0x80U ) ) return 0;
155 3953496 : at_subidentifier_start = !(octet & 0x80U);
156 3953496 : }
157 1303755 : return at_subidentifier_start;
158 1303776 : }
159 :
160 : FD_PROTOTYPES_END
161 :
162 : /* Initialize a cursor from a buffer pointer and length. */
163 :
164 : #define FD_DER_CURSOR_FROM_BUF( c, buf, sz ) \
165 337698 : fd_der_cursor_t c = { .p = (uchar const *)(buf), \
166 337698 : .end = (sz) ? (uchar const *)(buf) + (sz) \
167 337698 : : (uchar const *)(buf) }
168 :
169 : /* Enter a constructed type (SEQUENCE, SET, etc.).
170 : Must be paired with FD_DER_LEAVE. */
171 :
172 : #define FD_DER_ENTER( c, expected_tag ) \
173 1932651 : do { \
174 1932651 : uchar const * _fd_der_outer_end_ = (c).end; \
175 1932651 : do { \
176 1932651 : int _fd_der_tag_; ulong _fd_der_len_; \
177 1932651 : if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_, \
178 1932651 : &_fd_der_len_ ) ) ) \
179 1932651 : return -1; \
180 1932651 : if( FD_UNLIKELY( _fd_der_tag_ != (int)(expected_tag) ) ) \
181 1931874 : return -1; \
182 1931874 : (c).end = (c).p + _fd_der_len_; \
183 1931871 : } while(0)
184 :
185 : /* Leave a constructed type. Checks that all content was consumed
186 : and restores the outer end pointer. */
187 :
188 : #define FD_DER_LEAVE( c ) \
189 1931289 : if( FD_UNLIKELY( (c).p != (c).end ) ) return -1; \
190 1931289 : (c).end = _fd_der_outer_end_; \
191 1931283 : } while(0)
192 :
193 : /* Leave a constructed type without requiring all content consumed.
194 : Advances past any remaining content. */
195 :
196 : #define FD_DER_LEAVE_RELAXED( c ) \
197 3 : (c).p = (c).end; \
198 3 : (c).end = _fd_der_outer_end_; \
199 3 : } while(0)
200 :
201 : /* Read a primitive TLV. Verifies the tag and sets ptr/len to the
202 : content bytes. Advances the cursor past the TLV. OBJECT IDENTIFIER
203 : content is additionally checked for canonical DER encoding. */
204 :
205 : #define FD_DER_READ( c, expected_tag, out_ptr, out_len ) \
206 5146893 : do { \
207 5146893 : int _fd_der_tag_; ulong _fd_der_len_; \
208 5146893 : if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_, \
209 5146893 : &_fd_der_len_ ) ) ) \
210 5146893 : return -1; \
211 5146893 : if( FD_UNLIKELY( _fd_der_tag_ != (int)(expected_tag) ) ) \
212 5146884 : return -1; \
213 5146884 : if( FD_UNLIKELY( _fd_der_tag_ == (int)FD_DER_TAG_OID && \
214 5146869 : !fd_der_oid_valid( (c).p, _fd_der_len_ ) ) ) \
215 5146869 : return -1; \
216 5146869 : (out_ptr) = (c).p; \
217 5146869 : (out_len) = _fd_der_len_; \
218 5146869 : (c).p += _fd_der_len_; \
219 5146869 : } while(0)
220 :
221 : /* Read a TLV including its tag+length prefix. Sets raw_ptr to the
222 : start of the tag byte and raw_len to tag+length+content. OBJECT
223 : IDENTIFIER content is additionally checked for canonical DER encoding. */
224 :
225 : #define FD_DER_READ_RAW( c, expected_tag, raw_ptr, raw_len ) \
226 1616034 : do { \
227 1616034 : (raw_ptr) = (c).p; \
228 1616034 : int _fd_der_tag_; ulong _fd_der_len_; \
229 1616034 : if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_, \
230 1616034 : &_fd_der_len_ ) ) ) \
231 1616034 : return -1; \
232 1616034 : if( FD_UNLIKELY( _fd_der_tag_ != (int)(expected_tag) ) ) \
233 1616031 : return -1; \
234 1616031 : if( FD_UNLIKELY( _fd_der_tag_ == (int)FD_DER_TAG_OID && \
235 1616025 : !fd_der_oid_valid( (c).p, _fd_der_len_ ) ) ) \
236 1616025 : return -1; \
237 1616025 : (c).p += _fd_der_len_; \
238 1616001 : (raw_len) = (ulong)( (c).p - (raw_ptr) ); \
239 1616001 : } while(0)
240 :
241 : /* Peek at the next tag byte without consuming it.
242 : Sets out_tag to the tag value. Returns -1 if cursor is exhausted. */
243 :
244 : #define FD_DER_PEEK_TAG( c, out_tag ) \
245 321900 : do { \
246 321900 : if( FD_UNLIKELY( (c).p == (c).end ) ) return -1; \
247 321900 : (out_tag) = (int)*(c).p; \
248 321897 : } while(0)
249 :
250 : /* Peek at the next tag byte, returning a default if cursor exhausted. */
251 :
252 : #define FD_DER_PEEK_TAG_OR( c, out_tag, default_tag ) \
253 630756 : do { \
254 630756 : (out_tag) = ( (c).p != (c).end ) ? (int)*(c).p : (int)(default_tag); \
255 630756 : } while(0)
256 :
257 : /* Skip one TLV. */
258 :
259 : #define FD_DER_SKIP( c ) \
260 3 : do { \
261 3 : int _fd_der_tag_; ulong _fd_der_len_; \
262 3 : if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_, \
263 3 : &_fd_der_len_ ) ) ) \
264 3 : return -1; \
265 3 : (void)_fd_der_tag_; \
266 3 : (c).p += _fd_der_len_; \
267 3 : } while(0)
268 :
269 : /* Skip one TLV iff the next tag matches expected_tag.
270 : If the tag doesn't match or cursor is exhausted, does nothing. */
271 :
272 : #define FD_DER_SKIP_IF( c, expected_tag ) \
273 6 : do { \
274 6 : if( (c).p != (c).end && (int)*(c).p == (int)(expected_tag) ) { \
275 3 : FD_DER_SKIP( c ); \
276 3 : } \
277 6 : } while(0)
278 :
279 : /* Read a BIT STRING and strip the "unused bits" byte.
280 : DER BIT STRING content is represented as, unused_bits || actual_bits.
281 : For X.509 signatures and pubkeys, unused_bits is always 0x00.
282 : Sets ptr & len to the actual bits after the unused-bits byte. */
283 :
284 : #define FD_DER_READ_BITS( c, out_ptr, out_len ) \
285 644046 : do { \
286 644046 : uchar const * _fd_der_bits_raw_; ulong _fd_der_bits_raw_len_; \
287 644046 : FD_DER_READ( c, FD_DER_TAG_BIT_STRING, \
288 644046 : _fd_der_bits_raw_, _fd_der_bits_raw_len_ ); \
289 644046 : if( FD_UNLIKELY( _fd_der_bits_raw_len_ < 1 || \
290 644046 : _fd_der_bits_raw_[0] != 0x00 ) ) \
291 644046 : return -1; \
292 644046 : (out_ptr) = _fd_der_bits_raw_ + 1; \
293 644040 : (out_len) = _fd_der_bits_raw_len_ - 1; \
294 644040 : } while(0)
295 :
296 : /* Read a time field, either UTCTime or GeneralizedTime. */
297 :
298 : #define FD_DER_READ_TIME( c, out_tag, out_ptr, out_len ) \
299 644367 : do { \
300 644367 : int _fd_der_tag_; ulong _fd_der_len_; \
301 644367 : if( FD_UNLIKELY( fd_der_read_tl( &(c), &_fd_der_tag_, \
302 644367 : &_fd_der_len_ ) ) ) \
303 644367 : return -1; \
304 644367 : if( FD_UNLIKELY( _fd_der_tag_ != (int)FD_DER_TAG_UTC_TIME && \
305 644367 : _fd_der_tag_ != (int)FD_DER_TAG_GENERALIZED_TIME ) ) \
306 644367 : return -1; \
307 644367 : (out_tag) = (uchar)_fd_der_tag_; \
308 644364 : (out_ptr) = (c).p; \
309 644364 : (out_len) = _fd_der_len_; \
310 644364 : (c).p += _fd_der_len_; \
311 644364 : } while(0)
312 :
313 : /* Check if cursor has more content to read. */
314 :
315 4577322 : #define FD_DER_HAS_MORE( c ) ( (c).p != (c).end )
316 :
317 : static inline int
318 : fd_der_oid_match( uchar const * tlv,
319 : ulong tlv_len,
320 : uchar const * expected_oid,
321 2744013 : ulong expected_oid_len ) {
322 2744013 : return ( tlv_len == expected_oid_len &&
323 2744013 : 0 == memcmp( tlv, expected_oid, expected_oid_len ) );
324 2744013 : }
325 :
326 : #endif /* HEADER_fd_src_ballet_x509_fd_der_h */
|