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.
208 lines
5.6 KiB
208 lines
5.6 KiB
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" |
|
) |
|
|
|
// ServerExchangeError is returned when exchange fails due to |
|
// some security or validation checks. |
|
type ServerExchangeError struct { |
|
Code int32 |
|
Err error |
|
} |
|
|
|
// Error implements error. |
|
func (s *ServerExchangeError) Error() string { |
|
return s.Err.Error() |
|
} |
|
|
|
// Unwrap implements error wrapper interface. |
|
func (s *ServerExchangeError) Unwrap() error { |
|
return s.Err |
|
} |
|
|
|
func serverError(code int32, err error) error { |
|
return &ServerExchangeError{ |
|
Code: code, |
|
Err: err, |
|
} |
|
} |
|
|
|
// Run runs server-side flow. |
|
// If b parameter is not nil, it will be used as first read message. |
|
// Otherwise, it will be read from connection. |
|
func (s ServerExchange) Run(ctx context.Context) (ServerExchangeResult, error) { |
|
wrapKeyNotFound := func(err error) error { |
|
return serverError(codec.CodeAuthKeyNotFound, err) |
|
} |
|
|
|
// 1. Client sends query to server |
|
// |
|
// req_pq_multi#be7e8ef1 nonce:int128 = ResPQ; |
|
var pqReq mt.ReqPqMultiRequest |
|
b := new(bin.Buffer) |
|
if err := s.readUnencrypted(ctx, b, &pqReq); 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") |
|
} |
|
|
|
// 2. Server sends response of the form |
|
// |
|
// resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector long = ResPQ; |
|
pq, err := s.rng.PQ() |
|
if err != nil { |
|
return ServerExchangeResult{}, errors.Wrap(err, "generate pq") |
|
} |
|
|
|
s.log.Debug("Sending ResPQ", zap.String("pq", pq.String())) |
|
if err := s.writeUnencrypted(ctx, b, &mt.ResPQ{ |
|
Pq: pq.Bytes(), |
|
Nonce: pqReq.Nonce, |
|
ServerNonce: serverNonce, |
|
ServerPublicKeyFingerprints: []int64{ |
|
s.key.Fingerprint(), |
|
}, |
|
}); err != nil { |
|
return ServerExchangeResult{}, err |
|
} |
|
|
|
// 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 |
|
var dhParams mt.ReqDHParamsRequest |
|
if err := s.readUnencrypted(ctx, b, &dhParams); err != nil { |
|
return ServerExchangeResult{}, err |
|
} |
|
s.log.Debug("Received client ReqDHParamsRequest") |
|
|
|
var innerData mt.PQInnerData |
|
{ |
|
r, err := crypto.DecodeRSAPad(dhParams.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: pqReq.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)) |
|
// 5. Server responds with Server_DH_Params. |
|
if err := s.writeUnencrypted(ctx, b, &mt.ServerDHParamsOk{ |
|
Nonce: pqReq.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) |
|
} |
|
|
|
// DH key exchange complete |
|
// 8. Server responds in one of three ways: |
|
// dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 |
|
// new_nonce_hash1:int128 = Set_client_DH_params_answer; |
|
s.log.Debug("Sending DhGenOk") |
|
if err := s.writeUnencrypted(ctx, b, &mt.DhGenOk{ |
|
Nonce: pqReq.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 |
|
}
|
|
|