Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add methods for updating and resetting password #682

Merged
merged 6 commits into from
Jan 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions internal/crypto/cipher_decrypt_test.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
}
Loading