exchange_v2_test.go raw

   1  package crypto
   2  
   3  import (
   4  	"testing"
   5  
   6  	"git.mleku.dev/mleku/dendrite/pkg/ratio"
   7  )
   8  
   9  func TestPrepareExchange(t *testing.T) {
  10  	params := DefaultParams(Security128)
  11  	tags := []string{"word", "punct"}
  12  
  13  	kp, err := Generate(params, tags, testFactory)
  14  	if err != nil {
  15  		t.Fatalf("Generate: %v", err)
  16  	}
  17  
  18  	// Create a peer spore.
  19  	kpPeer, err := Generate(params, tags, testFactory)
  20  	if err != nil {
  21  		t.Fatalf("Generate(peer): %v", err)
  22  	}
  23  
  24  	msg, err := PrepareExchange(&kp.Private, kp.Public.Spore, kpPeer.Public.Spore, params)
  25  	if err != nil {
  26  		t.Fatalf("PrepareExchange: %v", err)
  27  	}
  28  
  29  	if msg == nil {
  30  		t.Fatal("ephemeral message should not be nil")
  31  	}
  32  	if msg.Signature == nil {
  33  		t.Error("signature should not be nil")
  34  	}
  35  	if len(msg.Pattern) == 0 {
  36  		t.Error("pattern should not be empty")
  37  	}
  38  	if msg.SenderFingerprint.Hash == "" {
  39  		t.Error("sender fingerprint should not be empty")
  40  	}
  41  }
  42  
  43  func TestExchangeV2FullProtocol(t *testing.T) {
  44  	params := DefaultParams(Security128)
  45  	tags := []string{"word", "punct"}
  46  
  47  	// Generate two keypairs.
  48  	kpAlice, err := Generate(params, tags, testFactory)
  49  	if err != nil {
  50  		t.Fatalf("Generate(Alice): %v", err)
  51  	}
  52  	kpBob, err := Generate(params, tags, testFactory)
  53  	if err != nil {
  54  		t.Fatalf("Generate(Bob): %v", err)
  55  	}
  56  
  57  	// Alice prepares her message for Bob.
  58  	aliceMsg, err := PrepareExchange(
  59  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
  60  	)
  61  	if err != nil {
  62  		t.Fatalf("PrepareExchange(Alice): %v", err)
  63  	}
  64  
  65  	// Bob prepares his message for Alice.
  66  	bobMsg, err := PrepareExchange(
  67  		&kpBob.Private, kpBob.Public.Spore, kpAlice.Public.Spore, params,
  68  	)
  69  	if err != nil {
  70  		t.Fatalf("PrepareExchange(Bob): %v", err)
  71  	}
  72  
  73  	// Alice completes the exchange using Bob's message.
  74  	aliceSecret, err := CompleteExchange(
  75  		kpAlice.Public.Spore, kpBob.Public.Spore,
  76  		aliceMsg, bobMsg, params,
  77  	)
  78  	if err != nil {
  79  		t.Fatalf("CompleteExchange(Alice): %v", err)
  80  	}
  81  
  82  	// Bob completes the exchange using Alice's message.
  83  	bobSecret, err := CompleteExchange(
  84  		kpBob.Public.Spore, kpAlice.Public.Spore,
  85  		bobMsg, aliceMsg, params,
  86  	)
  87  	if err != nil {
  88  		t.Fatalf("CompleteExchange(Bob): %v", err)
  89  	}
  90  
  91  	// Both should be authenticated.
  92  	if !aliceSecret.Authenticated {
  93  		t.Error("Alice's secret should be authenticated")
  94  	}
  95  	if !bobSecret.Authenticated {
  96  		t.Error("Bob's secret should be authenticated")
  97  	}
  98  
  99  	// Both should have common tags.
 100  	if len(aliceSecret.CommonTags) == 0 {
 101  		t.Error("should have common tags")
 102  	}
 103  
 104  	// Confidence should be positive.
 105  	if !aliceSecret.Confidence.IsPositive() {
 106  		t.Error("Alice's confidence should be positive")
 107  	}
 108  	if !bobSecret.Confidence.IsPositive() {
 109  		t.Error("Bob's confidence should be positive")
 110  	}
 111  
 112  	// Secrets should not be zero.
 113  	if aliceSecret.Secret == (Hamadryad{}) {
 114  		t.Error("Alice's secret should not be zero")
 115  	}
 116  	if bobSecret.Secret == (Hamadryad{}) {
 117  		t.Error("Bob's secret should not be zero")
 118  	}
 119  }
 120  
 121  func TestExchangeV2NilInputs(t *testing.T) {
 122  	params := DefaultParams(Security128)
 123  	tags := []string{"word"}
 124  	kp, _ := Generate(params, tags, testFactory)
 125  	kpPeer, _ := Generate(params, tags, testFactory)
 126  
 127  	// Nil private key.
 128  	_, err := PrepareExchange(nil, kp.Public.Spore, kpPeer.Public.Spore, params)
 129  	if err == nil {
 130  		t.Error("expected error with nil private key")
 131  	}
 132  
 133  	// Nil own spore.
 134  	_, err = PrepareExchange(&kp.Private, nil, kpPeer.Public.Spore, params)
 135  	if err == nil {
 136  		t.Error("expected error with nil own spore")
 137  	}
 138  
 139  	// Nil peer spore.
 140  	_, err = PrepareExchange(&kp.Private, kp.Public.Spore, nil, params)
 141  	if err == nil {
 142  		t.Error("expected error with nil peer spore")
 143  	}
 144  }
 145  
 146  func TestExchangeV2RejectsTamperedSignature(t *testing.T) {
 147  	params := DefaultParams(Security128)
 148  	tags := []string{"word", "punct"}
 149  
 150  	kpAlice, _ := Generate(params, tags, testFactory)
 151  	kpBob, _ := Generate(params, tags, testFactory)
 152  
 153  	aliceMsg, _ := PrepareExchange(
 154  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
 155  	)
 156  	bobMsg, _ := PrepareExchange(
 157  		&kpBob.Private, kpBob.Public.Spore, kpAlice.Public.Spore, params,
 158  	)
 159  
 160  	// Tamper with Bob's signature.
 161  	bobMsg.Signature.Challenge[0] ^= 0xFF
 162  
 163  	_, err := CompleteExchange(
 164  		kpAlice.Public.Spore, kpBob.Public.Spore,
 165  		aliceMsg, bobMsg, params,
 166  	)
 167  	if err == nil {
 168  		t.Error("should reject tampered signature")
 169  	}
 170  }
 171  
 172  func TestExchangeV2RejectsMITM(t *testing.T) {
 173  	// MITM attack: Eve intercepts and replaces Bob's message with her own.
 174  	params := DefaultParams(Security128)
 175  	tags := []string{"word", "punct"}
 176  
 177  	kpAlice, _ := Generate(params, tags, testFactory)
 178  	kpBob, _ := Generate(params, tags, testFactory)
 179  	kpEve, _ := Generate(params, tags, testFactory)
 180  
 181  	aliceMsg, _ := PrepareExchange(
 182  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
 183  	)
 184  
 185  	// Eve creates a message pretending to be for Alice, but using Eve's key.
 186  	eveMsg, _ := PrepareExchange(
 187  		&kpEve.Private, kpEve.Public.Spore, kpAlice.Public.Spore, params,
 188  	)
 189  
 190  	// Alice tries to complete with Eve's message (thinking it's Bob's).
 191  	_, err := CompleteExchange(
 192  		kpAlice.Public.Spore, kpBob.Public.Spore,
 193  		aliceMsg, eveMsg, params,
 194  	)
 195  	// This should fail because Eve's signature was made with Eve's key,
 196  	// not Bob's key. Alice verifies against Bob's fingerprint.
 197  	if err == nil {
 198  		t.Error("MITM attack should be rejected: Eve's signature doesn't verify as Bob's")
 199  	}
 200  }
 201  
 202  func TestExchangeV2EavesdropperCannotDerive(t *testing.T) {
 203  	// An eavesdropper who sees both spores (public) cannot derive the
 204  	// shared secret because they cannot produce the bonding patterns
 205  	// (which require the private constraint factories).
 206  	params := DefaultParams(Security128)
 207  	tags := []string{"word", "punct"}
 208  
 209  	kpAlice, _ := Generate(params, tags, testFactory)
 210  	kpBob, _ := Generate(params, tags, testFactory)
 211  
 212  	// The eavesdropper has both public spores.
 213  	// They can compute the old (broken) Exchange.
 214  	eaveSecret, err := Exchange(kpAlice.Public.Spore, kpBob.Public.Spore)
 215  	if err != nil {
 216  		t.Fatalf("eavesdropper Exchange: %v", err)
 217  	}
 218  
 219  	// The eavesdropper's secret is derived purely from public data.
 220  	// The authenticated ExchangeV2 requires the private key to produce
 221  	// the ephemeral bonding patterns.
 222  	// We just verify the eavesdropper can compute SOMETHING (the broken v1)
 223  	// but it won't match the authenticated v2 secret.
 224  	if eaveSecret.Secret == (Hamadryad{}) && len(eaveSecret.CommonTags) > 0 {
 225  		t.Error("eavesdropper should be able to compute v1 secret from public data")
 226  	}
 227  
 228  	// The v2 secret requires private key operations that the eavesdropper
 229  	// cannot perform. This is proven by the fact that PrepareExchange
 230  	// requires a private key, and CompleteExchange verifies signatures.
 231  }
 232  
 233  func TestExchangeV2DeriveKey(t *testing.T) {
 234  	params := DefaultParams(Security128)
 235  	tags := []string{"word", "punct"}
 236  
 237  	kpAlice, _ := Generate(params, tags, testFactory)
 238  	kpBob, _ := Generate(params, tags, testFactory)
 239  
 240  	aliceMsg, _ := PrepareExchange(
 241  		&kpAlice.Private, kpAlice.Public.Spore, kpBob.Public.Spore, params,
 242  	)
 243  	bobMsg, _ := PrepareExchange(
 244  		&kpBob.Private, kpBob.Public.Spore, kpAlice.Public.Spore, params,
 245  	)
 246  
 247  	aliceResult, err := CompleteExchange(
 248  		kpAlice.Public.Spore, kpBob.Public.Spore,
 249  		aliceMsg, bobMsg, params,
 250  	)
 251  	if err != nil {
 252  		t.Fatalf("CompleteExchange(Alice): %v", err)
 253  	}
 254  
 255  	// Derive a key from the authenticated secret.
 256  	ss := &SharedSecret{
 257  		CommonTags: aliceResult.CommonTags,
 258  		Secret:     aliceResult.Secret,
 259  		Confidence: aliceResult.Confidence,
 260  	}
 261  
 262  	key := DeriveKey(ss, "aes-256", 32)
 263  	if len(key) != 32 {
 264  		t.Errorf("key length = %d, want 32", len(key))
 265  	}
 266  
 267  	// Key should be non-zero.
 268  	allZero := true
 269  	for _, b := range key {
 270  		if b != 0 {
 271  			allZero = false
 272  			break
 273  		}
 274  	}
 275  	if allZero {
 276  		t.Error("derived key should not be all zeros")
 277  	}
 278  }
 279  
 280  func TestExchangeV2InvalidParams(t *testing.T) {
 281  	tags := []string{"word"}
 282  	params := DefaultParams(Security128)
 283  	kp, _ := Generate(params, tags, testFactory)
 284  	kpPeer, _ := Generate(params, tags, testFactory)
 285  
 286  	badParams := Params{} // invalid
 287  	_, err := PrepareExchange(&kp.Private, kp.Public.Spore, kpPeer.Public.Spore, badParams)
 288  	if err == nil {
 289  		t.Error("expected error with invalid params")
 290  	}
 291  }
 292  
 293  func TestExchangeV2Confidence(t *testing.T) {
 294  	params := DefaultParams(Security128)
 295  
 296  	// Same tags → confidence should be 1/1.
 297  	kp1, _ := Generate(params, []string{"word", "punct"}, testFactory)
 298  	kp2, _ := Generate(params, []string{"word", "punct"}, testFactory)
 299  
 300  	msg1, _ := PrepareExchange(&kp1.Private, kp1.Public.Spore, kp2.Public.Spore, params)
 301  	msg2, _ := PrepareExchange(&kp2.Private, kp2.Public.Spore, kp1.Public.Spore, params)
 302  
 303  	result, err := CompleteExchange(kp1.Public.Spore, kp2.Public.Spore, msg1, msg2, params)
 304  	if err != nil {
 305  		t.Fatalf("CompleteExchange: %v", err)
 306  	}
 307  
 308  	if !result.Confidence.Equal(ratio.One) {
 309  		t.Errorf("same tags: confidence = %s, want 1/1", result.Confidence)
 310  	}
 311  }
 312