move gotd fork into repo. (#111)
- update to latest telegram layer - remove some references to fields in tg.Entities that don't exist in the schema - originally added here: https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e - referenced here - https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3 - https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"math/big"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Hash computes user password hash using parameters from server.
|
||||
//
|
||||
// See https://core.telegram.org/api/srp#checking-the-password-with-srp.
|
||||
func (s SRP) Hash(password, srpB, random []byte, i Input) (Answer, error) {
|
||||
p := s.bigFromBytes(i.P)
|
||||
if err := checkInput(i.G, p); err != nil {
|
||||
return Answer{}, errors.Wrap(err, "validate algo")
|
||||
}
|
||||
|
||||
g := big.NewInt(int64(i.G))
|
||||
// It is safe to use FillBytes directly because we know that 64-bit G always smaller than
|
||||
// 256-bit destination array.
|
||||
var gBytes [256]byte
|
||||
g.FillBytes(gBytes[:])
|
||||
|
||||
// random 2048-bit number a
|
||||
a := s.bigFromBytes(random)
|
||||
|
||||
// `g_a = pow(g, a) mod p`
|
||||
ga, ok := s.pad256FromBig(s.bigExp(g, a, p))
|
||||
if !ok {
|
||||
return Answer{}, errors.New("g_a is too big")
|
||||
}
|
||||
|
||||
// `g_b = srp_B`
|
||||
gb := s.pad256(srpB)
|
||||
|
||||
// `u = H(g_a | g_b)`
|
||||
u := s.bigFromBytes(s.hash(ga[:], gb[:]))
|
||||
|
||||
// `x = PH2(password, salt1, salt2)`
|
||||
// `v = pow(g, x) mod p`
|
||||
x, v := s.computeXV(password, i.Salt1, i.Salt2, g, p)
|
||||
|
||||
// `k = (k * v) mod p`
|
||||
k := s.bigFromBytes(s.hash(i.P, gBytes[:]))
|
||||
|
||||
// `k_v = (k * v) % p`
|
||||
kv := k.Mul(k, v).Mod(k, p)
|
||||
|
||||
// `t = (g_b - k_v) % p`
|
||||
t := s.bigFromBytes(srpB)
|
||||
if t.Sub(t, kv).Cmp(big.NewInt(0)) == -1 {
|
||||
t.Add(t, p)
|
||||
}
|
||||
|
||||
// `s_a = pow(t, a + u * x) mod p`
|
||||
sa, ok := s.pad256FromBig(s.bigExp(t, u.Mul(u, x).Add(u, a), p))
|
||||
if !ok {
|
||||
return Answer{}, errors.New("s_a is too big")
|
||||
}
|
||||
|
||||
// `k_a = H(s_a)`
|
||||
ka := sha256.Sum256(sa[:])
|
||||
|
||||
// `M1 = H(H(p) xor H(g) | H2(salt1) | H2(salt2) | g_a | g_b | k_a)`
|
||||
xorHpHg := xor32(sha256.Sum256(i.P), sha256.Sum256(gBytes[:]))
|
||||
M1 := s.hash(
|
||||
xorHpHg[:],
|
||||
s.hash(i.Salt1),
|
||||
s.hash(i.Salt2),
|
||||
ga[:],
|
||||
gb[:],
|
||||
ka[:],
|
||||
)
|
||||
|
||||
return Answer{
|
||||
A: ga[:],
|
||||
M1: M1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// The main hashing function H is sha256:
|
||||
//
|
||||
// H(data) := sha256(data)
|
||||
func (s SRP) hash(data ...[]byte) []byte {
|
||||
h := sha256.New()
|
||||
for i := range data {
|
||||
h.Write(data[i])
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// The salting hashing function SH is defined as follows:
|
||||
//
|
||||
// SH(data, salt) := H(salt | data | salt)
|
||||
func (s SRP) saltHash(data, salt []byte) []byte {
|
||||
return s.hash(salt, data, salt)
|
||||
}
|
||||
|
||||
// The primary password hashing function is defined as follows:
|
||||
//
|
||||
// PH1(password, salt1, salt2) := SH(SH(password, salt1), salt2)
|
||||
func (s SRP) primary(password, salt1, salt2 []byte) []byte {
|
||||
return s.saltHash(s.saltHash(password, salt1), salt2)
|
||||
}
|
||||
|
||||
// The secondary password hashing function is defined as follows:
|
||||
//
|
||||
// PH2(password, salt1, salt2) := SH(pbkdf2(sha512, PH1(password, salt1, salt2), salt1, 100000), salt2)
|
||||
func (s SRP) secondary(password, salt1, salt2 []byte) []byte {
|
||||
return s.saltHash(s.pbkdf2(s.primary(password, salt1, salt2), salt1, 100000), salt2)
|
||||
}
|
||||
|
||||
func (s SRP) pbkdf2(ph1, salt1 []byte, n int) []byte {
|
||||
return pbkdf2.Key(ph1, salt1, n, 64, sha512.New)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
)
|
||||
|
||||
// computeXV computes following numbers
|
||||
//
|
||||
// `x = PH2(password, salt1, salt2)`
|
||||
// `v = pow(g, x) mod p`
|
||||
//
|
||||
// TDLib uses terms `clientSalt` for `salt1` and `serverSalt` for `salt2`.
|
||||
func (s SRP) computeXV(password, clientSalt, serverSalt []byte, g, p *big.Int) (x, v *big.Int) {
|
||||
// `x = PH2(password, salt1, salt2)`
|
||||
x = new(big.Int).SetBytes(s.secondary(password, clientSalt, serverSalt))
|
||||
// `v = pow(g, x) mod p`
|
||||
v = new(big.Int).Exp(g, x, p)
|
||||
return x, v
|
||||
}
|
||||
|
||||
// NewHash computes new user password hash using parameters from server.
|
||||
//
|
||||
// See https://core.telegram.org/api/srp#setting-a-new-2fa-password.
|
||||
//
|
||||
// TDLib implementation:
|
||||
// See https://github.com/tdlib/td/blob/fa8feefed70d64271945e9d5fd010b957d93c8cd/td/telegram/PasswordManager.cpp#L57.
|
||||
//
|
||||
// TDesktop implementation:
|
||||
// See https://github.com/telegramdesktop/tdesktop/blob/v3.4.8/Telegram/SourceFiles/core/core_cloud_password.cpp#L68.
|
||||
func (s SRP) NewHash(password []byte, i Input) (hash, newSalt []byte, _ error) {
|
||||
// Generate a new new_password_hash using the KDF algorithm specified in the new_settings,
|
||||
// just append 32 sufficiently random bytes to the salt1, first. Proceed as for checking passwords with SRP,
|
||||
// just stop at the generation of the v parameter, and use it as new_password_hash:
|
||||
p := new(big.Int).SetBytes(i.P)
|
||||
if err := checkInput(i.G, p); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "validate algo")
|
||||
}
|
||||
|
||||
// Make a copy.
|
||||
newClientSalt := append([]byte(nil), i.Salt1...)
|
||||
newClientSalt = append(newClientSalt, make([]byte, 32)...)
|
||||
// ... append 32 sufficiently random bytes to the salt1 ...
|
||||
if _, err := io.ReadFull(s.random, newClientSalt[len(newClientSalt)-32:]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, v := s.computeXV(password, newClientSalt, i.Salt2, big.NewInt(int64(i.G)), p)
|
||||
// As usual in big endian form, padded to 2048 bits.
|
||||
padded, _ := s.pad256FromBig(v)
|
||||
return padded[:], newClientSalt, nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
|
||||
)
|
||||
|
||||
func TestSRP_NewHash(t *testing.T) {
|
||||
password := []uint8{
|
||||
110, 101, 119, 80, 97, 115, 115, 119, 111, 114, 100,
|
||||
}
|
||||
i := Input{
|
||||
Salt1: []uint8{
|
||||
230, 200, 149, 125, 223, 152, 141, 72,
|
||||
},
|
||||
Salt2: []uint8{
|
||||
159, 99, 68, 130, 43, 9, 108, 255, 135, 239, 164, 38, 245, 120, 87, 182,
|
||||
},
|
||||
G: 3,
|
||||
P: []uint8{
|
||||
199, 28, 174, 185, 198, 177, 201, 4, 142, 108, 82, 47, 112, 241, 63, 115,
|
||||
152, 13, 64, 35, 142, 62, 33, 193, 73, 52, 208, 55, 86, 61, 147, 15,
|
||||
72, 25, 138, 10, 167, 193, 64, 88, 34, 148, 147, 210, 37, 48, 244, 219,
|
||||
250, 51, 111, 110, 10, 201, 37, 19, 149, 67, 174, 212, 76, 206, 124, 55,
|
||||
32, 253, 81, 246, 148, 88, 112, 90, 198, 140, 212, 254, 107, 107, 19, 171,
|
||||
220, 151, 70, 81, 41, 105, 50, 132, 84, 241, 143, 175, 140, 89, 95, 100,
|
||||
36, 119, 254, 150, 187, 42, 148, 29, 91, 205, 29, 74, 200, 204, 73, 136,
|
||||
7, 8, 250, 155, 55, 142, 60, 79, 58, 144, 96, 190, 230, 124, 249, 164,
|
||||
164, 166, 149, 129, 16, 81, 144, 126, 22, 39, 83, 181, 107, 15, 107, 65,
|
||||
13, 186, 116, 216, 168, 75, 42, 20, 179, 20, 78, 14, 241, 40, 71, 84,
|
||||
253, 23, 237, 149, 13, 89, 101, 180, 185, 221, 70, 88, 45, 177, 23, 141,
|
||||
22, 156, 107, 196, 101, 176, 214, 255, 156, 163, 146, 143, 239, 91, 154, 228,
|
||||
228, 24, 252, 21, 232, 62, 190, 160, 248, 127, 169, 255, 94, 237, 112, 5,
|
||||
13, 237, 40, 73, 244, 123, 249, 89, 217, 86, 133, 12, 233, 41, 133, 31,
|
||||
13, 129, 21, 246, 53, 177, 5, 238, 46, 78, 21, 208, 75, 36, 84, 191,
|
||||
111, 79, 173, 240, 52, 177, 4, 3, 17, 156, 216, 227, 185, 47, 204, 91,
|
||||
},
|
||||
}
|
||||
expectedHash := []uint8{
|
||||
24, 106, 193, 141, 204, 87, 144, 191, 107, 186, 33, 189, 149, 141, 55, 94,
|
||||
229, 72, 26, 240, 2, 158, 155, 215, 169, 198, 142, 201, 38, 189, 81, 150,
|
||||
216, 31, 140, 216, 181, 142, 224, 108, 138, 16, 173, 234, 204, 127, 86, 232,
|
||||
25, 255, 81, 72, 37, 222, 177, 91, 31, 173, 236, 106, 174, 23, 162, 68,
|
||||
203, 35, 72, 141, 23, 52, 156, 212, 38, 26, 139, 164, 218, 123, 156, 44,
|
||||
229, 196, 0, 20, 221, 158, 54, 39, 80, 172, 243, 172, 137, 184, 184, 245,
|
||||
198, 24, 240, 182, 165, 114, 195, 143, 255, 58, 85, 77, 136, 24, 160, 184,
|
||||
231, 182, 1, 94, 24, 54, 18, 138, 30, 78, 45, 92, 249, 151, 29, 29,
|
||||
208, 72, 170, 24, 29, 134, 17, 82, 234, 231, 21, 83, 150, 38, 128, 99,
|
||||
35, 135, 184, 154, 193, 134, 95, 222, 215, 200, 195, 218, 166, 78, 211, 141,
|
||||
194, 80, 54, 102, 63, 160, 207, 119, 72, 197, 46, 161, 156, 24, 126, 112,
|
||||
167, 82, 168, 5, 62, 64, 157, 72, 148, 33, 138, 66, 147, 147, 208, 51,
|
||||
130, 228, 30, 80, 183, 65, 91, 59, 138, 208, 146, 253, 7, 144, 248, 141,
|
||||
137, 78, 132, 220, 167, 143, 71, 244, 33, 137, 55, 215, 170, 153, 216, 140,
|
||||
135, 192, 155, 203, 141, 168, 144, 229, 53, 2, 102, 35, 206, 166, 252, 139,
|
||||
61, 37, 219, 112, 203, 66, 170, 164, 131, 35, 146, 125, 135, 168, 252, 241,
|
||||
}
|
||||
expectedNewSalt := []uint8{
|
||||
230, 200, 149, 125, 223, 152, 141, 72, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
a := require.New(t)
|
||||
s := NewSRP(testutil.ZeroRand{})
|
||||
hash, newSalt, err := s.NewHash(password, i)
|
||||
a.NoError(err)
|
||||
a.Equal(expectedHash, hash)
|
||||
a.Equal(expectedNewSalt, newSalt)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
|
||||
)
|
||||
|
||||
func (s SRP) pad256FromBig(i *big.Int) (b [256]byte, r bool) {
|
||||
r = crypto.FillBytes(i, b[:])
|
||||
return b, r
|
||||
}
|
||||
|
||||
func (s SRP) pad256(b []byte) [256]byte {
|
||||
if len(b) >= 256 {
|
||||
return *(*[256]byte)(b[len(b)-256:])
|
||||
}
|
||||
|
||||
var tmp [256]byte
|
||||
copy(tmp[256-len(b):], b)
|
||||
|
||||
return tmp
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package srp contains implementation of Secure Remote Password protocol.
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/go-faster/xor"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
|
||||
)
|
||||
|
||||
// SRP is client implementation of Secure Remote Password protocol.
|
||||
//
|
||||
// See https://core.telegram.org/api/srp.
|
||||
type SRP struct {
|
||||
random io.Reader
|
||||
}
|
||||
|
||||
// NewSRP creates new SRP instance.
|
||||
func NewSRP(random io.Reader) SRP {
|
||||
return SRP{random: random}
|
||||
}
|
||||
|
||||
// Input is hashing algorithm parameters from server.
|
||||
//
|
||||
// Copy of tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow
|
||||
type Input struct {
|
||||
// One of two salts used by the derivation function (see SRP 2FA login)
|
||||
Salt1 []byte
|
||||
// One of two salts used by the derivation function (see SRP 2FA login)
|
||||
Salt2 []byte
|
||||
// Base (see SRP 2FA login)
|
||||
G int
|
||||
// 2048-bit modulus (see SRP 2FA login)
|
||||
P []byte
|
||||
}
|
||||
|
||||
// Answer is result of SRP algorithm.
|
||||
type Answer struct {
|
||||
// A parameter (see SRP)
|
||||
A []byte
|
||||
// M1 parameter (see SRP)
|
||||
M1 []byte
|
||||
}
|
||||
|
||||
func xor32(a, b [sha256.Size]byte) (res [sha256.Size]byte) {
|
||||
xor.Bytes(res[:], a[:], b[:])
|
||||
return res
|
||||
}
|
||||
|
||||
func (s SRP) bigFromBytes(b []byte) *big.Int {
|
||||
return new(big.Int).SetBytes(b)
|
||||
}
|
||||
|
||||
func (s SRP) bigExp(x, y, m *big.Int) *big.Int {
|
||||
return new(big.Int).Exp(x, y, m)
|
||||
}
|
||||
|
||||
func checkInput(g int, p *big.Int) error {
|
||||
return crypto.CheckDH(g, p)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type srpArgs struct {
|
||||
password []byte
|
||||
srpB []byte
|
||||
mp Input
|
||||
}
|
||||
|
||||
func testSRPInput(t testing.TB) srpArgs {
|
||||
return srpArgs{
|
||||
password: []byte("123123"),
|
||||
srpB: getHex(t, "9C52401A6A8084EC82F01C3725D3FB448BD2F0C909F9D97726EAC4B7A74172D9"+
|
||||
"52F02466BE6734FA274D2B7429E27397F10372D66B400B80A5C5AE3F28B17BF3"+
|
||||
"105D7A2D2A885998CDC2DEFC208AEC217AB58859A9ABC2374AD93DC285F4B3FB"+
|
||||
"CAFF4143D7888F2425BD2FB711B25609CEB21757D935B1EF2F042173AD0CE2FE"+
|
||||
"0E474DAC53914BD25A8A9AED4AEA8953D55CB88621DB37B871EA0D04393AC098"+
|
||||
"7F68094CCC9DE8239251375D8FFFD263316CD528C097B7BC9FB919FBEDB76C52"+
|
||||
"5DF3413C374EE076D97A1E6D352BB7CC80FD13651B04B32E2E48C5268150842C"+
|
||||
"FD07CF855958B1B5EA9C36FDAD697FE3AEC8DCC6B1EFEC36874AF226204676CF"),
|
||||
mp: Input{
|
||||
Salt1: getHex(t, "4D11FB6BEC38F9D2546BB0F61E4F1C99A1BC0DB8F0D5F35B1291B37B213123D7ED48F3C6794D495B"),
|
||||
Salt2: getHex(t, "A1B181AAFE88188680AE32860D60BB01"),
|
||||
G: 3,
|
||||
P: getHex(t, "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F"+
|
||||
"48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37"+
|
||||
"20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64"+
|
||||
"2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4"+
|
||||
"A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754"+
|
||||
"FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4"+
|
||||
"E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F"+
|
||||
"0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestSRP(t *testing.T) {
|
||||
tests := []struct {
|
||||
args srpArgs
|
||||
want Answer
|
||||
expectError assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
args: testSRPInput(t),
|
||||
want: Answer{
|
||||
A: setByte(256, 3),
|
||||
M1: getHex(t, "999DF906BDA2C6CBB52F503406EBA2D0D0503ACE0CC302C38F13EE5010AD4051"),
|
||||
},
|
||||
expectError: assert.NoError,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
tcase := tests[i]
|
||||
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
|
||||
random := setByte(256, 1)
|
||||
srp := NewSRP(rand.Reader)
|
||||
got, err := srp.Hash(tcase.args.password, tcase.args.srpB, random, tcase.args.mp)
|
||||
if !tcase.expectError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, tcase.want, got) {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getHex(t testing.TB, in string) []byte {
|
||||
res, err := hex.DecodeString(in)
|
||||
if err != nil {
|
||||
t.Fatal("failed to get hex", err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func setByte(size, value int) []byte {
|
||||
res := make([]byte, size)
|
||||
binary.BigEndian.PutUint32(res[size-4:], uint32(value))
|
||||
return res
|
||||
}
|
||||
|
||||
const pBase64 = "xxyuucaxyQSObFIvcPE_c5gNQCOOPiHBSTTQN1Y9kw9IGYoKp8FAWCKUk9IlMPTb-jNvbgrJJROVQ67UTM58NyD9UfaUWHBaxozU_" +
|
||||
"mtrE6vcl0ZRKWkyhFTxj6-MWV9kJHf-lrsqlB1bzR1KyMxJiAcI-ps3jjxPOpBgvuZ8-aSkppWBEFGQfhYnU7VrD2tBDbp02KhLKhSzFE4O8ShHVP0" +
|
||||
"X7ZUNWWW0ud1GWC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-" +
|
||||
"t8DSxBAMRnNjjuS_MWw=="
|
||||
|
||||
func Test_checkInput(t *testing.T) {
|
||||
p, err := base64.URLEncoding.DecodeString(pBase64)
|
||||
if err != nil {
|
||||
t.Fatal("no err expected", err)
|
||||
}
|
||||
|
||||
err = checkInput(3, big.NewInt(0).SetBytes(p))
|
||||
if err != nil {
|
||||
t.Fatal("no err expected", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSRP_Auth(b *testing.B) {
|
||||
input := testSRPInput(b)
|
||||
srp := NewSRP(rand.Reader)
|
||||
random := setByte(256, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = srp.Hash(input.password, input.srpB, random, input.mp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user