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 }