feat: add aesgcm support

This commit is contained in:
minish 2025-12-18 21:23:09 -05:00
parent ec5c996e82
commit ae81d0cdae
Signed by: min
SSH Key Fingerprint: SHA256:mf+pUTmK92Y57BuCjlkBdd82LqztTfDCQIUp0fCKABc
7 changed files with 430 additions and 142 deletions

View File

@ -72,7 +72,6 @@ func (c *AutoPushClient) Unregister(channelID string) (UnregisterResponse, error
} }
func (c *AutoPushClient) Decrypt( func (c *AutoPushClient) Decrypt(
curve ecdh.Curve,
authSecret []byte, authSecret []byte,
useragentPrivateKey *ecdh.PrivateKey, useragentPrivateKey *ecdh.PrivateKey,
notification Notification, notification Notification,
@ -82,25 +81,16 @@ func (c *AutoPushClient) Decrypt(
return nil, fmt.Errorf("base64 decode error: %v", err) return nil, fmt.Errorf("base64 decode error: %v", err)
} }
payload, err := rfc8291.Unmarshal(data)
if err != nil {
return nil, fmt.Errorf("rfc8291 decode error: %v", err)
}
appserverPublicKey, err := curve.NewPublicKey(payload.KeyId)
if err != nil {
return nil, fmt.Errorf("ecdh public key load error: %v", err)
}
plaintext, err := c.ece.Decrypt( plaintext, err := c.ece.Decrypt(
payload.CipherText, data,
payload.Salt, rfc8291.Encoding(notification.Headers.Encoding),
notification.Headers.Encryption,
notification.Headers.CryptoKey,
authSecret, authSecret,
useragentPrivateKey, useragentPrivateKey,
appserverPublicKey,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("rfc8291 decrypt error: %v", err) return nil, fmt.Errorf("decrypt error: %v", err)
} }
var result webpush.WebPushPayload var result webpush.WebPushPayload

View File

@ -19,7 +19,7 @@ func appserver(authSecret []byte, useragentPublicKey *ecdh.PublicKey) []byte {
_, salt, privateKey := rfc8291.NewSecrets(CURVE) _, salt, privateKey := rfc8291.NewSecrets(CURVE)
ece := rfc8291.NewRFC8291(HASH) ece := rfc8291.NewRFC8291(HASH)
encrypted, err := ece.Encrypt( encrypted, err := ece.EncryptAes128gcm(
[]byte(PLAINTEXT), []byte(PLAINTEXT),
salt, salt,
authSecret, authSecret,
@ -53,7 +53,7 @@ func main() {
log.Panicln("Load PublicKey Error", err) log.Panicln("Load PublicKey Error", err)
} }
plaintext, err := ece.Decrypt( plaintext, err := ece.DecryptAes128gcm(
payload.CipherText, payload.CipherText,
payload.Salt, payload.Salt,
authSecret, authSecret,

154
rfc8291/aes128gcm.go Normal file
View File

@ -0,0 +1,154 @@
package rfc8291
import (
"bytes"
"crypto/ecdh"
"encoding/binary"
"errors"
"fmt"
"hash"
"io"
"golang.org/x/crypto/hkdf"
)
// Aes128gcmScheme implements EncodingScheme for the aes128gcm encoding.
type Aes128gcmScheme struct{}
func (s Aes128gcmScheme) DeriveIKM(hash func() hash.Hash, authSecret, ecdhSecret []byte, uaKey, asKey *ecdh.PublicKey) ([]byte, error) {
prkKey := hkdf.Extract(hash, ecdhSecret, authSecret)
// aes128gcm: "WebPush: info\0" + receiver key + sender key
keyInfo := bytes.Join([][]byte{
[]byte("WebPush: info\000"),
uaKey.Bytes(),
asKey.Bytes(),
}, nil)
ikm := make([]byte, HKDF_IKM_LEN)
if _, err := io.ReadFull(hkdf.Expand(hash, prkKey, keyInfo), ikm); err != nil {
return nil, fmt.Errorf("derive IKM failed: %v", err)
}
return ikm, nil
}
func (s Aes128gcmScheme) DeriveCEKAndNonce(hash func() hash.Hash, ikm, salt []byte, uaKey, asKey *ecdh.PublicKey) (cek, nonce []byte, err error) {
prk := hkdf.Extract(hash, ikm, salt)
// aes128gcm: simple info strings without keys
cekInfo := []byte("Content-Encoding: aes128gcm\000")
nonceInfo := []byte("Content-Encoding: nonce\000")
cek = make([]byte, HKDF_CEK_LEN)
if _, err := io.ReadFull(hkdf.Expand(hash, prk, cekInfo), cek); err != nil {
return nil, nil, fmt.Errorf("derive CEK failed: %v", err)
}
nonce = make([]byte, HKDF_NONCE_LEN)
if _, err := io.ReadFull(hkdf.Expand(hash, prk, nonceInfo), nonce); err != nil {
return nil, nil, fmt.Errorf("derive nonce failed: %v", err)
}
return cek, nonce, nil
}
func (s Aes128gcmScheme) Pad(plaintext []byte) []byte {
// aes128gcm: append 0x02 delimiter for final record
return append(plaintext, 0x02)
}
func (s Aes128gcmScheme) Unpad(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("data is empty")
}
// aes128gcm: remove trailing 0x01 or 0x02 delimiter
last := data[len(data)-1]
if last == 0x01 || last == 0x02 {
return data[:len(data)-1], nil
}
return data, nil
}
// Payload represents the aes128gcm message format with embedded crypto parameters.
type Payload struct {
RS uint32
Salt []byte
KeyId []byte
CipherText []byte
}
const (
baseHeaderLen = 21
)
// Marshal serializes a Payload into the aes128gcm binary format.
func Marshal(p Payload) (data []byte) {
rs := make([]byte, 4)
binary.BigEndian.PutUint32(rs, p.RS)
return bytes.Join([][]byte{
p.Salt,
rs,
{uint8(len(p.KeyId))},
p.KeyId,
p.CipherText,
}, nil)
}
// Unmarshal parses the aes128gcm binary format into a Payload.
func Unmarshal(data []byte) (p Payload, err error) {
if len(data) < baseHeaderLen {
return p, errors.New("data is too short")
}
p.Salt = data[:16]
p.RS = binary.BigEndian.Uint32(data[16:20])
idlen := int(data[20])
if len(data) < baseHeaderLen+idlen {
return p, errors.New("data is too short")
}
if idlen > 0 {
p.KeyId = data[baseHeaderLen : baseHeaderLen+idlen]
}
p.CipherText = data[baseHeaderLen+idlen:]
return p, nil
}
// EncryptAes128gcm encrypts a message using the aes128gcm encoding scheme.
// Returns the complete payload with embedded crypto parameters.
func (c *RFC8291) EncryptAes128gcm(
plaintext []byte,
salt []byte,
authSecret []byte,
receiverPublicKey *ecdh.PublicKey,
senderPrivateKey *ecdh.PrivateKey,
) ([]byte, error) {
ciphertext, err := c.encrypt(Aes128gcmScheme{}, plaintext, salt, authSecret, receiverPublicKey, senderPrivateKey)
if err != nil {
return nil, err
}
rs := uint32(len(plaintext) + 1 + AES_GCM_OVERHEAD)
return Marshal(Payload{
RS: rs,
Salt: salt,
KeyId: senderPrivateKey.PublicKey().Bytes(),
CipherText: ciphertext,
}), nil
}
// DecryptAes128gcm decrypts a message encrypted with the aes128gcm encoding scheme.
func (c *RFC8291) DecryptAes128gcm(
ciphertext []byte,
salt []byte,
authSecret []byte,
receiverPrivateKey *ecdh.PrivateKey,
senderPublicKey *ecdh.PublicKey,
) ([]byte, error) {
return c.decrypt(Aes128gcmScheme{}, ciphertext, salt, authSecret, receiverPrivateKey, senderPublicKey)
}

184
rfc8291/aesgcm.go Normal file
View File

@ -0,0 +1,184 @@
package rfc8291
import (
"bytes"
"crypto/ecdh"
"encoding/base64"
"fmt"
"hash"
"io"
"strings"
"golang.org/x/crypto/hkdf"
)
// AesgcmScheme implements EncodingScheme for the aesgcm encoding.
type AesgcmScheme struct{}
func (s AesgcmScheme) DeriveIKM(hash func() hash.Hash, authSecret, ecdhSecret []byte, uaKey, asKey *ecdh.PublicKey) ([]byte, error) {
prkKey := hkdf.Extract(hash, ecdhSecret, authSecret)
// aesgcm: just "Content-Encoding: auth\0" without keys
keyInfo := []byte("Content-Encoding: auth\000")
ikm := make([]byte, HKDF_IKM_LEN)
if _, err := io.ReadFull(hkdf.Expand(hash, prkKey, keyInfo), ikm); err != nil {
return nil, fmt.Errorf("derive IKM failed: %v", err)
}
return ikm, nil
}
func (s AesgcmScheme) DeriveCEKAndNonce(hash func() hash.Hash, ikm, salt []byte, uaKey, asKey *ecdh.PublicKey) (cek, nonce []byte, err error) {
prk := hkdf.Extract(hash, ikm, salt)
// aesgcm: info includes "P-256\0" and length-prefixed public keys
uaKeyBytes := uaKey.Bytes()
asKeyBytes := asKey.Bytes()
context := bytes.Join([][]byte{
[]byte("P-256\000"),
{0, byte(len(uaKeyBytes))},
uaKeyBytes,
{0, byte(len(asKeyBytes))},
asKeyBytes,
}, nil)
cekInfo := append([]byte("Content-Encoding: aesgcm\000"), context...)
nonceInfo := append([]byte("Content-Encoding: nonce\000"), context...)
cek = make([]byte, HKDF_CEK_LEN)
if _, err := io.ReadFull(hkdf.Expand(hash, prk, cekInfo), cek); err != nil {
return nil, nil, fmt.Errorf("derive CEK failed: %v", err)
}
nonce = make([]byte, HKDF_NONCE_LEN)
if _, err := io.ReadFull(hkdf.Expand(hash, prk, nonceInfo), nonce); err != nil {
return nil, nil, fmt.Errorf("derive nonce failed: %v", err)
}
return cek, nonce, nil
}
func (s AesgcmScheme) Pad(plaintext []byte) []byte {
// aesgcm: 2-byte big-endian padding length prefix (0 padding)
result := make([]byte, 2+len(plaintext))
// First two bytes are 0 (no padding), already zero-initialized
copy(result[2:], plaintext)
return result
}
func (s AesgcmScheme) Unpad(data []byte) ([]byte, error) {
if len(data) < 2 {
return nil, fmt.Errorf("data too short for aesgcm padding")
}
padLen := int(data[0])<<8 | int(data[1])
if 2+padLen > len(data) {
return nil, fmt.Errorf("invalid padding length: %d (data length: %d)", padLen, len(data))
}
// Verify padding bytes are all zeros
for i := 2; i < 2+padLen; i++ {
if data[i] != 0 {
return nil, fmt.Errorf("invalid padding: non-zero byte at position %d", i)
}
}
return data[2+padLen:], nil
}
// EncryptResult holds the result of aesgcm encryption.
// Unlike aes128gcm which embeds crypto params in the payload,
// aesgcm requires these to be sent as HTTP headers.
type EncryptResult struct {
Ciphertext []byte // The encrypted data (for request body)
Salt []byte // For Encryption header: salt=<base64url>
SenderPublicKey []byte // For Crypto-Key header: dh=<base64url>
}
// CryptoParams holds the extracted cryptographic parameters for decryption.
type CryptoParams struct {
Salt []byte
SenderPublicKey *ecdh.PublicKey
}
// ParseAesgcmHeaders extracts salt and sender public key from aesgcm HTTP headers.
// encryptionHeader: e.g., "salt=FiyMDLvlVl678odI9AWL3A"
// cryptoKeyHeader: e.g., "dh=BMLYo...;p256ecdsa=BF5o..."
func ParseAesgcmHeaders(encryptionHeader, cryptoKeyHeader string, curve ecdh.Curve) (*CryptoParams, error) {
salt, err := parseHeaderParam(encryptionHeader, "salt")
if err != nil {
return nil, fmt.Errorf("failed to parse salt: %v", err)
}
if len(salt) != SALT_LEN {
return nil, fmt.Errorf("salt must be %d bytes, got %d", SALT_LEN, len(salt))
}
dhBytes, err := parseHeaderParam(cryptoKeyHeader, "dh")
if err != nil {
return nil, fmt.Errorf("failed to parse dh: %v", err)
}
senderPublicKey, err := curve.NewPublicKey(dhBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse sender public key: %v", err)
}
return &CryptoParams{
Salt: salt,
SenderPublicKey: senderPublicKey,
}, nil
}
// parseHeaderParam extracts a base64url-encoded parameter value from a header string.
func parseHeaderParam(header, paramName string) ([]byte, error) {
parts := strings.FieldsFunc(header, func(r rune) bool {
return r == ';' || r == ','
})
prefix := paramName + "="
for _, part := range parts {
part = strings.TrimSpace(part)
if strings.HasPrefix(part, prefix) {
value := strings.TrimPrefix(part, prefix)
value = strings.Trim(value, "\"")
return base64.RawURLEncoding.DecodeString(value)
}
}
return nil, fmt.Errorf("parameter %q not found in header", paramName)
}
// EncryptAesgcm encrypts a message using the aesgcm encoding scheme.
// Returns the ciphertext and crypto parameters needed for HTTP headers.
func (c *RFC8291) EncryptAesgcm(
plaintext []byte,
salt []byte,
authSecret []byte,
receiverPublicKey *ecdh.PublicKey,
senderPrivateKey *ecdh.PrivateKey,
) (*EncryptResult, error) {
ciphertext, err := c.encrypt(AesgcmScheme{}, plaintext, salt, authSecret, receiverPublicKey, senderPrivateKey)
if err != nil {
return nil, err
}
return &EncryptResult{
Ciphertext: ciphertext,
Salt: salt,
SenderPublicKey: senderPrivateKey.PublicKey().Bytes(),
}, nil
}
// DecryptAesgcm decrypts a message encrypted with the aesgcm encoding scheme.
func (c *RFC8291) DecryptAesgcm(
ciphertext []byte,
salt []byte,
authSecret []byte,
receiverPrivateKey *ecdh.PrivateKey,
senderPublicKey *ecdh.PublicKey,
) ([]byte, error) {
return c.decrypt(AesgcmScheme{}, ciphertext, salt, authSecret, receiverPrivateKey, senderPublicKey)
}

View File

@ -1,54 +0,0 @@
package rfc8291
import (
"bytes"
"encoding/binary"
"errors"
)
const (
BASE_HEADER_LEN = 21
)
type Payload struct {
RS uint32
Salt []byte
KeyId []byte
CipherText []byte
}
func Marshal(p Payload) (data []byte) {
rs := make([]byte, 4)
binary.BigEndian.PutUint32(rs, p.RS)
return bytes.Join([][]byte{
p.Salt,
rs,
{uint8(len(p.KeyId))},
p.KeyId,
p.CipherText,
}, []byte{})
}
func Unmarshal(data []byte) (p Payload, err error) {
err = errors.New("data is too short")
if len(data) < BASE_HEADER_LEN {
return p, err
}
p.Salt = data[:16]
p.RS = binary.BigEndian.Uint32(data[16:20])
idlen := int(data[20])
if len(data) < BASE_HEADER_LEN+idlen {
return p, err
}
if idlen > 0 {
p.KeyId = data[BASE_HEADER_LEN : BASE_HEADER_LEN+idlen]
}
p.CipherText = data[BASE_HEADER_LEN+idlen:]
return p, nil
}

View File

@ -1,7 +1,6 @@
package rfc8291 package rfc8291
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/ecdh" "crypto/ecdh"
@ -11,8 +10,6 @@ import (
"hash" "hash"
"io" "io"
"log" "log"
"golang.org/x/crypto/hkdf"
) )
const ( const (
@ -26,11 +23,40 @@ const (
HKDF_NONCE_LEN = 12 HKDF_NONCE_LEN = 12
) )
// Encoding represents the Content-Encoding type for WebPush messages.
type Encoding string
const (
EncodingAes128gcm Encoding = "aes128gcm"
EncodingAesgcm Encoding = "aesgcm"
)
// EncodingScheme defines the encoding-specific operations for WebPush encryption.
type EncodingScheme interface {
DeriveIKM(hash func() hash.Hash, authSecret, ecdhSecret []byte, uaKey, asKey *ecdh.PublicKey) ([]byte, error)
DeriveCEKAndNonce(hash func() hash.Hash, ikm, salt []byte, uaKey, asKey *ecdh.PublicKey) (cek, nonce []byte, err error)
Pad(plaintext []byte) []byte
Unpad(data []byte) ([]byte, error)
}
// Scheme returns the EncodingScheme implementation for the given encoding type.
func Scheme(encoding Encoding) (EncodingScheme, error) {
switch encoding {
case EncodingAes128gcm:
return Aes128gcmScheme{}, nil
case EncodingAesgcm:
return AesgcmScheme{}, nil
default:
return nil, fmt.Errorf("unsupported encoding: %s", encoding)
}
}
// RFC8291 implements WebPush message encryption and decryption.
type RFC8291 struct { type RFC8291 struct {
hash func() hash.Hash hash func() hash.Hash
} }
// Default Hash is SHA256 // NewRFC8291 creates a new RFC8291 instance. Default hash is SHA256.
func NewRFC8291(hash func() hash.Hash) *RFC8291 { func NewRFC8291(hash func() hash.Hash) *RFC8291 {
if hash == nil { if hash == nil {
hash = sha256.New hash = sha256.New
@ -38,6 +64,7 @@ func NewRFC8291(hash func() hash.Hash) *RFC8291 {
return &RFC8291{hash: hash} return &RFC8291{hash: hash}
} }
// NewSecrets generates new random auth secret, salt, and ECDH private key.
func NewSecrets(curve ecdh.Curve) (auth, salt []byte, key *ecdh.PrivateKey) { func NewSecrets(curve ecdh.Curve) (auth, salt []byte, key *ecdh.PrivateKey) {
auth = make([]byte, AUTH_SECRET_LEN) auth = make([]byte, AUTH_SECRET_LEN)
salt = make([]byte, SALT_LEN) salt = make([]byte, SALT_LEN)
@ -56,12 +83,14 @@ func NewSecrets(curve ecdh.Curve) (auth, salt []byte, key *ecdh.PrivateKey) {
return auth, salt, key return auth, salt, key
} }
func (c *RFC8291) Encrypt( // encrypt performs encryption using the specified encoding scheme.
func (c *RFC8291) encrypt(
scheme EncodingScheme,
plaintext []byte, plaintext []byte,
salt []byte, salt []byte,
authSecret []byte, authSecret []byte,
useragentPublicKey *ecdh.PublicKey, receiverPublicKey *ecdh.PublicKey,
appserverPrivateKey *ecdh.PrivateKey, senderPrivateKey *ecdh.PrivateKey,
) ([]byte, error) { ) ([]byte, error) {
if len(authSecret) != AUTH_SECRET_LEN { if len(authSecret) != AUTH_SECRET_LEN {
return nil, fmt.Errorf("auth_secret must be %d bytes", AUTH_SECRET_LEN) return nil, fmt.Errorf("auth_secret must be %d bytes", AUTH_SECRET_LEN)
@ -70,17 +99,17 @@ func (c *RFC8291) Encrypt(
return nil, fmt.Errorf("salt must be %d bytes", SALT_LEN) return nil, fmt.Errorf("salt must be %d bytes", SALT_LEN)
} }
ecdhSecret, err := appserverPrivateKey.ECDH(useragentPublicKey) ecdhSecret, err := senderPrivateKey.ECDH(receiverPublicKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("calculate ecdh_secret failed: %v", err) return nil, fmt.Errorf("calculate ecdh_secret failed: %v", err)
} }
ikm, err := c.ikm(authSecret, ecdhSecret, useragentPublicKey, appserverPrivateKey.PublicKey()) ikm, err := scheme.DeriveIKM(c.hash, authSecret, ecdhSecret, receiverPublicKey, senderPrivateKey.PublicKey())
if err != nil { if err != nil {
return nil, err return nil, err
} }
cek, nonce, err := c.cekAndNonce(ikm, salt) cek, nonce, err := scheme.DeriveCEKAndNonce(c.hash, ikm, salt, receiverPublicKey, senderPrivateKey.PublicKey())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,27 +119,20 @@ func (c *RFC8291) Encrypt(
return nil, err return nil, err
} }
ciphertext := gcm.Seal(nil, nonce, plaintext, nil) paddedPlaintext := scheme.Pad(plaintext)
rs := uint32(len(plaintext) + 1 + AES_GCM_OVERHEAD) ciphertext := gcm.Seal(nil, nonce, paddedPlaintext, nil)
// RFC8188: 0x01 or 0x02 in tail of plaintext data return ciphertext, nil
// RFC8291: The push message plaintext has the padding delimiter octet (0x02) appended to produce
// ciphertext = bytes.Join([][]byte{ciphertext, {0x02}}, []byte{})
return Marshal(Payload{
RS: rs,
Salt: salt,
KeyId: appserverPrivateKey.PublicKey().Bytes(),
CipherText: ciphertext,
}), nil
} }
func (c *RFC8291) Decrypt( // decrypt performs decryption using the specified encoding scheme.
func (c *RFC8291) decrypt(
scheme EncodingScheme,
ciphertext []byte, ciphertext []byte,
salt []byte, salt []byte,
authSecret []byte, authSecret []byte,
useragentPrivateKey *ecdh.PrivateKey, receiverPrivateKey *ecdh.PrivateKey,
appserverPublicKey *ecdh.PublicKey, senderPublicKey *ecdh.PublicKey,
) ([]byte, error) { ) ([]byte, error) {
if len(authSecret) != AUTH_SECRET_LEN { if len(authSecret) != AUTH_SECRET_LEN {
return nil, fmt.Errorf("auth_secret must be %d bytes", AUTH_SECRET_LEN) return nil, fmt.Errorf("auth_secret must be %d bytes", AUTH_SECRET_LEN)
@ -119,17 +141,17 @@ func (c *RFC8291) Decrypt(
return nil, fmt.Errorf("salt must be %d bytes", SALT_LEN) return nil, fmt.Errorf("salt must be %d bytes", SALT_LEN)
} }
ecdhSecret, err := useragentPrivateKey.ECDH(appserverPublicKey) ecdhSecret, err := receiverPrivateKey.ECDH(senderPublicKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("calculate ecdh_secret failed: %v", err) return nil, fmt.Errorf("calculate ecdh_secret failed: %v", err)
} }
ikm, err := c.ikm(authSecret, ecdhSecret, useragentPrivateKey.PublicKey(), appserverPublicKey) ikm, err := scheme.DeriveIKM(c.hash, authSecret, ecdhSecret, receiverPrivateKey.PublicKey(), senderPublicKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cek, nonce, err := c.cekAndNonce(ikm, salt) cek, nonce, err := scheme.DeriveCEKAndNonce(c.hash, ikm, salt, receiverPrivateKey.PublicKey(), senderPublicKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -144,13 +166,7 @@ func (c *RFC8291) Decrypt(
return nil, err return nil, err
} }
// RFC8188: 0x01 or 0x02 in tail of plaintext data return scheme.Unpad(plaintext)
// RFC8291: The push message plaintext has the padding delimiter octet (0x02) appended to produce
if plaintext[len(plaintext)-1] == 0x01 || plaintext[len(plaintext)-1] == 0x02 {
plaintext = plaintext[:len(plaintext)-1]
}
return plaintext, err
} }
func (c *RFC8291) gcm(cek []byte) (cipher.AEAD, error) { func (c *RFC8291) gcm(cek []byte) (cipher.AEAD, error) {
@ -167,43 +183,42 @@ func (c *RFC8291) gcm(cek []byte) (cipher.AEAD, error) {
return gcm, nil return gcm, nil
} }
func (c *RFC8291) ikm( // Decrypt decrypts a push notification, automatically selecting the correct
// encoding scheme based on the encoding parameter.
//
// For aes128gcm: crypto params are extracted from the data payload.
// For aesgcm: crypto params are extracted from the HTTP headers.
func (c *RFC8291) Decrypt(
data []byte,
encoding Encoding,
encryptionHeader string,
cryptoKeyHeader string,
authSecret []byte, authSecret []byte,
ecdhSecret []byte, receiverPrivateKey *ecdh.PrivateKey,
useragentPublicKey *ecdh.PublicKey, ) ([]byte, error) {
appserverPublicKey *ecdh.PublicKey, switch encoding {
) (ikm []byte, err error) { case EncodingAes128gcm:
prkKey := hkdf.Extract(c.hash, ecdhSecret, authSecret) payload, err := Unmarshal(data)
if err != nil {
keyInfo := bytes.Join([][]byte{ return nil, fmt.Errorf("unmarshal aes128gcm payload: %v", err)
[]byte("WebPush: info\000"),
useragentPublicKey.Bytes(),
appserverPublicKey.Bytes(),
}, []byte{})
ikm = make([]byte, HKDF_IKM_LEN)
if _, err := io.ReadFull(hkdf.Expand(c.hash, prkKey, keyInfo), ikm); err != nil {
return nil, fmt.Errorf("read IKM failed: %v", err)
} }
return ikm, nil senderPublicKey, err := receiverPrivateKey.Curve().NewPublicKey(payload.KeyId)
if err != nil {
return nil, fmt.Errorf("parse sender public key: %v", err)
} }
func (c *RFC8291) cekAndNonce(ikm []byte, salt []byte) (cek, nonce []byte, err error) { return c.DecryptAes128gcm(payload.CipherText, payload.Salt, authSecret, receiverPrivateKey, senderPublicKey)
prk := hkdf.Extract(c.hash, ikm, salt)
cekInfo := []byte("Content-Encoding: aes128gcm\000") case EncodingAesgcm:
nonceInfo := []byte("Content-Encoding: nonce\000") params, err := ParseAesgcmHeaders(encryptionHeader, cryptoKeyHeader, receiverPrivateKey.Curve())
if err != nil {
cek = make([]byte, HKDF_CEK_LEN) return nil, fmt.Errorf("parse aesgcm headers: %v", err)
if _, err := io.ReadFull(hkdf.Expand(c.hash, prk, cekInfo), cek); err != nil {
return nil, nil, fmt.Errorf("read CEK failed: %v", err)
} }
nonce = make([]byte, HKDF_NONCE_LEN) return c.DecryptAesgcm(data, params.Salt, authSecret, receiverPrivateKey, params.SenderPublicKey)
if _, err := io.ReadFull(hkdf.Expand(c.hash, prk, nonceInfo), nonce); err != nil {
return nil, nil, fmt.Errorf("read Nonce failed: %v", err)
}
return cek, nonce, nil default:
return nil, fmt.Errorf("unsupported encoding: %s", encoding)
}
} }

View File

@ -99,7 +99,6 @@ func (c *NicoPushClient) Register() (channelID string, err error) {
func (c *NicoPushClient) Decrypt(data autopush.Notification) (*webpush.WebPushPayload, error) { func (c *NicoPushClient) Decrypt(data autopush.Notification) (*webpush.WebPushPayload, error) {
return c.autoPushClient.Decrypt( return c.autoPushClient.Decrypt(
ecdh.P256(),
c.authSecret, c.authSecret,
c.privateKey, c.privateKey,
data, data,