1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|
  5  * |#       #|  Copyright (c) 1999-2009 CardContact Software & System Consulting
  6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
  7  *  ---------
  8  *
  9  *  This file is part of OpenSCDP.
 10  *
 11  *  OpenSCDP is free software; you can redistribute it and/or modify
 12  *  it under the terms of the GNU General Public License version 2 as
 13  *  published by the Free Software Foundation.
 14  *
 15  *  OpenSCDP is distributed in the hope that it will be useful,
 16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18  *  GNU General Public License for more details.
 19  *
 20  *  You should have received a copy of the GNU General Public License
 21  *  along with OpenSCDP; if not, write to the Free Software
 22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 23  *
 24  * @fileoverview Support for card verifiable certificates and certificate requests according to EAC 1.1/2.0
 25  */
 26 
 27 
 28 // Imports
 29 var PublicKeyReference = require('scsh/eac/PublicKeyReference').PublicKeyReference;
 30 
 31 
 32 
 33 /**
 34  * Create a CVC object from a DER encoded ByteString.
 35  *
 36  * @class Class implementing a decoder for card verifiable certificates or requests according to
 37  *        Extended Access Control (EAC) as defined in BSI TR-03110 1.11 and 2.02.
 38  * @constructor
 39  * @param {ByteString} param the DER encoded certificate
 40  * @return
 41  */
 42 function CVC() {
 43 	if (arguments.length > 0) {
 44 		var arg = arguments[0];
 45 		if (arg instanceof ASN1) {
 46 			this.asn = arg;
 47 			this.bin = this.asn.getBytes();
 48 		} else if (arg instanceof ByteString) {
 49 			this.bin = arg;
 50 			this.asn = new ASN1(this.bin);
 51 		} else {
 52 			throw new GPError("CVC", GPError.INVALID_DATA, 0, "Argument must be of type ByteString or ASN1");
 53 		}
 54 		if (this.asn.tag == CVC.TAG_AT) {
 55 			this.body = this.asn.get(0).get(0);
 56 		} else if (this.asn.tag == CVC.TAG_CVC) {
 57 			this.body = this.asn.get(0);
 58 		} else {
 59 			throw new GPError("CVC", GPError.INVALID_DATA, 0, "Argument is neither a CVC or CVC request");
 60 		}
 61 	}
 62 }
 63 
 64 exports.CVC = CVC;
 65 
 66 CVC.crypto = new Crypto();
 67 
 68 
 69 /** Authentication Template */
 70 CVC.TAG_AT = 0x67;
 71 /** CV Certificate */
 72 CVC.TAG_CVC = 0x7F21;
 73 /** Certificate Body */
 74 CVC.TAG_BODY = 0x7F4E;
 75 /** Certificate Profile Identifier */
 76 CVC.TAG_CPI = 0x5F29;
 77 /** Certification Authority Reference */
 78 CVC.TAG_CAR = 0x42;
 79 /** Public Key */
 80 CVC.TAG_PUK = 0x7F49;
 81 /** Prime Modulus */
 82 CVC.TAG_ECC_P = 0x81;
 83 /** First coefficient a */
 84 CVC.TAG_ECC_A = 0x82;
 85 /** Second coefficient b */
 86 CVC.TAG_ECC_B = 0x83;
 87 /** Base Point G */
 88 CVC.TAG_ECC_G = 0x84;
 89 /** Order of the base point */
 90 CVC.TAG_ECC_N = 0x85;
 91 /** Public Point y */
 92 CVC.TAG_ECC_Q = 0x86;
 93 /** Cofactor f */
 94 CVC.TAG_ECC_H = 0x87;
 95 /** Certificate Holder Reference */
 96 CVC.TAG_CHR = 0x5F20;
 97 /** Certificate Holder Authorisation Template */
 98 CVC.TAG_CHAT = 0x7F4C;
 99 /** Relative Authorization */
100 CVC.TAG_AUT = 0x53;
101 /** Certificate Extension */
102 CVC.TAG_EXTN = 0x65;
103 /** Certificate Effective Date */
104 CVC.TAG_CED = 0x5F25;
105 /** Certificate Expiration Date */
106 CVC.TAG_CXD = 0x5F24;
107 /** Signature */
108 CVC.TAG_SIG = 0x5F37;
109 
110 
111 /** Table of tag names */
112 CVC.OBJECTNAMES = []
113 CVC.OBJECTNAMES[CVC.TAG_AT] = "Authentication Template";
114 CVC.OBJECTNAMES[CVC.TAG_CVC] = "CV Certificate";
115 CVC.OBJECTNAMES[CVC.TAG_BODY] = "Certificate Body";
116 CVC.OBJECTNAMES[CVC.TAG_CPI] = "Certificate Profile Indicator";
117 CVC.OBJECTNAMES[CVC.TAG_CAR] = "Certification Authority Reference";
118 CVC.OBJECTNAMES[CVC.TAG_PUK] = "Public Key";
119 CVC.OBJECTNAMES[CVC.TAG_ECC_P] = "Prime/Modulus";
120 CVC.OBJECTNAMES[CVC.TAG_ECC_A] = "First coefficient a/Exponent";
121 CVC.OBJECTNAMES[CVC.TAG_ECC_B] = "Second coefficient b";
122 CVC.OBJECTNAMES[CVC.TAG_ECC_G] = "Base Point G";
123 CVC.OBJECTNAMES[CVC.TAG_ECC_N] = "Order of the base point";
124 CVC.OBJECTNAMES[CVC.TAG_ECC_Q] = "Public Point y";
125 CVC.OBJECTNAMES[CVC.TAG_ECC_H] = "Cofactor f";
126 CVC.OBJECTNAMES[CVC.TAG_CHR] = "Certificate Holder Reference";
127 CVC.OBJECTNAMES[CVC.TAG_CHAT] = "Certificate Holder Authentication Template";
128 CVC.OBJECTNAMES[CVC.TAG_AUT] = "Relative Authorization";
129 CVC.OBJECTNAMES[CVC.TAG_EXTN] = "Extension";
130 CVC.OBJECTNAMES[CVC.TAG_CED] = "Certificate Effective Date";
131 CVC.OBJECTNAMES[CVC.TAG_CXD] = "Certificate Expiration Date";
132 CVC.OBJECTNAMES[CVC.TAG_SIG] = "Signature";
133 
134 CVC.PUK_SYNTAX = [
135 	{ tag: 0x06, minlen: 2, maxlen: 20 },
136 	{ tag: CVC.TAG_ECC_P, minlen: 20, maxlen: 512, optional: true },
137 	{ tag: CVC.TAG_ECC_A, minlen: 1, maxlen: 512, optional: true },
138 	{ tag: CVC.TAG_ECC_B, minlen: 20, maxlen: 66, optional: true },
139 	{ tag: CVC.TAG_ECC_G, minlen: 41, maxlen: 133, optional: true },
140 	{ tag: CVC.TAG_ECC_N, minlen: 20, maxlen: 66, optional: true },
141 	{ tag: CVC.TAG_ECC_Q, minlen: 41, maxlen: 133, optional: true },
142 	{ tag: CVC.TAG_ECC_H, minlen: 1, maxlen: 1, optional: true }
143 ];
144 
145 CVC.REQ_BODY_SYNTAX = [
146 	{ tag: CVC.TAG_CPI, minlen: 1, maxlen: 1 },
147 	{ tag: CVC.TAG_CAR, minlen: 8, maxlen: 16, optional: true },
148 	{ tag: CVC.TAG_PUK, content: CVC.PUK_SYNTAX },
149 	{ tag: CVC.TAG_CHR, minlen: 8, maxlen: 16 },
150 	{ tag: CVC.TAG_EXTN, optional: true }
151 ];
152 
153 CVC.REQ_SYNTAX = [
154 	{ tag: CVC.TAG_BODY, content: CVC.REQ_BODY_SYNTAX },
155 	{ tag: CVC.TAG_SIG, minlen: 40, maxlen: 550 }
156 ];
157 
158 CVC.ATREQ_SYNTAX = [
159 	{ tag: CVC.TAG_CVC, content: CVC.REQ_SYNTAX },
160 	{ tag: CVC.TAG_CAR, minlen: 8, maxlen: 16 },
161 	{ tag: CVC.TAG_SIG, minlen: 40, maxlen: 550 }
162 ];
163 
164 CVC.CHAT_SYNTAX = [
165 	{ tag: 0x06, minlen: 2, maxlen: 20 },
166 	{ tag: CVC.TAG_AUT, minlen: 1, maxlen: 5 }
167 ];
168 
169 CVC.CERT_BODY_SYNTAX = [
170 	{ tag: CVC.TAG_CPI, minlen: 1, maxlen: 1 },
171 	{ tag: CVC.TAG_CAR, minlen: 8, maxlen: 16 },
172 	{ tag: CVC.TAG_PUK, content: CVC.PUK_SYNTAX },
173 	{ tag: CVC.TAG_CHR, minlen: 8, maxlen: 16 },
174 	{ tag: CVC.TAG_CHAT, content: CVC.CHAT_SYNTAX },
175 	{ tag: CVC.TAG_CED, minlen: 6, maxlen: 6 },
176 	{ tag: CVC.TAG_CXD, minlen: 6, maxlen: 6 },
177 	{ tag: CVC.TAG_EXTN, optional: true }
178 ];
179 
180 CVC.CERT_SYNTAX = [
181 	{ tag: CVC.TAG_BODY, content: CVC.CERT_BODY_SYNTAX },
182 	{ tag: CVC.TAG_SIG, minlen: 24, maxlen: 550 }
183 ];
184 
185 
186 
187 /** Table of rights description for id-IS */
188 CVC.ISRIGHTS = [
189 	"Read access to ePassport application: DG 3 (Fingerprint)",
190 	"Read access to ePassport application: DG 4 (Iris)",
191 	"RFU (Bit 3)",
192 	"RFU (Bit 4)",
193 	"RFU (Bit 5)",
194 	"Read access to eID application"
195 ];
196 CVC.idIS = new ByteString("id-IS", OID);
197 
198 
199 /** Table of rights description for id-AT */
200 CVC.ATRIGHTS = [
201 	"Age Verification",
202 	"Community ID Verification",
203 	"Restricted Identification",
204 	"Privileged Terminal",
205 	"CAN allowed",
206 	"PIN Management",
207 	"Install Certificate",
208 	"Install Qualified Certificate",
209 
210 	"Read Access DG 1 (Document Type)",
211 	"Read Access DG 2 (Issuing State)",
212 	"Read Access DG 3 (Date of Expiration)",
213 	"Read Access DG 4 (Given Name)",
214 	"Read Access DG 5 (Surname)",
215 	"Read Access DG 6 (Pseudonym)",
216 	"Read Access DG 7 (Academic Grade)",
217 	"Read Access DG 8 (Date of Birth)",
218 
219 	"Read Access DG 9 (Place of Birth)",
220 	"Read Access DG 10",
221 	"Read Access DG 11",
222 	"Read Access DG 12",
223 	"Read Access DG 13",
224 	"Read Access DG 14",
225 	"Read Access DG 15",
226 	"Read Access DG 16",
227 
228 	"Read Access DG 17 (Place of Residence)",
229 	"Read Access DG 18 (Community ID)",
230 	"Read Access DG 19 (Conditions I-eAT)",
231 	"Read Access DG 20 (Conditions II-eAT)",
232 	"Read Access DG 21",
233 	"RFU (Bit 29)",
234 	"RFU (Bit 30)",
235 	"RFU (Bit 31)",
236 
237 	"RFU (Bit 32)",
238 	"Write Access DG 21",
239 	"Write Access DG 20 (Conditions II-eAT)",
240 	"Write Access DG 19 (Conditions I-eAT)",
241 	"Write Access DG 18 (Community ID)",
242 	"Write Access DG 17 (Place of Residence)"
243 ];
244 CVC.idAT = new ByteString("id-AT", OID);
245 
246 
247 
248 /** Table of rights description for id-ST */
249 CVC.STRIGHTS = [
250 	"Generate electronic signature",
251 	"Generate qualified electronic signature",
252 	"RFU (Bit 2)",
253 	"RFU (Bit 3)",
254 	"RFU (Bit 4)",
255 	"RFU (Bit 5)"
256 ];
257 CVC.idST = new ByteString("id-ST", OID);
258 
259 CVC.idSC_HSM = new ByteString("2B0601040181C31F030101", HEX);
260 
261 
262 
263 /** TA constants */
264 CVC.id_TA_ECDSA = new ByteString("id-TA-ECDSA", OID);
265 CVC.id_TA_ECDSA_SHA_1 = new ByteString("id-TA-ECDSA-SHA-1", OID);
266 CVC.id_TA_ECDSA_SHA_224 = new ByteString("id-TA-ECDSA-SHA-224", OID);
267 CVC.id_TA_ECDSA_SHA_256 = new ByteString("id-TA-ECDSA-SHA-256", OID);
268 CVC.id_TA_ECDSA_SHA_384 = new ByteString("id-TA-ECDSA-SHA-384", OID);
269 CVC.id_TA_ECDSA_SHA_512 = new ByteString("id-TA-ECDSA-SHA-512", OID);
270 CVC.id_TA_RSA_v1_5_SHA_1 = new ByteString("id-TA-RSA-v1-5-SHA-1", OID);
271 CVC.id_TA_RSA_v1_5_SHA_256 = new ByteString("id-TA-RSA-v1-5-SHA-256", OID);
272 CVC.id_TA_RSA_v1_5_SHA_512 = new ByteString("id-TA-RSA-v1-5-SHA-512", OID);
273 CVC.id_TA_RSA_PSS_SHA_1 = new ByteString("id-TA-RSA-PSS-SHA-1", OID);
274 CVC.id_TA_RSA_PSS_SHA_256 = new ByteString("id-TA-RSA-PSS-SHA-256", OID);
275 CVC.id_TA_RSA_PSS_SHA_512 = new ByteString("id-TA-RSA-PSS-SHA-512", OID);
276 
277 
278 
279 /**
280  * Return signature mechanism for object identifier
281  *
282  * @param {ByteString} oid the object identifer from the public key object
283  * @returns the signature mechanism as Crypto. constant or -1 if not defined
284  * @type Number
285  */
286 CVC.getSignatureMech = function(oid) {
287 	if (oid.equals(CVC.id_TA_ECDSA_SHA_1))
288 		return Crypto.ECDSA_SHA1;
289 	if (oid.equals(CVC.id_TA_ECDSA_SHA_224))
290 		return Crypto.ECDSA_SHA224;
291 	if (oid.equals(CVC.id_TA_ECDSA_SHA_256))
292 		return Crypto.ECDSA_SHA256;
293 	if (oid.equals(CVC.id_TA_ECDSA_SHA_384))
294 		return Crypto.ECDSA_SHA384;
295 	if (oid.equals(CVC.id_TA_ECDSA_SHA_512))
296 		return Crypto.ECDSA_SHA512;
297 	if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_1))
298 		return Crypto.RSA_SHA1;
299 	if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_256))
300 		return Crypto.RSA_SHA256;
301 	if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_512))
302 		return Crypto.RSA_SHA512;
303 	if (oid.equals(CVC.id_TA_RSA_PSS_SHA_1))
304 		return Crypto.RSA_PSS_SHA1;
305 	if (oid.equals(CVC.id_TA_RSA_PSS_SHA_256))
306 		return Crypto.RSA_PSS_SHA256;
307 	if (oid.equals(CVC.id_TA_RSA_PSS_SHA_512))
308 		return Crypto.RSA_PSS_SHA512;
309 	return -1;
310 }
311 
312 
313 
314 /**
315  * Return hash mechanism for object identifier
316  *
317  * @param {ByteString} oid the object identifer from the public key object
318  * @returns the hash mechanism as Crypto. constant or -1 if not defined
319  * @type Number
320  */
321 CVC.getHashMech = function(oid) {
322 	if (oid.equals(CVC.id_TA_ECDSA_SHA_1))
323 		return Crypto.SHA_1;
324 	if (oid.equals(CVC.id_TA_ECDSA_SHA_224))
325 		return Crypto.SHA_224;
326 	if (oid.equals(CVC.id_TA_ECDSA_SHA_256))
327 		return Crypto.SHA_256;
328 	if (oid.equals(CVC.id_TA_ECDSA_SHA_384))
329 		return Crypto.SHA_384;
330 	if (oid.equals(CVC.id_TA_ECDSA_SHA_512))
331 		return Crypto.SHA_512;
332 	if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_1))
333 		return Crypto.SHA_1;
334 	if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_256))
335 		return Crypto.SHA_256;
336 	if (oid.equals(CVC.id_TA_RSA_v1_5_SHA_512))
337 		return Crypto.SHA_512;
338 	if (oid.equals(CVC.id_TA_RSA_PSS_SHA_1))
339 		return Crypto.SHA_1;
340 	if (oid.equals(CVC.id_TA_RSA_PSS_SHA_256))
341 		return Crypto.SHA_256;
342 	if (oid.equals(CVC.id_TA_RSA_PSS_SHA_512))
343 		return Crypto.SHA_512;
344 	return -1;
345 }
346 
347 
348 
349 /**
350  * Return true of the object identifier starts with id-TA-ECDSA
351  *
352  * @type boolean
353  * @return true, if ECDSA based OID
354  */
355 CVC.isECDSA = function(oid) {
356 	return oid.startsWith(CVC.id_TA_ECDSA) == CVC.id_TA_ECDSA.length;
357 }
358 
359 
360 
361 /**
362  * Wrap an ECDSA signature in the format r || s into a TLV encoding as defined by RFC 3279
363  *
364  * @param signature ByteString containing the concatenation of r and s as unsigned integer values
365  * @returns ASN.1 SEQUENCE objects containing two signed integer r and s
366  */
367 CVC.wrapSignature = function(signature) {
368 	var len = signature.length / 2;
369 
370 	// r and s are unsigned big integer. We might need to pad a zero for ASN.1 INTEGER which is signed
371 	var r = signature.bytes(0, len);
372 	while ((r.length > 1) && (r.byteAt(0) == 0)) {
373 		r = r.bytes(1);
374 	}
375 	if (r.byteAt(0) >= 0x80) {
376 		r = ByteString.valueOf(0, 1).concat(r);
377 	}
378 
379 	var s = signature.bytes(len, len);
380 	while ((s.length > 1) && (s.byteAt(0) == 0)) {
381 		s = s.bytes(1);
382 	}
383 	if (s.byteAt(0) >= 0x80) {
384 		s = ByteString.valueOf(0, 1).concat(s);
385 	}
386 
387 	var t = new ASN1(ASN1.SEQUENCE);
388 	t.add(new ASN1(ASN1.INTEGER, r));
389 	t.add(new ASN1(ASN1.INTEGER, s));
390 
391 	return t.getBytes();
392 }
393 
394 
395 
396 /**
397  * Integer to octet string conversion
398  */
399 CVC.I2O = function(value, length) {
400 	if (value.length > length) {
401 		value = value.right(length);
402 	}
403 	while (value.length < length) {
404 		value = CVC.PAD.left((length - value.length - 1 & 15) + 1).concat(value);
405 	}
406 	return value;
407 }
408 CVC.PAD = new ByteString("00000000000000000000000000000000", HEX);
409 
410 
411 
412 /**
413  * Unwrap a ECDSA signature from the TLV encoding according to RFC3279 into the concatenation
414  * of the unsigned integer r and s
415  *
416  * @param signature TLV encoded signature
417  * @returns concatenation of r and s
418  */
419 CVC.unwrapSignature = function(signature, keylen) {
420 	var t = new ASN1(signature);
421 	if (typeof(keylen) != "undefined") {
422 		var r = CVC.I2O(t.get(0).value, keylen);
423 		var s = CVC.I2O(t.get(1).value, keylen);
424 	} else {
425 		var r = t.get(0).value;
426 		if (r.byteAt(0) == 00)
427 			r = r.bytes(1);
428 
429 		var s = t.get(1).value;
430 		if (s.byteAt(0) == 00)
431 			s = s.bytes(1);
432 	}
433 
434 	return r.concat(s);
435 }
436 
437 
438 
439 /**
440  * Rewrap an ECDSA signature that contains redundant leading zeros in integer.
441  *
442  * @param signature the signature
443  * @type ByteString
444  * @param the fixed signature
445  */
446 CVC.rewrapSignature = function(signature) {
447 	var a = new ASN1(signature);
448 	var r = a.get(0).value;
449 	while ((r.byteAt(0) == 0) && (r.byteAt(1) < 0x80)) {
450 		r = r.bytes(1);
451 	}
452 
453 	var s = a.get(1).value;
454 	while ((s.byteAt(0) == 0) && (s.byteAt(1) < 0x80)) {
455 		s = s.bytes(1);
456 	}
457 	return (new ASN1(0x30, new ASN1(0x02, r), new ASN1(0x02, s))).getBytes();
458 }
459 
460 
461 
462 /**
463  * Check a ASN sequence against a structure description
464  *
465  * @private
466  * @param {Object} s the structure description
467  * @param {ASN1} asn the asn sequence
468  * @type String
469  * @return null if valid or the validation error message
470  */
471 CVC.checkStructure = function(s, asn) {
472 	var j = 0;
473 	for (var i = 0; i < s.length; i++) {
474 		var d = s[i];
475 
476 //		print("Checking tag " + d.tag.toString(HEX));
477 
478 		if (j < asn.elements) {
479 			var a = asn.get(j);
480 
481 			if (a.tag == d.tag) {
482 				if ((typeof(d.minlen) != "undefined") && (a.value.length < d.minlen)) {
483 					return "Length (" + a.value.length + ") of " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") less than " + d.minlen;
484 				}
485 				if ((typeof(d.maxlen) != "undefined") && (a.value.length > d.maxlen)) {
486 					return "Length (" + a.value.length + ") of " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") more than " + d.maxlen;
487 				}
488 				if ((typeof(d.content) != "undefined")) {
489 					var r = CVC.checkStructure(d.content, a);
490 					if (r) {
491 						return r;
492 					}
493 				}
494 				j++;
495 			} else {
496 				if (!d.optional) {
497 					return "Expecting " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") but found tag " + a.tag.toString(HEX);
498 				}
499 			}
500 		} else {
501 			if (!d.optional) {
502 				return "Expecting " + CVC.OBJECTNAMES[d.tag] + "(" + d.tag.toString(HEX) + ") but sequence is exhaused";
503 			}
504 		}
505 	}
506 	return null;
507 }
508 
509 
510 
511 /**
512  * Validate ASN1 semantic for card verifiable certificate or certificate request
513  *
514  * @type String
515  * @return null if no error, or error message
516  */
517 CVC.prototype.validate = function() {
518 	var r = null;
519 	if (this.asn.tag == CVC.TAG_AT) {
520 		r = CVC.checkStructure(CVC.ATREQ_SYNTAX, this.asn);
521 	} else {
522 		if (this.body.find(CVC.TAG_CED)) {
523 			r = CVC.checkStructure(CVC.CERT_SYNTAX, this.asn);
524 		} else {
525 			r = CVC.checkStructure(CVC.REQ_SYNTAX, this.asn);
526 		}
527 	}
528 	return r;
529 }
530 
531 
532 
533 /**
534  * Return true of the certificate contains domain parameter
535  *
536  * @type boolean
537  * @return true, if certificate contains domain parameter
538  */
539 CVC.prototype.containsDomainParameter = function() {
540 	var pdo = this.body.find(CVC.TAG_PUK);
541 	if (pdo == null) {
542 		return false;
543 	}
544 
545 	var d = pdo.find(0x84);		// Generator
546 	return (d != null);
547 }
548 
549 
550 
551 /**
552  * Returns the certificate profile indicator (CPI)
553  *
554  * @return the CPI or null
555  * @type Number
556  */
557 CVC.prototype.getCPI = function() {
558 	var cpido = this.body.find(CVC.TAG_CPI);
559 
560 	if (!cpido) {
561 		return null;
562 	}
563 
564 	return cpido.value.toUnsigned();
565 }
566 
567 
568 
569 /**
570  * Returns the certification authority reference (CAR).
571  *
572  * @return the CAR or null
573  * @type PublicKeyReference
574  */
575 CVC.prototype.getCAR = function() {
576 	var cardo = this.body.find(CVC.TAG_CAR);
577 
578 	if (!cardo) {
579 		return null;
580 	}
581 
582 	return new PublicKeyReference(cardo.value);
583 }
584 
585 
586 
587 /**
588  * Returns the certificate holder reference (CHR).
589  *
590  * @return the CHR
591  * @type PublicKeyReference
592  */
593 CVC.prototype.getCHR = function() {
594 	var chrdo = this.body.find(CVC.TAG_CHR);
595 
596 	if (!chrdo) {
597 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a CHR");
598 	}
599 
600 	return new PublicKeyReference(chrdo.value);
601 }
602 
603 
604 
605 /**
606  * Returns the certificate effective date (CED).
607  *
608  * @return the CED or null
609  * @type Date
610  */
611 CVC.prototype.getCED = function() {
612 	var ceddo = this.body.find(CVC.TAG_CED);
613 
614 	if (!ceddo) {
615 		return null
616 	}
617 
618 	var b = ceddo.value;
619 
620 	var d = new Date();
621 	d.setFullYear(b.byteAt(0) * 10 + b.byteAt(1) + 2000,
622 				  b.byteAt(2) * 10 + b.byteAt(3) - 1,
623 				  b.byteAt(4) * 10 + b.byteAt(5));
624 	d.setHours(12, 0, 0, 0);
625 	return d;
626 }
627 
628 
629 
630 /**
631  * Returns the certificate expiration date (CXD).
632  *
633  * @return the CXD or null
634  * @type Date
635  */
636 CVC.prototype.getCXD = function() {
637 	var cxddo = this.body.find(CVC.TAG_CXD);
638 
639 	if (!cxddo) {
640 		return null
641 	}
642 
643 	var b = cxddo.value;
644 
645 	var d = new Date();
646 	d.setFullYear(b.byteAt(0) * 10 + b.byteAt(1) + 2000,
647 				  b.byteAt(2) * 10 + b.byteAt(3) - 1,
648 				  b.byteAt(4) * 10 + b.byteAt(5));
649 	d.setHours(12, 0, 0, 0);
650 	return d;
651 }
652 
653 
654 
655 /**
656  * Returns the outer certification authority reference (CAR).
657  *
658  * @return the outer CAR or null
659  * @type PublicKeyReference
660  */
661 CVC.prototype.getOuterCAR = function() {
662 	if (!this.isAuthenticatedRequest()) {
663 		return null;
664 	}
665 	var cardo = this.asn.get(1);
666 
667 	if (!cardo) {
668 		return null
669 	}
670 
671 	return new PublicKeyReference(cardo.value);
672 }
673 
674 
675 
676 /**
677  * Returns the extension identified by the object identifier.
678  *
679  * @return the extension including the OID or null if not defined
680  * @type ASN1
681  */
682 CVC.prototype.getExtension = function(extoid) {
683 	var extdo = this.body.find(CVC.TAG_EXTN);
684 
685 	if (!extdo) {
686 		return null;
687 	}
688 
689 //	print(extdo);
690 
691 	for (var i = 0; i < extdo.elements; i++) {
692 		var ext = extdo.get(i);
693 		var oid = ext.get(0);
694 		assert(oid.tag == ASN1.OBJECT_IDENTIFIER);
695 		if (oid.value.equals(extoid)) {
696 			return ext;
697 		}
698 	}
699 	return null;
700 }
701 
702 
703 
704 /**
705  * Returns the Certificate Holder Authorization Template.
706  *
707  * @return the chat or null if not defined
708  * @type ASN1
709  */
710 CVC.prototype.getCHAT = function() {
711 	var chat = this.body.find(CVC.TAG_CHAT);
712 
713 	return chat;
714 }
715 
716 
717 
718 /**
719  * Returns the public key object identifier
720  *
721  * @returns the object identifier assigned to the public key
722  * @type ByteString
723  */
724 CVC.prototype.getPublicKeyOID = function() {
725 	var pdo = this.body.find(CVC.TAG_PUK);
726 	if (pdo == null) {
727 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key");
728 	}
729 
730 	var d = pdo.find(ASN1.OBJECT_IDENTIFIER);
731 	if (d == null) {
732 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Public key does not contain an object identifier");
733 	}
734 	return d.value;
735 }
736 
737 
738 
739 /**
740  * Decode a public key from the TR-03110 format
741  *
742  * @param {ASN1} pdo the public key data object
743  * @param {Key} key the key object to fill
744  */
745 CVC.decodeECPublicKey = function(pdo, key) {
746 
747 	var d = pdo.find(0x86);		// Public point
748 	if (d == null) {
749 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key value");
750 	}
751 
752 	var b = d.value.bytes(1);
753 	key.setComponent(Key.ECC_QX, b.left(b.length >> 1));
754 	key.setComponent(Key.ECC_QY, b.right(b.length >> 1));
755 
756 	var d = pdo.find(0x81);		// Prime modulus
757 	if (d != null) {
758 		key.setComponent(Key.ECC_P, d.value);
759 	}
760 
761 	var d = pdo.find(0x82);		// First coefficient a
762 	if (d != null) {
763 		key.setComponent(Key.ECC_A, d.value);
764 	}
765 
766 	var d = pdo.find(0x83);		// First coefficient b
767 	if (d != null) {
768 		key.setComponent(Key.ECC_B, d.value);
769 	}
770 
771 	var d = pdo.find(0x84);		// Base Point G
772 	if (d != null) {
773 		var b = d.value.bytes(1);
774 		key.setComponent(Key.ECC_GX, b.left(b.length >> 1));
775 		key.setComponent(Key.ECC_GY, b.right(b.length >> 1));
776 	}
777 
778 	var d = pdo.find(0x85);		// Order of the base point
779 	if (d != null) {
780 		key.setComponent(Key.ECC_N, d.value);
781 	}
782 
783 	var d = pdo.find(0x87);		// Cofactor f
784 	if (d != null) {
785 		key.setComponent(Key.ECC_H, d.value);
786 	}
787 }
788 
789 
790 
791 /**
792  * Returns the EC public key contained in the certificate.
793  *
794  * @param {Key} domParam optional domain parameter if they are not contained in certificate
795  * @return the public key object
796  * @type Key
797  */
798 CVC.prototype.getECPublicKey = function(domParam) {
799 	if (typeof(domParam) != "undefined") {
800 		var key = new Key(domParam);
801 	} else {
802 		var key = new Key();
803 	}
804 
805 	key.setType(Key.PUBLIC);
806 
807 	var pdo = this.body.find(CVC.TAG_PUK);
808 	if (pdo == null) {
809 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key");
810 	}
811 
812 	CVC.decodeECPublicKey(pdo, key);
813 
814 	return key;
815 }
816 
817 
818 
819 /**
820  * Returns the RSA public key contained in the certificate.
821  *
822  * @return the public key object
823  * @type Key
824  */
825 CVC.prototype.getRSAPublicKey = function() {
826 	var key = new Key();
827 
828 	key.setType(Key.PUBLIC);
829 
830 	var pdo = this.body.find(CVC.TAG_PUK);
831 	if (pdo == null) {
832 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key");
833 	}
834 
835 	var d = pdo.find(0x81);		// modulus
836 	if (d != null) {
837 		key.setComponent(Key.MODULUS, d.value);
838 	}
839 
840 	var d = pdo.find(0x82);		// public exponent
841 	if (d != null) {
842 		key.setComponent(Key.EXPONENT, d.value);
843 	}
844 
845 	return key;
846 }
847 
848 
849 
850 /**
851  * Returns the public key contained in the certificate.
852  *
853  * @param {Key} domParam optional domain parameter if they are not contained in certificate
854  * @return the public key object
855  * @type Key
856  */
857 CVC.prototype.getPublicKey = function(domParam) {
858 	var pkoid = this.getPublicKeyOID();
859 
860 	if (CVC.isECDSA(pkoid)) {
861 		return this.getECPublicKey(domParam);
862 	}
863 	return this.getRSAPublicKey();
864 }
865 
866 
867 
868 /**
869  * Determine the SubjectKeyIdentifier as defined in X.509
870  *
871  * @return the key identifier
872  * @type ByteString
873  */
874 CVC.prototype.determineKeyIdentifier = function() {
875 	var pdo = this.body.find(CVC.TAG_PUK);
876 	if (pdo == null) {
877 		throw new GPError("CVC", GPError.OBJECT_NOT_FOUND, 0, "Certificate does not contain a public key");
878 	}
879 
880 	var pkoid = this.getPublicKeyOID();
881 
882 	var keybin;
883 	if (CVC.isECDSA(pkoid)) {
884 		var d = pdo.find(0x86);		// Public point
885 		keybin = d.value;
886 	} else {
887 		var modulus = pdo.find(0x81).value;		// modulus
888 		if (modulus.byteAt(0) >= 0x80) {
889 			modulus = ByteString.valueOf(0).concat(modulus);
890 		}
891 		var exponent = pdo.find(0x82).value;		// public exponent
892 		var rsapub = new ASN1("RSAPublicKey", ASN1.SEQUENCE,
893 				      new ASN1("modulus", ASN1.INTEGER, modulus),
894 				      new ASN1("publicKeyExponent", ASN1.INTEGER, exponent));
895 
896 		keybin = rsapub.getBytes();
897 	}
898 	return CVC.crypto.digest(Crypto.SHA_1, keybin);
899 }
900 
901 
902 
903 /**
904  * Determine if this is an authenticated request
905  *
906  * @returns true, if authenticated request
907  * @type Boolean
908  */
909 CVC.prototype.isAuthenticatedRequest = function() {
910 	return (this.asn.tag == CVC.TAG_AT);
911 }
912 
913 
914 
915 /**
916  * Determine if this is a certificate request
917  *
918  * @returns true, if certificate request
919  * @type Boolean
920  */
921 CVC.prototype.isCertificateRequest = function() {
922 	if (this.isAuthenticatedRequest()) {
923 		return true;
924 	}
925 
926 	var ced = this.getCED();
927 	return ced == null;
928 }
929 
930 
931 
932 /**
933  * Determine if this is a countersigned authenticated request
934  *
935  * @returns true, if countersigned authenticated request
936  * @type Boolean
937  */
938 CVC.prototype.isCountersignedRequest = function() {
939 	if (!this.isAuthenticatedRequest()) {
940 		return false;
941 	}
942 	return (this.getCHR().getHolder() != this.getOuterCAR().getHolder());
943 }
944 
945 
946 
947 /**
948  * Determine if this certificate is expired
949  *
950  * @returns true, if certificate is expired
951  * @type Boolean
952  */
953 CVC.prototype.isExpired = function() {
954 	var now = new Date();
955 	now.setHours(12, 0, 0, 0);
956 	return (now.valueOf() > this.getCXD().valueOf());
957 }
958 
959 
960 
961 /**
962  * Verify certificate signature with public key
963  *
964  * @param {Key} puk the public key
965  * @param {ByteString} oid the signature algorithm
966  * @returns true if the signature is valid
967  * @type Boolean
968  */
969 CVC.prototype.verifyWith = function(crypto, puk, oid) {
970 	if (this.asn.tag == CVC.TAG_AT) {
971 		var signature = this.asn.get(0).get(1);
972 	} else {
973 		var signature = this.asn.get(1);
974 	}
975 
976 	if (typeof(oid) == "undefined") {
977 		var oid = this.getPublicKeyOID();
978 	}
979 	var mech = CVC.getSignatureMech(oid);
980 
981 	if (CVC.isECDSA(oid)) {
982 		var signatureValue = CVC.wrapSignature(signature.value);
983 	} else {
984 		var signatureValue = signature.value;
985 	}
986 
987 	return crypto.verify(puk, mech, this.body.getBytes(), signatureValue);
988 }
989 
990 
991 
992 /**
993  * Verify certificate signature with public key from card verifiable certificate
994  *
995  * @param {CVC} cvc the card verifiable certificate used to obtain the public key
996  * @returns true if the signature is valid
997  * @type Boolean
998  */
999 CVC.prototype.verifyWithCVC = function(crypto, cvc) {
1000 	return this.verifyWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID());
1001 }
1002 
1003 
1004 
1005 /**
1006  * Verify outer signature of an authenticated request with public key
1007  *
1008  * @param {Key} puk the public key
1009  * @param {ByteString} oid the signature algorithm
1010  * @returns true if the signature is valid
1011  * @type Boolean
1012  */
1013 CVC.prototype.verifyATWith = function(crypto, puk, oid) {
1014 	if (!this.isAuthenticatedRequest()) {
1015 		throw new GPError("CVC", GPError.INVALID_DATA, 0, "Not an authenticated request");
1016 	}
1017 
1018 	var signature = this.asn.get(2);
1019 	var signatureInput = this.asn.get(0).getBytes().concat(this.asn.get(1).getBytes());
1020 
1021 	if (typeof(oid) == "undefined") {
1022 		var oid = this.getPublicKeyOID();
1023 	}
1024 	var mech = CVC.getSignatureMech(oid);
1025 
1026 	if (CVC.isECDSA(oid)) {
1027 		var signatureValue = CVC.wrapSignature(signature.value);
1028 	} else {
1029 		var signatureValue = signature.value;
1030 	}
1031 	return crypto.verify(puk, mech, signatureInput, signatureValue);
1032 }
1033 
1034 
1035 
1036 /**
1037  * Verify outer signature of an authenticated request with public key from card verifiable certificate
1038  *
1039  * @param {CVC} cvc the card verifiable certificate used to obtain the public key
1040  * @returns true if the signature is valid
1041  * @type Boolean
1042  */
1043 CVC.prototype.verifyATWithCVC = function(crypto, cvc) {
1044 	return this.verifyATWith(crypto, cvc.getPublicKey(), cvc.getPublicKeyOID());
1045 }
1046 
1047 
1048 
1049 /**
1050  * Returns the encoded certificate
1051  *
1052  * @return the DER encoded certificate
1053  * @type ByteString
1054  */
1055 CVC.prototype.getBytes = function() {
1056 	return this.bin;
1057 }
1058 
1059 
1060 
1061 /**
1062  * Returns the certificate as ASN1 structure
1063  *
1064  * @return the certificate as ASN1 structure
1065  * @type ASN1
1066  */
1067 CVC.prototype.getASN1 = function() {
1068 	return this.asn;
1069 }
1070 
1071 
1072 
1073 /**
1074  * Function to recursively walk the ASN.1 tree
1075  */
1076 CVC.decorateTree = function(node) {
1077 	var name = CVC.OBJECTNAMES[node.tag];
1078 
1079 	if (name) {
1080 		node.setName(name);
1081 	}
1082 
1083 	if (node.isconstructed) {
1084 		for (var i = 0; i < node.elements; i++) {
1085 			CVC.decorateTree(node.get(i));
1086 		}
1087 	}
1088 }
1089 
1090 
1091 
1092 /**
1093  * Decorate the ASN.1 object with the correct name
1094  */
1095 CVC.prototype.decorate = function() {
1096 	CVC.decorateTree(this.asn);
1097 	var cxddo = this.body.find(CVC.TAG_CXD);
1098 	if (cxddo == null) {
1099 		if (this.asn.tag == CVC.TAG_AT) {
1100 			this.asn.setName("Authenticated CVC Request");
1101 		} else {
1102 			this.asn.setName("CVC Request");
1103 		}
1104 	}
1105 }
1106 
1107 
1108 
1109 /**
1110  * Return list of rights granted by the certificate
1111  *
1112  * @returns the list of rights
1113  * @type String[]
1114  */
1115 CVC.prototype.getRightsAsList = function() {
1116 	var list = [];
1117 
1118 	var rtab;
1119 	var chat = this.getCHAT();
1120 	if (chat == null) {
1121 		return list;
1122 	}
1123 
1124 	var oid = chat.get(0).value;
1125 
1126 	if (oid.equals(CVC.idIS)) {
1127 		rtab = CVC.ISRIGHTS;
1128 	} else if (oid.equals(CVC.idAT)) {
1129 		rtab = CVC.ATRIGHTS;
1130 	} else if (oid.equals(CVC.idST)) {
1131 		rtab = CVC.STRIGHTS;
1132 	} else {
1133 		return null;
1134 	}
1135 
1136 	var mask = chat.get(1).value;
1137 	var c = 0;
1138 	for (var i = mask.length - 1; i >= 0; i--) {
1139 		var akku = mask.byteAt(i);
1140 		for (var j = 0; j < (i == 0 ? 6 : 8); j++) {
1141 			if (akku & 1) {
1142 				list.push(rtab[c]);
1143 			}
1144 			c++;
1145 			akku >>= 1;
1146 		}
1147 	}
1148 	return list;
1149 }
1150 
1151 
1152 
1153 /**
1154  * Return position of certificate in PKI hierachie
1155  *
1156  * @returns Position in PKI hierachie: 0 unknown, 1-CVCA, 2-DVCA, 3-Terminal
1157  * @type Number
1158  */
1159 CVC.prototype.getLevel = function() {
1160 	var chat = this.getCHAT();
1161 	if (!chat) {
1162 		return 0;
1163 	}
1164 
1165 	var l = chat.get(1).value.byteAt(0) & 0xC0;
1166 	if (l == 0xC0) {
1167 		return 1;
1168 	}
1169 	return l == 0x00 ? 3 : 2;
1170 }
1171 
1172 
1173 
1174 /**
1175  * Return a string describing the certificate type
1176  *
1177  * @returns a describing string
1178  * @type String
1179  */
1180 CVC.prototype.getType = function() {
1181 	var ced = this.getCED();
1182 	var chat = this.getCHAT();
1183 
1184 	// Decode certificate / request type
1185 	var str = "CVC ";
1186 	if (ced == null) {
1187 		if (this.asn.tag == CVC.TAG_AT) {
1188 			str = "AT-CVREQ ";
1189 		} else {
1190 			str = "CVREQ ";
1191 		}
1192 	}
1193 
1194 	// Decode CA type
1195 	if (chat != null) {
1196 		var oid = chat.get(0).value;
1197 
1198 		if (oid.equals(CVC.idSC_HSM)) {
1199 			str += "id-SC-HSM ";
1200 			switch(chat.get(1).value.byteAt(0) & 0xC0) {
1201 				case 0xC0: str += "SRCA "; break;
1202 				case 0x80: str += "DICA "; break;
1203 				case 0x40: str += "DICA "; break;
1204 				case 0x00: str += "Device "; break;
1205 			}
1206 		} else {
1207 			var trustedDV = "";
1208 			var untrustedDV = "";
1209 
1210 			if (oid.equals(CVC.idIS)) {
1211 				str += "id-IS ";
1212 				trustedDV = "(official domestic) ";
1213 				untrustedDV = "(official foreign) ";
1214 			} else if (oid.equals(CVC.idAT)) {
1215 				str += "id-AT ";
1216 				trustedDV = "(official domestic) ";
1217 				untrustedDV = "(non-official / foreign) ";
1218 			} else if (oid.equals(CVC.idST)) {
1219 				str += "id-ST ";
1220 				trustedDV = "(accreditation body) ";
1221 				untrustedDV = "(certification service provider) ";
1222 			} else {
1223 				str += oid.toString(OID) + " ";
1224 			}
1225 
1226 			switch(chat.get(1).value.byteAt(0) & 0xC0) {
1227 				case 0xC0: str += "CVCA "; break;
1228 				case 0x80: str += "DV " + trustedDV; break;
1229 				case 0x40: str += "DV " + untrustedDV; break;
1230 				case 0x00: str += "Terminal "; break;
1231 			}
1232 		}
1233 	}
1234 
1235 	return str;
1236 }
1237 
1238 
1239 
1240 /**
1241  * Return a textual description of the certificate
1242  *
1243  * @returns a string containing information about the certificate
1244  * @type String
1245  */
1246 CVC.prototype.toString = function() {
1247 	var car = this.getCAR();
1248 	var ced = this.getCED();
1249 
1250 	var str = this.getType();
1251 
1252 	if (car) {
1253 		str += "CAR=" + car.toString() + " ";
1254 	}
1255 
1256 	str += "CHR=" + this.getCHR().toString() + " ";
1257 
1258 	if (ced) {
1259 		str += "CED=" + ced.toLocaleDateString() + " ";
1260 	}
1261 
1262 	var cxd = this.getCXD();
1263 	if (cxd) {
1264 		str += "CXD=" + cxd.toLocaleDateString() + " ";
1265 	}
1266 
1267 	if (this.isAuthenticatedRequest()) {
1268 		str += "oCAR=" + this.getOuterCAR().toString() + " ";
1269 	}
1270 
1271 	return str;
1272 }
1273 
1274 
1275 
1276 /**
1277  * Return object suitable for JSON encoding
1278  *
1279  * @returns a JSON encodable object
1280  * @type Object
1281  */
1282 CVC.prototype.toJSON = function() {
1283 	return { clazz: "CVC", value: this.bin };
1284 }
1285 
1286 
1287 
1288 /**
1289  * Recreate object from JSON encoding
1290  *
1291  * @returns the CVC object
1292  * @type Object
1293  */
1294 CVC.fromJSON = function(obj) {
1295 	assert(obj.clazz.equals("CVC"), "JSON object must contain clazz: CVC");
1296 	return new CVC(obj.value);
1297 }
1298