]> WPIA git - cassiopeia.git/blob - src/crypto/X509.cpp
3d0efbcc8fa75f516cab1c01d3fb29eb6200f67f
[cassiopeia.git] / src / crypto / X509.cpp
1 #include "X509.h"
2
3 #include <fstream>
4 #include <iostream>
5
6 #include <openssl/ssl.h>
7 #include <openssl/bio.h>
8 #include <openssl/x509v3.h>
9
10 X509Req::X509Req( X509_REQ *csr ) : req( csr, X509_REQ_free ) {
11     EVP_PKEY *pkt = X509_REQ_get_pubkey( req.get() );
12
13     if( !pkt ) {
14         throw std::runtime_error( "Error extracting public key" );
15     }
16
17     pk = std::shared_ptr<EVP_PKEY>( pkt, EVP_PKEY_free );
18 }
19
20 X509Req::X509Req( std::string spkac ) {
21     if( spkac.compare( 0, 6, "SPKAC=" ) != 0 ) {
22         throw std::runtime_error( "Error: not a SPKAC" );
23     }
24
25     spkac = spkac.substr( 6 );
26     NETSCAPE_SPKI *spki_p = NETSCAPE_SPKI_b64_decode( spkac.c_str(), spkac.size() );
27
28     if( !spki_p ) {
29         throw std::runtime_error( "Error: decode failed" );
30     }
31
32     spki = std::shared_ptr<NETSCAPE_SPKI>( spki_p, NETSCAPE_SPKI_free );
33     EVP_PKEY *pkt_p = NETSCAPE_SPKI_get_pubkey( spki.get() );
34
35     if( !pkt_p ) {
36         throw std::runtime_error( "Error: reading SPKAC Pubkey failed" );
37     }
38
39     pk = std::shared_ptr<EVP_PKEY>( pkt_p, EVP_PKEY_free );
40 }
41
42 int X509Req::verify() {
43     if( !req ) {
44         return NETSCAPE_SPKI_verify( spki.get(), pk.get() );
45     }
46
47     return X509_REQ_verify( req.get(), pk.get() );
48 }
49
50 std::shared_ptr<EVP_PKEY> X509Req::getPkey() const {
51     return pk;
52 }
53
54 std::shared_ptr<X509Req> X509Req::parseCSR( std::string content ) {
55     std::shared_ptr<BIO> in = std::shared_ptr<BIO>( BIO_new_mem_buf( const_cast<char *>( content.c_str() ), -1 ), BIO_free );
56     X509_REQ *req = PEM_read_bio_X509_REQ( in.get(), NULL, NULL, NULL );
57
58     if( !req ) {
59         throw std::runtime_error( "Error parsing CSR" );
60     }
61
62     return std::shared_ptr<X509Req>( new X509Req( req ) ); // TODO ask
63 }
64
65 std::shared_ptr<X509Req> X509Req::parseSPKAC( std::string content ) {
66     return std::shared_ptr<X509Req>( new X509Req( content ) );
67 }
68
69 int add_ext( std::shared_ptr<X509> issuer, std::shared_ptr<X509> subj, int nid, const char *value ) {
70     X509_EXTENSION *ex;
71     X509V3_CTX ctx;
72
73     /* This sets the 'context' of the extensions. */
74     /* No configuration database */
75     X509V3_set_ctx_nodb( &ctx );
76
77     /* Issuer and subject certs: both the target since it is self signed,
78      * no request and no CRL
79      */
80     X509V3_set_ctx( &ctx, issuer.get(), subj.get(), NULL, NULL, 0 );
81     ex = X509V3_EXT_conf_nid( NULL, &ctx, nid, const_cast<char *>( value ) );
82
83     if( !ex ) {
84         return 0;
85     }
86
87     X509_add_ext( subj.get(), ex, -1 );
88     X509_EXTENSION_free( ex );
89
90     return 1;
91 }
92
93 X509Cert::X509Cert() {
94     X509 *c = X509_new();
95
96     if( !c ) {
97         throw std::runtime_error( "malloc failed" );
98     }
99
100     target = std::shared_ptr<X509>( c, X509_free );
101
102     if( !X509_set_version( c, 2 ) ) {
103         throw std::runtime_error( "Setting X509-version to 3 failed" );
104     }
105
106     X509_NAME *subjectP = X509_NAME_new();
107
108     if( !subjectP ) {
109         throw std::runtime_error( "malloc failure in construct." );
110     }
111
112     subject = std::shared_ptr<X509_NAME>( subjectP, X509_NAME_free );
113 }
114
115 void X509Cert::addRDN( int nid, std::string data ) {
116     if( ! X509_NAME_add_entry_by_NID( subject.get(), nid, MBSTRING_UTF8, ( unsigned char * )const_cast<char *>( data.data() ), data.size(), -1, 0 ) ) {
117         throw std::runtime_error( "malloc failure in RDN" );
118     }
119 }
120
121 void X509Cert::setIssuerNameFrom( std::shared_ptr<X509> caCert ) {
122     if( !X509_set_issuer_name( target.get(), X509_get_subject_name( caCert.get() ) ) ) {
123         throw std::runtime_error( "Error setting Issuer name" );
124     }
125 }
126
127 void X509Cert::setPubkeyFrom( std::shared_ptr<X509Req> req ) {
128     std::shared_ptr<EVP_PKEY> pktmp = req->getPkey();
129
130     if( !X509_set_pubkey( target.get(), pktmp.get() ) ) {
131         throw std::runtime_error( "Setting public key failed." );
132     }
133 }
134
135 void X509Cert::setSerialNumber( BIGNUM *num ) {
136     ASN1_INTEGER *i = BN_to_ASN1_INTEGER( num, NULL );
137     X509_set_serialNumber( target.get(), i );
138     ASN1_INTEGER_free( i );
139 }
140
141 void X509Cert::setTimes( uint32_t before, uint32_t after ) {
142     ASN1_TIME_set( X509_get_notBefore( target.get() ), before );
143     ASN1_TIME_set( X509_get_notAfter( target.get() ), after );
144 }
145
146 static X509_EXTENSION *do_ext_i2d( int ext_nid, int crit, ASN1_VALUE *ext_struc ) {
147     unsigned char *ext_der;
148     int ext_len;
149     ASN1_OCTET_STRING *ext_oct;
150     X509_EXTENSION *ext;
151     /* Convert internal representation to DER */
152     ext_der = NULL;
153     ext_len = ASN1_item_i2d( ext_struc, &ext_der, ASN1_ITEM_ptr( ASN1_ITEM_ref( GENERAL_NAMES ) ) );
154
155     if( ext_len < 0 ) {
156         goto merr;
157     }
158
159     if( !( ext_oct = ASN1_OCTET_STRING_new() ) ) {
160         goto merr;
161     }
162
163     ext_oct->data = ext_der;
164     ext_oct->length = ext_len;
165
166     ext = X509_EXTENSION_create_by_NID( NULL, ext_nid, crit, ext_oct );
167
168     if( !ext ) {
169         goto merr;
170     }
171
172     ASN1_OCTET_STRING_free( ext_oct );
173     return ext;
174
175 merr:
176     throw std::runtime_error( "memerr" );
177 }
178
179 extern std::string ocspPath;
180
181 void X509Cert::setExtensions( std::shared_ptr<X509> caCert, std::vector<std::shared_ptr<SAN>>& sans, Profile& prof, std::string crlURL, std::string crtURL ) {
182     add_ext( caCert, target, NID_basic_constraints, "critical,CA:FALSE" );
183     add_ext( caCert, target, NID_subject_key_identifier, "hash" );
184     add_ext( caCert, target, NID_authority_key_identifier, "keyid:always" );
185     std::string ku = std::string( "critical," ) + prof.ku;
186     add_ext( caCert, target, NID_key_usage, ku.c_str() );
187     add_ext( caCert, target, NID_ext_key_usage, prof.eku.c_str() );
188     add_ext( caCert, target, NID_info_access, ( ( ocspPath.empty() || prof.include.find( "noOCSP" ) != prof.include.end() ? "" : "OCSP;URI:" + ocspPath + "," ) + "caIssuers;URI:" + crtURL ).c_str() );
189     add_ext( caCert, target, NID_crl_distribution_points, ( "URI:" + crlURL ).c_str() );
190
191     if( sans.empty() ) {
192         return;
193     }
194
195     auto freeGeneralNames = []( GENERAL_NAMES * ref ) {
196         if( ref ) {
197             sk_GENERAL_NAME_pop_free( ref, GENERAL_NAME_free );
198         }
199     };
200     std::shared_ptr<GENERAL_NAMES> gens( sk_GENERAL_NAME_new_null(), freeGeneralNames );
201
202     for( auto& name : sans ) {
203         GENERAL_NAME *gen = GENERAL_NAME_new();
204
205         if( !gen ) {
206             throw std::runtime_error( "Malloc failure." );
207         }
208
209         gen->type = name->type == "DNS" ? GEN_DNS : name->type == "email" ? GEN_EMAIL : 0; // GEN_EMAIL;
210
211         if( !gen->type
212                 || !( gen->d.ia5 = ASN1_IA5STRING_new() )
213                 || !ASN1_STRING_set( gen->d.ia5, name->content.data(), name->content.size() ) ) {
214             GENERAL_NAME_free( gen );
215             throw std::runtime_error( "initing iasting5 failed" );
216         }
217
218         sk_GENERAL_NAME_push( gens.get(), gen );
219     }
220
221     X509_EXTENSION *ext = do_ext_i2d( NID_subject_alt_name, 0/*critical*/, ( ASN1_VALUE * )gens.get() );
222
223     X509_add_ext( target.get(), ext, -1 );
224     X509_EXTENSION_free( ext );
225 }
226
227 std::shared_ptr<SignedCertificate> X509Cert::sign( std::shared_ptr<EVP_PKEY> caKey, std::string signAlg ) {
228     if( !X509_set_subject_name( target.get(), subject.get() ) ) {
229         throw std::runtime_error( "error setting subject" );
230     }
231
232     const EVP_MD *md;
233
234     if( signAlg == "sha512" ) {
235         md = EVP_sha512();
236     } else if( signAlg == "sha384" ) {
237         md = EVP_sha384();
238     } else if( signAlg == "sha256" ) {
239         md = EVP_sha256();
240     } else if( signAlg == "sha1" ) {
241         throw std::runtime_error( "Refusing to sign with weak signature algorithm (SHA-1)." );
242     } else if( signAlg == "md5" ) {
243         throw std::runtime_error( "Refusing to sign with weak signature algorithm (MD5)." );
244     } else {
245         throw std::runtime_error( "Unknown signature algorithm" );
246     }
247
248     if( !X509_sign( target.get(), caKey.get(), md ) ) {
249         throw std::runtime_error( "Signing failed." );
250     }
251
252     //X509_print_fp( stdout, target.get() );
253
254     std::shared_ptr<BIO> mem = std::shared_ptr<BIO>( BIO_new( BIO_s_mem() ), BIO_free );
255
256     if( !mem ) {
257         throw std::runtime_error( "Failed to allocate memory for the signed certificate." );
258     }
259
260     PEM_write_bio_X509( mem.get(), target.get() );
261
262     BUF_MEM *buf = NULL;
263     BIO_get_mem_ptr( mem.get(), &buf );
264
265     auto res = std::make_shared<SignedCertificate>();
266     res->certificate = std::string( buf->data, buf->data + buf->length );
267
268     std::shared_ptr<BIGNUM> ser( ASN1_INTEGER_to_BN( X509_get_serialNumber( target.get() ), NULL ), BN_free );
269
270     if( !ser ) {
271         throw std::runtime_error( "Failed to retrieve certificate serial of signed certificate." );
272     }
273
274     auto freeMem = []( char *p ) {
275         OPENSSL_free( p );
276     };// OPENSSL_free is a macro...
277     std::shared_ptr<char> serStr( BN_bn2hex( ser.get() ), freeMem );
278     res->serial = serStr ? std::string( serStr.get() ) : "";
279
280     return res;
281 }