PaymentSession { public @Nullable PkiVerificationData verifyPki() throws PaymentRequestException { try { if (pkiVerificationData != null) return pkiVerificationData; if (paymentRequest.getPkiType().equals("none")) return null; String algorithm; if (paymentRequest.getPkiType().equals("x509+sha256")) algorithm = "SHA256withRSA"; else if (paymentRequest.getPkiType().equals("x509+sha1")) algorithm = "SHA1withRSA"; else throw new PaymentRequestException.InvalidPkiType("Unsupported PKI type: " + paymentRequest.getPkiType()); Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData()); if (protoCerts.getCertificateCount() == 0) throw new PaymentRequestException.InvalidPkiData("No certificates provided in message: server config error"); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); List<X509Certificate> certs = Lists.newArrayList(); for (ByteString bytes : protoCerts.getCertificateList()) certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput())); CertPath path = certificateFactory.generateCertPath(certs); PKIXParameters params = new PKIXParameters(createKeyStore(trustStorePath)); params.setRevocationEnabled(false); CertPathValidator validator = CertPathValidator.getInstance("PKIX"); PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params); PublicKey publicKey = result.getPublicKey(); Signature signature = Signature.getInstance(algorithm); signature.initVerify(publicKey); Protos.PaymentRequest.Builder reqToCheck = paymentRequest.toBuilder(); reqToCheck.setSignature(ByteString.EMPTY); signature.update(reqToCheck.build().toByteArray()); if (!signature.verify(paymentRequest.getSignature().toByteArray())) throw new PaymentRequestException.PkiVerificationException("Invalid signature, this payment request is not valid."); X500Principal principal = certs.get(0).getSubjectX500Principal(); org.spongycastle.asn1.x500.X500Name name = new X500Name(principal.getName()); String entityName = null, orgName = null; for (RDN rdn : name.getRDNs()) { AttributeTypeAndValue pair = rdn.getFirst(); if (pair.getType().equals(RFC4519Style.cn)) entityName = ((ASN1String)pair.getValue()).getString(); else if (pair.getType().equals(RFC4519Style.o)) orgName = ((ASN1String)pair.getValue()).getString(); } if (entityName == null && orgName == null) throw new PaymentRequestException.PkiVerificationException("Invalid certificate, no CN or O fields"); PkiVerificationData data = new PkiVerificationData(entityName, orgName, publicKey, result.getTrustAnchor()); pkiVerificationData = data; return data; } catch (InvalidProtocolBufferException e) { throw new PaymentRequestException.InvalidPkiData(e); } catch (CertificateException e) { throw new PaymentRequestException.PkiVerificationException(e); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch (CertPathValidatorException e) { throw new PaymentRequestException.PkiVerificationException(e); } catch (InvalidKeyException e) { throw new PaymentRequestException.PkiVerificationException(e); } catch (SignatureException e) { throw new PaymentRequestException.PkiVerificationException(e); } catch (IOException e) { throw new PaymentRequestException.PkiVerificationException(e); } catch (KeyStoreException e) { throw new RuntimeException(e); } } PaymentSession(Protos.PaymentRequest request);  PaymentSession(Protos.PaymentRequest request, boolean verifyPki);  PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final String trustStorePath); static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri); static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki); static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki, @Nullable final String trustStorePath); static ListenableFuture<PaymentSession> createFromUrl(final String url); static ListenableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki); static ListenableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki, @Nullable final String trustStorePath); @Nullable String getMemo(); BigInteger getValue(); Date getDate(); boolean isExpired(); @Nullable String getPaymentUrl(); Wallet.SendRequest getSendRequest(); @Nullable ListenableFuture<Ack> sendPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo); @Nullable Protos.Payment getPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo); @Nullable PkiVerificationData verifyPki(); Protos.PaymentRequest getPaymentRequest(); Protos.PaymentDetails getPaymentDetails(); public PkiVerificationData pkiVerificationData; }
@Test public void testPkiVerification() throws Exception { InputStream in = getClass().getResourceAsStream("pki_test.bitcoinpaymentrequest"); Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder().mergeFrom(in).build(); MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest); PaymentSession.PkiVerificationData pkiData = paymentSession.verifyPki(); assertEquals("www.bitcoincore.org", pkiData.name); assertEquals("The USERTRUST Network, Salt Lake City, US", pkiData.rootAuthorityName); }