1
votes

I’m trying to use web push notifications with the web push protocol in my app. In order to use the Push API with VAPID I need an applicationServerKey.

The PushManager subscribe method takes a VAPID key (public key alone) as a parameter and will give a subscription end point and keys to push messages.

To generate VAPID keys, I have been using node.js (google web-push package) and openssl till now. But in my use case VAPID keys should be generated within Java and passed to JavaScript to subscribe from the browser.

I am trying with the code below in Java to generate VAPID keys. I am able to create keys successfully but when I pass the generated public key (base64-encoded string), the subscribe method returns an error saying:

Unable to register service worker. DOMException: Failed to execute 'subscribe' on 'PushManager': The provided applicationServerKey is not valid..

Please help me resolve this issue. Below is my Java code:

ECNamedCurveParameterSpec parameterSpec = 
ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair serverKey = keyPairGenerator.generateKeyPair();

PrivateKey priv = serverKey.getPrivate();
PublicKey pub = serverKey.getPublic();`
System.out.println(Base64.toBase64String(pub.getEncoded()));
3
Hi - Any luck? Can you pls provide me some pointers too? - csharpnewbie

3 Answers

4
votes

Please refer below link for answer from MartijnDwars. https://github.com/web-push-libs/webpush-java/issues/30

you can use Utils.savePublicKey to convert your Java-generated PublicKey to a byte[]. This byte[] is then passed to the PushManager.subscribe method.

It may be more convenient to base64 encode the byte[] in Java and base64 decode the string in JavaScript. For example, after generating the keypair in Java:

KeyPair keyPair = generateKeyPair();
byte[] publicKey = Utils.savePublicKey((ECPublicKey) keyPair.getPublic());
String publicKeyBase64 = BaseEncoding.base64Url().encode(publicKey);
System.out.println("PublicKey = " + publicKeyBase64);
// PublicKey = BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=

Then in JavaScript:

function subscribe() {
    const publicKey = base64UrlToUint8Array('BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=');

    navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
        serviceWorkerRegistration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: publicKey
        })
        .then(function (subscription) {
            return sendSubscriptionToServer(subscription);
        })
        .catch(function (e) {
            if (Notification.permission === 'denied') {
                console.warn('Permission for Notifications was denied');
            } else {
                console.error('Unable to subscribe to push.', e);
            }
        });
    });
}

function base64UrlToUint8Array(base64UrlData) {
    const padding = '='.repeat((4 - base64UrlData.length % 4) % 4);
    const base64 = (base64UrlData + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

    const rawData = atob(base64);
    const buffer = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
        buffer[i] = rawData.charCodeAt(i);
    }

    return buffer;
}
1
votes

Having spent hours on this, I thought I would share the solution I worked out from gleaning several web sites, avoiding using Bouncy Castle which generated lots of other issues. Try the below:

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
keyPairGenerator.initialize(spec, new SecureRandom()); 
KeyPair keyPair = keyPairGenerator.generateKeyPair();

ECPublicKey publicKey = (ECPublicKey)   keyPair.getPublic();
ECPoint ecp = publicKey.getW();

byte[] x = ecp.getAffineX().toByteArray();
byte[] y = ecp.getAffineY().toByteArray();
// Convert 04 to bytes
String s= "04";
int len = s.length();
byte[] firstBit = new byte[len / 2];
for (int i = 0; i < len; i += 2) 
{
    firstBit[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + 
Character.digit(s.charAt(i+1), 16));
}

ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write(firstBit);
outputStream.write(x);
outputStream.write(y);


publicKeyBytes = outputStream.toByteArray( );

Base64 encoder = new Base64(-1,null,true);
byte[] encodedBytes = encoder.encode(publicKeyBytes);
String publicKeyBase64 = new String(encodedBytes, StandardCharsets.UTF_8);
1
votes

With bountycastle you can generate vapid keys with this code :

    ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
    keyPairGenerator.initialize(parameterSpec);
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
    ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
    String publicKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(publicKey.getQ().getEncoded(false));
    System.out.println(publicKeyString);
    String privateKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(privateKey.getD().toByteArray());
    System.out.println(privateKeyString);