| 1 25 package org.ofbiz.accounting.thirdparty.valuelink; 26 27 import java.math.BigInteger ; 28 import java.security.InvalidAlgorithmParameterException ; 29 import java.security.InvalidKeyException ; 30 import java.security.KeyFactory ; 31 import java.security.KeyPair ; 32 import java.security.KeyPairGenerator ; 33 import java.security.MessageDigest ; 34 import java.security.NoSuchAlgorithmException ; 35 import java.security.PrivateKey ; 36 import java.security.PublicKey ; 37 import java.security.spec.InvalidKeySpecException ; 38 import java.text.DecimalFormat ; 39 import java.text.ParseException ; 40 import java.text.SimpleDateFormat ; 41 import java.util.ArrayList ; 42 import java.util.Date ; 43 import java.util.HashMap ; 44 import java.util.List ; 45 import java.util.Map ; 46 import java.util.Properties ; 47 import java.util.Random ; 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 83 public class ValueLinkApi { 84 85 public static final String module = ValueLinkApi.class.getName(); 86 87 private static Map objectCache = new HashMap (); 89 90 protected GenericDelegator delegator = null; 92 protected Properties props = null; 93 protected SecretKey kek = null; 94 protected SecretKey mwk = null; 95 protected String merchantId = null; 96 protected String terminalId = null; 97 protected Long mwkIndex = null; 98 protected boolean debug = false; 99 100 protected ValueLinkApi() {} 101 protected ValueLinkApi(GenericDelegator delegator, Properties props) { 102 String mId = (String ) props.get("payment.valuelink.merchantId"); 103 String tId = (String ) 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 ) 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 126 public static ValueLinkApi getInstance(GenericDelegator delegator, Properties props, boolean reload) { 127 String merchantId = (String ) props.get("payment.valuelink.merchantId"); 128 if (props == null) { 129 throw new IllegalArgumentException ("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 ("Runtime problems with ValueLinkApi; unable to create instance"); 145 } 146 147 return api; 148 } 149 150 156 public static ValueLinkApi getInstance(GenericDelegator delegator, Properties props) { 157 return getInstance(delegator, props, false); 158 } 159 160 165 public String encryptPin(String pin) { 166 Cipher mwkCipher = this.getCipher(this.getMwkKey(), Cipher.ENCRYPT_MODE); 168 169 byte[] pinBytes = pin.getBytes(); 171 172 byte[] random = this.getRandomBytes(7); 174 175 byte[] checkSum = this.getPinCheckSum(pinBytes); 177 178 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 String encryptedEanHex = null; 191 try { 192 byte[] encryptedEan = mwkCipher.doFinal(eanBlock); 193 encryptedEanHex = StringUtil.toHexString(encryptedEan); 194 } catch (IllegalStateException 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 214 public String decryptPin(String pin) { 215 Cipher mwkCipher = this.getCipher(this.getMwkKey(), Cipher.DECRYPT_MODE); 217 218 String decryptedPinString = null; 220 try { 221 byte[] decryptedEan = mwkCipher.doFinal(StringUtil.fromHexString(pin)); 222 byte[] decryptedPin = getByteRange(decryptedEan, 8, 8); 223 decryptedPinString = new String (decryptedPin); 224 } catch (IllegalStateException 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 245 public Map send(Map request) throws HttpClientException { 246 return send((String ) props.get("payment.valuelink.url"), request); 247 } 248 249 256 public Map send(String url, Map request) throws HttpClientException { 257 if (debug) { 258 Debug.log("Request : " + url + " / " + request, module); 259 } 260 261 String timeoutString = (String ) props.get("payment.valuelink.timeout"); 263 int timeout = 34; 264 try { 265 timeout = Integer.parseInt(timeoutString); 266 } catch (NumberFormatException e) { 267 Debug.logError(e, "Unable to set timeout to " + timeoutString + " using default " + timeout); 268 } 269 270 HttpClient client = new HttpClient(url, request); 272 client.setTimeout(timeout * 1000); 273 client.setDebug(debug); 274 275 client.setClientCertificateAlias((String ) props.get("payment.valuelink.certificateAlias")); 276 String response = client.post(); 277 278 return this.parseResponse(response); 280 } 281 282 285 public StringBuffer outputKeyCreation(boolean kekOnly, String kekTest) { 286 return this.outputKeyCreation(0, kekOnly, kekTest); 287 } 288 289 private StringBuffer outputKeyCreation(int loop, boolean kekOnly, String kekTest) { 290 StringBuffer buf = new StringBuffer (); 291 loop++; 292 293 if (loop > 100) { 294 throw new IllegalStateException ("Unable to create 128 byte keys in 100 tries"); 296 } 297 298 DHPrivateKey privateKey = null; 300 DHPublicKey publicKey = null; 301 302 if (!kekOnly) { 303 KeyPair keyPair = null; 304 try { 305 keyPair = this.createKeys(); 306 } catch (NoSuchAlgorithmException e) { 307 Debug.logError(e, module); 308 } catch (InvalidAlgorithmParameterException e) { 309 Debug.logError(e, module); 310 } catch (InvalidKeySpecException 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 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 try { 329 privateKey = (DHPrivateKey) this.getPrivateKey(); 330 } catch (Exception e) { 331 Debug.logError(e, module); 332 } 333 } 334 335 byte[] kekBytes = null; 337 try { 338 kekBytes = this.generateKek(privateKey); 339 } catch (NoSuchAlgorithmException e) { 340 Debug.logError(e, module); 341 } catch (InvalidKeySpecException e) { 342 Debug.logError(e, module); 343 } catch (InvalidKeyException e) { 344 Debug.logError(e, module); 345 } 346 347 SecretKey loadedKek = this.getDesEdeKey(kekBytes); 349 byte[] loadKekBytes = loadedKek.getEncoded(); 350 351 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 try { 361 kekTestC = cipher.doFinal(kekTestB); 362 } catch (Exception e) { 363 Debug.logError(e, module); 364 } 365 366 if (!kekOnly) { 367 BigInteger y = publicKey.getY(); 369 byte[] yBytes = y.toByteArray(); 370 String 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 BigInteger x = privateKey.getX(); 377 byte[] xBytes = x.toByteArray(); 378 String 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 byte[] privateBytes = privateKey.getEncoded(); 385 String 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 416 public KeyPair createKeys() throws NoSuchAlgorithmException , InvalidAlgorithmParameterException , InvalidKeySpecException { 417 DHPublicKey publicKey = (DHPublicKey) this.getValueLinkPublicKey(); 419 DHParameterSpec dhParamSpec = publicKey.getParams(); 420 422 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH"); 424 keyGen.initialize(dhParamSpec); 425 KeyPair keyPair = keyGen.generateKeyPair(); 426 427 return keyPair; 428 } 429 430 438 public byte[] generateKek(PrivateKey privateKey) throws NoSuchAlgorithmException , InvalidKeySpecException , InvalidKeyException { 439 PublicKey vlPublic = this.getValueLinkPublicKey(); 441 442 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 MessageDigest 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 472 public PublicKey getValueLinkPublicKey() throws NoSuchAlgorithmException , InvalidKeySpecException { 473 String publicValue = (String ) props.get("payment.valuelink.publicValue"); 475 byte[] publicKeyBytes = StringUtil.fromHexString(publicValue); 476 477 DHParameterSpec dhParamSpec = this.getDHParameterSpec(); 479 480 KeyFactory keyFactory = KeyFactory.getInstance("DH"); 482 BigInteger publicKeyInt = new BigInteger (publicKeyBytes); 483 DHPublicKeySpec dhPublicSpec = new DHPublicKeySpec(publicKeyInt, dhParamSpec.getP(), dhParamSpec.getG()); 484 PublicKey vlPublic = keyFactory.generatePublic(dhPublicSpec); 485 486 return vlPublic; 487 } 488 489 493 public PrivateKey getPrivateKey() throws InvalidKeySpecException , NoSuchAlgorithmException { 494 byte[] privateKeyBytes = this.getPrivateKeyBytes(); 495 496 DHParameterSpec dhParamSpec = this.getDHParameterSpec(); 498 499 KeyFactory keyFactory = KeyFactory.getInstance("DH"); 501 BigInteger privateKeyInt = new BigInteger (privateKeyBytes); 502 DHPrivateKeySpec dhPrivateSpec = new DHPrivateKeySpec(privateKeyInt, dhParamSpec.getP(), dhParamSpec.getG()); 503 PrivateKey privateKey = keyFactory.generatePrivate(dhPrivateSpec); 504 505 return privateKey; 506 } 507 508 512 public byte[] generateMwk() { 513 KeyGenerator keyGen = null; 514 try { 515 keyGen = KeyGenerator.getInstance("DES"); 516 } catch (NoSuchAlgorithmException e) { 517 Debug.logError(e, module); 518 } 519 520 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 try { 531 if (DESKeySpec.isWeak(des1.getEncoded(), 0) || DESKeySpec.isWeak(des2.getEncoded(), 0)) { 532 return generateMwk(); 533 } 534 } catch (Exception 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 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 e) { 561 Debug.logError(e, module); 562 } 563 DESedeKeySpec desedeSpec2 = null; 564 try { 565 desedeSpec2 = new DESedeKeySpec(desBytes); 566 } catch (InvalidKeyException e) { 567 Debug.logError(e, module); 568 } 569 if (skf1 != null && desedeSpec2 != null) { 570 try { 571 mwk = skf1.generateSecret(desedeSpec2); 572 } catch (InvalidKeySpecException 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 588 public byte[] generateMwk(SecretKey mwkdes3) { 589 byte[] zeros = { 0, 0, 0, 0, 0, 0, 0, 0 }; 591 592 byte[] random = new byte[8]; 594 Random ran = new Random (); 595 ran.nextBytes(random); 596 597 598 Cipher cipher = this.getCipher(mwkdes3, Cipher.ENCRYPT_MODE); 600 601 byte[] encryptedZeros = new byte[0]; 603 try { 604 encryptedZeros = cipher.doFinal(zeros); 605 } catch (IllegalStateException 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 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 632 public byte[] encryptViaKek(byte[] content) { 633 return cryptoViaKek(content, Cipher.ENCRYPT_MODE); 634 } 635 636 641 public byte[] decryptViaKek(byte[] content) { 642 return cryptoViaKek(content, Cipher.DECRYPT_MODE); 643 } 644 645 649 public String getDateString() { 650 String format = (String ) props.get("payment.valuelink.timestamp"); 651 SimpleDateFormat sdf = new SimpleDateFormat (format); 652 return sdf.format(new Date ()); 653 } 654 655 659 public Long 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 680 public String getAmount(Double amount) { 681 if (amount == null) { 682 return "0.00"; 683 } 684 String currencyFormat = UtilProperties.getPropertyValue("general.properties", "currency.decimal.format", "##0.00"); 685 DecimalFormat formatter = new DecimalFormat (currencyFormat); 686 String amountString = formatter.format(amount.doubleValue()); 687 Double newAmount = null; 688 try { 689 newAmount = new Double (formatter.parse(amountString).doubleValue()); 690 } catch (ParseException e) { 691 Debug.logError(e, "Unable to parse amount Double"); 692 } 693 694 String formattedString = null; 695 if (newAmount != null) { 696 double amountDouble = newAmount.doubleValue() * 100; 697 formattedString = new String (new Integer (new Double (amountDouble).intValue()).toString()); 698 } 699 return formattedString; 700 } 701 702 707 public Double getAmount(String amount) { 708 if (amount == null) { 709 return new Double (0.00); 710 } 711 Double doubleAmount = new Double (amount); 712 return new Double (doubleAmount.doubleValue() / 100); 713 } 714 715 public String getCurrency(String currency) { 716 return "840"; } 718 719 724 public Map getInitialRequestMap(Map context) { 725 Map request = new HashMap (); 726 727 request.put("MerchID", merchantId + terminalId); 729 request.put("AltMerchNo", props.get("payment.valuelink.altMerchantId")); 730 731 String modes = (String ) props.get("payment.valuelink.modes"); 733 if (modes != null && modes.length() > 0) { 734 request.put("Modes", modes); 735 } 736 737 String merchTime = (String ) context.get("MerchTime"); 739 |