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 }