From 57f290eebe9bc9fcd94dbe228993e8c7fcb90dd5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 1 Feb 2024 22:39:49 +0100 Subject: [PATCH] Verify server cert properly --- handshake.go | 68 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/handshake.go b/handshake.go index f26bfe26..a5d99c29 100644 --- a/handshake.go +++ b/handshake.go @@ -11,6 +11,7 @@ import ( "fmt" "time" + "go.mau.fi/libsignal/ecc" "google.golang.org/protobuf/proto" waProto "go.mau.fi/whatsmeow/binary/proto" @@ -19,6 +20,9 @@ import ( ) const NoiseHandshakeResponseTimeout = 20 * time.Second +const WACertIssuerSerial = 0 + +var WACertPubKey = [...]byte{0x14, 0x23, 0x75, 0x57, 0x4d, 0xa, 0x58, 0x71, 0x66, 0xaa, 0xe7, 0x1e, 0xbe, 0x51, 0x64, 0x37, 0xc4, 0xa2, 0x8b, 0x73, 0xe3, 0x69, 0x5c, 0x6c, 0xe1, 0xf7, 0xf9, 0x54, 0x5d, 0xa8, 0xee, 0x6b} // doHandshake implements the Noise_XX_25519_AESGCM_SHA256 handshake for the WhatsApp web API. func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair) error { @@ -76,32 +80,9 @@ func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair) certDecrypted, err := nh.Decrypt(certificateCiphertext) if err != nil { return fmt.Errorf("failed to decrypt noise certificate ciphertext: %w", err) + } else if err = verifyServerCert(certDecrypted, staticDecrypted); err != nil { + return fmt.Errorf("failed to verify server cert: %w", err) } - var certChain waProto.CertChain - err = proto.Unmarshal(certDecrypted, &certChain) - if err != nil { - return fmt.Errorf("failed to unmarshal noise certificate: %w", err) - } - intermediateCertDetailsRaw := certChain.GetIntermediate().GetDetails() - intermediateCertSignature := certChain.GetIntermediate().GetSignature() - leafCertDetailsRaw := certChain.GetLeaf().GetDetails() - leafCertSignature := certChain.GetLeaf().GetSignature() - if intermediateCertDetailsRaw == nil || intermediateCertSignature == nil || leafCertDetailsRaw == nil || leafCertSignature == nil { - return fmt.Errorf("missing parts of noise certificate") - } - var leafCertDetails waProto.CertChain_NoiseCertificate_Details - err = proto.Unmarshal(leafCertDetailsRaw, &leafCertDetails) - if err != nil { - return fmt.Errorf("failed to unmarshal noise certificate details: %w", err) - } else if !bytes.Equal(leafCertDetails.GetKey(), staticDecrypted) { - return fmt.Errorf("cert key doesn't match decrypted static") - } - var intermediateCertDetails waProto.CertChain_NoiseCertificate_Details - err = proto.Unmarshal(intermediateCertDetailsRaw, &intermediateCertDetails) - if err != nil { - return fmt.Errorf("failed to unmarshal noise certificate details: %w", err) - } - // TODO check cert signatures? encryptedPubkey := nh.Encrypt(cli.Store.NoiseKey.Pub[:]) err = nh.MixSharedSecretIntoKey(*cli.Store.NoiseKey.Priv, serverEphemeralArr) @@ -137,3 +118,40 @@ func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair) return nil } + +func verifyServerCert(certDecrypted, staticDecrypted []byte) error { + var certChain waProto.CertChain + err := proto.Unmarshal(certDecrypted, &certChain) + if err != nil { + return fmt.Errorf("failed to unmarshal noise certificate: %w", err) + } + var intermediateCertDetails, leafCertDetails waProto.CertChain_NoiseCertificate_Details + intermediateCertDetailsRaw := certChain.GetIntermediate().GetDetails() + intermediateCertSignature := certChain.GetIntermediate().GetSignature() + leafCertDetailsRaw := certChain.GetLeaf().GetDetails() + leafCertSignature := certChain.GetLeaf().GetSignature() + if intermediateCertDetailsRaw == nil || intermediateCertSignature == nil || leafCertDetailsRaw == nil || leafCertSignature == nil { + return fmt.Errorf("missing parts of noise certificate") + } else if len(intermediateCertSignature) != 64 { + return fmt.Errorf("unexpected length of intermediate cert signature %d (expected 64)", len(intermediateCertSignature)) + } else if len(leafCertSignature) != 64 { + return fmt.Errorf("unexpected length of leaf cert signature %d (expected 64)", len(leafCertSignature)) + } else if !ecc.VerifySignature(ecc.NewDjbECPublicKey(WACertPubKey), intermediateCertDetailsRaw, [64]byte(intermediateCertSignature)) { + return fmt.Errorf("failed to verify intermediate cert signature") + } else if err = proto.Unmarshal(intermediateCertDetailsRaw, &intermediateCertDetails); err != nil { + return fmt.Errorf("failed to unmarshal noise certificate details: %w", err) + } else if intermediateCertDetails.GetIssuerSerial() != WACertIssuerSerial { + return fmt.Errorf("unexpected intermediate issuer serial %d (expected %d)", intermediateCertDetails.GetIssuerSerial(), WACertIssuerSerial) + } else if len(intermediateCertDetails.GetKey()) != 32 { + return fmt.Errorf("unexpected length of intermediate cert key %d (expected 32)", len(intermediateCertDetails.GetKey())) + } else if !ecc.VerifySignature(ecc.NewDjbECPublicKey([32]byte(intermediateCertDetails.GetKey())), leafCertDetailsRaw, [64]byte(leafCertSignature)) { + return fmt.Errorf("failed to verify intermediate cert signature") + } else if err = proto.Unmarshal(leafCertDetailsRaw, &leafCertDetails); err != nil { + return fmt.Errorf("failed to unmarshal noise certificate details: %w", err) + } else if leafCertDetails.GetIssuerSerial() != intermediateCertDetails.GetSerial() { + return fmt.Errorf("unexpected leaf issuer serial %d (expected %d)", leafCertDetails.GetIssuerSerial(), intermediateCertDetails.GetSerial()) + } else if !bytes.Equal(leafCertDetails.GetKey(), staticDecrypted) { + return fmt.Errorf("cert key doesn't match decrypted static") + } + return nil +}