diff --git a/src/git.xdrm.io/schastsp/client/keyset/keyset.go b/src/git.xdrm.io/schastsp/client/keyset/keyset.go new file mode 100644 index 0000000..60f09af --- /dev/null +++ b/src/git.xdrm.io/schastsp/client/keyset/keyset.go @@ -0,0 +1,212 @@ +package keyset + +import ( + "io" + "encoding/binary" + "errors" + "git.xdrm.io/schastsp/lib/scha" + "math/rand" + "time" +) + +const a = 12 + +/* Attributes */ +type Set struct { + min uint16 // min depth + depth uint16 // cur depth + max uint16 // max depth + + sec []byte // secret + consumption uint // consumption level + // 0: none + // 1: need to migrate + // 2: waiting migration + // 3: validated migration +} + +/* (1) Creates a new KeySet +* +* @min Minimum depth value +* @max Maximum depth value +* +* @return outName outDesc +* +---------------------------------------------------------*/ +func Create(min uint16, max uint16) (*Set, error) { + + /* (1) Fail if min >= max */ + if min >= max { + return nil, errors.New("Max depth is not greater than Min depth") + } + + /* (2) Instanciate */ + var instance = new(Set) + + /* (3) Set attributes */ + instance.min = min + instance.max = max + + /* (4) Generate values if secret */ + if instance.sec == nil { + instance.generate() + } + + return instance, nil +} + +/* (2) Generates a pseudo-random KeySet +* +---------------------------------------------------------*/ +func (s *Set) generate() { + + /* (1) Seed randomness */ + rand.Seed(time.Now().UTC().UnixNano()) + + /* (1) Generate new secret + ---------------------------------------------------------*/ + /* (1) Reset current secret */ + s.sec = make([]byte, scha.HSIZE) + + /* (2) Generate each char. until same length as hash digest */ + for i := uint16(0); i < scha.HSIZE; i++ { + s.sec[i] = byte(rand.Int() % 256) + } + + /* (2) Manage other attributes + ---------------------------------------------------------*/ + /* (1) Random depth pick init */ + var randMin, randMax uint16 = s.min + (s.max-s.min)/2, s.max + + /* (2) Select "random" depth */ + s.depth = randMin + uint16(rand.Intn(int(randMax-randMin))) + + /* (3) Reset comsumption level */ + s.consumption = 0 + +} + +/* (3) Get current hash +* +* @return digest<[]byte]> Current hash representing the set +* +---------------------------------------------------------*/ +func (s Set) Hash() ([]byte, error) { + + /* (1) Get digest */ + digest, err := scha.Hash(s.sec, uint(s.depth), nil, nil) + + /* (2) Dispatch error */ + if err != nil { + return nil, err + } + + /* (3) Else -> return digest */ + return digest, nil + +} + +/* (4) Decrement depth +* +* @return remaining Remaining hashes before migration +* +---------------------------------------------------------*/ +func (s *Set) Decrement() uint16 { + + /* (1) Decrement the depth */ + s.depth-- + + /* (2) If near minDepth (10 far): set consumption to 1 */ + if s.depth <= s.min+10 { + s.consumption = 1 + } + + /* (3) Return remaining attempts */ + return s.depth - s.max + +} + +/* (5) Serialisation +* +* @return serial String representation +* +* +* === FORMAT === +* +* | hsize | secret | depth | +* +-----------+------------+---------+ +* | 16 bits | hsize bits | 16 bits | +* +* == ENDIANNESS == +* +* network endianness -> big-endian +* +---------------------------------------------------------*/ +func (s *Set) Write(writer io.Writer) error { + + var err error; + + /* (1) Copy hash size */ + err = binary.Write(writer, binary.BigEndian, scha.HSIZE) + 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 } + + return nil + +} + +/* (6) Builds a KeySet from its serial representation +* +* @serial String representation +* +* @return instance<*Set> The corresponding set +* @return err Optional error +* +* === FORMAT === +* +* | hsize | secret | depth | +* +-----------+------------+---------+ +* | 16 bits | hsize bits | 16 bits | +* +---------------------------------------------------------*/ +func (s *Set) Read(reader io.Reader) error { + + var ( + err error + secretLength uint16 + ) + + /* (1) Read the secret size */ + err = binary.Read(reader, binary.BigEndian, &secretLength) + if err != nil { + return err + } + + /* (2) Fail if secretLength different than digest size */ + if secretLength != scha.HSIZE { + return errors.New("Invalid secret size (must be the same as digest size)") + } + + /* (3) Try to copy the secret */ + 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 } + + return nil + +} diff --git a/src/git.xdrm.io/schastsp/client/keyset/keyset_test.go b/src/git.xdrm.io/schastsp/client/keyset/keyset_test.go new file mode 100644 index 0000000..cd26580 --- /dev/null +++ b/src/git.xdrm.io/schastsp/client/keyset/keyset_test.go @@ -0,0 +1,124 @@ +package keyset + +import ( + "git.xdrm.io/schastsp/lib/scha" + "testing" +) + +func TestCreateNotMaxGreaterThanMin(t *testing.T) { + + var _, err = Create(2, 1) + + if err == nil { + t.Errorf("Expected an error") + } + +} + +func TestGenerationDepthBoundaries(t *testing.T) { + + var min, max uint16 = 0x0f0, 0xfff + var rangeMin = min + (max-min)/2 + var rangeMax = max + + var created, err = Create(min, max) + + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + if created.depth < min || created.depth > max { + t.Errorf("Expected 'depth' to be in the range [%d, %d], got '%d'", rangeMin, rangeMax, created.depth) + } + +} + +func TestSchaDecrementingProperty(t *testing.T) { + + var h1, h2, hcheck []byte + var err error + var created *Set + + created, err = Create(0x0f0, 0xfff) + + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (1) Get current hash */ + h1, err = created.Hash() + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (2) Decrement */ + created.Decrement() + + /* (3) Get new hash */ + h2, err = created.Hash() + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (4) Try to guess h1 from h2 */ + hcheck, err = scha.Hash(h2, 1, nil, nil) + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (5) Fail if property is not fulfilled */ + for k, v := range h1 { + + if v != hcheck[k] { + t.Errorf("Expected h(h[x-1]) to equel h[x]; expected '%x' ; got '%x'", h1, hcheck) + return + } + + } + +} + +func TestDecrementMinimum(t *testing.T) { + + var h1, h2, hcheck []byte + var err error + var created *Set + + created, err = Create(0x0f0, 0xfff) + + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (1) Get current hash */ + h1, err = created.Hash() + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (2) Decrement */ + created.Decrement() + + /* (3) Get new hash */ + h2, err = created.Hash() + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (4) Try to guess h1 from h2 */ + hcheck, err = scha.Hash(h2, 1, nil, nil) + if err != nil { + t.Errorf("Do not expected an error, got: %s", err) + } + + /* (5) Fail if property is not fulfilled */ + for k, v := range h1 { + + if v != hcheck[k] { + t.Errorf("Expected h(h[x-1]) to equel h[x]; expected '%x' ; got '%x'", h1, hcheck) + return + } + + } + +}