Skip to content

Commit

Permalink
Merge pull request #7 from safing/feature/labeled-hash-formats
Browse files Browse the repository at this point in the history
Switch to explicit labeled hash formatting, update deps and docs
  • Loading branch information
ppacher authored Jul 7, 2021
2 parents b7b78c7 + 4538b98 commit f6e1345
Show file tree
Hide file tree
Showing 7 changed files with 501 additions and 32 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ vendor
cmd/cmd*
cmd/jess*
dist

# Custom dev deops
go.mod.*
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ Some of these properties may also be used multiple times. For example, you could

Should any of these properties _not_ be required, the user has to intentionally remove requirements.

### Recommended Suites

In order to reduce the possibility of making unsuggested combinations of tools, the primary interface to choose tools is to use a suite:

The command `jess list` shows the available suites:

```
Name/ID Provides Security Level Tools Notes
key_v1 CIRS 128 b/s HKDF(BLAKE2b-256), CHACHA20-POLY1305 recommended
pw_v1 CIRS 128 b/s SCRYPT-20, HKDF(BLAKE2b-256), CHACHA20-POLY1305 recommended
rcpt_v1 CIR 128 b/s ECDH-X25519, HKDF(BLAKE2b-256), CHACHA20-POLY1305 recommended
sign_v1 S 128 b/s Ed25519(BLAKE2b-256) recommended
v1 CIRS 128 b/s ECDH-X25519, Ed25519(BLAKE2b-256), HKDF(BLAKE2b-256), CHACHA20-POLY1305 recommended
w1 CIR 128 b/s ECDH-X25519, HKDF(BLAKE2b-256), CHACHA20-POLY1305 recommended
```

### Specification

There is some more detail in [SPEC.md](./SPEC.md).
Expand Down
7 changes: 3 additions & 4 deletions core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,9 @@ func setupEnvelopeAndTrustStore(t *testing.T, suite *Suite) (*Envelope, error) {
}

// create envelope baseline
e := &Envelope{
SuiteID: suite.ID,
suite: suite,
}
e := NewUnconfiguredEnvelope()
e.SuiteID = suite.ID
e.suite = suite

// check vars
keyDerPresent := false
Expand Down
13 changes: 7 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/safing/jess

go 1.14
go 1.15

require (
github.com/AlecAivazis/survey/v2 v2.0.7
Expand All @@ -10,11 +10,12 @@ require (
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-isatty v0.0.11
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/safing/portbase v0.4.1
github.com/mr-tron/base58 v1.2.0
github.com/safing/portbase v0.11.0
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v0.0.5
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056
github.com/tevino/abool v1.2.0
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
)
354 changes: 354 additions & 0 deletions go.sum

Large diffs are not rendered by default.

65 changes: 57 additions & 8 deletions lhash/labeledhash.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package lhash
import (
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"

"github.com/mr-tron/base58"

"github.com/safing/portbase/container"
)

Expand Down Expand Up @@ -60,11 +63,33 @@ func Load(labeledHash []byte) (*LabeledHash, error) {
}, nil
}

// LoadFromString loads a labeled hash from the given string.
func LoadFromString(labeledHash string) (*LabeledHash, error) {
raw, err := base64.RawURLEncoding.DecodeString(labeledHash)
// FromHex loads a labeled hash from the given hexadecimal string.
func FromHex(hexEncoded string) (*LabeledHash, error) {
raw, err := hex.DecodeString(hexEncoded)
if err != nil {
return nil, fmt.Errorf("failed to decode: %s", err)
return nil, fmt.Errorf("failed to decode hex: %s", err)
}

return Load(raw)
}

// FromBase64 loads a labeled hash from the given Base64 string using raw url
// encoding.
func FromBase64(base64Encoded string) (*LabeledHash, error) {
raw, err := base64.RawURLEncoding.DecodeString(base64Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base64: %s", err)
}

return Load(raw)
}

// FromBase58 loads a labeled hash from the given Base58 string using the BTC
// alphabet.
func FromBase58(base58Encoded string) (*LabeledHash, error) {
raw, err := base58.Decode(base58Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base58: %s", err)
}

return Load(raw)
Expand All @@ -78,13 +103,37 @@ func (lh *LabeledHash) Bytes() []byte {
return c.CompileData()
}

// String returns the string representation of the labeled hash (base64 raw url encoding).
func (lh *LabeledHash) String() string {
// Hex returns the hexadecimal string representation of the labeled hash.
func (lh *LabeledHash) Hex() string {
return hex.EncodeToString(lh.Bytes())
}

// Base64 returns the Base64 string representation of the labeled hash using
// raw url encoding.
func (lh *LabeledHash) Base64() string {
return base64.RawURLEncoding.EncodeToString(lh.Bytes())
}

// Matches returns true if the digest of the given data matches the hash.
func (lh *LabeledHash) Matches(data []byte) bool {
// Base58 returns the Base58 string representation of the labeled hash using
// the BTC alphabet.
func (lh *LabeledHash) Base58() string {
return base58.Encode(lh.Bytes())
}

// Equal returns true if the given labeled hash is equal.
// Equality is checked by comparing both the algorithm and the digest value.
func (lh *LabeledHash) Equal(other *LabeledHash) bool {
return lh.alg == other.alg &&
subtle.ConstantTimeCompare(lh.digest, other.digest) == 1
}

// MatchesString returns true if the digest of the given string matches the hash.
func (lh *LabeledHash) MatchesString(s string) bool {
return lh.MatchesData([]byte(s))
}

// MatchesData returns true if the digest of the given data matches the hash.
func (lh *LabeledHash) MatchesData(data []byte) bool {
hasher := lh.alg.new()
_, _ = hasher.Write(data) // never returns an error
defer hasher.Reset() // internal state may leak data if kept in memory
Expand Down
75 changes: 61 additions & 14 deletions lhash/labeledhash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
)

var (
testEmpty = []byte("")
testFox = []byte("The quick brown fox jumps over the lazy dog.")
testEmpty = []byte("")
testFox = "The quick brown fox jumps over the lazy dog."
testFoxData = []byte(testFox)
noMatch = "no match"
noMatchData = []byte(noMatch)
)

func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
Expand All @@ -32,33 +35,77 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
}

// test fox
lh = Digest(alg, testFox)
lh = Digest(alg, testFoxData)
if !bytes.Equal(lh.Bytes()[2:], foxBytes) {
t.Errorf("alg %d: test fox: digest mismatch, expected %+v, got %+v", alg, foxBytes, lh.Bytes()[2:])
}

// test matching
if !lh.Matches(testFox) {
// test matching with serialized/loaded labeled hash
if !lh.MatchesData(testFoxData) {
t.Errorf("alg %d: failed to match reference", alg)
}
if !lh.MatchesString(testFox) {
t.Errorf("alg %d: failed to match reference", alg)
}
if lh.Matches([]byte("nope")) {
if lh.MatchesData(noMatchData) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
if lh.MatchesString(noMatch) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}

// Test representations

// Hex
lhs := Digest(alg, testFoxData)
loaded, err := FromHex(lhs.Hex())
if err != nil {
t.Errorf("alg %d: failed to load from hex string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)

// serialize
lhs := Digest(alg, testFox).String()
// load
loaded, err := LoadFromString(lhs)
// Base64
lhs = Digest(alg, testFoxData)
loaded, err = FromBase64(lhs.Base64())
if err != nil {
t.Errorf("alg %d: failed to load from string: %s", alg, err)
t.Errorf("alg %d: failed to load from base64 string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)

// test matching with serialized/loaded labeled hash
if !loaded.Matches(testFox) {
// Base58
lhs = Digest(alg, testFoxData)
loaded, err = FromBase58(lhs.Base58())
if err != nil {
t.Errorf("alg %d: failed to load from base58 string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
}

func testFormat(t *testing.T, alg Algorithm, lhs, loaded *LabeledHash) {
noMatchLH := Digest(alg, noMatchData)

// Test equality.
if !lhs.Equal(loaded) {
t.Errorf("alg %d: equality test failed", alg)
}
if lhs.Equal(noMatchLH) {
t.Errorf("alg %d: non-equality test failed", alg)
}

// Test matching.
if !loaded.MatchesData(testFoxData) {
t.Errorf("alg %d: failed to match reference", alg)
}
if !loaded.MatchesString(testFox) {
t.Errorf("alg %d: failed to match reference", alg)
}
if loaded.Matches([]byte("nope")) {
if loaded.MatchesData(noMatchData) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
if loaded.MatchesString(noMatch) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
}
Expand Down

0 comments on commit f6e1345

Please sign in to comment.