You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
8.9 KiB

package exchange
import (
"context"
"crypto/rand"
"math/big"
"github.com/go-faster/errors"
"go.uber.org/zap"
"github.com/gotd/td/bin"
"github.com/gotd/td/internal/crypto"
"github.com/gotd/td/internal/mt"
"github.com/gotd/td/internal/proto"
)
// Run runs client-side flow.
func (c ClientExchange) Run(ctx context.Context) (ClientExchangeResult, error) {
// 1. DH exchange initiation.
nonce, err := crypto.RandInt128(c.rand)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "client nonce generation")
}
b := new(bin.Buffer)
c.log.Debug("Sending ReqPqMultiRequest")
if err := c.writeUnencrypted(ctx, b, &mt.ReqPqMultiRequest{Nonce: nonce}); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "write ReqPqMultiRequest")
}
// 2. Server sends response of the form
// resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector long = ResPQ;
var res mt.ResPQ
if err := c.readUnencrypted(ctx, b, &res); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "read ResPQ response")
}
c.log.Debug("Received server ResPQ")
if res.Nonce != nonce {
return ClientExchangeResult{}, errors.New("ResPQ nonce mismatch")
}
serverNonce := res.ServerNonce
// Selecting first public key that match fingerprint.
var selectedPubKey PublicKey
Loop:
for _, key := range c.keys {
f := key.Fingerprint()
for _, fingerprint := range res.ServerPublicKeyFingerprints {
if fingerprint == f {
selectedPubKey = key
break Loop
}
}
}
if selectedPubKey.Zero() {
return ClientExchangeResult{}, ErrKeyFingerprintNotFound
}
// The pq is a representation of a natural number (in binary big endian format).
// SetBytes is also big endian.
pq := big.NewInt(0).SetBytes(res.Pq)
// Normally pq is less than or equal to 2^63-1.
pqMax := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(63), nil)
if pq.Cmp(pqMax) > 0 {
return ClientExchangeResult{}, errors.New("server provided bad pq")
}
start := c.clock.Now()
// 3. Client decomposes pq into prime factors such that p < q.
// Performing proof of work.
p, q, err := crypto.DecomposePQ(pq, c.rand)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "decompose pq")
}
c.log.Debug("PQ decomposing complete", zap.Duration("took", c.clock.Now().Sub(start)))
// Make a copy of p and q values to reduce allocations.
pBytes := p.Bytes()
qBytes := q.Bytes()
// 4. Client sends query to server.
// req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string
// public_key_fingerprint:long encrypted_data:string = Server_DH_Params
newNonce, err := crypto.RandInt256(c.rand)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "generate new nonce")
}
var encryptedData []byte
pqInnerData := &mt.PQInnerDataDC{
Pq: res.Pq,
Nonce: nonce,
NewNonce: newNonce,
ServerNonce: serverNonce,
P: pBytes,
Q: qBytes,
DC: c.dc,
}
b.Reset()
if err := pqInnerData.Encode(b); err != nil {
return ClientExchangeResult{}, err
}
// `encrypted_data := RSA_PAD(data, server_public_key);`
data, err := crypto.RSAPad(b.Buf, selectedPubKey.RSA, c.rand)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "encrypted_data generation")
}
encryptedData = data
reqDHParams := &mt.ReqDHParamsRequest{
Nonce: nonce,
ServerNonce: serverNonce,
P: pBytes,
Q: qBytes,
PublicKeyFingerprint: selectedPubKey.Fingerprint(),
EncryptedData: encryptedData,
}
c.log.Debug("Sending ReqDHParamsRequest")
if err := c.writeUnencrypted(ctx, b, reqDHParams); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "write ReqDHParamsRequest")
}
// 5. Server responds with Server_DH_Params.
if err := c.conn.Recv(ctx, b); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "read ServerDHParams message")
}
c.log.Debug("Received server ServerDHParams")
var plaintextMsg proto.UnencryptedMessage
if err := plaintextMsg.Decode(b); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "decode ServerDHParams message")
}
b.ResetTo(plaintextMsg.MessageData)
dhParams, err := mt.DecodeServerDHParams(b)
if err != nil {
return ClientExchangeResult{}, err
}
switch p := dhParams.(type) {
case *mt.ServerDHParamsOk:
// Success.
if p.Nonce != nonce {
return ClientExchangeResult{}, errors.New("ServerDHParamsOk nonce mismatch")
}
if p.ServerNonce != serverNonce {
return ClientExchangeResult{}, errors.New("ServerDHParamsOk server nonce mismatch")
}
key, iv := crypto.TempAESKeys(newNonce.BigInt(), serverNonce.BigInt())
// Decrypting inner data.
data, err := crypto.DecryptExchangeAnswer(p.EncryptedAnswer, key, iv)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "exchange answer decrypt")
}
b.ResetTo(data)
innerData := mt.ServerDHInnerData{}
if err := innerData.Decode(b); err != nil {
return ClientExchangeResult{}, err
}
if innerData.Nonce != nonce {
return ClientExchangeResult{}, errors.New("ServerDHInnerData nonce mismatch")
}
if innerData.ServerNonce != serverNonce {
return ClientExchangeResult{}, errors.New("ServerDHInnerData server nonce mismatch")
}
dhPrime := big.NewInt(0).SetBytes(innerData.DhPrime)
g := big.NewInt(int64(innerData.G))
if err := crypto.CheckDH(innerData.G, dhPrime); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "check DH params")
}
gA := big.NewInt(0).SetBytes(innerData.GA)
// 6. Random number b is computed:
randMax := big.NewInt(0).SetBit(big.NewInt(0), crypto.RSAKeyBits, 1)
bParam, err := rand.Int(c.rand, randMax)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "number b generation")
}
// g_b = g^b mod dh_prime
gB := big.NewInt(0).Exp(g, bParam, dhPrime)
// Checking key exchange parameters.
if err := crypto.CheckDHParams(dhPrime, g, gA, gB); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "key exchange failed: invalid params")
}
clientInnerData := mt.ClientDHInnerData{
ServerNonce: innerData.ServerNonce,
Nonce: innerData.Nonce,
GB: gB.Bytes(),
// first attempt
RetryID: 0,
}
b.Reset()
if err := clientInnerData.Encode(b); err != nil {
return ClientExchangeResult{}, err
}
clientEncrypted, err := crypto.EncryptExchangeAnswer(c.rand, b.Buf, key, iv)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "exchange answer encrypt")
}
setParamsReq := &mt.SetClientDHParamsRequest{
Nonce: nonce,
ServerNonce: reqDHParams.ServerNonce,
EncryptedData: clientEncrypted,
}
c.log.Debug("Sending SetClientDHParamsRequest")
if err := c.writeUnencrypted(ctx, b, setParamsReq); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "write SetClientDHParamsRequest")
}
// 7. Computing auth_key using formula (g_a)^b mod dh_prime
authKey := big.NewInt(0).Exp(gA, bParam, dhPrime)
b.Reset()
if err := c.conn.Recv(ctx, b); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "read DhGen message")
}
c.log.Debug("Received server DhGen")
if err := plaintextMsg.Decode(b); err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "decode DhGen message")
}
b.ResetTo(plaintextMsg.MessageData)
dhSetRes, err := mt.DecodeSetClientDHParamsAnswer(b)
if err != nil {
return ClientExchangeResult{}, errors.Wrap(err, "decode DhGen answer")
}
switch v := dhSetRes.(type) {
case *mt.DhGenOk: // dh_gen_ok#3bcbf734
if v.Nonce != nonce {
return ClientExchangeResult{}, errors.New("DhGenOk nonce mismatch")
}
if v.ServerNonce != serverNonce {
return ClientExchangeResult{}, errors.New("DhGenOk server nonce mismatch")
}
var key crypto.Key
authKey.FillBytes(key[:])
authKeyID := key.ID()
// Checking received hash.
nonceHash1 := crypto.NonceHash1(newNonce, key)
serverSalt := crypto.ServerSalt(newNonce, v.ServerNonce)
if nonceHash1 != v.NewNonceHash1 {
return ClientExchangeResult{}, errors.New("key exchange verification failed: hash mismatch")
}
// Generating new session id and salt.
sessionID, err := crypto.NewSessionID(c.rand)
if err != nil {
return ClientExchangeResult{}, err
}
return ClientExchangeResult{
AuthKey: crypto.AuthKey{Value: key, ID: authKeyID},
SessionID: sessionID,
ServerSalt: serverSalt,
}, nil
case *mt.DhGenRetry: // dh_gen_retry#46dc1fb9
return ClientExchangeResult{}, errors.Errorf("retry required: %x", v.NewNonceHash2)
case *mt.DhGenFail: // dh_gen_fail#a69dae02
return ClientExchangeResult{}, errors.Errorf("dh_hen_fail: %x", v.NewNonceHash3)
default:
return ClientExchangeResult{}, errors.Errorf("unexpected SetClientDHParamsRequest result %T", v)
}
case *mt.ServerDHParamsFail:
return ClientExchangeResult{}, errors.New("server respond with server_DH_params_fail")
default:
return ClientExchangeResult{}, errors.Errorf("unexpected ReqDHParamsRequest result %T", p)
}
}