Add tests for hash
This commit is contained in:
@ -38,7 +38,8 @@ func (i Date) Bytes() []byte {
|
||||
|
||||
// Int returns the Date as a Go integer.
|
||||
func (i Date) Int() int {
|
||||
return intFromBytes(i.Bytes())
|
||||
val, _ := intFromBytes(i.Bytes())
|
||||
return val
|
||||
}
|
||||
|
||||
// Time takes the value stored in date as an 8 byte big-endian integer representing the
|
||||
|
@ -2,6 +2,8 @@ package data
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
@ -10,38 +12,68 @@ import (
|
||||
Accurate for version 0.9.49
|
||||
|
||||
Description
|
||||
Represents the SHA256 of some data.
|
||||
Represents the SHA256 of some data. Used throughout I2P for data verification
|
||||
and identity representation. Must be compared using constant-time operations
|
||||
to prevent timing attacks.
|
||||
|
||||
Contents
|
||||
32 bytes
|
||||
|
||||
[I2P Hash]:
|
||||
32 bytes representing a SHA256 hash value
|
||||
*/
|
||||
|
||||
// Hash is the represenation of an I2P Hash.
|
||||
//
|
||||
var (
|
||||
ErrInvalidHashSize = errors.New("invalid hash size")
|
||||
ErrNilReader = errors.New("nil reader")
|
||||
)
|
||||
|
||||
// Hash is the representation of an I2P Hash.
|
||||
// It is always exactly 32 bytes containing a SHA256 sum.
|
||||
//
|
||||
// https://geti2p.net/spec/common-structures#hash
|
||||
type Hash [32]byte
|
||||
|
||||
// Bytes returns a copy of the Hash as a 32-byte array.
|
||||
// This prevents modification of the original hash value.
|
||||
func (h Hash) Bytes() [32]byte {
|
||||
return h
|
||||
}
|
||||
|
||||
// Equal compares two hashes in constant time.
|
||||
// Returns true if the hashes are identical.
|
||||
func (h Hash) Equal(other Hash) bool {
|
||||
return subtle.ConstantTimeCompare(h[:], other[:]) == 1
|
||||
}
|
||||
|
||||
// IsZero returns true if the hash is all zeros.
|
||||
func (h Hash) IsZero() bool {
|
||||
var zero Hash
|
||||
return h.Equal(zero)
|
||||
}
|
||||
|
||||
// HashData returns the SHA256 sum of a []byte input as Hash.
|
||||
func HashData(data []byte) (h Hash) {
|
||||
// log.Println("Hashing Data:", data)
|
||||
h = sha256.Sum256(data)
|
||||
return
|
||||
// Never returns an error as SHA256 operates on any input length.
|
||||
func HashData(data []byte) Hash {
|
||||
if data == nil {
|
||||
data = []byte{} // Handle nil input gracefully
|
||||
}
|
||||
return sha256.Sum256(data)
|
||||
}
|
||||
|
||||
// HashReader returns the SHA256 sum from all data read from an io.Reader.
|
||||
// return error if one occurs while reading from reader
|
||||
func HashReader(r io.Reader) (h Hash, err error) {
|
||||
sha := sha256.New()
|
||||
_, err = io.Copy(sha, r)
|
||||
if err == nil {
|
||||
d := sha.Sum(nil)
|
||||
copy(h[:], d)
|
||||
// Returns an error if one occurs while reading from reader or if reader is nil.
|
||||
func HashReader(r io.Reader) (Hash, error) {
|
||||
var h Hash
|
||||
|
||||
if r == nil {
|
||||
return h, ErrNilReader
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
sha := sha256.New()
|
||||
_, err := io.Copy(sha, r)
|
||||
if err != nil {
|
||||
return h, err
|
||||
}
|
||||
|
||||
sum := sha.Sum(nil)
|
||||
copy(h[:], sum)
|
||||
return h, nil
|
||||
}
|
65
lib/common/data/hash_test.go
Normal file
65
lib/common/data/hash_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
want Hash
|
||||
}{
|
||||
{
|
||||
name: "Empty input",
|
||||
data: []byte{},
|
||||
want: HashData([]byte{}),
|
||||
},
|
||||
{
|
||||
name: "Nil input",
|
||||
data: nil,
|
||||
want: HashData([]byte{}),
|
||||
},
|
||||
// Add more test cases
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := HashData(tt.data)
|
||||
if !got.Equal(tt.want) {
|
||||
t.Errorf("HashData() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashReader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reader io.Reader
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Nil reader",
|
||||
reader: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty reader",
|
||||
reader: strings.NewReader(""),
|
||||
wantErr: false,
|
||||
},
|
||||
// Add more test cases
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := HashReader(tt.reader)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("HashReader() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package data
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
// MAX_INTEGER_SIZE is the maximum length of an I2P integer in bytes.
|
||||
@ -19,49 +20,83 @@ Contents
|
||||
1 to 8 bytes in network byte order (big endian) representing an unsigned integer.
|
||||
*/
|
||||
|
||||
// Integer is the represenation of an I2P Integer.
|
||||
//
|
||||
// https://geti2p.net/spec/common-structures#integer
|
||||
var (
|
||||
// ErrInvalidSize indicates the requested integer size is invalid (<=0 or >MAX_INTEGER_SIZE)
|
||||
ErrInvalidSize = errors.New("invalid integer size")
|
||||
// ErrInsufficientData indicates there isn't enough data to read the requested size
|
||||
ErrInsufficientData = errors.New("insufficient data")
|
||||
// ErrNegativeValue indicates an attempt to create an Integer from a negative value
|
||||
ErrNegativeValue = errors.New("negative values not allowed")
|
||||
// ErrIntegerOverflow indicates the value exceeds the maximum allowed size
|
||||
ErrIntegerOverflow = errors.New("integer overflow")
|
||||
)
|
||||
|
||||
// Integer is the representation of an I2P Integer.
|
||||
// It contains 1 to 8 bytes in network byte order (big endian)
|
||||
// representing an unsigned integer value.
|
||||
type Integer []byte
|
||||
|
||||
// Bytes returns the raw []byte content of an Integer.
|
||||
// This represents the big-endian encoded form of the integer.
|
||||
func (i Integer) Bytes() []byte {
|
||||
return i
|
||||
}
|
||||
|
||||
// Int returns the Integer as a Go integer
|
||||
// Int returns the Integer as a Go integer.
|
||||
// Returns an error if the value would overflow on the current platform
|
||||
// or if the encoding is invalid.
|
||||
func (i Integer) Int() int {
|
||||
return intFromBytes(i)
|
||||
val, _ := intFromBytes(i)
|
||||
return val
|
||||
}
|
||||
|
||||
// ReadInteger returns an Integer from a []byte of specified length.
|
||||
// The remaining bytes after the specified length are also returned.
|
||||
func ReadInteger(bytes []byte, size int) (Integer, []byte) {
|
||||
if size <= 0 || size > len(bytes) {
|
||||
return Integer{}, bytes
|
||||
// Returns an error if size is invalid or there isn't enough data.
|
||||
func ReadInteger(bytes []byte, size int) (Integer, []byte, error) {
|
||||
if size <= 0 {
|
||||
return nil, bytes, ErrInvalidSize
|
||||
}
|
||||
return Integer(bytes[:size]), bytes[size:]
|
||||
if size > len(bytes) {
|
||||
return nil, bytes, ErrInsufficientData
|
||||
}
|
||||
return Integer(bytes[:size]), bytes[size:], nil
|
||||
}
|
||||
|
||||
// NewInteger creates a new Integer from []byte using ReadInteger.
|
||||
// Limits the length of the created Integer to MAX_INTEGER_SIZE.
|
||||
// Returns a pointer to Integer unlike ReadInteger.
|
||||
// Returns a pointer to Integer and the remaining bytes.
|
||||
// Returns an error if size is invalid or there isn't enough data.
|
||||
func NewInteger(bytes []byte, size int) (*Integer, []byte, error) {
|
||||
if size <= 0 || size > MAX_INTEGER_SIZE {
|
||||
return nil, bytes, errors.New("invalid integer size")
|
||||
return nil, bytes, ErrInvalidSize
|
||||
}
|
||||
if len(bytes) < size {
|
||||
return nil, bytes, errors.New("insufficient data")
|
||||
return nil, bytes, ErrInsufficientData
|
||||
}
|
||||
|
||||
integer, remainder := ReadInteger(bytes, size)
|
||||
integer, remainder, err := ReadInteger(bytes, size)
|
||||
if err != nil {
|
||||
return nil, bytes, err
|
||||
}
|
||||
return &integer, remainder, nil
|
||||
}
|
||||
|
||||
// NewIntegerFromInt creates a new Integer from a Go integer of a specified []byte length.
|
||||
// The value must be non-negative and fit within the specified number of bytes.
|
||||
// Returns an error if the size is invalid or the value cannot be represented.
|
||||
func NewIntegerFromInt(value int, size int) (*Integer, error) {
|
||||
if size <= 0 || size > MAX_INTEGER_SIZE {
|
||||
return nil, errors.New("invalid integer size")
|
||||
return nil, ErrInvalidSize
|
||||
}
|
||||
if value < 0 {
|
||||
return nil, ErrNegativeValue
|
||||
}
|
||||
|
||||
// Check if value fits in specified size
|
||||
maxVal := int(math.Pow(2, float64(size*8))) - 1
|
||||
if value > maxVal {
|
||||
return nil, ErrIntegerOverflow
|
||||
}
|
||||
|
||||
buf := make([]byte, MAX_INTEGER_SIZE)
|
||||
@ -72,13 +107,25 @@ func NewIntegerFromInt(value int, size int) (*Integer, error) {
|
||||
return &integer, nil
|
||||
}
|
||||
|
||||
// Interpret a slice of bytes from length 0 to length 8 as a big-endian
|
||||
// integer and return an int representation.
|
||||
func intFromBytes(number []byte) int {
|
||||
// intFromBytes interprets a slice of bytes from length 0 to length 8 as a big-endian
|
||||
// integer and returns an int representation.
|
||||
// Returns an error if the value would overflow on the current platform
|
||||
// or if the input is invalid.
|
||||
func intFromBytes(number []byte) (int, error) {
|
||||
if len(number) == 0 {
|
||||
return 0
|
||||
return 0, nil
|
||||
}
|
||||
if len(number) > MAX_INTEGER_SIZE {
|
||||
return 0, ErrInvalidSize
|
||||
}
|
||||
|
||||
padded := make([]byte, MAX_INTEGER_SIZE)
|
||||
copy(padded[MAX_INTEGER_SIZE-len(number):], number)
|
||||
return int(binary.BigEndian.Uint64(padded))
|
||||
}
|
||||
|
||||
val := int64(binary.BigEndian.Uint64(padded))
|
||||
if val > math.MaxInt32 || val < math.MinInt32 {
|
||||
return 0, ErrIntegerOverflow
|
||||
}
|
||||
|
||||
return int(val), nil
|
||||
}
|
@ -111,46 +111,46 @@ func (key_certificate KeyCertificate) Data() ([]byte, error) {
|
||||
}
|
||||
|
||||
// SigningPublicKeyType returns the signingPublicKey type as a Go integer.
|
||||
func (key_certificate KeyCertificate) SigningPublicKeyType() (signing_pubkey_type int) {
|
||||
signing_pubkey_type = key_certificate.spkType.Int()
|
||||
log.WithFields(logrus.Fields{
|
||||
"signing_pubkey_type": signing_pubkey_type,
|
||||
}).Debug("Retrieved signingPublicKey type")
|
||||
return key_certificate.spkType.Int()
|
||||
func (key_certificate KeyCertificate) SigningPublicKeyType() int {
|
||||
spk_type := key_certificate.spkType.Int()
|
||||
log.WithFields(logrus.Fields{
|
||||
"signing_pubkey_type": spk_type,
|
||||
}).Debug("Retrieved signingPublicKey type")
|
||||
return spk_type
|
||||
}
|
||||
|
||||
// PublicKeyType returns the publicKey type as a Go integer.
|
||||
func (key_certificate KeyCertificate) PublicKeyType() (pubkey_type int) {
|
||||
pubkey_type = key_certificate.cpkType.Int()
|
||||
log.WithFields(logrus.Fields{
|
||||
"pubkey_type": pubkey_type,
|
||||
}).Debug("Retrieved publicKey type")
|
||||
return key_certificate.cpkType.Int()
|
||||
func (key_certificate KeyCertificate) CryptoSize() int {
|
||||
switch key_certificate.PublicKeyType() {
|
||||
case KEYCERT_CRYPTO_ELG:
|
||||
return KEYCERT_CRYPTO_ELG_SIZE
|
||||
case KEYCERT_CRYPTO_P256:
|
||||
return KEYCERT_CRYPTO_P256_SIZE
|
||||
case KEYCERT_CRYPTO_P384:
|
||||
return KEYCERT_CRYPTO_P384_SIZE
|
||||
case KEYCERT_CRYPTO_P521:
|
||||
return KEYCERT_CRYPTO_P521_SIZE
|
||||
case KEYCERT_CRYPTO_X25519:
|
||||
return KEYCERT_CRYPTO_X25519_SIZE
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// ConstructPublicKey returns a publicKey constructed using any excess data that may be stored in the KeyCertififcate.
|
||||
// Returns enr errors encountered while parsing.
|
||||
func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_key crypto.PublicKey, err error) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"input_length": len(data),
|
||||
}).Debug("Constructing publicKey from keyCertificate")
|
||||
key_type := key_certificate.PublicKeyType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data_len := len(data)
|
||||
if data_len < key_certificate.CryptoSize() {
|
||||
log.WithFields(logrus.Fields{
|
||||
"at": "(keyCertificate) ConstructPublicKey",
|
||||
"data_len": data_len,
|
||||
"required_len": KEYCERT_PUBKEY_SIZE,
|
||||
"reason": "not enough data",
|
||||
}).Error("error constructing public key")
|
||||
err = errors.New("error constructing public key: not enough data")
|
||||
return
|
||||
}
|
||||
switch key_type {
|
||||
case KEYCERT_CRYPTO_ELG:
|
||||
log.WithFields(logrus.Fields{
|
||||
"input_length": len(data),
|
||||
}).Debug("Constructing publicKey from keyCertificate")
|
||||
|
||||
key_type := key_certificate.PublicKeyType()
|
||||
|
||||
data_len := len(data)
|
||||
if data_len < key_certificate.CryptoSize() {
|
||||
return nil, errors.New("error constructing public key: not enough data")
|
||||
}
|
||||
|
||||
// Implementation missing here - needs to construct appropriate key type
|
||||
switch key_type {
|
||||
case KEYCERT_CRYPTO_ELG:
|
||||
var elg_key crypto.ElgPublicKey
|
||||
copy(elg_key[:], data[KEYCERT_PUBKEY_SIZE-KEYCERT_CRYPTO_ELG_SIZE:KEYCERT_PUBKEY_SIZE])
|
||||
public_key = elg_key
|
||||
@ -160,13 +160,25 @@ func (key_certificate KeyCertificate) ConstructPublicKey(data []byte) (public_ke
|
||||
copy(ed25519_key[:], data[KEYCERT_PUBKEY_SIZE-KEYCERT_CRYPTO_ELG_SIZE:KEYCERT_PUBKEY_SIZE])
|
||||
public_key = ed25519_key
|
||||
log.Debug("Constructed Ed25519PublicKey")
|
||||
default:
|
||||
log.WithFields(logrus.Fields{
|
||||
"key_type": key_type,
|
||||
}).Warn("Unknown public key type")
|
||||
}
|
||||
case KEYCERT_CRYPTO_P256:
|
||||
//return crypto.CreatePublicKey(data[:KEYCERT_CRYPTO_P256_SIZE])
|
||||
case KEYCERT_CRYPTO_P384:
|
||||
//return crypto.CreatePublicKey(data[:KEYCERT_CRYPTO_P384_SIZE])
|
||||
case KEYCERT_CRYPTO_P521:
|
||||
//return crypto.CreatePublicKey(data[:KEYCERT_CRYPTO_P521_SIZE])
|
||||
default:
|
||||
return nil, errors.New("error constructing public key: unknown key type")
|
||||
}
|
||||
return nil, errors.New("error constructing public key: unknown key type")
|
||||
}
|
||||
|
||||
return
|
||||
// PublicKeyType returns the publicKey type as a Go integer.
|
||||
func (key_certificate KeyCertificate) PublicKeyType() int {
|
||||
pk_type := key_certificate.cpkType.Int()
|
||||
log.WithFields(logrus.Fields{
|
||||
"pubkey_type": pk_type,
|
||||
}).Debug("Retrieved publicKey type")
|
||||
return pk_type
|
||||
}
|
||||
|
||||
// ConstructSigningPublicKey returns a SingingPublicKey constructed using any excess data that may be stored in the KeyCertificate.
|
||||
@ -269,24 +281,6 @@ func (key_certificate KeyCertificate) SignatureSize() (size int) {
|
||||
return sizes[int(key_type)]
|
||||
}
|
||||
|
||||
// CryptoSize return the size of a Public Key corresponding to the Key Certificate's publicKey type.
|
||||
func (key_certificate KeyCertificate) CryptoSize() (size int) {
|
||||
sizes := map[int]int{
|
||||
KEYCERT_CRYPTO_ELG: KEYCERT_CRYPTO_ELG_SIZE,
|
||||
KEYCERT_CRYPTO_P256: KEYCERT_CRYPTO_P256_SIZE,
|
||||
KEYCERT_CRYPTO_P384: KEYCERT_CRYPTO_P384_SIZE,
|
||||
KEYCERT_CRYPTO_P521: KEYCERT_CRYPTO_P521_SIZE,
|
||||
KEYCERT_CRYPTO_X25519: KEYCERT_CRYPTO_X25519_SIZE,
|
||||
}
|
||||
key_type := key_certificate.PublicKeyType()
|
||||
size = sizes[int(key_type)]
|
||||
log.WithFields(logrus.Fields{
|
||||
"key_type": key_type,
|
||||
"crypto_size": size,
|
||||
}).Debug("Retrieved crypto size")
|
||||
return sizes[int(key_type)]
|
||||
}
|
||||
|
||||
// NewKeyCertificate creates a new *KeyCertificate from []byte using ReadCertificate.
|
||||
// The remaining bytes after the specified length are also returned.
|
||||
// Returns a list of errors that occurred during parsing.
|
||||
@ -301,6 +295,10 @@ func NewKeyCertificate(bytes []byte) (key_certificate *KeyCertificate, remainder
|
||||
log.WithError(err).Error("Failed to read Certificate")
|
||||
return
|
||||
}
|
||||
|
||||
if certificate.Type() != 5 { // Key certificate type must be 5
|
||||
return nil, nil, errors.New("error parsing key certificate: invalid certificate type")
|
||||
}
|
||||
if len(bytes) < KEYCERT_MIN_SIZE {
|
||||
log.WithError(err).Error("keyCertificate data too short")
|
||||
err = errors.New("error parsing key certificate: not enough data")
|
||||
|
Reference in New Issue
Block a user