schastsp/internal/client/keyset/keyset.go

253 lines
5.2 KiB
Go

package keyset
import (
"encoding/binary"
"errors"
"git.xdrm.io/schastsp/context"
"git.xdrm.io/schastsp/util/scha"
"io"
)
const SecretSize = scha.HSIZE * 4
/* Attributes */
type T struct {
ctx *context.T // current context
depth uint16 // cur depth
sec []byte // secret
mcode uint8 // migration code (secret renewal)
// 0: none
// 1: need to migrate
// 2: waiting migration
}
/* (1) Creates a new KeySet
*
* @ctx<Context> Context constants
*
---------------------------------------------------------*/
func Create(ctx *context.T) (*T, error) {
/* (1) Fail if min+thre >= max */
if ctx == nil {
return nil, errors.New("Context must not be null")
}
/* (2) Instanciate */
var instance = new(T)
/* (3) Set attributes */
instance.ctx = ctx
/* (4) Generate values if <nil> secret */
if instance.sec == nil {
instance.generate()
}
return instance, nil
}
/* (2) Get current hash
*
* @return digest<[]byte]> Current hash representing the set
*
---------------------------------------------------------*/
func (s T) CurrentHash() ([]byte, error) {
/* (1) Get digest */
digest, err := scha.Hash(s.sec, s.depth)
/* (2) Dispatch error */
if err != nil {
return nil, err
}
/* (3) Else -> return digest */
return digest, nil
}
/* (3) Decrement depth
*
* @return remaining<uint> Remaining hashes before migration
*
---------------------------------------------------------*/
func (s *T) Decrement() uint16 {
/* (1) Decrement the depth */
s.depth--
/* (2) If near minDepth (10 far): set consumption to 1 */
if s.mcode == 0 && s.depth <= s.ctx.MinDepth()+s.ctx.DepthThreshold() {
s.mcode = 1
}
/* (3) Return remaining attempts */
return s.depth - s.ctx.MinDepth()
}
/* (4) Serialisation
*
* @return serial<string> String representation
*
*
* === FORMAT ===
*
* | hsize | secret | depth |
* +-----------+------------+---------+
* | 16 bits | hsize bits | 16 bits |
*
* == ENDIANNESS ==
*
* network endianness -> big-endian
*
---------------------------------------------------------*/
func (s *T) Store(writer io.Writer) error {
var err error
/* (1) Copy secret size */
err = binary.Write(writer, binary.BigEndian, uint16(len(s.sec)))
if err != nil {
return err
}
/* (2) Copy secret */
err = binary.Write(writer, binary.BigEndian, s.sec)
if err != nil {
return err
}
/* (3) Copy depth */
err = binary.Write(writer, binary.BigEndian, s.depth)
if err != nil {
return err
}
/* (4) Copy migration code */
err = binary.Write(writer, binary.BigEndian, s.mcode)
if err != nil {
return err
}
return nil
}
/* (5) Builds a KeySet from its serial representation
*
* @serial<string> String representation
*
* @return instance<*Set> The corresponding set
* @return err<error> Optional error
*
---------------------------------------------------------*/
func (s *T) Fetch(reader io.Reader) error {
var err error
var secretLength uint16
/* (1) Read the secret size */
err = binary.Read(reader, binary.BigEndian, &secretLength)
if err != nil {
return err
}
/* (2) Fail if secretLength lower than digest size */
if secretLength < scha.HSIZE {
return errors.New("Invalid secret size (must be at least the same size as a digest)")
}
/* (3) Try to copy the secret */
s.sec = make([]byte, secretLength)
err = binary.Read(reader, binary.BigEndian, &s.sec)
if err != nil {
return err
}
/* (4) Manage invalid secret size (mismatch secretLength) */
if uint16(len(s.sec)) != secretLength {
return errors.New("Mismatching secret size")
}
/* (5) Try to copy the depth */
err = binary.Read(reader, binary.BigEndian, &s.depth)
if err != nil {
return err
}
/* (6) Try to copy the migration code */
err = binary.Read(reader, binary.BigEndian, &s.mcode)
if err != nil {
return err
}
return nil
}
/* (6) Getter/Setter for migration code 'mcode'
*
* @mcode<uint8> [OPT] New value
*
* @return mcode<uint8> Migration code
*
---------------------------------------------------------*/
func (s *T) MigrationCode(optional ...uint8) uint8 {
/* (1) If no valid code given -> return current one */
if len(optional) < 1 || optional[0] > 2 {
return s.mcode
}
/* (2) Set new code */
s.mcode = optional[0]
/* (3) Return new code */
return s.mcode
}
/* (7) Updates depth for rescuing from desynchroisation
*
* @lastHash<[]byte> Last received hash
*
* @return error<err> Error or NIL if not
*
* @description We must update @depth so the next sent hash h_depth(k)
* hashed MIN times equals the received hash @lastHash
* We must find depth = i-MIN+1
* with i so that h_MIN( h_i(k) ) = lastHash
*
---------------------------------------------------------*/
func (s *T) Rescue(lastHash []byte) error {
/* (1) Browse possible values
---------------------------------------------------------*/
for i := s.depth; i <= s.depth+s.ctx.MinDepth(); i++ {
/* (1) Process hash */
currentHash, err := scha.Hash(s.sec, i)
if err != nil { return err }
/* (2) If not found -> try again */
if string(currentHash) != string(lastHash) {
continue
}
/* (3) Store new depth */
s.depth = i - s.ctx.MinDepth() + 1
/* (4) Update migration code */
s.mcode = 2
return nil
}
return errors.New("Cannot find an available rescue depth")
}