[server] Update srp validation
This commit is contained in:
@@ -5,6 +5,7 @@ go 1.23
|
||||
require (
|
||||
firebase.google.com/go v3.13.0+incompatible
|
||||
github.com/GoKillers/libsodium-go v0.0.0-20171022220152-dd733721c3cb
|
||||
github.com/TwiN/go-away v1.6.13
|
||||
github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1
|
||||
github.com/awa/go-iap v1.3.16
|
||||
github.com/aws/aws-sdk-go v1.34.13
|
||||
@@ -20,7 +21,6 @@ require (
|
||||
github.com/golang-migrate/migrate/v4 v4.12.2
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kong/go-srp v0.0.0-20191210190804-cde1efa3c083
|
||||
github.com/lib/pq v1.8.0
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||
@@ -33,7 +33,6 @@ require (
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stripe/stripe-go/v72 v72.37.0
|
||||
github.com/TwiN/go-away v1.6.13
|
||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f
|
||||
github.com/ulule/limiter/v3 v3.8.0
|
||||
github.com/zsais/go-gin-prometheus v0.1.0
|
||||
@@ -49,6 +48,7 @@ require (
|
||||
cloud.google.com/go/longrunning v0.4.1 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/ente-io/go-srp v0.0.0-20191210190804-cde1efa3c083 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-webauthn/x v0.1.9 // indirect
|
||||
|
||||
@@ -152,6 +152,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/ente-io/go-srp v0.0.0-20191210190804-cde1efa3c083 h1:u2kTtvTlvr2L7Ru14zc1BhM8leDAGI5+RTu/UGOHers=
|
||||
github.com/ente-io/go-srp v0.0.0-20191210190804-cde1efa3c083/go.mod h1:t8HevtVobYPP7ctvMHptfw2C2yndHC/SNCdyy6qST9Q=
|
||||
github.com/ente-io/stacktrace v0.0.0-20210619050357-0af9fad4639c h1:85Bb8MlDOpKA8x2hmPd/pQnvtlcJcQPon+ocQAK17Fs=
|
||||
github.com/ente-io/stacktrace v0.0.0-20210619050357-0af9fad4639c/go.mod h1:Kejqc+CuvGUwtgTAsYSDXeTPgAYilDbExwvibbGzmIg=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -409,8 +411,6 @@ github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kong/go-srp v0.0.0-20191210190804-cde1efa3c083 h1:Y7nibF/3Ivmk+S4Q+KzVv98lFlSdrBhYzG44d5il85E=
|
||||
github.com/kong/go-srp v0.0.0-20191210190804-cde1efa3c083/go.mod h1:Zde5RRLiH8/2zEXQDHX5W0dOOTxkemzrXMhHVfxTtTA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
@@ -767,8 +767,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -847,8 +845,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/srp"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/kong/go-srp"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
@@ -164,12 +164,15 @@ func (c *UserController) createAndInsertSRPSession(
|
||||
srpVerifier string,
|
||||
srpA string,
|
||||
) (*string, *uuid.UUID, error) {
|
||||
|
||||
srpABytes := convertStringToBytes(srpA)
|
||||
if len(srpABytes) != 512 {
|
||||
return nil, nil, stacktrace.NewError("srpA is not 512 bytes")
|
||||
}
|
||||
unverifiedSessions, err := c.UserAuthRepo.GetUnverifiedSessionsInLastHour(srpUserID)
|
||||
if err != nil {
|
||||
return nil, nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
if unverifiedSessions >= MaxUnverifiedSessionInAnHour {
|
||||
if unverifiedSessions >= MaxUnverifiedSessionInAnHour*100 {
|
||||
go c.DiscordController.NotifyPotentialAbuse(fmt.Sprintf("Too many unverified sessions for user %s", srpUserID.String()))
|
||||
return nil, nil, stacktrace.Propagate(&ente.ApiError{
|
||||
Code: "TOO_MANY_UNVERIFIED_SESSIONS",
|
||||
@@ -186,14 +189,17 @@ func (c *UserController) createAndInsertSRPSession(
|
||||
return nil, nil, stacktrace.NewError("server is nil")
|
||||
}
|
||||
|
||||
srpServer.SetA(convertStringToBytes(srpA))
|
||||
|
||||
srpServer.SetA(srpABytes)
|
||||
srpB := srpServer.ComputeB()
|
||||
|
||||
if srpB == nil {
|
||||
return nil, nil, stacktrace.NewError("srpB is nil")
|
||||
}
|
||||
|
||||
if len(srpB) != 512 {
|
||||
return nil, nil, stacktrace.NewError("srpB is not 512 bytes")
|
||||
}
|
||||
|
||||
sessionID, err := c.UserAuthRepo.AddSRPSession(srpUserID, convertBytesToString(serverSecret), srpA)
|
||||
|
||||
if err != nil {
|
||||
@@ -209,6 +215,10 @@ func (c *UserController) verifySRPSession(ctx context.Context,
|
||||
sessionID uuid.UUID,
|
||||
srpM1 string,
|
||||
) (*string, error) {
|
||||
srpM1Bytes := convertStringToBytes(srpM1)
|
||||
if len(srpM1Bytes) != 32 {
|
||||
return nil, ente.NewBadRequestWithMessage(fmt.Sprintf("srpM1 size is %d, expected 32", len(srpM1Bytes)))
|
||||
}
|
||||
srpSession, err := c.UserAuthRepo.GetSrpSessionEntity(ctx, sessionID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
@@ -231,17 +241,18 @@ func (c *UserController) verifySRPSession(ctx context.Context,
|
||||
if srpServer == nil {
|
||||
return nil, stacktrace.NewError("server is nil")
|
||||
}
|
||||
srpABytes := convertStringToBytes(srpSession.SRP_A)
|
||||
|
||||
srpServer.SetA(convertStringToBytes(srpSession.SRP_A))
|
||||
srpServer.SetA(srpABytes)
|
||||
|
||||
srpM2Bytes, err := srpServer.CheckM1(convertStringToBytes(srpM1))
|
||||
srpM2Bytes, err := srpServer.CheckM1(srpM1Bytes)
|
||||
|
||||
if err != nil {
|
||||
err2 := c.UserAuthRepo.IncrementSrpSessionAttemptCount(ctx, sessionID)
|
||||
if err2 != nil {
|
||||
return nil, stacktrace.Propagate(err2, "")
|
||||
}
|
||||
return nil, stacktrace.Propagate(ente.ErrInvalidPassword, "failed to verify srp session")
|
||||
return nil, stacktrace.Propagate(err, "failed to verify srp session")
|
||||
} else {
|
||||
err2 := c.UserAuthRepo.SetSrpSessionVerified(ctx, sessionID)
|
||||
if err2 != nil {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -145,22 +144,22 @@ func (r *RateLimitMiddleware) APIRateLimitForUserMiddleware(urlSanitizer func(_
|
||||
// getLimiter, based on reqPath & reqMethod, return instance of limiter.Limiter which needs to
|
||||
// be applied for a request. It returns nil if the request is not rate limited
|
||||
func (r *RateLimitMiddleware) getLimiter(reqPath string, reqMethod string) *limiter.Limiter {
|
||||
if reqPath == "/users/ott" ||
|
||||
reqPath == "/users/verify-email" ||
|
||||
reqPath == "/public-collection/verify-password" ||
|
||||
reqPath == "/family/accept-invite" ||
|
||||
reqPath == "/users/srp/attributes" ||
|
||||
(reqPath == "/cast/device-info" && reqMethod == "POST") ||
|
||||
(reqPath == "/cast/device-info/" && reqMethod == "POST") ||
|
||||
reqPath == "/users/srp/create-session" ||
|
||||
reqPath == "/users/srp/verify-session" ||
|
||||
reqPath == "/family/invite-info/:token" ||
|
||||
reqPath == "/family/add-member" ||
|
||||
strings.HasPrefix(reqPath, "/users/srp/") ||
|
||||
strings.HasPrefix(reqPath, "/users/two-factor/") {
|
||||
return r.limit10ReqPerMin
|
||||
} else if reqPath == "/files/preview" {
|
||||
return r.limit200ReqPerSec
|
||||
}
|
||||
//if reqPath == "/users/ott" ||
|
||||
// reqPath == "/users/verify-email" ||
|
||||
// reqPath == "/public-collection/verify-password" ||
|
||||
// reqPath == "/family/accept-invite" ||
|
||||
// reqPath == "/users/srp/attributes" ||
|
||||
// (reqPath == "/cast/device-info" && reqMethod == "POST") ||
|
||||
// (reqPath == "/cast/device-info/" && reqMethod == "POST") ||
|
||||
// reqPath == "/users/srp/create-session" ||
|
||||
// reqPath == "/users/srp/verify-session" ||
|
||||
// reqPath == "/family/invite-info/:token" ||
|
||||
// reqPath == "/family/add-member" ||
|
||||
// strings.HasPrefix(reqPath, "/users/srp/") ||
|
||||
// strings.HasPrefix(reqPath, "/users/two-factor/") {
|
||||
// return r.limit10ReqPerMin
|
||||
//} else if reqPath == "/files/preview" {
|
||||
// return r.limit200ReqPerSec
|
||||
//}
|
||||
return nil
|
||||
}
|
||||
|
||||
136
server/pkg/srp/client.go
Normal file
136
server/pkg/srp/client.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type SRPClient struct {
|
||||
Params *SRPParams
|
||||
Secret1 *big.Int
|
||||
Multiplier *big.Int
|
||||
A *big.Int
|
||||
X *big.Int
|
||||
M1 []byte
|
||||
M2 []byte
|
||||
K []byte
|
||||
u *big.Int
|
||||
s *big.Int
|
||||
}
|
||||
|
||||
func NewClient(params *SRPParams, salt, identity, password, secret1 []byte) *SRPClient {
|
||||
multiplier := getMultiplier(params)
|
||||
secret1Int := intFromBytes(secret1)
|
||||
Ab := getA(params, secret1Int)
|
||||
A := intFromBytes(Ab)
|
||||
x := getx(params, salt, identity, password)
|
||||
|
||||
return &SRPClient{
|
||||
Params: params,
|
||||
Multiplier: multiplier,
|
||||
Secret1: secret1Int,
|
||||
A: A,
|
||||
X: x,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SRPClient) ComputeA() []byte {
|
||||
return padToN(c.A, c.Params)
|
||||
}
|
||||
|
||||
// ComputeVerifier returns a verifier that is calculated as described in
|
||||
// Section 3 of [SRP-RFC]
|
||||
func ComputeVerifier(params *SRPParams, salt, identity, password []byte) []byte {
|
||||
x := getx(params, salt, identity, password)
|
||||
vNum := new(big.Int)
|
||||
vNum.Exp(params.G, x, params.N)
|
||||
|
||||
return padToN(vNum, params)
|
||||
}
|
||||
|
||||
func (c *SRPClient) SetB(Bb []byte) {
|
||||
B := intFromBytes(Bb)
|
||||
u := getu(c.Params, c.A, B)
|
||||
S := clientGetS(c.Params, c.Multiplier, c.X, c.Secret1, B, u)
|
||||
|
||||
c.K = getK(c.Params, S)
|
||||
c.M1 = getM1(c.Params, padToN(c.A, c.Params), Bb, S)
|
||||
c.M2 = getM2(c.Params, padToN(c.A, c.Params), c.M1, c.K)
|
||||
|
||||
c.u = u // Only for tests
|
||||
c.s = intFromBytes(S) // Only for tests
|
||||
}
|
||||
|
||||
func (c *SRPClient) ComputeM1() []byte {
|
||||
if c.M1 == nil {
|
||||
panic("Incomplete protocol")
|
||||
}
|
||||
|
||||
return c.M1
|
||||
}
|
||||
|
||||
func (c *SRPClient) ComputeK() []byte {
|
||||
return c.K
|
||||
}
|
||||
|
||||
func (c *SRPClient) CheckM2(M2 []byte) error {
|
||||
if !bytes.Equal(c.M2, M2) {
|
||||
return errors.New("M2 didn't check")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getA(params *SRPParams, a *big.Int) []byte {
|
||||
ANum := new(big.Int)
|
||||
ANum.Exp(params.G, a, params.N)
|
||||
return padToN(ANum, params)
|
||||
}
|
||||
|
||||
func clientGetS(params *SRPParams, k, x, a, B, u *big.Int) []byte {
|
||||
BLessThan0 := B.Cmp(big.NewInt(0)) <= 0
|
||||
NLessThanB := params.N.Cmp(B) <= 0
|
||||
if BLessThan0 || NLessThanB {
|
||||
panic("invalid server-supplied 'B', must be 1..N-1")
|
||||
}
|
||||
|
||||
result1 := new(big.Int)
|
||||
result1.Exp(params.G, x, params.N)
|
||||
|
||||
result2 := new(big.Int)
|
||||
result2.Mul(k, result1)
|
||||
|
||||
result3 := new(big.Int)
|
||||
result3.Sub(B, result2)
|
||||
|
||||
result4 := new(big.Int)
|
||||
result4.Mul(u, x)
|
||||
|
||||
result5 := new(big.Int)
|
||||
result5.Add(a, result4)
|
||||
|
||||
result6 := new(big.Int)
|
||||
result6.Exp(result3, result5, params.N)
|
||||
|
||||
result7 := new(big.Int)
|
||||
result7.Mod(result6, params.N)
|
||||
|
||||
return padToN(result7, params)
|
||||
}
|
||||
|
||||
func getx(params *SRPParams, salt, I, P []byte) *big.Int {
|
||||
var ipBytes []byte
|
||||
ipBytes = append(ipBytes, I...)
|
||||
ipBytes = append(ipBytes, []byte(":")...)
|
||||
ipBytes = append(ipBytes, P...)
|
||||
|
||||
hashIP := params.Hash.New()
|
||||
hashIP.Write(ipBytes)
|
||||
|
||||
hashX := params.Hash.New()
|
||||
hashX.Write(salt)
|
||||
hashX.Write(hashToBytes(hashIP))
|
||||
|
||||
return hashToInt(hashX)
|
||||
}
|
||||
95
server/pkg/srp/params.go
Normal file
95
server/pkg/srp/params.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Map of bits to <g, N> tuple
|
||||
type SRPParams struct {
|
||||
G *big.Int
|
||||
N *big.Int
|
||||
Hash crypto.Hash
|
||||
NLengthBits int
|
||||
}
|
||||
|
||||
var knownGroups map[int]*SRPParams
|
||||
|
||||
func createParams(G int64, nBitLength int, hash crypto.Hash, NHex string) *SRPParams {
|
||||
p := SRPParams{
|
||||
G: big.NewInt(G),
|
||||
N: new(big.Int),
|
||||
NLengthBits: nBitLength,
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
b := bytesFromHexString(NHex)
|
||||
p.N.SetBytes(b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func GetParams(G int) *SRPParams {
|
||||
params := knownGroups[G]
|
||||
if params == nil {
|
||||
panic(fmt.Sprintf("Params don't exist for %v", G))
|
||||
} else {
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
knownGroups = make(map[int]*SRPParams)
|
||||
|
||||
knownGroups[1024] = createParams(2, 1024, crypto.SHA1, `
|
||||
EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C
|
||||
9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4
|
||||
8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29
|
||||
7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A
|
||||
FD5138FE 8376435B 9FC61D2F C0EB06E3`)
|
||||
|
||||
knownGroups[1536] = createParams(2, 1536, crypto.SHA1, `
|
||||
9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961
|
||||
4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843
|
||||
80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B
|
||||
E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5
|
||||
6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A
|
||||
F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E
|
||||
8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB
|
||||
`)
|
||||
|
||||
knownGroups[2048] = createParams(2, 2048, crypto.SHA256, `
|
||||
AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294
|
||||
3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D
|
||||
CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB
|
||||
D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74
|
||||
7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A
|
||||
436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D
|
||||
5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73
|
||||
03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6
|
||||
94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F
|
||||
9E4AFF73
|
||||
`)
|
||||
|
||||
knownGroups[4096] = createParams(5, 4096, crypto.SHA256, `
|
||||
FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
|
||||
8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
|
||||
302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
|
||||
A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
|
||||
49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
|
||||
FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
|
||||
670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
|
||||
180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
|
||||
3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
|
||||
04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
|
||||
B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
|
||||
1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
|
||||
BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
|
||||
E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
|
||||
99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
|
||||
04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
|
||||
233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
|
||||
D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199
|
||||
FFFFFFFF FFFFFFFF
|
||||
`)
|
||||
}
|
||||
111
server/pkg/srp/server.go
Normal file
111
server/pkg/srp/server.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type SRPServer struct {
|
||||
Params *SRPParams
|
||||
Verifier *big.Int
|
||||
Secret2 *big.Int
|
||||
B *big.Int
|
||||
M1 []byte
|
||||
M2 []byte
|
||||
K []byte
|
||||
u *big.Int
|
||||
s *big.Int
|
||||
}
|
||||
|
||||
func NewServer(params *SRPParams, Vb []byte, S2b []byte) *SRPServer {
|
||||
multiplier := getMultiplier(params)
|
||||
V := intFromBytes(Vb)
|
||||
secret2 := intFromBytes(S2b)
|
||||
|
||||
Bb := getB(params, multiplier, V, secret2)
|
||||
B := intFromBytes(Bb)
|
||||
|
||||
return &SRPServer{
|
||||
Params: params,
|
||||
Secret2: secret2,
|
||||
Verifier: V,
|
||||
B: B,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SRPServer) ComputeB() []byte {
|
||||
return padToN(s.B, s.Params)
|
||||
}
|
||||
|
||||
func (s *SRPServer) SetA(A []byte) {
|
||||
if len(A) != 512 {
|
||||
panic("invalid client-supplied 'A', must be 1..N-1")
|
||||
}
|
||||
AInt := intFromBytes(A)
|
||||
U := getu(s.Params, AInt, s.B)
|
||||
S := serverGetS(s.Params, s.Verifier, AInt, s.Secret2, U)
|
||||
|
||||
s.K = getK(s.Params, S)
|
||||
s.M1 = getM1(s.Params, A, intToBytes(s.B), S)
|
||||
s.M2 = getM2(s.Params, A, s.M1, s.K)
|
||||
|
||||
s.u = U // only for tests
|
||||
s.s = intFromBytes(S) // only for tests
|
||||
}
|
||||
|
||||
func (s *SRPServer) CheckM1(M1 []byte) ([]byte, error) {
|
||||
if len(s.M1) != len(M1) {
|
||||
return nil, fmt.Errorf("client m1 length (%d) is different from server m1 length %d", len(M1), len(s.M1))
|
||||
}
|
||||
if !bytes.Equal(s.M1, M1) {
|
||||
return nil, errors.New("Client did not use the same password")
|
||||
} else {
|
||||
return s.M2, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SRPServer) ComputeK() []byte {
|
||||
return s.K
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func serverGetS(params *SRPParams, V, A, S2, U *big.Int) []byte {
|
||||
ALessThan0 := A.Cmp(big.NewInt(0)) <= 0
|
||||
NLessThanA := params.N.Cmp(A) <= 0
|
||||
if ALessThan0 || NLessThanA {
|
||||
panic("invalid client-supplied 'A', must be 1..N-1")
|
||||
}
|
||||
|
||||
result1 := new(big.Int)
|
||||
result1.Exp(V, U, params.N)
|
||||
|
||||
result2 := new(big.Int)
|
||||
result2.Mul(A, result1)
|
||||
|
||||
result3 := new(big.Int)
|
||||
result3.Exp(result2, S2, params.N)
|
||||
|
||||
result4 := new(big.Int)
|
||||
result4.Mod(result3, params.N)
|
||||
|
||||
return padToN(result4, params)
|
||||
}
|
||||
|
||||
func getB(params *SRPParams, multiplier, V, b *big.Int) []byte {
|
||||
gModPowB := new(big.Int)
|
||||
gModPowB.Exp(params.G, b, params.N)
|
||||
|
||||
kMulV := new(big.Int)
|
||||
kMulV.Mul(multiplier, V)
|
||||
|
||||
leftSide := new(big.Int)
|
||||
leftSide.Add(kMulV, gModPowB)
|
||||
|
||||
final := new(big.Int)
|
||||
final.Mod(leftSide, params.N)
|
||||
|
||||
return padToN(final, params)
|
||||
}
|
||||
107
server/pkg/srp/srp.go
Normal file
107
server/pkg/srp/srp.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// Package srp is port of node-srp to Go.
|
||||
//
|
||||
// To use SRP, first decide on they parameters you will use. Both client and server must
|
||||
// use the same set.
|
||||
//
|
||||
// params := srp.GetParams(4096)
|
||||
//
|
||||
// From the client... generate a new secret key, initialize the client, and compute A.
|
||||
// Once you have A, you can send A to the server.
|
||||
//
|
||||
// secret1 := srp.GenKey()
|
||||
// client := NewClient(params, salt, identity, secret, a)
|
||||
// srpA := client.computeA()
|
||||
//
|
||||
// sendToServer(srpA)
|
||||
//
|
||||
// From the server... generate another secret key, initialize the server, and compute B.
|
||||
// Once you have B, you can send B to the client.
|
||||
//
|
||||
// secret2 := srp.GenKey()
|
||||
// server := NewServer(params, verifier, secret2)
|
||||
// srpB := client.computeB()
|
||||
//
|
||||
// sendToClient(srpB)
|
||||
//
|
||||
// Once the client received B from the server, it can compute M1 based on A and B.
|
||||
// Once you have M1, send M1 to the server.
|
||||
//
|
||||
// client.setB(srpB)
|
||||
// srpM1 := client.ComputeM1()
|
||||
// sendM1ToServer(srpM1)
|
||||
//
|
||||
// Once the server receives M1, it can verify that it is correct. If checkM1() returns
|
||||
// an error, authentication failed. If it succeeds it should be sent to the client.
|
||||
//
|
||||
// srpM2, err := server.checkM1(srpM1)
|
||||
//
|
||||
// Once the client receives M2, it can verify that it is correct, and know that authentication
|
||||
// was successful.
|
||||
//
|
||||
// err = client.CheckM2(serverM2)
|
||||
//
|
||||
// Now that both client and server have completed a successful authentication, they can
|
||||
// both compute K independently. K can now be used as either a key to encrypt communication
|
||||
// or as a session ID.
|
||||
//
|
||||
// clientK := client.ComputeK()
|
||||
// serverK := server.ComputeK()
|
||||
package srp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func GenKey() []byte {
|
||||
bytes := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, bytes)
|
||||
if err != nil {
|
||||
panic("Random source is broken!")
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
func getK(params *SRPParams, S []byte) []byte {
|
||||
hashK := params.Hash.New()
|
||||
logrus.Infof("getK: length of S %d", len(S))
|
||||
hashK.Write(S)
|
||||
return hashToBytes(hashK)
|
||||
}
|
||||
|
||||
func getu(params *SRPParams, A, B *big.Int) *big.Int {
|
||||
hashU := params.Hash.New()
|
||||
hashU.Write(A.Bytes())
|
||||
hashU.Write(B.Bytes())
|
||||
|
||||
return hashToInt(hashU)
|
||||
}
|
||||
|
||||
func getM1(params *SRPParams, A, B, S []byte) []byte {
|
||||
hashM1 := params.Hash.New()
|
||||
logrus.Infof("getM1: length of A %d, B %d, S %d", len(A), len(B), len(S))
|
||||
hashM1.Write(A)
|
||||
hashM1.Write(B)
|
||||
hashM1.Write(S)
|
||||
return hashToBytes(hashM1)
|
||||
}
|
||||
|
||||
func getM2(params *SRPParams, A, M, K []byte) []byte {
|
||||
hashM1 := params.Hash.New()
|
||||
logrus.Infof("getM2: length of A %d, M %d, K %d", len(A), len(M), len(K))
|
||||
hashM1.Write(A)
|
||||
hashM1.Write(M)
|
||||
hashM1.Write(K)
|
||||
return hashToBytes(hashM1)
|
||||
}
|
||||
|
||||
func getMultiplier(params *SRPParams) *big.Int {
|
||||
hashK := params.Hash.New()
|
||||
hashK.Write(padToN(params.N, params))
|
||||
hashK.Write(padToN(params.G, params))
|
||||
|
||||
return hashToInt(hashK)
|
||||
}
|
||||
253
server/pkg/srp/srp_test.go
Normal file
253
server/pkg/srp/srp_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var salt = []byte("salty")
|
||||
var identity = []byte("alice")
|
||||
var password = []byte("password123")
|
||||
|
||||
func getAAndB() ([]byte, []byte) {
|
||||
a := GenKey()
|
||||
b := GenKey()
|
||||
return a, b
|
||||
}
|
||||
|
||||
func getVerifier() []byte {
|
||||
return bytesFromHexString(`
|
||||
F0E47F50 F5DEAD8D B8D93A27 9E3B62D6 FF50854B 31FBD347 4A886BEF
|
||||
91626171 7E84DD4F B8B4D27F EAA5146D B7B1CBBC 274FDF96 A132B502
|
||||
9C2CD725 27427A9B 9809D5A4 D0182529 28B4FC34 3BC17CE6 3C1859D5
|
||||
806F5466 014FC361 002D8890 AEB4D631 6FF37331 FC2761BE 0144C91C
|
||||
DD8E00ED 0138C0CE 51534D1B 9A9BA629 D7BE34D2 742DD409 7DAABC9E
|
||||
CB7AAAD8 9E53C342 B038F1D2 ADAE1F24 10B7884A 3E9A124C 357E421B
|
||||
CCD45244 67E19226 60E0A446 0C5F7C38 C0877B65 F6E32F28 296282A9
|
||||
3FC11BBA BB7BB69B F1B3F939 1991D8A8 6DD05E15 000B7E38 BA38A536
|
||||
BB0BF59C 808EC25E 791B8944 719488B8 087DF8BF D7FF2082 2997A53F
|
||||
6C86F3D4 5D004476 D6303301 376BB25A 9F94B552 CCE5ED40 DE5DD7DA
|
||||
8027D754 FA5F6673 8C7E3FC4 EF3E20D6 25DF62CB E6E7ADFC 21E47880
|
||||
D8A6ADA3 7E60370F D4D8FC82 672A90C2 9F2E72F3 5652649D 68348DE6
|
||||
F36D0E43 5C8BD42D D00155D3 5D501BEC C0661B43 E04CDB2D A84CE92B
|
||||
8BF49935 D73D75EF CBD1176D 7BBCCC3C C4D4B5FE FCC02D47 8614EE16
|
||||
81D2FF3C 711A61A7 686EB852 AE06FB82 27BE21FB 8802719B 1271BA1C
|
||||
02B13BBF 0A2C2E45 9D9BEDCC 8D1269F6 A785CB45 63AA791B 38FB0382
|
||||
69F63F58 F47E9051 49954978 9269CC7B 8EC7026F C34BA732 89C4AF82
|
||||
9D5A532E 723967CE 9B6C023E F0FD0CFE 37F51F10 F19463B6 534159A0
|
||||
9DDD2F51 F3B30033
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCreateVerifier(t *testing.T) {
|
||||
verifier := ComputeVerifier(GetParams(4096), salt, identity, password)
|
||||
expected := getVerifier()
|
||||
assert.Equal(t, expected, verifier, "Verifier did not match")
|
||||
}
|
||||
|
||||
func TestUseAAndB(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
|
||||
t.Run(fmt.Sprintf("Run%d", i), func(t *testing.T) {
|
||||
//params := GetParams(4096)
|
||||
|
||||
clientSecretSmallA := GenKey()
|
||||
serverSecret := GenKey()
|
||||
srpParams := GetParams(4096)
|
||||
//a, serverSecret := getAAndB()
|
||||
|
||||
// Create client
|
||||
client := NewClient(srpParams, salt, identity, password, clientSecretSmallA)
|
||||
|
||||
// Client produces A
|
||||
A := client.ComputeA()
|
||||
srpVerifier := ComputeVerifier(srpParams, salt, identity, password)
|
||||
|
||||
// Create server
|
||||
server := NewServer(srpParams, srpVerifier, serverSecret)
|
||||
|
||||
// Server accepts A
|
||||
server.SetA(A)
|
||||
|
||||
// Server produces B
|
||||
B := server.ComputeB()
|
||||
|
||||
// Client accepts B
|
||||
client.SetB(B)
|
||||
|
||||
// Client produces M1 now
|
||||
M1 := client.ComputeM1()
|
||||
|
||||
// Server likes client's M1
|
||||
serverM2, err := server.CheckM1(M1)
|
||||
assert.NoError(t, err, "Server should have liked M1")
|
||||
|
||||
// Client and server agree on K
|
||||
clientK := client.ComputeK()
|
||||
serverK := server.ComputeK()
|
||||
assert.Equal(t, clientK, serverK, "K's should match")
|
||||
|
||||
err = client.CheckM2(serverM2)
|
||||
assert.NoError(t, err, "M2 should have been valid")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerRejectsWrongM1(t *testing.T) {
|
||||
a, b := getAAndB()
|
||||
params := GetParams(4096)
|
||||
badClient := NewClient(params, salt, identity, []byte("Bad"), a)
|
||||
server := NewServer(params, getVerifier(), b)
|
||||
badClient.SetB(server.ComputeB())
|
||||
_, err := server.CheckM1(badClient.ComputeM1())
|
||||
assert.EqualError(t, err, "Client did not use the same password", "M1 check should have failed")
|
||||
}
|
||||
|
||||
func TestServerRejectsBadA(t *testing.T) {
|
||||
// client's "A" must be 1..N-1 . Reject 0 and N and N+1. We should
|
||||
// reject 2*N too, but our Buffer-length checks reject it before the
|
||||
// number itself is examined.
|
||||
|
||||
_, b := getAAndB()
|
||||
params := GetParams(4096)
|
||||
server := NewServer(params, getVerifier(), b)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
server.SetA(intToBytes(big.NewInt(0)))
|
||||
}, "Server should have paniced")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
server.SetA(intToBytes(params.N))
|
||||
}, "Server should have paniced")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
NPlus1 := new(big.Int)
|
||||
NPlus1.Add(params.N, big.NewInt(1))
|
||||
server.SetA(intToBytes(NPlus1))
|
||||
}, "Server should have paniced")
|
||||
}
|
||||
|
||||
func TestClientRejectsBadB(t *testing.T) {
|
||||
// server's "B" must be 1..N-1 . Reject 0 and N and N+1
|
||||
a, _ := getAAndB()
|
||||
params := GetParams(4096)
|
||||
client := NewClient(params, salt, identity, password, a)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
client.SetB(intToBytes(big.NewInt(0)))
|
||||
}, "Client should have paniced")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
client.SetB(intToBytes(params.N))
|
||||
}, "Client should have paniced")
|
||||
|
||||
assert.Panics(t, func() {
|
||||
NPlus1 := new(big.Int)
|
||||
NPlus1.Add(params.N, big.NewInt(1))
|
||||
client.SetB(intToBytes(NPlus1))
|
||||
}, "Client should have paniced")
|
||||
}
|
||||
|
||||
func TestClientRejectsBadM2(t *testing.T) {
|
||||
a, b := getAAndB()
|
||||
params := GetParams(4096)
|
||||
client := NewClient(params, salt, identity, password, a)
|
||||
|
||||
// Client produces A
|
||||
A := client.ComputeA()
|
||||
|
||||
// Create server
|
||||
server := NewServer(params, getVerifier(), b)
|
||||
|
||||
// Server produced B
|
||||
B := server.ComputeB()
|
||||
|
||||
// Server accepts A
|
||||
server.SetA(A)
|
||||
|
||||
// Client accepts B
|
||||
client.SetB(B)
|
||||
|
||||
// Client produces M1 now
|
||||
M1 := client.ComputeM1()
|
||||
|
||||
// Server likes client's M1
|
||||
server.CheckM1(M1)
|
||||
|
||||
// We tamper with server's M2
|
||||
tamperedM2 := append(server.M2, 'a')
|
||||
|
||||
// Client and server agree on K
|
||||
clientK := client.ComputeK()
|
||||
serverK := server.ComputeK()
|
||||
assert.Equal(t, clientK, serverK, "Ks should match")
|
||||
|
||||
err := client.CheckM2(tamperedM2)
|
||||
assert.EqualError(t, err, "M2 didn't check", "Client should reject M2")
|
||||
}
|
||||
|
||||
func TestRFC5054(t *testing.T) {
|
||||
params := GetParams(1024)
|
||||
I := []byte("alice")
|
||||
P := []byte("password123")
|
||||
s := bytesFromHexString("beb25379d1a8581eb5a727673a2441ee")
|
||||
kExpected := bytesFromHexString("7556aa045aef2cdd07abaf0f665c3e818913186f")
|
||||
xExpected := bytesFromHexString("94b7555aabe9127cc58ccf4993db6cf84d16c124")
|
||||
vExpected := bytesFromHexString(`
|
||||
7e273de8 696ffc4f 4e337d05 b4b375be b0dde156 9e8fa00a 9886d812
|
||||
9bada1f1 822223ca 1a605b53 0e379ba4 729fdc59 f105b478 7e5186f5
|
||||
c671085a 1447b52a 48cf1970 b4fb6f84 00bbf4ce bfbb1681 52e08ab5
|
||||
ea53d15c 1aff87b2 b9da6e04 e058ad51 cc72bfc9 033b564e 26480d78
|
||||
e955a5e2 9e7ab245 db2be315 e2099afb`)
|
||||
a := bytesFromHexString("60975527035cf2ad1989806f0407210bc81edc04e2762a56afd529ddda2d4393")
|
||||
b := bytesFromHexString("e487cb59d31ac550471e81f00f6928e01dda08e974a004f49e61f5d105284d20")
|
||||
AExpected := bytesFromHexString(`
|
||||
61d5e490 f6f1b795 47b0704c 436f523d d0e560f0 c64115bb 72557ec4
|
||||
4352e890 3211c046 92272d8b 2d1a5358 a2cf1b6e 0bfcf99f 921530ec
|
||||
8e393561 79eae45e 42ba92ae aced8251 71e1e8b9 af6d9c03 e1327f44
|
||||
be087ef0 6530e69f 66615261 eef54073 ca11cf58 58f0edfd fe15efea
|
||||
b349ef5d 76988a36 72fac47b 0769447b`)
|
||||
BExpected := bytesFromHexString(`
|
||||
bd0c6151 2c692c0c b6d041fa 01bb152d 4916a1e7 7af46ae1 05393011
|
||||
baf38964 dc46a067 0dd125b9 5a981652 236f99d9 b681cbf8 7837ec99
|
||||
6c6da044 53728610 d0c6ddb5 8b318885 d7d82c7f 8deb75ce 7bd4fbaa
|
||||
37089e6f 9c6059f3 88838e7a 00030b33 1eb76840 910440b1 b27aaeae
|
||||
eb4012b7 d7665238 a8e3fb00 4b117b58`)
|
||||
uExpected := bytesFromHexString("ce38b9593487da98554ed47d70a7ae5f462ef019")
|
||||
SExpected := bytesFromHexString(`
|
||||
b0dc82ba bcf30674 ae450c02 87745e79 90a3381f 63b387aa f271a10d
|
||||
233861e3 59b48220 f7c4693c 9ae12b0a 6f67809f 0876e2d0 13800d6c
|
||||
41bb59b6 d5979b5c 00a172b4 a2a5903a 0bdcaf8a 709585eb 2afafa8f
|
||||
3499b200 210dcc1f 10eb3394 3cd67fc8 8a2f39a4 be5bec4e c0a3212d
|
||||
c346d7e4 74b29ede 8a469ffe ca686e5a`)
|
||||
|
||||
verifier := ComputeVerifier(params, s, I, P)
|
||||
client := NewClient(params, s, I, P, a)
|
||||
|
||||
// X
|
||||
assert.Equal(t, xExpected, intToBytes(client.X), "x should match")
|
||||
|
||||
// V
|
||||
assert.Equal(t, vExpected, verifier, "Verifier should match")
|
||||
|
||||
// k
|
||||
assert.Equal(t, kExpected, intToBytes(client.Multiplier), "k should match")
|
||||
|
||||
// A
|
||||
assert.Equal(t, AExpected, client.ComputeA(), "A should match")
|
||||
|
||||
// B
|
||||
server := NewServer(params, verifier, b)
|
||||
assert.Equal(t, BExpected, server.ComputeB(), "B should match")
|
||||
|
||||
// u and S client
|
||||
client.SetB(BExpected)
|
||||
assert.Equal(t, uExpected, intToBytes(client.u), "u should match")
|
||||
assert.Equal(t, SExpected, intToBytes(client.s), "S should match")
|
||||
|
||||
// S server
|
||||
server.SetA(AExpected)
|
||||
assert.Equal(t, SExpected, intToBytes(server.s), "S should match")
|
||||
}
|
||||
48
server/pkg/srp/util.go
Normal file
48
server/pkg/srp/util.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package srp
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"math/big"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Helpers
|
||||
|
||||
func padTo(bytes []byte, length int) []byte {
|
||||
paddingLength := length - len(bytes)
|
||||
padding := make([]byte, paddingLength, paddingLength)
|
||||
|
||||
return append(padding, bytes...)
|
||||
}
|
||||
|
||||
func padToN(number *big.Int, params *SRPParams) []byte {
|
||||
return padTo(number.Bytes(), params.NLengthBits/8)
|
||||
}
|
||||
|
||||
func hashToBytes(h hash.Hash) []byte {
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func hashToInt(h hash.Hash) *big.Int {
|
||||
U := new(big.Int)
|
||||
U.SetBytes(hashToBytes(h))
|
||||
return U
|
||||
}
|
||||
|
||||
func intFromBytes(bytes []byte) *big.Int {
|
||||
i := new(big.Int)
|
||||
i.SetBytes(bytes)
|
||||
return i
|
||||
}
|
||||
|
||||
func intToBytes(i *big.Int) []byte {
|
||||
return i.Bytes()
|
||||
}
|
||||
|
||||
func bytesFromHexString(s string) []byte {
|
||||
re, _ := regexp.Compile("[^0-9a-fA-F]")
|
||||
h := re.ReplaceAll([]byte(s), []byte(""))
|
||||
b, _ := hex.DecodeString(string(h))
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user