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 Implementation of a secure messaging channel as defined in ISO 7814-4 and eSign-K 25 */ 26 27 28 29 /** 30 * Create a secure channel 31 * 32 * @class Class implementing a secure messaging channel 33 * @constructor 34 * @param {Crypto} crypto the crypto provider 35 */ 36 function SecureChannel(crypto) { 37 this.crypto = crypto; 38 this.policy = IsoSecureChannel.SSC_DEFAULT_POLICY; 39 } 40 41 42 43 /** 44 * Sets the policy for handling send sequence counters 45 * 46 * See IsoSecureChannel for details 47 * 48 * @param {Number} policy one of IsoSecureChannel.SSC_DEFAULT_POLICY, SSC_SYNC_POLICY or SSC_SYNC_ENC_POLICY 49 */ 50 SecureChannel.prototype.setSendSequenceCounterPolicy = function(policy) { 51 this.policy = policy; 52 } 53 54 55 56 /** 57 * Sets the key used for encryption 58 * 59 * @param {Key} key the encryption key 60 */ 61 SecureChannel.prototype.setEncKey = function(key) { 62 this.encKey = key; 63 64 if (key.getComponent(Key.AES)) { 65 this.encBlockSize = 16; 66 this.encMechanism = Crypto.AES_CBC; 67 this.iv = new ByteString("00000000000000000000000000000000", HEX); 68 } else { 69 this.encBlockSize = 8; 70 this.encMechanism = Crypto.DES_CBC; 71 this.iv = new ByteString("0000000000000000", HEX); 72 } 73 } 74 75 76 77 /** 78 * Sets the key used for message authentication 79 * 80 * @param {Key} key the message authentication key 81 */ 82 SecureChannel.prototype.setMacKey = function(key) { 83 this.macKey = key; 84 85 if (key.getComponent(Key.AES)) { 86 this.macBlockSize = 16; 87 this.macMechanism = Crypto.AES_CMAC; 88 } else { 89 this.macBlockSize = 8; 90 this.macMechanism = Crypto.DES_MAC_EMV; 91 } 92 } 93 94 95 96 /** 97 * Set the send sequence counter for MAC calculation 98 * 99 * @param {ByteString} ssc the send sequence counter 100 */ 101 SecureChannel.prototype.setMACSendSequenceCounter = function(ssc) { 102 this.macSendSequenceCounter = ssc; 103 } 104 105 106 107 /** 108 * Set the send sequence counter for encryption calculation 109 * 110 * @param {ByteString} ssc the send sequence counter 111 */ 112 SecureChannel.prototype.setEncSendSequenceCounter = function(ssc) { 113 this.encSendSequenceCounter = ssc; 114 } 115 116 117 118 /** 119 * Return an initialisation vector based on the defined policy 120 * 121 * @type ByteString 122 * @return the IV 123 */ 124 SecureChannel.prototype.getIV = function() { 125 var iv = this.iv; 126 if (this.policy == IsoSecureChannel.SSC_SYNC_ENC_POLICY) { 127 iv = this.crypto.encrypt(this.encKey, this.encMechanism, this.macSendSequenceCounter, iv); 128 } else if (this.policy == IsoSecureChannel.SSC_SYNC_POLICY) { 129 iv = this.macSendSequenceCounter; 130 } else { 131 if (typeof(this.encSendSequenceCounter) != "undefined") { 132 iv = this.encSendSequenceCounter 133 } 134 } 135 return iv; 136 } 137 138 139 140 /** 141 * Unwrap a secure messaging APDU recovering the content 142 * 143 * @param {APDU} apdu the APDU to unwrap 144 */ 145 SecureChannel.prototype.unwrap = function(apdu) { 146 var decoder = new SecureMessagingCommandAPDUDecoder(this, apdu); 147 if (!decoder.verifyMAC(this.macKey)) { 148 throw new GPError("SecureChannel", GPError.CRYPTO_FAILED, APDU.SW_INCSMDATAOBJECT, "MAC verification failed"); 149 } 150 151 var le = decoder.getLe(); 152 if (le >= 0) { 153 apdu.ne = le; 154 } 155 156 var plain = decoder.decryptBody(this.encKey); 157 apdu.setCData(plain); 158 } 159 160 161 162 /** 163 * Wrap an APDU for secure messaging 164 * 165 * @param {APDU} apdu the APDU to wrap 166 */ 167 SecureChannel.prototype.wrap = function(apdu) { 168 var rdata = new ByteBuffer(); 169 170 var macinp = new ByteBuffer(); 171 172 if (typeof(this.macSendSequenceCounter) != "undefined") { 173 var ssc = this.macSendSequenceCounter.add(1); 174 this.macSendSequenceCounter = ssc; 175 macinp.append(ssc); 176 } 177 178 if (apdu.hasRData()) { 179 var padbuff = new ByteBuffer(apdu.getRData()); 180 SecureChannel.pad(padbuff, this.encBlockSize); 181 var iv = this.getIV(); 182 var cryptogram = this.crypto.encrypt(this.encKey, this.encMechanism, padbuff.toByteString(), iv); 183 var padind = new ByteString("01", HEX); 184 185 var do87 = new TLV(0x87, padind.concat(cryptogram), TLV.EMV); 186 rdata.append(do87.getTLV()); 187 } 188 189 rdata.append(0x99); 190 rdata.append(2); 191 rdata.append(apdu.getSW() >> 8); 192 rdata.append(apdu.getSW() & 0xFF); 193 194 macinp.append(rdata); 195 196 SecureChannel.pad(macinp, this.macBlockSize); 197 var mac = this.crypto.sign(this.macKey, this.macMechanism, macinp.toByteString()); 198 199 rdata.append(0x8E); 200 rdata.append(0x08); 201 rdata.append(mac.left(8)); 202 203 apdu.setRData(rdata.toByteString()); 204 } 205 206 207 208 /** 209 * Applies ISO padding to the input buffer 210 * 211 * @param {ByteBuffer} buffer the input buffer 212 * @param {Number} blocksize the block size 213 * @type ByteBuffer 214 * @return the buffer argument 215 */ 216 SecureChannel.pad = function(buffer, blocksize) { 217 buffer.append(0x80); 218 while (buffer.length % blocksize) { 219 buffer.append(0x00); 220 } 221 return buffer; 222 } 223 224 225 226 /** 227 * Removes the ISO padding 228 * 229 * @param {ByteString} buffer the input with with padding 230 * @type ByteString 231 * @return the buffer without padding 232 */ 233 SecureChannel.removePadding = function(buffer) { 234 var i = buffer.length - 1; 235 236 while ((i >= 0) && (buffer.byteAt(i) == 0x00)) { 237 i--; 238 } 239 240 if ((i < 0) || (buffer.byteAt(i) != 0x80)) { 241 throw new GPError("SecureMessagingCommandAPDUDecoder", GPError.CRYPTO_FAILED, APDU.SW_INCSMDATAOBJECT, "Invalid ISO padding"); 242 } 243 244 return buffer.left(i); 245 } 246 247 248 249 /** 250 * Creates a decoder for a single secure messaging command APDU 251 * 252 * @class Decoder for a secure messaging APDU 253 * @constructor 254 * @param {SecureChannel} channel the secure channel object 255 * @param {APDU} apdu the secure messaging APDU 256 */ 257 function SecureMessagingCommandAPDUDecoder(channel, apdu) { 258 this.channel = channel; 259 this.apdu = apdu; 260 this.tlvlist = apdu.getCDataAsTLVList(); 261 } 262 263 264 265 /** 266 * Verify the message authentication code (MAC) 267 * 268 * @type boolean 269 * @return true if the MAC is valid 270 */ 271 SecureMessagingCommandAPDUDecoder.prototype.verifyMAC = function() { 272 var macinp = this.buildMACInput(); 273 274 var mac = this.tlvlist.find(0x8E); 275 276 if (mac == null) { 277 throw new GPError("SecureMessagingCommandAPDUDecoder", GPError.INVALID_DATA, APDU.SW_SMOBJMISSING, "MAC data object (8E) not found"); 278 } 279 280 return this.channel.crypto.verify(this.channel.macKey, this.channel.macMechanism, macinp, mac.getValue()); 281 } 282 283 284 285 /** 286 * Build the MAC input block 287 * 288 * @type ByteString 289 * @return the MAC calculation input block 290 */ 291 SecureMessagingCommandAPDUDecoder.prototype.buildMACInput = function() { 292 var macinp = new ByteBuffer(); 293 294 if (typeof(this.channel.macSendSequenceCounter) != "undefined") { 295 var ssc = this.channel.macSendSequenceCounter.add(1); 296 this.channel.macSendSequenceCounter = ssc; 297 macinp.append(ssc); 298 } 299 300 if (this.apdu.isAuthenticatedHeader()) { 301 macinp.append(this.apdu.getCLA()); 302 macinp.append(this.apdu.getINS()); 303 macinp.append(this.apdu.getP1()); 304 macinp.append(this.apdu.getP2()); 305 SecureChannel.pad(macinp, this.channel.macBlockSize); 306 } 307 308 var someadded = false; 309 for (var i = 0; i < this.tlvlist.length; i++) { 310 var tlv = this.tlvlist.index(i); 311 312 if (tlv.getTag() & 0x01) { 313 macinp.append(tlv.getTLV()); 314 someadded = true; 315 } 316 } 317 if (someadded) { 318 SecureChannel.pad(macinp, this.channel.macBlockSize); 319 } 320 return macinp.toByteString(); 321 } 322 323 324 325 /** 326 * Decrypt the body of a secure messaging APDU 327 * 328 * @param {Key} key the encryption key 329 * @type ByteString 330 * @return the plain body 331 */ 332 SecureMessagingCommandAPDUDecoder.prototype.decryptBody = function(key) { 333 var body = this.tlvlist.find(0x87); 334 var ofs = 1; 335 if (body == null) { 336 var body = this.tlvlist.find(0x85); 337 if (body == null) { 338 return null; 339 } 340 var ofs = 0; 341 } else { 342 var paddingIndicator = body.getValue().byteAt(0); 343 if (paddingIndicator != 0x01) { 344 throw new GPError("SecureMessagingCommandAPDUDecoder", GPError.INVALID_DATA, APDU.SW_INCSMDATAOBJECT, "Padding indicator " + paddingIndicator + " not supported"); 345 } 346 } 347 348 var cryptogram = body.getValue().bytes(ofs); 349 350 var iv = this.channel.getIV(); 351 var plain = this.channel.crypto.decrypt(this.channel.encKey, this.channel.encMechanism, cryptogram, iv); 352 353 plain = SecureChannel.removePadding(plain); 354 355 return plain; 356 } 357 358 359 360 /** 361 * Return value of optional Le element with tag '97' 362 * 363 * @type Number 364 * @return the value of the Le element 365 */ 366 SecureMessagingCommandAPDUDecoder.prototype.getLe = function() { 367 var le = this.tlvlist.find(0x97); 368 if (le == null) { 369 return -1; 370 } 371 return le.getValue().toUnsigned(); 372 } 373