Compare commits

...

20 Commits

Author SHA1 Message Date
Adrien Marquès 8c65fa33a7 server challenge typo 2019-08-02 08:28:45 +00:00
Adrien Marquès 5ad404c493 fix undefined expression for server challenge 2019-08-02 08:28:20 +00:00
Adrien Marquès 00e34d7346 fix undefined expression 2019-08-02 08:26:21 +00:00
Adrien Marquès eced6eb738 forgot latex boundaries 2019-08-02 08:22:40 +00:00
Adrien Marquès 2dc332a145 protocol typos 2019-08-02 08:21:24 +00:00
Adrien Marquès 85160c557a minfix 2018-10-08 10:34:07 +02:00
Adrien Marquès 4d46723011 update protocol 2018-09-11 10:17:37 +02:00
Adrien Marquès df7e20c855 add protocol steps for the server 2018-09-10 14:38:28 +02:00
Adrien Marquès a33a3dba72 format protocol steps 2018-09-09 19:50:53 +02:00
Adrien Marquès f58c8ff3a9 gofmt, govet, .. 2018-09-06 16:41:03 +02:00
Adrien Marquès f567b75f4e simplified protocol 2018-09-06 16:37:31 +02:00
Adrien Marquès 8519769b37 ... 2018-09-05 17:50:50 +02:00
Adrien Marquès b0d8166897 add sequence diagram 2018-09-05 17:38:38 +02:00
Adrien Marquès a386ccd5a1 updated PROTOCOL.md (WIP) > began actual protocol 2018-09-05 17:29:04 +02:00
Adrien Marquès 09d7a6aa12 updated PROTOCOL.md (WIP) > add 'stsp' 2018-09-05 16:56:36 +02:00
Adrien Marquès 125c8297a2 updated PROTOCOL.md (WIP) 2018-09-04 18:46:59 +02:00
Adrien Marquès b9e8dbcf2a add PROTOCOL.md [WIP] 2018-07-29 11:57:27 +02:00
Adrien Marquès 78d34dddb4 gofmt | update readme (client cli) + minmod 2018-07-25 14:19:44 +02:00
Adrien Marquès 65f08ff10e moved from 'git.xdrm.io/schastsp' to 'git.xdrm.io/logauth/schastsp' + add readme 2018-07-24 15:31:38 +02:00
Adrien Marquès 7c92d5c8ac fix lib 'scha' that is now in 'internal/' not 'lib/' 2018-07-24 14:39:19 +02:00
20 changed files with 892 additions and 431 deletions

194
PROTOCOL.md Normal file
View File

@ -0,0 +1,194 @@
# Stateless Time-Scrambled Cyclic Hash Protocol
**S**a**TS CH**i**P**
<u>Vocabulary</u>
- A `hash` or `digest` is a fixed-length string that is the output of a hash function.
- A `token` is a fixed-length string sent by a client to a server to authenticate.
- A `private key` is a fairly unique and hardly guessable data that is generated by a machine and never shared.
- A `request` is sent from a client to a server, the server sends back a `response`.
## Abstract
After designing some APIs, I found out that you must have at some point a *token* system to authenticate to your server. For the simplest design, the server attributes a unique token for each client, this token will then be sent over each request to authenticate.
> The server may also regenerate the token at some time to avoid token theft.
An easy MITM attack could be to repeat a previously sent request while the token is still valid or even catch the client *token* and build a malicious request with its authentication.
> In this document we will assume that what travels through the network is public, so any MITM can store it. Obviously I highly recommend using TLS for communicating, but we look here for a consistent token system, it only can be better through TLS.
A good alternative could be to use a *one-time token* where each request features a unique token. This design implies that the token is sent from the server to the client before being used ; so any MITM can catch it. To overcome this flaw we could have a system where *one key* is used to generate all the tokens in a consistent manner. If so, the tokens do not have to be sent and can be guessed from the key. But this also implies that the key is shared at some point between the client and the server.
A better solution would be to generate a private key on each client and use it to generate a token for each request. Also, to avoid to share the key and allow the server to easily check tokens, we would like a system where each token can be checked from the previous token without having to know the private key (because it is private...). It would avoid attackers to repeat requests (because each token is unique) or guess the private key because it never travels over the network. In addition short-lived one-time passwords have a mechanism that we could use to build a time-dependent system.
**What we need**
1. Generate a *unique token* for each request from a fixed *private key*
2. The token *never* to be the same
3. The token to be easily checked directly from the previous one
4. Each token to be only valid a few seconds after sending it
5. Each token to give no clue that could help guessing the next token.
6. A system where no key is shared ; only clients do the key processing and the server only to process a simple checking algorithm
**Technology requirements**
1. Mixing 2 hashes in a way that without one of them, the other is *cryptographically impossible* to guess (*i.e. [one-time pad](https://en.wikipedia.org/wiki/One-time_pad)*).
2. Having a time-dependent unique feature, that could be found only a few seconds after sending it (as for *[TOTP](https://tools.ietf.org/html/rfc6238)*).
3. A cryptographic hash function that, from an input of any length, outputs a fixed-length digest in a way that is *impossible* to guess the input back from it.
**Protocols to define**
This document will define and bundle 2 distinct protocols to make up a token system that implements the previous statements.
1. a <u>Stateless Time Scrambling Protocol</u> to take care of the request's invalidation over time.
2. a <u>Stateless Cyclic Hash Algorithm</u> to use a private key as a one-time token generator in a way that no clue is given over published tokens (*i.e. one-way function*).
3. A key <u>renewal mechanism</u> in a way that no clue is given over neither the old nor the new key.
4. A <u>rescue protocol</u> to resynchronise the client with a new key in a way that no clue is given over the network and the client has to process a "proof of work".
## General knowledge & Notations
##### Notations
| Symbols | Description |
|:-----:|:----------|
| $\parallel a\parallel $ | The absolute value of $a$ ; *e.g.* $\parallel -12 \parallel = 12$ |
| $\mid a \mid$ | The integer value of $a$ ; *e.g.* $\mid 12.34 \mid = 12$ |
| $a \oplus b$ | The bitwise operation XOR between binary words $a$ and $b$ |
| $a \And b$ | The bitwise operation AND between binary words $a$ and $b$ |
| $h(m)$ | The digest of the message $m$ by a (consistent) secure cryptographic hash function $h()$ ; *e.g. sha512* |
| $h^n(m)$ | The digest of the $n$-recursive hash function $h()$ with the input data $m$ ; <br>$h^2(m) \equiv h(h(m))$, $h^1(m) \equiv h(m)$, and $h^0(m) \equiv m$. |
| $a \ \mathbb{Z}_{(b)}$ | The result of $a$ modulo $b$ ; *i.e.* the remainder of the Euclidean division of $a$ by $b$ |
| $t_{now}$ | The current *Unix Timestamp* in seconds |
## Variables
The whole system share a common **context**, each client holds a **private keyset**, and the server stores the **last valid token** of each client.
#### 1. Common context
These variables are both on the server and clients. They are specific to the server so each client must match its values. These variables shape the system's **context** $(W, min, belt, max)$.
| Variable | Name | Description |
|:--------:|:----:|:------------|
| $W$ | time window | A number of seconds that is typically the maximum transmission time from end to end. It will be used by the *time-scrambling protocol*. The lower the number, the less time an attacker has to try to brute-force the tokens. |
| $min$ | resynchonization range | A number that is used to resynchronize the client if there is a communication issue (*e.g. lost request, lost response, attack*). The higher the value, the higher the challenge for the client to recover the authentication, thus the harder for an attacker to guess it. |
| $belt$ | security range | A number that is used to resynchronize the client if there is a communication shift (*e.g. lost request, lost response, attack*). It corresponds of the number of desynchronizations the client can handle before never being able to gain the authentication again. |
| $max$ | maximum nonce | A number that is used to cap the value of client's nonces. A too high value will result on keys that will never be replaced, thus making them open to long-processing attacks (*e.g. brute-force*). |
#### 2. Client keyset
Every client holds a **keyset** $(K, n, s)$. It represents its private key and is used to generate the tokens. The secure hash function is extended to a **one-way function** to generate all the tokens from the keyset. Note that the client may hold a secondary keyset between the generation of a new keyset and the server's validation of it.
| Variable | Name | Description |
|:--------:|:----:|:------------|
| $K$ | private key | A secret binary data that must be large and *random* enough not to be brute forced. |
| $n$ | key nonce | A number that is decremented at each token generation. Before $n$ reaches 0, a new keyset must be generated. |
| $s$ | key state | A number that reflects the state of the keyset. It is used to know what to do on the **next request** : <br>- $0$ : normal request<br>- $1$ : will switch to the new key<br>- $2$ : rescue proof of work sent, waiting for the server's acknowledgement |
#### 3. Server variables
| Variable | Name | Description |
|:--------:|:----:|:------------|
| $T$ | last valid token | The server stores the last valid token from the client to check the next one. |
## Protocol
## 1. Client request generation
In each request, the client will send a pair of time-scrambled hashes $(x_1, x_2)$ :
- $x1$ will hold the current one-time token
- $x2$ will hold the necessary data to check the next token
The client implements 3 protocols according to the **keyset state** :
- 0 : `NORMAL` - default authentication protocol.
- 1 : `SWITCH` - default protocol variation to switch to a new keyset when the current one is consumed (*i.e. when $n$ $\leq$ $min+belt$*).
- 2 : `RESCUE` - process the proof of work after receiving the server's challenge when there is a desynchronisation and generate a new keyset.
When the client switches to a new key, it has to store the new keyset along the current one, in order not to lose its authentication if the network fails.
- $(K,n,s)$ - the current keyset
- $(K',n',s')$ - the new keyset
#### A. `NORMAL` protocol (state 0)
| Step | Calculation |
|:--------:|:------|
| `1` | Decrement $n$ |
| `2` | $t\_c = \mid \frac{t\_{now}}{W}\mid, m\_c = t\_c \ \mathbb{Z}\_{(2)}$ |
| `3` | $x\_1 = h^{n}(K) \oplus h(t\_c)$ |
| `4` | $x\_2 = x\_1 \oplus m\_c$ |
| `5` | if $i <= min+belt \Rightarrow s = 1$ |
Send $x_1$ and $x_2$.
#### B. `SWITCH` protocol (state 1)
| Step | Calculation |
|:--------:|:------|
| `1` | $t\_c = \mid \frac{t\_{now}}{W}\mid, m\_c = t\_c \ \mathbb{Z}\_{(2)}$ |
| `2` | $x\_1 = h^{n}(K) \oplus h(t\_c)$ |
| `3` | Generate $(K',n',s')$ until <br><ul><li>$[(h^n(K) \oplus h^{n'}(K')) \And 0x1] \ \mathbb{Z}\_{(2)} = m\_c$</li><li>$[(h^N(K) \oplus h^{n'}(K')) \And 0xf0] \ \mathbb{Z}\_{(3)} = s$</li></ul> |
| `4` | $x\_2 = h^{n'}(K') \oplus h(t\_c)$ |
Send $x_1$ and $x_2$.
If the response succeeds, replace the key with the new one : $K = K', n = n', s = 0$
#### C. `RESCUE` protocol (state 2)
This protocol is processed when the server sends the 2 hashes $(y_1, y_2)$ to the client (instead of the standard response). It means that the server has received a wrong hash, so it sends the rescue challenge to the client.
| Step | Calculation |
|:--------:|:------|
| `1` | $t\_c = \mid \frac{t\_{now}}{W} \mid $, $m\_c = t\_c \ \mathbb{Z}\_{(2)}$
| `2` | $t'\_s = t\_c - \parallel m\_c - (y_1 \oplus y_2)\parallel$
| `3` | $T = x\_1 \oplus h(t'\_s)$
| `4` | Find $N \in [min ; n-min[,\ h^{N}(K) = T$.
| `5` | $x\_1 = h^N(K) \oplus h(t\_c)$
| `6` | Generate $(K',n',s')$ until :<br><ul><li>$[(h^N(K) \oplus h^{n'}(K')) \And 1] \ \mathbb{Z}\_{(2)} = m\_c$</li><li>$[(h^N(K) \oplus h^{n'}(K')) \And 11110000] \ \mathbb{Z}\_{(3)} = s$</li></ul> |
| `7` | $x\_2 = h^{n'}(K') \oplus h(t\_c)$ |
Send $x_1$ and $x_2$.
If the response succeeds, replace the key with the new one : $K = K', n = n', s = 0$
## 2. Server management
When receiving a pair of hashes $(x_1, x_2)$, the server has to :
1. Unscramble the request using the time-scrambling algorithm
2. Check the token $t_1$ held into $x_1$ :
- `VALID` $\rightarrow$ authenticated : store the token $t_2$ held into $x_2$, forward request to the logic unit
- `INVALID` $\rightarrow$ not authenticated : send the challenge hashes $(y_1, y_2)$.
#### 1. Unscramble the request
| Step | Calculation |
|:--------:|:------|
| `1` | $t\_s = \mid \frac{t\_{now}}{W}\mid, m\_s = t\_s \ \mathbb{Z}\_{(2)}$ |
| `2` | $m\_c = x\_1 \oplus x\_2$ |
| `3` | $t'\_c = t\_s - \parallel m_c - m_s \parallel$ |
| `4` | $t\_1 = x\_1 \oplus h(t'\_c)$ |
| `5` | $t\_2 = x\_2 \oplus h(t'\_c)$ |
If $h(t_1) = T$ :
- $t_1$ is valid
- store the next token $T=t_2$
Else, go to the [invalid request management](#2-invalid-request-management).
#### 2. Invalid request management
When receiving an invalid token, the server may send back to the client a challenge to get the authentication back. This challenge is formed by 2 hashes $(y_1, y_2)$.
| Step | Calculation |
|:--------:|:------|
| `1` | $t\_s = \mid \frac{t\_{now}}{W}\mid, m\_s = t\_s \ \mathbb{Z}\_{(2)}$ |
| `2` | $y\_1 = T \oplus h(t\_s)$ |
| `3` | $y\_2 = y\_1 \oplus m\_s$ |
Send $y_1$ and $y_2$.

113
README.md Normal file
View File

@ -0,0 +1,113 @@
# SCHA/STSP
**Copyright (C) 2017 xdrm-brackets**
[![Go version](https://img.shields.io/badge/go_version-1.10.3-blue.svg)](https://golang.org/doc/go1.10) [![Go Report Card](https://goreportcard.com/badge/git.xdrm.io/logauth/schastsp)](https://goreportcard.com/report/git.xdrm.io/logauth/schastsp) [![Go doc](https://godoc.org/git.xdrm.io/logauth/schastsp?status.svg)](https://godoc.org/git.xdrm.io/logauth/schastsp)
## Overview
----
This software defines and uses its own protocol which bundles 2 technologies :
- Stateless Cyclic Hash Algorithm
- Stateless Time Scrambling Protocol
It is meant to be used over request/response stateless networking and has been designed with HTTP in mind. The protocol only covers the generation and management of a pair of *tokens* which are hexadecimal strings. These are sent inside each request and a pair also has to be sent back to the sender. These tokens are mainly sent in the HTTP `Authorization` header for HTTP requests and responses.
**Features**
Beyond security issues, this protocol has some additional features :
- **Trust Chain** - Each exchange between the server and the client is bound to the previous one. Each request is unique and can only give information about previous ones, not future ones. This principle ensures the server that no-one can be faking the client (unless someone has access to its key).
> If an attacker can guess (*e.g. bruteforce*) a successful request and gain access to the server - if he hasn't the client's key - it is fairly impossible that he also guesses the next request.
> A resynchronization protocol is featured in this package in order for a client to regain ownership on the trust chain if lost (by network issue or by an attack).
- **Time-awareness** - A request is only valid a short amount of time after its generation. This amount is usually the maximum transmission time.
> Any MITM that catches the client's request to change its content only has a minimum time to forward it. As a result - if the amount is well chosen - any request modification is blocked by the time it takes.
- [TODO]
**Security**
The aim of this package is to provide a **secure** authentication system between a server and its clients. Secure is defined as follows :
- No *man-in-the-middle* (MITM) can gather enough information to fake any client. Every data sent over the network is the result of one or more <u>one-way algorithms</u>.
- Neither a protocol understanding nor the source code can help an attacker fake a client or find useful information among requests.
- The server has no secret key other than the <u>synchronization key</u> (used once to bind the client). The server has no clue what each client's key is, in fact it knows as much as a *MITM*.
## Requirements
----
You need a recent machine with `go` installed.
> This package has not been tested under the version **1.10**.
## Installation
----
Download the package with `go get` :
```bash
go get git.xdrm.io/logauth/schastsp
```
Build the executables :
```bash
go install git.xdrm.io/logauth/schastsp/cmd/client;
go install git.xdrm.io/logauth/schastsp/cmd/server;
```
The executables are now available in your `$GOBIN` (*i.e. `$GOPATH/bin`*) folder.
## Client
----
### 1. Usage
```bash
client <command> [options]
```
Available commands are :
- `-req` generates a new request into standard output
- `-res` reads and manages a response from standard input
- `-sync` generates a synchronization key into standard output (*i.e. resets the system*)
Available options are :
- `-file` sets the folder where the keys are (or will be) stored.
- `-win` sets the window value (in milliseconds). Typically the maximum transmission time.
- `-min` sets the minimum cyclic-hash depth.
- `-max` sets the maximum cyclic-hash depth.
- `-thr` sets the cyclic-hash threshold. This value represents at which offset of the key's TTL the key will be generated. It also corresponds to the number of time the client can desynchronize before becoming impossible to synchronize again.

View File

@ -3,9 +3,9 @@ package client
import ( import (
"errors" "errors"
"fmt" "fmt"
"git.xdrm.io/schastsp/internal/keyset" "git.xdrm.io/logauth/schastsp/context"
"git.xdrm.io/schastsp/context" "git.xdrm.io/logauth/schastsp/internal/keyset"
"git.xdrm.io/schastsp/internal/scha" "git.xdrm.io/logauth/schastsp/internal/scha"
"io" "io"
) )
@ -14,11 +14,11 @@ const DEBUG = false
/* (1) Structure /* (1) Structure
---------------------------------------------------------*/ ---------------------------------------------------------*/
type T struct { type T struct {
ctx *context.T // shared context ctx *context.T // shared context
key *keyset.T // current key key *keyset.T // current key
sync *keyset.T // next bufferised key sync *keyset.T // next bufferised key
fkey *config // key file management fkey *config // key file management
fsync *config // sync file management fsync *config // sync file management
} }
/* (2) Constructor /* (2) Constructor
@ -34,16 +34,22 @@ func New(ctx *context.T, saveDir string) (*T, error) {
inst := new(T) inst := new(T)
/* (1) Store context */ /* (1) Store context */
if ctx == nil { return nil, errors.New("Context must not be nil"); } if ctx == nil {
return nil, errors.New("Context must not be nil")
}
inst.ctx = ctx inst.ctx = ctx
/* (2) Get file management for KEY */ /* (2) Get file management for KEY */
inst.fkey, err = Config(saveDir, "key") inst.fkey, err = Config(saveDir, "key")
if err != nil { return nil, err } if err != nil {
return nil, err
}
/* (3) Get file management for SYNC */ /* (3) Get file management for SYNC */
inst.fsync, err = Config(saveDir, "sync") inst.fsync, err = Config(saveDir, "sync")
if err != nil { return nil, err } if err != nil {
return nil, err
}
/* (4) Restore from config */ /* (4) Restore from config */
inst.updateConfig() inst.updateConfig()
@ -66,16 +72,26 @@ func (c *T) Send(w io.Writer) error {
x2 := make([]byte, scha.HSIZE) x2 := make([]byte, scha.HSIZE)
err := c.generateRequest(x1, x2) err := c.generateRequest(x1, x2)
if err != nil { return err } if err != nil {
return err
}
/* (2) Write request into writer */ /* (2) Write request into writer */
written, err := w.Write(x1) written, err := w.Write(x1)
if err != nil { return err } if err != nil {
if written != len(x1) { return errors.New("Cannot write x1") } return err
}
if written != len(x1) {
return errors.New("Cannot write x1")
}
written, err = w.Write(x2) written, err = w.Write(x2)
if err != nil { return err } if err != nil {
if written != len(x1) { return errors.New("Cannot write x2") } return err
}
if written != len(x1) {
return errors.New("Cannot write x2")
}
return nil return nil
} }
@ -93,10 +109,16 @@ func (c *T) Receive(r io.Reader) error {
---------------------------------------------------------*/ ---------------------------------------------------------*/
errCode := make([]byte, 1) errCode := make([]byte, 1)
read, err := r.Read(errCode) read, err := r.Read(errCode)
if err != nil { return err } if err != nil {
if uint16(read) != 1 { return errors.New("Cannot read error code") } return err
}
if uint16(read) != 1 {
return errors.New("Cannot read error code")
}
if DEBUG { fmt.Printf("ERROR CODE : %d\n", errCode[0]) } if DEBUG {
fmt.Printf("ERROR CODE : %d\n", errCode[0])
}
/* (2) Manage success /* (2) Manage success
---------------------------------------------------------*/ ---------------------------------------------------------*/
@ -105,7 +127,9 @@ func (c *T) Receive(r io.Reader) error {
/* (1) If pending migration -> migrate */ /* (1) If pending migration -> migrate */
if c.key.MigrationCode() == 2 { if c.key.MigrationCode() == 2 {
c.migrateKey() c.migrateKey()
if DEBUG { fmt.Printf("*** VALIDATED MIGRATION\n") } if DEBUG {
fmt.Printf("*** VALIDATED MIGRATION\n")
}
} }
/* (2) No error anyway */ /* (2) No error anyway */
@ -118,28 +142,37 @@ func (c *T) Receive(r io.Reader) error {
/* (1) Read y1 */ /* (1) Read y1 */
y1 := make([]byte, scha.HSIZE) y1 := make([]byte, scha.HSIZE)
read, err = r.Read(y1) read, err = r.Read(y1)
if err != nil { return err } if err != nil {
if uint16(read) != scha.HSIZE { return errors.New("Cannot read y1") } return err
}
if uint16(read) != scha.HSIZE {
return errors.New("Cannot read y1")
}
/* (2) Read y2 */ /* (2) Read y2 */
y2 := make([]byte, scha.HSIZE) y2 := make([]byte, scha.HSIZE)
read, err = r.Read(y2) read, err = r.Read(y2)
if err != nil { return err } if err != nil {
if uint16(read) != scha.HSIZE { return errors.New("Cannot read enough y2") } return err
}
if uint16(read) != scha.HSIZE {
return errors.New("Cannot read enough y2")
}
/* (3) Manage rescue mode */ /* (3) Manage rescue mode */
err = c.rescue(y1, y2) err = c.rescue(y1, y2)
if err != nil { return err } if err != nil {
return err
}
if DEBUG { fmt.Printf("*** MIGRATION PREPARED\n") } if DEBUG {
fmt.Printf("*** MIGRATION PREPARED\n")
}
return nil return nil
} }
/* (5) Returns a synchronisation key (first server connection) /* (5) Returns a synchronisation key (first server connection)
* *
* @return key<[]byte> Synchronisation key * @return key<[]byte> Synchronisation key
@ -148,17 +181,19 @@ func (c *T) Receive(r io.Reader) error {
---------------------------------------------------------*/ ---------------------------------------------------------*/
func (c *T) SynchronisationKey() ([]byte, error) { func (c *T) SynchronisationKey() ([]byte, error) {
/* (1) Reset keys so no value can be guessed*/ /* (1) Reset keys so no value can be guessed */
c.migrateKey(); // 1: copies 'sync' into 'key' c.migrateKey() // 1: copies 'sync' into 'key'
c.migrateKey(); // 2: copies random new 'sync' into 'key' (old 'sync) c.migrateKey() // 2: copies random new 'sync' into 'key' (old 'sync)
/* (2) Get current hash */ /* (2) Get current hash */
hash, err := c.key.CurrentHash() hash, err := c.key.CurrentHash()
if err != nil { return nil, err } if err != nil {
return nil, err
}
/* (3) Decrement key so 'hash' is valid */ /* (3) Decrement key so 'hash' is valid */
c.key.Decrement() c.key.Decrement()
/* (4) Return key */ /* (4) Return key */
return hash, nil; return hash, nil
} }

View File

@ -3,10 +3,10 @@ package client
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"git.xdrm.io/schastsp/internal/keyset" "git.xdrm.io/logauth/schastsp/internal/keyset"
"git.xdrm.io/schastsp/internal/scha" "git.xdrm.io/logauth/schastsp/internal/scha"
"git.xdrm.io/schastsp/internal/timeid" "git.xdrm.io/logauth/schastsp/internal/timeid"
"git.xdrm.io/schastsp/internal/xor" "git.xdrm.io/logauth/schastsp/internal/xor"
) )
/* (1) Updates 'key' and 'sync' with files /* (1) Updates 'key' and 'sync' with files
@ -27,7 +27,9 @@ func (c *T) updateConfig() {
err = c.fkey.Fetch(c.key) err = c.fkey.Fetch(c.key)
/* (3) On error -> set key to NIL */ /* (3) On error -> set key to NIL */
if err != nil { c.key = nil } if err != nil {
c.key = nil
}
/* (4) Create default sync */ /* (4) Create default sync */
c.sync, err = keyset.Create(c.ctx) c.sync, err = keyset.Create(c.ctx)
@ -36,7 +38,9 @@ func (c *T) updateConfig() {
err = c.fsync.Fetch(c.sync) err = c.fsync.Fetch(c.sync)
/* (6) On error -> set sync to NIL */ /* (6) On error -> set sync to NIL */
if err != nil { c.sync = nil } if err != nil {
c.sync = nil
}
/* (7) Exit if all keysets have been fetched */ /* (7) Exit if all keysets have been fetched */
if c.key != nil && c.sync != nil { if c.key != nil && c.sync != nil {
@ -45,7 +49,6 @@ func (c *T) updateConfig() {
} }
/* (2) If cannot fetch -> create new keysets /* (2) If cannot fetch -> create new keysets
---------------------------------------------------------*/ ---------------------------------------------------------*/
if c.key == nil { if c.key == nil {
@ -56,16 +59,19 @@ func (c *T) updateConfig() {
c.sync, _ = keyset.Create(c.ctx) c.sync, _ = keyset.Create(c.ctx)
} }
/* (3) Store current value /* (3) Store current value
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Store key */ /* (1) Store key */
err = c.fkey.Store(c.key) err = c.fkey.Store(c.key)
if err != nil { panic("Cannot store key") } if err != nil {
panic("Cannot store key")
}
/* (2) Store sync */ /* (2) Store sync */
err = c.fsync.Store(c.sync) err = c.fsync.Store(c.sync)
if err != nil { panic("Cannot store sync") } if err != nil {
panic("Cannot store sync")
}
} }
@ -81,7 +87,9 @@ func (c *T) migrateKey() {
/* (2) Regenerate sync */ /* (2) Regenerate sync */
c.sync, err = keyset.Create(c.ctx) c.sync, err = keyset.Create(c.ctx)
if err != nil { panic(err) } if err != nil {
panic(err)
}
/* (3) Store keysets to files */ /* (3) Store keysets to files */
c.updateConfig() c.updateConfig()
@ -95,7 +103,9 @@ func (c *T) generateKeyWithConstraints() {
/* Get current hash */ /* Get current hash */
keyHash, err := c.key.CurrentHash() keyHash, err := c.key.CurrentHash()
if err != nil { panic(err) } if err != nil {
panic(err)
}
/* Search key one is respects contraints */ /* Search key one is respects contraints */
for true { for true {
@ -111,19 +121,21 @@ func (c *T) generateKeyWithConstraints() {
/* (3) Hash time id */ /* (3) Hash time id */
hashedTimeID, err := scha.Hash(timeIDBytes, 1) hashedTimeID, err := scha.Hash(timeIDBytes, 1)
if err != nil { continue } if err != nil {
continue
}
/* (2) Generate a new sync /* (2) Generate a new sync
---------------------------------------------------------*/ ---------------------------------------------------------*/
newKey, _ := keyset.Create(c.ctx) newKey, _ := keyset.Create(c.ctx)
/* (3) Check constraints /* (3) Check constraints
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Get next hash */ /* (1) Get next hash */
syncHash, err := newKey.CurrentHash() syncHash, err := newKey.CurrentHash()
if err != nil { continue } if err != nil {
continue
}
/* (2) Get x1 */ /* (2) Get x1 */
x1 := make([]byte, scha.HSIZE) x1 := make([]byte, scha.HSIZE)
@ -145,18 +157,26 @@ func (c *T) generateKeyWithConstraints() {
/* (2) Get time mod difference (first byte) */ /* (2) Get time mod difference (first byte) */
timeConstraintValue := x[0]%2 == byte(timeMod) timeConstraintValue := x[0]%2 == byte(timeMod)
if DEBUG { fmt.Printf(" %.2x ^ %.2x = %.2x[%d] %% 2 = %d == %d ? %t\n", x1[0], x2[0], x[0], x[0], x[0]%2, timeMod, timeConstraintValue) } if DEBUG {
fmt.Printf(" %.2x ^ %.2x = %.2x[%d] %% 2 = %d == %d ? %t\n", x1[0], x2[0], x[0], x[0], x[0]%2, timeMod, timeConstraintValue)
}
/* (4) Retry if invalid time constraint */ /* (4) Retry if invalid time constraint */
if !timeConstraintValue { continue } if !timeConstraintValue {
continue
}
/* (5) Get migration mod difference (second byte) */ /* (5) Get migration mod difference (second byte) */
migrationConstraintValue := x[1]%3 == byte(c.key.MigrationCode()) migrationConstraintValue := x[1]%3 == byte(c.key.MigrationCode())
if DEBUG { fmt.Printf(" %.2x ^ %.2x = %.2x[%d] %% 3 = %d == %d ? %t\n", x1[1], x2[1], x[1], x[1], x[1]%3, c.key.MigrationCode(), migrationConstraintValue) } if DEBUG {
fmt.Printf(" %.2x ^ %.2x = %.2x[%d] %% 3 = %d == %d ? %t\n", x1[1], x2[1], x[1], x[1], x[1]%3, c.key.MigrationCode(), migrationConstraintValue)
}
/* (6) Retry if invalid time constraint */ /* (6) Retry if invalid time constraint */
if !migrationConstraintValue { continue } if !migrationConstraintValue {
continue
}
/* (7) Store new sync */ /* (7) Store new sync */
c.sync = newKey c.sync = newKey
@ -215,7 +235,9 @@ func (c *T) generateRequest(x1 []byte, x2 []byte) error {
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Store current hash */ /* (1) Store current hash */
h0, err := c.key.CurrentHash() h0, err := c.key.CurrentHash()
if err != nil { return err } if err != nil {
return err
}
/* (2) Copy into next hash (same value) */ /* (2) Copy into next hash (same value) */
h1 := make([]byte, scha.HSIZE) h1 := make([]byte, scha.HSIZE)
@ -226,7 +248,9 @@ func (c *T) generateRequest(x1 []byte, x2 []byte) error {
// 2. Else -> use 'sync' // 2. Else -> use 'sync'
if c.key.MigrationCode() > 0 { if c.key.MigrationCode() > 0 {
h1, err = c.sync.CurrentHash() h1, err = c.sync.CurrentHash()
if err != nil { return err } if err != nil {
return err
}
} }
/* (5) Manage time id /* (5) Manage time id
@ -302,14 +326,18 @@ func (c *T) rescue(y1 []byte, y2 []byte) error {
/* (3) Hash timeId */ /* (3) Hash timeId */
hashedTimeID, err := scha.Hash(timeIDBytes, 1) hashedTimeID, err := scha.Hash(timeIDBytes, 1)
if err != nil { return err } if err != nil {
return err
}
/* (4) Get the received hash */ /* (4) Get the received hash */
receivedHash := xor.ByteArray(y1, hashedTimeID) receivedHash := xor.ByteArray(y1, hashedTimeID)
/* (5) Try to rescue the key */ /* (5) Try to rescue the key */
err = c.key.Rescue(receivedHash) err = c.key.Rescue(receivedHash)
if err != nil { return err } if err != nil {
return err
}
/* (6) Store updated key */ /* (6) Store updated key */
c.updateConfig() c.updateConfig()

View File

@ -1,14 +1,13 @@
package client; package client
import ( import (
"git.xdrm.io/schastsp/internal/keyset"
"fmt"
"path/filepath"
"errors" "errors"
"fmt"
"git.xdrm.io/logauth/schastsp/internal/keyset"
"os" "os"
"path/filepath"
) )
type config struct { type config struct {
path string // absolute configuration file path path string // absolute configuration file path
} }
@ -21,47 +20,54 @@ type config struct {
---------------------------------------------------------*/ ---------------------------------------------------------*/
func Config(dir string, name string) (*config, error) { func Config(dir string, name string) (*config, error) {
inst := new(config); inst := new(config)
/* (1) Build full path */ /* (1) Build full path */
absoluteDir, err := filepath.Abs(dir); absoluteDir, err := filepath.Abs(dir)
if err != nil { return nil, err; } if err != nil {
return nil, err
basename := filepath.Base(name); }
fullpath := fmt.Sprintf("%s/%s", absoluteDir, basename);
basename := filepath.Base(name)
fullpath := fmt.Sprintf("%s/%s", absoluteDir, basename)
/* (2) Check directory */ /* (2) Check directory */
if info, err := os.Stat(absoluteDir); err != nil { if info, err := os.Stat(absoluteDir); err != nil {
// Unknown error // Unknown error
if !os.IsNotExist(err) { return nil, err } if !os.IsNotExist(err) {
return nil, err
}
// not exists -> try to create dir // not exists -> try to create dir
err2 := os.MkdirAll(absoluteDir, 0755); err2 := os.MkdirAll(absoluteDir, 0755)
if err2 != nil { return nil, err2 } if err2 != nil {
return nil, err2
}
// fail if not a directory // fail if not a directory
} else if !info.Mode().IsDir() { } else if !info.Mode().IsDir() {
return nil, errors.New("Configuration dir is not a directory"); return nil, errors.New("Configuration dir is not a directory")
} }
/* (3) Check file */ /* (3) Check file */
info, err := os.Stat(fullpath); info, err := os.Stat(fullpath)
if err != nil { if err != nil {
// Unknown error // Unknown error
if !os.IsNotExist(err) { return nil, err } if !os.IsNotExist(err) {
return nil, err
}
// File does not exist -> try to create file // File does not exist -> try to create file
_, err2 := os.Create(fullpath); _, err2 := os.Create(fullpath)
if err2 != nil { return nil, err2 } if err2 != nil {
return nil, err2
}
// fail if exists but not regular file
// fail if exists but not regular file
} else if !info.Mode().IsRegular() { } else if !info.Mode().IsRegular() {
return nil, errors.New("Configuration file is not a regular file"); return nil, errors.New("Configuration file is not a regular file")
} }
inst.path = fullpath inst.path = fullpath
@ -70,7 +76,6 @@ func Config(dir string, name string) (*config, error) {
} }
/* (2) Fetches a keyset from a file /* (2) Fetches a keyset from a file
* *
* @ks<*keyset.T> Set to fetch into * @ks<*keyset.T> Set to fetch into
@ -83,20 +88,23 @@ func (c config) Fetch(ks *keyset.T) (err error) {
/* (1) Open file */ /* (1) Open file */
file, err := os.Open(c.path) file, err := os.Open(c.path)
if err != nil { return err } if err != nil {
return err
}
/* (2) Defer close */ /* (2) Defer close */
defer file.Close() defer file.Close()
/* (3) Fetch from file */ /* (3) Fetch from file */
err = ks.Fetch(file); err = ks.Fetch(file)
if err != nil { return err } if err != nil {
return err
}
return nil return nil
} }
/* (3) Stores a keyset into file /* (3) Stores a keyset into file
* *
* @ks<*keyset.T> Set to store * @ks<*keyset.T> Set to store
@ -108,16 +116,20 @@ func (c config) Fetch(ks *keyset.T) (err error) {
func (c config) Store(ks *keyset.T) error { func (c config) Store(ks *keyset.T) error {
/* (1) Open file */ /* (1) Open file */
file, err := os.OpenFile(c.path, os.O_RDWR|os.O_CREATE, 0755); file, err := os.OpenFile(c.path, os.O_RDWR|os.O_CREATE, 0755)
if err != nil { return err } if err != nil {
return err
}
/* (2) Defer close */ /* (2) Defer close */
defer file.Close() defer file.Close()
/* (3) Store into file */ /* (3) Store into file */
err = ks.Store(file) err = ks.Store(file)
if err != nil { return err } if err != nil {
return err
}
return nil return nil
} }

View File

@ -1,38 +1,38 @@
package main package main
import ( import (
"time"
"os"
"flag" "flag"
"fmt" "fmt"
"git.xdrm.io/schastsp/context" "git.xdrm.io/logauth/schastsp/client"
"git.xdrm.io/schastsp/client" "git.xdrm.io/logauth/schastsp/context"
"os"
"time"
) )
func main(){ func main() {
executionStart := time.Now().UnixNano() executionStart := time.Now().UnixNano()
/* (1) Flag management (cli arguments) /* (1) Flag management (cli arguments)
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Secret folder */ /* (1) Secret folder */
clientConfigPath := flag.String("file", "/tmp/schastsp_keys", "Configuration folder, it will contain sensitive data (keys), make sure to control it properly. (default: /tmp/schastsp_keys)") clientConfigPath := flag.String("file", "/tmp/schastsp_keys", "Configuration folder, it will contain sensitive data (keys), make sure to control it properly.")
/* (2) request | response */ /* (2) request | response */
isRequest := flag.Bool("req", false, "Will generate a request into standard output.") isRequest := flag.Bool("req", false, "Will generate a request into standard output.")
isResponse := flag.Bool("res", false, "Will proceed a response management from standard input.") isResponse := flag.Bool("res", false, "Will proceed a response management from standard input.")
/* (3) Context window size */ /* (3) Context window size */
winSize := flag.Uint("win", 2000, "Time window value in ms. (default: 2000)") winSize := flag.Uint("win", 2000, "Time window value in ms.")
/* (4) Context minimum depth value */ /* (4) Context minimum depth value */
minDepth := flag.Uint("min", 0x0f0, "Minimum depth value. (default: 240)") minDepth := flag.Uint("min", 0x0f0, "Minimum depth value.")
/* (5) Context maximum depth value */ /* (5) Context maximum depth value */
maxDepth := flag.Uint("max", 0xfff, "Maximum depth value. (default: 4095)") maxDepth := flag.Uint("max", 0xfff, "Maximum depth value.")
/* (6) Context depth threshold */ /* (6) Context depth threshold */
thrDepth := flag.Uint("thr", 0x00a, "Depth threshold protecting minimum depth to be reached. (default: 10)") thrDepth := flag.Uint("thr", 0x00a, "Depth threshold protecting minimum depth to be reached.")
/* (7) Synchronisation request (special order) */ /* (7) Synchronisation request (special order) */
syncRequest := flag.Bool("sync", false, "If set, proceeds a synchronisation request and outputs the synchronisation key.") syncRequest := flag.Bool("sync", false, "If set, proceeds a synchronisation request and outputs the synchronisation key.")
@ -40,72 +40,81 @@ func main(){
/* (8) Parse flags */ /* (8) Parse flags */
flag.Parse() flag.Parse()
/* (2) Create context + client /* (2) Create context + client
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Create context */ /* (1) Create context */
ctx, err := context.Create( float64(*winSize) / 1e3, uint16(*minDepth), uint16(*thrDepth), uint16(*maxDepth) ); ctx, err := context.Create(float64(*winSize)/1e3, uint16(*minDepth), uint16(*thrDepth), uint16(*maxDepth))
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[CLIENT_ERROR:context] %s\n", err) ); return } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[CLIENT_ERROR:context] %s\n", err))
return
}
/* (2) Create client */ /* (2) Create client */
cli, err := client.New(ctx, *clientConfigPath) cli, err := client.New(ctx, *clientConfigPath)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[CLIENT_ERROR:client] %s\n", err) ); return } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[CLIENT_ERROR:client] %s\n", err))
return
}
/* (3) Dispatch execution /* (3) Dispatch execution
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) If synchronisation request */ /* (1) If synchronisation request */
if *syncRequest { if *syncRequest {
synchronisationRequest(cli); synchronisationRequest(cli)
/* (2) If request */ /* (2) If request */
} else if *isRequest { } else if *isRequest {
err = cli.Send(os.Stdout) err = cli.Send(os.Stdout)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[CLIENT_ERROR:request] %s\n", err) ) } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[CLIENT_ERROR:request] %s\n", err))
}
/* (3) If response */ /* (3) If response */
} else if *isResponse { } else if *isResponse {
err = cli.Receive(os.Stdin) err = cli.Receive(os.Stdin)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[CLIENT_ERROR:response] %s\n", err) ); return } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[CLIENT_ERROR:response] %s\n", err))
return
}
/* (4) Else -> nothing */ /* (4) Else -> nothing */
}else { } else {
os.Stderr.WriteString( fmt.Sprintf("Missing argument.\n\nYou must give one of the 3 available actions :\n -req to manage a request\n -res to manage a response\n -sync to get a synchronisation key\n") ) os.Stderr.WriteString(fmt.Sprintf("Missing argument.\n\nYou must give one of the 3 available actions :\n -req to manage a request\n -res to manage a response\n -sync to get a synchronisation key\n"))
} }
executionStop := time.Now().UnixNano() executionStop := time.Now().UnixNano()
nsElapsed := float64(executionStop - executionStart) nsElapsed := float64(executionStop - executionStart)
usElapsed := float64(nsElapsed / 1e3) usElapsed := float64(nsElapsed / 1e3)
msElapsed := float64(usElapsed / 1e3) msElapsed := float64(usElapsed / 1e3)
sElapsed := float64(msElapsed / 1e3) sElapsed := float64(msElapsed / 1e3)
if sElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f s\n", sElapsed) ) if sElapsed >= 1 {
} else if msElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f ms\n", msElapsed) ) os.Stderr.WriteString(fmt.Sprintf("executed in %.3f s\n", sElapsed))
} else if usElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f us\n", usElapsed) ) } else if msElapsed >= 1 {
} else if nsElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f ns\n", nsElapsed) ) } os.Stderr.WriteString(fmt.Sprintf("executed in %.3f ms\n", msElapsed))
} else if usElapsed >= 1 {
os.Stderr.WriteString(fmt.Sprintf("executed in %.3f us\n", usElapsed))
} else if nsElapsed >= 1 {
os.Stderr.WriteString(fmt.Sprintf("executed in %.3f ns\n", nsElapsed))
}
return return
} }
func synchronisationRequest(cli *client.T) {
func synchronisationRequest(cli *client.T){
/* (1) Get synchronisation key */ /* (1) Get synchronisation key */
syncKey, err := cli.SynchronisationKey() syncKey, err := cli.SynchronisationKey()
if err != nil { fmt.Errorf("[CLIENT_ERROR:syncKey] %s\n", err); return } if err != nil {
fmt.Errorf("[CLIENT_ERROR:syncKey] %s\n", err)
return
}
/* (2) Print synchronisation key */ /* (2) Print synchronisation key */
os.Stdout.Write(syncKey) os.Stdout.Write(syncKey)
return; return
} }

View File

@ -1,16 +1,16 @@
package main package main
import ( import (
"git.xdrm.io/schastsp/internal/scha"
"time"
"os"
"flag" "flag"
"fmt" "fmt"
"git.xdrm.io/schastsp/context" "git.xdrm.io/logauth/schastsp/context"
"git.xdrm.io/schastsp/server" "git.xdrm.io/logauth/schastsp/internal/scha"
"git.xdrm.io/logauth/schastsp/server"
"os"
"time"
) )
func main(){ func main() {
executionStart := time.Now().UnixNano() executionStart := time.Now().UnixNano()
@ -37,69 +37,81 @@ func main(){
/* (7) Parse flags */ /* (7) Parse flags */
flag.Parse() flag.Parse()
/* (2) Create context + client /* (2) Create context + client
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Create context */ /* (1) Create context */
ctx, err := context.Create( float64(*winSize) / 1e3, uint16(*minDepth), uint16(*thrDepth), uint16(*maxDepth) ); ctx, err := context.Create(float64(*winSize)/1e3, uint16(*minDepth), uint16(*thrDepth), uint16(*maxDepth))
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[SERVER_ERROR:context] %s\n", err) ); return } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[SERVER_ERROR:context] %s\n", err))
return
}
/* (2) Create server */ /* (2) Create server */
ser, err := server.New(ctx, *serverConfigPath) ser, err := server.New(ctx, *serverConfigPath)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[SERVER_ERROR:server] %s\n", err) ); return } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[SERVER_ERROR:server] %s\n", err))
return
}
/* (3) Dispatch execution /* (3) Dispatch execution
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) If synchronisation request */ /* (1) If synchronisation request */
if *syncRequest { if *syncRequest {
synchronisationCommit(ser); synchronisationCommit(ser)
/* (2) If request handling */ /* (2) If request handling */
} else { } else {
err = ser.HandleRequest(os.Stdin, os.Stdout) err = ser.HandleRequest(os.Stdin, os.Stdout)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[SERVER_ERROR:request] %s\n", err) ) } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[SERVER_ERROR:request] %s\n", err))
}
} }
executionStop := time.Now().UnixNano() executionStop := time.Now().UnixNano()
nsElapsed := float64(executionStop - executionStart) nsElapsed := float64(executionStop - executionStart)
usElapsed := float64(nsElapsed / 1e3) usElapsed := float64(nsElapsed / 1e3)
msElapsed := float64(usElapsed / 1e3) msElapsed := float64(usElapsed / 1e3)
sElapsed := float64(msElapsed / 1e3) sElapsed := float64(msElapsed / 1e3)
if sElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f s\n", sElapsed) ) if sElapsed >= 1 {
} else if msElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f ms\n", msElapsed) ) os.Stderr.WriteString(fmt.Sprintf("executed in %.3f s\n", sElapsed))
} else if usElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f us\n", usElapsed) ) } else if msElapsed >= 1 {
} else if nsElapsed >= 1 { os.Stderr.WriteString( fmt.Sprintf("executed in %.3f ns\n", nsElapsed) ) } os.Stderr.WriteString(fmt.Sprintf("executed in %.3f ms\n", msElapsed))
} else if usElapsed >= 1 {
os.Stderr.WriteString(fmt.Sprintf("executed in %.3f us\n", usElapsed))
} else if nsElapsed >= 1 {
os.Stderr.WriteString(fmt.Sprintf("executed in %.3f ns\n", nsElapsed))
}
return return
} }
func synchronisationCommit(ser *server.T) {
func synchronisationCommit(ser *server.T){
/* (1) Try to read key from standard input */ /* (1) Try to read key from standard input */
syncKey := make([]byte, scha.HSIZE) syncKey := make([]byte, scha.HSIZE)
read, err := os.Stdin.Read(syncKey) read, err := os.Stdin.Read(syncKey)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[SERVER_ERROR:syncKey] %s\n", err) ); return } if err != nil {
if uint16(read) != scha.HSIZE { os.Stderr.WriteString( fmt.Sprintf("[SERVER_ERROR:syncKey] Cannot read proper key size\n") ); return } os.Stderr.WriteString(fmt.Sprintf("[SERVER_ERROR:syncKey] %s\n", err))
return
}
if uint16(read) != scha.HSIZE {
os.Stderr.WriteString(fmt.Sprintf("[SERVER_ERROR:syncKey] Cannot read proper key size\n"))
return
}
/* (2) Store synchronisation key */ /* (2) Store synchronisation key */
err = ser.SynchronisationKey(syncKey) err = ser.SynchronisationKey(syncKey)
if err != nil { os.Stderr.WriteString( fmt.Sprintf("[SERVER_ERROR:syncKey] %s\n", err) ); return } if err != nil {
os.Stderr.WriteString(fmt.Sprintf("[SERVER_ERROR:syncKey] %s\n", err))
return
}
/* (3) Debug */ /* (3) Debug */
os.Stdout.WriteString("Synchronisation key successfully written\n") os.Stdout.WriteString("Synchronisation key successfully written\n")
return; return
} }

View File

@ -1,41 +1,41 @@
package main package main
import ( import (
"time"
"bytes" "bytes"
"os"
"flag" "flag"
"fmt" "fmt"
"git.xdrm.io/schastsp/context" "git.xdrm.io/logauth/schastsp/client"
"git.xdrm.io/schastsp/client" "git.xdrm.io/logauth/schastsp/context"
"git.xdrm.io/schastsp/server" "git.xdrm.io/logauth/schastsp/server"
"os"
"time"
) )
/* Store target config paths */ /* Store target config paths */
var clientConfigPath = "/tmp/cyclichash/client/"; var clientConfigPath = "/tmp/cyclichash/client/"
var serverConfigPath = "/tmp/cyclichash/server/lastkey"; var serverConfigPath = "/tmp/cyclichash/server/lastkey"
func main() {
func main(){
executionStart := time.Now().UnixNano() executionStart := time.Now().UnixNano()
/* (1) Create context /* (1) Create context
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Create it */ /* (1) Create it */
ctx, err := context.Create(2, 0x0f0, 0x0a, 0xfff); ctx, err := context.Create(2, 0x0f0, 0x0a, 0xfff)
if err != nil { fmt.Printf("[ERROR:context] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:context] %s\n", err)
return
}
/* (2) Manage flags (cli arguments) /* (2) Manage flags (cli arguments)
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Define 'sync' mode */ /* (1) Define 'sync' mode */
var sync bool; var sync bool
flag.BoolVar(&sync, "sync", false, "If set, a new synchronisation key will be outputed\nNote: it will break existing systems") flag.BoolVar(&sync, "sync", false, "If set, a new synchronisation key will be outputed\nNote: it will break existing systems")
/* (2) Define connection 'latency' */ /* (2) Define connection 'latency' */
var latency int64; var latency int64
flag.Int64Var(&latency, "t", 0, "Connection latency in ms") flag.Int64Var(&latency, "t", 0, "Connection latency in ms")
/* (3) Parse cli arguments */ /* (3) Parse cli arguments */
@ -48,86 +48,106 @@ func main(){
testCommunication(ctx, latency) testCommunication(ctx, latency)
} }
executionStop := time.Now().UnixNano() executionStop := time.Now().UnixNano()
nsElapsed := float64(executionStop - executionStart) nsElapsed := float64(executionStop - executionStart)
usElapsed := float64(nsElapsed / 1e3) usElapsed := float64(nsElapsed / 1e3)
msElapsed := float64(usElapsed / 1e3) msElapsed := float64(usElapsed / 1e3)
sElapsed := float64(msElapsed / 1e3) sElapsed := float64(msElapsed / 1e3)
if sElapsed >= 1 { fmt.Printf("executed in %.3f s\n", sElapsed)
} else if msElapsed >= 1 { fmt.Printf("executed in %.3f ms\n", msElapsed)
} else if usElapsed >= 1 { fmt.Printf("executed in %.3f us\n", usElapsed)
} else if nsElapsed >= 1 { fmt.Printf("executed in %.3f ns\n", nsElapsed) }
if sElapsed >= 1 {
fmt.Printf("executed in %.3f s\n", sElapsed)
} else if msElapsed >= 1 {
fmt.Printf("executed in %.3f ms\n", msElapsed)
} else if usElapsed >= 1 {
fmt.Printf("executed in %.3f us\n", usElapsed)
} else if nsElapsed >= 1 {
fmt.Printf("executed in %.3f ns\n", nsElapsed)
}
return return
} }
func synchronise(ctx *context.T) {
func synchronise(ctx *context.T){
/* (1) Create client */ /* (1) Create client */
client, err := client.New(ctx, clientConfigPath); client, err := client.New(ctx, clientConfigPath)
if err != nil { fmt.Printf("[ERROR:client] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:client] %s\n", err)
return
}
/* (2) Get synchronisation key */ /* (2) Get synchronisation key */
syncKey, err := client.SynchronisationKey() syncKey, err := client.SynchronisationKey()
if err != nil { fmt.Printf("[ERROR:syncKey] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:syncKey] %s\n", err)
return
}
/* (3) Store synchronisation key */ /* (3) Store synchronisation key */
conf, err := os.OpenFile(serverConfigPath, os.O_RDWR|os.O_CREATE, 0775) conf, err := os.OpenFile(serverConfigPath, os.O_RDWR|os.O_CREATE, 0775)
if err != nil { fmt.Printf("[ERROR:syncKey:storage] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:syncKey:storage] %s\n", err)
return
}
defer conf.Close() defer conf.Close()
conf.Write(syncKey) conf.Write(syncKey)
fmt.Printf("Synchronisation Key stored\n"); fmt.Printf("Synchronisation Key stored\n")
return; return
} }
func testCommunication(ctx *context.T, latency int64) {
func testCommunication(ctx *context.T, latency int64){
/* (1) Setup /* (1) Setup
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Create client */ /* (1) Create client */
client, err := client.New(ctx, clientConfigPath); client, err := client.New(ctx, clientConfigPath)
if err != nil { fmt.Printf("[ERROR:client] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:client] %s\n", err)
return
}
/* (2) Create server */ /* (2) Create server */
server, err := server.New(ctx, serverConfigPath) server, err := server.New(ctx, serverConfigPath)
if err != nil { fmt.Printf("[ERROR:server] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:server] %s\n", err)
return
}
/* (3) Create emulation buffers */ /* (3) Create emulation buffers */
requestSocket, responseSocket := new(bytes.Buffer), new(bytes.Buffer) requestSocket, responseSocket := new(bytes.Buffer), new(bytes.Buffer)
/* (2) REAL STUFF HAPPENS HERE /* (2) REAL STUFF HAPPENS HERE
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Request | client send */ /* (1) Request | client send */
err = client.Send(requestSocket) err = client.Send(requestSocket)
if err != nil { fmt.Printf("[ERROR:request:send] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:request:send] %s\n", err)
return
}
/* (2) Emulate latency */ /* (2) Emulate latency */
time.Sleep( time.Duration(latency) * time.Second / 1e3) time.Sleep(time.Duration(latency) * time.Second / 1e3)
/* (3) Request | server receive */ /* (3) Request | server receive */
/* Response | server send */ /* Response | server send */
err = server.HandleRequest(requestSocket, responseSocket) err = server.HandleRequest(requestSocket, responseSocket)
if err != nil { fmt.Printf("[ERROR:request:receive] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:request:receive] %s\n", err)
return
}
/* (4) Response | client receive */ /* (4) Response | client receive */
err = client.Receive(responseSocket) err = client.Receive(responseSocket)
if err != nil { fmt.Printf("[ERROR:response:receive] %s\n", err); return } if err != nil {
fmt.Printf("[ERROR:response:receive] %s\n", err)
return
}
return; return
} }

View File

@ -11,13 +11,12 @@ const DefaultMin = 0x00f0
const DefaultMax = 0x0fff const DefaultMax = 0x0fff
const DefaultThr = 0x000a const DefaultThr = 0x000a
/* (2) Struct attributes */ /* (2) Struct attributes */
type T struct { type T struct {
win float64; // 'timeid' window size win float64 // 'timeid' window size
min uint16; // minimum scha depth min uint16 // minimum scha depth
max uint16; // maximum scha depth max uint16 // maximum scha depth
thr uint16; // scha depth threshold thr uint16 // scha depth threshold
} }
/* (3) Constructor /* (3) Constructor
@ -30,13 +29,15 @@ type T struct {
* @return outName<outType> outDesc * @return outName<outType> outDesc
* *
---------------------------------------------------------*/ ---------------------------------------------------------*/
func Create(win float64, optional... uint16) (*T, error) { func Create(win float64, optional ...uint16) (*T, error) {
var inst = new(T); var inst = new(T)
/* (1) Window size error */ /* (1) Window size error */
if win < 0 { return nil, errors.New("Window size must be positive and is negative") } if win < 0 {
inst.win = win; return nil, errors.New("Window size must be positive and is negative")
}
inst.win = win
/* (2) Default values */ /* (2) Default values */
inst.min = DefaultMin inst.min = DefaultMin
@ -49,13 +50,13 @@ func Create(win float64, optional... uint16) (*T, error) {
if optional[0] < 0x0f { if optional[0] < 0x0f {
return nil, errors.New("Minimum depth must be greater than 0x0f (decimal 15) for consistency issues") return nil, errors.New("Minimum depth must be greater than 0x0f (decimal 15) for consistency issues")
} }
inst.min = optional[0]; inst.min = optional[0]
} }
/* (4) Optional 'thr' */ /* (4) Optional 'thr' */
if len(optional) > 1 { if len(optional) > 1 {
inst.thr = optional[1]; inst.thr = optional[1]
} }
/* (5) Optional 'max' */ /* (5) Optional 'max' */
@ -64,7 +65,7 @@ func Create(win float64, optional... uint16) (*T, error) {
if optional[2] <= inst.min+inst.thr { if optional[2] <= inst.min+inst.thr {
return nil, errors.New("Minimum depth must be greater than 0x0f (decimal 15) for consistency issues") return nil, errors.New("Minimum depth must be greater than 0x0f (decimal 15) for consistency issues")
} }
inst.max = optional[2]; inst.max = optional[2]
} }
@ -72,7 +73,7 @@ func Create(win float64, optional... uint16) (*T, error) {
} }
/* (4) Getters */ /* (4) Getters */
func (c T) Window() float64 { return c.win } func (c T) Window() float64 { return c.win }
func (c T) MinDepth() uint16 { return c.min } func (c T) MinDepth() uint16 { return c.min }
func (c T) MaxDepth() uint16 { return c.max } func (c T) MaxDepth() uint16 { return c.max }
func (c T) DepthThreshold() uint16 { return c.thr } func (c T) DepthThreshold() uint16 { return c.thr }

View File

@ -4,14 +4,13 @@ import (
"testing" "testing"
) )
func TestDefaultArguments(t *testing.T) { func TestDefaultArguments(t *testing.T) {
ctx, err := Create(2.2); ctx, err := Create(2.2)
if err != nil { if err != nil {
t.Errorf("[Unexpected error]: %s", err); t.Errorf("[Unexpected error]: %s", err)
return; return
} }
// check all optional arguments // check all optional arguments
if ctx.Window() != 2.2 { if ctx.Window() != 2.2 {
@ -38,34 +37,34 @@ func TestDefaultArguments(t *testing.T) {
func TestOptionalMinConstraint(t *testing.T) { func TestOptionalMinConstraint(t *testing.T) {
ctx, err := Create(2.2, 0x0f); ctx, err := Create(2.2, 0x0f)
if err != nil { if err != nil {
t.Errorf("[Unexpected error]: %s", err); t.Errorf("[Unexpected error]: %s", err)
return; return
} }
ctx, err = Create(2.2, 0x0f-1); ctx, err = Create(2.2, 0x0f-1)
if err == nil { if err == nil {
t.Errorf("Expected an error with 'min' < %d ; got min=%d", 0x0f, ctx.MinDepth()); t.Errorf("Expected an error with 'min' < %d ; got min=%d", 0x0f, ctx.MinDepth())
return; return
} }
} }
func TestOptionalMaxConstraint(t *testing.T) { func TestOptionalMaxConstraint(t *testing.T) {
ctx, err := Create(2.2, 0xf1, 0x02, 0xf1+0x02+1); ctx, err := Create(2.2, 0xf1, 0x02, 0xf1+0x02+1)
if err != nil { if err != nil {
t.Errorf("[Unexpected error]: %s", err); t.Errorf("[Unexpected error]: %s", err)
return; return
} }
ctx, err = Create(2.2, 0xf1, 0x02, 0xf1+0x02); ctx, err = Create(2.2, 0xf1, 0x02, 0xf1+0x02)
if err == nil { if err == nil {
t.Errorf("Expected an error with 'max' > 'min'+'thr' ; got max=%d, min=%d, thr=%d", ctx.MinDepth(), ctx.MaxDepth(), ctx.DepthThreshold()); t.Errorf("Expected an error with 'max' > 'min'+'thr' ; got max=%d, min=%d, thr=%d", ctx.MinDepth(), ctx.MaxDepth(), ctx.DepthThreshold())
return; return
} }
} }

View File

@ -3,8 +3,8 @@ package keyset
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"git.xdrm.io/schastsp/context" "git.xdrm.io/logauth/schastsp/context"
"git.xdrm.io/schastsp/internal/scha" "git.xdrm.io/logauth/schastsp/internal/scha"
"io" "io"
) )
@ -230,7 +230,9 @@ func (s *T) Rescue(lastHash []byte) error {
/* (1) Process hash */ /* (1) Process hash */
currentHash, err := scha.Hash(s.sec, i) currentHash, err := scha.Hash(s.sec, i)
if err != nil { return err } if err != nil {
return err
}
/* (2) If not found -> try again */ /* (2) If not found -> try again */
if string(currentHash) != string(lastHash) { if string(currentHash) != string(lastHash) {

View File

@ -5,7 +5,6 @@ import (
"time" "time"
) )
/* (2) Generates a pseudo-random KeySet /* (2) Generates a pseudo-random KeySet
* *
---------------------------------------------------------*/ ---------------------------------------------------------*/
@ -27,7 +26,7 @@ func (s *T) generate() {
/* (2) Manage other attributes /* (2) Manage other attributes
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Random depth pick init */ /* (1) Random depth pick init */
var randMin, randMax = s.ctx.MinDepth() + (s.ctx.MaxDepth()-s.ctx.MinDepth()) / 2, s.ctx.MaxDepth() var randMin, randMax = s.ctx.MinDepth() + (s.ctx.MaxDepth()-s.ctx.MinDepth())/2, s.ctx.MaxDepth()
/* (2) Select "random" depth */ /* (2) Select "random" depth */
s.depth = randMin + uint16(rand.Intn(int(randMax+1-randMin))) s.depth = randMin + uint16(rand.Intn(int(randMax+1-randMin)))
@ -35,4 +34,4 @@ func (s *T) generate() {
/* (3) Reset comsumption level */ /* (3) Reset comsumption level */
s.mcode = 0 s.mcode = 0
} }

View File

@ -2,8 +2,8 @@ package keyset
import ( import (
"bytes" "bytes"
"git.xdrm.io/schastsp/context" "git.xdrm.io/logauth/schastsp/context"
"git.xdrm.io/schastsp/lib/scha" "git.xdrm.io/logauth/schastsp/internal/scha"
"testing" "testing"
) )

View File

@ -1,19 +1,16 @@
package scha package scha
import ( import (
"errors"
"crypto/sha512" "crypto/sha512"
"git.xdrm.io/schastsp/internal/xor" "errors"
"git.xdrm.io/logauth/schastsp/internal/xor"
) )
/* (0) Static /* (0) Static
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Constants */ /* (1) Constants */
const HSIZE uint16 = sha512.Size; const HSIZE uint16 = sha512.Size
const HBITSIZE uint32 = uint32(HSIZE) * 8; const HBITSIZE uint32 = uint32(HSIZE) * 8
/* (1) Basic hash function /* (1) Basic hash function
* *
@ -22,23 +19,22 @@ const HBITSIZE uint32 = uint32(HSIZE) * 8;
* @return digest<[]byte]> Byte array digest * @return digest<[]byte]> Byte array digest
* *
---------------------------------------------------------*/ ---------------------------------------------------------*/
func hash(input []byte) []byte{ func hash(input []byte) []byte {
/* (1) Create sha512 hasher */ /* (1) Create sha512 hasher */
hasher := sha512.New(); hasher := sha512.New()
/* (2) Defer memory cleanup */ /* (2) Defer memory cleanup */
defer hasher.Reset(); defer hasher.Reset()
/* (3) Set input to be hashed */ /* (3) Set input to be hashed */
hasher.Write(input); hasher.Write(input)
/* (4) Extract digest */ /* (4) Extract digest */
return hasher.Sum(nil); return hasher.Sum(nil)
} }
/* (2) Public hashing interface /* (2) Public hashing interface
* *
* @input<[]byte> Byte array input * @input<[]byte> Byte array input
@ -50,32 +46,33 @@ func hash(input []byte) []byte{
* @return err<error> If consistence error * @return err<error> If consistence error
* *
---------------------------------------------------------*/ ---------------------------------------------------------*/
func Hash(input []byte, depth uint16, options... []byte) ([]byte, error) { func Hash(input []byte, depth uint16, options ...[]byte) ([]byte, error) {
/* (1) Manage errors errors /* (1) Manage errors errors
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Avoid no depth (no hash at all) */ /* (1) Avoid no depth (no hash at all) */
if( depth < 1 ){ if depth < 1 {
return nil, errors.New("Cannot use a 'depth' of zero. This is inconsistent and means that no hash will be processed"); return nil, errors.New("Cannot use a 'depth' of zero. This is inconsistent and means that no hash will be processed")
} }
/* (2) Avoir empty input */ /* (2) Avoir empty input */
if( len(input) < 1 ){ if len(input) < 1 {
return nil, errors.New("Cannot use an empty 'input'. This is inconsistent"); return nil, errors.New("Cannot use an empty 'input'. This is inconsistent")
} }
/* (2) Extract optional arguments /* (2) Extract optional arguments
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Optional salt */ /* (1) Optional salt */
salt := make([]byte, 0) salt := make([]byte, 0)
if len(options) > 0 { salt = options[0] } if len(options) > 0 {
salt = options[0]
}
/* (2) Optional pepper */ /* (2) Optional pepper */
pepper := make([]byte, 0) pepper := make([]byte, 0)
if len(options) > 0 { pepper = options[0] } if len(options) > 0 {
pepper = options[0]
}
/* (3) Process cyclic hash /* (3) Process cyclic hash
---------------------------------------------------------*/ ---------------------------------------------------------*/
@ -83,14 +80,14 @@ func Hash(input []byte, depth uint16, options... []byte) ([]byte, error) {
digest := make([]byte, 0, HSIZE) digest := make([]byte, 0, HSIZE)
/* (2) Process first hash + salt */ /* (2) Process first hash + salt */
digest = hash( xor.ByteArray(input, salt) ) digest = hash(xor.ByteArray(input, salt))
/* (3) Iterate @depth times */ /* (3) Iterate @depth times */
for depth--; depth > 0; depth-- { for depth--; depth > 0; depth-- {
// Add Pepper only for last time // Add Pepper only for last time
if( depth == 1 ){ if depth == 1 {
digest = hash( xor.ByteArray(digest, pepper) ) digest = hash(xor.ByteArray(digest, pepper))
} else { } else {
digest = hash(digest) digest = hash(digest)
} }
@ -98,4 +95,4 @@ func Hash(input []byte, depth uint16, options... []byte) ([]byte, error) {
} }
return digest, nil return digest, nil
} }

View File

@ -2,155 +2,147 @@ package scha
import "testing" import "testing"
func TestSimpleHash(t *testing.T){ func TestSimpleHash(t *testing.T) {
input := []byte("somePlainText"); input := []byte("somePlainText")
expected := []byte{ expected := []byte{
0x4c, 0xcb, 0x0e, 0xf6, 0x81, 0x99, 0x2e, 0xd6, 0xb8, 0x17, 0x52, 0x1d, 0x09, 0x4c, 0xcb, 0x0e, 0xf6, 0x81, 0x99, 0x2e, 0xd6, 0xb8, 0x17, 0x52, 0x1d, 0x09,
0x6e, 0x99, 0x19, 0xe7, 0xda, 0x50, 0xc8, 0xbf, 0x64, 0xae, 0xc1, 0x4f, 0xaa, 0x6e, 0x99, 0x19, 0xe7, 0xda, 0x50, 0xc8, 0xbf, 0x64, 0xae, 0xc1, 0x4f, 0xaa,
0x47, 0x06, 0xf3, 0x49, 0x30, 0x8a, 0x90, 0x8e, 0xd2, 0xff, 0xc2, 0x6d, 0xee, 0x47, 0x06, 0xf3, 0x49, 0x30, 0x8a, 0x90, 0x8e, 0xd2, 0xff, 0xc2, 0x6d, 0xee,
0xaa, 0xd6, 0x45, 0xd8, 0xb3, 0x17, 0xe3, 0xb9, 0x45, 0x29, 0x26, 0xe2, 0x8e, 0xaa, 0xd6, 0x45, 0xd8, 0xb3, 0x17, 0xe3, 0xb9, 0x45, 0x29, 0x26, 0xe2, 0x8e,
0x99, 0x50, 0x94, 0x49, 0x90, 0x02, 0xa5, 0x61, 0x4a, 0x3f, 0x5e, 0xfa}; 0x99, 0x50, 0x94, 0x49, 0x90, 0x02, 0xa5, 0x61, 0x4a, 0x3f, 0x5e, 0xfa}
got, err := Hash(input, 1); got, err := Hash(input, 1)
digestLength := uint(len(got)); digestLength := uint(len(got))
/* (2) Fail on errors */ /* (2) Fail on errors */
if err != nil { if err != nil {
t.Errorf("Expected no errors, got: %s", err); t.Errorf("Expected no errors, got: %s", err)
} }
/* (2) Fail on wrong size */ /* (2) Fail on wrong size */
if uint16(digestLength) != HSIZE { if uint16(digestLength) != HSIZE {
t.Errorf("Expected hash digest of %d bytes ; %d bytes received", HSIZE, digestLength); t.Errorf("Expected hash digest of %d bytes ; %d bytes received", HSIZE, digestLength)
} }
/* (3) Check each byte */ /* (3) Check each byte */
for k, v := range got{ for k, v := range got {
if v != expected[k] { if v != expected[k] {
t.Errorf("Expected sha[%d] of '%x' to be '%x' ; received '%x'", HSIZE, input, expected, got); t.Errorf("Expected sha[%d] of '%x' to be '%x' ; received '%x'", HSIZE, input, expected, got)
return; return
} }
} }
} }
func TestDepth1Plus1Equals2(t *testing.T) {
input := []byte("someOtherPlainText")
func TestDepth1Plus1Equals2(t *testing.T){
input := []byte("someOtherPlainText");
/* (1) Calculate H1 */ /* (1) Calculate H1 */
h1, err := Hash(input, 1) h1, err := Hash(input, 1)
if err != nil { if err != nil {
t.Errorf("Expected no error"); t.Errorf("Expected no error")
return; return
} }
/* (2) Calculate H1(H1) */ /* (2) Calculate H1(H1) */
h11, err := Hash(h1, 1); h11, err := Hash(h1, 1)
if err != nil { if err != nil {
t.Errorf("Expected no error"); t.Errorf("Expected no error")
return; return
} }
/* (3) Calculate H2 */ /* (3) Calculate H2 */
h2, err := Hash(input, 1+1); h2, err := Hash(input, 1+1)
if err != nil { if err != nil {
t.Errorf("Expected no error"); t.Errorf("Expected no error")
return; return
} }
/* (4) Manage different length */ /* (4) Manage different length */
if len(h11) != len(h2) || len(h11) != int(HSIZE) { if len(h11) != len(h2) || len(h11) != int(HSIZE) {
t.Errorf("Expected digest lengths to be %d, got %d and %d", HSIZE, len(h11), len(h2)); t.Errorf("Expected digest lengths to be %d, got %d and %d", HSIZE, len(h11), len(h2))
return; return
} }
/* (5) Compare the 2 strings */ /* (5) Compare the 2 strings */
for k, v := range h11 { for k, v := range h11 {
if v != h2[k] { if v != h2[k] {
t.Errorf("Expected h2() to be equal to h1(h1())\n got '%x'\n expected '%x'", h2, h11) t.Errorf("Expected h2() to be equal to h1(h1())\n got '%x'\n expected '%x'", h2, h11)
return; return
} }
} }
} }
func TestDepth52Plus64Equals116(t *testing.T) {
input := []byte("someOtherPlainText")
func TestDepth52Plus64Equals116(t *testing.T){
input := []byte("someOtherPlainText");
/* (1) Calculate H52 */ /* (1) Calculate H52 */
h52, err := Hash(input, 52) h52, err := Hash(input, 52)
if err != nil { if err != nil {
t.Errorf("Expected no error"); t.Errorf("Expected no error")
return; return
} }
/* (2) Calculate H52(H64) */ /* (2) Calculate H52(H64) */
h5264, err := Hash(h52, 64); h5264, err := Hash(h52, 64)
if err != nil { if err != nil {
t.Errorf("Expected no error"); t.Errorf("Expected no error")
return; return
} }
/* (3) Calculate H116 */ /* (3) Calculate H116 */
h116, err := Hash(input, 52+64); h116, err := Hash(input, 52+64)
if err != nil { if err != nil {
t.Errorf("Expected no error"); t.Errorf("Expected no error")
return; return
} }
/* (4) Manage different length */ /* (4) Manage different length */
if len(h5264) != len(h116) || len(h5264) != int(HSIZE) { if len(h5264) != len(h116) || len(h5264) != int(HSIZE) {
t.Errorf("Expected digest lengths to be %d, got %d and %d", HSIZE, len(h5264), len(h116)); t.Errorf("Expected digest lengths to be %d, got %d and %d", HSIZE, len(h5264), len(h116))
return; return
} }
/* (5) Compare the 2 strings */ /* (5) Compare the 2 strings */
for k, v := range h5264 { for k, v := range h5264 {
if v != h116[k] { if v != h116[k] {
t.Errorf("Expected h116() to be equal to h52(h64())\n got '%x'\n expected '%x'", h116, h5264) t.Errorf("Expected h116() to be equal to h52(h64())\n got '%x'\n expected '%x'", h116, h5264)
return; return
} }
} }
} }
func TestDepthError(t *testing.T) {
input := []byte("somePlainText")
func TestDepthError(t *testing.T){ _, err := Hash(input, 0)
input := []byte("somePlainText");
_, err := Hash(input, 0);
/* (2) Fail on errors */ /* (2) Fail on errors */
if err == nil { if err == nil {
t.Errorf("Expected an error for depth of 0"); t.Errorf("Expected an error for depth of 0")
} }
} }
func TestEmptyInputError(t *testing.T) {
_, err := Hash(nil, 1)
func TestEmptyInputError(t *testing.T){
_, err := Hash(nil, 1);
/* (2) Fail on errors */ /* (2) Fail on errors */
if err == nil { if err == nil {
t.Errorf("Expected an error for empty input"); t.Errorf("Expected an error for empty input")
} }
} }

View File

@ -5,7 +5,6 @@ import (
"time" "time"
) )
/* (1) Generates the current time id /* (1) Generates the current time id
* *
* @wsize<float64> Window Size in seconds * @wsize<float64> Window Size in seconds
@ -14,24 +13,25 @@ import (
* @return parity<uint32> Current time parity * @return parity<uint32> Current time parity
* *
---------------------------------------------------------*/ ---------------------------------------------------------*/
func Generate(wsize float64) (uint32, uint32){ func Generate(wsize float64) (uint32, uint32) {
/* (1) If wsize is 0 (div by zero possible error) */ /* (1) If wsize is 0 (div by zero possible error) */
if wsize == 0 { return 0, 0 } if wsize == 0 {
return 0, 0
}
/* (2) Get current timestamp */ /* (2) Get current timestamp */
timestamp := float64( time.Now().Unix() ); timestamp := float64(time.Now().Unix())
/* (3) Calculate the time id */ /* (3) Calculate the time id */
var id = uint32( timestamp / wsize ); var id = uint32(timestamp / wsize)
/* (4) Calculate parity */ /* (4) Calculate parity */
var parity = id % 2; var parity = id % 2
return id, parity; return id, parity
} }
/* (2) Try to guess a previous time id from its parity /* (2) Try to guess a previous time id from its parity
* *
* @wsize<float64> Window Size in seconds * @wsize<float64> Window Size in seconds
@ -40,12 +40,12 @@ func Generate(wsize float64) (uint32, uint32){
* @return id<uint32> The guessed time id * @return id<uint32> The guessed time id
* *
---------------------------------------------------------*/ ---------------------------------------------------------*/
func Guess(wsize float64, parity uint32) uint32{ func Guess(wsize float64, parity uint32) uint32 {
/* (1) Get current time id */ /* (1) Get current time id */
var idNow, parityNow = Generate(wsize); var idNow, parityNow = Generate(wsize)
/* (2) Update ID with tidNow parity difference */ /* (2) Update ID with tidNow parity difference */
return idNow - uint32(math.Abs( float64(parityNow) - float64(parity) )); return idNow - uint32(math.Abs(float64(parityNow)-float64(parity)))
} }

View File

@ -1,23 +1,22 @@
package timeid package timeid
import ( import (
"time"
"testing" "testing"
"time"
) )
func TestGuessing(t *testing.T) {
func TestGuessing(t *testing.T){ var windowSize float64 = .5
var windowSize float64 = .5; id, parity := Generate(windowSize)
id, parity := Generate(windowSize); time.Sleep(time.Duration(windowSize) * time.Second)
time.Sleep( time.Duration(windowSize) * time.Second); var guessedId = Guess(windowSize, parity)
var guessedId = Guess(windowSize, parity);
if id != guessedId { if id != guessedId {
t.Errorf("Wrong guessed id, expected '%d' ; got '%d'", id, guessedId); t.Errorf("Wrong guessed id, expected '%d' ; got '%d'", id, guessedId)
} }
} }

View File

@ -12,11 +12,10 @@ import "math"
---------------------------------------------------------*/ ---------------------------------------------------------*/
func Byte(_left byte, _right byte) byte { func Byte(_left byte, _right byte) byte {
return _left ^ _right; return _left ^ _right
} }
/* (2) bitwise XOR between Byte arrays /* (2) bitwise XOR between Byte arrays
* *
* @_left<byte[]> Left operand * @_left<byte[]> Left operand
@ -43,16 +42,16 @@ func ByteArray(_left []byte, _right []byte) []byte {
out := make([]byte, l, l) out := make([]byte, l, l)
/* (2) Process bitwise XOR */ /* (2) Process bitwise XOR */
for i := 0 ; i < l ; i++ { for i := 0; i < l; i++ {
// 1. Out of range for _left // 1. Out of range for _left
if i >= ll { if i >= ll {
out[i] = _right[i]; out[i] = _right[i]
} else if i >= lr { } else if i >= lr {
out[i] = _left[i]; out[i] = _left[i]
} else { } else {

View File

@ -1,13 +1,13 @@
package server package server
import ( import (
"errors"
"fmt" "fmt"
"git.xdrm.io/schastsp/internal/scha" "git.xdrm.io/logauth/schastsp/context"
"git.xdrm.io/logauth/schastsp/internal/scha"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"errors"
"git.xdrm.io/schastsp/context"
) )
const DEBUG = false const DEBUG = false
@ -15,12 +15,11 @@ const DEBUG = false
/* (1) Structure /* (1) Structure
---------------------------------------------------------*/ ---------------------------------------------------------*/
type T struct { type T struct {
ctx *context.T // shared context ctx *context.T // shared context
hash []byte // current key hash []byte // current key
conf string // configuration file path conf string // configuration file path
} }
/* (2) Constructor /* (2) Constructor
* *
* @ctx<context.T> Shared context * @ctx<context.T> Shared context
@ -32,43 +31,49 @@ func New(ctx *context.T, savePath string) (*T, error) {
inst := new(T) inst := new(T)
/* (1) Store context */ /* (1) Store context */
if ctx == nil { return nil, errors.New("Context must not be nil"); } if ctx == nil {
inst.ctx = ctx; return nil, errors.New("Context must not be nil")
}
inst.ctx = ctx
/* (2) Get absolute file path */ /* (2) Get absolute file path */
absolutePath, err := filepath.Abs(savePath); absolutePath, err := filepath.Abs(savePath)
if err != nil { return nil, err; } if err != nil {
return nil, err
}
/* (3) Check file */ /* (3) Check file */
info, err := os.Stat(absolutePath); info, err := os.Stat(absolutePath)
if err != nil { if err != nil {
// Unknown error // Unknown error
if !os.IsNotExist(err) { return nil, err } if !os.IsNotExist(err) {
return nil, err
}
// File does not exist -> try to create file // File does not exist -> try to create file
_, err2 := os.Create(absolutePath); _, err2 := os.Create(absolutePath)
if err2 != nil { return nil, err2 } if err2 != nil {
return nil, err2
}
// fail if exists but not regular file // fail if exists but not regular file
} else if !info.Mode().IsRegular() { } else if !info.Mode().IsRegular() {
return nil, errors.New("Configuration file is not a regular file"); return nil, errors.New("Configuration file is not a regular file")
} }
/* (4) Store file path */ /* (4) Store file path */
inst.conf = absolutePath; inst.conf = absolutePath
/* (5) Try to fetch hash from conf */ /* (5) Try to fetch hash from conf */
inst.hash = make([]byte, scha.HSIZE) inst.hash = make([]byte, scha.HSIZE)
err = inst.fetch(); err = inst.fetch()
// if err != nil { return nil, err } // if err != nil { return nil, err }
return inst, nil; return inst, nil
} }
/* (3) Handle request and send response /* (3) Handle request and send response
* *
* @req<io.Reader> Request reader * @req<io.Reader> Request reader
@ -82,33 +87,44 @@ func (s *T) HandleRequest(req io.Reader, res io.Writer) error {
/* (1) Manage request /* (1) Manage request
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Read x1 */ /* (1) Read x1 */
x1 := make([]byte, scha.HSIZE); x1 := make([]byte, scha.HSIZE)
read, err := req.Read(x1); read, err := req.Read(x1)
if err != nil { return fmt.Errorf("Reading x1 : %s", err) } if err != nil {
if uint16(read) != scha.HSIZE { return fmt.Errorf("Cannot read x1 : %d / %d bytes available", read, scha.HSIZE) } return fmt.Errorf("Reading x1 : %s", err)
}
if uint16(read) != scha.HSIZE {
return fmt.Errorf("Cannot read x1 : %d / %d bytes available", read, scha.HSIZE)
}
/* (2) Read x2 */ /* (2) Read x2 */
x2 := make([]byte, scha.HSIZE); x2 := make([]byte, scha.HSIZE)
read, err = req.Read(x2); read, err = req.Read(x2)
if err != nil { return fmt.Errorf("Reading x2 : %s", err) } if err != nil {
if uint16(read) != scha.HSIZE { return fmt.Errorf("Cannot read x2 : %d / %d bytes available", read, scha.HSIZE) } return fmt.Errorf("Reading x2 : %s", err)
}
if uint16(read) != scha.HSIZE {
return fmt.Errorf("Cannot read x2 : %d / %d bytes available", read, scha.HSIZE)
}
/* (3) Manage request */ /* (3) Manage request */
errCode, err := s.manageRequest(x1, x2) errCode, err := s.manageRequest(x1, x2)
if err != nil { return err } if err != nil {
return err
}
/* (4) Valid authentication */ /* (4) Valid authentication */
if errCode == 0 { if errCode == 0 {
response := []byte{0x00} response := []byte{0x00}
written, err := res.Write(response) written, err := res.Write(response)
if written != 1 || err != nil { return err } if written != 1 || err != nil {
return err
}
return nil; return nil
} }
/* (2) If not authenticated -> build resynchronisation response /* (2) If not authenticated -> build resynchronisation response
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Init y1 + y2 */ /* (1) Init y1 + y2 */
@ -117,29 +133,41 @@ func (s *T) HandleRequest(req io.Reader, res io.Writer) error {
/* (2) Generate response */ /* (2) Generate response */
err = s.generateResynchronisationResponse(y1, y2) err = s.generateResynchronisationResponse(y1, y2)
if err != nil { return err } if err != nil {
return err
}
/* (3) Write error code */ /* (3) Write error code */
responseCode := []byte{errCode} responseCode := []byte{errCode}
written, err := res.Write(responseCode); written, err := res.Write(responseCode)
if err != nil { return err } if err != nil {
if written != len(responseCode) { return errors.New("Cannot write response code") } return err
}
if written != len(responseCode) {
return errors.New("Cannot write response code")
}
/* (4) Write y1 into response */ /* (4) Write y1 into response */
written, err = res.Write(y1) written, err = res.Write(y1)
if err != nil { return err } if err != nil {
if written != len(y1) { return errors.New("Cannot write y1") } return err
}
if written != len(y1) {
return errors.New("Cannot write y1")
}
/* (5) Write y2 into response */ /* (5) Write y2 into response */
written, err = res.Write(y2) written, err = res.Write(y2)
if err != nil { return err } if err != nil {
if written != len(y1) { return errors.New("Cannot write y2") } return err
}
if written != len(y1) {
return errors.New("Cannot write y2")
}
return nil return nil
} }
/* (4) Apply a synchronisation key /* (4) Apply a synchronisation key
* *
* @syncKey<[]byte> Synchronisation key from client * @syncKey<[]byte> Synchronisation key from client
@ -162,4 +190,4 @@ func (s *T) SynchronisationKey(syncKey []byte) error {
return nil return nil
} }

View File

@ -4,9 +4,9 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"git.xdrm.io/schastsp/internal/scha" "git.xdrm.io/logauth/schastsp/internal/scha"
"git.xdrm.io/schastsp/internal/timeid" "git.xdrm.io/logauth/schastsp/internal/timeid"
"git.xdrm.io/schastsp/internal/xor" "git.xdrm.io/logauth/schastsp/internal/xor"
"os" "os"
) )
@ -19,14 +19,18 @@ func (s T) store() error {
/* (1) Try to open file for writing */ /* (1) Try to open file for writing */
file, err := os.OpenFile(s.conf, os.O_RDWR|os.O_CREATE, 0755) file, err := os.OpenFile(s.conf, os.O_RDWR|os.O_CREATE, 0755)
if err != nil { return err } if err != nil {
return err
}
/* (2) Defer close */ /* (2) Defer close */
defer file.Close() defer file.Close()
/* (3) Write hash into file */ /* (3) Write hash into file */
err = binary.Write(file, binary.BigEndian, s.hash) err = binary.Write(file, binary.BigEndian, s.hash)
if err != nil { return err } if err != nil {
return err
}
return nil return nil
@ -41,7 +45,9 @@ func (s *T) fetch() error {
/* (1) Try to open file for reading */ /* (1) Try to open file for reading */
file, err := os.Open(s.conf) file, err := os.Open(s.conf)
if err != nil { return err } if err != nil {
return err
}
/* (2) Defer close */ /* (2) Defer close */
defer file.Close() defer file.Close()
@ -49,7 +55,9 @@ func (s *T) fetch() error {
/* (3) Try to fetch hash from file */ /* (3) Try to fetch hash from file */
fetchedHash := make([]byte, scha.HSIZE) fetchedHash := make([]byte, scha.HSIZE)
err = binary.Read(file, binary.BigEndian, fetchedHash) err = binary.Read(file, binary.BigEndian, fetchedHash)
if err != nil { return err } if err != nil {
return err
}
/* (4) Fail if wrong size */ /* (4) Fail if wrong size */
if uint16(len(fetchedHash)) != scha.HSIZE { if uint16(len(fetchedHash)) != scha.HSIZE {
@ -77,7 +85,9 @@ func (s *T) fetch() error {
---------------------------------------------------------*/ ---------------------------------------------------------*/
func (s *T) manageRequest(x1 []byte, x2 []byte) (byte, error) { func (s *T) manageRequest(x1 []byte, x2 []byte) (byte, error) {
if DEBUG { fmt.Printf(" stored hash is H = %x\n", s.hash) } if DEBUG {
fmt.Printf(" stored hash is H = %x\n", s.hash)
}
/* (1) Extract meta data /* (1) Extract meta data
---------------------------------------------------------*/ ---------------------------------------------------------*/
@ -86,19 +96,22 @@ func (s *T) manageRequest(x1 []byte, x2 []byte) (byte, error) {
/* (2) Extract migration code */ /* (2) Extract migration code */
mcode := uint8(x[1]) % 3 mcode := uint8(x[1]) % 3
if DEBUG { fmt.Printf(" extracted code is o = %d\n", mcode) } if DEBUG {
fmt.Printf(" extracted code is o = %d\n", mcode)
}
/* (3) Fail if no migration but different hashes */ /* (3) Fail if no migration but different hashes */
if mcode == 0 && string(x1[2:]) != string(x2[2:]) { if mcode == 0 && string(x1[2:]) != string(x2[2:]) {
return 1, nil return 1, nil
} }
/* (2) TimeID management /* (2) TimeID management
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Extract time mod */ /* (1) Extract time mod */
timeMod := uint32(x[0]) % 2 timeMod := uint32(x[0]) % 2
if DEBUG { fmt.Printf(" extracted time mod m = %d\n", timeMod) } if DEBUG {
fmt.Printf(" extracted time mod m = %d\n", timeMod)
}
/* (2) Try to guess time id */ /* (2) Try to guess time id */
timeID := timeid.Guess(s.ctx.Window(), timeMod) timeID := timeid.Guess(s.ctx.Window(), timeMod)
@ -107,35 +120,43 @@ func (s *T) manageRequest(x1 []byte, x2 []byte) (byte, error) {
/* (3) Hash guessed time id */ /* (3) Hash guessed time id */
hashedTimeID, err := scha.Hash(timeIDBytes, 1) hashedTimeID, err := scha.Hash(timeIDBytes, 1)
if err != nil { return 2, err } if err != nil {
return 2, err
}
/* (3) Extract hashes /* (3) Extract hashes
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Extract hash from x0 */ /* (1) Extract hash from x0 */
h0 := xor.ByteArray(x1, hashedTimeID) h0 := xor.ByteArray(x1, hashedTimeID)
if DEBUG { fmt.Printf(" supposing hash is h0 = %x\n", h0) } if DEBUG {
fmt.Printf(" supposing hash is h0 = %x\n", h0)
}
/* (2) Extract next hash from x1 */ /* (2) Extract next hash from x1 */
h1 := xor.ByteArray(x2, hashedTimeID) h1 := xor.ByteArray(x2, hashedTimeID)
if DEBUG { fmt.Printf(" supposing next is h1 = %x\n", h1) } if DEBUG {
fmt.Printf(" supposing next is h1 = %x\n", h1)
}
/* (3) Only remove timeMod if migration code = 0 */ /* (3) Only remove timeMod if migration code = 0 */
if mcode == 0 { if mcode == 0 {
h1[0] = xor.Byte(h1[0], byte(timeMod)) h1[0] = xor.Byte(h1[0], byte(timeMod))
} }
/* (4) Hash h0 to compare with stored hash 's.hash' /* (4) Hash h0 to compare with stored hash 's.hash'
---------------------------------------------------------*/ ---------------------------------------------------------*/
/* (1) Hash 1 time to check if matches */ /* (1) Hash 1 time to check if matches */
hashedH0, err := scha.Hash(h0, 1) hashedH0, err := scha.Hash(h0, 1)
if err != nil { return 2, err } if err != nil {
return 2, err
}
/* (2) If migration code = 2 (Rescue mode) -> hash MIN times */ /* (2) If migration code = 2 (Rescue mode) -> hash MIN times */
if mcode == 2 { if mcode == 2 {
hashedH0, err = scha.Hash(h0, s.ctx.MinDepth()) hashedH0, err = scha.Hash(h0, s.ctx.MinDepth())
if err != nil { return 2, err } if err != nil {
return 2, err
}
if DEBUG { if DEBUG {
fmt.Printf(" hashed is h(min) = %x\n", hashedH0) fmt.Printf(" hashed is h(min) = %x\n", hashedH0)
@ -149,7 +170,6 @@ func (s *T) manageRequest(x1 []byte, x2 []byte) (byte, error) {
return 1, nil return 1, nil
} }
/* (5) Store next hash /* (5) Store next hash
---------------------------------------------------------*/ ---------------------------------------------------------*/
copy(s.hash, h1) copy(s.hash, h1)
@ -177,7 +197,9 @@ func (s *T) generateResynchronisationResponse(y1 []byte, y2 []byte) error {
binary.BigEndian.PutUint32(timeIDBytes, timeID) binary.BigEndian.PutUint32(timeIDBytes, timeID)
hashedTimeID, err := scha.Hash(timeIDBytes, 1) hashedTimeID, err := scha.Hash(timeIDBytes, 1)
if err != nil { return err } if err != nil {
return err
}
/* (4) Process y1 = H ^ h(timeId) */ /* (4) Process y1 = H ^ h(timeId) */
copy(y1, xor.ByteArray(s.hash, hashedTimeID)) copy(y1, xor.ByteArray(s.hash, hashedTimeID))