Fix decrypt15 IV advancement to match wumble's decrypt logic
The old decrypt15 only patched byte 0 of the decryptIV, never properly incrementing the full 16-byte IV. After ~210 packets byte 0 wraps and byte 1 doesn't carry — nonce diverges from the server, causing OCB authentication failures. Now matches wumble's decrypt exactly: when decrypt_iv[0]+1 == iv_byte, advance the full IV (including carries). Also added backupIV for late-packet rewind support. Renamed incrementIV to advanceIV for consistency with wumble's advance_iv naming. Added comprehensive late/reorder/drop handling mirroring wumble's diff-based IV tracking.
This commit is contained in:
committed by
Brandon McGinty
parent
e93087afeb
commit
81347893d7
+45
-14
@@ -149,7 +149,7 @@ func (cs *cryptState15) encrypt15(plaintext []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Increment IV (little-endian, byte 0 is LSB).
|
||||
incrementIV(cs.encryptIV[:])
|
||||
advanceIV(cs.encryptIV[:])
|
||||
|
||||
ciphertext, tag := ocb15Encrypt(cs.key[:], cs.encryptIV[:], plaintext)
|
||||
|
||||
@@ -163,7 +163,7 @@ func (cs *cryptState15) encrypt15(plaintext []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// decrypt15 decrypts a Mumble 1.5 native UDP packet.
|
||||
// Returns plaintext or nil on failure.
|
||||
// Matches wumble's decrypt: advances IV when decrypt_iv[0]+1 == iv_byte.
|
||||
func (cs *cryptState15) decrypt15(packet []byte) ([]byte, error) {
|
||||
if !cs.initialized {
|
||||
return nil, errors.New("gumble: crypto not initialized")
|
||||
@@ -177,10 +177,15 @@ func (cs *cryptState15) decrypt15(packet []byte) ([]byte, error) {
|
||||
encrypted := packet[4:]
|
||||
|
||||
savedIV := cs.decryptIV
|
||||
restore := false
|
||||
|
||||
// Determine how the received iv_byte relates to our expected decryptIV[0].
|
||||
if ivByte == cs.decryptIV[0] {
|
||||
// Perfect match — packet arrived in order.
|
||||
// Match wumble: if decrypt_iv[0] + 1 == iv_byte, advance and accept.
|
||||
next := cs.decryptIV[0] + 1
|
||||
if next == ivByte {
|
||||
if ivByte < cs.decryptIV[0] {
|
||||
advanceIV(cs.decryptIV[:])
|
||||
}
|
||||
cs.decryptIV[0] = ivByte
|
||||
} else {
|
||||
diff := int(ivByte) - int(cs.decryptIV[0])
|
||||
if diff < -128 {
|
||||
@@ -189,17 +194,26 @@ func (cs *cryptState15) decrypt15(packet []byte) ([]byte, error) {
|
||||
diff -= 256
|
||||
}
|
||||
|
||||
if diff >= -30 && diff <= 30 {
|
||||
if ivByte < cs.decryptIV[0] && diff > -30 && diff < 0 {
|
||||
// Late packet.
|
||||
cs.decryptIV[0] = ivByte
|
||||
restore = true
|
||||
} else if ivByte > cs.decryptIV[0] && diff > -30 && diff < 0 {
|
||||
// Late packet (wrapped diff).
|
||||
cs.decryptIV[0] = ivByte
|
||||
backupIV(cs.decryptIV[:])
|
||||
restore = true
|
||||
} else if ivByte > cs.decryptIV[0] && diff > 0 {
|
||||
// We missed packets; catch up. Already handled above.
|
||||
} else if ivByte < cs.decryptIV[0] && diff > 0 {
|
||||
// Wrapped forward; advance and catch up.
|
||||
advanceIV(cs.decryptIV[:])
|
||||
cs.decryptIV[0] = ivByte
|
||||
if diff < 0 {
|
||||
// Late packet — we're ahead. Restore after decrypt.
|
||||
defer func() { cs.decryptIV = savedIV }()
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("gumble: OCB IV too far off")
|
||||
}
|
||||
|
||||
// Replay check
|
||||
// Replay check.
|
||||
if cs.history[ivByte] != 0 && cs.history[ivByte] == cs.decryptIV[1] {
|
||||
cs.decryptIV = savedIV
|
||||
return nil, errors.New("gumble: OCB replay detected")
|
||||
@@ -221,12 +235,15 @@ func (cs *cryptState15) decrypt15(packet []byte) ([]byte, error) {
|
||||
// Update replay history.
|
||||
cs.history[ivByte] = cs.decryptIV[1]
|
||||
|
||||
if restore {
|
||||
cs.decryptIV = savedIV
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// incrementIV increments a 16-byte IV as a little-endian integer
|
||||
// (byte 0 is the least significant byte). Matches wumble's increment_encrypt_iv.
|
||||
func incrementIV(iv []byte) {
|
||||
// advanceIV increments a 16-byte IV as a little-endian integer.
|
||||
func advanceIV(iv []byte) {
|
||||
for i := 0; i < len(iv); i++ {
|
||||
iv[i]++
|
||||
if iv[i] != 0 {
|
||||
@@ -235,6 +252,20 @@ func incrementIV(iv []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// backupIV decrements a 16-byte IV as a little-endian integer.
|
||||
func backupIV(iv []byte) {
|
||||
for i := 0; i < len(iv); i++ {
|
||||
if iv[i] == 0 {
|
||||
iv[i] = 0xFF
|
||||
} else {
|
||||
iv[i]--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OCB variant for Mumble 1.5 native UDP.
|
||||
// Matches the implementation in Wumble's crypt_state.cr.
|
||||
|
||||
@@ -7,26 +7,23 @@ import (
|
||||
)
|
||||
|
||||
// Test IV increment matches wumble's little-endian behavior.
|
||||
func TestIncrementIV(t *testing.T) {
|
||||
func TestAdvanceIV(t *testing.T) {
|
||||
iv := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
incrementIV(iv)
|
||||
// After first increment: byte 0 should be 0x01
|
||||
advanceIV(iv)
|
||||
if iv[0] != 0x01 {
|
||||
t.Fatalf("after increment 1, iv[0]=%02x, want 01", iv[0])
|
||||
}
|
||||
|
||||
// Increment 254 more times to get byte 0 to 0xFF, then overflow
|
||||
for i := 0; i < 254; i++ {
|
||||
incrementIV(iv)
|
||||
advanceIV(iv)
|
||||
}
|
||||
if iv[0] != 0xFF || iv[1] != 0x00 {
|
||||
t.Fatalf("after 255 increments, iv[0]=%02x iv[1]=%02x, want FF 00", iv[0], iv[1])
|
||||
}
|
||||
|
||||
// One more: overflow to byte 1
|
||||
incrementIV(iv)
|
||||
advanceIV(iv)
|
||||
if iv[0] != 0x00 || iv[1] != 0x01 {
|
||||
t.Fatalf("after 256 increments, iv[0]=%02x iv[1]=%02x, want 00 01", iv[0], iv[1])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user