370 lines
11 KiB
Go
370 lines
11 KiB
Go
package mint
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
plaintextHex = "1503010005F0F1F2F3F4"
|
|
|
|
// Random key and IV; hand-encoded ciphertext for the above plaintext
|
|
keyHex = "45c71e5819170d622a9f4e3a089a0beb"
|
|
ivHex = "2b7fbbf689f240e3e7aa44a6"
|
|
paddingLength = 4
|
|
sequenceChange = 17
|
|
ciphertext0Hex = "1703010016621a75932c037ff74d2a9ec7776790e09dcd4811db97"
|
|
ciphertext1Hex = "170301001a621a75932c03076e386b3cebbb8dbf2f37e49ad3e82a70a17833"
|
|
ciphertext2Hex = "170301001a1da650d5da822b7f4eba67f954767fcbbbd4c4bc7f1c61daf701"
|
|
)
|
|
|
|
func TestRekey(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
|
|
r := NewRecordLayerTLS(bytes.NewBuffer(nil))
|
|
err := r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
assertNotError(t, err, "Failed to rekey")
|
|
}
|
|
|
|
func TestSequenceNumberRollover(t *testing.T) {
|
|
defer func() {
|
|
r := recover()
|
|
assert(t, r != nil, "failed to panic on sequence number overflow")
|
|
}()
|
|
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
|
|
cs, err := newCipherStateAead(EpochApplicationData, newAESGCM, key, iv)
|
|
assertNotError(t, err, "Couldn't create cipher state")
|
|
for i := 0; i < sequenceNumberLen; i++ {
|
|
cs.seq[cs.ivLength-i-1] = 0xFF
|
|
}
|
|
cs.incrementSequenceNumber()
|
|
}
|
|
|
|
func TestReadRecord(t *testing.T) {
|
|
plaintext := unhex(plaintextHex)
|
|
|
|
// Test that a known-good frame decodes properly
|
|
r := NewRecordLayerTLS(bytes.NewBuffer(plaintext))
|
|
pt, err := r.ReadRecord()
|
|
assertNotError(t, err, "Failed to decode valid plaintext")
|
|
assertEquals(t, pt.contentType, RecordTypeAlert)
|
|
assertByteEquals(t, pt.fragment, plaintext[5:])
|
|
|
|
// Test failure on unkown record type
|
|
plaintext[0] = 0xFF
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(plaintext))
|
|
pt, err = r.ReadRecord()
|
|
assertError(t, err, "Failed to reject record with unknown type")
|
|
plaintext[0] = 0x15
|
|
|
|
// Test failure on wrong version
|
|
originalAllowWrongVersionNumber := allowWrongVersionNumber
|
|
allowWrongVersionNumber = false
|
|
plaintext[2] = 0x02
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(plaintext))
|
|
pt, err = r.ReadRecord()
|
|
assertError(t, err, "Failed to reject record with incorrect version")
|
|
plaintext[2] = 0x01
|
|
allowWrongVersionNumber = originalAllowWrongVersionNumber
|
|
|
|
// Test failure on size too big
|
|
plaintext[3] = 0xFF
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(plaintext))
|
|
pt, err = r.ReadRecord()
|
|
assertError(t, err, "Failed to reject record exceeding size limit")
|
|
plaintext[3] = 0x00
|
|
|
|
// Test failure on header read failure
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(plaintext[:3]))
|
|
pt, err = r.ReadRecord()
|
|
assertError(t, err, "Didn't fail when unable to read header")
|
|
|
|
// Test failure on body read failure
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(plaintext[:7]))
|
|
pt, err = r.ReadRecord()
|
|
assertError(t, err, "Didn't fail when unable to read fragment")
|
|
}
|
|
|
|
func TestWriteRecord(t *testing.T) {
|
|
plaintext := unhex(plaintextHex)
|
|
|
|
// Test that plain WriteRecord works
|
|
pt := &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
b := bytes.NewBuffer(nil)
|
|
r := NewRecordLayerTLS(b)
|
|
err := r.WriteRecord(pt)
|
|
assertNotError(t, err, "Failed to write valid record")
|
|
assertByteEquals(t, b.Bytes(), plaintext)
|
|
|
|
// Test failure on size too big
|
|
pt = &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: bytes.Repeat([]byte{0}, maxFragmentLen+1),
|
|
}
|
|
err = r.WriteRecord(pt)
|
|
assertError(t, err, "Allowed a too-large record")
|
|
|
|
// Test failure if padding is requested without encryption
|
|
pt = &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: bytes.Repeat([]byte{0}, 5),
|
|
}
|
|
err = r.WriteRecordWithPadding(pt, 5)
|
|
assertError(t, err, "Allowed padding without encryption")
|
|
}
|
|
|
|
func TestDecryptRecord(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
plaintext := unhex(plaintextHex)
|
|
ciphertext1 := unhex(ciphertext1Hex)
|
|
ciphertext2 := unhex(ciphertext2Hex)
|
|
|
|
// Test successful decrypt
|
|
r := NewRecordLayerTLS(bytes.NewBuffer(ciphertext1))
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt, err := r.ReadRecord()
|
|
assertNotError(t, err, "Failed to decrypt valid record")
|
|
assertEquals(t, pt.contentType, RecordTypeAlert)
|
|
assertByteEquals(t, pt.fragment, plaintext[5:])
|
|
|
|
// Test successful decrypt after sequence number change
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(ciphertext2))
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
for i := 0; i < sequenceChange; i++ {
|
|
r.cipher.incrementSequenceNumber()
|
|
}
|
|
pt, err = r.ReadRecord()
|
|
assertNotError(t, err, "Failed to properly handle sequence number change")
|
|
assertEquals(t, pt.contentType, RecordTypeAlert)
|
|
assertByteEquals(t, pt.fragment, plaintext[5:])
|
|
|
|
// Test failure on decrypt failure
|
|
ciphertext1[7] ^= 0xFF
|
|
r = NewRecordLayerTLS(bytes.NewBuffer(ciphertext1))
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt, err = r.ReadRecord()
|
|
assertError(t, err, "Failed to reject invalid record")
|
|
ciphertext1[7] ^= 0xFF
|
|
}
|
|
|
|
func TestEncryptRecord(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
plaintext := unhex(plaintextHex)
|
|
ciphertext0 := unhex(ciphertext0Hex)
|
|
ciphertext1 := unhex(ciphertext1Hex)
|
|
ciphertext2 := unhex(ciphertext2Hex)
|
|
|
|
// Test successful encrypt
|
|
b := bytes.NewBuffer(nil)
|
|
r := NewRecordLayerTLS(b)
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt := &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
err := r.WriteRecord(pt)
|
|
assertNotError(t, err, "Failed to encrypt valid record")
|
|
assertByteEquals(t, b.Bytes(), ciphertext0)
|
|
|
|
// Test successful encrypt with padding
|
|
b.Truncate(0)
|
|
r = NewRecordLayerTLS(b)
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt = &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
err = r.WriteRecordWithPadding(pt, paddingLength)
|
|
assertNotError(t, err, "Failed to encrypt valid record")
|
|
assertByteEquals(t, b.Bytes(), ciphertext1)
|
|
|
|
// Test successful enc after sequence number change
|
|
b.Truncate(0)
|
|
r = NewRecordLayerTLS(b)
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
for i := 0; i < sequenceChange; i++ {
|
|
r.cipher.incrementSequenceNumber()
|
|
}
|
|
pt = &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
err = r.WriteRecordWithPadding(pt, paddingLength)
|
|
assertNotError(t, err, "Failed to properly handle sequence number change")
|
|
assertByteEquals(t, b.Bytes(), ciphertext2)
|
|
|
|
// Test failure on size too big after encrypt
|
|
b.Truncate(0)
|
|
r = NewRecordLayerTLS(b)
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt = &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: bytes.Repeat([]byte{0}, maxFragmentLen-paddingLength),
|
|
}
|
|
err = r.WriteRecordWithPadding(pt, paddingLength)
|
|
assertError(t, err, "Allowed a too-large record")
|
|
}
|
|
|
|
func TestReadWriteTLS(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
plaintext := unhex(plaintextHex)
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
out := NewRecordLayerTLS(b)
|
|
in := NewRecordLayerTLS(b)
|
|
|
|
// Unencrypted
|
|
ptIn := &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
err := out.WriteRecord(ptIn)
|
|
assertNotError(t, err, "Failed to write record")
|
|
ptOut, err := in.ReadRecord()
|
|
assertNotError(t, err, "Failed to read record")
|
|
assertEquals(t, ptIn.contentType, ptOut.contentType)
|
|
assertByteEquals(t, ptIn.fragment, ptOut.fragment)
|
|
|
|
// Encrypted
|
|
in.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
out.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
err = out.WriteRecord(ptIn)
|
|
assertNotError(t, err, "Failed to write record")
|
|
ptOut, err = in.ReadRecord()
|
|
assertNotError(t, err, "Failed to read record")
|
|
assertEquals(t, ptIn.contentType, ptOut.contentType)
|
|
assertByteEquals(t, ptIn.fragment, ptOut.fragment)
|
|
}
|
|
|
|
func TestReadWriteDTLS(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
plaintext := unhex(plaintextHex)
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
out := NewRecordLayerDTLS(b)
|
|
out.SetVersion(tls12Version)
|
|
in := NewRecordLayerDTLS(b)
|
|
in.SetVersion(tls12Version)
|
|
|
|
// Unencrypted
|
|
ptIn := &TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
err := out.WriteRecord(ptIn)
|
|
assertNotError(t, err, "Failed to write record")
|
|
ptOut, err := in.ReadRecord()
|
|
assertNotError(t, err, "Failed to read record")
|
|
|
|
assertEquals(t, ptIn.contentType, ptOut.contentType)
|
|
assertByteEquals(t, ptIn.fragment, ptOut.fragment)
|
|
|
|
// Encrypted
|
|
in.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
out.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
err = out.WriteRecord(ptIn)
|
|
assertNotError(t, err, "Failed to write record")
|
|
ptOut, err = in.ReadRecord()
|
|
assertNotError(t, err, "Failed to read record")
|
|
assertEquals(t, ptIn.contentType, ptOut.contentType)
|
|
assertByteEquals(t, ptIn.fragment, ptOut.fragment)
|
|
}
|
|
|
|
func TestOverSocket(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
plaintext := unhex(plaintextHex)
|
|
|
|
socketReady := make(chan bool)
|
|
done := make(chan TLSPlaintext, 1)
|
|
port := ":9001"
|
|
|
|
ptIn := TLSPlaintext{
|
|
contentType: RecordType(plaintext[0]),
|
|
fragment: plaintext[5:],
|
|
}
|
|
|
|
go func() {
|
|
ln, err := net.Listen("tcp", port)
|
|
assertNotError(t, err, "Unable to listen")
|
|
socketReady <- true
|
|
|
|
conn, err := ln.Accept()
|
|
assertNotError(t, err, "Unable to accept")
|
|
defer conn.Close()
|
|
|
|
in := NewRecordLayerTLS(conn)
|
|
in.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt, err := in.ReadRecord()
|
|
assertNotError(t, err, "Unable to read record")
|
|
|
|
done <- *pt
|
|
}()
|
|
|
|
<-socketReady
|
|
conn, err := net.Dial("tcp", port)
|
|
assertNotError(t, err, "Unable to dial")
|
|
|
|
out := NewRecordLayerTLS(conn)
|
|
out.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
err = out.WriteRecord(&ptIn)
|
|
assertNotError(t, err, "Unable to write record")
|
|
|
|
ptOut := <-done
|
|
assertEquals(t, ptIn.contentType, ptOut.contentType)
|
|
assertByteEquals(t, ptIn.fragment, ptOut.fragment)
|
|
}
|
|
|
|
type NoEofReader struct {
|
|
r *bytes.Buffer
|
|
}
|
|
|
|
func (p *NoEofReader) Read(data []byte) (n int, err error) {
|
|
n, err = p.r.Read(data)
|
|
|
|
// Suppress bytes.Buffer's EOF on an empty buffer
|
|
if err == io.EOF {
|
|
err = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *NoEofReader) Write(data []byte) (n int, err error) {
|
|
return 0, fmt.Errorf("Not allowed")
|
|
}
|
|
|
|
func TestNonblockingRecord(t *testing.T) {
|
|
key := unhex(keyHex)
|
|
iv := unhex(ivHex)
|
|
plaintext := unhex(plaintextHex)
|
|
ciphertext1 := unhex(ciphertext1Hex)
|
|
|
|
// Add the prefix, which should cause blocking.
|
|
b := bytes.NewBuffer(ciphertext1[:1])
|
|
r := NewRecordLayerTLS(&NoEofReader{b})
|
|
r.Rekey(EpochApplicationData, newAESGCM, key, iv)
|
|
pt, err := r.ReadRecord()
|
|
assertEquals(t, err, WouldBlock)
|
|
|
|
// Now the rest of the record, which lets us decrypt it
|
|
b.Write(ciphertext1[1:])
|
|
pt, err = r.ReadRecord()
|
|
assertNotError(t, err, "Failed to decrypt valid record")
|
|
assertEquals(t, pt.contentType, RecordTypeAlert)
|
|
assertByteEquals(t, pt.fragment, plaintext[5:])
|
|
}
|