diff --git a/srp.go b/srp.go index 2bf2bb9..87fcfcc 100644 --- a/srp.go +++ b/srp.go @@ -449,6 +449,93 @@ type Server struct { xM []byte } +// Marshal returns a string encoding of the Server. This encoded string can be stored by the +// server for use later in the SRP process in the case that the client and server can not +// maintain a session and thus a live copy of the Server struct. +func (s *Server) Marshal() string { + return strings.Join([]string{ + strconv.Itoa(s.s.FieldSize()), + strconv.FormatUint(uint64(s.s.h), 10), + hex.EncodeToString(s.i), + hex.EncodeToString(s.salt), + s.v.Text(10), + s.xB.Text(10), + hex.EncodeToString(s.xK), + hex.EncodeToString(s.xM), + }, ":") +} + +// UnmarshalServer parses the encoded string generated by Marshal and returns a populated +// Server struct with the data if possible, otherwise it returns an error. +func UnmarshalServer(s string) (*Server, error) { + p := strings.Split(s, ":") + if len(p) != 8 { + return nil, fmt.Errorf("unmarshal: malformed fields exp 8, saw %d", len(p)) + } + + sz, err := strconv.Atoi(p[0]) + if err != nil || sz <= 0 { + return nil, fmt.Errorf("unmarshal: malformed field size %s", p[0]) + } + pf, ok := pflist[sz] + if !ok { + return nil, fmt.Errorf("unmarshal: invalid prime-field size: %d", sz) + } + + h, err := strconv.Atoi(p[1]) + if err != nil || h <= 0 { + return nil, fmt.Errorf("unmarshal: malformed field size %s", p[1]) + } + + hf := crypto.Hash(h) + if !hf.Available() { + return nil, fmt.Errorf("unmarshal: hash algorithm %d unavailable", h) + } + + i, err := hex.DecodeString(p[2]) + if err != nil { + return nil, fmt.Errorf("unmarshal: invalid identity: %s", p[2]) + } + + salt, err := hex.DecodeString(p[3]) + if err != nil { + return nil, fmt.Errorf("unmarshal: invalid salt: %s", p[3]) + } + + v := atobi(p[4], 10) + if r := recover(); r != nil { + return nil, fmt.Errorf("unmarshal: invalid verifier: %s", p[4]) + } + + B := atobi(p[5], 10) + if r := recover(); r != nil { + return nil, fmt.Errorf("unmarshal: invalid ephemeral key B: %s", p[5]) + } + + K, err := hex.DecodeString(p[6]) + if err != nil { + return nil, fmt.Errorf("unmarshal: invalid key: %s", p[6]) + } + + M, err := hex.DecodeString(p[7]) + if err != nil { + return nil, fmt.Errorf("unmarshal: invalid M: %s", p[7]) + } + + return &Server{ + s: &SRP{ + h: hf, + pf: pf, + }, + i: i, + salt: salt, + v: v, + xB: B, + xK: K, + xM: M, + }, nil +} + // NewServer constructs a Server instance for computing a shared secret. func (s *SRP) NewServer(v *Verifier, A *big.Int) (*Server, error) { @@ -595,7 +682,7 @@ func randbytes(n int) []byte { // Generate and return a bigInt 'bits' bits in length func randBigInt(bits int) *big.Int { n := bits / 8 - if (bits%8) != 0 { + if (bits % 8) != 0 { n += 1 } b := randbytes(n) diff --git a/srp_test.go b/srp_test.go index faf499b..9ae948f 100644 --- a/srp_test.go +++ b/srp_test.go @@ -101,16 +101,21 @@ func (db *userdb) verify(t *testing.T, user, pass []byte, goodPw bool) { sCreds := srv.Credentials() // Server --> sends 'sCreds' to client + // Marshal the server for use later as-if the client can't remain connected + srv_m := srv.Marshal() // Client generates a mutual auth and sends to server mauth, err := c.Generate(sCreds) assert(err == nil, "Client.Generate: %s", err) // Client --> sends 'mauth' to server + // Unmarshal the previously marshaled server for use after the client reconnects + srv_um, err := UnmarshalServer(srv_m) + assert(err == nil, "UnmarshalServer: %s", err) // Server validates the mutual authenticator and creates its proof of having derived // the same key. This proof is sent to the client. - proof, ok := srv.ClientOk(mauth) + proof, ok := srv_um.ClientOk(mauth) if goodPw { assert(ok, "server: bad client proof") } else { @@ -128,7 +133,7 @@ func (db *userdb) verify(t *testing.T, user, pass []byte, goodPw bool) { // mutual secret -- which should be identical kc := c.RawKey() - ks := srv.RawKey() + ks := srv_um.RawKey() assert(subtle.ConstantTimeCompare(kc, ks) == 1, "key mismatch;\nclient %x, server %x", kc, ks) }