1 /** 2 * X.509 Public Key 3 * 4 * Copyright: 5 * (C) 1999-2010 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.pubkey.x509_key; 12 13 import botan.constants; 14 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO && BOTAN_HAS_X509_CERTIFICATES): 15 16 alias x509_key = botan.pubkey.x509_key; 17 18 public import botan.pubkey.pk_keys; 19 public import botan.asn1.alg_id; 20 public import botan.filters.pipe; 21 import botan.asn1.der_enc; 22 import botan.asn1.ber_dec; 23 import botan.asn1.alg_id; 24 import botan.codec.pem; 25 import botan.pubkey.pk_algs; 26 import botan.utils.types; 27 28 alias X509Encoding = bool; 29 /** 30 * The two types of X509 encoding supported by Botan. 31 */ 32 enum : X509Encoding { RAW_BER, PEM_ } 33 34 /** 35 * BER encode a key 36 * Params: 37 * key = the public key to encode 38 * Returns: BER encoding of this key 39 */ 40 Vector!ubyte BER_encode(in PublicKey key) 41 { 42 auto der_enc = DEREncoder() 43 .startCons(ASN1Tag.SEQUENCE) 44 .encode(key.algorithmIdentifier()) 45 .encode(key.x509SubjectPublicKey(), ASN1Tag.BIT_STRING); 46 der_enc.endCons(); 47 return der_enc.getContentsUnlocked(); 48 } 49 50 /** 51 * PEM encode a public key into a string. 52 * Params: 53 * key = the key to encode 54 * Returns: PEM encoded key 55 */ 56 string PEM_encode(in PublicKey key) 57 { 58 return PEM.encode(x509_key.BER_encode(key), "PUBLIC KEY"); 59 } 60 61 /** 62 * Create a public key from a data source. 63 * Params: 64 * source = the source providing the DER or PEM encoded key 65 * Returns: new public key object 66 */ 67 PublicKey loadKey(DataSource source) 68 { 69 try { 70 auto alg_id = AlgorithmIdentifier(); 71 SecureVector!ubyte key_bits; 72 73 if (maybeBER(source) && !PEM.matches(source)) 74 { 75 BERDecoder(source) 76 .startCons(ASN1Tag.SEQUENCE) 77 .decode(alg_id) 78 .decode(key_bits, ASN1Tag.BIT_STRING) 79 .verifyEnd() 80 .endCons(); 81 } 82 else 83 { 84 auto ber = DataSourceMemory(PEM.decodeCheckLabel(source, "PUBLIC KEY")); 85 86 BERDecoder(cast(DataSource)ber) 87 .startCons(ASN1Tag.SEQUENCE) 88 .decode(alg_id) 89 .decode(key_bits, ASN1Tag.BIT_STRING) 90 .verifyEnd() 91 .endCons(); 92 } 93 94 if (key_bits.empty) 95 throw new DecodingError("X.509 public key decoding failed"); 96 97 return makePublicKey(alg_id, key_bits); 98 } 99 catch(DecodingError) 100 { 101 throw new DecodingError("X.509 public key decoding failed"); 102 } 103 } 104 105 /** 106 * Create a public key from a file 107 * Params: 108 * filename = pathname to the file to load 109 * Returns: new public key object 110 */ 111 PublicKey loadKey(in string filename) 112 { 113 auto source = DataSourceStream(filename, true); 114 return x509_key.loadKey(cast(DataSource)source); 115 } 116 117 118 /** 119 * Create a public key from a memory region. 120 * Params: 121 * enc = the memory region containing the DER or PEM encoded key 122 * Returns: new public key object 123 */ 124 PublicKey loadKey(ALLOC)(auto const ref Vector!(ubyte, ALLOC) enc) 125 { 126 auto source = DataSourceMemory(&enc); 127 return x509_key.loadKey(cast(DataSource)source); 128 } 129 130 /** 131 * Copy a key. 132 * Params: 133 * key = the public key to copy 134 * Returns: new public key object 135 */ 136 PublicKey copyKey(in PublicKey key) 137 { 138 auto source = DataSourceMemory(PEM_encode(key)); 139 return x509_key.loadKey(cast(DataSource)source); 140 } 141 142 static if (BOTAN_TEST && BOTAN_HAS_X509_CERTIFICATES && BOTAN_HAS_RSA && BOTAN_HAS_DSA): 143 144 import botan.test; 145 import botan.filters.filters; 146 import botan.rng.auto_rng; 147 import botan.pubkey.algo.rsa; 148 import botan.pubkey.algo.dsa; 149 import botan.pubkey.algo.ecdsa; 150 151 import botan.cert.x509.x509self; 152 import botan.cert.x509.x509path; 153 import botan.cert.x509.x509_ca; 154 import botan.asn1.asn1_time; 155 import botan.cert.x509.pkcs10; 156 157 ulong keyId(in PublicKey key) 158 { 159 Pipe pipe = Pipe(new HashFilter("SHA-1", 8)); 160 pipe.startMsg(); 161 pipe.write(key.algoName()); 162 pipe.write(key.algorithmIdentifier().parameters); 163 pipe.write(key.x509SubjectPublicKey()); 164 pipe.endMsg(); 165 166 SecureVector!ubyte output = pipe.readAll(); 167 168 if (output.length != 8) 169 throw new InternalError("PublicKey::key_id: Incorrect output size"); 170 171 ulong id = 0; 172 for(uint j = 0; j != 8; ++j) 173 id = (id << 8) | output[j]; 174 return id; 175 } 176 177 178 /* Return some option sets */ 179 X509CertOptions caOpts() 180 { 181 X509CertOptions opts = X509CertOptions("Test CA/US/Botan Project/Testing"); 182 183 opts.uri = "http://botan.randombit.net"; 184 opts.dns = "botan.randombit.net"; 185 opts.email = "testing@globecsys.com"; 186 187 opts.CAKey(1); 188 189 return opts; 190 } 191 192 X509CertOptions reqOpts1() 193 { 194 X509CertOptions opts = X509CertOptions("Test User 1/US/Botan Project/Testing"); 195 196 opts.uri = "http://botan.randombit.net"; 197 opts.dns = "botan.randombit.net"; 198 opts.email = "testing@globecsys.com"; 199 200 return opts; 201 } 202 203 X509CertOptions reqOpts2() 204 { 205 X509CertOptions opts = X509CertOptions("Test User 2/US/Botan Project/Testing"); 206 207 opts.uri = "http://botan.randombit.net"; 208 opts.dns = "botan.randombit.net"; 209 opts.email = "testing@randombit.net"; 210 211 opts.addExConstraint("PKIX.EmailProtection"); 212 213 return opts; 214 } 215 216 uint checkAgainstCopy(const PrivateKey orig, RandomNumberGenerator rng) 217 { 218 219 auto copy_priv = pkcs8.copyKey(orig, rng); 220 auto copy_pub = x509_key.copyKey(orig); 221 222 const string passphrase = "I need work! -Mr. T"; 223 auto pem = pkcs8.PEM_encode(orig, rng, passphrase); 224 //logDebug(pem[]); 225 auto enc_source = cast(DataSource)DataSourceMemory(pem); 226 auto copy_priv_enc = pkcs8.loadKey(enc_source, rng, passphrase); 227 ulong orig_id = keyId(orig); 228 ulong pub_id = keyId(copy_pub); 229 ulong priv_id = keyId(copy_priv); 230 ulong priv_enc_id = keyId(copy_priv_enc); 231 232 if (orig_id != pub_id || orig_id != priv_id || orig_id != priv_enc_id) 233 { 234 logError("Failed copy check for " ~ orig.algoName()); 235 return 1; 236 } 237 return 0; 238 } 239 240 static if (BOTAN_HAS_TESTS && !SKIP_X509_KEY_TEST) unittest 241 { 242 logDebug("Testing x509_key ..."); 243 Unique!AutoSeededRNG rng = new AutoSeededRNG; 244 const string hash_fn = "SHA-256"; 245 246 size_t fails = 0; 247 248 /* Create the CA's key and self-signed cert */ 249 auto ca_key = RSAPrivateKey(*rng, 2048); 250 X509Certificate ca_cert = x509self.createSelfSignedCert(caOpts(), *ca_key, hash_fn, *rng); 251 252 /* Create user #1's key and cert request */ 253 auto dl_group = DLGroup("dsa/botan/2048"); 254 auto user1_key = DSAPrivateKey(*rng, dl_group.move); 255 256 auto opts1 = reqOpts1(); 257 PKCS10Request user1_req = x509self.createCertReq(opts1, *user1_key, "SHA-1", *rng); 258 259 /* Create user #2's key and cert request */ 260 static if (BOTAN_HAS_ECDSA) { 261 ECGroup ecc_domain = ECGroup(OID("1.2.840.10045.3.1.7")); 262 auto user2_key = ECDSAPrivateKey(*rng, ecc_domain); 263 } else static if (BOTAN_HAS_RSA) { 264 auto user2_key = RSAPrivateKey(*rng, 1536); 265 } else static assert(false, "Must have ECSA or RSA for X509!"); 266 267 PKCS10Request user2_req = x509self.createCertReq(reqOpts2(), *user2_key, hash_fn, *rng); 268 269 /* Create the CA object */ 270 X509CA ca = X509CA(ca_cert, *ca_key, hash_fn); 271 272 /* Sign the requests to create the certs */ 273 X509Certificate user1_cert = ca.signRequest(user1_req, *rng, X509Time("2008-01-01"), X509Time("2100-01-01")); 274 275 X509Certificate user2_cert = ca.signRequest(user2_req, *rng, X509Time("2008-01-01"), X509Time("2100-01-01")); 276 X509CRL crl1 = ca.newCRL(*rng); 277 278 /* Verify the certs */ 279 Unique!CertificateStoreInMemory store = new CertificateStoreInMemory(); 280 281 store.addCertificate(ca_cert); 282 283 PathValidationRestrictions restrictions = PathValidationRestrictions(false); 284 285 PathValidationResult result_u1 = x509PathValidate(user1_cert, restrictions, *store); 286 if (!result_u1.successfulValidation()) 287 { 288 logError("FAILED: User cert #1 did not validate - " ~ result_u1.resultString()); 289 ++fails; 290 } 291 292 PathValidationResult result_u2 = x509PathValidate(user2_cert, restrictions, *store); 293 if (!result_u2.successfulValidation()) 294 { 295 logError("FAILED: User cert #2 did not validate - " ~ result_u2.resultString()); 296 ++fails; 297 } 298 299 store.addCrl(crl1); 300 301 Vector!CRLEntry revoked; 302 auto crl_entry1 = CRLEntry(user1_cert, CESSATION_OF_OPERATION); 303 auto crl_entry2 = CRLEntry(user2_cert); 304 revoked.pushBack(crl_entry1); 305 revoked.pushBack(crl_entry2); 306 307 X509CRL crl2 = ca.updateCRL(crl1, revoked, *rng); 308 309 store.addCrl(crl2); 310 311 result_u1 = x509PathValidate(user1_cert, restrictions, *store); 312 if (result_u1.result() != CertificateStatusCode.CERT_IS_REVOKED) 313 { 314 logError("FAILED: User cert #1 was not revoked - " ~ result_u1.resultString()); 315 ++fails; 316 } 317 318 result_u2 = x509PathValidate(user2_cert, restrictions, *store); 319 if (result_u2.result() != CertificateStatusCode.CERT_IS_REVOKED) 320 { 321 logError("FAILED: User cert #2 was not revoked - " ~ result_u2.resultString()); 322 ++fails; 323 } 324 325 auto crl_entry = CRLEntry(user1_cert, REMOVE_FROM_CRL); 326 revoked.clear(); 327 revoked.pushBack(crl_entry); 328 X509CRL crl3 = ca.updateCRL(crl2, revoked, *rng); 329 330 store.addCrl(crl3); 331 332 333 result_u1 = x509PathValidate(user1_cert, restrictions, *store); 334 335 if (!result_u1.successfulValidation()) 336 { 337 logError("FAILED: User cert #1 was not un-revoked - " ~ result_u1.resultString()); 338 ++fails; 339 } 340 341 checkAgainstCopy(*ca_key, *rng); 342 checkAgainstCopy(*user1_key, *rng); 343 checkAgainstCopy(*user2_key, *rng); 344 testReport("X509_key", 5, fails); 345 }