1 /** 2 * CVC Self-Signed Certificate 3 * 4 * Copyright: 5 * (C) 2007 FlexSecure GmbH 6 * 2008 Jack Lloyd 7 * (C) 2014-2015 Etienne Cimon 8 * 9 * License: 10 * Botan is released under the Simplified BSD License (see LICENSE.md) 11 */ 12 module botan.cert.cvc.cvc_self; 13 14 import botan.constants; 15 static if (BOTAN_HAS_CARD_VERIFIABLE_CERTIFICATES): 16 17 alias cvc_self = botan.cert.cvc.cvc_self; 18 19 public import botan.pubkey.algo.ecc_key; 20 import botan.pubkey.pkcs8; 21 import botan.asn1.oids; 22 import botan.asn1.asn1_obj; 23 import botan.cert.cvc.cvc_cert; 24 public import botan.cert.cvc.cvc_req; 25 import botan.cert.cvc.cvc_ado; 26 import botan.cert.cvc.cvc_gen_cert; 27 import botan.math.ec_gfp.curve_gfp; 28 import botan.pubkey.pubkey; 29 import botan.pubkey.algo.ecdsa; 30 import botan.cert.cvc.eac_asn_obj; 31 import botan.rng.rng; 32 import botan.utils.types; 33 import std.datetime; 34 import std.array : Appender; 35 36 /** 37 * This class represents a set of options used for the creation of CVC certificates 38 */ 39 struct EAC11CVCOptions 40 { 41 public: 42 43 ASN1Car car; 44 ASN1Chr chr; 45 ubyte holder_auth_templ; 46 ASN1Ced ced; 47 ASN1Cex cex; 48 string hash_alg; 49 } 50 51 /** 52 * Create a selfsigned CVCA 53 * Params: 54 * key = the ECDSA private key to be used to sign the certificate 55 * opt = used to set several parameters. Necessary are: 56 * car, holder_auth_templ, hash_alg, ced, cex and hash_alg 57 * rng = the rng to use 58 * Returns: the self signed certificate 59 */ 60 EAC11CVC createSelfSignedCert(in PrivateKey key, 61 in EAC11CVCOptions opt, 62 RandomNumberGenerator rng) 63 { 64 // NOTE: we ignore the value of opt.chr 65 auto priv_key = ECDSAPrivateKey(key); 66 67 if (!priv_key) 68 throw new InvalidArgument("CVC_EAC.createSelfSignedCert(): unsupported key type"); 69 70 ASN1Chr chr = ASN1Chr(opt.car.value()); 71 72 auto sig_algo = AlgorithmIdentifier(); 73 string padding_and_hash = "EMSA1_BSI(" ~ opt.hash_alg ~ ")"; 74 //logTrace(padding_and_hash); 75 sig_algo.oid = OIDS.lookup(priv_key.algoName ~ "/" ~ padding_and_hash); 76 sig_algo = AlgorithmIdentifier(cast(OID)sig_algo.oid, AlgorithmIdentifierImpl.USE_NULL_PARAM); 77 78 PKSigner signer = PKSigner(key, padding_and_hash); 79 80 Vector!ubyte enc_public_key = eac11Encoding(cast(ECPublicKey)key, sig_algo.oid); 81 82 return makeCvcCert(signer, 83 enc_public_key, 84 opt.car, chr, 85 opt.holder_auth_templ, 86 opt.ced, opt.cex, rng); 87 } 88 89 90 /** 91 * Create a CVC request. The key encoding will be according to the provided private key. 92 * Params: 93 * key = the private key associated with the requesting entity 94 * chr = the chr to appear in the certificate (to be provided without 95 * sequence number) 96 * hash_alg = the string defining the hash algorithm to be used for the creation 97 * of the signature 98 * rng = the rng to use 99 * Returns: the new request 100 */ 101 EAC11Req createCvcReq(in PrivateKey key, 102 in ASN1Chr chr, 103 in string hash_alg, 104 RandomNumberGenerator rng) 105 { 106 107 const ECDSAPrivateKey priv_key = cast(const ECDSAPrivateKey) key; 108 if (priv_key is null) 109 { 110 throw new InvalidArgument("CVC_EAC.createSelfSignedCert(): unsupported key type"); 111 } 112 auto sig_algo = AlgorithmIdentifier(); 113 string padding_and_hash = "EMSA1_BSI(" ~ hash_alg ~ ")"; 114 sig_algo.oid = OIDS.lookup(priv_key.algoName ~ "/" ~ padding_and_hash); 115 sig_algo = AlgorithmIdentifier(cast(OID)sig_algo.oid, AlgorithmIdentifierImpl.USE_NULL_PARAM); 116 117 PKSigner signer = PKSigner(priv_key, padding_and_hash); 118 119 Vector!ubyte enc_public_key = eac11Encoding(priv_key, sig_algo.oid); 120 121 Vector!ubyte enc_cpi; 122 enc_cpi.pushBack(0x00); 123 Vector!ubyte tbs = DEREncoder() 124 .encode(enc_cpi, ASN1Tag.OCTET_STRING, (cast(ASN1Tag)41), ASN1Tag.APPLICATION) 125 .rawBytes(enc_public_key) 126 .encode(chr) 127 .getContentsUnlocked(); 128 129 Vector!ubyte signed_cert = 130 EAC11genCVC!EAC11ReqImpl.makeSigned(signer, 131 EAC11genCVC!EAC11ReqImpl.buildCertBody(tbs), 132 rng); 133 134 auto source = DataSourceMemory(&signed_cert); 135 return EAC11Req(cast(DataSource)source); 136 } 137 138 /** 139 * Create an ADO from a request object. 140 * Params: 141 * key = the private key used to sign the ADO 142 * req = the request forming the body of the ADO 143 * car = the CAR forming the body of the ADO, i.e. the 144 * CHR of the entity associated with the provided private key 145 * rng = the rng to use 146 */ 147 EAC11ADO createAdoReq(in PrivateKey key, 148 in EAC11Req req, 149 in ASN1Car car, 150 RandomNumberGenerator rng) 151 { 152 153 const ECDSAPrivateKey priv_key = cast(const ECDSAPrivateKey) key; 154 if (!priv_key) 155 { 156 throw new InvalidArgument("CVC_EAC.createSelfSignedCert(): unsupported key type"); 157 } 158 159 string padding_and_hash = paddingAndHashFromOid(req.signatureAlgorithm().oid); 160 PKSigner signer = PKSigner(priv_key, padding_and_hash); 161 Vector!ubyte tbs_bits = req.BER_encode(); 162 tbs_bits ~= DEREncoder().encode(car).getContentsUnlocked(); 163 164 Vector!ubyte signed_cert = EAC11ADO.makeSigned(signer, tbs_bits, rng); 165 166 auto source = DataSourceMemory(&signed_cert); 167 return EAC11ADO(cast(DataSource)source); 168 } 169 170 171 /** 172 * Create a CVCA certificate. 173 * Params: 174 * key = the private key associated with the CVCA certificate 175 * to be created 176 * hash = the string identifying the hash algorithm to be used 177 * for signing the certificate to be created 178 * car = the CAR of the certificate to be created 179 * iris = indicates whether the entity associated with the certificate 180 * shall be entitled to read the biometrical iris image 181 * fingerpr = indicates whether the entity associated with the certificate 182 * shall be entitled to read the biometrical fingerprint image 183 * cvca_validity_months = length of time in months this will be valid 184 * rng = a random number generator 185 * Returns: the CVCA certificate created 186 */ 187 EAC11CVC createCvca(in PrivateKey key, 188 in string hash, 189 in ASN1Car car, bool iris, bool fingerpr, 190 uint cvca_validity_months, 191 RandomNumberGenerator rng) 192 { 193 const ECDSAPrivateKey priv_key = cast(const ECDSAPrivateKey) key; 194 if (!priv_key) 195 { 196 throw new InvalidArgument("CVC_EAC.createSelfSignedCert(): unsupported key type"); 197 } 198 EAC11CVCOptions opts; 199 opts.car = cast(ASN1Car)car; 200 201 opts.ced = ASN1Ced(Clock.currTime(UTC())); 202 opts.cex = ASN1Cex(opts.ced); 203 opts.cex.addMonths(cvca_validity_months); 204 opts.holder_auth_templ = (CVCA | (iris * IRIS) | (fingerpr * FINGERPRINT)); 205 opts.hash_alg = hash; 206 return createSelfSignedCert(priv_key, opts, rng); 207 } 208 209 210 /** 211 * Create a link certificate between two CVCA certificates. The key 212 * encoding will be implicitCA. 213 * Params: 214 * signer = the cvca certificate associated with the signing 215 * entity 216 * key = the private key associated with the signer 217 * signee = the certificate which whose CAR/CHR will be 218 * the holder of the link certificate 219 * rng = a random number generator 220 */ 221 EAC11CVC linkCvca(in EAC11CVC signer, 222 in PrivateKey key, 223 in EAC11CVC signee, 224 RandomNumberGenerator rng) 225 { 226 const ECDSAPrivateKey priv_key = cast(const ECDSAPrivateKey) key; 227 228 if (!priv_key) 229 throw new InvalidArgument("linkCvca(): unsupported key type"); 230 231 ASN1Ced ced = ASN1Ced(Clock.currTime(UTC())); 232 ASN1Cex cex = ASN1Cex(signee.getCex()); 233 if (cast(EACTimeImpl)(*ced) > cast(EACTimeImpl)(*cex)) 234 { 235 Appender!string detail = "linkCvca(): validity periods of provided certificates don't overlap: currend time = ced = "; 236 detail ~= ced.toString(); 237 detail ~= ", signee.cex = "; 238 detail ~= cex.toString(); 239 throw new InvalidArgument(detail.data); 240 } 241 if (signer.signatureAlgorithm() != signee.signatureAlgorithm()) 242 { 243 throw new InvalidArgument("linkCvca(): signature algorithms of signer and signee don't match"); 244 } 245 AlgorithmIdentifier sig_algo = cast(AlgorithmIdentifier) signer.signatureAlgorithm(); 246 string padding_and_hash = paddingAndHashFromOid(sig_algo.oid); 247 PKSigner pk_signer = PKSigner(priv_key, padding_and_hash); 248 Unique!PublicKey pk = signee.subjectPublicKey(); 249 auto subj_pk = ECDSAPublicKey(*pk); 250 subj_pk.setParameterEncoding(EC_DOMPAR_ENC_EXPLICIT); 251 252 Vector!ubyte enc_public_key = eac11Encoding(priv_key, sig_algo.oid); 253 254 return makeCvcCert(pk_signer, enc_public_key, 255 signer.getCar(), 256 signee.getChr(), 257 signer.getChatValue(), 258 ced, cex, 259 rng); 260 } 261 262 /** 263 * Create a CVC request. The key encoding will be implicitCA. 264 * Params: 265 * prkey = the private key associated with the requesting entity 266 * chr = the chr to appear in the certificate (to be provided without 267 * sequence number) 268 * hash_alg = the string defining the hash algorithm to be used for the creation 269 * of the signature 270 * rng = a random number generator 271 * Returns: the new request 272 */ 273 EAC11Req createCVCReqImplicitca(PrivateKey prkey, in ASN1Chr chr, 274 in string hash_alg, RandomNumberGenerator rng) 275 { 276 auto priv_key = ECDSAPrivateKey(prkey); 277 if (!priv_key) 278 { 279 throw new InvalidArgument("CVC_EAC.createSelfSignedCert(): unsupported key type"); 280 } 281 priv_key.setParameterEncoding(EC_DOMPAR_ENC_IMPLICITCA); 282 return createCvcReq(priv_key, chr, hash_alg, rng); 283 } 284 285 /** 286 * Sign a CVC request. 287 * 288 * Params: 289 * signer_cert = the certificate of the signing entity 290 * key = the private key of the signing entity 291 * signee = the request to be signed 292 * seqnr = the sequence number of the certificate to be created 293 * seqnr_len = the number of digits the sequence number will be encoded in 294 * domestic = indicates whether to sign a domestic or a foreign certificate. Set to true for domestic 295 * dvca_validity_months = validity period in months 296 * ca_is_validity_months = validity period in months 297 * rng = a random number generator 298 * 299 * Returns: the new certificate 300 * 301 **/ 302 303 EAC11CVC signRequest(in EAC11CVC signer_cert, 304 in PrivateKey key, 305 in EAC11Req signee, 306 uint seqnr, 307 uint seqnr_len, 308 bool domestic, 309 uint dvca_validity_months, 310 uint ca_is_validity_months, 311 RandomNumberGenerator rng) 312 { 313 const ECDSAPrivateKey priv_key = cast(const ECDSAPrivateKey) key; 314 if (!priv_key) 315 { 316 throw new InvalidArgument("CVC_EAC.createSelfSignedCert(): unsupported key type"); 317 } 318 string chr_str = signee.getChr().value(); 319 320 string seqnr_string = to!string(seqnr); 321 322 while (seqnr_string.length < seqnr_len) 323 seqnr_string = '0' ~ seqnr_string; 324 325 chr_str ~= seqnr_string; 326 ASN1Chr chr = ASN1Chr(chr_str); 327 string padding_and_hash = paddingAndHashFromOid(signee.signatureAlgorithm().oid); 328 PKSigner pk_signer = PKSigner(priv_key, padding_and_hash); 329 Unique!PublicKey pk = signee.subjectPublicKey(); 330 auto subj_pk = ECDSAPublicKey(*pk); 331 // Unique!PublicKey signer_pk = signer_cert.subjectPublicKey(); 332 333 // for the case that the domain parameters are not set... 334 // (we use those from the signer because they must fit) 335 //subj_pk.setDomainParameters(priv_key.domain_parameters()); 336 337 subj_pk.setParameterEncoding(EC_DOMPAR_ENC_IMPLICITCA); 338 339 AlgorithmIdentifier sig_algo = cast(AlgorithmIdentifier) signer_cert.signatureAlgorithm(); 340 341 ASN1Ced ced = ASN1Ced(Clock.currTime(UTC())); 342 343 ubyte chat_val; 344 ubyte chat_low = signer_cert.getChatValue() & 0x3; // take the chat rights from signer 345 ASN1Cex cex = ASN1Cex(ced); 346 if ((signer_cert.getChatValue() & CVCA) == CVCA) 347 { 348 // we sign a dvca 349 cex.addMonths(dvca_validity_months); 350 if (domestic) 351 chat_val = DVCA_domestic | chat_low; 352 else 353 chat_val = DVCA_foreign | chat_low; 354 } 355 else if ((signer_cert.getChatValue() & DVCA_domestic) == DVCA_domestic || 356 (signer_cert.getChatValue() & DVCA_foreign) == DVCA_foreign) 357 { 358 cex.addMonths(ca_is_validity_months); 359 chat_val = IS | chat_low; 360 } 361 else 362 { 363 throw new InvalidArgument("signRequest(): encountered illegal value for CHAT"); 364 // (IS cannot sign certificates) 365 } 366 367 Vector!ubyte enc_public_key = eac11Encoding(priv_key, sig_algo.oid); 368 369 return makeCvcCert(pk_signer, enc_public_key, 370 ASN1Car(signer_cert.getChr().iso8859()), 371 chr, 372 chat_val, 373 ced, 374 cex, 375 rng); 376 } 377 378 /* 379 * cvc CHAT values 380 */ 381 alias CHATValues = ubyte; 382 enum : CHATValues { 383 CVCA = 0xC0, 384 DVCA_domestic = 0x80, 385 DVCA_foreign = 0x40, 386 IS = 0x00, 387 388 IRIS = 0x02, 389 FINGERPRINT = 0x01 390 } 391 392 void encodeEacBigint()(ref DEREncoder der, auto const ref BigInt x, ASN1Tag tag) 393 { 394 der.encode(BigInt.encode1363(x, x.bytes()), ASN1Tag.OCTET_STRING, tag); 395 } 396 397 Vector!ubyte eac11Encoding(const ECPublicKey key, in OID sig_algo) 398 { 399 if (key.domainFormat() == EC_DOMPAR_ENC_OID) 400 throw new EncodingError("CVC encoder: cannot encode parameters by OID"); 401 402 const ECGroup* domain = &key.domain(); 403 404 // This is why we can't have nice things 405 406 DEREncoder enc; 407 enc.startCons((cast(ASN1Tag)73), ASN1Tag.APPLICATION).encode(sig_algo); 408 409 if (key.domainFormat() == EC_DOMPAR_ENC_EXPLICIT) 410 { 411 encodeEacBigint(enc, domain.getCurve().getP(), (cast(ASN1Tag)1)); 412 encodeEacBigint(enc, domain.getCurve().getA(), (cast(ASN1Tag)2)); 413 encodeEacBigint(enc, domain.getCurve().getB(), (cast(ASN1Tag)3)); 414 415 enc.encode(EC2OSP(domain.getBasePoint(), PointGFp.UNCOMPRESSED), 416 ASN1Tag.OCTET_STRING, (cast(ASN1Tag)4)); 417 418 encodeEacBigint(enc, domain.getOrder(), (cast(ASN1Tag)4)); 419 } 420 421 enc.encode(EC2OSP(key.publicPoint(), PointGFp.UNCOMPRESSED), 422 ASN1Tag.OCTET_STRING, (cast(ASN1Tag)6)); 423 424 if (key.domainFormat() == EC_DOMPAR_ENC_EXPLICIT) 425 encodeEacBigint(enc, domain.getCofactor(), (cast(ASN1Tag)7)); 426 427 enc.endCons(); 428 429 return enc.getContentsUnlocked(); 430 } 431 432 string paddingAndHashFromOid(in OID oid) 433 { 434 import std..string : indexOf; 435 string padding_and_hash = OIDS.lookup(oid); // use the hash 436 437 if (padding_and_hash[0 .. 6] != "ECDSA/") 438 throw new InvalidState("CVC: Can only use ECDSA, not " ~ padding_and_hash); 439 440 padding_and_hash = padding_and_hash[padding_and_hash.indexOf("/") + 1 .. $]; 441 return padding_and_hash; 442 }