1 /** 2 * X.509 Self-Signed Certificate 3 * 4 * Copyright: 5 * (C) 1999-2007 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.cert.x509.x509self; 12 import botan.constants; 13 14 alias x509self = botan.cert.x509.x509self; 15 16 public import botan.cert.x509.x509_ca; 17 public import botan.cert.x509.x509cert; 18 public import botan.asn1.asn1_time; 19 import botan.cert.x509.pkcs10; 20 import botan.cert.x509.x509_ext; 21 import botan.cert.x509.key_constraint; 22 import botan.asn1.oids; 23 import botan.asn1.der_enc; 24 import botan.asn1.asn1_attribute; 25 import botan.asn1.asn1_alt_name; 26 import botan.filters.pipe; 27 import botan.utils.types; 28 import botan.utils.parsing; 29 import botan.pubkey.pkcs8; 30 import std.datetime; 31 32 static if (__traits(compiles, { X509Time time; }())): 33 34 alias X509CertOptions = RefCounted!X509CertOptionsImpl; 35 36 /** 37 * Options for X.509 certificates. 38 */ 39 final class X509CertOptionsImpl 40 { 41 public: 42 /** 43 * the subject common name 44 */ 45 string common_name; 46 47 /** 48 * the subject counry 49 */ 50 string country; 51 52 /** 53 * the subject organization 54 */ 55 string organization; 56 57 /** 58 * the subject organizational unit 59 */ 60 string org_unit; 61 62 /** 63 * the subject locality 64 */ 65 string locality; 66 67 /** 68 * the subject state 69 */ 70 string state; 71 72 /** 73 * the subject serial number 74 */ 75 string serial_number; 76 77 /** 78 * the subject email adress 79 */ 80 string email; 81 82 /** 83 * the subject URI 84 */ 85 string uri; 86 87 /** 88 * the subject IPv4 address 89 */ 90 string ip; 91 92 /** 93 * the subject DNS 94 */ 95 string dns; 96 97 /** 98 * the subject XMPP 99 */ 100 string xmpp; 101 102 /** 103 * the subject challenge password 104 */ 105 string challenge; 106 107 /** 108 * the subject notBefore 109 */ 110 X509Time start; 111 /** 112 * the subject notAfter 113 */ 114 X509Time end; 115 116 /** 117 * Indicates whether the certificate request 118 */ 119 bool is_CA; 120 121 /** 122 * Indicates the BasicConstraints path limit 123 */ 124 size_t path_limit; 125 126 /** 127 * The key constraints for the subject public key 128 */ 129 KeyConstraints constraints; 130 131 /** 132 * The key extended constraints for the subject public key 133 */ 134 Vector!OID ex_constraints; 135 136 /** 137 * Check the options set in this object for validity. 138 */ 139 void sanityCheck() const 140 { 141 if (common_name == "" || country == "") 142 throw new EncodingError("X.509 certificate: name and country MUST be set"); 143 if (country.length != 2) 144 throw new EncodingError("Invalid ISO country code: " ~ country); 145 if (start >= end) 146 throw new EncodingError("X509_Cert_Options: invalid time constraints"); 147 } 148 149 150 151 /** 152 * Mark the certificate as a CA certificate and set the path limit. 153 * 154 * Params: 155 * limit = the path limit to be set in the BasicConstraints extension. 156 */ 157 void CAKey(size_t limit = 1) 158 { 159 is_CA = true; 160 path_limit = limit; 161 } 162 163 164 /** 165 * Set when the certificate should become valid 166 * Params: 167 * time_string = the notBefore value of the certificate 168 */ 169 void notBefore(in string time_string) 170 { 171 start = X509Time(time_string); 172 } 173 174 /** 175 * Set the notAfter of the certificate. 176 * 177 * Params: 178 * time_string = the notAfter value of the certificate 179 */ 180 void notAfter(in string time_string) 181 { 182 end = X509Time(time_string); 183 } 184 185 /** 186 * Add the key constraints of the KeyUsage extension. 187 * 188 * Params: 189 * usage = the constraints to set 190 */ 191 void addConstraints(KeyConstraints usage) 192 { 193 constraints = usage; 194 } 195 196 /** 197 * Add constraints to the ExtendedKeyUsage extension. 198 * 199 * Params: 200 * oid = the oid to add 201 */ 202 void addExConstraint()(auto ref OID oid) 203 { 204 ex_constraints.pushBack(oid.dup); 205 } 206 207 /** 208 * Add constraints to the ExtendedKeyUsage extension. 209 * 210 * Params: 211 * oid_str = the name to look up the oid to add 212 */ 213 void addExConstraint(in string oid_str) 214 { 215 ex_constraints.pushBack(OIDS.lookup(oid_str)); 216 } 217 218 /** 219 * Construct a new options object 220 * Params: 221 * initial_opts = define the common name of this object. An example for this 222 * parameter would be "common_name/country/organization/organizational_unit". 223 * expiration_time = the expiration time (default 1 year) 224 */ 225 this(in string initial_opts = "", Duration expiration_time = 365.days) 226 { 227 is_CA = false; 228 path_limit = 0; 229 constraints = KeyConstraints.NO_CONSTRAINTS; 230 231 auto now = Clock.currTime(UTC()); 232 start = X509Time(now); 233 end = X509Time(now + expiration_time); 234 235 if (initial_opts == "") 236 return; 237 238 Vector!string parsed = botan.utils.parsing.splitter(initial_opts, '/'); 239 240 if (parsed.length > 4) 241 throw new InvalidArgument("X.509 cert options: Too many names: " ~ initial_opts); 242 243 if (parsed.length >= 1) common_name = parsed[0]; 244 if (parsed.length >= 2) country = parsed[1]; 245 if (parsed.length >= 3) organization = parsed[2]; 246 if (parsed.length == 4) org_unit = parsed[3]; 247 } 248 } 249 250 /** 251 * Create a self-signed X.509 certificate. 252 * Params: 253 * opts = the options defining the certificate to create 254 * key = the private key used for signing, i.e. the key 255 * associated with this self-signed certificate 256 * hash_fn = the hash function to use 257 * rng = the rng to use 258 * Returns: newly created self-signed certificate 259 */ 260 X509Certificate createSelfSignedCert()(auto const ref X509CertOptions opts, 261 in PrivateKey key, 262 in string hash_fn, 263 RandomNumberGenerator rng) 264 { 265 auto sig_algo = AlgorithmIdentifier(); 266 X509DN subject_dn = X509DN(); 267 AlternativeName subject_alt = AlternativeName(); 268 269 opts.sanityCheck(); 270 271 Vector!ubyte pub_key = x509_key.BER_encode(key); 272 PKSigner signer = chooseSigFormat(key, hash_fn, sig_algo); 273 loadInfo(opts, subject_dn, subject_alt); 274 275 KeyConstraints constraints; 276 if (opts.is_CA) 277 constraints = KeyConstraints.KEY_CERT_SIGN | KeyConstraints.CRL_SIGN; 278 else 279 constraints = findConstraints(key, opts.constraints); 280 281 X509Extensions extensions; 282 283 extensions.add(new BasicConstraints(opts.is_CA, opts.path_limit), true); 284 285 extensions.add(new KeyUsage(constraints), true); 286 extensions.add(new SubjectKeyID(pub_key)); 287 extensions.add(new SubjectAlternativeName(subject_alt)); 288 extensions.add(new ExtendedKeyUsage(opts.ex_constraints)); 289 return X509CA.makeCert(signer, rng, sig_algo, pub_key, 290 opts.start, opts.end, 291 subject_dn, subject_dn, 292 extensions); 293 } 294 295 /** 296 * Create a PKCS#10 certificate request. 297 * Params: 298 * opts = the options defining the request to create 299 * key = the key used to sign this request 300 * hash_fn = the hash function to use 301 * rng = the rng to use 302 * Returns: newly created PKCS#10 request 303 */ 304 PKCS10Request createCertReq()(auto const ref X509CertOptions opts, 305 in PrivateKey key, 306 in string hash_fn, 307 RandomNumberGenerator rng) 308 { 309 auto sig_algo = AlgorithmIdentifier(); 310 X509DN subject_dn = X509DN(); 311 AlternativeName subject_alt = AlternativeName(); 312 313 opts.sanityCheck(); 314 315 Vector!ubyte pub_key = x509_key.BER_encode(key); 316 PKSigner signer = chooseSigFormat(key, hash_fn, sig_algo); 317 loadInfo(opts, subject_dn, subject_alt); 318 __gshared immutable size_t PKCS10_VERSION = 0; 319 auto extensions = X509Extensions(); 320 321 extensions.add(new BasicConstraints(opts.is_CA, opts.path_limit)); 322 323 extensions.add(new KeyUsage(opts.is_CA ? KeyConstraints.KEY_CERT_SIGN | KeyConstraints.CRL_SIGN : findConstraints(key, opts.constraints))); 324 325 extensions.add(new ExtendedKeyUsage(opts.ex_constraints)); 326 extensions.add(new SubjectAlternativeName(subject_alt)); 327 328 DEREncoder tbs_req; 329 330 tbs_req.startCons(ASN1Tag.SEQUENCE) 331 .encode(PKCS10_VERSION) 332 .encode(subject_dn) 333 .rawBytes(pub_key) 334 .startExplicit(0); 335 336 if (opts.challenge != "") 337 { 338 ASN1String challenge = ASN1String(opts.challenge, ASN1Tag.DIRECTORY_STRING); 339 340 tbs_req.encode(Attribute("PKCS9.ChallengePassword", DEREncoder().encode(challenge).getContentsUnlocked())); 341 } 342 343 tbs_req.encode(Attribute("PKCS9.ExtensionRequest", 344 DEREncoder() 345 .startCons(ASN1Tag.SEQUENCE) 346 .encode(extensions) 347 .endCons() 348 .getContentsUnlocked() 349 ) 350 ).endExplicit().endCons(); 351 const Vector!ubyte req = X509Object.makeSigned(signer, rng, sig_algo, tbs_req.getContents()); 352 return PKCS10Request(&req); 353 } 354 355 /* 356 * Load information from the X509_Cert_Options 357 */ 358 private void loadInfo(in X509CertOptions opts, 359 ref X509DN subject_dn, 360 ref AlternativeName subject_alt) 361 { 362 subject_dn.addAttribute("X520.CommonName", opts.common_name); 363 subject_dn.addAttribute("X520.Country", opts.country); 364 subject_dn.addAttribute("X520.State", opts.state); 365 subject_dn.addAttribute("X520.Locality", opts.locality); 366 subject_dn.addAttribute("X520.Organization", opts.organization); 367 subject_dn.addAttribute("X520.OrganizationalUnit", opts.org_unit); 368 subject_dn.addAttribute("X520.SerialNumber", opts.serial_number); 369 subject_alt = AlternativeName(opts.email, opts.uri, opts.dns, opts.ip); 370 subject_alt.addOthername(OIDS.lookup("PKIX.XMPPAddr"), opts.xmpp, ASN1Tag.UTF8_STRING); 371 }