package webpush import ( "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "encoding/base64" "fmt" "math/big" ) // ecdhPublicKeyToECDSA converts an ECDH key to an ECDSA key. // This is deprecated as per https://github.com/golang/go/issues/63963 // but we need to do it in order to parse the legacy private key format. func ecdhPublicKeyToECDSA(key *ecdh.PublicKey) (*ecdsa.PublicKey, error) { rawKey := key.Bytes() switch key.Curve() { case ecdh.P256(): return &ecdsa.PublicKey{ Curve: elliptic.P256(), X: big.NewInt(0).SetBytes(rawKey[1:33]), Y: big.NewInt(0).SetBytes(rawKey[33:]), }, nil case ecdh.P384(): return &ecdsa.PublicKey{ Curve: elliptic.P384(), X: big.NewInt(0).SetBytes(rawKey[1:49]), Y: big.NewInt(0).SetBytes(rawKey[49:]), }, nil case ecdh.P521(): return &ecdsa.PublicKey{ Curve: elliptic.P521(), X: big.NewInt(0).SetBytes(rawKey[1:67]), Y: big.NewInt(0).SetBytes(rawKey[67:]), }, nil default: return nil, fmt.Errorf("cannot convert non-NIST *ecdh.PublicKey to *ecdsa.PublicKey") } } func ecdhPrivateKeyToECDSA(key *ecdh.PrivateKey) (*ecdsa.PrivateKey, error) { // see https://github.com/golang/go/issues/63963 pubKey, err := ecdhPublicKeyToECDSA(key.PublicKey()) if err != nil { return nil, fmt.Errorf("converting PublicKey part of *ecdh.PrivateKey: %w", err) } return &ecdsa.PrivateKey{ PublicKey: *pubKey, D: big.NewInt(0).SetBytes(key.Bytes()), }, nil } // DecodeLegacyVAPIDPrivateKey decodes the legacy string private key format // returned by GenerateVAPIDKeys in v1. func DecodeLegacyVAPIDPrivateKey(key string) (*VAPIDKeys, error) { bytes, err := decodeSubscriptionKey(key) if err != nil { return nil, err } ecdhPrivKey, err := ecdh.P256().NewPrivateKey(bytes) if err != nil { return nil, err } ecdsaPrivKey, err := ecdhPrivateKeyToECDSA(ecdhPrivKey) if err != nil { return nil, err } publicKey := base64.RawURLEncoding.EncodeToString(ecdhPrivKey.PublicKey().Bytes()) return &VAPIDKeys{ privateKey: ecdsaPrivKey, publicKey: publicKey, }, nil }