Subject: Re: [Webpush] Vapid public key

From: Martin Thomson <[email-masked]>
Date: Wed, 2 Nov 2016 14:55:50 +1100

On 2 November 2016 at 05:02, Costin Manolache <[email-masked]> wrote:
> I would also add that some of the bugs we've seen in our implementation
> were related to parsing and passing along the crypto key. Even for
> VAPID I would love to see the sender public key appended to the
> Authorization header somehow instead of in a separate Crypto-Key header.

Putting the public key in the JWT header is probably doable, but it
means double base64 encoding for key. I'd want to hear that people are
OK with it before making a change.

Note that this is probably a net increase in header size (I make it to
be a 22 octet increase, even losing the Crypto-Key header field
entirely).  Header size probably isn't that important on this request
though.

---

From: Costin Manolache <[email-masked]>
Date: Wed, 02 Nov 2016 05:10:57 +0000

Can we do something like:

Authorization: webpush JWT;PUBLICKEY

or even

Authorization: webpush PUBLICKEY:JWT_TOKEN

The public key is the 'identity' of the sender, the JWT token is
authenticating it -
it's common to use USER:SECRET.

I agree having the publickey inside the JWT token - and double b64 - is
ugly, also
it may be easier to verify the signature first, and decode the body after.

Costin

---

From: Martin Thomson <[email-masked]>
Date: Wed, 2 Nov 2016 16:59:55 +1100

> Authorization: webpush PUBLICKEY:JWT_TOKEN

The grammar (RFC 7235) is:

     credentials = auth-scheme [ 1*SP ( token68 / #auth-param ) ]
     token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="

With JWT, we've opted to use the "token68" form (also shown here).
Given that base64url takes '-', '_', and '.', we could separate on
'/'.  '/' is a valid base64 (the non-url-safe variant) character, but
we could split on that.

For existing implementations, you would have to accept that you are
going to have to sniff for this, or we could use a new auth-scheme.  I
think that sniffing should be workable given that you won't have a
Crypto-Key header field.

And before I forget, the ugliest option of all is to use JWK inside
the JWT.  Here's an example of a JWK:

{"crv":"P-256","ext":true,"key_ops":["verify"],"kty":"EC","x":"20zCfUuIs0NGtaVxENI4VH0YyJOUuxp973BTZTfhe1A","y":"WEMWyistS_sD6gGLN4IISWdIMQxZoCHAhlZ8zkmcVUI"}

The ext and key_ops fields can be omitted safely - I just pulled this
straight out of webcrypto - though it's still even bigger than you
might like.

---

From: jr conlin <[email-masked]>
Date: Wed, 2 Nov 2016 08:32:00 -0700

I'm ok with making the new auth scheme:
2/PUBLICKEY/JWT_TOKEN

While this would break older libraries (ick) it would at least allow
servers to easily determine if this is a new form rather than an older
version.

I'll also add that many libraries have a limit to the length of the
header line they're willing to accept. For instance, python's twisted
library tops out header lines at 16384 characters, and a 16K limit is
pretty common for others.
https://twistedmatrix.com/documents/current/api/twisted.protocols.basic.LineReceiver.html
We're currently far short of that, but remember that people will be
putting extra values inside of their JWT so that Ops teams can properly
coordinate, and those values will be re-encoded as base64. So it's quite
possible that folks could bump up against that limit.

---

From: Costin Manolache <[email-masked]>
Date: Wed, 02 Nov 2016 18:11:47 +0000

IMHO the main problem with JWK is not size, but the inconsistency - would
be good for the
exact string used in subscribe() to also show in Authenticate.

Any problem with using auth-param:

Authorization: webpush JWTTOKEN, id=PUBKEY

Costin

---

From: Costin Manolache <[email-masked]>
Date: Wed, 02 Nov 2016 18:14:04 +0000

>From rfc2617 (digest auth):

Authorization: Digest username="Mufasa",
                 realm="[email-masked]",
                 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                 uri="/dir/index.html",
                 qop=auth,
                 nc=00000001,
                 cnonce="0a4f113b",
                 response="6629fae49393a05397450978507c4ef1",
                 opaque="5ccc069c403ebaf9f0171e9517f40e41"

---

From: jr conlin <[email-masked]>
Date: Wed, 2 Nov 2016 12:43:26 -0700

On 11/2/2016 11:14 AM, Costin Manolache wrote:
>                  realm="[email-masked] <mailto:[email-masked]>",
No "favoriteDrink"?

/me fights back his x.500 PTSD.

---

From: Costin Manolache <[email-masked]>
Date: Wed, 02 Nov 2016 20:22:40 +0000

Apologies for any bad memories related to implementing digest auth :-)

If we are updating the vapid spec - I would like to ask for one more change:

"A UA may also authenticate with VAPID when making a subscribe request. If
subscribe was
authenticated with VAPID, all subsequent subscribe, unsubscribe, ack or
monitor requests
associated with the same pushset MUST be authenticated with VAPID with same
public key"

I know subscribe/monitor are over SSL - but I think it improves a bit the
security, and the device
already needs to have code to handle EC for the e2e encryption. The main
concern is
an attacker 'acking' or unsubscribing - since the messages are e2e
encrypted they can't read it.

Costin

---

From: Martin Thomson <[email-masked]>
Date: Thu, 3 Nov 2016 11:49:50 +1100

Well, I haven't revised VAPID recently (it missed the cutoff due to a
rush on -encryption).  I have an open PR, and now these changes.  I'll
add this to the pile.  (I think that I'd phrase this slightly
differently, but it's reasonable to point out that this new
authentication scheme is available to the UA and PS).

https://github.com/webpush-wg/webpush-vapid/issues/27

On 3 November 2016 at 07:22, Costin Manolache <[email-masked]> wrote:
> an attacker 'acking' or unsubscribing - since the messages are e2e encrypted
> they can't read it.
>
> Costin
>

---

From: Martin Thomson <[email-masked]>
Date: Thu, 3 Nov 2016 11:56:27 +1100

> Authorization: webpush JWTTOKEN, id=PUBKEY

Yeah, authparam requires that everything have a key-value pair.  We
could do it though.  It's a toss-up:

Authorization: webpush JWTTOKEN.blah.blah_/AApubkey-___

Authorization: webpush token="JWTTOKEN.blah.blah",k="AApubkey-___"

I could live with either.  The second form is recommended by RFC 7235,
so we should probably pick that one.  That said, it makes sniffing
marginally harder because the double-quotes are optional and '=' is
valid in either form.  You have to look for a comma.

---

From: Martin Thomson <[email-masked]>
Date: Fri, 4 Nov 2016 14:09:45 +1100

> valid in either form.  You have to look for a comma.

Here's the proposed changes:

https://github.com/webpush-wg/webpush-vapid/pull/29

---

From: Costin Manolache <[email-masked]>
Date: Sat, 19 Nov 2016 15:53:21 +0000

One more small suggestion: one of the pain points ( for me ) is the W3C
subscribe taking the
vapid pubkey as bytes instead of a string, and having to include the key in
.js files as a constant.

Would it be possible to define a .well-known/vapid/... file where the
public key can be saved ?
This may simplify tools, testing ( test env may use test sender keys), etc.
One problem is that
.well-known is at root - so either have
/.well-known/vapid/ENCODED_SW_URL.pub or
have a less standard .well-known/ in the same directory with the SW.

This would be optional - if the key is not included in the register call
explicitly.

GCM has a similar mechanism.

Costin

---

From: Martin Thomson <[email-masked]>
Date: Mon, 21 Nov 2016 12:32:19 +1100

> vapid pubkey as bytes instead of a string, and having to include the key in

Your suggestion being that we define a base64url parser.  You can
include the key in the form Uint8Array.from([4, 5, 6, 6, ...]);  It's
bigger and you have to work it out once, but it saves shipping a
converter.

Either way, this is probably something to take here:
https://github.com/w3c/push-api/issues

> Would it be possible to define a .well-known/vapid/... file where the public
> key can be saved ?

Now that we are considering removal of Crypto-Key (I'm about to send
out drafts with that change), I need to work out how to push the keys
along with the registration.  I don't think that using .well-known
will give us the right binding to the subscription request though.

Maybe I don't understand the proposal, though.  Can you walk through
how a client would use this?

---

From: Costin Manolache <[email-masked]>
Date: Mon, 21 Nov 2016 04:01:52 +0000

> bigger and you have to work it out once, but it saves shipping a

Not really - the UA will need to take the uint8[] and convert it to b64 -
since that's what
the push service expects. AFAIK the UA has no use with the vapid public key
in current
protocol (except pass it along in subscribe) - we do not pass the
Authorization header
from push service to UA

> public

If client calls subscribe() - the UA will look for .well-known/vapid.pub
and pass it
in the /subscribe request to the push service.

If client calls subscribe( Uint8Array ) - UA will use the explicit key.

It is a bit simpler to just copy the key in a defined location - in
particular if you have
multiple environments. It is possible to do it in code too ( using the URL
to decide
which key to use ). Some people may also like the more declarative
approach.

Not a big deal - but I think it would be nice.

Re. Crypto-Key: my understanding was that there are use cases for it, as a
way to
distribute keys, as discussed in the other thread. Subscribe needs some
header
( or query param ?) to pass the authorized entity to the push service. We
just
don't need it for authorization or encrypted body.

Costin

---

From: jr conlin <[email-masked]>
Date: Mon, 21 Nov 2016 09:51:39 -0800

FWIW, I'm really not a fan of having an extra data path for things like
this. More paths mean more things that folks can screw up or not be able
to implement for various reasons.

I think it's HIGHLY reasonable to ask that for a given restricted
update, the Application that is making the request include a public key
for the VAPID spec. The application could pull it from where ever, hard
code it, or whatever makes most sense to the Application developer team
(and the corresponding Subscription Update service provider).

Things aren't exactly easy now, making them harder just means that less
folks will be able to do it.

On 11/20/2016 8:01 PM, Costin Manolache wrote:
>
>
> On Sun, Nov 20, 2016 at 5:32 PM Martin Thomson
> <[email-masked] <mailto:[email-masked]>> wrote:
>
>     On 20 November 2016 at 02:53, Costin Manolache <[email-masked]
>     <mailto:[email-masked]>> wrote:
>     bigger and you have to work itI"m a out once, but it saves shipping a
>     converter.
>
>
> Not really - the UA will need to take the uint8[] and convert it to
> b64 - since that's what 
> the push service expects. AFAIK the UA has no use with the vapid
> public key in current

I'm a bit confused here. If you use most key generation tools like
$ openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem
$ openssl ec -in vapid_private.pem -pubout -out vapid_public.pem
$ grep -v "^-" vapid_public.pe
that dumps a b64 string the user can easily work with. Is the suggestion
that we ask users to convert to binary themselves? That may reduce the
need for base64 in the UA, but means that developers will need to add
one. Considering the fun that happened with "lpad" not too long ago, I'm
a bit concerned about the frustration that may come with that.

> If client calls subscribe() - the UA will look for
> .well-known/vapid.pub and pass it 
... So the subscription provider will have to be able to create and
maintain a discrete path, the UA will need to make an extra successful
data call, just to provide a string that is for public consumption?
> If client calls subscribe( Uint8Array ) - UA will use the explicit key.
Again, bit confused here. Granted, the UA's subscribe() function can do
whatever it wants once it has the public key. How the server actually
enacts the subscription restriction is irrelevant to both the UA and the
Subscription provider.

The UA can also accept a key in several different formats by doing
simple tests. Likewise, how the Application acquires the key is really
up to the Application.

I'm seeing this more as "suggested practices" and "platform features"
rather than part of the specification.
> multiple environments. It is possible to do it in code too ( using the
> URL to decide
Huh, I'd argue that it may not always be possible. Some small vendor
platforms may not offer access to directories like "/.well-known" (e.g.
if you're working with classical Apache, you may be forced to root under
"/username/...", or some platforms may prohibit the use of leading "."
because of poor path management. I've had to deal with both in the past.

Hopefully, I'm must misunderstanding the proposal again.

> Re. Crypto-Key: my understanding was that there are use cases for it,
> as a way to 
> distribute keys, as discussed in the other thread. Subscribe needs
> some header 
> ( or query param ?) to pass the authorized entity to the push service.
> We just

---

From: Costin Manolache <[email-masked]>
Date: Mon, 21 Nov 2016 23:36:43 +0000

On Mon, Nov 21, 2016 at 9:51 AM jr conlin <[email-masked]> wrote:

> this. More paths mean more things that folks can screw up or not be able to
> implement for various reasons.
>

> I think it's HIGHLY reasonable to ask that for a given restricted update,
> the Application that is making the request include a public key for the
> VAPID spec. The application could pull it from where ever, hard code it, or
> whatever makes most sense to the Application developer team (and the
> corresponding Subscription Update service provider).
>

My goal was to make it easier - in particular in cases where it's easy to
screw up. If a developer has a test key and
a production key ( which is a good idea - you want the prod private key
well protected, and dev/testing be done with
a different key ), it would be easier to have a different pub key file
rather than different .js file or extra complexity/if

It is a bit more work for the UA - but not much more.

> the push service expects. AFAIK the UA has no use with the vapid public
> key in current
> that we ask users to convert to binary themselves? That may reduce the need
> for base64 in the UA, but means that developers will need to add one.
> Considering the fun that happened with "lpad" not too long ago, I'm a bit
> concerned about the frustration that may come with that.
>

My suggestion is to _not_ ask the users to convert to binary ( or array of
decimals ) themselves - that's what the
current W3C API requires. I'll file the bug when I return (if Peter or
someone else doesn't beat me to it :-).

And as currently defined, the browser will need to take the Uint8array and
b64 encode it before calling the
webpush /subscribe. I think it would be better if W3C subscribe would take
a string, and just pass it to the push
service with no conversions - and let the pushservice deal with it.
It would also be more future proof.

> maintain a discrete path, the UA will need to make an extra successful data
> call, just to provide a string that is for public consumption?
>

subscribe is not a very frequent event - and a page requires plenty of
fetches.
The convenience to just copy a file - instead of editing a JS file - may or
may not be worth it,
I'm not feeling very strongly about it.

> The UA can also accept a key in several different formats by doing simple
> tests. Likewise, how the Application acquires the key is really up to the
> Application.
>

Agreed. UA can also just pass it to push service, and let the push service
decide how to interpret it.

> platforms may not offer access to directories like "/.well-known" (e.g. if
> you're working with classical Apache, you may be forced to root under

My concern was to make it easier for developers to specify the public key.
The '.well-known' was just one idea, keeping the manifest or using a
relative path to SW, or anything else
is fine too.

Costin

---

From: jr conlin <[email-masked]>
Date: Mon, 21 Nov 2016 15:53:33 -0800

Ah, ok, my apologies.

Summing up my understanding:

* Yes, having the UA pass the public key string to the push server would
be the easiest for the developer. "Ok, here's a key, you deal with it."
There would need to be some effort on either the UA or the Push Service
to verify that the string is a valid key set, but I believe you're
specifying that. As I understand, a provided public key that is
determined as being invalid whether by the UA or PS, would return an
error to the application.

* How that key is passed in to the UA is a point of discussion. It could
be provided as part of the static page content provided by the
subscription request page, it could be provided via a secondary delivery
mechanism (e.g. included as part of a secondary script), it could be
fetched by the application out of a well known URL (be it
"/.well-known", via the app manifest, or
"bobs-discount-public-key-goes-here.js") I'm less inclined to make this
bit part of the formal spec so much as offering guidance for how app
developers could do this. Various 3rd party sites, hosting sites, or
library authors may have differing opinions based on experience and
restrictions.

* I should not read spec changes over the weekend.

Does this properly sum up the changes?

(Again, my apologies if I seem dense)

On 11/21/2016 3:36 PM, Costin Manolache wrote:
> On Mon, Nov 21, 2016 at 9:51 AM jr conlin <[email-masked]
> <mailto:[email-masked]>> wrote:
>
>     FWIW, I'm really not a fan of having an extra data path for things
>     like this. More paths mean more things that folks can screw up or
>     not be able to implement for various reasons.
>
>
>
>     update, the Application that is making the request include a
>     public key for the VAPID spec. The application could pull it from
>     where ever, hard code it, or whatever makes most sense to the
>     Application developer team (and the corresponding Subscription
>     Update service provider).
>
>     Things aren't exactly easy now, making them harder just means that
>     less folks will be able to do it.
>
>
> My goal was to make it easier - in particular in cases where it's easy
> to screw up. If a developer has a test key and 
> a production key ( which is a good idea - you want the prod private
> key well protected, and dev/testing be done with
>>         On 20 November 2016 at 02:53, Costin Manolache
>>         <[email-masked] <mailto:[email-masked]>> wrote:
>>     Not really - the UA will need to take the uint8[] and convert it
>>     to b64 - since that's what 
>     $ grep -v "^-" vapid_public.pe <http://vapid_public.pe>
>     that dumps a b64 string the user can easily work with. Is the
>     suggestion that we ask users to convert to binary themselves? That
>     may reduce the need for base64 in the UA, but means that
>     developers will need to add one. Considering the fun that happened
>     with "lpad" not too long ago, I'm a bit concerned about the
>     frustration that may come with that.
>
>
> My suggestion is to _not_ ask the users to convert to binary ( or
> array of decimals ) themselves - that's what the
> And as currently defined, the browser will need to take the Uint8array
> and b64 encode it before calling the 
> webpush /subscribe. I think it would be better if W3C subscribe would
> take a string, and just pass it to the push 
>     ... So the subscription provider will have to be able to create
>     and maintain a discrete path, the UA will need to make an extra
>     successful data call, just to provide a string that is for public
>     consumption?
>
>
> The convenience to just copy a file - instead of editing a JS file -
> may or may not be worth it, 
>     Again, bit confused here. Granted, the UA's subscribe() function
>     can do whatever it wants once it has the public key. How the
>     server actually enacts the subscription restriction is irrelevant
>     to both the UA and the Subscription provider.
>
>     simple tests. Likewise, how the Application acquires the key is
>     really up to the Application.
>
>
> Agreed. UA can also just pass it to push service, and let the push
> service decide how to interpret it. 
>
>  
>
>     I'm seeing this more as "suggested practices" and "platform
>     features" rather than part of the specification.
>
>  
>
>>     It is a bit simpler to just copy the key in a defined location -
>>     in particular if you have 
>>     multiple environments. It is possible to do it in code too (
>>     using the URL to decide
>>     which key to use ). Some people may also like the more
>>     declarative approach.
>     Huh, I'd argue that it may not always be possible. Some small
>     vendor platforms may not offer access to directories like
>     "/.well-known" (e.g. if you're working with classical Apache, you
>     may be forced to root under "/username/...", or some platforms may
>     prohibit the use of leading "." because of poor path management.
>     I've had to deal with both in the past.
>
>>     Re. Crypto-Key: my understanding was that there are use cases for
>>     it, as a way to 
>>     distribute keys, as discussed in the other thread. Subscribe
>>     needs some header 
>>     ( or query param ?) to pass the authorized entity to the push
>>     service. We just

---

From: Martin Thomson <[email-masked]>
Date: Fri, 25 Nov 2016 11:22:47 +1100

I think that we should discuss this over on the W3C side:
Opened: https://github.com/w3c/push-api/issues/226

On 22 November 2016 at 10:53, jr conlin <[email-masked]> wrote:
> * Yes, having the UA pass the public key string to the push server would be
> the easiest for the developer. "Ok, here's a key, you deal with it." There
> would need to be some effort on either the UA or the Push Service to verify
> that the string is a valid key set, but I believe you're specifying that. As
> I understand, a provided public key that is determined as being invalid
> whether by the UA or PS, would return an error to the application.
>
> * How that key is passed in to the UA is a point of discussion. It could be
> provided as part of the static page content provided by the subscription
> request page, it could be provided via a secondary delivery mechanism (e.g.
> included as part of a secondary script), it could be fetched by the
> application out of a well known URL (be it "/.well-known", via the app
> manifest, or "bobs-discount-public-key-goes-here.js") I'm less inclined to
> make this bit part of the formal spec so much as offering guidance for how
> app developers could do this. Various 3rd party sites, hosting sites, or
> a production key ( which is a good idea - you want the prod private key well
> protected, and dev/testing be done with
> webpush /subscribe. I think it would be better if W3C subscribe would take a
> string, and just pass it to the push
>> whatever it wants once it has the public key. How the server actually enacts
>> the subscription restriction is irrelevant to both the UA and the
>> Subscription provider.
>>