Line data Source code
1 : #include "fd_x509_ca_store.h"
2 :
3 : #if FD_HAS_HOSTED
4 :
5 : #include "../base64/fd_base64.h"
6 : #include "../../util/io/fd_io.h"
7 : #include "../../util/log/fd_log.h"
8 : #include <fcntl.h>
9 : #include <stdlib.h>
10 : #include <string.h>
11 : #include <sys/stat.h>
12 : #include <unistd.h>
13 :
14 9117 : #define PEM_BEGIN "-----BEGIN CERTIFICATE-----"
15 5244 : #define PEM_END "-----END CERTIFICATE-----"
16 5433 : #define PEM_BEGIN_SZ (sizeof(PEM_BEGIN)-1)
17 3495 : #define PEM_END_SZ (sizeof(PEM_END)-1)
18 :
19 : /* Scan [p, end) for needle. Returns pointer to first match, or NULL. */
20 : static char const *
21 5433 : find_line( char const * p, char const * end, char const * needle, ulong needle_sz ) {
22 1239000 : for( ; (ulong)(end-p)>=needle_sz; p++ ) {
23 1238622 : if( p[0]==needle[0] && !memcmp( p, needle, needle_sz ) ) return p;
24 1238622 : }
25 378 : return NULL;
26 5433 : }
27 :
28 : #define PEM_B64_STRIPPED_MAX (8192UL)
29 :
30 : /* Sanity bound on the bundle file. Real CA bundles are 200-400 KB;
31 : this only exists so that pointing pem_path at something absurd fails
32 : instead of allocating without end. */
33 :
34 : #define CA_BUNDLE_SZ_MAX (64UL<<20)
35 :
36 : static long
37 : pem_b64_decode( uchar * out, ulong out_max,
38 1746 : char const * b64, ulong b64_sz ) {
39 1746 : char stripped[ PEM_B64_STRIPPED_MAX ];
40 1746 : ulong j = 0;
41 597501 : for( ulong i=0; i<b64_sz; i++ ) {
42 595755 : char c = b64[i];
43 595755 : if( c=='\n' || c=='\r' || c==' ' || c=='\t' ) continue;
44 591372 : if( FD_UNLIKELY( j >= sizeof(stripped) ) ) return -1;
45 591372 : stripped[j++] = c;
46 591372 : }
47 1746 : while( j & 3 ) {
48 0 : if( FD_UNLIKELY( j >= sizeof(stripped) ) ) return -1;
49 0 : stripped[j++] = '=';
50 0 : }
51 1746 : if( !j ) return -1;
52 1746 : if( FD_UNLIKELY( FD_BASE64_DEC_SZ(j) > out_max ) ) return -1;
53 1746 : return fd_base64_decode( out, stripped, j );
54 1746 : }
55 :
56 : long
57 : fd_x509_ca_store_load( fd_x509_ca_store_t * store,
58 192 : char const * pem_path ) {
59 192 : store->cnt = 0;
60 :
61 192 : int fd = open( pem_path, O_RDONLY|O_CLOEXEC );
62 192 : if( FD_UNLIKELY( fd<0 ) ) return -1;
63 :
64 192 : struct stat st;
65 192 : if( FD_UNLIKELY( fstat( fd, &st )<0 ) ) { close( fd ); return -1; }
66 192 : ulong file_sz = (ulong)st.st_size;
67 192 : if( FD_UNLIKELY( !file_sz ) ) { close( fd ); return -1; }
68 192 : if( FD_UNLIKELY( file_sz>CA_BUNDLE_SZ_MAX ) ) {
69 3 : FD_LOG_WARNING(( "CA bundle %s is %lu bytes, larger than the %lu byte limit",
70 3 : pem_path, file_sz, CA_BUNDLE_SZ_MAX ));
71 3 : close( fd ); return -1;
72 3 : }
73 :
74 189 : uchar * file_buf = malloc( file_sz );
75 189 : if( FD_UNLIKELY( !file_buf ) ) { close( fd ); return -1; }
76 :
77 189 : ulong read_sz;
78 189 : int err = fd_io_read( fd, file_buf, file_sz, file_sz, &read_sz );
79 189 : (void)close( fd );
80 189 : if( FD_UNLIKELY( err ) ) {
81 0 : FD_LOG_WARNING(( "read(%s) failed (%i-%s)", pem_path, err, fd_io_strerror( err ) ));
82 0 : free( file_buf ); return -1;
83 0 : }
84 :
85 189 : char const * p = (char const *)file_buf;
86 189 : char const * end = p + file_sz;
87 189 : ulong loaded = 0;
88 189 : ulong unsupported_key = 0;
89 :
90 1935 : while( p < end ) {
91 : /* Find next PEM certificate block */
92 1935 : char const * begin = find_line( p, end, PEM_BEGIN, PEM_BEGIN_SZ );
93 1935 : if( !begin ) break;
94 1749 : char const * b64 = begin + PEM_BEGIN_SZ;
95 :
96 1749 : char const * next_begin = find_line( b64, end, PEM_BEGIN, PEM_BEGIN_SZ );
97 1749 : char const * finish = find_line( b64, next_begin ? next_begin : end, PEM_END, PEM_END_SZ );
98 1749 : if( !finish && next_begin ) {
99 3 : p = next_begin;
100 3 : continue;
101 3 : }
102 1746 : if( !finish ) break;
103 1746 : p = finish + PEM_END_SZ;
104 :
105 1746 : uchar der[ FD_BASE64_DEC_SZ(PEM_B64_STRIPPED_MAX) ];
106 1746 : long der_sz = pem_b64_decode( der, sizeof(der), b64, (ulong)(finish - b64) );
107 1746 : if( FD_UNLIKELY( -1L==der_sz ) ) continue;
108 :
109 1743 : fd_x509_cert_info_t info;
110 1743 : char const * reason = NULL;
111 1743 : if( fd_x509_cert_parse( der, (ulong)der_sz, &info ) ) reason = "parse failed";
112 1737 : else if( info.key_type == FD_X509_KEY_UNKNOWN ) { unsupported_key++; continue; }
113 1734 : else if( info.subject_len > FD_X509_CA_SUBJECT_MAX ) reason = "subject too long";
114 1734 : else if( info.pubkey_len > sizeof(store->entries[0].pubkey) ) reason = "public key too long";
115 1734 : else if( !info.is_ca ) reason = "not a CA";
116 1731 : else if( info.has_key_usage &&
117 1731 : !( info.key_usage & FD_X509_KU_KEY_CERT_SIGN ) ) reason = "keyUsage lacks keyCertSign";
118 1731 : else if( info.has_ext_key_usage &&
119 1731 : !( info.ext_key_usage &
120 6 : ( FD_X509_EKU_SERVER_AUTH|FD_X509_EKU_ANY ) ) ) reason = "extKeyUsage lacks serverAuth";
121 1728 : else if( info.name_constraints_permitted_len+
122 1728 : info.name_constraints_excluded_len >
123 1728 : FD_X509_CA_NAME_CONSTRAINTS_MAX ) reason = "name constraints too long";
124 1740 : if( reason ) {
125 12 : FD_LOG_INFO(( "ignoring CA cert at %s offset %ld: %s",
126 12 : pem_path, (long)(begin-(char const *)file_buf), reason ));
127 12 : continue;
128 12 : }
129 :
130 1728 : if( FD_UNLIKELY( store->cnt >= FD_X509_CA_STORE_MAX ) ) {
131 3 : FD_LOG_WARNING(( "CA bundle %s holds more than %lu trust anchors; the rest were dropped",
132 3 : pem_path, FD_X509_CA_STORE_MAX ));
133 3 : break;
134 3 : }
135 :
136 1725 : fd_x509_ca_entry_t * e = &store->entries[ store->cnt++ ];
137 1725 : fd_memcpy( e->subject, info.subject, info.subject_len );
138 1725 : e->subject_len = info.subject_len;
139 1725 : fd_memcpy( e->pubkey, info.pubkey, info.pubkey_len );
140 1725 : e->pubkey_len = info.pubkey_len;
141 1725 : e->key_type = info.key_type;
142 1725 : e->path_len_constraint = info.path_len_constraint;
143 1725 : e->has_path_len_constraint = info.has_path_len_constraint;
144 1725 : fd_memcpy( e->name_constraints,
145 1725 : info.name_constraints_permitted, info.name_constraints_permitted_len );
146 1725 : fd_memcpy( e->name_constraints+info.name_constraints_permitted_len,
147 1725 : info.name_constraints_excluded, info.name_constraints_excluded_len );
148 1725 : e->name_constraints_permitted_len = info.name_constraints_permitted_len;
149 1725 : e->name_constraints_excluded_len = info.name_constraints_excluded_len;
150 1725 : e->has_name_constraints = info.has_name_constraints;
151 1725 : loaded++;
152 1725 : }
153 :
154 189 : free( file_buf );
155 :
156 189 : if( unsupported_key )
157 3 : FD_LOG_INFO(( "ignored %lu CA certificates in %s with unsupported public key types",
158 189 : unsupported_key, pem_path ));
159 :
160 189 : return (long)loaded;
161 189 : }
162 :
163 : long
164 0 : fd_x509_ca_store_load_system( fd_x509_ca_store_t * store ) {
165 0 : static char const * const ca_paths[] = {
166 0 : "/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
167 0 : "/etc/pki/tls/certs/ca-bundle.crt", /* RHEL/Fedora */
168 0 : "/etc/ssl/ca-bundle.pem", /* openSUSE/SLES */
169 0 : "/etc/ssl/cert.pem", /* Alpine */
170 0 : NULL
171 0 : };
172 :
173 0 : for( ulong i=0UL; ca_paths[i]; i++ ) {
174 0 : long loaded = fd_x509_ca_store_load( store, ca_paths[i] );
175 0 : if( loaded<0L ) continue;
176 0 : if( FD_UNLIKELY( !loaded ) ) {
177 0 : FD_LOG_WARNING(( "CA bundle %s holds no usable trust anchor; ignoring it", ca_paths[i] ));
178 0 : continue;
179 0 : }
180 0 : FD_LOG_INFO(( "Loaded %ld CA certificates from %s", loaded, ca_paths[i] ));
181 0 : return loaded;
182 0 : }
183 :
184 0 : return -1L;
185 0 : }
186 :
187 : #endif /* FD_HAS_HOSTED */
188 :
189 : fd_x509_ca_entry_t const *
190 : fd_x509_ca_store_find_next( fd_x509_ca_store_t const * store,
191 : uchar const * subject,
192 : ulong subject_len,
193 2238 : ulong * idx ) {
194 4617 : for( ulong i=*idx; i < store->cnt; i++ ) {
195 3669 : fd_x509_ca_entry_t const * e = &store->entries[i];
196 3669 : if( fd_x509_name_equal( e->subject, e->subject_len, subject, subject_len ) ) {
197 1290 : *idx = i+1UL;
198 1290 : return e;
199 1290 : }
200 3669 : }
201 948 : *idx = store->cnt;
202 : return NULL;
203 2238 : }
|