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