diff --git a/.travis.yml b/.travis.yml index b0df96c..eefe331 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,21 +11,9 @@ env: before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -before_install: - - nvm i node -install: - - npm install github:GoogleChromeLabs/web-push-testing-service -g -before_script: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start || echo \"Unable to start virtual display.\"" - - sleep 3 script: - - web-push-testing-service start wpts - ./gradlew clean check - - web-push-testing-service stop wpts cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - - ~/.selenium-assistant - - node_modules diff --git a/README.md b/README.md index b9e31cd..746878b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A Web Push library for Java 8. Supports payloads and VAPID. For Gradle, add the following dependency to `build.gradle`: ```groovy -compile group: 'nl.martijndwars', name: 'web-push', version: '5.1.1' +compile group: 'nl.martijndwars', name: 'web-push', version: '5.1.2' ``` For Maven, add the following dependency to `pom.xml`: @@ -19,7 +19,7 @@ For Maven, add the following dependency to `pom.xml`:     nl.martijndwars     web-push -    5.1.1 +    5.1.2 ``` diff --git a/build.gradle b/build.gradle index 87be355..83ca068 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,18 @@ plugins { id 'application' - id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'com.github.johnrengelman.shadow' version '7.1.1' // Used by release.gradle id 'maven-publish' id 'signing' - id 'io.codearte.nexus-staging' version '0.22.0' + id 'io.codearte.nexus-staging' version '0.30.0' } apply plugin: 'application' apply plugin: 'com.github.johnrengelman.shadow' group 'nl.martijndwars' -version '5.1.2-SNAPSHOT' +version '5.1.2' repositories { mavenLocal() @@ -21,40 +21,40 @@ repositories { dependencies { // For CLI - implementation group: 'com.beust', name: 'jcommander', version: '1.78' + implementation group: 'com.beust', name: 'jcommander', version: '1.81' // For making HTTP requests - implementation group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.4' + implementation group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.5' // For making async HTTP requests - implementation group: 'org.asynchttpclient', name: 'async-http-client', version: '2.12.2' + implementation group: 'org.asynchttpclient', name: 'async-http-client', version: '2.12.4' // For cryptographic operations - shadow group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.68' + shadow group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.70' // For creating and signing JWT - implementation group: 'org.bitbucket.b_c', name: 'jose4j', version: '0.7.6' + implementation group: 'org.bitbucket.b_c', name: 'jose4j', version: '0.9.6' // For parsing JSON - testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' + testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9' // For making HTTP requests testImplementation group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.5.13' // For testing, obviously - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.8.1' // For running JUnit tests - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' // For turning InputStream to String - testImplementation group: 'commons-io', name: 'commons-io', version: '2.8.0' + testImplementation group: 'commons-io', name: 'commons-io', version: '2.11.0' // For reading the demo vapid keypair from a pem file - testImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.68' + testImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.70' // For verifying Base64Encoder results in unit tests - testImplementation group: 'com.google.guava', name: 'guava', version: '30.1-jre' + testImplementation group: 'com.google.guava', name: 'guava', version: '33.4.8-jre' } wrapper { @@ -84,6 +84,8 @@ test { showStandardStreams true exceptionFormat 'full' } + + exclude '**/SeleniumTests.class' } task javadocJar(type: Jar) { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c..7454180 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 28ff446..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..1b6c787 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/src/main/java/nl/martijndwars/webpush/AbstractPushService.java b/src/main/java/nl/martijndwars/webpush/AbstractPushService.java index 8e2d598..f3f25ed 100644 --- a/src/main/java/nl/martijndwars/webpush/AbstractPushService.java +++ b/src/main/java/nl/martijndwars/webpush/AbstractPushService.java @@ -19,6 +19,7 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -164,8 +165,8 @@ protected final HttpRequest prepareRequest(Notification notification, Encoding e headers.put("Content-Encoding", "aes128gcm"); } else if (encoding == Encoding.AESGCM) { headers.put("Content-Encoding", "aesgcm"); - headers.put("Encryption", "salt=" + Base64Encoder.encodeUrlWithoutPadding(salt)); - headers.put("Crypto-Key", "dh=" + Base64Encoder.encodeUrl(dh)); + headers.put("Encryption", "salt=" + Base64.getUrlEncoder().withoutPadding().encodeToString(salt)); + headers.put("Crypto-Key", "dh=" + Base64.getUrlEncoder().withoutPadding().encodeToString(dh)); } body = encrypted.getCiphertext(); @@ -201,15 +202,15 @@ protected final HttpRequest prepareRequest(Notification notification, Encoding e byte[] pk = Utils.encode((ECPublicKey) getPublicKey()); if (encoding == Encoding.AES128GCM) { - headers.put("Authorization", "vapid t=" + jws.getCompactSerialization() + ", k=" + Base64Encoder.encodeUrlWithoutPadding(pk)); + headers.put("Authorization", "vapid t=" + jws.getCompactSerialization() + ", k=" + Base64.getUrlEncoder().withoutPadding().encodeToString(pk)); } else if (encoding == Encoding.AESGCM) { headers.put("Authorization", "WebPush " + jws.getCompactSerialization()); } if (headers.containsKey("Crypto-Key")) { - headers.put("Crypto-Key", headers.get("Crypto-Key") + ";p256ecdsa=" + Base64Encoder.encodeUrlWithoutPadding(pk)); + headers.put("Crypto-Key", headers.get("Crypto-Key") + ";p256ecdsa=" + Base64.getUrlEncoder().withoutPadding().encodeToString(pk)); } else { - headers.put("Crypto-Key", "p256ecdsa=" + Base64Encoder.encodeUrl(pk)); + headers.put("Crypto-Key", "p256ecdsa=" + Base64.getUrlEncoder().withoutPadding().encodeToString(pk)); } } else if (notification.isFcm() && getGcmApiKey() != null) { headers.put("Authorization", "key=" + getGcmApiKey()); diff --git a/src/main/java/nl/martijndwars/webpush/Base64Encoder.java b/src/main/java/nl/martijndwars/webpush/Base64Encoder.java deleted file mode 100644 index b9f05ee..0000000 --- a/src/main/java/nl/martijndwars/webpush/Base64Encoder.java +++ /dev/null @@ -1,51 +0,0 @@ -package nl.martijndwars.webpush; - - -import org.apache.commons.codec.binary.Base64; - -/** - * Java 7 compatible Base64 encode/decode functions. Based on Apache Commons Codec. - * - *

- * Note: Once upgrading to Java 8+, replace by native Base64 encoder. - *

- */ -public class Base64Encoder { - - public static byte[] decode(String base64Encoded) { - return Base64.decodeBase64(base64Encoded); - } - - public static String encodeWithoutPadding(byte[] bytes) { - return unpad(Base64.encodeBase64String(bytes)); - } - - public static String encodeUrl(byte[] bytes) { - return pad(Base64.encodeBase64URLSafeString(bytes)); - } - - public static String encodeUrlWithoutPadding(byte[] bytes) { - return Base64.encodeBase64URLSafeString(bytes); - } - - private static String pad(String base64Encoded) { - int m = base64Encoded.length() % 4; - if (m == 2) { - return base64Encoded + "=="; - } else if (m == 3) { - return base64Encoded + "="; - } else { - return base64Encoded; - } - } - - private static String unpad(String base64Encoded) { - if (base64Encoded.endsWith("==")) { - return base64Encoded.substring(0, base64Encoded.length() - 2); - } else if (base64Encoded.endsWith("=")) { - return base64Encoded.substring(0, base64Encoded.length() - 1); - } else { - return base64Encoded; - } - } -} diff --git a/src/main/java/nl/martijndwars/webpush/HttpEce.java b/src/main/java/nl/martijndwars/webpush/HttpEce.java index 4aacdc0..b9e9436 100644 --- a/src/main/java/nl/martijndwars/webpush/HttpEce.java +++ b/src/main/java/nl/martijndwars/webpush/HttpEce.java @@ -12,6 +12,7 @@ import java.nio.ByteBuffer; import java.security.*; import java.util.Arrays; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -437,7 +438,7 @@ private static byte[] intToBytes(int number) { */ private static byte[] log(String info, byte[] array) { if ("1".equals(System.getenv("ECE_KEYLOG"))) { - System.out.println(info + " [" + array.length + "]: " + Base64Encoder.encodeUrlWithoutPadding(array)); + System.out.println(info + " [" + array.length + "]: " + Base64.getUrlEncoder().withoutPadding().encodeToString(array)); } return array; diff --git a/src/main/java/nl/martijndwars/webpush/Notification.java b/src/main/java/nl/martijndwars/webpush/Notification.java index 50ab0c9..6fdc493 100644 --- a/src/main/java/nl/martijndwars/webpush/Notification.java +++ b/src/main/java/nl/martijndwars/webpush/Notification.java @@ -8,6 +8,7 @@ import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.Base64; import static java.nio.charset.StandardCharsets.UTF_8; @@ -71,7 +72,7 @@ public Notification(String endpoint, PublicKey userPublicKey, byte[] userAuth, b } public Notification(String endpoint, String userPublicKey, String userAuth, byte[] payload, int ttl) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { - this(endpoint, Utils.loadPublicKey(userPublicKey), Base64Encoder.decode(userAuth), payload, ttl); + this(endpoint, Utils.loadPublicKey(userPublicKey), Base64.getUrlDecoder().decode(userAuth), payload, ttl); } public Notification(String endpoint, PublicKey userPublicKey, byte[] userAuth, byte[] payload) { @@ -79,15 +80,15 @@ public Notification(String endpoint, PublicKey userPublicKey, byte[] userAuth, b } public Notification(String endpoint, String userPublicKey, String userAuth, byte[] payload) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { - this(endpoint, Utils.loadPublicKey(userPublicKey), Base64Encoder.decode(userAuth), payload); + this(endpoint, Utils.loadPublicKey(userPublicKey), Base64.getUrlDecoder().decode(userAuth), payload); } public Notification(String endpoint, String userPublicKey, String userAuth, String payload) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { - this(endpoint, Utils.loadPublicKey(userPublicKey), Base64Encoder.decode(userAuth), payload.getBytes(UTF_8)); + this(endpoint, Utils.loadPublicKey(userPublicKey), Base64.getUrlDecoder().decode(userAuth), payload.getBytes(UTF_8)); } public Notification(String endpoint, String userPublicKey, String userAuth, String payload, Urgency urgency) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { - this(endpoint, Utils.loadPublicKey(userPublicKey), Base64Encoder.decode(userAuth), payload.getBytes(UTF_8)); + this(endpoint, Utils.loadPublicKey(userPublicKey), Base64.getUrlDecoder().decode(userAuth), payload.getBytes(UTF_8)); this.urgency = urgency; } @@ -200,7 +201,7 @@ public NotificationBuilder userPublicKey(byte[] publicKey) throws NoSuchAlgorith } public NotificationBuilder userAuth(String userAuth) { - this.userAuth = Base64Encoder.decode(userAuth); + this.userAuth = Base64.getUrlDecoder().decode(userAuth); return this; } diff --git a/src/main/java/nl/martijndwars/webpush/PushService.java b/src/main/java/nl/martijndwars/webpush/PushService.java index cd117dc..e15647b 100644 --- a/src/main/java/nl/martijndwars/webpush/PushService.java +++ b/src/main/java/nl/martijndwars/webpush/PushService.java @@ -65,7 +65,7 @@ public HttpResponse send(Notification notification, Encoding encoding) throws Ge } public HttpResponse send(Notification notification) throws GeneralSecurityException, IOException, JoseException, ExecutionException, InterruptedException { - return send(notification, Encoding.AESGCM); + return send(notification, Encoding.AES128GCM); } /** diff --git a/src/main/java/nl/martijndwars/webpush/Utils.java b/src/main/java/nl/martijndwars/webpush/Utils.java index aa625e5..d135f38 100644 --- a/src/main/java/nl/martijndwars/webpush/Utils.java +++ b/src/main/java/nl/martijndwars/webpush/Utils.java @@ -15,6 +15,7 @@ import java.nio.ByteBuffer; import java.security.*; import java.security.spec.InvalidKeySpecException; +import java.util.Base64; import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; @@ -45,7 +46,7 @@ public static byte[] encode(ECPrivateKey privateKey) { * @param encodedPublicKey */ public static PublicKey loadPublicKey(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException { - byte[] decodedPublicKey = Base64Encoder.decode(encodedPublicKey); + byte[] decodedPublicKey = Base64.getUrlDecoder().decode(encodedPublicKey); return loadPublicKey(decodedPublicKey); } @@ -74,7 +75,7 @@ public static PublicKey loadPublicKey(byte[] decodedPublicKey) throws NoSuchProv * @throws InvalidKeySpecException */ public static PrivateKey loadPrivateKey(String encodedPrivateKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException { - byte[] decodedPrivateKey = Base64Encoder.decode(encodedPrivateKey); + byte[] decodedPrivateKey = Base64.getUrlDecoder().decode(encodedPrivateKey); return loadPrivateKey(decodedPrivateKey); } diff --git a/src/main/java/nl/martijndwars/webpush/cli/handlers/GenerateKeyHandler.java b/src/main/java/nl/martijndwars/webpush/cli/handlers/GenerateKeyHandler.java index 68bfd99..1747adf 100644 --- a/src/main/java/nl/martijndwars/webpush/cli/handlers/GenerateKeyHandler.java +++ b/src/main/java/nl/martijndwars/webpush/cli/handlers/GenerateKeyHandler.java @@ -1,6 +1,5 @@ package nl.martijndwars.webpush.cli.handlers; -import nl.martijndwars.webpush.Base64Encoder; import nl.martijndwars.webpush.Utils; import nl.martijndwars.webpush.cli.commands.GenerateKeyCommand; import org.bouncycastle.jce.ECNamedCurveTable; @@ -15,6 +14,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.security.*; +import java.util.Base64; import static nl.martijndwars.webpush.Utils.ALGORITHM; import static nl.martijndwars.webpush.Utils.CURVE; @@ -42,10 +42,10 @@ public void run() throws InvalidAlgorithmParameterException, NoSuchAlgorithmExce } System.out.println("PublicKey:"); - System.out.println(Base64Encoder.encodeUrl(encodedPublicKey)); + System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(encodedPublicKey)); System.out.println("PrivateKey:"); - System.out.println(Base64Encoder.encodeUrl(encodedPrivateKey)); + System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(encodedPrivateKey)); } /** diff --git a/src/test/java/nl/martijndwars/webpush/Base64EncoderTest.java b/src/test/java/nl/martijndwars/webpush/Base64EncoderTest.java deleted file mode 100644 index 962e405..0000000 --- a/src/test/java/nl/martijndwars/webpush/Base64EncoderTest.java +++ /dev/null @@ -1,128 +0,0 @@ -package nl.martijndwars.webpush; - -import org.junit.jupiter.api.Test; - -import static com.google.common.io.BaseEncoding.base64; -import static com.google.common.io.BaseEncoding.base64Url; -import static java.nio.charset.StandardCharsets.UTF_8; -import static nl.martijndwars.webpush.Base64Encoder.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - -class Base64EncoderTest { - - @Test - void decodeTest() { - // first compare with previous guava implementation, make sure non-breaking changes - assertEquals(new String(base64().decode("")), new String(decode(""))); - assertEquals(new String(base64().decode("dw")), new String(decode("dw"))); - assertEquals(new String(base64().decode("dw==")), new String(decode("dw=="))); - assertEquals(new String(base64().decode("d2U")), new String(decode("d2U"))); - assertEquals(new String(base64().decode("d2Vi")), new String(decode("d2Vi"))); - assertEquals(new String(base64().decode("d2ViLQ")), new String(decode("d2ViLQ"))); - assertEquals(new String(base64().decode("d2ViLQ==")), new String(decode("d2ViLQ=="))); - assertEquals(new String(base64().decode("d2ViLXA")), new String(decode("d2ViLXA"))); - assertEquals(new String(base64().decode("d2ViLXA=")), new String(decode("d2ViLXA="))); - assertEquals(new String(base64().decode("d2ViLXB1")), new String(decode("d2ViLXB1"))); - assertEquals(new String(base64().decode("d2ViLXB1cw")), new String(decode("d2ViLXB1cw"))); - assertEquals(new String(base64().decode("d2ViLXB1cw==")), new String(decode("d2ViLXB1cw=="))); - assertEquals(new String(base64().decode("d2ViLXB1c2g")), new String(decode("d2ViLXB1c2g"))); - assertEquals(new String(base64().decode("d2ViLXB1c2g=")), new String(decode("d2ViLXB1c2g="))); - assertEquals(new String(base64().decode("d2ViLXB1c2g/")), new String(decode("d2ViLXB1c2g/"))); - assertEquals(new String(base64Url().decode("d2ViLXB1c2g_")), new String(decode("d2ViLXB1c2g_"))); - - assertEquals("", new String(decode(""))); - assertEquals("w", new String(decode("dw"))); - assertEquals("w", new String(decode("dw=="))); - assertEquals("we", new String(decode("d2U"))); - assertEquals("web", new String(decode("d2Vi"))); - assertEquals("web-", new String(decode("d2ViLQ"))); - assertEquals("web-", new String(decode("d2ViLQ=="))); - assertEquals("web-p", new String(decode("d2ViLXA"))); - assertEquals("web-p", new String(decode("d2ViLXA="))); - assertEquals("web-pu", new String(decode("d2ViLXB1"))); - assertEquals("web-pus", new String(decode("d2ViLXB1cw"))); - assertEquals("web-pus", new String(decode("d2ViLXB1cw=="))); - assertEquals("web-push", new String(decode("d2ViLXB1c2g"))); - assertEquals("web-push", new String(decode("d2ViLXB1c2g="))); - assertEquals("web-push?", new String(decode("d2ViLXB1c2g/"))); - assertEquals("web-push?", new String(decode("d2ViLXB1c2g_"))); - } - - @Test - void encodeWithoutPaddingTest() { - // first verify non breaking changes after removing guava as compile dependency - assertEquals(base64().omitPadding().encode("".getBytes()), encodeWithoutPadding("".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("w".getBytes()), encodeWithoutPadding("w".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("we".getBytes()), encodeWithoutPadding("we".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web".getBytes()), encodeWithoutPadding("web".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web-".getBytes()), encodeWithoutPadding("web-".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web-p".getBytes()), encodeWithoutPadding("web-p".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web-pu".getBytes()), encodeWithoutPadding("web-pu".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web-pus".getBytes()), encodeWithoutPadding("web-pus".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web-push".getBytes()), encodeWithoutPadding("web-push".getBytes(UTF_8))); - assertEquals(base64().omitPadding().encode("web-push?".getBytes()), encodeWithoutPadding("web-push?".getBytes(UTF_8))); - - assertEquals("", encodeWithoutPadding("".getBytes(UTF_8))); - assertEquals("dw", encodeWithoutPadding("w".getBytes(UTF_8))); - assertEquals("d2U", encodeWithoutPadding("we".getBytes(UTF_8))); - assertEquals("d2Vi", encodeWithoutPadding("web".getBytes(UTF_8))); - assertEquals("d2ViLQ", encodeWithoutPadding("web-".getBytes(UTF_8))); - assertEquals("d2ViLXA", encodeWithoutPadding("web-p".getBytes(UTF_8))); - assertEquals("d2ViLXB1", encodeWithoutPadding("web-pu".getBytes(UTF_8))); - assertEquals("d2ViLXB1cw", encodeWithoutPadding("web-pus".getBytes(UTF_8))); - assertEquals("d2ViLXB1c2g", encodeWithoutPadding("web-push".getBytes(UTF_8))); - assertEquals("d2ViLXB1c2g/", encodeWithoutPadding("web-push?".getBytes(UTF_8))); - } - - @Test - void encodeUrlTest() { - // first verify non breaking changes after removing guava as compile dependency - assertEquals(base64Url().encode("".getBytes()), encodeUrl("".getBytes(UTF_8))); - assertEquals(base64Url().encode("w".getBytes()), encodeUrl("w".getBytes(UTF_8))); - assertEquals(base64Url().encode("we".getBytes()), encodeUrl("we".getBytes(UTF_8))); - assertEquals(base64Url().encode("web".getBytes()), encodeUrl("web".getBytes(UTF_8))); - assertEquals(base64Url().encode("web-".getBytes()), encodeUrl("web-".getBytes(UTF_8))); - assertEquals(base64Url().encode("web-p".getBytes()), encodeUrl("web-p".getBytes(UTF_8))); - assertEquals(base64Url().encode("web-pu".getBytes()), encodeUrl("web-pu".getBytes(UTF_8))); - assertEquals(base64Url().encode("web-pus".getBytes()), encodeUrl("web-pus".getBytes(UTF_8))); - assertEquals(base64Url().encode("web-push".getBytes()), encodeUrl("web-push".getBytes(UTF_8))); - assertEquals(base64Url().encode("web-push?".getBytes()), encodeUrl("web-push?".getBytes(UTF_8))); - - assertEquals("", encodeUrl("".getBytes(UTF_8))); - assertEquals("dw==", encodeUrl("w".getBytes(UTF_8))); - assertEquals("d2U=", encodeUrl("we".getBytes(UTF_8))); - assertEquals("d2Vi", encodeUrl("web".getBytes(UTF_8))); - assertEquals("d2ViLQ==", encodeUrl("web-".getBytes(UTF_8))); - assertEquals("d2ViLXA=", encodeUrl("web-p".getBytes(UTF_8))); - assertEquals("d2ViLXB1", encodeUrl("web-pu".getBytes(UTF_8))); - assertEquals("d2ViLXB1cw==", encodeUrl("web-pus".getBytes(UTF_8))); - assertEquals("d2ViLXB1c2g=", encodeUrl("web-push".getBytes(UTF_8))); - assertEquals("d2ViLXB1c2g_", encodeUrl("web-push?".getBytes(UTF_8))); - } - - @Test - void encodeUrlWithoutPaddingTest() { - // first verify non breaking changes after removing guava as compile dependency - assertEquals(base64Url().omitPadding().encode("".getBytes()), encodeUrlWithoutPadding("".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("w".getBytes()), encodeUrlWithoutPadding("w".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("we".getBytes()), encodeUrlWithoutPadding("we".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web".getBytes()), encodeUrlWithoutPadding("web".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web-".getBytes()), encodeUrlWithoutPadding("web-".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web-p".getBytes()), encodeUrlWithoutPadding("web-p".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web-pu".getBytes()), encodeUrlWithoutPadding("web-pu".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web-pus".getBytes()), encodeUrlWithoutPadding("web-pus".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web-push".getBytes()), encodeUrlWithoutPadding("web-push".getBytes(UTF_8))); - assertEquals(base64Url().omitPadding().encode("web-push?".getBytes()), encodeUrlWithoutPadding("web-push?".getBytes(UTF_8))); - - assertEquals("", encodeUrlWithoutPadding("".getBytes(UTF_8))); - assertEquals("dw", encodeUrlWithoutPadding("w".getBytes(UTF_8))); - assertEquals("d2U", encodeUrlWithoutPadding("we".getBytes(UTF_8))); - assertEquals("d2Vi", encodeUrlWithoutPadding("web".getBytes(UTF_8))); - assertEquals("d2ViLQ", encodeUrlWithoutPadding("web-".getBytes(UTF_8))); - assertEquals("d2ViLXA", encodeUrlWithoutPadding("web-p".getBytes(UTF_8))); - assertEquals("d2ViLXB1", encodeUrlWithoutPadding("web-pu".getBytes(UTF_8))); - assertEquals("d2ViLXB1cw", encodeUrlWithoutPadding("web-pus".getBytes(UTF_8))); - assertEquals("d2ViLXB1c2g", encodeUrlWithoutPadding("web-push".getBytes(UTF_8))); - assertEquals("d2ViLXB1c2g_", encodeUrlWithoutPadding("web-push?".getBytes(UTF_8))); - } -} \ No newline at end of file diff --git a/src/test/java/nl/martijndwars/webpush/HttpEceTest.java b/src/test/java/nl/martijndwars/webpush/HttpEceTest.java index fadc408..c3f6884 100644 --- a/src/test/java/nl/martijndwars/webpush/HttpEceTest.java +++ b/src/test/java/nl/martijndwars/webpush/HttpEceTest.java @@ -7,9 +7,9 @@ import org.junit.jupiter.api.Test; import java.security.*; +import java.util.Base64; import java.util.HashMap; -import static nl.martijndwars.webpush.Base64Encoder.decode; import static nl.martijndwars.webpush.Encoding.AES128GCM; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -19,6 +19,10 @@ public static void addSecurityProvider() { Security.addProvider(new BouncyCastleProvider()); } + private byte[] decode(String s) { + return Base64.getUrlDecoder().decode(s); + } + @Test public void testZeroSaltAndKey() throws GeneralSecurityException { HttpEce httpEce = new HttpEce(); diff --git a/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java b/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java index de1353e..1054486 100644 --- a/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java +++ b/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java @@ -1,6 +1,5 @@ package nl.martijndwars.webpush.selenium; -import nl.martijndwars.webpush.Base64Encoder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DynamicTest; @@ -8,6 +7,7 @@ import java.io.IOException; import java.security.Security; +import java.util.Base64; import java.util.stream.Stream; import static org.junit.jupiter.api.DynamicTest.dynamicTest; @@ -58,8 +58,8 @@ public Stream dynamicTests() throws IOException { * @return */ protected Stream getConfigurations() { - String PUBLIC_KEY_NO_PADDING = Base64Encoder.encodeWithoutPadding( - Base64Encoder.decode(PUBLIC_KEY) + String PUBLIC_KEY_NO_PADDING = Base64.getUrlEncoder().withoutPadding().encodeToString( + Base64.getUrlDecoder().decode(PUBLIC_KEY) ); return Stream.of(