diff --git a/go.mod b/go.mod index de799c1..b8f8917 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect @@ -38,6 +39,7 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index a709cde..3d87649 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -81,6 +83,10 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/hashtools/blake2.go b/hashtools/blake2.go index a2262cd..d9a9c19 100644 --- a/hashtools/blake2.go +++ b/hashtools/blake2.go @@ -18,7 +18,8 @@ func init() { Register(blake2bBase.With(&HashTool{ Name: "BLAKE2s-256", - Hash: crypto.BLAKE2s_256, + NewHash: crypto.BLAKE2s_256.New, + CryptoHashID: crypto.BLAKE2b_256, DigestSize: crypto.BLAKE2s_256.Size(), BlockSize: crypto.BLAKE2s_256.New().BlockSize(), SecurityLevel: 128, @@ -27,7 +28,8 @@ func init() { })) Register(blake2bBase.With(&HashTool{ Name: "BLAKE2b-256", - Hash: crypto.BLAKE2b_256, + NewHash: crypto.BLAKE2b_256.New, + CryptoHashID: crypto.BLAKE2b_256, DigestSize: crypto.BLAKE2b_256.Size(), BlockSize: crypto.BLAKE2b_256.New().BlockSize(), SecurityLevel: 128, @@ -35,7 +37,8 @@ func init() { })) Register(blake2bBase.With(&HashTool{ Name: "BLAKE2b-384", - Hash: crypto.BLAKE2b_384, + NewHash: crypto.BLAKE2b_384.New, + CryptoHashID: crypto.BLAKE2b_384, DigestSize: crypto.BLAKE2b_384.Size(), BlockSize: crypto.BLAKE2b_384.New().BlockSize(), SecurityLevel: 192, @@ -43,7 +46,8 @@ func init() { })) Register(blake2bBase.With(&HashTool{ Name: "BLAKE2b-512", - Hash: crypto.BLAKE2b_512, + NewHash: crypto.BLAKE2b_512.New, + CryptoHashID: crypto.BLAKE2b_512, DigestSize: crypto.BLAKE2b_512.Size(), BlockSize: crypto.BLAKE2b_512.New().BlockSize(), SecurityLevel: 256, diff --git a/hashtools/blake3.go b/hashtools/blake3.go new file mode 100644 index 0000000..2cec861 --- /dev/null +++ b/hashtools/blake3.go @@ -0,0 +1,26 @@ +package hashtools + +import ( + "hash" + + "github.com/zeebo/blake3" + + "github.com/safing/jess/lhash" +) + +func init() { + Register(&HashTool{ + Name: "BLAKE3", + NewHash: newBlake3, + DigestSize: newBlake3().Size(), + BlockSize: newBlake3().BlockSize(), + SecurityLevel: 128, + Comment: "cryptographic hash function based on Bao and BLAKE2", + Author: "Jean-Philippe Aumasson et al., 2020", + labeledAlg: lhash.BLAKE3, + }) +} + +func newBlake3() hash.Hash { + return blake3.New() +} diff --git a/hashtools/hashtool.go b/hashtools/hashtool.go index a86fff2..18a3479 100644 --- a/hashtools/hashtool.go +++ b/hashtools/hashtool.go @@ -10,7 +10,9 @@ import ( // HashTool holds generic information about a hash tool. type HashTool struct { Name string - Hash crypto.Hash + + NewHash func() hash.Hash + CryptoHashID crypto.Hash DigestSize int // in bytes BlockSize int // in bytes @@ -24,7 +26,7 @@ type HashTool struct { // New returns a new hash.Hash instance of the hash tool. func (ht *HashTool) New() hash.Hash { - return ht.Hash.New() + return ht.NewHash() } // With uses the original HashTool as a template for a new HashTool and returns the new HashTool. @@ -32,8 +34,11 @@ func (ht *HashTool) With(changes *HashTool) *HashTool { if changes.Name == "" { changes.Name = ht.Name } - if changes.Hash == 0 { - changes.Hash = ht.Hash + if changes.NewHash == nil { + changes.NewHash = ht.NewHash + } + if changes.CryptoHashID == 0 { + changes.CryptoHashID = ht.CryptoHashID } if changes.DigestSize == 0 { changes.DigestSize = ht.DigestSize diff --git a/hashtools/sha.go b/hashtools/sha.go index ea16311..b7d1834 100644 --- a/hashtools/sha.go +++ b/hashtools/sha.go @@ -20,7 +20,8 @@ func init() { } Register(sha2Base.With(&HashTool{ Name: "SHA2-224", - Hash: crypto.SHA224, + NewHash: crypto.SHA224.New, + CryptoHashID: crypto.SHA224, DigestSize: crypto.SHA224.Size(), BlockSize: crypto.SHA224.New().BlockSize(), SecurityLevel: 112, @@ -29,7 +30,8 @@ func init() { })) Register(sha2Base.With(&HashTool{ Name: "SHA2-256", - Hash: crypto.SHA256, + NewHash: crypto.SHA256.New, + CryptoHashID: crypto.SHA256, DigestSize: crypto.SHA256.Size(), BlockSize: crypto.SHA256.New().BlockSize(), SecurityLevel: 128, @@ -37,7 +39,8 @@ func init() { })) Register(sha2Base.With(&HashTool{ Name: "SHA2-384", - Hash: crypto.SHA384, + NewHash: crypto.SHA384.New, + CryptoHashID: crypto.SHA384, DigestSize: crypto.SHA384.Size(), BlockSize: crypto.SHA384.New().BlockSize(), SecurityLevel: 192, @@ -45,7 +48,8 @@ func init() { })) Register(sha2Base.With(&HashTool{ Name: "SHA2-512", - Hash: crypto.SHA512, + NewHash: crypto.SHA512.New, + CryptoHashID: crypto.SHA512, DigestSize: crypto.SHA512.Size(), BlockSize: crypto.SHA512.New().BlockSize(), SecurityLevel: 256, @@ -53,7 +57,8 @@ func init() { })) Register(sha2Base.With(&HashTool{ Name: "SHA2-512-224", - Hash: crypto.SHA512_224, + NewHash: crypto.SHA512_224.New, + CryptoHashID: crypto.SHA512_224, DigestSize: crypto.SHA512_224.Size(), BlockSize: crypto.SHA512_224.New().BlockSize(), SecurityLevel: 112, @@ -61,7 +66,8 @@ func init() { })) Register(sha2Base.With(&HashTool{ Name: "SHA2-512-256", - Hash: crypto.SHA512_256, + NewHash: crypto.SHA512_256.New, + CryptoHashID: crypto.SHA512_256, DigestSize: crypto.SHA512_256.Size(), BlockSize: crypto.SHA512_256.New().BlockSize(), SecurityLevel: 128, @@ -75,7 +81,8 @@ func init() { } Register(sha3Base.With(&HashTool{ Name: "SHA3-224", - Hash: crypto.SHA3_224, + NewHash: crypto.SHA3_224.New, + CryptoHashID: crypto.SHA3_224, DigestSize: crypto.SHA3_224.Size(), BlockSize: crypto.SHA3_224.New().BlockSize(), SecurityLevel: 112, @@ -83,7 +90,8 @@ func init() { })) Register(sha3Base.With(&HashTool{ Name: "SHA3-256", - Hash: crypto.SHA3_256, + NewHash: crypto.SHA3_256.New, + CryptoHashID: crypto.SHA3_256, DigestSize: crypto.SHA3_256.Size(), BlockSize: crypto.SHA3_256.New().BlockSize(), SecurityLevel: 128, @@ -91,7 +99,8 @@ func init() { })) Register(sha3Base.With(&HashTool{ Name: "SHA3-384", - Hash: crypto.SHA3_384, + NewHash: crypto.SHA3_384.New, + CryptoHashID: crypto.SHA3_384, DigestSize: crypto.SHA3_384.Size(), BlockSize: crypto.SHA3_384.New().BlockSize(), SecurityLevel: 192, @@ -99,7 +108,8 @@ func init() { })) Register(sha3Base.With(&HashTool{ Name: "SHA3-512", - Hash: crypto.SHA3_512, + NewHash: crypto.SHA3_512.New, + CryptoHashID: crypto.SHA3_512, DigestSize: crypto.SHA3_512.Size(), BlockSize: crypto.SHA3_512.New().BlockSize(), SecurityLevel: 256, diff --git a/hashtools/tools_test.go b/hashtools/tools_test.go index 4884104..62ac492 100644 --- a/hashtools/tools_test.go +++ b/hashtools/tools_test.go @@ -1,16 +1,18 @@ package hashtools -import "testing" +import ( + "encoding/hex" + "testing" +) func TestAll(t *testing.T) { t.Parallel() - testData := []byte("The quick brown fox jumps over the lazy dog. ") + testData := []byte("The quick brown fox jumps over the lazy dog.") all := AsList() for _, hashTool := range all { - - // take detour in getting hash.Hash for testing + // Test hash usage. hash, err := New(hashTool.Name) if err != nil { t.Fatalf("failed to get HashTool %s", hashTool.Name) @@ -30,5 +32,97 @@ func TestAll(t *testing.T) { t.Errorf("hashTool %s is broken or reports invalid digest size. Expected %d, got %d.", hashTool.Name, hashTool.DigestSize, len(sum)) } + // Check hash outputs. + expectedOutputs, ok := testOutputs[hashTool.Name] + if !ok { + t.Errorf("no test outputs available for %s", hashTool.Name) + continue + } + + // Test empty string. + hash.Reset() + _, _ = hash.Write(testInputEmpty) + hexSum := hex.EncodeToString(hash.Sum(nil)) + if hexSum != expectedOutputs[0] { + t.Errorf("hash tool %s: test empty: digest mismatch, expected %+v, got %+v", + hashTool.Name, expectedOutputs[0], hexSum) + } + + // Test fox string. + hash.Reset() + _, _ = hash.Write(testInputFox) + hexSum = hex.EncodeToString(hash.Sum(nil)) + if hexSum != expectedOutputs[1] { + t.Errorf("hash tool %s: test empty: digest mismatch, expected %+v, got %+v", + hashTool.Name, expectedOutputs[1], hexSum) + } } } + +var ( + testInputEmpty = []byte("") + testInputFox = []byte("The quick brown fox jumps over the lazy dog.") +) + +var testOutputs = map[string][2]string{ + "SHA2-224": { + "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", + "619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c", + }, + "SHA2-256": { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c", + }, + "SHA2-384": { + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7", + }, + "SHA2-512": { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", + "91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed", + }, + "SHA2-512-224": { + "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4", + "6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de", + }, + "SHA2-512-256": { + "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a", + "1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3", + }, + "SHA3-224": { + "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", + "2d0708903833afabdd232a20201176e8b58c5be8a6fe74265ac54db0", + }, + "SHA3-256": { + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + "a80f839cd4f83f6c3dafc87feae470045e4eb0d366397d5c6ce34ba1739f734d", + }, + "SHA3-384": { + "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", + "1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9", + }, + "SHA3-512": { + "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", + "18f4f4bd419603f95538837003d9d254c26c23765565162247483f65c50303597bc9ce4d289f21d1c2f1f458828e33dc442100331b35e7eb031b5d38ba6460f8", + }, + "BLAKE2s-256": { + "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", + "95bca6e1b761dca1323505cc629949a0e03edf11633cc7935bd8b56f393afcf2", + }, + "BLAKE2b-256": { + "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8", + "69d7d3b0afba81826d27024c17f7f183659ed0812cf27b382eaef9fdc29b5712", + }, + "BLAKE2b-384": { + "b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100", + "16d65de1a3caf1c26247234c39af636284c7e19ca448c0de788272081410778852c94d9cef6b939968d4f872c7f78337", + }, + "BLAKE2b-512": { + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", + "87af9dc4afe5651b7aa89124b905fd214bf17c79af58610db86a0fb1e0194622a4e9d8e395b352223a8183b0d421c0994b98286cbf8c68a495902e0fe6e2bda2", + }, + "BLAKE3": { + "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", + "4c9bd68d7f0baa2e167cef98295eb1ec99a3ec8f0656b33dbae943b387f31d5d", + }, +} diff --git a/lhash/algs.go b/lhash/algs.go index e4a15cc..50c6c35 100644 --- a/lhash/algs.go +++ b/lhash/algs.go @@ -18,6 +18,8 @@ import ( // Register BLAKE2 in Go's internal registry. _ "golang.org/x/crypto/blake2b" _ "golang.org/x/crypto/blake2s" + + "github.com/zeebo/blake3" ) // Algorithm is an identifier for a hash function. @@ -41,6 +43,8 @@ const ( BLAKE2b_256 Algorithm = 25 BLAKE2b_384 Algorithm = 26 BLAKE2b_512 Algorithm = 27 + + BLAKE3 Algorithm = 32 ) func (a Algorithm) new() hash.Hash { @@ -70,7 +74,7 @@ func (a Algorithm) new() hash.Hash { case SHA3_512: return crypto.SHA3_512.New() - // BLAKE2 + // BLAKE2 case BLAKE2s_256: return crypto.BLAKE2s_256.New() case BLAKE2b_256: @@ -80,6 +84,10 @@ func (a Algorithm) new() hash.Hash { case BLAKE2b_512: return crypto.BLAKE2b_512.New() + // BLAKE3 + case BLAKE3: + return blake3.New() + default: return nil } @@ -122,6 +130,10 @@ func (a Algorithm) String() string { case BLAKE2b_512: return "BLAKE2b_512" + // BLAKE3 + case BLAKE3: + return "BLAKE3" + default: return "unknown" } diff --git a/lhash/labeledhash_test.go b/lhash/labeledhash_test.go index 5facd3c..ee9079a 100644 --- a/lhash/labeledhash_test.go +++ b/lhash/labeledhash_test.go @@ -32,27 +32,29 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) { // test empty lh := Digest(alg, testEmpty) if !bytes.Equal(lh.Bytes()[2:], emptyBytes) { - t.Errorf("alg %d: test empty: digest mismatch, expected %+v, got %+v", alg, emptyBytes, lh.Bytes()[2:]) + t.Errorf("alg %s: test empty: digest mismatch, expected %+v, got %+v", + alg, hex.EncodeToString(emptyBytes), hex.EncodeToString(lh.Bytes()[2:])) } // test fox 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:]) + t.Errorf("alg %s: test fox: digest mismatch, expected %+v, got %+v", + alg, hex.EncodeToString(foxBytes), hex.EncodeToString(lh.Bytes()[2:])) } // test matching with serialized/loaded labeled hash if !lh.Matches(testFoxData) { - t.Errorf("alg %d: failed to match reference", alg) + t.Errorf("alg %s: failed to match reference", alg) } if !lh.MatchesString(testFox) { - t.Errorf("alg %d: failed to match reference", alg) + t.Errorf("alg %s: failed to match reference", alg) } if lh.Matches(noMatchData) { - t.Errorf("alg %d: failed to non-match garbage", alg) + t.Errorf("alg %s: failed to non-match garbage", alg) } if lh.MatchesString(noMatch) { - t.Errorf("alg %d: failed to non-match garbage", alg) + t.Errorf("alg %s: failed to non-match garbage", alg) } // Test representations @@ -61,7 +63,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) { 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) + t.Errorf("alg %s: failed to load from hex string: %s", alg, err) return } testFormat(t, alg, lhs, loaded) @@ -70,7 +72,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) { lhs = Digest(alg, testFoxData) loaded, err = FromBase64(lhs.Base64()) if err != nil { - t.Errorf("alg %d: failed to load from base64 string: %s", alg, err) + t.Errorf("alg %s: failed to load from base64 string: %s", alg, err) return } testFormat(t, alg, lhs, loaded) @@ -79,7 +81,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) { 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) + t.Errorf("alg %s: failed to load from base58 string: %s", alg, err) return } testFormat(t, alg, lhs, loaded) @@ -92,47 +94,88 @@ func testFormat(t *testing.T, alg Algorithm, lhs, loaded *LabeledHash) { // Test equality. if !lhs.Equal(loaded) { - t.Errorf("alg %d: equality test failed", alg) + t.Errorf("alg %s: equality test failed", alg) } if lhs.Equal(noMatchLH) { - t.Errorf("alg %d: non-equality test failed", alg) + t.Errorf("alg %s: non-equality test failed", alg) } // Test matching. if !loaded.Matches(testFoxData) { - t.Errorf("alg %d: failed to match reference", alg) + t.Errorf("alg %s: failed to match reference", alg) } if !loaded.MatchesString(testFox) { - t.Errorf("alg %d: failed to match reference", alg) + t.Errorf("alg %s: failed to match reference", alg) } if loaded.Matches(noMatchData) { - t.Errorf("alg %d: failed to non-match garbage", alg) + t.Errorf("alg %s: failed to non-match garbage", alg) } if loaded.MatchesString(noMatch) { - t.Errorf("alg %d: failed to non-match garbage", alg) + t.Errorf("alg %s: failed to non-match garbage", alg) } } func TestHash(t *testing.T) { t.Parallel() + testAlgorithm(t, SHA2_224, + "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", + "619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c", + ) testAlgorithm(t, SHA2_256, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c", ) - + testAlgorithm(t, SHA2_384, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7", + ) testAlgorithm(t, SHA2_512, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", "91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed", ) - + testAlgorithm(t, SHA2_512_224, + "6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4", + "6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de", + ) + testAlgorithm(t, SHA2_512_256, + "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a", + "1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3", + ) + testAlgorithm(t, SHA3_224, + "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", + "2d0708903833afabdd232a20201176e8b58c5be8a6fe74265ac54db0", + ) + testAlgorithm(t, SHA3_256, + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + "a80f839cd4f83f6c3dafc87feae470045e4eb0d366397d5c6ce34ba1739f734d", + ) + testAlgorithm(t, SHA3_384, + "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", + "1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9", + ) testAlgorithm(t, SHA3_512, "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", "18f4f4bd419603f95538837003d9d254c26c23765565162247483f65c50303597bc9ce4d289f21d1c2f1f458828e33dc442100331b35e7eb031b5d38ba6460f8", ) - + testAlgorithm(t, BLAKE2s_256, + "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", + "95bca6e1b761dca1323505cc629949a0e03edf11633cc7935bd8b56f393afcf2", + ) + testAlgorithm(t, BLAKE2b_256, + "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8", + "69d7d3b0afba81826d27024c17f7f183659ed0812cf27b382eaef9fdc29b5712", + ) + testAlgorithm(t, BLAKE2b_384, + "b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100", + "16d65de1a3caf1c26247234c39af636284c7e19ca448c0de788272081410778852c94d9cef6b939968d4f872c7f78337", + ) testAlgorithm(t, BLAKE2b_512, "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce", "87af9dc4afe5651b7aa89124b905fd214bf17c79af58610db86a0fb1e0194622a4e9d8e395b352223a8183b0d421c0994b98286cbf8c68a495902e0fe6e2bda2", ) + testAlgorithm(t, BLAKE3, + "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", + "4c9bd68d7f0baa2e167cef98295eb1ec99a3ec8f0656b33dbae943b387f31d5d", + ) } diff --git a/suites.go b/suites.go index c9b5915..dcdbb2b 100644 --- a/suites.go +++ b/suites.go @@ -1,72 +1,7 @@ package jess +// Currently Recommended Suites. var ( - // Suite Lists. - suitesMap = make(map[string]*Suite) - suitesList []*Suite - - // Suite Definitions. - - // SuiteKeyV1 is a cipher suite for encryption with a key. - SuiteKeyV1 = registerSuite(&Suite{ - ID: "key_v1", - Tools: []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, - Provides: NewRequirements(), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - // SuitePasswordV1 is a cipher suite for encryption with a password. - SuitePasswordV1 = registerSuite(&Suite{ - ID: "pw_v1", - Tools: []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, - Provides: NewRequirements(), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - // SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source. - SuiteRcptOnlyV1 = registerSuite(&Suite{ - ID: "rcpt_v1", - Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, - Provides: NewRequirements().Remove(SenderAuthentication), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - // SuiteSignV1 is a cipher suite for signing (no encryption). - SuiteSignV1 = registerSuite(&Suite{ - ID: "sign_v1", - Tools: []string{"Ed25519(BLAKE2b-256)"}, - Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - // SuiteSignFileV1 is a cipher suite for signing files (no encryption). - // SHA2_256 is chosen for better compatibility with other tool sets and workflows. - SuiteSignFileV1 = registerSuite(&Suite{ - ID: "signfile_v1", - Tools: []string{"Ed25519(SHA2-256)"}, - Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - // SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing. - SuiteCompleteV1 = registerSuite(&Suite{ - ID: "v1", - Tools: []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, - Provides: NewRequirements(), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - // SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client. - SuiteWireV1 = registerSuite(&Suite{ - ID: "w1", - Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, - Provides: NewRequirements().Remove(SenderAuthentication), - SecurityLevel: 128, - Status: SuiteStatusRecommended, - }) - - // Currently Recommended Suites. - // SuiteKey is a cipher suite for encryption with a key. SuiteKey = SuiteKeyV1 // SuitePassword is a cipher suite for encryption with a password. @@ -83,6 +18,12 @@ var ( SuiteWire = SuiteWireV1 ) +// Suite Lists. +var ( + suitesMap = make(map[string]*Suite) + suitesList []*Suite +) + func registerSuite(suite *Suite) (suiteID string) { // add if not exists _, ok := suitesMap[suite.ID] diff --git a/suites_v1.go b/suites_v1.go new file mode 100644 index 0000000..455246e --- /dev/null +++ b/suites_v1.go @@ -0,0 +1,61 @@ +package jess //nolint:dupl + +var ( + // SuiteKeyV1 is a cipher suite for encryption with a key. + SuiteKeyV1 = registerSuite(&Suite{ + ID: "key_v1", + Tools: []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, + Provides: NewRequirements(), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) + // SuitePasswordV1 is a cipher suite for encryption with a password. + SuitePasswordV1 = registerSuite(&Suite{ + ID: "pw_v1", + Tools: []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, + Provides: NewRequirements(), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) + // SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source. + SuiteRcptOnlyV1 = registerSuite(&Suite{ + ID: "rcpt_v1", + Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, + Provides: NewRequirements().Remove(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) + // SuiteSignV1 is a cipher suite for signing (no encryption). + SuiteSignV1 = registerSuite(&Suite{ + ID: "sign_v1", + Tools: []string{"Ed25519(BLAKE2b-256)"}, + Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) + // SuiteSignFileV1 is a cipher suite for signing files (no encryption). + // SHA2_256 is chosen for better compatibility with other tool sets and workflows. + SuiteSignFileV1 = registerSuite(&Suite{ + ID: "signfile_v1", + Tools: []string{"Ed25519(SHA2-256)"}, + Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) + // SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing. + SuiteCompleteV1 = registerSuite(&Suite{ + ID: "v1", + Tools: []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, + Provides: NewRequirements(), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) + // SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client. + SuiteWireV1 = registerSuite(&Suite{ + ID: "w1", + Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"}, + Provides: NewRequirements().Remove(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusRecommended, + }) +) diff --git a/suites_v2.go b/suites_v2.go new file mode 100644 index 0000000..2bcf907 --- /dev/null +++ b/suites_v2.go @@ -0,0 +1,61 @@ +package jess //nolint:dupl + +var ( + // SuiteKeyV2 is a cipher suite for encryption with a key. + SuiteKeyV2 = registerSuite(&Suite{ + ID: "key_v2", + Tools: []string{"BLAKE3-KDF", "CHACHA20-POLY1305"}, + Provides: NewRequirements(), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) + // SuitePasswordV2 is a cipher suite for encryption with a password. + SuitePasswordV2 = registerSuite(&Suite{ + ID: "pw_v2", + Tools: []string{"SCRYPT-20", "BLAKE3-KDF", "CHACHA20-POLY1305"}, + Provides: NewRequirements(), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) + // SuiteRcptOnlyV2 is a cipher suite for encrypting for someone, but without verifying the sender/source. + SuiteRcptOnlyV2 = registerSuite(&Suite{ + ID: "rcpt_v2", + Tools: []string{"ECDH-X25519", "BLAKE3-KDF", "CHACHA20-POLY1305"}, + Provides: NewRequirements().Remove(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) + // SuiteSignV2 is a cipher suite for signing (no encryption). + SuiteSignV2 = registerSuite(&Suite{ + ID: "sign_v2", + Tools: []string{"Ed25519(BLAKE3)"}, + Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) + // SuiteSignFileV2 is a cipher suite for signing files (no encryption). + // SHA2_256 is chosen for better compatibility with other tool sets and workflows. + SuiteSignFileV2 = registerSuite(&Suite{ + ID: "signfile_v2", + Tools: []string{"Ed25519(BLAKE3)"}, + Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) + // SuiteCompleteV2 is a cipher suite for both encrypting for someone and signing. + SuiteCompleteV2 = registerSuite(&Suite{ + ID: "v2", + Tools: []string{"ECDH-X25519", "Ed25519(BLAKE3)", "BLAKE3-KDF", "CHACHA20-POLY1305"}, + Provides: NewRequirements(), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) + // SuiteWireV2 is a cipher suite for network communication, including authentication of the server, but not the client. + SuiteWireV2 = registerSuite(&Suite{ + ID: "w2", + Tools: []string{"ECDH-X25519", "BLAKE3-KDF", "CHACHA20-POLY1305"}, + Provides: NewRequirements().Remove(SenderAuthentication), + SecurityLevel: 128, + Status: SuiteStatusPermitted, + }) +) diff --git a/tools/all/all.go b/tools/all/all.go index b1d0080..25c0deb 100644 --- a/tools/all/all.go +++ b/tools/all/all.go @@ -3,6 +3,7 @@ package all import ( // Import all tool subpackages. + _ "github.com/safing/jess/tools/blake3" _ "github.com/safing/jess/tools/ecdh" _ "github.com/safing/jess/tools/gostdlib" ) diff --git a/tools/blake3/kdf.go b/tools/blake3/kdf.go new file mode 100644 index 0000000..f95a8af --- /dev/null +++ b/tools/blake3/kdf.go @@ -0,0 +1,68 @@ +package blake3 + +import ( + "errors" + "fmt" + "io" + + "github.com/zeebo/blake3" + + "github.com/safing/jess/tools" +) + +func init() { + tools.Register(&tools.Tool{ + Info: &tools.ToolInfo{ + Name: "BLAKE3-KDF", + Purpose: tools.PurposeKeyDerivation, + SecurityLevel: 128, + Comment: "cryptographic hash function based on Bao and BLAKE2", + Author: "Jean-Philippe Aumasson et al., 2020", + }, + Factory: func() tools.ToolLogic { return &KDF{} }, + }) +} + +// KDF implements the cryptographic interface for BLAKE3 key derivation. +type KDF struct { + tools.ToolLogicBase + reader io.Reader +} + +// InitKeyDerivation implements the ToolLogic interface. +func (keyder *KDF) InitKeyDerivation(nonce []byte, material ...[]byte) error { + // Check params. + if len(material) < 1 || len(material[0]) == 0 || len(nonce) == 0 { + return errors.New("must supply at least one key and a nonce as key material") + } + + // Setup KDF. + // Use nonce as kdf context. + h := blake3.NewDeriveKey(string(nonce)) + // Then add all the key material. + for _, m := range material { + _, _ = h.Write(m) + } + // Get key reader. + keyder.reader = h.Digest() + + return nil +} + +// DeriveKey implements the ToolLogic interface. +func (keyder *KDF) DeriveKey(size int) ([]byte, error) { + key := make([]byte, size) + return key, keyder.DeriveKeyWriteTo(key) +} + +// DeriveKeyWriteTo implements the ToolLogic interface. +func (keyder *KDF) DeriveKeyWriteTo(newKey []byte) error { + n, err := io.ReadFull(keyder.reader, newKey) + if err != nil { + return fmt.Errorf("failed to generate key: %w", err) + } + if n != len(newKey) { + return errors.New("failed to generate key: EOF") + } + return nil +} diff --git a/tools/gostdlib/rsa-pss.go b/tools/gostdlib/rsa-pss.go index 5038f09..a603f05 100644 --- a/tools/gostdlib/rsa-pss.go +++ b/tools/gostdlib/rsa-pss.go @@ -2,6 +2,7 @@ package gostdlib import ( "crypto/rsa" + "errors" "github.com/safing/jess/tools" ) @@ -38,11 +39,14 @@ func (pss *RsaPSS) Sign(data, associatedData []byte, signet tools.SignetInt) ([] if err != nil { return nil, err } + if pss.HashTool().CryptoHashID == 0 { + return nil, errors.New("tool PSS is only compatible with Golang crypto.Hash hash functions") + } return rsa.SignPSS( pss.Helper().Random(), rsaPrivKey, - pss.HashTool().Hash, + pss.HashTool().CryptoHashID, hashsum, nil, // *rsa.PSSOptions ) @@ -59,10 +63,13 @@ func (pss *RsaPSS) Verify(data, associatedData, signature []byte, signet tools.S if err != nil { return err } + if pss.HashTool().CryptoHashID == 0 { + return errors.New("tool PSS is only compatible with Golang crypto.Hash hash functions") + } return rsa.VerifyPSS( rsaPubKey, - pss.HashTool().Hash, + pss.HashTool().CryptoHashID, hashsum, signature, nil, // *rsa.PSSOptions