Add tests for hash

This commit is contained in:
eyedeekay
2024-11-11 20:11:58 -05:00
parent 575e877115
commit dc9b969bcf
5 changed files with 242 additions and 99 deletions

View File

@ -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

View File

@ -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
}

View 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)
}
})
}
}

View File

@ -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
}

View File

@ -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")