|
||||||||
PREV NEXT | FRAMES NO FRAMES |
Implementation of the PACE/SAC protocol for both card and terminal
Class Summary | |
PACE | This class implements the PACE protocol |
PACEDomainParameterInfo | This class encodes and decodes PACEDomainParameterInfo objects. The class implements the following ASN.1 syntax: PACEDomainParameterInfo ::= SEQUENCE { protocol OBJECT IDENTIFIER(id-PACE-DH | id-PACE-ECDH), domainParameter AlgorithmIdentifier, parameterId INTEGER OPTIONAL } |
PACEInfo | This class encodes and decodes PACEInfo objects. The class implements the following ASN.1 syntax: PACEInfo ::= SEQUENCE { protocol OBJECT IDENTIFIER, version INTEGER, -- MUST be 1 or 2 parameterId INTEGER OPTIONAL } |
/** * --------- * |.##> <##.| Open Smart Card Development Platform (www.openscdp.org) * |# #| * |# #| Copyright (c) 1999-2009 CardContact Software & System Consulting * |'##> <##'| Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de) * --------- * * This file is part of OpenSCDP. * * OpenSCDP is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * OpenSCDP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenSCDP; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * @fileoverview Implementation of the PACE/SAC protocol for both card and terminal */ /** * Create a PACEInfo object * * @class <p>This class encodes and decodes PACEInfo objects.</p> * <p>The class implements the following ASN.1 syntax:</p> * <pre> * PACEInfo ::= SEQUENCE { * protocol OBJECT IDENTIFIER, * version INTEGER, -- MUST be 1 or 2 * parameterId INTEGER OPTIONAL * } * </pre> * @constructor * @param {ASN1} the optional tlv structure to initialize the object */ function PACEInfo(tlv) { if (tlv && (tlv instanceof ASN1)) { assert(tlv.isconstructed); assert(tlv.elements >= 2); var i = 0; var t = tlv.get(i++); assert(t.tag == ASN1.OBJECT_IDENTIFIER); this.protocol = t.value; var t = tlv.get(i++); assert(t.tag == ASN1.INTEGER); this.version = t.value.toSigned(); if (i < tlv.elements) { var t = tlv.get(i++); assert(t.tag == ASN1.INTEGER); this.parameterId = t.value.toSigned(); } } } /** * Convert object to TLV structure * * @return the TLV structure * @type ASN1 */ PACEInfo.prototype.toTLV = function() { var t = new ASN1(ASN1.SEQUENCE); t.add(new ASN1(ASN1.OBJECT_IDENTIFIER, this.protocol)); var bb = new ByteBuffer(); bb.append(this.version); t.add(new ASN1(ASN1.INTEGER, bb.toByteString())); if (typeof(this.parameterId) != "undefined") { var bb = new ByteBuffer(); bb.append(this.parameterId); t.add(new ASN1(ASN1.INTEGER, bb.toByteString())); } return t; } PACEInfo.prototype.toString = function() { return "PACEInfo(protocol=" + this.protocol + ", version=" + this.version + ", parameterId=" + this.protocolId + ")"; } /** * Create a PACEDomainParameterInfo object * * @class <p>This class encodes and decodes PACEDomainParameterInfo objects.</p> * <p>The class implements the following ASN.1 syntax:</p> * <pre> * PACEDomainParameterInfo ::= SEQUENCE { * protocol OBJECT IDENTIFIER(id-PACE-DH | id-PACE-ECDH), * domainParameter AlgorithmIdentifier, * parameterId INTEGER OPTIONAL * } * </pre> * @constructor * @param {ASN1} the optional tlv structure to initialize the object */ function PACEDomainParameterInfo(tlv) { if (tlv && (tlv instanceof ASN1)) { assert(tlv.isconstructed); assert(tlv.elements >= 2); var i = 0; var t = tlv.get(i++); assert(t.tag == ASN1.OBJECT_IDENTIFIER); this.protocol = t.value; var t = tlv.get(i++); assert(t.tag == ASN1.SEQUENCE); if (t.elements > 0) { this.domainParameter = ECCUtils.decodeECParameters(t.get(1)); } else { this.domainParameter = new Key(); this.domainParameter.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID)); } if (i < tlv.elements) { var t = tlv.get(i++); assert(t.tag == ASN1.INTEGER); this.parameterId = t.value.toSigned(); } } } /** * Convert object to TLV structure * * @return the TLV structure * @type ASN1 */ PACEDomainParameterInfo.prototype.toTLV = function() { var t = new ASN1(ASN1.SEQUENCE); t.add(new ASN1(ASN1.OBJECT_IDENTIFIER, this.protocol)); var c = new ASN1(ASN1.SEQUENCE); if (this.standardizedDomainParameter) { c.add(new ASN1(ASN1.OBJECT_IDENTIFIER, new ByteString("standardizedDomainParameter", OID))); c.add(new ASN1(ASN1.INTEGER, ByteString.valueOf(this.standardizedDomainParameter))); } else { } t.add(c); if (typeof(this.parameterId) != "undefined") { var bb = new ByteBuffer(); bb.append(this.parameterId); t.add(new ASN1(ASN1.INTEGER, bb.toByteString())); } return t; } PACEDomainParameterInfo.getStandardizedDomainParameter = function(id) { var key = new Key(); key.setComponent(Key.ECC_CURVE_OID, new ByteString("brainpoolP256r1", OID)); return key; } PACEDomainParameterInfo.prototype.toString = function() { return "PACEDomainParameterInfo(protocol=" + this.protocol + ", parameterId=" + this.protocolId + ")"; } /** * Create a PACE protocol object * * @class This class implements the PACE protocol * * @constructor * * @param {Crypto} crypto the crypto provider * @param {ByteString} algo the algorithm OID * @param {Key} domainparam the key object holding ECC domain parameter * @param {Number} version protocol version (1 or 2) */ function PACE(crypto, algo, domparam, version) { this.crypto = crypto; this.algo = algo.toString(OID); this.domparam = domparam; if (typeof(version) != "undefined") { this.version = version; } else { this.version = 1; } if (this.algo == PACE.id_PACE_ECDH_GM_3DES_CBC_CBC) { this.symalgo = Key.DES; } else { this.symalgo = Key.AES; } this.sym = Crypto.AES; } /** * Return algorithm type * * @type Number * @returns Either Key.DES or Key.AES */ PACE.prototype.getSymmetricAlgorithm = function() { return this.symalgo; } /** * Derive key from input parameter, counter and optional nonce * * @param {ByteString} input the first part of the hash input * @param {Number} counter the counter value * @param {nonce} the optional nonce inserted between the input and the counter * @return the key object * @type Key */ PACE.prototype.deriveKey = function(input, counter, nonce) { if (typeof(nonce) != "undefined") { input = input.concat(nonce); } var bb = new ByteBuffer("000000", HEX); bb.append(counter); input = input.concat(bb.toByteString()); var key = new Key(); if (this.algo == PACE.id_PACE_ECDH_GM_3DES_CBC_CBC) { var digest = this.crypto.digest(Crypto.SHA_1, input); key.setComponent(Key.DES, digest.left(16)); } else if (this.algo == PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_128) { var digest = this.crypto.digest(Crypto.SHA_1, input); key.setComponent(Key.AES, digest.left(16)); } else if (this.algo == PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_192) { var digest = this.crypto.digest(Crypto.SHA_256, input); key.setComponent(Key.AES, digest.left(24)); } else if (this.algo == PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_256) { var digest = this.crypto.digest(Crypto.SHA_256, input); key.setComponent(Key.AES, digest); } else { throw new GPError("pace", GPError.INVALID_MECH, 0x6A80, "Algorithm not supported"); } return key; } /** * Set the password and derive the PACE key. * @param {ByteString} pwd the PACE password (Hash Value for MRZ and ASCII string for others) * @return the PACE key. */ PACE.prototype.setPassword = function(pwd) { this.pacekey = this.deriveKey(pwd, 3); } /** * Generate nonce and encrypt using PACE key. * @return the encrypted nonce * @type ByteString */ PACE.prototype.getEncryptedNonce = function() { this.nonce = this.crypto.generateRandom(16); if (this.symalgo == Key.DES) { var encnonce = this.crypto.encrypt(this.pacekey, Crypto.DES_CBC, this.nonce); } else { var encnonce = this.crypto.encrypt(this.pacekey, Crypto.AES_ECB, this.nonce); } return encnonce; } /** * Decrypt and store nonce using PACE key. * * @param {ByteString} nonce the encrypted nonce */ PACE.prototype.decryptNonce = function(encnonce) { if (this.symalgo == Key.DES) { this.nonce = this.crypto.decrypt(this.pacekey, Crypto.DES_CBC, encnonce); } else { this.nonce = this.crypto.decrypt(this.pacekey, Crypto.AES_ECB, encnonce); } } /** * Returns true, if the nonce is known. * @return true if the nonce is known * @type Boolean */ PACE.prototype.hasNonce = function() { return (typeof(this.nonce) != "undefined"); } /** * Generate ephemeral ECC key pair. * * @param domainParameter the domain parameter for the key pair * @return the ephemeral public key * @type Key */ PACE.prototype.generateEphemeralKeyPair = function(domainParameter) { this.prk = new Key(domainParameter); this.prk.setType(Key.PRIVATE); this.puk = new Key(domainParameter); this.puk.setType(Key.PUBLIC); this.crypto.generateKeyPair(Crypto.EC, this.puk, this.prk); return this.puk; } /** * Generates and returns the mapping data for this instance * @return the mapping data * @type ByteString */ PACE.prototype.getMappingData = function() { if (typeof(this.prk) == "undefined") { this.generateEphemeralKeyPair(this.domparam); } var ecpk = new ByteString("04", HEX); ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QX)); ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QY)); return ecpk; } /** * Performs the mapping operation with mapping data from the other side * */ PACE.prototype.performMapping = function(mappingData) { if (mappingData.byteAt(0) != 0x04) throw new GPError("PACE", GPError.INVALID_DATA, 0x6A80, "Public key must start with '04'"); if (typeof(this.nonce) == "undefined") throw new GPError("PACE", GPError.INVALID_MECH, 0x6985, "Nonce is not yet defined"); var l = (mappingData.length - 1) >> 1; if (l != this.prk.getComponent(Key.ECC_P).length) { throw new GPError("PACE", GPError.INVALID_DATA, 0, "Public key size does not match private key size"); } var h = this.crypto.decrypt(this.prk, Crypto.ECDHP, mappingData.bytes(1)); var l = h.length >> 1; var H = new Key(this.domparam); H.setComponent(Key.ECC_QX, h.bytes(0, l)); H.setComponent(Key.ECC_QY, h.bytes(l, l)); var G = new Key(this.domparam); // Copy generator point into public key point G.setComponent(Key.ECC_QX, G.getComponent(Key.ECC_GX)); G.setComponent(Key.ECC_QY, G.getComponent(Key.ECC_GY)); // Calculate G' = s * G + P, where P is initially stored in H and // G' is finally stored in H. this.crypto.deriveKey(G, Crypto.EC_MULTIPLY_ADD, this.nonce, H); // Create new domain parameter with G' this.ephDomParam = new Key(this.domparam); this.ephDomParam.setComponent(Key.ECC_GX, H.getComponent(Key.ECC_QX)); this.ephDomParam.setComponent(Key.ECC_GY, H.getComponent(Key.ECC_QY)); } /** * Returns the ephemeral public key based on the new domain parameter * * @return the encoded public key * @type ByteString */ PACE.prototype.getEphemeralPublicKey = function() { this.generateEphemeralKeyPair(this.ephDomParam); var ecpk = new ByteString("04", HEX); ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QX)); ecpk = ecpk.concat(this.puk.getComponent(Key.ECC_QY)); return ecpk; } /** * Performs the mapping operation with mapping data from the other side * * @param {ByteString} publicKey the public key in encoded format */ PACE.prototype.performKeyAgreement = function(publicKey) { if (publicKey.byteAt(0) != 0x04) throw new GPError("PACE", GPError.INVALID_DATA, 0x6A80, "Public key must start with '04'"); if (typeof(this.nonce) == "undefined") throw new GPError("PACE", GPError.INVALID_MECH, 0x6985, "Nonce is not yet defined"); var l = (publicKey.length - 1) >> 1; if (l != this.prk.getComponent(Key.ECC_P).length) { throw new GPError("PACE", GPError.INVALID_DATA, 0, "Public key size does not match private key size"); } this.otherPuK = new Key(this.ephDomParam); this.otherPuK.setComponent(Key.ECC_QX, publicKey.bytes( 1, l)); this.otherPuK.setComponent(Key.ECC_QY, publicKey.bytes(l + 1, l)); var k = this.crypto.decrypt(this.prk, Crypto.ECDH, publicKey.bytes(1)); GPSystem.trace("Shared Secret K:"); GPSystem.trace(k); this.kenc = this.deriveKey(k, 1); this.kmac = this.deriveKey(k, 2); } /** * Strips leading zeros of a ByteString * * @param {ByteString} value the ByteString value * @return the stripped ByteString object, may be an empty ByteString * @type ByteString */ PACE.stripLeadingZeros = function(value) { var i = 0; for (; (i < value.length) && (value.byteAt(i) == 0); i++); return value.right(value.length - i); } /** * Encode an ECC public key in the format defined by the EAC 2.0 specification * * @param {String} oid the object identifier to encode * @param {Key} key the EC public key * @param {Boolean} withDP true to encode domain parameter as well * @type ASN1 * @returns the ASN1 encoded public key object */ PACE.encodePublicKey = function(oid, key, withDP) { var t = new ASN1("ecPublicKey", 0x7F49); t.add(new ASN1("objectIdentifier", ASN1.OBJECT_IDENTIFIER, new ByteString(oid, OID))); if (withDP) { t.add(new ASN1("primeModulus", 0x81, key.getComponent(Key.ECC_P))); t.add(new ASN1("firstCoefficient", 0x82, key.getComponent(Key.ECC_A))); t.add(new ASN1("secondCoefficient", 0x83, key.getComponent(Key.ECC_B))); var point = new ByteString("04", HEX); point = point.concat(key.getComponent(Key.ECC_GX)); point = point.concat(key.getComponent(Key.ECC_GY)); t.add(new ASN1("basePoint", 0x84, point)); t.add(new ASN1("orderOfTheBasePoint", 0x85, key.getComponent(Key.ECC_N))); } var point = new ByteString("04", HEX); point = point.concat(key.getComponent(Key.ECC_QX)); point = point.concat(key.getComponent(Key.ECC_QY)); t.add(new ASN1("publicPoint", 0x86, point)); if (withDP) { var cofactor = key.getComponent(Key.ECC_H); cofactor = PACE.stripLeadingZeros(cofactor); t.add(new ASN1("cofactor", 0x87, cofactor)); } return t; } /** * Calculate the authentication token over the public key received from * the other side * * @return the MAC over the authentication data * @type ByteString */ PACE.prototype.calculateAuthenticationToken = function() { var t = PACE.encodePublicKey(this.algo, this.otherPuK, (this.version == 1)); GPSystem.trace("Authentication Token:"); GPSystem.trace(t); if (this.symalgo == Key.DES) { var inp = t.getBytes().pad(Crypto.ISO9797_METHOD_2); var at = this.crypto.sign(this.kmac, Crypto.DES_MAC_EMV, inp); } else { var at = this.crypto.sign(this.kmac, Crypto.AES_CMAC, t.getBytes()).left(8); } return at; } /** * Calculate and verify the authentication token over the public key received from * the other side * * @param {ByteString} the MAC over the authentication data * @return true if the MAC is valid * @type Boolean */ PACE.prototype.verifyAuthenticationToken = function(authToken) { var t = PACE.encodePublicKey(this.algo, this.puk, (this.version == 1)); GPSystem.trace("Authentication Token:"); GPSystem.trace(t); if (this.symalgo == Key.DES) { var inp = t.getBytes().pad(Crypto.ISO9797_METHOD_2); var at = this.crypto.sign(this.kmac, Crypto.DES_MAC_EMV, inp); } else { var at = this.crypto.sign(this.kmac, Crypto.AES_CMAC, t.getBytes()).left(8); } return at.equals(authToken); } /** * Returns true, if the mapping has been performed. * @return true if the mapping has been performed * @type Boolean */ PACE.prototype.hasMapping = function() { return (typeof(this.ephDomParam) != "undefined"); } /** * Describe key * @param {Key} the key * @return the string describing the key * @type String */ PACE.keyToString = function(key) { var str = ""; var kval = key.getComponent(Key.AES); if (kval) { str += "(AES) " + kval + "\n"; } var kval = key.getComponent(Key.DES); if (kval) { str += "(DES) " + kval + "\n"; } return str; } /** * Returns a human readable presentation of the current pace state. * return {String} the object information */ PACE.prototype.toString = function() { var str = "Algorithm " + this.algo + "\n"; if (typeof(this.pacekey) != "undefined") { str += "PACE Key " + PACE.keyToString(this.pacekey); } if (typeof(this.nonce) != "undefined") { str += "Nonce " + this.nonce + "\n"; } if (typeof(this.ephDomParam) != "undefined") { str += "Point G' " + this.ephDomParam.getComponent(Key.ECC_GX) + " " + this.ephDomParam.getComponent(Key.ECC_GY) + "\n"; } if (typeof(this.kenc) != "undefined") { str += "Kenc " + PACE.keyToString(this.kenc); } if (typeof(this.kmac) != "undefined") { str += "Kmac" + PACE.keyToString(this.kmac); } return str; } PACE.bsi_de = "0.4.0.127.0.7"; PACE.id_PACE = PACE.bsi_de + ".2.2.4"; PACE.id_PACE_ECDH_GM = PACE.id_PACE + ".2"; PACE.id_PACE_ECDH_GM_3DES_CBC_CBC = PACE.id_PACE_ECDH_GM + ".1"; PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_128 = PACE.id_PACE_ECDH_GM + ".2"; PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_192 = PACE.id_PACE_ECDH_GM + ".3"; PACE.id_PACE_ECDH_GM_AES_CBC_CMAC_256 = PACE.id_PACE_ECDH_GM + ".4"; PACE.id_roles = PACE.bsi_de + ".3.1.2"; PACE.id_IS = PACE.id_roles + ".1";
|
||||||||
PREV NEXT | FRAMES NO FRAMES |