예제 #1
0
  /**
   * Sign the provided payment request.
   *
   * @param paymentRequest Payment request to sign, in its builder form.
   * @param certificateChain Certificate chain to send with the payment request, ordered from client
   *     certificate to root certificate. The root certificate itself may be omitted.
   * @param privateKey The key to sign with. Must match the public key from the first certificate of
   *     the certificate chain.
   */
  public static void signPaymentRequest(
      Protos.PaymentRequest.Builder paymentRequest,
      X509Certificate[] certificateChain,
      PrivateKey privateKey) {
    try {
      final Protos.X509Certificates.Builder certificates = Protos.X509Certificates.newBuilder();
      for (final Certificate certificate : certificateChain)
        certificates.addCertificate(ByteString.copyFrom(certificate.getEncoded()));

      paymentRequest.setPkiType("x509+sha256");
      paymentRequest.setPkiData(certificates.build().toByteString());
      paymentRequest.setSignature(ByteString.EMPTY);
      final Protos.PaymentRequest paymentRequestToSign = paymentRequest.build();

      final String algorithm;
      if ("RSA".equalsIgnoreCase(privateKey.getAlgorithm())) algorithm = "SHA256withRSA";
      else throw new IllegalStateException(privateKey.getAlgorithm());

      final Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(paymentRequestToSign.toByteArray());

      paymentRequest.setSignature(ByteString.copyFrom(signature.sign()));
    } catch (final GeneralSecurityException x) {
      // Should never happen so don't make users have to think about it.
      throw new RuntimeException(x);
    }
  }
예제 #2
0
 private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentRequestException {
   try {
     if (request == null) throw new PaymentRequestException("request cannot be null");
     if (request.getPaymentDetailsVersion() != 1)
       throw new PaymentRequestException.InvalidVersion(
           "Version 1 required. Received version " + request.getPaymentDetailsVersion());
     paymentRequest = request;
     if (!request.hasSerializedPaymentDetails())
       throw new PaymentRequestException("No PaymentDetails");
     paymentDetails =
         Protos.PaymentDetails.newBuilder()
             .mergeFrom(request.getSerializedPaymentDetails())
             .build();
     if (paymentDetails == null) throw new PaymentRequestException("Invalid PaymentDetails");
     if (!paymentDetails.hasNetwork()) params = MainNetParams.get();
     else params = NetworkParameters.fromPmtProtocolID(paymentDetails.getNetwork());
     if (params == null)
       throw new PaymentRequestException.InvalidNetwork(
           "Invalid network " + paymentDetails.getNetwork());
     if (paymentDetails.getOutputsCount() < 1)
       throw new PaymentRequestException.InvalidOutputs("No outputs");
     for (Protos.Output output : paymentDetails.getOutputsList()) {
       if (output.hasAmount()) totalValue = totalValue.add(BigInteger.valueOf(output.getAmount()));
     }
     // This won't ever happen in practice. It would only happen if the user provided outputs
     // that are obviously invalid. Still, we don't want to silently overflow.
     if (totalValue.compareTo(NetworkParameters.MAX_MONEY) > 0)
       throw new PaymentRequestException.InvalidOutputs("The outputs are way too big.");
   } catch (InvalidProtocolBufferException e) {
     throw new PaymentRequestException(e);
   }
 }
예제 #3
0
 private Protos.PaymentRequest minimalPaymentRequest() {
   Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
   paymentDetails.setTime(System.currentTimeMillis());
   Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
   paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
   return paymentRequest.build();
 }
예제 #4
0
 private static void sendPaymentRequest(String location, boolean verifyPki) {
   if (location.startsWith("http") || location.startsWith("defcoin")) {
     try {
       ListenableFuture<PaymentSession> future;
       if (location.startsWith("http")) {
         future = PaymentSession.createFromUrl(location, verifyPki);
       } else {
         BitcoinURI paymentRequestURI = new BitcoinURI(location);
         future = PaymentSession.createFromBitcoinUri(paymentRequestURI, verifyPki);
       }
       PaymentSession session = future.get();
       if (session != null) {
         send(session);
       } else {
         System.err.println("Server returned null session");
         System.exit(1);
       }
     } catch (PaymentRequestException e) {
       System.err.println("Error creating payment session " + e.getMessage());
       System.exit(1);
     } catch (BitcoinURIParseException e) {
       System.err.println("Invalid defcoin uri: " + e.getMessage());
       System.exit(1);
     } catch (InterruptedException e) {
       // Ignore.
     } catch (ExecutionException e) {
       throw new RuntimeException(e);
     }
   } else {
     // Try to open the payment request as a file.
     FileInputStream stream = null;
     try {
       File paymentRequestFile = new File(location);
       stream = new FileInputStream(paymentRequestFile);
     } catch (Exception e) {
       System.err.println("Failed to open file: " + e.getMessage());
       System.exit(1);
     }
     try {
       paymentRequest =
           org.bitcoin.protocols.payments.Protos.PaymentRequest.newBuilder()
               .mergeFrom(stream)
               .build();
     } catch (IOException e) {
       System.err.println("Failed to parse payment request from file " + e.getMessage());
       System.exit(1);
     }
     PaymentSession session = null;
     try {
       session = new PaymentSession(paymentRequest, verifyPki);
     } catch (PaymentRequestException e) {
       System.err.println("Error creating payment session " + e.getMessage());
       System.exit(1);
     }
     send(session);
   }
 }
예제 #5
0
 @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);
 }
예제 #6
0
 @Test
 public void testDefaults() throws Exception {
   Protos.Output.Builder outputBuilder =
       Protos.Output.newBuilder().setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
   Protos.PaymentDetails paymentDetails =
       Protos.PaymentDetails.newBuilder().setTime(time).addOutputs(outputBuilder).build();
   Protos.PaymentRequest paymentRequest =
       Protos.PaymentRequest.newBuilder()
           .setSerializedPaymentDetails(paymentDetails.toByteString())
           .build();
   MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest);
   assertEquals(BigInteger.ZERO, paymentSession.getValue());
   assertNull(paymentSession.getPaymentUrl());
   assertNull(paymentSession.getMemo());
 }
예제 #7
0
  /**
   * Create a payment request. You may want to sign the request using {@link #signPaymentRequest}.
   * Use {@link Protos.PaymentRequest.Builder#build} to get the actual payment request.
   *
   * @param params network parameters
   * @param outputs list of outputs to request coins to
   * @param memo arbitrary, user readable memo, or null if none
   * @param paymentUrl URL to send payment message to, or null if none
   * @param merchantData arbitrary merchant data, or null if none
   * @return created payment request, in its builder form
   */
  public static Protos.PaymentRequest.Builder createPaymentRequest(
      NetworkParameters params,
      List<Protos.Output> outputs,
      @Nullable String memo,
      @Nullable String paymentUrl,
      @Nullable byte[] merchantData) {
    final Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
    paymentDetails.setNetwork(params.getPaymentProtocolId());
    for (Protos.Output output : outputs) paymentDetails.addOutputs(output);
    if (memo != null) paymentDetails.setMemo(memo);
    if (paymentUrl != null) paymentDetails.setPaymentUrl(paymentUrl);
    if (merchantData != null) paymentDetails.setMerchantData(ByteString.copyFrom(merchantData));
    paymentDetails.setTime(Utils.currentTimeSeconds());

    final Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
    paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
    return paymentRequest;
  }
예제 #8
0
 private Protos.PaymentRequest newSimplePaymentRequest() {
   Protos.Output.Builder outputBuilder =
       Protos.Output.newBuilder()
           .setAmount(nanoCoins.longValue())
           .setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
   Protos.PaymentDetails paymentDetails =
       Protos.PaymentDetails.newBuilder()
           .setNetwork("test")
           .setTime(time)
           .setPaymentUrl(simplePaymentUrl)
           .addOutputs(outputBuilder)
           .setMemo(paymentRequestMemo)
           .setMerchantData(merchantData)
           .build();
   Protos.PaymentRequest paymentRequest =
       Protos.PaymentRequest.newBuilder()
           .setPaymentDetailsVersion(1)
           .setPkiType("none")
           .setSerializedPaymentDetails(paymentDetails.toByteString())
           .build();
   return paymentRequest;
 }
예제 #9
0
  /**
   * 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);
    }
  }
예제 #10
0
  /**
   * 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);
    }
  }
예제 #11
0
  public static PaymentIntent parsePaymentRequest(@Nonnull final byte[] serializedPaymentRequest)
      throws PaymentProtocolException {
    try {
      if (serializedPaymentRequest.length > 50000)
        throw new PaymentProtocolException(
            "payment request too big: " + serializedPaymentRequest.length);

      final Protos.PaymentRequest paymentRequest =
          Protos.PaymentRequest.parseFrom(serializedPaymentRequest);

      final String pkiName;
      final String pkiCaName;
      if (!"none".equals(paymentRequest.getPkiType())) {
        final KeyStore keystore = new TrustStoreLoader.DefaultTrustStoreLoader().getKeyStore();
        final PkiVerificationData verificationData =
            PaymentProtocol.verifyPaymentRequestPki(paymentRequest, keystore);
        pkiName = verificationData.displayName;
        pkiCaName = verificationData.rootAuthorityName;
      } else {
        pkiName = null;
        pkiCaName = null;
      }

      final PaymentSession paymentSession = PaymentProtocol.parsePaymentRequest(paymentRequest);

      if (paymentSession.isExpired())
        throw new PaymentProtocolException.Expired(
            "payment details expired: current time "
                + new Date()
                + " after expiry time "
                + paymentSession.getExpires());

      if (!paymentSession.getNetworkParameters().equals(Constants.NETWORK_PARAMETERS))
        throw new PaymentProtocolException.InvalidNetwork(
            "cannot handle payment request network: " + paymentSession.getNetworkParameters());

      final ArrayList<PaymentIntent.Output> outputs = new ArrayList<PaymentIntent.Output>(1);
      for (final PaymentProtocol.Output output : paymentSession.getOutputs())
        outputs.add(PaymentIntent.Output.valueOf(output));

      final String memo = paymentSession.getMemo();

      final String paymentUrl = paymentSession.getPaymentUrl();

      final byte[] merchantData = paymentSession.getMerchantData();

      final byte[] paymentRequestHash =
          Hashing.sha256().hashBytes(serializedPaymentRequest).asBytes();

      final PaymentIntent paymentIntent =
          new PaymentIntent(
              PaymentIntent.Standard.BIP70,
              pkiName,
              pkiCaName,
              outputs.toArray(new PaymentIntent.Output[0]),
              memo,
              paymentUrl,
              merchantData,
              null,
              paymentRequestHash);

      if (paymentIntent.hasPaymentUrl() && !paymentIntent.isSupportedPaymentUrl())
        throw new PaymentProtocolException.InvalidPaymentURL(
            "cannot handle payment url: " + paymentIntent.paymentUrl);

      return paymentIntent;
    } catch (final InvalidProtocolBufferException x) {
      throw new PaymentProtocolException(x);
    } catch (final UninitializedMessageException x) {
      throw new PaymentProtocolException(x);
    } catch (final FileNotFoundException x) {
      throw new RuntimeException(x);
    } catch (final KeyStoreException x) {
      throw new RuntimeException(x);
    }
  }