Open thihara opened 8 years ago
Both userPublicKey
and userAuth
are generated by the browser. More details can be found in the specification. Specifically, userPublicKey
and userAuth
correspond to the following two keys:
- Generate a new P-256 ECDH key pair. Store the private key in an internal slot associated with the subscription; this value must not be made available to applications. The public key is also stored in an internal slot and can be retrieved by calling the getKey method of the PushSubscription with an argument of p256dh.
- Generate a new authentication secret, which is a sequence of octets as defined in [WEBPUSH-ENCRYPTION]. Store the authentication secret in an internal slot associated with the subscription. This key can be retrieved by calling the getKey method of the PushSubscription with an argument of auth.
I use the following JavaScript code to send both keys to the server. Note that this snippet uses the Fetch API, which is an experimental technology, but you probably get the idea.
var key = subscription.getKey ? subscription.getKey('p256dh') : '';
var auth = subscription.getKey ? subscription.getKey('auth') : '';
return fetch('/profile/subscription', {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': $('meta[name="_csrf"]').attr('content')
},
method: 'POST',
body: JSON.stringify({
endpoint: subscription.endpoint,
key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '',
auth: auth ? btoa(String.fromCharCode.apply(null, new Uint8Array(auth))) : ''
})
});
The full code for registering a service worker, subscribing to push messages, and sending the keys to the server can be found here. I'll try to update the README to make things more clear.
Also please confirm that the PushService's default constructor is to be used with non-chrome browsers (Firefox at the moment.). Seem to be the case but I'd prefer to confirm and update the docs.
Chrome's Push API uses Google Cloud Messaging (GCM) and GCM requires an API key on the server (and gcm_sender_id and manifest on the client, full setup instructions here). As the Web Push Protocol matures Chrome will move towards using the Web Push Protocol and the API key will become superfluous. I'll update the README to reflect this information.
TL;DR. You are correct. If you are only targeting FireFox, you can use the no-args constructor.
@MartijnDwars Thank you for the example of the javascript code, it saved me a lot of time. Recommend you put it in some documentation somewhere :)
However, could you please also post the way you parse the key serverside? I am doing the following, but no luck:
final byte[] publicBytes = Base64.decode(input.getUserPubKey());
final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicBytes);
// Get ECDH algorithm from bouncyCastle
final KeyFactory keyFactory = KeyFactory.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME);
final PublicKey userPublicKey = keyFactory.generatePublic(pubKeySpec);
On the last line it throws the following:
java.security.spec.InvalidKeySpecException: encoded key spec not recognised
at org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi.engineGeneratePublic(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(KeyFactory.java:328)
I used your method of base64 encoding the key before sending it, and one examle of a key looks like this:
BPta2KYHoeh15Cro4ZbM3TbGV6FOhL9kzJ66+YtxS17J4gToS3LmOSVVOhHzIyux62Lq4+NzAXUvoD3HwJptMDg=
I am using oracle jdk 8
Hi @esphen,
I first send the subscription to the server (i.e. the endpoint
, key
, and auth
in push.js). When I need to send a notification, I retrieve the key
and auth
as follows. Note the getAuthAsBytes
and getUserPublicKey
methods (you'll probably be interested in the latter).
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
class Subscription {
... // Getters and setters for e.g. endpoint
public void setAuth(String auth) {
this.auth = auth;
}
public String getAuth() {
return auth;
}
// Convert the base64 auth string to byte[]
public byte[] getAuthAsBytes() {
return Base64.getDecoder().decode(getAuth());
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
// Convert the base64 key string to byte[]
public byte[] getKeyAsBytes() {
return Base64.getDecoder().decode(getKey());
}
// Convert the string key to a PublicKey object
public PublicKey getUserPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECPoint point = ecSpec.getCurve().decodePoint(getKeyAsBytes());
ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, ecSpec);
return kf.generatePublic(pubSpec);
}
}
To send a notification I first construct a notification object as follows:
new Notification(
subscription.getEndpoint(),
subscription.getUserPublicKey(),
subscription.getAuthAsBytes(),
payload
);
and then I send it using:
pushService.send(notification);
Thanks! It seems to work as intended now.
@MartijnDwars would you be willing to merge a doc
folder with a UsageExample.md
, detailing how to use the library from the user agent, all the way to the backend? I could write a suggestion based on the information in this thread.
@MartijnDwars would you be willing to merge a doc folder with a UsageExample.md, detailing how to use the library from the user agent, all the way to the backend? I could write a suggestion based on the information in this thread.
Definitely. I'm open to PRs! :)
I am using the code exactly as mentioned in your "https://github.com/web-push-libs/webpush-java/wiki/Usage-Example". I am getting error specifically when generating pubic key in the following code (calling Java APIs from my Scala code):
val kf: KeyFactory = KeyFactory.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME)
val ecSpec: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1")
val point: ECPoint = ecSpec.getCurve().decodePoint(Base64.getUrlDecoder().decode(v.keys.p256dh))
val pubSpec: ECPublicKeySpec = new ECPublicKeySpec(point, ecSpec)
kf.generatePublic(pubSpec)
I am using 1.62 version for bcprov-jdk15on and bcpkix-jdk15on. I also tried ECNamedCurveTable.getParameterSpec("secp256r1") but same error. Error:
2019-06-14 06:47:52,331 [error] a.d.TaskInvocation - Unknown KeySpec type: org.bouncycastle.jce.spec.ECPublicKeySpec
java.security.spec.InvalidKeySpecException: Unknown KeySpec type: org.bouncycastle.jce.spec.ECPublicKeySpec
at org.bouncycastle.jce.provider.asymmetric.ec.KeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(KeyFactory.java:328)
Just a few things that aren't clear at once by reading the documentation is what the PublicKey and userAuth are.
I believe userAuth is the key generated by the push messaging service from the browser side. Is the PublicKey somehow our public key?
Also please confirm that the PushService's default constructor is to be used with non-chrome browsers (Firefox at the moment.). Seem to be the case but I'd prefer to confirm and update the docs.