mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 23:41:37 +00:00
No commit suggestions generated
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
// Package crypto provides comprehensive security tests for cryptographic implementations
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/argon2"
|
||||
ecdsaPkg "github.com/sonr-io/sonr/crypto/ecdsa"
|
||||
"github.com/sonr-io/sonr/crypto/password"
|
||||
"github.com/sonr-io/sonr/crypto/wasm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestTimingAttackResistance verifies constant-time operations
|
||||
func TestTimingAttackResistance(t *testing.T) {
|
||||
// Test Argon2 constant-time comparison
|
||||
kdf := argon2.New(argon2.LightConfig())
|
||||
password := []byte("correct-password")
|
||||
hash, err := kdf.HashPassword(password)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Measure timing for correct vs incorrect passwords
|
||||
correctTimes := make([]time.Duration, 100)
|
||||
incorrectTimes := make([]time.Duration, 100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
// Time correct password
|
||||
start := time.Now()
|
||||
_, _ = argon2.VerifyPassword(password, hash)
|
||||
correctTimes[i] = time.Since(start)
|
||||
|
||||
// Time incorrect password
|
||||
wrongPassword := []byte("wrong-password-x")
|
||||
start = time.Now()
|
||||
_, _ = argon2.VerifyPassword(wrongPassword, hash)
|
||||
incorrectTimes[i] = time.Since(start)
|
||||
}
|
||||
|
||||
// Calculate average times
|
||||
var correctAvg, incorrectAvg time.Duration
|
||||
for i := 0; i < 100; i++ {
|
||||
correctAvg += correctTimes[i]
|
||||
incorrectAvg += incorrectTimes[i]
|
||||
}
|
||||
correctAvg /= 100
|
||||
incorrectAvg /= 100
|
||||
|
||||
// Times should be similar (within 20% variance)
|
||||
diff := correctAvg - incorrectAvg
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
maxDiff := correctAvg / 5 // 20% threshold
|
||||
|
||||
assert.Less(t, diff, maxDiff, "timing difference suggests non-constant-time comparison")
|
||||
}
|
||||
|
||||
// TestSignatureMalleabilityAttack tests protection against signature malleability
|
||||
func TestSignatureMalleabilityAttack(t *testing.T) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
message := []byte("transaction data")
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
// Create deterministic signature
|
||||
r, s, err := ecdsaPkg.DeterministicSign(priv, hash[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify original signature
|
||||
assert.True(t, ecdsa.Verify(&priv.PublicKey, hash[:], r, s))
|
||||
|
||||
// Create malleable signature (r, -s mod N)
|
||||
N := priv.Curve.Params().N
|
||||
sMalleable := new(big.Int).Sub(N, s)
|
||||
|
||||
// Standard ECDSA would accept this, but our canonical check should reject it
|
||||
assert.False(t, ecdsaPkg.VerifyDeterministic(&priv.PublicKey, hash[:], r, sMalleable),
|
||||
"malleable signature should be rejected")
|
||||
|
||||
// Canonicalize should fix it
|
||||
rCanon, sCanon, err := ecdsaPkg.CanonicalizeSignature(r, sMalleable, priv.Curve)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, s, sCanon, "canonicalized signature should match original")
|
||||
assert.True(t, ecdsa.Verify(&priv.PublicKey, hash[:], rCanon, sCanon))
|
||||
}
|
||||
|
||||
// TestNonceReuseAttack verifies protection against nonce reuse
|
||||
func TestNonceReuseAttack(t *testing.T) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign same message multiple times with deterministic ECDSA
|
||||
message := []byte("sensitive data")
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
signatures := make([]struct{ r, s *big.Int }, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
r, s, err := ecdsaPkg.DeterministicSign(priv, hash[:])
|
||||
require.NoError(t, err)
|
||||
signatures[i].r = r
|
||||
signatures[i].s = s
|
||||
}
|
||||
|
||||
// All signatures should be identical (deterministic)
|
||||
for i := 1; i < 10; i++ {
|
||||
assert.Equal(t, signatures[0].r, signatures[i].r,
|
||||
"deterministic signatures should use same nonce")
|
||||
assert.Equal(t, signatures[0].s, signatures[i].s,
|
||||
"deterministic signatures should be identical")
|
||||
}
|
||||
|
||||
// Different messages should use different nonces
|
||||
message2 := []byte("different data")
|
||||
hash2 := sha256.Sum256(message2)
|
||||
r2, s2, err := ecdsaPkg.DeterministicSign(priv, hash2[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, signatures[0].r, r2,
|
||||
"different messages must use different nonces")
|
||||
assert.NotEqual(t, signatures[0].s, s2,
|
||||
"different messages must produce different signatures")
|
||||
}
|
||||
|
||||
// TestPasswordDictionaryAttack tests resistance to dictionary attacks
|
||||
func TestPasswordDictionaryAttack(t *testing.T) {
|
||||
commonPasswords := []string{
|
||||
"password", "123456", "password123", "admin", "letmein",
|
||||
"welcome", "monkey", "dragon", "master", "qwerty",
|
||||
}
|
||||
|
||||
validator := password.NewValidator(password.DefaultPasswordConfig())
|
||||
|
||||
// All common passwords should be rejected
|
||||
for _, pwd := range commonPasswords {
|
||||
err := validator.Validate([]byte(pwd))
|
||||
assert.Error(t, err, "common password '%s' should be rejected", pwd)
|
||||
}
|
||||
|
||||
// Test that Argon2 makes dictionary attacks expensive
|
||||
kdf := argon2.New(argon2.DefaultConfig())
|
||||
|
||||
start := time.Now()
|
||||
for _, pwd := range commonPasswords {
|
||||
hash, err := kdf.HashPassword([]byte(pwd))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to crack with dictionary
|
||||
for _, attempt := range commonPasswords {
|
||||
_, _ = argon2.VerifyPassword([]byte(attempt), hash)
|
||||
}
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// Should take significant time (> 1 second for 100 attempts)
|
||||
assert.Greater(t, elapsed, 1*time.Second,
|
||||
"Argon2 should make dictionary attacks expensive")
|
||||
}
|
||||
|
||||
// TestWASMHashCollisionAttack tests resistance to hash collision attacks
|
||||
func TestWASMHashCollisionAttack(t *testing.T) {
|
||||
verifier := wasm.NewHashVerifier()
|
||||
|
||||
// Create two different modules
|
||||
module1 := []byte("wasm module version 1.0")
|
||||
module2 := []byte("wasm module version 2.0")
|
||||
|
||||
hash1 := verifier.ComputeHash(module1)
|
||||
hash2 := verifier.ComputeHash(module2)
|
||||
|
||||
// Hashes must be different
|
||||
assert.NotEqual(t, hash1, hash2,
|
||||
"different modules must have different hashes")
|
||||
|
||||
// Test collision resistance with similar modules
|
||||
similarModules := make([][]byte, 100)
|
||||
hashes := make(map[string]bool)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
similarModules[i] = []byte(fmt.Sprintf("wasm module version 1.%d", i))
|
||||
hash := verifier.ComputeHash(similarModules[i])
|
||||
|
||||
// Check for collisions
|
||||
assert.False(t, hashes[hash],
|
||||
"hash collision detected for module %d", i)
|
||||
hashes[hash] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TestRaceConditionSafety tests thread safety of cryptographic operations
|
||||
func TestRaceConditionSafety(t *testing.T) {
|
||||
// Test concurrent Argon2 operations
|
||||
kdf := argon2.New(argon2.LightConfig())
|
||||
password := []byte("test-password")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
hash, err := kdf.HashPassword(password)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
|
||||
valid, err := argon2.VerifyPassword(password, hash)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
if !valid {
|
||||
errors <- fmt.Errorf("password verification failed")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check for any errors
|
||||
for err := range errors {
|
||||
t.Errorf("concurrent operation failed: %v", err)
|
||||
}
|
||||
|
||||
// Test concurrent ECDSA signing
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
message := []byte("concurrent test")
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
signatures := make(chan struct{ r, s *big.Int }, 100)
|
||||
var sigWg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sigWg.Add(1)
|
||||
go func() {
|
||||
defer sigWg.Done()
|
||||
|
||||
r, s, err := ecdsaPkg.DeterministicSign(priv, hash[:])
|
||||
if err == nil {
|
||||
signatures <- struct{ r, s *big.Int }{r, s}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
sigWg.Wait()
|
||||
close(signatures)
|
||||
|
||||
// All signatures should be identical (deterministic)
|
||||
var firstSig struct{ r, s *big.Int }
|
||||
count := 0
|
||||
for sig := range signatures {
|
||||
if count == 0 {
|
||||
firstSig = sig
|
||||
} else {
|
||||
assert.Equal(t, firstSig.r, sig.r,
|
||||
"concurrent signatures should be identical")
|
||||
assert.Equal(t, firstSig.s, sig.s,
|
||||
"concurrent signatures should be identical")
|
||||
}
|
||||
count++
|
||||
}
|
||||
assert.Equal(t, 100, count, "all concurrent operations should succeed")
|
||||
}
|
||||
|
||||
// TestMemoryExhaustionAttack tests resistance to memory exhaustion
|
||||
func TestMemoryExhaustionAttack(t *testing.T) {
|
||||
// Test with high memory Argon2 config
|
||||
config := &argon2.Config{
|
||||
Time: 1,
|
||||
Memory: 128 * 1024, // 128MB
|
||||
Parallelism: 4,
|
||||
SaltLength: 32,
|
||||
KeyLength: 32,
|
||||
}
|
||||
|
||||
// Validate config prevents excessive memory use
|
||||
err := argon2.ValidateConfig(config)
|
||||
assert.NoError(t, err, "reasonable memory config should be valid")
|
||||
|
||||
// Test with excessive memory request
|
||||
excessiveConfig := &argon2.Config{
|
||||
Time: 1,
|
||||
Memory: 4 * 1024 * 1024, // 4GB - should be rejected
|
||||
Parallelism: 4,
|
||||
SaltLength: 32,
|
||||
KeyLength: 32,
|
||||
}
|
||||
|
||||
// This should be caught by reasonable implementations
|
||||
kdf := argon2.New(excessiveConfig)
|
||||
|
||||
// Attempt derivation with memory limit
|
||||
password := []byte("test")
|
||||
salt := make([]byte, 32)
|
||||
_, _ = rand.Read(salt)
|
||||
|
||||
// Monitor memory usage (simplified test)
|
||||
start := time.Now()
|
||||
_ = kdf.DeriveKey(password, salt)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// Excessive memory should cause noticeable delay
|
||||
assert.Less(t, elapsed, 10*time.Second,
|
||||
"operation should complete in reasonable time")
|
||||
}
|
||||
|
||||
// TestSaltReuseVulnerability tests that salts are unique
|
||||
func TestSaltReuseVulnerability(t *testing.T) {
|
||||
kdf := argon2.New(argon2.DefaultConfig())
|
||||
|
||||
salts := make(map[string]bool)
|
||||
|
||||
// Generate many salts
|
||||
for i := 0; i < 1000; i++ {
|
||||
salt, err := kdf.GenerateSalt()
|
||||
require.NoError(t, err)
|
||||
|
||||
saltStr := string(salt)
|
||||
assert.False(t, salts[saltStr],
|
||||
"salt reuse detected at iteration %d", i)
|
||||
salts[saltStr] = true
|
||||
}
|
||||
}
|
||||
|
||||
// TestWeakRandomnessDetection tests quality of random number generation
|
||||
func TestWeakRandomnessDetection(t *testing.T) {
|
||||
// Generate multiple ECDSA keys and check for patterns
|
||||
keys := make([]*ecdsa.PrivateKey, 100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
keys[i] = key
|
||||
}
|
||||
|
||||
// Check that all private keys are unique
|
||||
seen := make(map[string]bool)
|
||||
for i, key := range keys {
|
||||
keyStr := key.D.String()
|
||||
assert.False(t, seen[keyStr],
|
||||
"duplicate private key detected at index %d", i)
|
||||
seen[keyStr] = true
|
||||
}
|
||||
|
||||
// Check distribution (simplified chi-square test)
|
||||
// Count leading bits
|
||||
zeros := 0
|
||||
ones := 0
|
||||
for _, key := range keys {
|
||||
if key.D.Bit(0) == 0 {
|
||||
zeros++
|
||||
} else {
|
||||
ones++
|
||||
}
|
||||
}
|
||||
|
||||
// Should be roughly 50/50 distribution
|
||||
// For 100 samples, we expect ~50 each, but allow for statistical variance
|
||||
// Using binomial distribution, 99% confidence interval is approximately ±3 standard deviations
|
||||
// Standard deviation = sqrt(n*p*(1-p)) = sqrt(100*0.5*0.5) = 5
|
||||
// So we allow difference up to 3*5 = 15, but we'll be more lenient with 25
|
||||
diff := zeros - ones
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
assert.Less(t, diff, 25,
|
||||
"random bit distribution appears biased: %d zeros, %d ones", zeros, ones)
|
||||
}
|
||||
|
||||
// TestCryptographicAgility tests ability to switch algorithms
|
||||
func TestCryptographicAgility(t *testing.T) {
|
||||
// Test different Argon2 configurations
|
||||
configs := []*argon2.Config{
|
||||
argon2.LightConfig(),
|
||||
argon2.DefaultConfig(),
|
||||
argon2.HighSecurityConfig(),
|
||||
}
|
||||
|
||||
password := []byte("test-password")
|
||||
|
||||
for i, config := range configs {
|
||||
kdf := argon2.New(config)
|
||||
|
||||
hash, err := kdf.HashPassword(password)
|
||||
require.NoError(t, err, "config %d should work", i)
|
||||
|
||||
// Verify with same config
|
||||
valid, err := argon2.VerifyPassword(password, hash)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, valid, "config %d verification should succeed", i)
|
||||
}
|
||||
|
||||
// Test different elliptic curves
|
||||
curves := []elliptic.Curve{
|
||||
elliptic.P224(),
|
||||
elliptic.P256(),
|
||||
elliptic.P384(),
|
||||
elliptic.P521(),
|
||||
}
|
||||
|
||||
message := []byte("test message")
|
||||
hashMsg := sha256.Sum256(message)
|
||||
|
||||
for _, curve := range curves {
|
||||
priv, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, s, err := ecdsaPkg.DeterministicSign(priv, hashMsg[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
valid := ecdsa.Verify(&priv.PublicKey, hashMsg[:], r, s)
|
||||
assert.True(t, valid, "curve %s should work", curve.Params().Name)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSecurityOperations benchmarks security-critical operations
|
||||
func BenchmarkSecurityOperations(b *testing.B) {
|
||||
b.Run("Argon2Default", func(b *testing.B) {
|
||||
kdf := argon2.New(argon2.DefaultConfig())
|
||||
password := []byte("benchmark-password")
|
||||
salt, _ := kdf.GenerateSalt()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = kdf.DeriveKey(password, salt)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ECDSADeterministic", func(b *testing.B) {
|
||||
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
hash := sha256.Sum256([]byte("benchmark"))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _ = ecdsaPkg.DeterministicSign(priv, hash[:])
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("WASMHashVerification", func(b *testing.B) {
|
||||
verifier := wasm.NewHashVerifier()
|
||||
module := make([]byte, 1024*1024) // 1MB module
|
||||
rand.Read(module)
|
||||
hash := verifier.ComputeHash(module)
|
||||
verifier.AddTrustedHash("bench", hash)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = verifier.VerifyHash("bench", module)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user