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