0001: /*
0002: * $Id: ValueLinkApi.java,v 1.6 2004/03/12 23:22:51 ajzeneski Exp $
0003: *
0004: * Copyright (c) 2003 The Open For Business Project - www.ofbiz.org
0005: *
0006: * Permission is hereby granted, free of charge, to any person obtaining a
0007: * copy of this software and associated documentation files (the "Software"),
0008: * to deal in the Software without restriction, including without limitation
0009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
0010: * and/or sell copies of the Software, and to permit persons to whom the
0011: * Software is furnished to do so, subject to the following conditions:
0012: *
0013: * The above copyright notice and this permission notice shall be included
0014: * in all copies or substantial portions of the Software.
0015: *
0016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
0017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
0018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
0019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
0020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
0021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
0022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0023: *
0024: */
0025: package org.ofbiz.accounting.thirdparty.valuelink;
0026:
0027: import org.ofbiz.base.util.*;
0028: import org.ofbiz.entity.GenericDelegator;
0029: import org.ofbiz.entity.GenericValue;
0030: import org.ofbiz.entity.GenericEntityException;
0031:
0032: import javax.crypto.*;
0033: import javax.crypto.Cipher;
0034: import javax.crypto.IllegalBlockSizeException;
0035: import javax.crypto.KeyGenerator;
0036: import javax.crypto.SecretKey;
0037: import javax.crypto.interfaces.DHPublicKey;
0038: import javax.crypto.interfaces.DHPrivateKey;
0039: import javax.crypto.spec.IvParameterSpec;
0040: import javax.crypto.spec.DESedeKeySpec;
0041: import javax.crypto.spec.DHParameterSpec;
0042: import javax.crypto.spec.DHPublicKeySpec;
0043: import javax.crypto.spec.DHPrivateKeySpec;
0044: import javax.crypto.spec.DESKeySpec;
0045: import java.security.*;
0046: import java.security.spec.InvalidKeySpecException;
0047: import java.security.spec.X509EncodedKeySpec;
0048: import java.util.*;
0049: import java.math.BigInteger;
0050: import java.text.SimpleDateFormat;
0051: import java.text.DecimalFormat;
0052: import java.text.ParseException;
0053:
0054: /**
0055: * ValueLinkApi - Implementation of ValueLink Encryption & Transport
0056: *
0057: * @author <a href="mailto:jaz@ofbiz.org">Andy Zeneski</a>
0058: * @version $Revision: 1.6 $
0059: * @since 3.0
0060: */
0061: public class ValueLinkApi {
0062:
0063: public static final String module = ValueLinkApi.class.getName();
0064:
0065: // static object cache
0066: private static Map objectCache = new HashMap();
0067:
0068: // instance variables
0069: protected GenericDelegator delegator = null;
0070: protected Properties props = null;
0071: protected SecretKey kek = null;
0072: protected SecretKey mwk = null;
0073: protected String merchantId = null;
0074: protected String terminalId = null;
0075: protected Long mwkIndex = null;
0076: protected boolean debug = false;
0077:
0078: protected ValueLinkApi() {
0079: }
0080:
0081: protected ValueLinkApi(GenericDelegator delegator, Properties props) {
0082: String mId = (String) props.get("payment.valuelink.merchantId");
0083: String tId = (String) props.get("payment.valuelink.terminalId");
0084: this .delegator = delegator;
0085: this .merchantId = mId;
0086: this .terminalId = tId;
0087: this .props = props;
0088: if ("Y".equalsIgnoreCase((String) props
0089: .get("payment.valuelink.debug"))) {
0090: this .debug = true;
0091: }
0092:
0093: if (debug) {
0094: Debug.log("New ValueLinkApi instance created", module);
0095: Debug.log("Merchant ID : " + merchantId, module);
0096: Debug.log("Terminal ID : " + terminalId, module);
0097: }
0098: }
0099:
0100: /**
0101: * Obtain an instance of the ValueLinkApi
0102: * @param delegator GenericDelegator used to query the encryption keys
0103: * @param props Properties to use for the Api (usually payment.properties)
0104: * @param reload When true, will replace an existing instance in the cache and reload all properties
0105: * @return ValueLinkApi reference
0106: */
0107: public static ValueLinkApi getInstance(GenericDelegator delegator,
0108: Properties props, boolean reload) {
0109: String merchantId = (String) props
0110: .get("payment.valuelink.merchantId");
0111: if (props == null) {
0112: throw new IllegalArgumentException(
0113: "Properties cannot be null");
0114: }
0115:
0116: ValueLinkApi api = (ValueLinkApi) objectCache.get(merchantId);
0117: if (api == null || reload) {
0118: synchronized (ValueLinkApi.class) {
0119: api = (ValueLinkApi) objectCache.get(merchantId);
0120: if (api == null || reload) {
0121: api = new ValueLinkApi(delegator, props);
0122: objectCache.put(merchantId, api);
0123: }
0124: }
0125: }
0126:
0127: if (api == null) {
0128: throw new RuntimeException(
0129: "Runtime problems with ValueLinkApi; unable to create instance");
0130: }
0131:
0132: return api;
0133: }
0134:
0135: /**
0136: * Obtain an instance of the ValueLinkApi; this method will always return an existing reference if one is available
0137: * @param delegator GenericDelegator used to query the encryption keys
0138: * @param props Properties to use for the Api (usually payment.properties)
0139: * @return
0140: */
0141: public static ValueLinkApi getInstance(GenericDelegator delegator,
0142: Properties props) {
0143: return getInstance(delegator, props, false);
0144: }
0145:
0146: /**
0147: * Encrypt the defined pin using the configured keys
0148: * @param pin Plain text String of the pin
0149: * @return Hex String of the encrypted pin (EAN) for transmission to ValueLink
0150: */
0151: public String encryptPin(String pin) {
0152: // get the Cipher
0153: Cipher mwkCipher = this .getCipher(this .getMwkKey(),
0154: Cipher.ENCRYPT_MODE);
0155:
0156: // pin to bytes
0157: byte[] pinBytes = pin.getBytes();
0158:
0159: // 7 bytes of random data
0160: byte[] random = this .getRandomBytes(7);
0161:
0162: // pin checksum
0163: byte[] checkSum = this .getPinCheckSum(pinBytes);
0164:
0165: // put all together
0166: byte[] eanBlock = new byte[16];
0167: int i;
0168: for (i = 0; i < random.length; i++) {
0169: eanBlock[i] = random[i];
0170: }
0171: eanBlock[7] = checkSum[0];
0172: for (i = 0; i < pinBytes.length; i++) {
0173: eanBlock[i + 8] = pinBytes[i];
0174: }
0175:
0176: // encrypy the ean
0177: String encryptedEanHex = null;
0178: try {
0179: byte[] encryptedEan = mwkCipher.doFinal(eanBlock);
0180: encryptedEanHex = StringUtil.toHexString(encryptedEan);
0181: } catch (IllegalStateException e) {
0182: Debug.logError(e, module);
0183: } catch (IllegalBlockSizeException e) {
0184: Debug.logError(e, module);
0185: } catch (BadPaddingException e) {
0186: Debug.logError(e, module);
0187: }
0188:
0189: if (debug) {
0190: Debug.log("encryptPin : " + pin + " / " + encryptedEanHex,
0191: module);
0192: }
0193:
0194: return encryptedEanHex;
0195: }
0196:
0197: /**
0198: * Decrypt an encrypted pin using the configured keys
0199: * @param pin Hex String of the encrypted pin (EAN)
0200: * @return Plain text String of the pin
0201: */
0202: public String decryptPin(String pin) {
0203: // get the Cipher
0204: Cipher mwkCipher = this .getCipher(this .getMwkKey(),
0205: Cipher.DECRYPT_MODE);
0206:
0207: // decrypt pin
0208: String decryptedPinString = null;
0209: try {
0210: byte[] decryptedEan = mwkCipher.doFinal(StringUtil
0211: .fromHexString(pin));
0212: byte[] decryptedPin = getByteRange(decryptedEan, 8, 8);
0213: decryptedPinString = new String(decryptedPin);
0214: } catch (IllegalStateException e) {
0215: Debug.logError(e, module);
0216: } catch (IllegalBlockSizeException e) {
0217: Debug.logError(e, module);
0218: } catch (BadPaddingException e) {
0219: Debug.logError(e, module);
0220: }
0221:
0222: if (debug) {
0223: Debug.log("decryptPin : " + pin + " / "
0224: + decryptedPinString, module);
0225: }
0226:
0227: return decryptedPinString;
0228: }
0229:
0230: /**
0231: * Transmit a request to ValueLink
0232: * @param request Map of request parameters
0233: * @return Map of response parameters
0234: * @throws HttpClientException
0235: */
0236: public Map send(Map request) throws HttpClientException {
0237: return send((String) props.get("payment.valuelink.url"),
0238: request);
0239: }
0240:
0241: /**
0242: * Transmit a request to ValueLink
0243: * @param url override URL from what is defined in the properties
0244: * @param request request Map of request parameters
0245: * @return Map of response parameters
0246: * @throws HttpClientException
0247: */
0248: public Map send(String url, Map request) throws HttpClientException {
0249: if (debug) {
0250: Debug.log("Request : " + url + " / " + request, module);
0251: }
0252:
0253: // read the timeout value
0254: String timeoutString = (String) props
0255: .get("payment.valuelink.timeout");
0256: int timeout = 34;
0257: try {
0258: timeout = Integer.parseInt(timeoutString);
0259: } catch (NumberFormatException e) {
0260: Debug.logError(e, "Unable to set timeout to "
0261: + timeoutString + " using default " + timeout);
0262: }
0263:
0264: // create the HTTP client
0265: HttpClient client = new HttpClient(url, request);
0266: client.setTimeout(timeout * 1000);
0267: client.setDebug(debug);
0268:
0269: client.setClientCertificateAlias((String) props
0270: .get("payment.valuelink.certificateAlias"));
0271: String response = client.post();
0272:
0273: // parse the response and return a map
0274: return this .parseResponse(response);
0275: }
0276:
0277: /**
0278: * Output the creation of public/private keys + KEK to the console for manual database update
0279: */
0280: public StringBuffer outputKeyCreation(boolean kekOnly,
0281: String kekTest) {
0282: return this .outputKeyCreation(0, kekOnly, kekTest);
0283: }
0284:
0285: private StringBuffer outputKeyCreation(int loop, boolean kekOnly,
0286: String kekTest) {
0287: StringBuffer buf = new StringBuffer();
0288: loop++;
0289:
0290: if (loop > 100) {
0291: // only loop 100 times; then throw an exception
0292: throw new IllegalStateException(
0293: "Unable to create 128 byte keys in 100 tries");
0294: }
0295:
0296: // place holder for the keys
0297: DHPrivateKey privateKey = null;
0298: DHPublicKey publicKey = null;
0299:
0300: if (!kekOnly) {
0301: KeyPair keyPair = null;
0302: try {
0303: keyPair = this .createKeys();
0304: } catch (NoSuchAlgorithmException e) {
0305: Debug.logError(e, module);
0306: } catch (InvalidAlgorithmParameterException e) {
0307: Debug.logError(e, module);
0308: } catch (InvalidKeySpecException e) {
0309: Debug.logError(e, module);
0310: }
0311:
0312: if (keyPair != null) {
0313: publicKey = (DHPublicKey) keyPair.getPublic();
0314: privateKey = (DHPrivateKey) keyPair.getPrivate();
0315:
0316: if (publicKey == null
0317: || publicKey.getY().toByteArray().length != 128) {
0318: // run again until we get a 128 byte public key for VL
0319: return this .outputKeyCreation(loop, kekOnly,
0320: kekTest);
0321: }
0322: } else {
0323: Debug.log("Returned a null KeyPair", module);
0324: return this .outputKeyCreation(loop, kekOnly, kekTest);
0325: }
0326: } else {
0327: // use our existing private key to generate a KEK
0328: try {
0329: privateKey = (DHPrivateKey) this .getPrivateKey();
0330: } catch (Exception e) {
0331: Debug.logError(e, module);
0332: }
0333: }
0334:
0335: // the KEK
0336: byte[] kekBytes = null;
0337: try {
0338: kekBytes = this .generateKek(privateKey);
0339: } catch (NoSuchAlgorithmException e) {
0340: Debug.logError(e, module);
0341: } catch (InvalidKeySpecException e) {
0342: Debug.logError(e, module);
0343: } catch (InvalidKeyException e) {
0344: Debug.logError(e, module);
0345: }
0346:
0347: // the 3DES KEK value
0348: SecretKey loadedKek = this .getDesEdeKey(kekBytes);
0349: byte[] loadKekBytes = loadedKek.getEncoded();
0350:
0351: // test the KEK
0352: Cipher cipher = this .getCipher(this .getKekKey(),
0353: Cipher.ENCRYPT_MODE);
0354: byte[] kekTestB = { 0, 0, 0, 0, 0, 0, 0, 0 };
0355: byte[] kekTestC = new byte[0];
0356: if (kekTest != null) {
0357: kekTestB = StringUtil.fromHexString(kekTest);
0358: }
0359:
0360: // encrypt the test bytes
0361: try {
0362: kekTestC = cipher.doFinal(kekTestB);
0363: } catch (Exception e) {
0364: Debug.logError(e, module);
0365: }
0366:
0367: if (!kekOnly) {
0368: // public key (just Y)
0369: BigInteger y = publicKey.getY();
0370: byte[] yBytes = y.toByteArray();
0371: String yHex = StringUtil.toHexString(yBytes);
0372: buf.append("======== Begin Public Key (Y @ "
0373: + yBytes.length + " / " + yHex.length()
0374: + ") ========\n");
0375: buf.append(yHex + "\n");
0376: buf.append("======== End Public Key ========\n\n");
0377:
0378: // private key (just X)
0379: BigInteger x = privateKey.getX();
0380: byte[] xBytes = x.toByteArray();
0381: String xHex = StringUtil.toHexString(xBytes);
0382: buf.append("======== Begin Private Key (X @ "
0383: + xBytes.length + " / " + xHex.length()
0384: + ") ========\n");
0385: buf.append(xHex + "\n");
0386: buf.append("======== End Private Key ========\n\n");
0387:
0388: // private key (full)
0389: byte[] privateBytes = privateKey.getEncoded();
0390: String privateHex = StringUtil.toHexString(privateBytes);
0391: buf.append("======== Begin Private Key (Full @ "
0392: + privateBytes.length + " / " + privateHex.length()
0393: + ") ========\n");
0394: buf.append(privateHex + "\n");
0395: buf.append("======== End Private Key ========\n\n");
0396: }
0397:
0398: if (kekBytes != null) {
0399: buf.append("======== Begin KEK (" + kekBytes.length
0400: + ") ========\n");
0401: buf.append(StringUtil.toHexString(kekBytes) + "\n");
0402: buf.append("======== End KEK ========\n\n");
0403:
0404: buf.append("======== Begin KEK (DES) ("
0405: + loadKekBytes.length + ") ========\n");
0406: buf.append(StringUtil.toHexString(loadKekBytes) + "\n");
0407: buf.append("======== End KEK (DES) ========\n\n");
0408:
0409: buf.append("======== Begin KEK Test (" + kekTestC.length
0410: + ") ========\n");
0411: buf.append(StringUtil.toHexString(kekTestC) + "\n");
0412: buf.append("======== End KEK Test ========\n\n");
0413: } else {
0414: Debug.logError("KEK came back empty", module);
0415: }
0416:
0417: return buf;
0418: }
0419:
0420: /**
0421: * Create a set of public/private keys using ValueLinks defined parameters
0422: * @return KeyPair object containing both public and private keys
0423: * @throws NoSuchAlgorithmException
0424: * @throws InvalidAlgorithmParameterException
0425: */
0426: public KeyPair createKeys() throws NoSuchAlgorithmException,
0427: InvalidAlgorithmParameterException, InvalidKeySpecException {
0428: // initialize the parameter spec
0429: DHPublicKey publicKey = (DHPublicKey) this
0430: .getValueLinkPublicKey();
0431: DHParameterSpec dhParamSpec = publicKey.getParams();
0432: //Debug.log(dhParamSpec.getP().toString() + " / " + dhParamSpec.getG().toString(), module);
0433:
0434: // create the public/private key pair using parameters defined by valuelink
0435: KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH");
0436: keyGen.initialize(dhParamSpec);
0437: KeyPair keyPair = keyGen.generateKeyPair();
0438:
0439: return keyPair;
0440: }
0441:
0442: /**
0443: * Generate a key exchange key for use in encrypting the mwk
0444: * @param privateKey The private key for the merchant
0445: * @return byte array containing the kek
0446: * @throws NoSuchAlgorithmException
0447: * @throws InvalidKeySpecException
0448: * @throws InvalidKeyException
0449: */
0450: public byte[] generateKek(PrivateKey privateKey)
0451: throws NoSuchAlgorithmException, InvalidKeySpecException,
0452: InvalidKeyException {
0453: // get the ValueLink public key
0454: PublicKey vlPublic = this .getValueLinkPublicKey();
0455:
0456: // generate shared secret key
0457: KeyAgreement ka = KeyAgreement.getInstance("DH");
0458: ka.init(privateKey);
0459: ka.doPhase(vlPublic, true);
0460: byte[] secretKey = ka.generateSecret();
0461:
0462: if (debug) {
0463: Debug.log("Secret Key : "
0464: + StringUtil.toHexString(secretKey) + " / "
0465: + secretKey.length, module);
0466: }
0467:
0468: // generate 3DES from secret key using VL algorithm (KEK)
0469: MessageDigest md = MessageDigest.getInstance("SHA1");
0470: byte[] digest = md.digest(secretKey);
0471: byte[] des2 = getByteRange(digest, 0, 16);
0472: byte[] first8 = getByteRange(des2, 0, 8);
0473: byte[] kek = copyBytes(des2, first8, 0);
0474:
0475: if (debug) {
0476: Debug.log("Generated KEK : " + StringUtil.toHexString(kek)
0477: + " / " + kek.length, module);
0478: }
0479:
0480: return kek;
0481: }
0482:
0483: /**
0484: * Get a public key object for the ValueLink supplied public key
0485: * @return PublicKey object of ValueLinks's public key
0486: * @throws NoSuchAlgorithmException
0487: * @throws InvalidKeySpecException
0488: */
0489: public PublicKey getValueLinkPublicKey()
0490: throws NoSuchAlgorithmException, InvalidKeySpecException {
0491: // read the valuelink public key
0492: String publicValue = (String) props
0493: .get("payment.valuelink.publicValue");
0494: byte[] publicKeyBytes = StringUtil.fromHexString(publicValue);
0495:
0496: // initialize the parameter spec
0497: DHParameterSpec dhParamSpec = this .getDHParameterSpec();
0498:
0499: // load the valuelink public key
0500: KeyFactory keyFactory = KeyFactory.getInstance("DH");
0501: BigInteger publicKeyInt = new BigInteger(publicKeyBytes);
0502: DHPublicKeySpec dhPublicSpec = new DHPublicKeySpec(
0503: publicKeyInt, dhParamSpec.getP(), dhParamSpec.getG());
0504: PublicKey vlPublic = keyFactory.generatePublic(dhPublicSpec);
0505:
0506: return vlPublic;
0507: }
0508:
0509: /**
0510: * Get merchant Private Key
0511: * @return PrivateKey object for the merchant
0512: */
0513: public PrivateKey getPrivateKey() throws InvalidKeySpecException,
0514: NoSuchAlgorithmException {
0515: byte[] privateKeyBytes = this .getPrivateKeyBytes();
0516:
0517: // initialize the parameter spec
0518: DHParameterSpec dhParamSpec = this .getDHParameterSpec();
0519:
0520: // load the private key
0521: KeyFactory keyFactory = KeyFactory.getInstance("DH");
0522: BigInteger privateKeyInt = new BigInteger(privateKeyBytes);
0523: DHPrivateKeySpec dhPrivateSpec = new DHPrivateKeySpec(
0524: privateKeyInt, dhParamSpec.getP(), dhParamSpec.getG());
0525: PrivateKey privateKey = keyFactory
0526: .generatePrivate(dhPrivateSpec);
0527:
0528: return privateKey;
0529: }
0530:
0531: /**
0532: * Generate a new MWK
0533: * @return Hex String of the new encrypted MWK ready for transmission to ValueLink
0534: */
0535: public byte[] generateMwk() {
0536: KeyGenerator keyGen = null;
0537: try {
0538: keyGen = KeyGenerator.getInstance("DES");
0539: } catch (NoSuchAlgorithmException e) {
0540: Debug.logError(e, module);
0541: }
0542:
0543: // generate the DES key 1
0544: SecretKey des1 = keyGen.generateKey();
0545: SecretKey des2 = keyGen.generateKey();
0546:
0547: if (des1 != null && des2 != null) {
0548: byte[] desByte1 = des1.getEncoded();
0549: byte[] desByte2 = des2.getEncoded();
0550: byte[] desByte3 = des1.getEncoded();
0551:
0552: // check for weak keys
0553: try {
0554: if (DESKeySpec.isWeak(des1.getEncoded(), 0)
0555: || DESKeySpec.isWeak(des2.getEncoded(), 0)) {
0556: return generateMwk();
0557: }
0558: } catch (Exception e) {
0559: Debug.logError(e, module);
0560: }
0561:
0562: byte[] des3 = copyBytes(desByte1, copyBytes(desByte2,
0563: desByte3, 0), 0);
0564: return generateMwk(des3);
0565: } else {
0566: Debug.log("Null DES keys returned", module);
0567: }
0568:
0569: return null;
0570: }
0571:
0572: /**
0573: * Generate a new MWK
0574: * @param desBytes byte array of the DES key (24 bytes)
0575: * @return Hex String of the new encrypted MWK ready for transmission to ValueLink
0576: */
0577: public byte[] generateMwk(byte[] desBytes) {
0578: if (debug) {
0579: Debug.log("DES Key : " + StringUtil.toHexString(desBytes)
0580: + " / " + desBytes.length, module);
0581: }
0582: SecretKeyFactory skf1 = null;
0583: SecretKey mwk = null;
0584: try {
0585: skf1 = SecretKeyFactory.getInstance("DESede");
0586: } catch (NoSuchAlgorithmException e) {
0587: Debug.logError(e, module);
0588: }
0589: DESedeKeySpec desedeSpec2 = null;
0590: try {
0591: desedeSpec2 = new DESedeKeySpec(desBytes);
0592: } catch (InvalidKeyException e) {
0593: Debug.logError(e, module);
0594: }
0595: if (skf1 != null && desedeSpec2 != null) {
0596: try {
0597: mwk = skf1.generateSecret(desedeSpec2);
0598: } catch (InvalidKeySpecException e) {
0599: Debug.logError(e, module);
0600: }
0601: }
0602: if (mwk != null) {
0603: return generateMwk(mwk);
0604: } else {
0605: return null;
0606: }
0607: }
0608:
0609: /**
0610: * Generate a new MWK
0611: * @param mwkdes3 pre-generated DES3 SecretKey
0612: * @return Hex String of the new encrypted MWK ready for transmission to ValueLink
0613: */
0614: public byte[] generateMwk(SecretKey mwkdes3) {
0615: // zeros for checksum
0616: byte[] zeros = { 0, 0, 0, 0, 0, 0, 0, 0 };
0617:
0618: // 8 bytes random data
0619: byte[] random = new byte[8];
0620: Random ran = new Random();
0621: ran.nextBytes(random);
0622:
0623: // open a cipher using the new mwk
0624: Cipher cipher = this .getCipher(mwkdes3, Cipher.ENCRYPT_MODE);
0625:
0626: // make the checksum - encrypted 8 bytes of 0's
0627: byte[] encryptedZeros = new byte[0];
0628: try {
0629: encryptedZeros = cipher.doFinal(zeros);
0630: } catch (IllegalStateException e) {
0631: Debug.logError(e, module);
0632: } catch (IllegalBlockSizeException e) {
0633: Debug.logError(e, module);
0634: } catch (BadPaddingException e) {
0635: Debug.logError(e, module);
0636: }
0637:
0638: // make the 40 byte MWK - random 8 bytes + key + checksum
0639: byte[] newMwk = copyBytes(mwkdes3.getEncoded(), encryptedZeros,
0640: 0);
0641: newMwk = copyBytes(random, newMwk, 0);
0642:
0643: if (debug) {
0644: Debug.log("Random 8 byte : "
0645: + StringUtil.toHexString(random), module);
0646: Debug.log("Encrypted 0's : "
0647: + StringUtil.toHexString(encryptedZeros), module);
0648: Debug.log("Decrypted MWK : "
0649: + StringUtil.toHexString(mwkdes3.getEncoded())
0650: + " / " + mwkdes3.getEncoded().length, module);
0651: Debug.log("Encrypted MWK : "
0652: + StringUtil.toHexString(newMwk) + " / "
0653: + newMwk.length, module);
0654: }
0655:
0656: return newMwk;
0657: }
0658:
0659: /**
0660: * Use the KEK to encrypt a value usually the MWK
0661: * @param content byte array to encrypt
0662: * @return encrypted byte array
0663: */
0664: public byte[] encryptViaKek(byte[] content) {
0665: return cryptoViaKek(content, Cipher.ENCRYPT_MODE);
0666: }
0667:
0668: /**
0669: * Ue the KEK to decrypt a value
0670: * @param content byte array to decrypt
0671: * @return decrypted byte array
0672: */
0673: public byte[] decryptViaKek(byte[] content) {
0674: return cryptoViaKek(content, Cipher.DECRYPT_MODE);
0675: }
0676:
0677: /**
0678: * Returns a date string formatted as directed by ValueLink
0679: * @return ValueLink formatted date String
0680: */
0681: public String getDateString() {
0682: String format = (String) props
0683: .get("payment.valuelink.timestamp");
0684: SimpleDateFormat sdf = new SimpleDateFormat(format);
0685: return sdf.format(new Date());
0686: }
0687:
0688: /**
0689: * Returns the current working key index
0690: * @return Long number of the current working key index
0691: */
0692: public Long getWorkingKeyIndex() {
0693: if (this .mwkIndex == null) {
0694: synchronized (this ) {
0695: if (this .mwkIndex == null) {
0696: this .mwkIndex = this .getGenericValue().getLong(
0697: "workingKeyIndex");
0698: }
0699: }
0700: }
0701:
0702: if (debug) {
0703: Debug.log("Current Working Key Index : " + this .mwkIndex,
0704: module);
0705: }
0706:
0707: return this .mwkIndex;
0708: }
0709:
0710: /**
0711: * Returns a ValueLink formatted amount String
0712: * @param amount Double value to format
0713: * @return Formatted String
0714: */
0715: public String getAmount(Double amount) {
0716: if (amount == null) {
0717: return "0.00";
0718: }
0719: String currencyFormat = UtilProperties.getPropertyValue(
0720: "general.properties", "currency.decimal.format",
0721: "##0.00");
0722: DecimalFormat formatter = new DecimalFormat(currencyFormat);
0723: String amountString = formatter.format(amount.doubleValue());
0724: Double newAmount = null;
0725: try {
0726: newAmount = new Double(formatter.parse(amountString)
0727: .doubleValue());
0728: } catch (ParseException e) {
0729: Debug.logError(e, "Unable to parse amount Double");
0730: }
0731:
0732: String formattedString = null;
0733: if (newAmount != null) {
0734: double amountDouble = newAmount.doubleValue() * 100;
0735: formattedString = new String(new Integer(new Double(
0736: amountDouble).intValue()).toString());
0737: }
0738: return formattedString;
0739: }
0740:
0741: /**
0742: * Returns a Double from a ValueLink formatted amount String
0743: * @param amount The ValueLink formatted amount String
0744: * @return Double object
0745: */
0746: public Double getAmount(String amount) {
0747: if (amount == null) {
0748: return new Double(0.00);
0749: }
0750: Double doubleAmount = new Double(amount);
0751: return new Double(doubleAmount.doubleValue() / 100);
0752: }
0753:
0754: public String getCurrency(String currency) {
0755: return "840"; // todo make this multi-currency
0756: }
0757:
0758: /**
0759: * Creates a Map of initial request values (MerchID, AltMerchNo, Modes, MerchTime, TermTxnNo, EncryptID)
0760: * Note: For 2010 (assign working key) transaction, the EncryptID will need to be adjusted
0761: * @return Map containing the inital request values
0762: */
0763: public Map getInitialRequestMap(Map context) {
0764: Map request = new HashMap();
0765:
0766: // merchant information
0767: request.put("MerchID", merchantId + terminalId);
0768: request.put("AltMerchNo", props
0769: .get("payment.valuelink.altMerchantId"));
0770:
0771: // mode settings
0772: String modes = (String) props.get("payment.valuelink.modes");
0773: if (modes != null && modes.length() > 0) {
0774: request.put("Modes", modes);
0775: }
0776:
0777: // merchant timestamp
0778: String merchTime = (String) context.get("MerchTime");
0779: if (merchTime == null) {
0780: merchTime = this .getDateString();
0781: }
0782: request.put("MerchTime", merchTime);
0783:
0784: // transaction number
0785: String termTxNo = (String) context.get("TermTxnNo");
0786: if (termTxNo == null) {
0787: termTxNo = delegator.getNextSeqId("ValueLinkKey")
0788: .toString();
0789: }
0790: request.put("TermTxnNo", termTxNo);
0791:
0792: // current working key index
0793: request.put("EncryptID", this .getWorkingKeyIndex());
0794:
0795: if (debug) {
0796: Debug.log("Created Initial Request Map : " + request,
0797: module);
0798: }
0799:
0800: return request;
0801: }
0802:
0803: /**
0804: * Gets the cached value object for this merchant's keys
0805: * @return Cached GenericValue object
0806: */
0807: public GenericValue getGenericValue() {
0808: GenericValue value = null;
0809: try {
0810: value = delegator.findByPrimaryKeyCache("ValueLinkKey",
0811: UtilMisc.toMap("merchantId", merchantId));
0812: } catch (GenericEntityException e) {
0813: Debug.logError(e, module);
0814: }
0815: if (value == null) {
0816: throw new RuntimeException(
0817: "No ValueLinkKey record found for Merchant ID : "
0818: + merchantId);
0819: }
0820: return value;
0821: }
0822:
0823: /**
0824: * Reloads the keys in the object cache; use this when re-creating keys
0825: */
0826: public void reload() {
0827: this .kek = null;
0828: this .mwk = null;
0829: this .mwkIndex = null;
0830: }
0831:
0832: // using the prime and generator provided by valuelink; create a parameter object
0833: protected DHParameterSpec getDHParameterSpec() {
0834: String primeHex = (String) props.get("payment.valuelink.prime");
0835: String genString = (String) props
0836: .get("payment.valuelink.generator");
0837:
0838: // convert the p/g hex values
0839: byte[] primeByte = StringUtil.fromHexString(primeHex);
0840: BigInteger prime = new BigInteger(1, primeByte); // force positive (unsigned)
0841: BigInteger generator = new BigInteger(genString);
0842:
0843: // initialize the parameter spec
0844: DHParameterSpec dhParamSpec = new DHParameterSpec(prime,
0845: generator, 1024);
0846:
0847: return dhParamSpec;
0848: }
0849:
0850: // actual kek encryption/decryption code
0851: protected byte[] cryptoViaKek(byte[] content, int mode) {
0852: // open a cipher using the kek for transport
0853: Cipher cipher = this .getCipher(this .getKekKey(), mode);
0854: byte[] dec = new byte[0];
0855: try {
0856: dec = cipher.doFinal(content);
0857: } catch (IllegalStateException e) {
0858: Debug.logError(e, module);
0859: } catch (IllegalBlockSizeException e) {
0860: Debug.logError(e, module);
0861: } catch (BadPaddingException e) {
0862: Debug.logError(e, module);
0863: }
0864: return dec;
0865: }
0866:
0867: // return a cipher for a key - DESede/CBC/NoPadding IV = 0
0868: protected Cipher getCipher(SecretKey key, int mode) {
0869: byte[] zeros = { 0, 0, 0, 0, 0, 0, 0, 0 };
0870: IvParameterSpec iv = new IvParameterSpec(zeros);
0871:
0872: // create the Cipher - DESede/CBC/NoPadding
0873: Cipher mwkCipher = null;
0874: try {
0875: mwkCipher = Cipher.getInstance("DESede/CBC/NoPadding");
0876: } catch (NoSuchAlgorithmException e) {
0877: Debug.logError(e, module);
0878: return null;
0879: } catch (NoSuchPaddingException e) {
0880: Debug.logError(e, module);
0881: }
0882: try {
0883: mwkCipher.init(mode, key, iv);
0884: } catch (InvalidKeyException e) {
0885: Debug.logError(e, "Invalid key", module);
0886: } catch (InvalidAlgorithmParameterException e) {
0887: Debug.logError(e, module);
0888: }
0889: return mwkCipher;
0890: }
0891:
0892: protected byte[] getPinCheckSum(byte[] pinBytes) {
0893: byte[] checkSum = new byte[1];
0894: checkSum[0] = 0;
0895: for (int i = 0; i < pinBytes.length; i++) {
0896: checkSum[0] += pinBytes[i];
0897: }
0898: return checkSum;
0899: }
0900:
0901: protected byte[] getRandomBytes(int length) {
0902: Random rand = new Random();
0903: byte[] randomBytes = new byte[length];
0904: rand.nextBytes(randomBytes);
0905: return randomBytes;
0906: }
0907:
0908: protected SecretKey getMwkKey() {
0909: if (mwk == null) {
0910: mwk = this .getDesEdeKey(getByteRange(getMwk(), 8, 24));
0911: }
0912:
0913: if (debug) {
0914: Debug.log("Raw MWK : " + StringUtil.toHexString(getMwk()),
0915: module);
0916: Debug.log("MWK : "
0917: + StringUtil.toHexString(mwk.getEncoded()), module);
0918: }
0919:
0920: return mwk;
0921: }
0922:
0923: protected SecretKey getKekKey() {
0924: if (kek == null) {
0925: kek = this .getDesEdeKey(getKek());
0926: }
0927:
0928: if (debug) {
0929: Debug.log("Raw KEK : " + StringUtil.toHexString(getKek()),
0930: module);
0931: Debug.log("KEK : "
0932: + StringUtil.toHexString(kek.getEncoded()), module);
0933: }
0934:
0935: return kek;
0936: }
0937:
0938: protected SecretKey getDesEdeKey(byte[] rawKey) {
0939: SecretKeyFactory skf = null;
0940: try {
0941: skf = SecretKeyFactory.getInstance("DESede");
0942: } catch (NoSuchAlgorithmException e) {
0943: // should never happen since DESede is a standard algorithm
0944: Debug.logError(e, module);
0945: return null;
0946: }
0947:
0948: // load the raw key
0949: if (rawKey.length > 0) {
0950: DESedeKeySpec desedeSpec1 = null;
0951: try {
0952: desedeSpec1 = new DESedeKeySpec(rawKey);
0953: } catch (InvalidKeyException e) {
0954: Debug.logError(e, "Not a valid DESede key", module);
0955: return null;
0956: }
0957:
0958: // create the SecretKey Object
0959: SecretKey key = null;
0960: try {
0961: key = skf.generateSecret(desedeSpec1);
0962: } catch (InvalidKeySpecException e) {
0963: Debug.logError(e, module);
0964: }
0965: return key;
0966: } else {
0967: throw new RuntimeException("No valid DESede key available");
0968: }
0969: }
0970:
0971: protected byte[] getMwk() {
0972: return StringUtil.fromHexString(this .getGenericValue()
0973: .getString("workingKey"));
0974: }
0975:
0976: protected byte[] getKek() {
0977: return StringUtil.fromHexString(this .getGenericValue()
0978: .getString("exchangeKey"));
0979: }
0980:
0981: protected byte[] getPrivateKeyBytes() {
0982: return StringUtil.fromHexString(this .getGenericValue()
0983: .getString("privateKey"));
0984: }
0985:
0986: protected Map parseResponse(String response) {
0987: if (debug) {
0988: Debug.log("Raw Response : " + response, module);
0989: }
0990:
0991: // covert to all lowercase and trim off the html header
0992: String subResponse = response.toLowerCase();
0993: int firstIndex = subResponse.indexOf("<tr>");
0994: int lastIndex = subResponse.lastIndexOf("</tr>");
0995: subResponse = subResponse.substring(firstIndex, lastIndex);
0996:
0997: // check for a history table
0998: String history = null;
0999: List historyMapList = null;
1000: if (subResponse.indexOf("<table") > -1) {
1001: int startHistory = subResponse.indexOf("<table");
1002: int endHistory = subResponse.indexOf("</table>") + 8;
1003: history = subResponse.substring(startHistory, endHistory);
1004:
1005: // replace the subResponse string so it doesn't conflict
1006: subResponse = StringUtil.replaceString(subResponse,
1007: history, "[_HISTORY_]");
1008:
1009: // parse the history into a list of maps
1010: historyMapList = this .parseHistoryResponse(history);
1011: }
1012:
1013: // replace all end rows with | this is the name delimiter
1014: subResponse = StringUtil.replaceString(subResponse, "</tr>",
1015: "|");
1016:
1017: // replace all </TD><TD> with = this is the value delimiter
1018: subResponse = StringUtil.replaceString(subResponse,
1019: "</td><td>", "=");
1020:
1021: // clean off a bunch of other useless stuff
1022: subResponse = StringUtil.replaceString(subResponse, "<tr>", "");
1023: subResponse = StringUtil.replaceString(subResponse, "<td>", "");
1024: subResponse = StringUtil
1025: .replaceString(subResponse, "</td>", "");
1026:
1027: // make the map
1028: Map responseMap = StringUtil.strToMap(subResponse, true);
1029:
1030: // add the raw html back in just in case we need it later
1031: responseMap.put("_rawHtmlResponse", response);
1032:
1033: // if we have a history add it back in
1034: if (history != null) {
1035: responseMap.put("_rawHistoryHtml", history);
1036: responseMap.put("history", historyMapList);
1037: }
1038:
1039: if (debug) {
1040: Debug.log("Response Map : " + responseMap, module);
1041: }
1042:
1043: return responseMap;
1044: }
1045:
1046: private List parseHistoryResponse(String response) {
1047: if (debug) {
1048: Debug.log("Raw History : " + response, module);
1049: }
1050:
1051: // covert to all lowercase and trim off the html header
1052: String subResponse = response.toLowerCase();
1053: int firstIndex = subResponse.indexOf("<tr>");
1054: int lastIndex = subResponse.lastIndexOf("</tr>");
1055: subResponse = subResponse.substring(firstIndex, lastIndex);
1056:
1057: // clean up the html and replace the delimiters with '|'
1058: subResponse = StringUtil.replaceString(subResponse, "<td>", "");
1059: subResponse = StringUtil.replaceString(subResponse, "</td>",
1060: "|");
1061:
1062: // test the string to make sure we have fields to parse
1063: String testResponse = StringUtil.replaceString(subResponse,
1064: "<tr>", "");
1065: testResponse = StringUtil.replaceString(testResponse, "</tr>",
1066: "");
1067: testResponse = StringUtil.replaceString(testResponse, "|", "");
1068: testResponse = testResponse.trim();
1069: if (testResponse.length() == 0) {
1070: if (debug) {
1071: Debug
1072: .log(
1073: "History did not contain any fields, returning null",
1074: module);
1075: }
1076: return null;
1077: }
1078:
1079: // break up the keys from the values
1080: int valueStart = subResponse.indexOf("</tr>");
1081: String keys = subResponse.substring(4, valueStart - 1);
1082: String values = subResponse.substring(valueStart + 9,
1083: subResponse.length() - 6);
1084:
1085: // split sets of values up
1086: values = StringUtil.replaceString(values, "|</tr><tr>", "&");
1087: List valueList = StringUtil.split(values, "&");
1088:
1089: // create a List of Maps for each set of values
1090: List valueMap = new ArrayList();
1091: for (int i = 0; i < valueList.size(); i++) {
1092: valueMap.add(StringUtil.createMap(StringUtil.split(keys,
1093: "|"), StringUtil.split((String) valueList.get(i),
1094: "|")));
1095: }
1096:
1097: if (debug) {
1098: Debug.log("History Map : " + valueMap, module);
1099: }
1100:
1101: return valueMap;
1102: }
1103:
1104: /**
1105: * Returns a new byte[] from the offset of the defined byte[] with a specific number of bytes
1106: * @param bytes The byte[] to extract from
1107: * @param offset The starting postition
1108: * @param length The number of bytes to copy
1109: * @return a new byte[]
1110: */
1111: public static byte[] getByteRange(byte[] bytes, int offset,
1112: int length) {
1113: byte[] newBytes = new byte[length];
1114: for (int i = 0; i < length; i++) {
1115: newBytes[i] = bytes[offset + i];
1116: }
1117: return newBytes;
1118: }
1119:
1120: /**
1121: * Copies a byte[] into another byte[] starting at a specific position
1122: * @param source byte[] to copy from
1123: * @param target byte[] coping into
1124: * @param position the position on target where source will be copied to
1125: * @return a new byte[]
1126: */
1127: public static byte[] copyBytes(byte[] source, byte[] target,
1128: int position) {
1129: byte[] newBytes = new byte[target.length + source.length];
1130: for (int i = 0, n = 0, x = 0; i < newBytes.length; i++) {
1131: if (i < position || i > (position + source.length - 2)) {
1132: newBytes[i] = target[n];
1133: n++;
1134: } else {
1135: for (; x < source.length; x++) {
1136: newBytes[i] = source[x];
1137: if (source.length - 1 > x) {
1138: i++;
1139: }
1140: }
1141: }
1142: }
1143: return newBytes;
1144: }
1145: }
|