      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