Skip to content

Commit

Permalink
Merge pull request #682 from tdakkota/feat/add-more-auth-methods
Browse files Browse the repository at this point in the history
feat(auth): add methods for updating and resetting password
  • Loading branch information
ernado committed Jan 29, 2022
2 parents 30f12fb + 26c5138 commit b084b6b
Show file tree
Hide file tree
Showing 25 changed files with 954 additions and 180 deletions.
10 changes: 3 additions & 7 deletions internal/crypto/cipher_decrypt_test.go
Expand Up @@ -11,13 +11,9 @@ import (
"github.com/gotd/td/internal/testutil"
)

type Zero struct{}

func (Zero) Read(p []byte) (n int, err error) { return len(p), nil }

func TestDecrypt(t *testing.T) {
// Test vector from grammers.
c := NewClientCipher(Zero{})
c := NewClientCipher(testutil.ZeroRand{})
var msg EncryptedMessage
b := &bin.Buffer{Buf: []byte{
122, 113, 131, 194, 193, 14, 79, 77, 249, 69, 250, 154, 154, 189, 53, 231, 195, 132,
Expand Down Expand Up @@ -57,8 +53,8 @@ func TestCipher_Decrypt(t *testing.T) {
t.Fatal(err)
}

c := NewClientCipher(Zero{})
s := NewServerCipher(Zero{})
c := NewClientCipher(testutil.ZeroRand{})
s := NewServerCipher(testutil.ZeroRand{})
tests := []struct {
name string
data []byte
Expand Down
3 changes: 2 additions & 1 deletion internal/crypto/cipher_encrypt_test.go
Expand Up @@ -6,10 +6,11 @@ import (
"github.com/stretchr/testify/require"

"github.com/gotd/td/bin"
"github.com/gotd/td/internal/testutil"
)

func TestEncrypt(t *testing.T) {
c := NewClientCipher(Zero{})
c := NewClientCipher(testutil.ZeroRand{})

var authKey Key
for i := 0; i < 256; i++ {
Expand Down
2 changes: 1 addition & 1 deletion internal/crypto/rsa_pad_test.go
Expand Up @@ -28,7 +28,7 @@ t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs
a.NoError(err)
data := bytes.Repeat([]byte{'a'}, 144)

encrypted, err := RSAPad(data, keys[0], Zero{})
encrypted, err := RSAPad(data, keys[0], testutil.ZeroRand{})
a.NoError(err)
a.Len(encrypted, 256)

Expand Down
118 changes: 118 additions & 0 deletions internal/crypto/srp/hash.go
@@ -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)
}
54 changes: 54 additions & 0 deletions internal/crypto/srp/new_hash.go
@@ -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
}
72 changes: 72 additions & 0 deletions internal/crypto/srp/new_hash_test.go
@@ -0,0 +1,72 @@
package srp

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/gotd/td/internal/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)
}
23 changes: 23 additions & 0 deletions internal/crypto/srp/pad.go
@@ -0,0 +1,23 @@
package srp

import (
"math/big"

"github.com/gotd/td/internal/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 comments on commit b084b6b

Please sign in to comment.