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