1 /** 2 * --------- 3 * |.##> <##.| Open Smart Card Development Platform (www.openscdp.org) 4 * |# #| 5 * |# #| Copyright (c) 1999-2026 CardContact Systems GmbH 6 * |'##> <##'| 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 File encryptor. 25 */ 26 27 var CVC = require("scsh/eac/CVC").CVC; 28 var PKIXCommon = require("scsh/x509/PKIXCommon").PKIXCommon; 29 30 31 32 /** 33 * Craate a FileEncryptor instance. 34 * 35 * @class Class implementing file encryption 36 * @constructor 37 * @param {SmartCardHSM} sc the SmartCardHSM object used to access the token or undefined at the sender in the hybrid scheme. 38 * @param {Number} mode the mode to use (One of FileEncryptor.NATIVE, .DERIVED or .HYBRID). Must be specified at the sending side. 39 */ 40 function FileEncryptor(sc, mode) { 41 this.mode = mode; 42 this.sc = sc; 43 this.crypto = new Crypto(); 44 45 this.keyId = new ByteString("UNKNOWN", ASCII); 46 } 47 48 exports.FileEncryptor = FileEncryptor; 49 50 FileEncryptor.NATIVE = 1; 51 FileEncryptor.DERIVED = 2; 52 FileEncryptor.HYBRID = 3; 53 54 55 56 /** 57 * Get the crypto object. 58 * 59 * @private 60 * @type Crypto 61 * @return the crypto object. 62 */ 63 FileEncryptor.prototype.getCrypto = function() { 64 if (typeof(this.sc) != "undefined") { 65 return this.sc.getCrypto(); 66 } 67 return this.crypto; 68 } 69 70 71 72 /** 73 * Generate the IV 74 * 75 * @private 76 */ 77 FileEncryptor.prototype.generateIV = function() { 78 this.iv = this.crypto.generateRandom(16); 79 } 80 81 82 83 /** 84 * Calculate the hash of the encrypted payload. 85 * 86 * @private 87 * @param {ByteString} content the encrypted payload 88 * @type ByteString 89 * @return the hash value. 90 */ 91 FileEncryptor.prototype.calculateHash = function(content) { 92 return this.crypto.digest(Crypto.SHA_256, content); 93 } 94 95 96 97 /** 98 * Create the TLV encoded header of the encrypted file. 99 * 100 * @private 101 * @type ASN1 102 * @return the header. 103 */ 104 FileEncryptor.prototype.createHeader = function() { 105 var tbs = new ASN1("tbs", ASN1.SEQUENCE, 106 new ASN1("oid", ASN1.OBJECT_IDENTIFIER, new ByteString("CardContact 4 6 " + this.mode, OID)), 107 new ASN1("keyId", ASN1.OCTET_STRING, this.keyId), 108 new ASN1("hash", ASN1.OCTET_STRING, this.hash), 109 new ASN1("iv", ASN1.OCTET_STRING, this.iv), 110 new ASN1("extensions", ASN1.SEQUENCE) 111 ); 112 113 if (this.mode == FileEncryptor.HYBRID) { 114 tbs.add(new ASN1("senderPublicKey", 0xA0, PKIXCommon.createECSubjectPublicKeyInfo(this.pukey))); 115 } 116 117 var mac = this.getCrypto().sign(this.mackey, Crypto.AES_CMAC, tbs.getBytes()); 118 119 var header = 120 new ASN1("EncryptionHeader", ASN1.SEQUENCE, 121 tbs, 122 new ASN1("mac", ASN1.OCTET_STRING, mac) 123 ); 124 125 return header; 126 } 127 128 129 130 /** 131 * Parse the header of the encrypted file. 132 * 133 * @param {ByteString} buffer the encrypted file with header. 134 * @type Number 135 * @return the offset at which the payload starts. 136 */ 137 FileEncryptor.prototype.parseHeader = function(buffer) { 138 if (buffer.byteAt(0) != 0x30) { 139 throw new GPError(module.id, GPError.INVALID_DATA, 0, "Header corrupted"); 140 } 141 142 var a = new ASN1(buffer); 143 144 if (a.elements != 2) { 145 throw new GPError(module.id, GPError.INVALID_DATA, 0, "Header corrupted"); 146 } 147 148 this.mac = a.get(1).value; 149 this.tbs = a.get(0); 150 if ((this.tbs.elements < 5) || (this.tbs.elements > 6)) { 151 throw new GPError(module.id, GPError.INVALID_DATA, 0, "Header corrupted"); 152 } 153 154 var oid = this.tbs.get(0).value; 155 if (!oid.startsWith(new ByteString("CardContact 4 6", OID))) { 156 throw new GPError(module.id, GPError.INVALID_DATA, 0, "File format not supported"); 157 } 158 159 this.mode = oid.byteAt(oid.length - 1); 160 this.keyId = this.tbs.get(1).value; 161 this.hash = this.tbs.get(2).value; 162 this.iv = this.tbs.get(3).value; 163 164 if (this.tbs.elements == 6) { 165 // Parse sender public key 166 var spki = this.tbs.get(5).get(0); 167 var oid = spki.get(0).get(1).value; 168 var pk = spki.get(1).value.bytes(2); 169 this.pukey = new Key(); 170 this.pukey.setType(Key.PUBLIC); 171 this.pukey.setComponent(Key.ECC_CURVE_OID, oid); 172 this.pukey.setComponent(Key.ECC_QX, pk.left(pk.length >> 1)); 173 this.pukey.setComponent(Key.ECC_QY, pk.right(pk.length >> 1)); 174 } 175 176 return a.size; 177 } 178 179 180 181 /** 182 * Validate the Message Authentication Code (MAC) over the header. 183 * 184 * @throws GPError is MAC verification failed. 185 */ 186 FileEncryptor.prototype.validateHeader = function() { 187 var mac = this.getCrypto().sign(this.mackey, Crypto.AES_CMAC, this.tbs.getBytes()); 188 if (!mac.equals(this.mac)) { 189 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Integrity check failed (Invalid MAC)."); 190 } 191 } 192 193 194 195 /** 196 * Generate ephemeral EC key pair at the sending side in hybrid scheme. 197 * 198 * @param {Key} key the domain parameter. 199 */ 200 FileEncryptor.prototype.generateEphemeralKeyPair = function(dp) { 201 this.prkey = new Key(dp); 202 this.prkey.setType(Key.PRIVATE); 203 204 this.pukey = new Key(dp); 205 this.pukey.setType(Key.PUBLIC); 206 207 this.crypto.generateKeyPair(Crypto.EC, this.pukey, this.prkey); 208 } 209 210 211 212 /** 213 * Derive a AES key from a AES master key using a CMAC based Key Derivation Function in 214 * Counter Mode as per NIST SP 800-108. 215 * 216 * @param {Key} key the AES master key. 217 * @param {Number} size the requested number of derived bytes. 218 * @param {ByteString} context the derivation context used to differ keys derived from the same master. 219 * @type ByteString 220 * @return the derived key value. 221 */ 222 FileEncryptor.prototype.keyDerivationFunction1 = function(key, size, context) { 223 var dd = new ByteBuffer(); 224 var os = size; 225 var iter = 1; 226 while (os > 0) { 227 var dp = new ByteBuffer(); 228 dp.append(ByteString.valueOf(iter, 4)); 229 dp.append(context); 230 dp.append(ByteString.valueOf(size << 3, 4)); 231 232 var mac = this.getCrypto().sign(key, Crypto.AES_CMAC, dp.toByteString()); 233 234 dd.append(mac.left(os > mac.length ? mac.length : os)); 235 os -= mac.length; 236 mac.clear(); 237 iter++; 238 } 239 var result = dd.toByteString(); 240 dd.clear(); 241 return result; 242 } 243 244 245 246 /** 247 * Derive an AES key from the shared secret resulting from performing the ECDH using 248 * a one-step-kdf with SHA-256 (Option 1) as defined in NIST SP 800-56C. 249 * 250 * @param {Number} id the key identifier (0x01 for K.ENC, 0x02 for K.MAC). 251 * @param {ByteString} sharedSecret the shared secret resulting from ECDH. 252 * @return the key value as 32 bytes. 253 */ 254 FileEncryptor.prototype.keyDerivationFunction2 = function(id, sharedSecret) { 255 var inp = ByteString.valueOf(1, 4).concat(sharedSecret).concat(ByteString.valueOf(id)); 256 var res = this.crypto.digest(Crypto.SHA_256, inp); 257 inp.clear(); 258 return res; 259 } 260 261 262 263 /** 264 * Derive K.ENC and K.MAC based on the selected scheme. 265 * 266 * @param {Key} key the AES key or private EC key. 267 * @param {CVC} key the CVC with the public of the receiver. 268 */ 269 FileEncryptor.prototype.deriveKey = function(key) { 270 if (typeof(this.iv) == "undefined") { 271 this.generateIV(); 272 } 273 274 switch(this.mode) { 275 case FileEncryptor.NATIVE: 276 this.mackey = key; 277 this.enckey = key; 278 if (typeof(key.getPKCS15Id) == "function") { 279 this.keyId = key.getPKCS15Id(); 280 } 281 break; 282 case FileEncryptor.DERIVED: 283 this.mackey = key; 284 this.enckey = new Key(); 285 var kv = this.keyDerivationFunction1(key, key.getSize() >> 3, this.iv); 286 this.enckey.setComponent(Key.AES, kv); 287 kv.clear(); 288 289 if (typeof(key.getPKCS15Id) == "function") { 290 this.keyId = key.getPKCS15Id(); 291 } 292 break; 293 case FileEncryptor.HYBRID: 294 var ss; 295 if (key instanceof CVC) { 296 var pk = key.getPublicKey(); 297 this.generateEphemeralKeyPair(pk); 298 var point = pk.getComponent(Key.ECC_QX).concat(pk.getComponent(Key.ECC_QY)); 299 ss = this.crypto.decrypt(this.prkey, Crypto.ECDHP, point, new ByteString("", HEX)); 300 this.keyId = key.determineKeyIdentifier(); 301 } else { 302 var point = this.pukey.getComponent(Key.ECC_QX).concat(this.pukey.getComponent(Key.ECC_QY)); 303 ss = this.getCrypto().decrypt(key, Crypto.ECDHP, point, new ByteString("", HEX)); 304 } 305 306 this.enckey = new Key(); 307 var kv = this.keyDerivationFunction2(1, ss); 308 this.enckey.setComponent(Key.AES, kv); 309 kv.clear(); 310 311 this.mackey = new Key(); 312 var kv = this.keyDerivationFunction2(2, ss); 313 this.mackey.setComponent(Key.AES, kv); 314 kv.clear(); 315 ss.clear(); 316 break; 317 default: 318 throw new GPError(module.id, GPError.INVALID_DATA, 0, "Mode not defined or invalid"); 319 } 320 } 321 322 323 324 /** 325 * Encrypt the buffer with K.ENC previously derived with deriveKey() and prepend header. 326 * 327 * @param {ByteString} plain the plain input. The caller should clear the buffer after use with plain.clear(). 328 * @type ByteString 329 * @return the header concatenated with the encrypted payload. 330 */ 331 FileEncryptor.prototype.encrypt = function(plain) { 332 333 var p = plain.pad(Crypto.ISO9797_METHOD_2_16); 334 335 var c = this.getCrypto().encrypt(this.enckey, Crypto.AES_CBC, p, this.iv); 336 p.clear(); 337 338 this.hash = this.calculateHash(c); 339 340 var header = this.createHeader(); 341 342 return header.getBytes().concat(c); 343 } 344 345 346 347 /** 348 * Validate and strip the ISO padding. 349 * 350 * @param {ByteString} buffer the decrypted buffer. 351 * @throw GPError is padding is invalid, indicating the wrong key or corrupted cipher. 352 * @type ByteString 353 * @return the buffer without padding. 354 */ 355 FileEncryptor.stripPadding = function(buffer) { 356 357 var ofs = buffer.length - 1; 358 while((ofs > 0) && (buffer.byteAt(ofs) == 0)) { 359 ofs--; 360 } 361 362 if ((ofs <= 0) || (buffer.byteAt(ofs) != 0x80)) { 363 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Plain block has invalid padding. Key probably wrong."); 364 } 365 366 return buffer.left(ofs); 367 } 368 369 370 371 /** 372 * Validate and decrypt the payload. 373 * 374 * @param {ByteString} buffer the encrypted payload. 375 * @type ByteString 376 * @return the plain content. 377 */ 378 FileEncryptor.prototype.decrypt = function(buffer) { 379 380 this.validateHeader(); 381 382 var hash = this.calculateHash(buffer); 383 if (!hash.equals(this.hash)) { 384 throw new GPError(module.id, GPError.CRYPTO_FAILED, 0, "Integrity check failed (Invalid hash)."); 385 } 386 387 var pp = this.getCrypto().decrypt(this.enckey, Crypto.AES_CBC, buffer, this.iv); 388 var p = FileEncryptor.stripPadding(pp); 389 pp.clear(); 390 391 return p; 392 } 393