KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ofbiz > accounting > thirdparty > valuelink > ValueLinkApi


1 /*
2  * $Id: ValueLinkApi.java 5462 2005-08-05 18:35:48Z jonesde $
3  *
4  * Copyright (c) 2003 The Open For Business Project - www.ofbiz.org
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
21  * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  *
24  */

25 package org.ofbiz.accounting.thirdparty.valuelink;
26
27 import java.math.BigInteger JavaDoc;
28 import java.security.InvalidAlgorithmParameterException JavaDoc;
29 import java.security.InvalidKeyException JavaDoc;
30 import java.security.KeyFactory JavaDoc;
31 import java.security.KeyPair JavaDoc;
32 import java.security.KeyPairGenerator JavaDoc;
33 import java.security.MessageDigest JavaDoc;
34 import java.security.NoSuchAlgorithmException JavaDoc;
35 import java.security.PrivateKey JavaDoc;
36 import java.security.PublicKey JavaDoc;
37 import java.security.spec.InvalidKeySpecException JavaDoc;
38 import java.text.DecimalFormat JavaDoc;
39 import java.text.ParseException JavaDoc;
40 import java.text.SimpleDateFormat JavaDoc;
41 import java.util.ArrayList JavaDoc;
42 import java.util.Date JavaDoc;
43 import java.util.HashMap JavaDoc;
44 import java.util.List JavaDoc;
45 import java.util.Map JavaDoc;
46 import java.util.Properties JavaDoc;
47 import java.util.Random JavaDoc;
48
49 import javax.crypto.BadPaddingException;
50 import javax.crypto.Cipher;
51 import javax.crypto.IllegalBlockSizeException;
52 import javax.crypto.KeyAgreement;
53 import javax.crypto.KeyGenerator;
54 import javax.crypto.NoSuchPaddingException;
55 import javax.crypto.SecretKey;
56 import javax.crypto.SecretKeyFactory;
57 import javax.crypto.interfaces.DHPrivateKey;
58 import javax.crypto.interfaces.DHPublicKey;
59 import javax.crypto.spec.DESKeySpec;
60 import javax.crypto.spec.DESedeKeySpec;
61 import javax.crypto.spec.DHParameterSpec;
62 import javax.crypto.spec.DHPrivateKeySpec;
63 import javax.crypto.spec.DHPublicKeySpec;
64 import javax.crypto.spec.IvParameterSpec;
65
66 import org.ofbiz.base.util.Debug;
67 import org.ofbiz.base.util.HttpClient;
68 import org.ofbiz.base.util.HttpClientException;
69 import org.ofbiz.base.util.StringUtil;
70 import org.ofbiz.base.util.UtilMisc;
71 import org.ofbiz.base.util.UtilProperties;
72 import org.ofbiz.entity.GenericDelegator;
73 import org.ofbiz.entity.GenericEntityException;
74 import org.ofbiz.entity.GenericValue;
75
76 /**
77  * ValueLinkApi - Implementation of ValueLink Encryption & Transport
78  *
79  * @author <a HREF="mailto:jaz@ofbiz.org">Andy Zeneski</a>
80  * @version $Rev: 5462 $
81  * @since 3.0
82  */

83 public class ValueLinkApi {
84
85     public static final String JavaDoc module = ValueLinkApi.class.getName();
86
87     // static object cache
88
private static Map JavaDoc objectCache = new HashMap JavaDoc();
89
90     // instance variables
91
protected GenericDelegator delegator = null;
92     protected Properties JavaDoc props = null;
93     protected SecretKey kek = null;
94     protected SecretKey mwk = null;
95     protected String JavaDoc merchantId = null;
96     protected String JavaDoc terminalId = null;
97     protected Long JavaDoc mwkIndex = null;
98     protected boolean debug = false;
99
100     protected ValueLinkApi() {}
101     protected ValueLinkApi(GenericDelegator delegator, Properties JavaDoc props) {
102         String JavaDoc mId = (String JavaDoc) props.get("payment.valuelink.merchantId");
103         String JavaDoc tId = (String JavaDoc) props.get("payment.valuelink.terminalId");
104         this.delegator = delegator;
105         this.merchantId = mId;
106         this.terminalId = tId;
107         this.props = props;
108         if ("Y".equalsIgnoreCase((String JavaDoc) props.get("payment.valuelink.debug"))) {
109             this.debug = true;
110         }
111
112         if (debug) {
113             Debug.log("New ValueLinkApi instance created", module);
114             Debug.log("Merchant ID : " + merchantId, module);
115             Debug.log("Terminal ID : " + terminalId, module);
116         }
117     }
118
119     /**
120      * Obtain an instance of the ValueLinkApi
121      * @param delegator GenericDelegator used to query the encryption keys
122      * @param props Properties to use for the Api (usually payment.properties)
123      * @param reload When true, will replace an existing instance in the cache and reload all properties
124      * @return ValueLinkApi reference
125      */

126     public static ValueLinkApi getInstance(GenericDelegator delegator, Properties JavaDoc props, boolean reload) {
127         String JavaDoc merchantId = (String JavaDoc) props.get("payment.valuelink.merchantId");
128         if (props == null) {
129             throw new IllegalArgumentException JavaDoc("Properties cannot be null");
130         }
131
132         ValueLinkApi api = (ValueLinkApi) objectCache.get(merchantId);
133         if (api == null || reload) {
134             synchronized(ValueLinkApi.class) {
135                 api = (ValueLinkApi) objectCache.get(merchantId);
136                 if (api == null || reload) {
137                     api = new ValueLinkApi(delegator, props);
138                     objectCache.put(merchantId, api);
139                 }
140             }
141         }
142
143         if (api == null) {
144             throw new RuntimeException JavaDoc("Runtime problems with ValueLinkApi; unable to create instance");
145         }
146
147         return api;
148     }
149
150     /**
151      * Obtain an instance of the ValueLinkApi; this method will always return an existing reference if one is available
152      * @param delegator GenericDelegator used to query the encryption keys
153      * @param props Properties to use for the Api (usually payment.properties)
154      * @return
155      */

156     public static ValueLinkApi getInstance(GenericDelegator delegator, Properties JavaDoc props) {
157         return getInstance(delegator, props, false);
158     }
159
160     /**
161      * Encrypt the defined pin using the configured keys
162      * @param pin Plain text String of the pin
163      * @return Hex String of the encrypted pin (EAN) for transmission to ValueLink
164      */

165     public String JavaDoc encryptPin(String JavaDoc pin) {
166         // get the Cipher
167
Cipher mwkCipher = this.getCipher(this.getMwkKey(), Cipher.ENCRYPT_MODE);
168
169         // pin to bytes
170
byte[] pinBytes = pin.getBytes();
171
172         // 7 bytes of random data
173
byte[] random = this.getRandomBytes(7);
174
175         // pin checksum
176
byte[] checkSum = this.getPinCheckSum(pinBytes);
177
178         // put all together
179
byte[] eanBlock = new byte[16];
180         int i;
181         for (i = 0; i < random.length; i++) {
182             eanBlock[i] = random[i];
183         }
184         eanBlock[7] = checkSum[0];
185         for (i = 0; i < pinBytes.length; i++) {
186             eanBlock[i + 8] = pinBytes[i];
187         }
188
189         // encrypy the ean
190
String JavaDoc encryptedEanHex = null;
191         try {
192             byte[] encryptedEan = mwkCipher.doFinal(eanBlock);
193             encryptedEanHex = StringUtil.toHexString(encryptedEan);
194         } catch (IllegalStateException JavaDoc e) {
195             Debug.logError(e, module);
196         } catch (IllegalBlockSizeException e) {
197             Debug.logError(e, module);
198         } catch (BadPaddingException e) {
199             Debug.logError(e, module);
200         }
201
202         if (debug) {
203             Debug.log("encryptPin : " + pin + " / " + encryptedEanHex, module);
204         }
205
206         return encryptedEanHex;
207     }
208
209     /**
210      * Decrypt an encrypted pin using the configured keys
211      * @param pin Hex String of the encrypted pin (EAN)
212      * @return Plain text String of the pin
213      */

214     public String JavaDoc decryptPin(String JavaDoc pin) {
215         // get the Cipher
216
Cipher mwkCipher = this.getCipher(this.getMwkKey(), Cipher.DECRYPT_MODE);
217
218         // decrypt pin
219
String JavaDoc decryptedPinString = null;
220         try {
221             byte[] decryptedEan = mwkCipher.doFinal(StringUtil.fromHexString(pin));
222             byte[] decryptedPin = getByteRange(decryptedEan, 8, 8);
223             decryptedPinString = new String JavaDoc(decryptedPin);
224         } catch (IllegalStateException JavaDoc e) {
225             Debug.logError(e, module);
226         } catch (IllegalBlockSizeException e) {
227             Debug.logError(e, module);
228         } catch (BadPaddingException e) {
229             Debug.logError(e, module);
230         }
231
232         if (debug) {
233             Debug.log("decryptPin : " + pin + " / " + decryptedPinString, module);
234         }
235
236         return decryptedPinString;
237     }
238
239     /**
240      * Transmit a request to ValueLink
241      * @param request Map of request parameters
242      * @return Map of response parameters
243      * @throws HttpClientException
244      */

245     public Map JavaDoc send(Map JavaDoc request) throws HttpClientException {
246         return send((String JavaDoc) props.get("payment.valuelink.url"), request);
247     }
248
249     /**
250      * Transmit a request to ValueLink
251      * @param url override URL from what is defined in the properties
252      * @param request request Map of request parameters
253      * @return Map of response parameters
254      * @throws HttpClientException
255      */

256     public Map JavaDoc send(String JavaDoc url, Map JavaDoc request) throws HttpClientException {
257         if (debug) {
258             Debug.log("Request : " + url + " / " + request, module);
259         }
260
261         // read the timeout value
262
String JavaDoc timeoutString = (String JavaDoc) props.get("payment.valuelink.timeout");
263         int timeout = 34;
264         try {
265             timeout = Integer.parseInt(timeoutString);
266         } catch (NumberFormatException JavaDoc e) {
267             Debug.logError(e, "Unable to set timeout to " + timeoutString + " using default " + timeout);
268         }
269
270         // create the HTTP client
271
HttpClient client = new HttpClient(url, request);
272         client.setTimeout(timeout * 1000);
273         client.setDebug(debug);
274
275         client.setClientCertificateAlias((String JavaDoc) props.get("payment.valuelink.certificateAlias"));
276         String JavaDoc response = client.post();
277
278         // parse the response and return a map
279
return this.parseResponse(response);
280     }
281
282     /**
283      * Output the creation of public/private keys + KEK to the console for manual database update
284      */

285     public StringBuffer JavaDoc outputKeyCreation(boolean kekOnly, String JavaDoc kekTest) {
286         return this.outputKeyCreation(0, kekOnly, kekTest);
287     }
288
289     private StringBuffer JavaDoc outputKeyCreation(int loop, boolean kekOnly, String JavaDoc kekTest) {
290         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
291         loop++;
292
293         if (loop > 100) {
294             // only loop 100 times; then throw an exception
295
throw new IllegalStateException JavaDoc("Unable to create 128 byte keys in 100 tries");
296         }
297
298         // place holder for the keys
299
DHPrivateKey privateKey = null;
300         DHPublicKey publicKey = null;
301
302         if (!kekOnly) {
303             KeyPair JavaDoc keyPair = null;
304             try {
305                 keyPair = this.createKeys();
306             } catch (NoSuchAlgorithmException JavaDoc e) {
307                 Debug.logError(e, module);
308             } catch (InvalidAlgorithmParameterException JavaDoc e) {
309                 Debug.logError(e, module);
310             } catch (InvalidKeySpecException JavaDoc e) {
311                 Debug.logError(e, module);
312             }
313
314             if (keyPair != null) {
315                 publicKey = (DHPublicKey) keyPair.getPublic();
316                 privateKey = (DHPrivateKey) keyPair.getPrivate();
317
318                 if (publicKey == null || publicKey.getY().toByteArray().length != 128) {
319                     // run again until we get a 128 byte public key for VL
320
return this.outputKeyCreation(loop, kekOnly, kekTest);
321                 }
322             } else {
323                 Debug.log("Returned a null KeyPair", module);
324                 return this.outputKeyCreation(loop, kekOnly, kekTest);
325             }
326         } else {
327             // use our existing private key to generate a KEK
328
try {
329                 privateKey = (DHPrivateKey) this.getPrivateKey();
330             } catch (Exception JavaDoc e) {
331                 Debug.logError(e, module);
332             }
333         }
334
335         // the KEK
336
byte[] kekBytes = null;
337         try {
338             kekBytes = this.generateKek(privateKey);
339         } catch (NoSuchAlgorithmException JavaDoc e) {
340             Debug.logError(e, module);
341         } catch (InvalidKeySpecException JavaDoc e) {
342             Debug.logError(e, module);
343         } catch (InvalidKeyException JavaDoc e) {
344             Debug.logError(e, module);
345         }
346
347         // the 3DES KEK value
348
SecretKey loadedKek = this.getDesEdeKey(kekBytes);
349         byte[] loadKekBytes = loadedKek.getEncoded();
350
351         // test the KEK
352
Cipher cipher = this.getCipher(this.getKekKey(), Cipher.ENCRYPT_MODE);
353         byte[] kekTestB = { 0, 0, 0, 0, 0, 0, 0, 0 };
354         byte[] kekTestC = new byte[0];
355         if (kekTest != null) {
356             kekTestB = StringUtil.fromHexString(kekTest);
357         }
358
359         // encrypt the test bytes
360
try {
361             kekTestC = cipher.doFinal(kekTestB);
362         } catch (Exception JavaDoc e) {
363             Debug.logError(e, module);
364         }
365
366         if (!kekOnly) {
367             // public key (just Y)
368
BigInteger JavaDoc y = publicKey.getY();
369             byte[] yBytes = y.toByteArray();
370             String JavaDoc yHex = StringUtil.toHexString(yBytes);
371             buf.append("======== Begin Public Key (Y @ " + yBytes.length + " / " + yHex.length() + ") ========\n");
372             buf.append(yHex + "\n");
373             buf.append("======== End Public Key ========\n\n");
374
375             // private key (just X)
376
BigInteger JavaDoc x = privateKey.getX();
377             byte[] xBytes = x.toByteArray();
378             String JavaDoc xHex = StringUtil.toHexString(xBytes);
379             buf.append("======== Begin Private Key (X @ " + xBytes.length + " / " + xHex.length() + ") ========\n");
380             buf.append(xHex + "\n");
381             buf.append("======== End Private Key ========\n\n");
382
383             // private key (full)
384
byte[] privateBytes = privateKey.getEncoded();
385             String JavaDoc privateHex = StringUtil.toHexString(privateBytes);
386             buf.append("======== Begin Private Key (Full @ " + privateBytes.length + " / " + privateHex.length() + ") ========\n");
387             buf.append(privateHex + "\n");
388             buf.append("======== End Private Key ========\n\n");
389         }
390
391         if (kekBytes != null) {
392             buf.append("======== Begin KEK (" + kekBytes.length + ") ========\n");
393             buf.append(StringUtil.toHexString(kekBytes) + "\n");
394             buf.append("======== End KEK ========\n\n");
395
396             buf.append("======== Begin KEK (DES) (" + loadKekBytes.length + ") ========\n");
397             buf.append(StringUtil.toHexString(loadKekBytes) + "\n");
398             buf.append("======== End KEK (DES) ========\n\n");
399
400             buf.append("======== Begin KEK Test (" + kekTestC.length + ") ========\n");
401             buf.append(StringUtil.toHexString(kekTestC) + "\n");
402             buf.append("======== End KEK Test ========\n\n");
403         } else {
404             Debug.logError("KEK came back empty", module);
405         }
406
407         return buf;
408     }
409
410     /**
411      * Create a set of public/private keys using ValueLinks defined parameters
412      * @return KeyPair object containing both public and private keys
413      * @throws NoSuchAlgorithmException
414      * @throws InvalidAlgorithmParameterException
415      */

416     public KeyPair JavaDoc createKeys() throws NoSuchAlgorithmException JavaDoc, InvalidAlgorithmParameterException JavaDoc, InvalidKeySpecException JavaDoc {
417         // initialize the parameter spec
418
DHPublicKey publicKey = (DHPublicKey) this.getValueLinkPublicKey();
419         DHParameterSpec dhParamSpec = publicKey.getParams();
420         //Debug.log(dhParamSpec.getP().toString() + " / " + dhParamSpec.getG().toString(), module);
421

422         // create the public/private key pair using parameters defined by valuelink
423
KeyPairGenerator JavaDoc keyGen = KeyPairGenerator.getInstance("DH");
424         keyGen.initialize(dhParamSpec);
425         KeyPair JavaDoc keyPair = keyGen.generateKeyPair();
426
427         return keyPair;
428     }
429
430     /**
431      * Generate a key exchange key for use in encrypting the mwk
432      * @param privateKey The private key for the merchant
433      * @return byte array containing the kek
434      * @throws NoSuchAlgorithmException
435      * @throws InvalidKeySpecException
436      * @throws InvalidKeyException
437      */

438     public byte[] generateKek(PrivateKey JavaDoc privateKey) throws NoSuchAlgorithmException JavaDoc, InvalidKeySpecException JavaDoc, InvalidKeyException JavaDoc {
439         // get the ValueLink public key
440
PublicKey JavaDoc vlPublic = this.getValueLinkPublicKey();
441
442         // generate shared secret key
443
KeyAgreement ka = KeyAgreement.getInstance("DH");
444         ka.init(privateKey);
445         ka.doPhase(vlPublic, true);
446         byte[] secretKey = ka.generateSecret();
447
448         if (debug) {
449             Debug.log("Secret Key : " + StringUtil.toHexString(secretKey) + " / " + secretKey.length, module);
450         }
451
452         // generate 3DES from secret key using VL algorithm (KEK)
453
MessageDigest JavaDoc md = MessageDigest.getInstance("SHA1");
454         byte[] digest = md.digest(secretKey);
455         byte[] des2 = getByteRange(digest, 0, 16);
456         byte[] first8 = getByteRange(des2, 0, 8);
457         byte[] kek = copyBytes(des2, first8, 0);
458
459         if (debug) {
460             Debug.log("Generated KEK : " + StringUtil.toHexString(kek) + " / " + kek.length, module);
461         }
462
463         return kek;
464     }
465
466     /**
467      * Get a public key object for the ValueLink supplied public key
468      * @return PublicKey object of ValueLinks's public key
469      * @throws NoSuchAlgorithmException
470      * @throws InvalidKeySpecException
471      */

472     public PublicKey JavaDoc getValueLinkPublicKey() throws NoSuchAlgorithmException JavaDoc, InvalidKeySpecException JavaDoc {
473         // read the valuelink public key
474
String JavaDoc publicValue = (String JavaDoc) props.get("payment.valuelink.publicValue");
475         byte[] publicKeyBytes = StringUtil.fromHexString(publicValue);
476
477         // initialize the parameter spec
478
DHParameterSpec dhParamSpec = this.getDHParameterSpec();
479
480         // load the valuelink public key
481
KeyFactory JavaDoc keyFactory = KeyFactory.getInstance("DH");
482         BigInteger JavaDoc publicKeyInt = new BigInteger JavaDoc(publicKeyBytes);
483         DHPublicKeySpec dhPublicSpec = new DHPublicKeySpec(publicKeyInt, dhParamSpec.getP(), dhParamSpec.getG());
484         PublicKey JavaDoc vlPublic = keyFactory.generatePublic(dhPublicSpec);
485
486         return vlPublic;
487     }
488
489     /**
490      * Get merchant Private Key
491      * @return PrivateKey object for the merchant
492      */

493     public PrivateKey JavaDoc getPrivateKey() throws InvalidKeySpecException JavaDoc, NoSuchAlgorithmException JavaDoc {
494         byte[] privateKeyBytes = this.getPrivateKeyBytes();
495
496         // initialize the parameter spec
497
DHParameterSpec dhParamSpec = this.getDHParameterSpec();
498
499         // load the private key
500
KeyFactory JavaDoc keyFactory = KeyFactory.getInstance("DH");
501         BigInteger JavaDoc privateKeyInt = new BigInteger JavaDoc(privateKeyBytes);
502         DHPrivateKeySpec dhPrivateSpec = new DHPrivateKeySpec(privateKeyInt, dhParamSpec.getP(), dhParamSpec.getG());
503         PrivateKey JavaDoc privateKey = keyFactory.generatePrivate(dhPrivateSpec);
504
505         return privateKey;
506     }
507
508     /**
509      * Generate a new MWK
510      * @return Hex String of the new encrypted MWK ready for transmission to ValueLink
511      */

512     public byte[] generateMwk() {
513         KeyGenerator keyGen = null;
514         try {
515             keyGen = KeyGenerator.getInstance("DES");
516         } catch (NoSuchAlgorithmException JavaDoc e) {
517             Debug.logError(e, module);
518         }
519
520         // generate the DES key 1
521
SecretKey des1 = keyGen.generateKey();
522         SecretKey des2 = keyGen.generateKey();
523
524         if (des1 != null && des2 != null) {
525             byte[] desByte1 = des1.getEncoded();
526             byte[] desByte2 = des2.getEncoded();
527             byte[] desByte3 = des1.getEncoded();
528
529             // check for weak keys
530
try {
531                 if (DESKeySpec.isWeak(des1.getEncoded(), 0) || DESKeySpec.isWeak(des2.getEncoded(), 0)) {
532                     return generateMwk();
533                 }
534             } catch (Exception JavaDoc e) {
535                 Debug.logError(e, module);
536             }
537
538             byte[] des3 = copyBytes(desByte1, copyBytes(desByte2, desByte3, 0), 0);
539             return generateMwk(des3);
540         } else {
541             Debug.log("Null DES keys returned", module);
542         }
543
544         return null;
545     }
546
547     /**
548      * Generate a new MWK
549      * @param desBytes byte array of the DES key (24 bytes)
550      * @return Hex String of the new encrypted MWK ready for transmission to ValueLink
551      */

552     public byte[] generateMwk(byte[] desBytes) {
553         if (debug) {
554             Debug.log("DES Key : " + StringUtil.toHexString(desBytes) + " / " + desBytes.length, module);
555         }
556         SecretKeyFactory skf1 = null;
557         SecretKey mwk = null;
558         try {
559             skf1 = SecretKeyFactory.getInstance("DESede");
560         } catch (NoSuchAlgorithmException JavaDoc e) {
561             Debug.logError(e, module);
562         }
563         DESedeKeySpec desedeSpec2 = null;
564         try {
565             desedeSpec2 = new DESedeKeySpec(desBytes);
566         } catch (InvalidKeyException JavaDoc e) {
567             Debug.logError(e, module);
568         }
569         if (skf1 != null && desedeSpec2 != null) {
570             try {
571                 mwk = skf1.generateSecret(desedeSpec2);
572             } catch (InvalidKeySpecException JavaDoc e) {
573                 Debug.logError(e, module);
574             }
575         }
576         if (mwk != null) {
577             return generateMwk(mwk);
578         } else {
579             return null;
580         }
581     }
582
583     /**
584      * Generate a new MWK
585      * @param mwkdes3 pre-generated DES3 SecretKey
586      * @return Hex String of the new encrypted MWK ready for transmission to ValueLink
587      */

588     public byte[] generateMwk(SecretKey mwkdes3) {
589         // zeros for checksum
590
byte[] zeros = { 0, 0, 0, 0, 0, 0, 0, 0 };
591
592         // 8 bytes random data
593
byte[] random = new byte[8];
594         Random JavaDoc ran = new Random JavaDoc();
595         ran.nextBytes(random);
596
597
598         // open a cipher using the new mwk
599
Cipher cipher = this.getCipher(mwkdes3, Cipher.ENCRYPT_MODE);
600
601         // make the checksum - encrypted 8 bytes of 0's
602
byte[] encryptedZeros = new byte[0];
603         try {
604             encryptedZeros = cipher.doFinal(zeros);
605         } catch (IllegalStateException JavaDoc e) {
606             Debug.logError(e, module);
607         } catch (IllegalBlockSizeException e) {
608             Debug.logError(e, module);
609         } catch (BadPaddingException e) {
610             Debug.logError(e, module);
611         }
612
613         // make the 40 byte MWK - random 8 bytes + key + checksum
614
byte[] newMwk = copyBytes(mwkdes3.getEncoded(), encryptedZeros, 0);
615         newMwk = copyBytes(random, newMwk, 0);
616
617         if (debug) {
618             Debug.log("Random 8 byte : " + StringUtil.toHexString(random), module);
619             Debug.log("Encrypted 0's : " + StringUtil.toHexString(encryptedZeros), module);
620             Debug.log("Decrypted MWK : " + StringUtil.toHexString(mwkdes3.getEncoded()) + " / " + mwkdes3.getEncoded().length, module);
621             Debug.log("Encrypted MWK : " + StringUtil.toHexString(newMwk) + " / " + newMwk.length, module);
622         }
623
624         return newMwk;
625     }
626
627     /**
628      * Use the KEK to encrypt a value usually the MWK
629      * @param content byte array to encrypt
630      * @return encrypted byte array
631      */

632     public byte[] encryptViaKek(byte[] content) {
633         return cryptoViaKek(content, Cipher.ENCRYPT_MODE);
634     }
635
636     /**
637      * Ue the KEK to decrypt a value
638      * @param content byte array to decrypt
639      * @return decrypted byte array
640      */

641     public byte[] decryptViaKek(byte[] content) {
642         return cryptoViaKek(content, Cipher.DECRYPT_MODE);
643     }
644
645     /**
646      * Returns a date string formatted as directed by ValueLink
647      * @return ValueLink formatted date String
648      */

649     public String JavaDoc getDateString() {
650         String JavaDoc format = (String JavaDoc) props.get("payment.valuelink.timestamp");
651         SimpleDateFormat JavaDoc sdf = new SimpleDateFormat JavaDoc(format);
652         return sdf.format(new Date JavaDoc());
653     }
654
655     /**
656      * Returns the current working key index
657      * @return Long number of the current working key index
658      */

659     public Long JavaDoc getWorkingKeyIndex() {
660         if (this.mwkIndex == null) {
661             synchronized(this) {
662                 if (this.mwkIndex == null) {
663                     this.mwkIndex = this.getGenericValue().getLong("workingKeyIndex");
664                 }
665             }
666         }
667
668         if (debug) {
669             Debug.log("Current Working Key Index : " + this.mwkIndex, module);
670         }
671
672         return this.mwkIndex;
673     }
674
675     /**
676      * Returns a ValueLink formatted amount String
677      * @param amount Double value to format
678      * @return Formatted String
679      */

680     public String JavaDoc getAmount(Double JavaDoc amount) {
681         if (amount == null) {
682             return "0.00";
683         }
684         String JavaDoc currencyFormat = UtilProperties.getPropertyValue("general.properties", "currency.decimal.format", "##0.00");
685         DecimalFormat JavaDoc formatter = new DecimalFormat JavaDoc(currencyFormat);
686         String JavaDoc amountString = formatter.format(amount.doubleValue());
687         Double JavaDoc newAmount = null;
688         try {
689             newAmount = new Double JavaDoc(formatter.parse(amountString).doubleValue());
690         } catch (ParseException JavaDoc e) {
691             Debug.logError(e, "Unable to parse amount Double");
692         }
693
694         String JavaDoc formattedString = null;
695         if (newAmount != null) {
696             double amountDouble = newAmount.doubleValue() * 100;
697             formattedString = new String JavaDoc(new Integer JavaDoc(new Double JavaDoc(amountDouble).intValue()).toString());
698         }
699         return formattedString;
700     }
701
702     /**
703      * Returns a Double from a ValueLink formatted amount String
704      * @param amount The ValueLink formatted amount String
705      * @return Double object
706      */

707     public Double JavaDoc getAmount(String JavaDoc amount) {
708         if (amount == null) {
709             return new Double JavaDoc(0.00);
710         }
711         Double JavaDoc doubleAmount = new Double JavaDoc(amount);
712         return new Double JavaDoc(doubleAmount.doubleValue() / 100);
713     }
714     
715     public String JavaDoc getCurrency(String JavaDoc currency) {
716         return "840"; // todo make this multi-currency
717
}
718
719     /**
720      * Creates a Map of initial request values (MerchID, AltMerchNo, Modes, MerchTime, TermTxnNo, EncryptID)
721      * Note: For 2010 (assign working key) transaction, the EncryptID will need to be adjusted
722      * @return Map containing the inital request values
723      */

724     public Map JavaDoc getInitialRequestMap(Map JavaDoc context) {
725         Map JavaDoc request = new HashMap JavaDoc();
726
727         // merchant information
728
request.put("MerchID", merchantId + terminalId);
729         request.put("AltMerchNo", props.get("payment.valuelink.altMerchantId"));
730
731         // mode settings
732
String JavaDoc modes = (String JavaDoc) props.get("payment.valuelink.modes");
733         if (modes != null && modes.length() > 0) {
734             request.put("Modes", modes);
735         }
736
737         // merchant timestamp
738
String JavaDoc merchTime = (String JavaDoc) context.get("MerchTime");
739         if (merchTime == null) {
740             merchTime = this.getDateString();
741         }
742         request.put("MerchTime", merchTime);
743
744         // transaction number
745
String JavaDoc termTxNo = (String JavaDoc) context.get("TermTxnNo");
746         if (termTxNo == null) {
747             termTxNo = delegator.getNextSeqId("ValueLinkKey").toString();
748         }
749         request.put("TermTxnNo", termTxNo);
750
751         // current working key index
752
request.put("EncryptID", this.getWorkingKeyIndex());
753
754         if (debug) {
755             Debug.log("Created Initial Request Map : " + request, module);
756         }
757
758         return request;
759     }
760
761     /**
762      * Gets the cached value object for this merchant's keys
763      * @return Cached GenericValue object
764      */

765     public GenericValue getGenericValue() {
766         GenericValue value = null;
767         try {
768             value = delegator.findByPrimaryKeyCache("ValueLinkKey", UtilMisc.toMap("merchantId", merchantId));
769         } catch (GenericEntityException e) {
770             Debug.logError(e, module);
771         }
772         if (value == null) {
773             throw new RuntimeException JavaDoc("No ValueLinkKey record found for Merchant ID : " + merchantId);
774         }
775         return value;
776     }
777
778     /**
779      * Reloads the keys in the object cache; use this when re-creating keys
780      */

781     public void reload() {
782         this.kek = null;
783         this.mwk = null;
784         this.mwkIndex = null;
785     }
786
787     // using the prime and generator provided by valuelink; create a parameter object
788
protected DHParameterSpec getDHParameterSpec() {
789         String JavaDoc primeHex = (String JavaDoc) props.get("payment.valuelink.prime");
790         String JavaDoc genString = (String JavaDoc) props.get("payment.valuelink.generator");
791
792         // convert the p/g hex values
793
byte[] primeByte = StringUtil.fromHexString(primeHex);
794         BigInteger JavaDoc prime = new BigInteger JavaDoc(1, primeByte); // force positive (unsigned)
795
BigInteger JavaDoc generator = new BigInteger JavaDoc(genString);
796
797         // initialize the parameter spec
798
DHParameterSpec dhParamSpec = new DHParameterSpec(prime, generator, 1024);
799
800         return dhParamSpec;
801     }
802
803     // actual kek encryption/decryption code
804
protected byte[] cryptoViaKek(byte[] content, int mode) {
805         // open a cipher using the kek for transport
806
Cipher cipher = this.getCipher(this.getKekKey(), mode);
807         byte[] dec = new byte[0];
808         try {
809             dec = cipher.doFinal(content);
810         } catch (IllegalStateException JavaDoc e) {
811             Debug.logError(e, module);
812         } catch (IllegalBlockSizeException e) {
813             Debug.logError(e, module);
814         } catch (BadPaddingException e) {
815             Debug.logError(e, module);
816         }
817         return dec;
818     }
819
820     // return a cipher for a key - DESede/CBC/NoPadding IV = 0
821
protected Cipher getCipher(SecretKey key, int mode) {
822         byte[] zeros = { 0, 0, 0, 0, 0, 0, 0, 0 };
823         IvParameterSpec iv = new IvParameterSpec(zeros);
824
825         // create the Cipher - DESede/CBC/NoPadding
826
Cipher mwkCipher = null;
827         try {
828             mwkCipher = Cipher.getInstance("DESede/CBC/NoPadding");
829         } catch (NoSuchAlgorithmException JavaDoc e) {
830             Debug.logError(e, module);
831             return null;
832         } catch (NoSuchPaddingException e) {
833             Debug.logError(e, module);
834         }
835         try {
836             mwkCipher.init(mode, key, iv);
837         } catch (InvalidKeyException JavaDoc e) {
838             Debug.logError(e, "Invalid key", module);
839         } catch (InvalidAlgorithmParameterException JavaDoc e) {
840             Debug.logError(e, module);
841         }
842         return mwkCipher;
843     }
844
845     protected byte[] getPinCheckSum(byte[] pinBytes) {
846         byte[] checkSum = new byte[1];
847         checkSum[0] = 0;
848         for (int i = 0; i < pinBytes.length; i++) {
849             checkSum[0] += pinBytes[i];
850         }
851         return checkSum;
852     }
853
854     protected byte[] getRandomBytes(int length) {
855         Random JavaDoc rand = new Random JavaDoc();
856         byte[] randomBytes = new byte[length];
857         rand.nextBytes(randomBytes);
858         return randomBytes;
859     }
860
861     protected SecretKey getMwkKey() {
862         if (mwk == null) {
863             mwk = this.getDesEdeKey(getByteRange(getMwk(), 8, 24));
864         }
865
866         if (debug) {
867             Debug.log("Raw MWK : " + StringUtil.toHexString(getMwk()), module);
868             Debug.log("MWK : " + StringUtil.toHexString(mwk.getEncoded()), module);
869         }
870
871         return mwk;
872     }
873
874     protected SecretKey getKekKey() {
875         if (kek == null) {
876             kek = this.getDesEdeKey(getKek());
877         }
878
879         if (debug) {
880             Debug.log("Raw KEK : " + StringUtil.toHexString(getKek()), module);
881             Debug.log("KEK : " + StringUtil.toHexString(kek.getEncoded()), module);
882         }
883
884         return kek;
885     }
886
887     protected SecretKey getDesEdeKey(byte[] rawKey) {
888         SecretKeyFactory skf = null;
889         try {
890             skf = SecretKeyFactory.getInstance("DESede");
891         } catch (NoSuchAlgorithmException JavaDoc e) {
892             // should never happen since DESede is a standard algorithm
893
Debug.logError(e, module);
894             return null;
895         }
896
897         // load the raw key
898
if (rawKey.length > 0) {
899             DESedeKeySpec desedeSpec1 = null;
900             try {
901                 desedeSpec1 = new DESedeKeySpec(rawKey);
902             } catch (InvalidKeyException JavaDoc e) {
903                 Debug.logError(e, "Not a valid DESede key", module);
904                 return null;
905             }
906
907             // create the SecretKey Object
908
SecretKey key = null;
909             try {
910                 key = skf.generateSecret(desedeSpec1);
911             } catch (InvalidKeySpecException JavaDoc e) {
912                 Debug.logError(e, module);
913             }
914             return key;
915         } else {
916             throw new RuntimeException JavaDoc("No valid DESede key available");
917         }
918     }
919
920     protected byte[] getMwk() {
921         return StringUtil.fromHexString(this.getGenericValue().getString("workingKey"));
922     }
923
924     protected byte[] getKek() {
925         return StringUtil.fromHexString(this.getGenericValue().getString("exchangeKey"));
926     }
927
928     protected byte[] getPrivateKeyBytes() {
929         return StringUtil.fromHexString(this.getGenericValue().getString("privateKey"));
930     }
931
932     protected Map JavaDoc parseResponse(String JavaDoc response) {
933         if (debug) {
934             Debug.log("Raw Response : " + response, module);
935         }
936
937         // covert to all lowercase and trim off the html header
938
String JavaDoc subResponse = response.toLowerCase();
939         int firstIndex = subResponse.indexOf("<tr>");
940         int lastIndex = subResponse.lastIndexOf("</tr>");
941         subResponse = subResponse.substring(firstIndex, lastIndex);
942
943         // check for a history table
944
String JavaDoc history = null;
945         List JavaDoc historyMapList = null;
946         if (subResponse.indexOf("<table") > -1) {
947             int startHistory = subResponse.indexOf("<table");
948             int endHistory = subResponse.indexOf("</table>") + 8;
949             history = subResponse.substring(startHistory, endHistory);
950
951             // replace the subResponse string so it doesn't conflict
952
subResponse = StringUtil.replaceString(subResponse, history, "[_HISTORY_]");
953
954             // parse the history into a list of maps
955
historyMapList = this.parseHistoryResponse(history);
956         }
957
958         // replace all end rows with | this is the name delimiter
959
subResponse = StringUtil.replaceString(subResponse, "</tr>", "|");
960
961         // replace all </TD><TD> with = this is the value delimiter
962
subResponse = StringUtil.replaceString(subResponse, "</td><td>", "=");
963
964         // clean off a bunch of other useless stuff
965
subResponse = StringUtil.replaceString(subResponse, "<tr>", "");
966         subResponse = StringUtil.replaceString(subResponse, "<td>", "");
967         subResponse = StringUtil.replaceString(subResponse, "</td>", "");
968
969         // make the map
970
Map JavaDoc responseMap = StringUtil.strToMap(subResponse, true);
971
972         // add the raw html back in just in case we need it later
973
responseMap.put("_rawHtmlResponse", response);
974
975         // if we have a history add it back in
976
if (history != null) {
977             responseMap.put("_rawHistoryHtml", history);
978             responseMap.put("history", historyMapList);
979         }
980
981         if (debug) {
982             Debug.log("Response Map : " + responseMap, module);
983         }
984
985         return responseMap;
986     }
987
988     private List JavaDoc parseHistoryResponse(String JavaDoc response) {
989         if (debug) {
990             Debug.log("Raw History : " + response, module);
991         }
992
993         // covert to all lowercase and trim off the html header
994
String JavaDoc subResponse = response.toLowerCase();
995         int firstIndex = subResponse.indexOf("<tr>");
996         int lastIndex = subResponse.lastIndexOf("</tr>");
997         subResponse = subResponse.substring(firstIndex, lastIndex);
998
999         // clean up the html and replace the delimiters with '|'
1000
subResponse = StringUtil.replaceString(subResponse, "<td>", "");
1001        subResponse = StringUtil.replaceString(subResponse, "</td>", "|");
1002
1003        // test the string to make sure we have fields to parse
1004
String JavaDoc testResponse = StringUtil.replaceString(subResponse, "<tr>", "");
1005        testResponse = StringUtil.replaceString(testResponse, "</tr>", "");
1006        testResponse = StringUtil.replaceString(testResponse, "|", "");
1007        testResponse = testResponse.trim();
1008        if (testResponse.length() == 0) {
1009            if (debug) {
1010                Debug.log("History did not contain any fields, returning null", module);
1011            }
1012            return null;
1013        }
1014
1015        // break up the keys from the values
1016
int valueStart = subResponse.indexOf("</tr>");
1017        String JavaDoc keys = subResponse.substring(4, valueStart - 1);
1018        String JavaDoc values = subResponse.substring(valueStart + 9, subResponse.length() - 6);
1019
1020        // split sets of values up
1021
values = StringUtil.replaceString(values, "|</tr><tr>", "&");
1022        List JavaDoc valueList = StringUtil.split(values, "&");
1023
1024        // create a List of Maps for each set of values
1025
List JavaDoc valueMap = new ArrayList JavaDoc();
1026        for (int i = 0; i < valueList.size(); i++) {
1027            valueMap.add(StringUtil.createMap(StringUtil.split(keys, "|"), StringUtil.split((String JavaDoc) valueList.get(i), "|")));
1028        }
1029
1030        if (debug) {
1031            Debug.log("History Map : " + valueMap, module);
1032        }
1033
1034        return valueMap;
1035    }
1036
1037    /**
1038     * Returns a new byte[] from the offset of the defined byte[] with a specific number of bytes
1039     * @param bytes The byte[] to extract from
1040     * @param offset The starting postition
1041     * @param length The number of bytes to copy
1042     * @return a new byte[]
1043     */

1044    public static byte[] getByteRange(byte[] bytes, int offset, int length) {
1045        byte[] newBytes = new byte[length];
1046        for (int i = 0; i < length; i++) {
1047            newBytes[i] = bytes[offset + i];
1048        }
1049        return newBytes;
1050    }
1051
1052    /**
1053     * Copies a byte[] into another byte[] starting at a specific position
1054     * @param source byte[] to copy from
1055     * @param target byte[] coping into
1056     * @param position the position on target where source will be copied to
1057     * @return a new byte[]
1058     */

1059    public static byte[] copyBytes(byte[] source, byte[] target, int position) {
1060        byte[] newBytes = new byte[target.length + source.length];
1061        for (int i = 0, n = 0, x = 0; i < newBytes.length; i++) {
1062            if (i < position || i > (position + source.length - 2)) {
1063                newBytes[i] = target[n];
1064                n++;
1065            } else {
1066                for (; x < source.length; x++) {
1067                    newBytes[i] = source[x];
1068                    if (source.length - 1 > x) {
1069                        i++;
1070                    }
1071                }
1072            }
1073        }
1074        return newBytes;
1075    }
1076}
1077
Popular Tags