Documentation
¶
Overview ¶
Package webpush sends Web Push messages: RFC 8030 delivery, the payload encrypted for the receiving browser (RFC 8291, "aes128gcm") and every request signed with the application server's VAPID key (RFC 8292).
It is the transport and nothing else. Who is told what, and when, is internal/notify's (push.go): a push is one more reader of a person's bell, never a channel with rules of its own.
Standard library only: the key is ECDSA P-256 (crypto/ecdsa for the VAPID signature, crypto/ecdh for the payload's key agreement), the key schedule is crypto/hkdf, the record is AES-128-GCM.
⚠ An endpoint is written by the browser but reaches the server in a person's request, so it is an address a person chose. Policy.Check (an allow-list of the browsers' push services) and SafeClient (no address on this machine or a private network, no proxy, no redirect) are what keep the sender from being a way to make the server POST into its own network.
Index ¶
- Constants
- Variables
- func CheckKeys(p256dh, auth string) error
- func Decrypt(body []byte, ua *ecdh.PrivateKey, authSecret []byte) ([]byte, error)
- func Encrypt(plaintext []byte, p256dh, auth string) ([]byte, error)
- func EndpointHash(endpoint string) string
- func PublicAddr(ip netip.Addr) bool
- func SafeClient(timeout time.Duration) *http.Client
- func ServiceOf(endpoint string) string
- type Key
- type Message
- type Policy
- type Result
- type Sender
- type Subscription
Constants ¶
const MaxEndpoint = 2048
MaxEndpoint bounds an endpoint's length. The longest real ones (Windows Push Notification Services) are a few hundred characters.
const MaxPayload = recordSize - headerSize - 1 - 16
MaxPayload is the most plaintext one push carries: a push service must take a 4096-byte body (RFC 8291 §4), which holds the header, the plaintext, its one-byte delimiter and the 16-byte tag.
Variables ¶
var DefaultHosts = []string{
"fcm.googleapis.com",
"android.googleapis.com",
"updates.push.services.mozilla.com",
"push.services.mozilla.com",
"web.push.apple.com",
"notify.windows.com",
}
DefaultHosts are the push services of the browsers filex supports. An endpoint's host is accepted when it is one of them or below one of them.
var ErrEndpoint = errors.New("webpush: endpoint refused")
ErrEndpoint is what Policy.Check answers an endpoint it refuses with.
var ErrKeys = errors.New("webpush: subscription keys unusable")
ErrKeys is what CheckKeys answers a subscription's unusable keys with.
var ErrTooLarge = errors.New("webpush: payload too large")
ErrTooLarge is a payload over MaxPayload.
Functions ¶
func CheckKeys ¶
CheckKeys reports whether a subscription's keys are what RFC 8291 needs: a P-256 point (65 bytes, uncompressed) and a 16-byte auth secret.
func Decrypt ¶
Decrypt is the browser's half of Encrypt: what a subscription whose private key is ua and whose auth secret is authSecret reads out of body. filex never receives a push; this is how its tests read what it sent.
func Encrypt ¶
Encrypt seals plaintext for one browser subscription (RFC 8291): a fresh key agreed with the browser's p256dh key, the auth secret mixed in, a random salt, one aes128gcm record. The result is the request's body.
func EndpointHash ¶
EndpointHash is the lower-hex SHA-256 of an endpoint, as given. It is how a device is named on the wire (GET /api/notifications/push): the client hashes its own subscription's endpoint the same way to find itself in the list, and the endpoint itself - an address anybody holding it may push to, given the key - never leaves the server.
func PublicAddr ¶
PublicAddr reports whether ip is an address on the public internet: not this machine, not a private, link-local, shared or multicast network.
func SafeClient ¶
SafeClient is the client pushes go out on: direct connections only (no proxy from the environment - a proxy would dial the address for us, past the check), never to an address that is not public, and no redirects.
Types ¶
type Key ¶
type Key struct {
// contains filtered or unexported fields
}
Key is the application server's VAPID key pair (RFC 8292): ECDSA on P-256. Its public half is the `applicationServerKey` every browser subscribes with, and a push service accepts a push for a subscription only when it is signed by the private half - so a leaked endpoint alone pushes nothing.
func (*Key) Authorization ¶
Authorization is the VAPID header of a request to endpoint, valid until exp (RFC 8292 §2-3): `vapid t=<JWT>, k=<public key>`. The token's audience is the endpoint's origin; subject is the contact a push service may write to about this server (a mailto: or an https: address - Apple refuses others).
⚠ exp no later than 24 hours ahead: a push service refuses a longer one.
func (*Key) MarshalPrivate ¶
MarshalPrivate is the key as text (PKCS #8, base64) - what is sealed and stored. ⚠ A secret: it never leaves the server unsealed.
type Message ¶
type Message struct {
Payload []byte
// TTL is how long the push service keeps it for a device that is offline.
TTL time.Duration
// Urgency is very-low, low, normal or high (RFC 8030 §5.3); "" is normal.
Urgency string
}
Message is one push.
type Policy ¶
type Policy struct {
// Hosts are the push services accepted. Empty: DefaultHosts.
Hosts []string
// AnyHost accepts any https host that is a name, not an address
// (FILEX_PUSH_HOSTS=*): a browser whose push service the list does not
// know. SafeClient still refuses one that resolves into a private network.
AnyHost bool
// Insecure accepts http:// and any host and port - tests only, where the
// push service is an httptest server on this machine.
Insecure bool
}
Policy decides which endpoints a push may be sent to.
type Result ¶
type Result struct {
Status int
// Gone: the subscription no longer exists (404, 410) and is to be
// forgotten - the browser unsubscribed, or the person cleared the site.
Gone bool
}
Result is the push service's answer.
type Sender ¶
type Sender struct {
// Client is the HTTP client. Nil: SafeClient (10 s).
Client *http.Client
// Subject is the VAPID contact (mailto: or https:).
Subject string
// Now is the clock the tokens' expiry is read from. Nil: time.Now.
Now func() time.Time
}
Sender sends pushes.
type Subscription ¶
Subscription is where one browser takes its pushes: what its PushSubscription.toJSON() gives.