Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Remove requirement for bouncycastle #55

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ jobs:
- name: Install dependencies & run tests
env:
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
PUBLIC_KEY: ${{ secrets.PUBLIC_KEY }}
run: mvn install
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@ Add the `gr4vy-java` dependency to your pom.xml:
<dependency>
<groupId>com.github.gr4vy</groupId>
<artifactId>gr4vy-java</artifactId>
<version>0.28.0</version>
<version>0.31.0</version>
</dependency>
```

## Getting Started

To make your first API call, you will need to [request](https://gr4vy.com) a
Gr4vy instance to be set up. Please contact our sales team for a demo. Please ensure
that you have the latest version of com.squareup.okhttp3
that you have the latest version of com.squareup.okhttp3.

Once you have been set up with a Gr4vy account you will need to head over to the
**Integrations** panel and generate a private key. We recommend storing this key
in a secure location but in this code sample we simply read the file from disk.

Due to a restriction in Java 17+ the EC public key must first be generated from the private key by running:
```
openssl ec -in private_key.pem -pubout -out public_key.pem
```

Import Gr4vy:
```java
import com.gr4vy.sdk.*;
Expand All @@ -42,6 +47,7 @@ Call the API:
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]")
.privateKeyLocation("private_key.pem")
.publicKeyLocation("public_key.pem")
.build();

try {
Expand All @@ -64,12 +70,14 @@ The SDK defaults the environment to "sandbox", to send transactions to productio
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]")
.privateKeyLocation("private_key.pem")
.publicKeyLocation("public_key.pem")
.environment("sandbox")
.build();

Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]")
.privateKeyLocation("private_key.pem")
.publicKeyLocation("public_key.pem")
.environment("production")
.build();

Expand All @@ -83,6 +91,7 @@ In a multi-merchant environment, the merchant account ID can be set after the SD
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]")
.privateKeyLocation("private_key.pem")
.publicKeyLocation("public_key.pem")
.merchantAccountId("default")
.build();
```
Expand All @@ -97,6 +106,7 @@ Embed.
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]")
.privateKeyLocation("private_key.pem")
.publicKeyLocation("public_key.pem")
.build();

Map<String, Object> embed = new HashMap<String, Object>();
Expand All @@ -117,6 +127,7 @@ needs to be created before it can be used in this way.
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]")
.privateKeyLocation("private_key.pem")
.publicKeyLocation("public_key.pem")
.build();

BuyerRequest buyer = new BuyerRequest();
Expand All @@ -139,6 +150,8 @@ The following fields can be optionally set using the builder:
.gr4vyId("[YOUR_GR4VY_ID]") // required
.privateKeyLocation("private_key.pem") // conditional
.privateKeyString("-----BEGIN PRIVATE KEY-----\n...") // conditional
.publicKeyLocation("public_key.pem") // conditional
.publicKeyString("-----BEGIN PUBLIC KEY-----\n...") // conditional
.environment("sandbox") // optional, defaults to sandbox
.host(null) // optional - allows setting a custom host
.client(null) // optional - allows setting the http client
Expand Down
7 changes: 1 addition & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<artifactId>gr4vy</artifactId>
<packaging>jar</packaging>
<name>gr4vy</name>
<version>0.30.0</version>
<version>0.31.0</version>
<url>https://gr4vy.com</url>
<description>Gr4vy Java SDK</description>

Expand Down Expand Up @@ -193,11 +193,6 @@
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.37.2</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.69</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
Expand Down
82 changes: 47 additions & 35 deletions src/main/java/com/gr4vy/sdk/Gr4vyClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.text.ParseException;
import java.util.Date;
Expand All @@ -22,22 +16,16 @@

import java.time.Duration;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;

import com.google.gson.Gson;
import com.gr4vy.api.model.*;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

Expand All @@ -53,6 +41,8 @@ public class Gr4vyClient {
private OkHttpClient okClient;
private String privateKeyLocation;
private String privateKeyString;
private String publicKeyLocation;
private String publicKeyString;
private String merchantAccountId;
private Duration connectTimeout;
private Duration writeTimeout;
Expand All @@ -72,6 +62,8 @@ public Gr4vyClient(Builder builder) {
this.okClient = builder.okClient;
this.privateKeyLocation = builder.privateKeyLocation;
this.privateKeyString = builder.privateKeyString;
this.publicKeyLocation = builder.publicKeyLocation;
this.publicKeyString = builder.publicKeyString;
this.merchantAccountId = builder.merchantAccountId;
this.connectTimeout = builder.connectTimeout;
this.writeTimeout = builder.writeTimeout;
Expand All @@ -85,6 +77,8 @@ public static class Builder {
private OkHttpClient okClient = null;
private String privateKeyLocation = null;
private String privateKeyString = null;
private String publicKeyLocation = null;
private String publicKeyString = null;
private String merchantAccountId = "default";
private Duration connectTimeout = Duration.ofSeconds(10);
private Duration writeTimeout = Duration.ofSeconds(10);
Expand Down Expand Up @@ -115,6 +109,14 @@ public Builder privateKeyLocation(String privateKeyLocation) {
this.privateKeyLocation = privateKeyLocation;
return this;
}
public Builder publicKeyString(String publicKeyString) {
this.publicKeyString = publicKeyString;
return this;
}
public Builder publicKeyLocation(String publicKeyLocation) {
this.publicKeyLocation = publicKeyLocation;
return this;
}
public Builder merchantAccountId(String merchantAccountId) {
this.merchantAccountId = merchantAccountId;
return this;
Expand Down Expand Up @@ -198,20 +200,11 @@ public String getToken(String key, String[] scopes, Map<String, Object> embed, U
if (cachedToken != null && cachedToken.isValid()) {
return cachedToken.getToken();
}
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

Reader reader = new StringReader(key);
PEMParser pemParser = new PEMParser(reader);
PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
pemParser.close();

ECPrivateKey ecKey = (ECPrivateKey) new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo);
ECKey e = new ECKey.Builder(Curve.P_521, publicFromPrivate(ecKey))
.privateKey(ecKey)
.build();
JWK jwk = ECKey.parseFromPEMEncodedObjects(key);

String keyId = e.computeThumbprint("SHA256").toString();
JWSSigner signer = new ECDSASigner(e);
String keyId = jwk.computeThumbprint("SHA256").toString();
JWSSigner signer = new ECDSASigner((ECKey) jwk);

Date now = new Date();
Date expire = new Date(now.getTime() + tokenExpiryMillis);
Expand All @@ -220,7 +213,7 @@ public String getToken(String key, String[] scopes, Map<String, Object> embed, U
.jwtID(UUID.randomUUID().toString())
.notBeforeTime(now)
.issueTime(now)
.issuer("Gr4vy SDK 0.22.0 - Java")
.issuer("Gr4vy SDK 0.31.0 - Java")
.expirationTime(expire)
.claim("scopes", scopes);

Expand All @@ -244,16 +237,35 @@ public String getToken(String key, String[] scopes, Map<String, Object> embed, U
return token;
}

private static ECPublicKey publicFromPrivate(ECPrivateKey key) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp521r1");
org.bouncycastle.math.ec.ECPoint Q = ecSpec.getG().multiply(((org.bouncycastle.jce.interfaces.ECPrivateKey) key).getD());
ECPublicKeySpec pubSpec = new ECPublicKeySpec(Q, ecSpec);
PublicKey publicKeyGenerated = keyFactory.generatePublic(pubSpec);
return (ECPublicKey) publicKeyGenerated;
public String getKey() {
String publicKey = getPublicKey();
String privateKey = getPrivateKey();
return publicKey + "\n" + privateKey;
}

public String getKey() {
public String getPublicKey() {
if (this.publicKeyString != null) {
return this.publicKeyString;
}
String value = System.getenv("PUBLIC_KEY");
if (value != null) {
return value;
}
else {
if (this.publicKeyLocation == null || this.publicKeyLocation.length() < 2) {
throw new Gr4vyException("Unable to read public key");
}
try {
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource(this.publicKeyLocation).getFile());
String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
return key;
} catch (IOException e) {
throw new Gr4vyException("Unable to find public key", e);
}
}
}
public String getPrivateKey() {
if (this.privateKeyString != null) {
return this.privateKeyString;
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/public_key.pem.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
29 changes: 20 additions & 9 deletions src/test/java/com/gr4vy/sdk/Gr4vyClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ public class Gr4vyClientTest {

protected static Gr4vyClient shared;

final static String PRIVATE_KEY_NAME = "private_key.pem";
final static String PUBLIC_KEY_NAME = "public_key.pem";

@BeforeClass
public static void setup() {
shared = new Gr4vyClient.Builder()
.gr4vyId("spider")
.privateKeyLocation("private_key.pem")
.merchantAccountId("steve")
.privateKeyLocation(PRIVATE_KEY_NAME)
.publicKeyLocation(PUBLIC_KEY_NAME)
.environment("sandbox")
.build();
}
Expand All @@ -37,8 +42,10 @@ public static void setup() {
public void setTimeoutTest() throws Gr4vyException {
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("[YOUR_GR4VY_ID]") // required
.privateKeyLocation("private_key.pem") // conditional
.privateKeyLocation(PRIVATE_KEY_NAME) // conditional
.privateKeyString("-----BEGIN PRIVATE KEY-----\n...") // conditional
.publicKeyLocation(PUBLIC_KEY_NAME) // conditional
.publicKeyString("-----BEGIN PUBLIC KEY-----\n...") // conditional
.environment("sandbox") // optional, defaults to sandbox
.host(null) // optional - allows setting a custom host
.client(null) // optional - allows setting the http client
Expand All @@ -57,7 +64,8 @@ public void setTimeoutTest() throws Gr4vyException {
public void getEmbedTokenTest() throws Gr4vyException {
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("spider")
.privateKeyLocation("private_key.pem")
.privateKeyLocation(PRIVATE_KEY_NAME)
.publicKeyLocation(PUBLIC_KEY_NAME)
.merchantAccountId("default")
.build();

Expand All @@ -73,7 +81,8 @@ public void getEmbedTokenTest() throws Gr4vyException {
public void getEmbedTokenTestWithCheckoutSessionPassedIn() throws Gr4vyException {
Gr4vyClient client = new Gr4vyClient.Builder()
.gr4vyId("spider")
.privateKeyLocation("private_key.pem")
.privateKeyLocation(PRIVATE_KEY_NAME)
.publicKeyLocation(PUBLIC_KEY_NAME)
.build();

CheckoutSession checkoutSession = client.newCheckoutSession(null);
Expand Down Expand Up @@ -295,8 +304,8 @@ public void newTransactionWithStoredPaymentMethodTest() throws Gr4vyException {

TransactionPaymentMethodRequest pm = new TransactionPaymentMethodRequest()
.method(MethodEnum.ID)
.id(id);

.id(id);
TransactionRequest request = new TransactionRequest()
.amount(100)
.currency("USD")
Expand Down Expand Up @@ -505,9 +514,11 @@ public void listPaymentOptionsTest() throws Gr4vyException {
@Test
public void getPaymentServiceTokensTest() throws Gr4vyException {
PaymentMethods paymentMethods = shared.listPaymentMethods();
PaymentMethod paymentMethod = paymentMethods.getItems().get(0);
PaymentServiceTokens response = shared.getPaymentServiceTokens(paymentMethod.getId().toString());
assert response != null;
if (paymentMethods.getItems().size() > 0) {
PaymentMethod paymentMethod = paymentMethods.getItems().get(0);
PaymentServiceTokens response = shared.getPaymentServiceTokens(paymentMethod.getId().toString());
assert response != null;
}
}

}
Loading