/** * Uses the provided PKI method to find the corresponding public key and verify the provided * signature. * * @param paymentRequest Payment request to verify. * @param trustStore KeyStore of trusted root certificate authorities. * @return verification data, or null if no PKI method was specified in the {@link * Protos.PaymentRequest}. * @throws PaymentProtocolException if payment request could not be verified. */ @Nullable public static PkiVerificationData verifyPaymentRequestPki( Protos.PaymentRequest paymentRequest, KeyStore trustStore) throws PaymentProtocolException { List<X509Certificate> certs = null; try { final String pkiType = paymentRequest.getPkiType(); if ("none".equals(pkiType)) // Nothing to verify. Everything is fine. Move along. return null; String algorithm; if ("x509+sha256".equals(pkiType)) algorithm = "SHA256withRSA"; else if ("x509+sha1".equals(pkiType)) algorithm = "SHA1withRSA"; else throw new PaymentProtocolException.InvalidPkiType("Unsupported PKI type: " + pkiType); Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData()); if (protoCerts.getCertificateCount() == 0) throw new PaymentProtocolException.InvalidPkiData( "No certificates provided in message: server config error"); // Parse the certs and turn into a certificate chain object. Cert factories can parse both DER // and base64. // The ordering of certificates is defined by the payment protocol spec to be the same as what // the Java // crypto API requires - convenient! CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); certs = Lists.newArrayList(); for (ByteString bytes : protoCerts.getCertificateList()) certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput())); CertPath path = certificateFactory.generateCertPath(certs); // Retrieves the most-trusted CAs from keystore. PKIXParameters params = new PKIXParameters(trustStore); // Revocation not supported in the current version. params.setRevocationEnabled(false); // Now verify the certificate chain is correct and trusted. This let's us get an identity // linked pubkey. CertPathValidator validator = CertPathValidator.getInstance("PKIX"); PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params); PublicKey publicKey = result.getPublicKey(); // OK, we got an identity, now check it was used to sign this message. Signature signature = Signature.getInstance(algorithm); // Note that we don't use signature.initVerify(certs.get(0)) here despite it being the most // obvious // way to set it up, because we don't care about the constraints specified on the // certificates: any // cert that links a key to a domain name or other identity will do for us. 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 PaymentProtocolException.PkiVerificationException( "Invalid signature, this payment request is not valid."); // Signature verifies, get the names from the identity we just verified for presentation to // the user. final X509Certificate cert = certs.get(0); String displayName = X509Utils.getDisplayNameFromCertificate(cert, true); if (displayName == null) throw new PaymentProtocolException.PkiVerificationException( "Could not extract name from certificate"); // Everything is peachy. Return some useful data to the caller. return new PkiVerificationData(displayName, publicKey, result.getTrustAnchor()); } catch (InvalidProtocolBufferException e) { // Data structures are malformed. throw new PaymentProtocolException.InvalidPkiData(e); } catch (CertificateException e) { // The X.509 certificate data didn't parse correctly. throw new PaymentProtocolException.PkiVerificationException(e); } catch (NoSuchAlgorithmException e) { // Should never happen so don't make users have to think about it. PKIX is always present. throw new RuntimeException(e); } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch (CertPathValidatorException e) { // The certificate chain isn't known or trusted, probably, the server is using an SSL root we // don't // know about and the user needs to upgrade to a new version of the software (or import a root // cert). throw new PaymentProtocolException.PkiVerificationException(e, certs); } catch (InvalidKeyException e) { // Shouldn't happen if the certs verified correctly. throw new PaymentProtocolException.PkiVerificationException(e); } catch (SignatureException e) { // Something went wrong during hashing (yes, despite the name, this does not mean the sig was // invalid). throw new PaymentProtocolException.PkiVerificationException(e); } catch (KeyStoreException e) { throw new RuntimeException(e); } }
public void performTest() throws Exception { CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC"); // initialise CertStore X509Certificate rootCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(CertPathTest.rootCertBin)); X509Certificate interCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(CertPathTest.interCertBin)); X509Certificate finalCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(CertPathTest.finalCertBin)); X509CRL rootCrl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(CertPathTest.rootCrlBin)); X509CRL interCrl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(CertPathTest.interCrlBin)); List list = new ArrayList(); list.add(rootCert); list.add(interCert); list.add(finalCert); list.add(rootCrl); list.add(interCrl); CollectionCertStoreParameters ccsp = new CollectionCertStoreParameters(list); CertStore store = CertStore.getInstance("Collection", ccsp, "BC"); Date validDate = new Date(rootCrl.getThisUpdate().getTime() + 60 * 60 * 1000); // validating path List certchain = new ArrayList(); certchain.add(finalCert); certchain.add(interCert); CertPath cp = CertificateFactory.getInstance("X.509", "BC").generateCertPath(certchain); Set trust = new HashSet(); trust.add(new TrustAnchor(rootCert, null)); CertPathValidator cpv = CertPathValidator.getInstance("PKIX", "BC"); PKIXParameters param = new PKIXParameters(trust); param.addCertStore(store); param.setDate(validDate); MyChecker checker = new MyChecker(); param.addCertPathChecker(checker); PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, param); PolicyNode policyTree = result.getPolicyTree(); PublicKey subjectPublicKey = result.getPublicKey(); if (checker.getCount() != 2) { fail("checker not evaluated for each certificate"); } if (!subjectPublicKey.equals(finalCert.getPublicKey())) { fail("wrong public key returned"); } // // invalid path containing a valid one test // try { // initialise CertStore rootCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(AC_RAIZ_ICPBRASIL)); interCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(AC_PR)); finalCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(schefer)); list = new ArrayList(); list.add(rootCert); list.add(interCert); list.add(finalCert); ccsp = new CollectionCertStoreParameters(list); store = CertStore.getInstance("Collection", ccsp); validDate = new Date(finalCert.getNotBefore().getTime() + 60 * 60 * 1000); // validating path certchain = new ArrayList(); certchain.add(finalCert); certchain.add(interCert); cp = CertificateFactory.getInstance("X.509", "BC").generateCertPath(certchain); trust = new HashSet(); trust.add(new TrustAnchor(rootCert, null)); cpv = CertPathValidator.getInstance("PKIX", "BC"); param = new PKIXParameters(trust); param.addCertStore(store); param.setRevocationEnabled(false); param.setDate(validDate); result = (PKIXCertPathValidatorResult) cpv.validate(cp, param); policyTree = result.getPolicyTree(); subjectPublicKey = result.getPublicKey(); fail("Invalid path validated"); } catch (Exception e) { if (!(e instanceof CertPathValidatorException && e.getMessage().startsWith("Could not validate certificate signature."))) { fail("unexpected exception", e); } } checkCircProcessing(); checkPolicyProcessingAtDomainMatch(); validateWithExtendedKeyUsage(); testEmptyPath(); }
/** * Uses the provided PKI method to find the corresponding public key and verify the provided * signature. Returns null if no PKI method was specified in the {@link Protos.PaymentRequest}. */ public @Nullable PkiVerificationData verifyPki() throws PaymentRequestException { try { if (pkiVerificationData != null) return pkiVerificationData; if (paymentRequest.getPkiType().equals("none")) // Nothing to verify. Everything is fine. Move along. 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"); // Parse the certs and turn into a certificate chain object. Cert factories can parse both DER // and base64. // The ordering of certificates is defined by the payment protocol spec to be the same as what // the Java // crypto API requires - convenient! 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); // Retrieves the most-trusted CAs from keystore. PKIXParameters params = new PKIXParameters(createKeyStore(trustStorePath)); // Revocation not supported in the current version. params.setRevocationEnabled(false); // Now verify the certificate chain is correct and trusted. This let's us get an identity // linked pubkey. CertPathValidator validator = CertPathValidator.getInstance("PKIX"); PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params); PublicKey publicKey = result.getPublicKey(); // OK, we got an identity, now check it was used to sign this message. Signature signature = Signature.getInstance(algorithm); // Note that we don't use signature.initVerify(certs.get(0)) here despite it being the most // obvious // way to set it up, because we don't care about the constraints specified on the // certificates: any // cert that links a key to a domain name or other identity will do for us. 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."); // Signature verifies, get the names from the identity we just verified for presentation to // the user. X500Principal principal = certs.get(0).getSubjectX500Principal(); // At this point the Java crypto API falls flat on its face and dies - there's no clean way to // get the // different parts of the certificate name except for parsing the string. That's hard because // of various // custom escaping rules and the usual crap. So, use Bouncy Castle to re-parse the string into // binary form // again and then look for the names we want. Fail! 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"); // Everything is peachy. Return some useful data to the caller. PkiVerificationData data = new PkiVerificationData(entityName, orgName, publicKey, result.getTrustAnchor()); // Cache the result so we don't have to re-verify if this method is called again. pkiVerificationData = data; return data; } catch (InvalidProtocolBufferException e) { // Data structures are malformed. throw new PaymentRequestException.InvalidPkiData(e); } catch (CertificateException e) { // The X.509 certificate data didn't parse correctly. throw new PaymentRequestException.PkiVerificationException(e); } catch (NoSuchAlgorithmException e) { // Should never happen so don't make users have to think about it. PKIX is always present. throw new RuntimeException(e); } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch (CertPathValidatorException e) { // The certificate chain isn't known or trusted, probably, the server is using an SSL root we // don't // know about and the user needs to upgrade to a new version of the software (or import a root // cert). throw new PaymentRequestException.PkiVerificationException(e); } catch (InvalidKeyException e) { // Shouldn't happen if the certs verified correctly. throw new PaymentRequestException.PkiVerificationException(e); } catch (SignatureException e) { // Something went wrong during hashing (yes, despite the name, this does not mean the sig was // invalid). throw new PaymentRequestException.PkiVerificationException(e); } catch (IOException e) { throw new PaymentRequestException.PkiVerificationException(e); } catch (KeyStoreException e) { throw new RuntimeException(e); } }