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.
278 lines
8.9 KiB
278 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) |
|
} |
|
}
|
|
|