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"
)
func (c ClientExchange ) Run (ctx context .Context ) (ClientExchangeResult , error ) {
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" )
}
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
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
}
pq := big .NewInt (0 ).SetBytes (res .Pq )
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 ()
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 )))
pBytes := p .Bytes ()
qBytes := q .Bytes ()
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
}
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" )
}
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 :
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 ())
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 )
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" )
}
gB := big .NewInt (0 ).Exp (g , bParam , dhPrime )
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 (),
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" )
}
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 :
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 ()
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" )
}
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 :
return ClientExchangeResult {}, errors .Errorf ("retry required: %x" , v .NewNonceHash2 )
case *mt .DhGenFail :
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 )
}
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .