Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sha1: Add collision resistent implementation
Implement the same SHA1 collision resistent algorithm used by both the Git CLI and libgit2. Only commits with input that match the unavoidable bit conditions will be further processed, which will result in different hashes. Which is the same behaviour experienced in the Git CLI and Libgit2. Users can override the hash algorithm used with: hash.RegisterHash(crypto.SHA1, sha1.New) xref links: libgit2/libgit2@2dfd129 git/git@28dc98e Signed-off-by: Paulo Gomes <pjbgf@linux.com>
- Loading branch information
Showing
10 changed files
with
184 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// package hash provides a way for managing the | ||
// underlying hash implementations used across go-git. | ||
package hash | ||
|
||
import ( | ||
"crypto" | ||
"fmt" | ||
"hash" | ||
|
||
"github.com/pjbgf/sha1cd/cgo" | ||
) | ||
|
||
// algos is a map of hash algorithms. | ||
var algos = map[crypto.Hash]func() hash.Hash{} | ||
|
||
func init() { | ||
reset() | ||
} | ||
|
||
// reset resets the default algos value. Can be used after running tests | ||
// that registers new algorithms to avoid side effects. | ||
func reset() { | ||
// For performance reasons the cgo version of the collision | ||
// detection algorithm is being used. | ||
algos[crypto.SHA1] = cgo.New | ||
} | ||
|
||
// RegisterHash allows for the hash algorithm used to be overriden. | ||
// This ensures the hash selection for go-git must be explicit, when | ||
// overriding the default value. | ||
func RegisterHash(h crypto.Hash, f func() hash.Hash) error { | ||
if f == nil { | ||
return fmt.Errorf("cannot register hash: f is nil") | ||
} | ||
|
||
switch h { | ||
case crypto.SHA1: | ||
algos[h] = f | ||
default: | ||
return fmt.Errorf("unsupported hash function: %v", h) | ||
} | ||
return nil | ||
} | ||
|
||
// Hash is the same as hash.Hash. This allows consumers | ||
// to not having to import this package alongside "hash". | ||
type Hash interface { | ||
hash.Hash | ||
} | ||
|
||
// New returns a new Hash for the given hash function. | ||
// It panics if the hash function is not registered. | ||
func New(h crypto.Hash) Hash { | ||
hh, ok := algos[h] | ||
if !ok { | ||
panic(fmt.Sprintf("hash algorithm not registered: %v", h)) | ||
} | ||
return hh() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package hash | ||
|
||
import ( | ||
"crypto" | ||
"crypto/sha1" | ||
"crypto/sha512" | ||
"encoding/hex" | ||
"hash" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestRegisterHash(t *testing.T) { | ||
// Reset default hash to avoid side effects. | ||
defer reset() | ||
|
||
tests := []struct { | ||
name string | ||
hash crypto.Hash | ||
new func() hash.Hash | ||
wantErr string | ||
}{ | ||
{ | ||
name: "sha1", | ||
hash: crypto.SHA1, | ||
new: sha1.New, | ||
}, | ||
{ | ||
name: "sha1", | ||
hash: crypto.SHA1, | ||
wantErr: "cannot register hash: f is nil", | ||
}, | ||
{ | ||
name: "sha512", | ||
hash: crypto.SHA512, | ||
new: sha512.New, | ||
wantErr: "unsupported hash function", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := RegisterHash(tt.hash, tt.new) | ||
if tt.wantErr == "" && err != nil { | ||
t.Errorf("unexpected error: %v", err) | ||
} else if tt.wantErr != "" && err == nil { | ||
t.Errorf("expected error: %v got: nil", tt.wantErr) | ||
} else if err != nil && !strings.Contains(err.Error(), tt.wantErr) { | ||
t.Errorf("expected error: %v got: %v", tt.wantErr, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// Verifies that the SHA1 implementation used is collision-resistant | ||
// by default. | ||
func TestSha1Collision(t *testing.T) { | ||
defer reset() | ||
|
||
tests := []struct { | ||
name string | ||
content string | ||
hash string | ||
before func() | ||
}{ | ||
{ | ||
name: "sha-mbles-1: with collision detection", | ||
content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", | ||
hash: "4f3d9be4a472c4dae83c6314aa6c36a064c1fd14", | ||
}, | ||
{ | ||
name: "sha-mbles-1: with default SHA1", | ||
content: "99040d047fe81780012000ff4b65792069732070617274206f66206120636f6c6c6973696f6e212049742773206120747261702179c61af0afcc054515d9274e7307624b1dc7fb23988bb8de8b575dba7b9eab31c1674b6d974378a827732ff5851c76a2e60772b5a47ce1eac40bb993c12d8c70e24a4f8d5fcdedc1b32c9cf19e31af2429759d42e4dfdb31719f587623ee552939b6dcdc459fca53553b70f87ede30a247ea3af6c759a2f20b320d760db64ff479084fd3ccb3cdd48362d96a9c430617caff6c36c637e53fde28417f626fec54ed7943a46e5f5730f2bb38fb1df6e0090010d00e24ad78bf92641993608e8d158a789f34c46fe1e6027f35a4cbfb827076c50eca0e8b7cca69bb2c2b790259f9bf9570dd8d4437a3115faff7c3cac09ad25266055c27104755178eaeff825a2caa2acfb5de64ce7641dc59a541a9fc9c756756e2e23dc713c8c24c9790aa6b0e38a7f55f14452a1ca2850ddd9562fd9a18ad42496aa97008f74672f68ef461eb88b09933d626b4f918749cc027fddd6c425fc4216835d0134d15285bab2cb784a4f7cbb4fb514d4bf0f6237cf00a9e9f132b9a066e6fd17f6c42987478586ff651af96747fb426b9872b9a88e4063f59bb334cc00650f83a80c42751b71974d300fc2819a2e8f1e32c1b51cb18e6bfc4db9baef675d4aaf5b1574a047f8f6dd2ec153a93412293974d928f88ced9363cfef97ce2e742bf34c96b8ef3875676fea5cca8e5f7dea0bab2413d4de00ee71ee01f162bdb6d1eafd925e6aebaae6a354ef17cf205a404fbdb12fc454d41fdd95cf2459664a2ad032d1da60a73264075d7f1e0d6c1403ae7a0d861df3fe5707188dd5e07d1589b9f8b6630553f8fc352b3e0c27da80bddba4c64020d", | ||
hash: "8ac60ba76f1999a1ab70223f225aefdc78d4ddc0", | ||
before: func() { | ||
RegisterHash(crypto.SHA1, sha1.New) | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if tt.before != nil { | ||
tt.before() | ||
} | ||
|
||
h := New(crypto.SHA1) | ||
data, err := hex.DecodeString(tt.content) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
h.Reset() | ||
h.Write(data) | ||
sum := h.Sum(nil) | ||
got := hex.EncodeToString(sum) | ||
|
||
if tt.hash != got { | ||
t.Errorf("\n got: %q\nwanted: %q", got, tt.hash) | ||
} | ||
}) | ||
} | ||
} |