// Copyright © 2022 siddharth // SPDX-License-Identifier: ISC package lib import "math/big" // SRP - implementation. // Reference http://srp.stanford.edu/design.html // SRP Client UI - Spec // // > register s@ricketyspace.net // Enter password // > ****** // Registering with server...registered! // > login s@ricketyspace.net // Enter password // > ****** // Logging in...in! // s@ricketyspace.net> // s@ricketyspace.net> login s@ricketyspace.net // login command not allowed when logged in // s@ricketyspace.net> register rsd@gnu.org // register command not allowed when logged in // s@ricketyspace.net> logout // Logging out..out! // > register s@ricketyspace.net // Already registered! // SRP Server. type SRPServer struct { users []SRPUser } // Registered user on the SRP server. type SRPUser struct { // Large safe prime. Server and client agree upon the value of // N. n *big.Int // Generator modulo N. Server and client agree upon the value // of N. g *big.Int // Multipier parameter. Server and client agree upon the value // of N. k *big.Int // Hashing object for H() function. h Sha256 // User's email address ident string // Salt. Randomly generator by the server. salt []byte // Private key derived from salt and user's pass. x *big.Int // Scrambling parameter. u *big.Int // Secret ephemeral value. b *big.Int // Password verifier. v *big.Int // Session key. sk []byte } // SRP client. type SRPClient struct { session SRPClientSession } // User session on the SRP client. type SRPClientSession struct { // Large safe prime. Client and server agree upon the value of // N. n *big.Int // Generator modulo N. Client and server agree upon the value // of N. g *big.Int // Multipier parameter. Client and server agree upon the value // of N. k *big.Int // Hashing object for H() function. h Sha256 // User's email address ident string // Scrambling parameter. u *big.Int // Secret ephemeral value. a *big.Int // Session key. sk []byte } func NewSRPUser(n, g, k, ident, pass string) (*SRPUser, error) { var err error var ok bool user := new(SRPUser) user.n, ok = new(big.Int).SetString(StripSpaceChars(n), 16) if !ok { return nil, CPError{"n is invalid"} } user.g, ok = new(big.Int).SetString(StripSpaceChars(g), 16) if !ok { return nil, CPError{"g is invalid"} } user.k, ok = new(big.Int).SetString(StripSpaceChars(k), 16) if !ok { return nil, CPError{"k is invalid"} } user.ident = ident user.x = big.NewInt(0) user.v = big.NewInt(0) // Initialize hashing object. user.h = Sha256{} user.h.Init([]uint32{}) // Generate salt. user.salt, err = RandomBytes(8) if err != nil { return nil, err } // Generate private key `x` from salt+pass m := make([]byte, 0) copy(m, user.salt) m = append(m, StrToBytes(pass)...) user.h.Message(m) user.x.SetBytes(user.h.Hash()) // Generate password verifier `v` user.v.Exp(user.g, user.x, user.n) return user, nil } func (u *SRPUser) EphemeralKeyGen() { for { u.b = big.NewInt(RandomInt(1, 10000000)) if u.b.Cmp(big.NewInt(0)) == 1 { break } } } func (u *SRPUser) EphemeralKeyPub() (*big.Int, error) { if u.k == nil || u.k.Cmp(big.NewInt(0)) != 1 { return nil, CPError{"k is not initialized"} } if u.v == nil || u.v.Cmp(big.NewInt(0)) != 1 { return nil, CPError{"v is not initialized"} } if u.g == nil || u.g.Cmp(big.NewInt(0)) != 1 { return nil, CPError{"g is not initialized"} } if u.b == nil || u.b.Cmp(big.NewInt(0)) != 1 { return nil, CPError{"b is not initialized"} } kv := new(big.Int) kv.Mul(u.k, u.v) gb := new(big.Int) gb.Exp(u.g, u.b, u.n) // pub is 'B' pub := new(big.Int) pub.Add(kv, gb) return pub, nil } func (u *SRPUser) SetScramblingParam(a *big.Int) error { b, err := u.EphemeralKeyPub() if err != nil { return err } bb := b.Bytes() ab := a.Bytes() // Make M=A+B m := make([]byte, 0) m = append(m, ab...) m = append(m, bb...) if len(m) != (len(ab) + len(bb)) { return CPError{"length of m is incorrect"} } // Hash M u.h.Message(m) h := u.h.Hash() // Set scrambling paramter u u.u = new(big.Int) u.u.SetBytes(h) if u.u.Cmp(big.NewInt(0)) != 1 { return CPError{"u is invalid"} } return nil } func NewSRPClientSession(n, g, k, ident string) (*SRPClientSession, error) { var ok bool session := new(SRPClientSession) session.n, ok = new(big.Int).SetString(StripSpaceChars(n), 16) if !ok { return nil, CPError{"n is invalid"} } session.g, ok = new(big.Int).SetString(StripSpaceChars(g), 16) if !ok { return nil, CPError{"g is invalid"} } session.k, ok = new(big.Int).SetString(StripSpaceChars(k), 16) if !ok { return nil, CPError{"k is invalid"} } session.ident = ident // Initialize hashing object. session.h = Sha256{} session.h.Init([]uint32{}) // Generate secret ephemeral value. session.a = big.NewInt(RandomInt(1, 10000000)) return session, nil } func (s *SRPClientSession) EphemeralKeyPub() (*big.Int, error) { if s.g == nil || s.g.Cmp(big.NewInt(0)) != 1 { return nil, CPError{"g is not initialized"} } if s.a == nil || s.a.Cmp(big.NewInt(0)) != 1 { return nil, CPError{"a is not initialized"} } // pub is 'A' pub := new(big.Int) pub.Exp(s.g, s.a, s.n) return pub, nil } func (s *SRPClientSession) SetScramblingParam(b *big.Int) error { a, err := s.EphemeralKeyPub() if err != nil { return err } ab := a.Bytes() bb := b.Bytes() // Make M=A+B m := make([]byte, 0) m = append(m, ab...) m = append(m, bb...) if len(m) != (len(ab) + len(bb)) { return CPError{"length of m is incorrect"} } // Hash M s.h.Message(m) h := s.h.Hash() // Set scrambling paramter u s.u = new(big.Int) s.u.SetBytes(h) if s.u.Cmp(big.NewInt(0)) != 1 { return CPError{"u is invalid"} } return nil }