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 TrustAnchor Base class for card verifiable certificate based access controller
 25  */
 26 
 27  
 28 /**
 29  * Create a TrustAnchor object that handles certificate validation, terminal authentication and access control
 30  *
 31  * @constructor
 32  * @class Class implementing a CVC based access controller
 33  * @param {CVC} root the root certificate
 34  */
 35 function TrustAnchor(root) {
 36 	if (typeof(root) == "undefined") {
 37 		return;
 38 	}
 39 
 40 	this.chain = [];
 41 	this.chain.push(root);
 42 	this.recentCAROnly = false;					// Maintain only the latest trust anchor
 43 
 44 	// Save in file system under id, which is the last byte in the CHAT OID
 45 	var chat = root.getCHAT();
 46 	var id = chat.get(0).value.right(1).toUnsigned();
 47 	var name = root.getCHR().getHolder();		// Use the name of the CVCA
 48 	FileSystemIdObject.call(this, name, id);
 49 }
 50 
 51 TrustAnchor.prototype = new FileSystemIdObject();
 52 TrustAnchor.prototype.constructor = TrustAnchor;
 53 
 54 TrustAnchor.TYPE = "TrustAnchor";
 55 TrustAnchor.idIS = new ByteString("id-IS", OID);
 56 
 57 
 58 
 59 /**
 60  * Return type of file system object
 61  *
 62  * @type String
 63  * @return the type string
 64  */
 65 TrustAnchor.prototype.getType = function() {
 66 	return TrustAnchor.TYPE;
 67 }
 68 
 69 
 70 
 71 
 72 /**
 73  * Add recent trust anchor to PACE response
 74  *
 75  * @param {ASN1} response the response object to receive tag 87 and 88
 76  */
 77 TrustAnchor.prototype.addCARforPACE = function(response) {
 78 	var cl = this.chain.length;
 79 	response.add(new ASN1(0x87, this.chain[cl - 1].getCHR().getBytes()));
 80 	if ((cl > 1) && !this.recentCAROnly) {
 81 		response.add(new ASN1(0x88, this.chain[cl - 2].getCHR().getBytes()));
 82 	}
 83 }
 84 
 85 
 86 
 87 /**
 88  * Is a recent trust anchor issuer of the certificate chr in question
 89  *
 90  * @param {PublicKeyReference} chr the certificate holder
 91  * @type boolean
 92  * @return true if trust anchor issued certificate
 93  */
 94 TrustAnchor.prototype.isIssuer = function(chr) {
 95 	var cvc = this.getCertificateFor(chr);
 96 //	print("isIssuer(" + chr + "):");
 97 //	print(cvc);
 98 	return cvc != null;
 99 }
100 
101 
102 
103 /**
104  * Get public key from certificate, possibly determine the domain parameter from previous trust anchors
105  *
106  * @param {PublicKeyReference} chr the certificate holder
107  * @type Key
108  * @return the public key or null
109  */
110 TrustAnchor.prototype.getPublicKeyFor = function(chr) {
111 //	print("Get public key for " + chr);
112 	var cl = this.chain.length - 1;
113 	for (; (cl >= 0) && !this.chain[cl].getCHR().equals(chr); cl--) {
114 	}
115 
116 	if (cl < 0) {
117 //		print("chr not found");
118 		return null;
119 	}
120 
121 	var i = cl;
122 	if (CVC.isECDSA(this.chain[cl].getPublicKeyOID()) && !this.chain[i].containsDomainParameter()) {
123 //		print("Looking for DPs down the chain");
124 		for (cl--; (cl >= 0) && !this.chain[cl].containsDomainParameter(); cl--) {}
125 		if (cl < 0) {
126 			return null;
127 		}
128 //		print("Found domain parameter in " + this.chain[cl]);
129 		var dp = this.chain[cl].getPublicKey();
130 		return this.chain[i].getPublicKey(dp);
131 	} else {
132 //		print(this.chain[i]);
133 		return this.chain[i].getPublicKey();
134 	}
135 }
136 
137 
138 
139 /**
140  * Return certificate for chr
141  *
142  * @param {PublicKeyReference} chr the certificate holder
143  * @type CVC
144  * @return the certificate or null
145  */
146 TrustAnchor.prototype.getCertificateFor = function(chr) {
147 	var cl = this.chain.length;
148 	if (this.chain[cl - 1].getCHR().toString() == chr) {
149 		return this.chain[cl - 1];
150 	}
151 	if ((cl > 1) && !this.recentCAROnly) {
152 		if (this.chain[cl - 2].getCHR().toString() == chr) {
153 			return this.chain[cl - 2];
154 		}
155 	}
156 	return null;
157 }
158 
159 
160 
161 /**
162  * Update EF.CVCA with list of valid trust anchors
163  *
164  * @param {Object} dataProvider object implementing getDate(), setDate() and updateEFCVCA()
165  */
166 TrustAnchor.prototype.updateEFCVCA = function(dataProvider) {
167 	var cl = this.chain.length - 1;
168 	var bb = new ByteBuffer();
169 	bb.append((new ASN1(0x42, this.chain[cl].getCHR().getBytes())).getBytes());
170 	if ((cl > 0) && !this.recentCAROnly) {
171 		bb.append((new ASN1(0x42, this.chain[cl - 1].getCHR().getBytes())).getBytes());
172 	}
173 	bb.append(0);
174 	dataProvider.updateEFCVCA(bb.toByteString());
175 }
176 
177 
178 
179 /**
180  * Check certificate
181  *
182  * <p>This method updates the current date for certificates issued by domestic DVCAs.</p>
183  * @param {CVC} issuer the issuing certificate
184  * @param {CVC} subject the subjects certificate
185  * @param {Object} dataProvider object implementing getDate(), setDate() and updateEFCVCA()
186  * @param {Key} dp domain parameter for checking the public key
187  */
188 TrustAnchor.prototype.checkCertificate = function(issuer, subject, dataProvider, dp) {
189 	// Some basic sanity checks
190 	if (subject.getCXD().valueOf() < subject.getCED().valueOf()) {
191 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Certificate expiration is before effective date");
192 	}
193 	
194 	try	{
195 		var puk = subject.getPublicKey(dp);
196 		if (puk.getComponent(Key.ECC_QX).length + puk.getComponent(Key.ECC_QY).length != (puk.getComponent(Key.ECC_P).length << 1)) {
197 			throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid public key");
198 		}
199 	}
200 	catch(e) {
201 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, e.message);
202 	}
203 	
204 	var chatissuer = issuer.getCHAT();
205 	var chatsubject = subject.getCHAT();
206 	
207 	var rolesubject = chatsubject.get(0).value;
208 	if (!chatissuer.get(0).value.equals(rolesubject)) {
209 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Role mismatch");
210 	}
211 
212 	rightsissuer = chatissuer.get(1).value;
213 	rightssubject = chatsubject.get(1).value;
214 	
215 	if (rightsissuer.length != rightssubject.length) {
216 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Different size in rights mask of chat");
217 	}
218 
219 	typeissuer = rightsissuer.byteAt(0) & 0xC0;
220 	typesubject = rightssubject.byteAt(0) & 0xC0;
221 
222 	// C0 - CVCA, 80 - DV domestic, 40 - DV foreign, 00 - Terminal
223 	if (typeissuer == 0x40) {		// Ignore domestic and foreign
224 		typeissuer = 0x80;
225 	}
226 	if (typesubject == 0x40) {
227 		typesubject = 0x80;
228 	}
229 //	print("issuer " + typeissuer);
230 //	print("subject " + typesubject);
231 
232 	if (((typesubject >= typeissuer) && (typeissuer != 0xC0)) || 
233 		((typesubject == 0x00) && (typeissuer == 0xC0))) {
234 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Certificate hierachy invalid");
235 	}
236 
237 	if (!issuer.getPublicKeyOID().equals(subject.getPublicKeyOID())) {
238 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Public key algorithm mismatch");
239 	}
240 
241 	var date = dataProvider.getDate().valueOf();
242 	if (typesubject != 0xC0) {			// CVCA certificates do not expire
243 		if (subject.getCXD().valueOf() < date) {
244 			throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Certificate is expired");
245 		}
246 	} else {
247 		print("Add to chain: " + subject);
248 		this.chain.push(subject);							// Add new CVCA link to the chain
249 		if (rolesubject.equals(TrustAnchor.idIS)) {
250 			this.updateEFCVCA(dataProvider);					// Update /DF.ePass/EF.CVCA
251 		}
252 	}
253 
254 	if ((rightsissuer.byteAt(0) & 0xC0) != 0x40) {			// Trust all except foreign DVCAs
255 		if (subject.getCED().valueOf() > date) {
256 			dataProvider.setDate(subject.getCED());
257 		}
258 	}
259 }
260 
261 
262 
263 /**
264  * Validate certificate issued by CVCA
265  *
266  * @param {Crypto} crypto the crypto object to use for verification
267  * @param {CVC} cert the certificate to validate
268  * @param {Object} dataProvider object implementing getDate(), setDate() and updateEFCVCA()
269  */
270 TrustAnchor.prototype.validateCertificateIssuedByCVCA = function(crypto, cert, dataProvider) {
271 	cc = this.getCertificateFor(cert.getCAR());
272 	puk = this.getPublicKeyFor(cert.getCAR());
273 	if (!puk || !cert.verifyWith(crypto, puk, cc.getPublicKeyOID())) {
274 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Could not verify certificate signature");
275 	}
276 	this.checkCertificate(cc, cert, dataProvider, puk);
277 }
278 
279 
280 
281 /**
282  * Validate certificate issued by CVCA
283  *
284  * @param {Crypto} crypto the crypto object to use for verification
285  * @param {CVC} cert the certificate to validate
286  * @param {CVC} dvca the issuing certificate
287  * @param {Object} dataProvider object implementing getDate(), setDate() and updateEFCVCA()
288  */
289 TrustAnchor.prototype.validateCertificateIssuedByDVCA = function(crypto, cert, dvca, dataProvider) {
290 	var dp = this.getPublicKeyFor(dvca.getCAR());
291 //	print(dp);
292 	if (!dp || !cert.verifyWith(crypto, dvca.getPublicKey(dp), dvca.getPublicKeyOID())) {
293 		throw new GPError("TrustAnchor", GPError.INVALID_DATA, APDU.SW_INVDATA, "Could not verify certificate signature");
294 	}
295 	this.checkCertificate(dvca, cert, dataProvider, dvca.getPublicKey(dp));
296 }
297