package exchange
import (
"context"
"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/codec"
)
type ServerExchangeError struct {
Code int32
Err error
}
func (s *ServerExchangeError ) Error () string {
return s .Err .Error()
}
func (s *ServerExchangeError ) Unwrap () error {
return s .Err
}
func serverError (code int32 , err error ) error {
return &ServerExchangeError {
Code : code ,
Err : err ,
}
}
type reqPQ struct {
Type uint32
Nonce bin .Int128
}
func (r *reqPQ ) Decode (b *bin .Buffer ) error {
var (
legacy mt .ReqPqRequest
multi mt .ReqPqMultiRequest
)
id , err := b .PeekID ()
if err != nil {
return err
}
r .Type = id
switch id {
case legacy .TypeID ():
if err := legacy .Decode (b ); err != nil {
return err
}
r .Nonce = legacy .Nonce
return nil
case multi .TypeID ():
if err := multi .Decode (b ); err != nil {
return err
}
r .Nonce = multi .Nonce
return nil
default :
return bin .NewUnexpectedID (id )
}
}
type reqOrDH struct {
Type uint32
DH mt .ReqDHParamsRequest
Req reqPQ
}
func (r *reqOrDH ) Decode (b *bin .Buffer ) error {
id , err := b .PeekID ()
if err != nil {
return err
}
r .Type = id
switch id {
case r .DH .TypeID ():
return r .DH .Decode (b )
default :
return r .Req .Decode (b )
}
}
func (s ServerExchange ) Run (ctx context .Context ) (ServerExchangeResult , error ) {
wrapKeyNotFound := func (err error ) error {
return serverError (codec .CodeAuthKeyNotFound , err )
}
var req reqPQ
b := new (bin .Buffer )
if err := s .readUnencrypted (ctx , b , &req ); err != nil {
return ServerExchangeResult {}, err
}
s .log .Debug ("Received client ReqPqMultiRequest" )
serverNonce , err := crypto .RandInt128 (s .rand )
if err != nil {
return ServerExchangeResult {}, errors .Wrap (err , "generate server nonce" )
}
pq , err := s .rng .PQ ()
if err != nil {
return ServerExchangeResult {}, errors .Wrap (err , "generate pq" )
}
SendResPQ :
s .log .Debug ("Sending ResPQ" , zap .String ("pq" , pq .String ()))
if err := s .writeUnencrypted (ctx , b , &mt .ResPQ {
Pq : pq .Bytes (),
Nonce : req .Nonce ,
ServerNonce : serverNonce ,
ServerPublicKeyFingerprints : []int64 {
s .key .Fingerprint (),
},
}); err != nil {
return ServerExchangeResult {}, err
}
var dhParams reqOrDH
if err := s .readUnencrypted (ctx , b , &dhParams ); err != nil {
return ServerExchangeResult {}, err
}
switch dhParams .Type {
case mt .ReqPqRequestTypeID , mt .ReqPqMultiRequestTypeID :
s .log .Debug ("Received ReqPQ again" )
req = dhParams .Req
goto SendResPQ
default :
s .log .Debug ("Received client ReqDHParamsRequest" )
}
var innerData mt .PQInnerData
{
r , err := crypto .DecodeRSAPad (dhParams .DH .EncryptedData , s .key .RSA )
if err != nil {
return ServerExchangeResult {}, wrapKeyNotFound (err )
}
b .ResetTo (r )
d , err := mt .DecodePQInnerData (b )
if err != nil {
return ServerExchangeResult {}, err
}
if innerDataDC , ok := d .(*mt .PQInnerDataDC ); ok && innerDataDC .DC != s .dc {
err := errors .Errorf (
"wrong DC ID, want %d, got %d" ,
s .dc , innerDataDC .DC ,
)
return ServerExchangeResult {}, serverError (codec .CodeWrongDC , err )
}
innerData = mt .PQInnerData {
Pq : d .GetPq (),
P : d .GetP (),
Q : d .GetQ (),
Nonce : d .GetNonce (),
ServerNonce : d .GetServerNonce (),
NewNonce : d .GetNewNonce (),
}
}
dhPrime , err := s .rng .DhPrime ()
if err != nil {
return ServerExchangeResult {}, errors .Wrap (err , "generate dh_prime" )
}
g := 3
a , ga , err := s .rng .GA (g , dhPrime )
if err != nil {
return ServerExchangeResult {}, errors .Wrap (err , "generate g_a" )
}
data := mt .ServerDHInnerData {
Nonce : req .Nonce ,
ServerNonce : serverNonce ,
G : g ,
GA : ga .Bytes (),
DhPrime : dhPrime .Bytes (),
ServerTime : int (s .clock .Now ().Unix ()),
}
b .Reset ()
if err := data .Encode (b ); err != nil {
return ServerExchangeResult {}, err
}
key , iv := crypto .TempAESKeys (innerData .NewNonce .BigInt (), serverNonce .BigInt ())
answer , err := crypto .EncryptExchangeAnswer (s .rand , b .Raw (), key , iv )
if err != nil {
return ServerExchangeResult {}, err
}
s .log .Debug ("Sending ServerDHParamsOk" , zap .Int ("g" , g ))
if err := s .writeUnencrypted (ctx , b , &mt .ServerDHParamsOk {
Nonce : req .Nonce ,
ServerNonce : serverNonce ,
EncryptedAnswer : answer ,
}); err != nil {
return ServerExchangeResult {}, err
}
var clientDhParams mt .SetClientDHParamsRequest
if err := s .readUnencrypted (ctx , b , &clientDhParams ); err != nil {
return ServerExchangeResult {}, err
}
s .log .Debug ("Received client SetClientDHParamsRequest" )
decrypted , err := crypto .DecryptExchangeAnswer (clientDhParams .EncryptedData , key , iv )
if err != nil {
err = errors .Wrap (err , "decrypt exchange answer" )
return ServerExchangeResult {}, wrapKeyNotFound (err )
}
b .ResetTo (decrypted )
var clientInnerData mt .ClientDHInnerData
if err := clientInnerData .Decode (b ); err != nil {
return ServerExchangeResult {}, wrapKeyNotFound (err )
}
gB := big .NewInt (0 ).SetBytes (clientInnerData .GB )
var authKey crypto .Key
if !crypto .FillBytes (big .NewInt (0 ).Exp (gB , a , dhPrime ), authKey [:]) {
err := errors .New ("auth_key is too big" )
return ServerExchangeResult {}, wrapKeyNotFound (err )
}
s .log .Debug ("Sending DhGenOk" )
if err := s .writeUnencrypted (ctx , b , &mt .DhGenOk {
Nonce : req .Nonce ,
ServerNonce : serverNonce ,
NewNonceHash1 : crypto .NonceHash1 (innerData .NewNonce , authKey ),
}); err != nil {
return ServerExchangeResult {}, err
}
serverSalt := crypto .ServerSalt (innerData .NewNonce , serverNonce )
return ServerExchangeResult {
Key : authKey .WithID (),
ServerSalt : serverSalt ,
}, nil
}
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 .