Skip to content

Commit d6e7ccc

Browse files
authored
Merge pull request #68 from fischermatte/remove-guava
Removed guava compile dependency
2 parents 786759d + a737f94 commit d6e7ccc

File tree

8 files changed

+196
-38
lines changed

8 files changed

+196
-38
lines changed

build.gradle

+3-3
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,6 @@ dependencies {
141141
// For making async HTTP requests
142142
compile group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.3'
143143

144-
// Not sure what for..
145-
compile group: 'com.google.guava', name: 'guava', version: '19.0'
146-
147144
// For cryptographic operations
148145
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.54'
149146

@@ -164,4 +161,7 @@ dependencies {
164161

165162
// For reading the demo vapid keypair from a pem file
166163
testCompile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.55'
164+
165+
// For verifying Base64Encoder results in unit tests
166+
testCompile group: 'com.google.guava', name: 'guava', version: '19.0'
167167
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package nl.martijndwars.webpush;
2+
3+
4+
import org.apache.commons.codec.binary.Base64;
5+
6+
/**
7+
* Java 7 compatible Base64 encode/decode functions. Based on Apache Commons Codec.
8+
*
9+
* <p>
10+
* Note: Once upgrading to Java 8+, replace by native Base64 encoder.
11+
* </p>
12+
*/
13+
public class Base64Encoder {
14+
15+
public static byte[] decode(String base64Encoded) {
16+
return Base64.decodeBase64(base64Encoded);
17+
}
18+
19+
public static String encodeWithoutPadding(byte[] bytes) {
20+
return unpad(Base64.encodeBase64String(bytes));
21+
}
22+
23+
public static String encodeUrl(byte[] bytes) {
24+
return pad(Base64.encodeBase64URLSafeString(bytes));
25+
}
26+
27+
public static String encodeUrlWithoutPadding(byte[] bytes) {
28+
return Base64.encodeBase64URLSafeString(bytes);
29+
}
30+
31+
private static String pad(String base64Encoded) {
32+
int m = base64Encoded.length() % 4;
33+
if (m == 2) {
34+
return base64Encoded + "==";
35+
} else if (m == 3) {
36+
return base64Encoded + "=";
37+
} else {
38+
return base64Encoded;
39+
}
40+
}
41+
42+
private static String unpad(String base64Encoded) {
43+
if (base64Encoded.endsWith("==")) {
44+
return base64Encoded.substring(0, base64Encoded.length() - 2);
45+
} else if (base64Encoded.endsWith("=")) {
46+
return base64Encoded.substring(0, base64Encoded.length() - 1);
47+
} else {
48+
return base64Encoded;
49+
}
50+
}
51+
}

src/main/java/nl/martijndwars/webpush/Notification.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ public Notification(String endpoint, PublicKey userPublicKey, byte[] userAuth, b
4848
}
4949

5050
public Notification(String endpoint, String userPublicKey, String userAuth, byte[] payload) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
51-
this(endpoint, Utils.loadPublicKey(userPublicKey), Utils.base64Decode(userAuth), payload);
51+
this(endpoint, Utils.loadPublicKey(userPublicKey), Base64Encoder.decode(userAuth), payload);
5252
}
5353

5454
public Notification(String endpoint, String userPublicKey, String userAuth, String payload) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
55-
this(endpoint, Utils.loadPublicKey(userPublicKey), Utils.base64Decode(userAuth), payload.getBytes(UTF_8));
55+
this(endpoint, Utils.loadPublicKey(userPublicKey), Base64Encoder.decode(userAuth), payload.getBytes(UTF_8));
5656
}
5757

5858
public Notification(Subscription subscription, String payload) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {

src/main/java/nl/martijndwars/webpush/PushService.java

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package nl.martijndwars.webpush;
22

3-
import com.google.common.io.BaseEncoding;
43
import org.apache.http.HttpResponse;
54
import org.apache.http.client.methods.HttpPost;
65
import org.apache.http.entity.ByteArrayEntity;
@@ -148,7 +147,6 @@ public Future<HttpResponse> sendAsync(Notification notification) throws GeneralS
148147
public HttpPost preparePost(Notification notification) throws GeneralSecurityException, IOException, JoseException {
149148
assert (verifyKeyPair());
150149

151-
BaseEncoding base64url = BaseEncoding.base64Url();
152150

153151
Encrypted encrypted = encrypt(
154152
notification.getPayload(),
@@ -168,8 +166,8 @@ public HttpPost preparePost(Notification notification) throws GeneralSecurityExc
168166
if (notification.hasPayload()) {
169167
headers.put("Content-Type", "application/octet-stream");
170168
headers.put("Content-Encoding", "aesgcm");
171-
headers.put("Encryption", "salt=" + base64url.omitPadding().encode(salt));
172-
headers.put("Crypto-Key", "dh=" + base64url.encode(dh));
169+
headers.put("Encryption", "salt=" + Base64Encoder.encodeUrlWithoutPadding(salt));
170+
headers.put("Crypto-Key", "dh=" + Base64Encoder.encodeUrl(dh));
173171

174172
httpPost.setEntity(new ByteArrayEntity(encrypted.getCiphertext()));
175173
}
@@ -200,9 +198,9 @@ public HttpPost preparePost(Notification notification) throws GeneralSecurityExc
200198
byte[] pk = Utils.savePublicKey((ECPublicKey) publicKey);
201199

202200
if (headers.containsKey("Crypto-Key")) {
203-
headers.put("Crypto-Key", headers.get("Crypto-Key") + ";p256ecdsa=" + base64url.omitPadding().encode(pk));
201+
headers.put("Crypto-Key", headers.get("Crypto-Key") + ";p256ecdsa=" + Base64Encoder.encodeUrlWithoutPadding(pk));
204202
} else {
205-
headers.put("Crypto-Key", "p256ecdsa=" + base64url.encode(pk));
203+
headers.put("Crypto-Key", "p256ecdsa=" + Base64Encoder.encodeUrl(pk));
206204
}
207205
}
208206

src/main/java/nl/martijndwars/webpush/Utils.java

+2-19
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package nl.martijndwars.webpush;
22

3-
import com.google.common.io.BaseEncoding;
43
import org.bouncycastle.jce.ECNamedCurveTable;
54
import org.bouncycastle.jce.interfaces.ECPrivateKey;
65
import org.bouncycastle.jce.interfaces.ECPublicKey;
7-
import org.bouncycastle.jce.provider.BouncyCastleProvider;
86
import org.bouncycastle.jce.spec.ECParameterSpec;
97
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
108
import org.bouncycastle.jce.spec.ECPublicKeySpec;
@@ -38,29 +36,14 @@ public static byte[] savePrivateKey(ECPrivateKey privateKey) {
3836
return privateKey.getD().toByteArray();
3937
}
4038

41-
/**
42-
* Base64-decode a string. Works for both url-safe and non-url-safe
43-
* encodings.
44-
*
45-
* @param base64Encoded
46-
* @return
47-
*/
48-
public static byte[] base64Decode(String base64Encoded) {
49-
if (base64Encoded.contains("+") || base64Encoded.contains("/")) {
50-
return BaseEncoding.base64().decode(base64Encoded);
51-
} else {
52-
return BaseEncoding.base64Url().decode(base64Encoded);
53-
}
54-
}
55-
5639
/**
5740
* Load the public key from a URL-safe base64 encoded string. Takes into
5841
* account the different encodings, including point compression.
5942
*
6043
* @param encodedPublicKey
6144
*/
6245
public static PublicKey loadPublicKey(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
63-
byte[] decodedPublicKey = base64Decode(encodedPublicKey);
46+
byte[] decodedPublicKey = Base64Encoder.decode(encodedPublicKey);
6447
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, PROVIDER_NAME);
6548
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(CURVE);
6649
ECCurve curve = parameterSpec.getCurve();
@@ -80,7 +63,7 @@ public static PublicKey loadPublicKey(String encodedPublicKey) throws NoSuchProv
8063
* @throws InvalidKeySpecException
8164
*/
8265
public static PrivateKey loadPrivateKey(String encodedPrivateKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
83-
byte[] decodedPrivateKey = base64Decode(encodedPrivateKey);
66+
byte[] decodedPrivateKey = Base64Encoder.decode(encodedPrivateKey);
8467
BigInteger s = BigIntegers.fromUnsignedByteArray(decodedPrivateKey);
8568
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(CURVE);
8669
ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(s, parameterSpec);

src/main/java/nl/martijndwars/webpush/cli/handlers/GenerateKeyHandler.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package nl.martijndwars.webpush.cli.handlers;
22

3-
import com.google.common.io.BaseEncoding;
3+
import nl.martijndwars.webpush.Base64Encoder;
44
import nl.martijndwars.webpush.Utils;
55
import nl.martijndwars.webpush.cli.commands.GenerateKeyCommand;
66
import org.bouncycastle.jce.ECNamedCurveTable;
@@ -39,10 +39,10 @@ public void run() throws InvalidAlgorithmParameterException, NoSuchAlgorithmExce
3939
}
4040

4141
System.out.println("PublicKey:");
42-
System.out.println(BaseEncoding.base64Url().encode(publicKey));
42+
System.out.println(Base64Encoder.encodeUrl(publicKey));
4343

4444
System.out.println("PrivateKey:");
45-
System.out.println(BaseEncoding.base64Url().encode(privateKey));
45+
System.out.println(Base64Encoder.encodeUrl(privateKey));
4646
}
4747

4848
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package nl.martijndwars.webpush;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static com.google.common.io.BaseEncoding.base64;
6+
import static com.google.common.io.BaseEncoding.base64Url;
7+
import static java.nio.charset.StandardCharsets.UTF_8;
8+
import static nl.martijndwars.webpush.Base64Encoder.*;
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
11+
class Base64EncoderTest {
12+
13+
@Test
14+
void decodeTest() {
15+
// first compare with previous guava implementation, make sure non-breaking changes
16+
assertEquals(new String(base64().decode("")), new String(decode("")));
17+
assertEquals(new String(base64().decode("dw")), new String(decode("dw")));
18+
assertEquals(new String(base64().decode("dw==")), new String(decode("dw==")));
19+
assertEquals(new String(base64().decode("d2U")), new String(decode("d2U")));
20+
assertEquals(new String(base64().decode("d2Vi")), new String(decode("d2Vi")));
21+
assertEquals(new String(base64().decode("d2ViLQ")), new String(decode("d2ViLQ")));
22+
assertEquals(new String(base64().decode("d2ViLQ==")), new String(decode("d2ViLQ==")));
23+
assertEquals(new String(base64().decode("d2ViLXA")), new String(decode("d2ViLXA")));
24+
assertEquals(new String(base64().decode("d2ViLXA=")), new String(decode("d2ViLXA=")));
25+
assertEquals(new String(base64().decode("d2ViLXB1")), new String(decode("d2ViLXB1")));
26+
assertEquals(new String(base64().decode("d2ViLXB1cw")), new String(decode("d2ViLXB1cw")));
27+
assertEquals(new String(base64().decode("d2ViLXB1cw==")), new String(decode("d2ViLXB1cw==")));
28+
assertEquals(new String(base64().decode("d2ViLXB1c2g")), new String(decode("d2ViLXB1c2g")));
29+
assertEquals(new String(base64().decode("d2ViLXB1c2g=")), new String(decode("d2ViLXB1c2g=")));
30+
assertEquals(new String(base64().decode("d2ViLXB1c2g/")), new String(decode("d2ViLXB1c2g/")));
31+
assertEquals(new String(base64Url().decode("d2ViLXB1c2g_")), new String(decode("d2ViLXB1c2g_")));
32+
33+
assertEquals("", new String(decode("")));
34+
assertEquals("w", new String(decode("dw")));
35+
assertEquals("w", new String(decode("dw==")));
36+
assertEquals("we", new String(decode("d2U")));
37+
assertEquals("web", new String(decode("d2Vi")));
38+
assertEquals("web-", new String(decode("d2ViLQ")));
39+
assertEquals("web-", new String(decode("d2ViLQ==")));
40+
assertEquals("web-p", new String(decode("d2ViLXA")));
41+
assertEquals("web-p", new String(decode("d2ViLXA=")));
42+
assertEquals("web-pu", new String(decode("d2ViLXB1")));
43+
assertEquals("web-pus", new String(decode("d2ViLXB1cw")));
44+
assertEquals("web-pus", new String(decode("d2ViLXB1cw==")));
45+
assertEquals("web-push", new String(decode("d2ViLXB1c2g")));
46+
assertEquals("web-push", new String(decode("d2ViLXB1c2g=")));
47+
assertEquals("web-push?", new String(decode("d2ViLXB1c2g/")));
48+
assertEquals("web-push?", new String(decode("d2ViLXB1c2g_")));
49+
}
50+
51+
@Test
52+
void encodeWithoutPaddingTest() {
53+
// first verify non breaking changes after removing guava as compile dependency
54+
assertEquals(base64().omitPadding().encode("".getBytes()), encodeWithoutPadding("".getBytes(UTF_8)));
55+
assertEquals(base64().omitPadding().encode("w".getBytes()), encodeWithoutPadding("w".getBytes(UTF_8)));
56+
assertEquals(base64().omitPadding().encode("we".getBytes()), encodeWithoutPadding("we".getBytes(UTF_8)));
57+
assertEquals(base64().omitPadding().encode("web".getBytes()), encodeWithoutPadding("web".getBytes(UTF_8)));
58+
assertEquals(base64().omitPadding().encode("web-".getBytes()), encodeWithoutPadding("web-".getBytes(UTF_8)));
59+
assertEquals(base64().omitPadding().encode("web-p".getBytes()), encodeWithoutPadding("web-p".getBytes(UTF_8)));
60+
assertEquals(base64().omitPadding().encode("web-pu".getBytes()), encodeWithoutPadding("web-pu".getBytes(UTF_8)));
61+
assertEquals(base64().omitPadding().encode("web-pus".getBytes()), encodeWithoutPadding("web-pus".getBytes(UTF_8)));
62+
assertEquals(base64().omitPadding().encode("web-push".getBytes()), encodeWithoutPadding("web-push".getBytes(UTF_8)));
63+
assertEquals(base64().omitPadding().encode("web-push?".getBytes()), encodeWithoutPadding("web-push?".getBytes(UTF_8)));
64+
65+
assertEquals("", encodeWithoutPadding("".getBytes(UTF_8)));
66+
assertEquals("dw", encodeWithoutPadding("w".getBytes(UTF_8)));
67+
assertEquals("d2U", encodeWithoutPadding("we".getBytes(UTF_8)));
68+
assertEquals("d2Vi", encodeWithoutPadding("web".getBytes(UTF_8)));
69+
assertEquals("d2ViLQ", encodeWithoutPadding("web-".getBytes(UTF_8)));
70+
assertEquals("d2ViLXA", encodeWithoutPadding("web-p".getBytes(UTF_8)));
71+
assertEquals("d2ViLXB1", encodeWithoutPadding("web-pu".getBytes(UTF_8)));
72+
assertEquals("d2ViLXB1cw", encodeWithoutPadding("web-pus".getBytes(UTF_8)));
73+
assertEquals("d2ViLXB1c2g", encodeWithoutPadding("web-push".getBytes(UTF_8)));
74+
assertEquals("d2ViLXB1c2g/", encodeWithoutPadding("web-push?".getBytes(UTF_8)));
75+
}
76+
77+
@Test
78+
void encodeUrlTest() {
79+
// first verify non breaking changes after removing guava as compile dependency
80+
assertEquals(base64Url().encode("".getBytes()), encodeUrl("".getBytes(UTF_8)));
81+
assertEquals(base64Url().encode("w".getBytes()), encodeUrl("w".getBytes(UTF_8)));
82+
assertEquals(base64Url().encode("we".getBytes()), encodeUrl("we".getBytes(UTF_8)));
83+
assertEquals(base64Url().encode("web".getBytes()), encodeUrl("web".getBytes(UTF_8)));
84+
assertEquals(base64Url().encode("web-".getBytes()), encodeUrl("web-".getBytes(UTF_8)));
85+
assertEquals(base64Url().encode("web-p".getBytes()), encodeUrl("web-p".getBytes(UTF_8)));
86+
assertEquals(base64Url().encode("web-pu".getBytes()), encodeUrl("web-pu".getBytes(UTF_8)));
87+
assertEquals(base64Url().encode("web-pus".getBytes()), encodeUrl("web-pus".getBytes(UTF_8)));
88+
assertEquals(base64Url().encode("web-push".getBytes()), encodeUrl("web-push".getBytes(UTF_8)));
89+
assertEquals(base64Url().encode("web-push?".getBytes()), encodeUrl("web-push?".getBytes(UTF_8)));
90+
91+
assertEquals("", encodeUrl("".getBytes(UTF_8)));
92+
assertEquals("dw==", encodeUrl("w".getBytes(UTF_8)));
93+
assertEquals("d2U=", encodeUrl("we".getBytes(UTF_8)));
94+
assertEquals("d2Vi", encodeUrl("web".getBytes(UTF_8)));
95+
assertEquals("d2ViLQ==", encodeUrl("web-".getBytes(UTF_8)));
96+
assertEquals("d2ViLXA=", encodeUrl("web-p".getBytes(UTF_8)));
97+
assertEquals("d2ViLXB1", encodeUrl("web-pu".getBytes(UTF_8)));
98+
assertEquals("d2ViLXB1cw==", encodeUrl("web-pus".getBytes(UTF_8)));
99+
assertEquals("d2ViLXB1c2g=", encodeUrl("web-push".getBytes(UTF_8)));
100+
assertEquals("d2ViLXB1c2g_", encodeUrl("web-push?".getBytes(UTF_8)));
101+
}
102+
103+
@Test
104+
void encodeUrlWithoutPaddingTest() {
105+
// first verify non breaking changes after removing guava as compile dependency
106+
assertEquals(base64Url().omitPadding().encode("".getBytes()), encodeUrlWithoutPadding("".getBytes(UTF_8)));
107+
assertEquals(base64Url().omitPadding().encode("w".getBytes()), encodeUrlWithoutPadding("w".getBytes(UTF_8)));
108+
assertEquals(base64Url().omitPadding().encode("we".getBytes()), encodeUrlWithoutPadding("we".getBytes(UTF_8)));
109+
assertEquals(base64Url().omitPadding().encode("web".getBytes()), encodeUrlWithoutPadding("web".getBytes(UTF_8)));
110+
assertEquals(base64Url().omitPadding().encode("web-".getBytes()), encodeUrlWithoutPadding("web-".getBytes(UTF_8)));
111+
assertEquals(base64Url().omitPadding().encode("web-p".getBytes()), encodeUrlWithoutPadding("web-p".getBytes(UTF_8)));
112+
assertEquals(base64Url().omitPadding().encode("web-pu".getBytes()), encodeUrlWithoutPadding("web-pu".getBytes(UTF_8)));
113+
assertEquals(base64Url().omitPadding().encode("web-pus".getBytes()), encodeUrlWithoutPadding("web-pus".getBytes(UTF_8)));
114+
assertEquals(base64Url().omitPadding().encode("web-push".getBytes()), encodeUrlWithoutPadding("web-push".getBytes(UTF_8)));
115+
assertEquals(base64Url().omitPadding().encode("web-push?".getBytes()), encodeUrlWithoutPadding("web-push?".getBytes(UTF_8)));
116+
117+
assertEquals("", encodeUrlWithoutPadding("".getBytes(UTF_8)));
118+
assertEquals("dw", encodeUrlWithoutPadding("w".getBytes(UTF_8)));
119+
assertEquals("d2U", encodeUrlWithoutPadding("we".getBytes(UTF_8)));
120+
assertEquals("d2Vi", encodeUrlWithoutPadding("web".getBytes(UTF_8)));
121+
assertEquals("d2ViLQ", encodeUrlWithoutPadding("web-".getBytes(UTF_8)));
122+
assertEquals("d2ViLXA", encodeUrlWithoutPadding("web-p".getBytes(UTF_8)));
123+
assertEquals("d2ViLXB1", encodeUrlWithoutPadding("web-pu".getBytes(UTF_8)));
124+
assertEquals("d2ViLXB1cw", encodeUrlWithoutPadding("web-pus".getBytes(UTF_8)));
125+
assertEquals("d2ViLXB1c2g", encodeUrlWithoutPadding("web-push".getBytes(UTF_8)));
126+
assertEquals("d2ViLXB1c2g_", encodeUrlWithoutPadding("web-push?".getBytes(UTF_8)));
127+
}
128+
}

src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package nl.martijndwars.webpush.selenium;
22

3-
import com.google.common.io.BaseEncoding;
3+
import nl.martijndwars.webpush.Base64Encoder;
44
import nl.martijndwars.webpush.PushService;
55
import org.bouncycastle.jce.provider.BouncyCastleProvider;
66
import org.junit.jupiter.api.AfterAll;
@@ -64,10 +64,8 @@ public Stream<DynamicTest> dynamicTests() throws IOException {
6464
* @return
6565
*/
6666
protected Stream<Configuration> getConfigurations() {
67-
BaseEncoding base64Encoding = BaseEncoding.base64();
68-
69-
String PUBLIC_KEY_NO_PADDING = base64Encoding.omitPadding().encode(
70-
base64Encoding.decode(PUBLIC_KEY)
67+
String PUBLIC_KEY_NO_PADDING = Base64Encoder.encodeWithoutPadding(
68+
Base64Encoder.decode(PUBLIC_KEY)
7169
);
7270

7371
return Stream.of(

0 commit comments

Comments
 (0)