Skip to content

Commit 52a6bcc

Browse files
authored
Mimic DTLS 1.3 features (#10)
* Add key_share extension and mimicry of any extension * Refactor
1 parent 1fcb825 commit 52a6bcc

File tree

13 files changed

+515
-159
lines changed

13 files changed

+515
-159
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This library was developed as part of a Master thesis: "*[Reducing distinguishab
1919
## Features
2020

2121
- Mimicking/replaying *ClientHello*
22+
- key_share with fake keys (DTLS 1.3)
2223
- Randomization of *ClientHello*
2324
- cipher suites: shuffle and random size
2425
- extensions: shuffle

pkg/mimicry/errors.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package mimicry
2+
3+
import (
4+
"errors"
5+
6+
"github.com/pion/dtls/v3/pkg/protocol"
7+
)
8+
9+
// Typed errors.
10+
var (
11+
errCookieTooLong = &protocol.FatalError{
12+
Err: errors.New("cookie must not be longer then 255 bytes"), //nolint:err113
13+
}
14+
errBufferTooSmall = &protocol.TemporaryError{
15+
Err: errors.New("buffer is too small"), //nolint:err113
16+
}
17+
errLengthMismatch = &protocol.InternalError{
18+
Err: errors.New("data length and declared length do not match"), //nolint:err113
19+
}
20+
errNoFingerprints = errors.New("no fingerprints available")
21+
errHexstringDecode = errors.New("mimicry: failed to decode mimicry hexstring")
22+
)

pkg/mimicry/extension.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package mimicry
2+
3+
import (
4+
"encoding/binary"
5+
"github.com/pion/dtls/v3/pkg/protocol/extension"
6+
"github.com/theodorsm/covert-dtls/pkg/utils"
7+
)
8+
9+
// Unmarshal many extensions at once.
10+
func MimicExtensionsUnmarshal(buf []byte) ([]extension.Extension, error) {
11+
switch {
12+
case len(buf) == 0:
13+
return []extension.Extension{}, nil
14+
case len(buf) < 2:
15+
return nil, errBufferTooSmall
16+
}
17+
18+
declaredLen := binary.BigEndian.Uint16(buf)
19+
if len(buf)-2 != int(declaredLen) {
20+
return nil, errLengthMismatch
21+
}
22+
23+
extensions := []extension.Extension{}
24+
unmarshalAndAppend := func(data []byte, e extension.Extension) error {
25+
err := e.Unmarshal(data)
26+
if err != nil {
27+
return err
28+
}
29+
extensions = append(extensions, e)
30+
31+
return nil
32+
}
33+
34+
for offset := 2; offset < len(buf); {
35+
if len(buf) < (offset + 2) {
36+
return nil, errBufferTooSmall
37+
}
38+
var err error
39+
switch extension.TypeValue(binary.BigEndian.Uint16(buf[offset:])) {
40+
case extension.ServerNameTypeValue:
41+
err = unmarshalAndAppend(buf[offset:], &extension.ServerName{})
42+
case extension.SupportedEllipticCurvesTypeValue:
43+
// Mimic
44+
err = unmarshalAndAppend(buf[offset:], &utils.FakeExt{})
45+
case extension.SupportedPointFormatsTypeValue:
46+
err = unmarshalAndAppend(buf[offset:], &extension.SupportedPointFormats{})
47+
case extension.SupportedSignatureAlgorithmsTypeValue:
48+
// Mimic
49+
err = unmarshalAndAppend(buf[offset:], &utils.FakeExt{})
50+
case extension.UseSRTPTypeValue:
51+
err = unmarshalAndAppend(buf[offset:], &extension.UseSRTP{})
52+
case extension.ALPNTypeValue:
53+
err = unmarshalAndAppend(buf[offset:], &extension.ALPN{})
54+
case extension.UseExtendedMasterSecretTypeValue:
55+
err = unmarshalAndAppend(buf[offset:], &extension.UseExtendedMasterSecret{})
56+
case extension.RenegotiationInfoTypeValue:
57+
err = unmarshalAndAppend(buf[offset:], &extension.RenegotiationInfo{})
58+
case extension.ConnectionIDTypeValue:
59+
err = unmarshalAndAppend(buf[offset:], &extension.ConnectionID{})
60+
case utils.KeyShareTypeValue:
61+
// Unmarshal mimicked KeyShare
62+
err = unmarshalAndAppend(buf[offset:], &utils.KeyShare{})
63+
default:
64+
// Unmarshal any mimicked unimplemented extension
65+
err = unmarshalAndAppend(buf[offset:], &utils.FakeExt{})
66+
}
67+
if err != nil {
68+
return nil, err
69+
}
70+
if len(buf) < (offset + 4) {
71+
return nil, errBufferTooSmall
72+
}
73+
extensionLength := binary.BigEndian.Uint16(buf[offset+2:])
74+
offset += (4 + int(extensionLength))
75+
}
76+
77+
return extensions, nil
78+
}

pkg/mimicry/mimic_client_hello.go

Lines changed: 93 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
package mimicry
22

33
import (
4+
"encoding/binary"
45
"encoding/hex"
5-
"errors"
66

7+
"github.com/pion/dtls/v3/pkg/protocol"
78
"github.com/pion/dtls/v3/pkg/protocol/extension"
89
"github.com/pion/dtls/v3/pkg/protocol/handshake"
910
"github.com/theodorsm/covert-dtls/pkg/fingerprints"
1011
"github.com/theodorsm/covert-dtls/pkg/utils"
1112
)
1213

13-
var (
14-
errBufferTooSmall = errors.New("buffer is too small")
15-
errNoFingerprints = errors.New("no fingerprints available")
16-
errHexstringDecode = errors.New("mimicry: failed to decode mimicry hexstring")
17-
)
14+
const handshakeMessageClientHelloVariableWidthStart = 34
1815

1916
// MimickedClientHello is to be used as a way to replay DTLS client hello messages. To be used with the Pion dtls library.
2017
type MimickedClientHello struct {
2118
clientHelloFingerprint fingerprints.ClientHelloFingerprint
19+
Version protocol.Version
2220
Random handshake.Random
23-
SessionID []byte
2421
Cookie []byte
22+
23+
SessionID []byte
24+
25+
CipherSuiteIDs []uint16
26+
CompressionMethods []*protocol.CompressionMethod
2527
Extensions []extension.Extension
2628
SRTPProtectionProfiles []extension.SRTPProtectionProfile
2729
}
@@ -42,31 +44,12 @@ func (m MimickedClientHello) Type() handshake.Type {
4244
// Parses hexstring fingerprint and sets Extensions and SRTPProtectionProfiles
4345
func (m *MimickedClientHello) LoadFingerprint(fingerprint fingerprints.ClientHelloFingerprint) error {
4446
m.clientHelloFingerprint = fingerprint
45-
clientHello := handshake.MessageClientHello{}
4647
data, err := hex.DecodeString(string(m.clientHelloFingerprint))
4748
if err != nil {
4849
return errHexstringDecode
4950
}
50-
err = clientHello.Unmarshal(data)
51-
if err != nil {
52-
return err
53-
}
54-
m.Extensions = clientHello.Extensions
55-
for _, ext := range m.Extensions {
56-
if ext.TypeValue() == extension.UseSRTPTypeValue {
57-
srtp := extension.UseSRTP{}
58-
buf, err := ext.Marshal()
59-
if err != nil {
60-
return err
61-
}
62-
err = srtp.Unmarshal(buf)
63-
if err != nil {
64-
return err
65-
}
66-
m.SRTPProtectionProfiles = srtp.ProtectionProfiles
67-
}
68-
}
69-
return nil
51+
err = m.Unmarshal(data)
52+
return err
7053
}
7154

7255
// Loads a random fingerprint to mimic
@@ -79,7 +62,7 @@ func (m *MimickedClientHello) LoadRandomFingerprint() error {
7962

8063
// Marshal encodes the Handshake
8164
func (m *MimickedClientHello) Marshal() ([]byte, error) {
82-
var out []byte
65+
out := make([]byte, handshakeMessageClientHelloVariableWidthStart)
8366

8467
fingerprint := m.clientHelloFingerprint
8568

@@ -94,55 +77,110 @@ func (m *MimickedClientHello) Marshal() ([]byte, error) {
9477

9578
data, err := hex.DecodeString(string(fingerprint))
9679
if err != nil {
97-
err = errHexstringDecode
80+
return out, errHexstringDecode
9881
}
9982

10083
if len(data) <= 2 {
10184
return out, errBufferTooSmall
10285
}
10386

104-
// Major and minor version
105-
currOffset := 2
106-
out = append(out, data[:currOffset]...)
87+
if len(m.Cookie) > 255 {
88+
return nil, errCookieTooLong
89+
}
90+
91+
out[0] = m.Version.Major
92+
out[1] = m.Version.Minor
93+
94+
rand := m.Random.MarshalFixed()
95+
copy(out[2:], rand[:])
96+
97+
out = append(out, byte(len(m.SessionID)))
98+
out = append(out, m.SessionID...)
99+
100+
out = append(out, byte(len(m.Cookie)))
101+
out = append(out, m.Cookie...)
102+
out = append(out, utils.EncodeCipherSuiteIDs(m.CipherSuiteIDs)...)
103+
out = append(out, protocol.EncodeCompressionMethods(m.CompressionMethods)...)
104+
105+
extensions, err := utils.ExtensionMarshal(m.Extensions)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
return append(out, extensions...), nil
111+
}
112+
113+
// Unmarshal populates the message from encoded data
114+
func (m *MimickedClientHello) Unmarshal(data []byte) error {
115+
if len(data) < 2+handshake.RandomLength {
116+
return errBufferTooSmall
117+
}
118+
119+
m.Version.Major = data[0]
120+
m.Version.Minor = data[1]
107121

108-
rb := m.Random.MarshalFixed()
109-
out = append(out, rb[:]...)
122+
var random [handshake.RandomLength]byte
123+
copy(random[:], data[2:])
124+
m.Random.UnmarshalFixed(random)
110125

111-
// Skip past random
112-
currOffset += 32
126+
// rest of packet has variable width sections
127+
currOffset := handshakeMessageClientHelloVariableWidthStart
113128

114129
currOffset++
115130
if len(data) <= currOffset {
116-
return out, errBufferTooSmall
131+
return errBufferTooSmall
117132
}
118133
n := int(data[currOffset-1])
119134
if len(data) <= currOffset+n {
120-
return out, errBufferTooSmall
135+
return errBufferTooSmall
121136
}
122-
mimickedSessionID := append([]byte{}, data[currOffset:currOffset+n]...)
123-
currOffset += len(mimickedSessionID)
137+
m.SessionID = append([]byte{}, data[currOffset:currOffset+n]...)
138+
currOffset += len(m.SessionID)
124139

125140
currOffset++
126141
if len(data) <= currOffset {
127-
return out, errBufferTooSmall
142+
return errBufferTooSmall
128143
}
129144
n = int(data[currOffset-1])
130145
if len(data) <= currOffset+n {
131-
return out, errBufferTooSmall
146+
return errBufferTooSmall
132147
}
133-
mimickedCookie := append([]byte{}, data[currOffset:currOffset+n]...)
134-
currOffset += len(mimickedCookie)
148+
m.Cookie = append([]byte{}, data[currOffset:currOffset+n]...)
149+
currOffset += len(m.Cookie)
135150

136-
out = append(out, byte(len(m.SessionID)))
137-
out = append(out, m.SessionID...)
138-
139-
out = append(out, byte(len(m.Cookie)))
140-
out = append(out, m.Cookie...)
151+
// Cipher Suites
152+
if len(data) < currOffset {
153+
return errBufferTooSmall
154+
}
155+
cipherSuiteIDs, err := utils.DecodeCipherSuiteIDs(data[currOffset:])
156+
if err != nil {
157+
return err
158+
}
159+
m.CipherSuiteIDs = cipherSuiteIDs
160+
if len(data) < currOffset+2 {
161+
return errBufferTooSmall
162+
}
163+
currOffset += int(binary.BigEndian.Uint16(data[currOffset:])) + 2
141164

142-
out = append(out, data[currOffset:]...)
165+
// Compression Methods
166+
if len(data) < currOffset {
167+
return errBufferTooSmall
168+
}
169+
compressionMethods, err := protocol.DecodeCompressionMethods(data[currOffset:])
170+
if err != nil {
171+
return err
172+
}
173+
m.CompressionMethods = compressionMethods
174+
if len(data) < currOffset {
175+
return errBufferTooSmall
176+
}
177+
currOffset += int(data[currOffset]) + 1
143178

144-
return out, err
179+
// Extensions
180+
extensions, err := MimicExtensionsUnmarshal(data[currOffset:])
181+
if err != nil {
182+
return err
183+
}
184+
m.Extensions = extensions
185+
return nil
145186
}
146-
147-
// Unmarshal populates the message from encoded data
148-
func (m *MimickedClientHello) Unmarshal(data []byte) error { return nil }

pkg/mimicry/mimic_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package mimicry
2+
3+
import (
4+
"fmt"
5+
"github.com/theodorsm/covert-dtls/pkg/utils"
6+
"testing"
7+
)
8+
9+
func TestUnmarshalKeyShare(t *testing.T) {
10+
keyShareBytes := []byte{0x0, 0x33, 0x0, 0x6b, 0x0, 0x69, 0x0, 0x1d, 0x0, 0x20, 0x52, 0x61, 0xca, 0x3a, 0xa4, 0xa3, 0x2f, 0xac, 0xf3, 0xf7, 0x1e, 0xdb, 0x5e, 0xbb, 0x7f, 0xdf, 0xa8, 0x8d, 0x4, 0x31, 0x9e, 0x56, 0xe3, 0x81, 0x86, 0x32, 0x3c, 0x24, 0xe8, 0x44, 0x5e, 0xe, 0x0, 0x17, 0x0, 0x41, 0x4, 0x7b, 0xdd, 0xd9, 0xaa, 0xb2, 0xd6, 0x56, 0xb8, 0x23, 0x1e, 0xb0, 0xe3, 0x2c, 0xc7, 0xf2, 0x6a, 0xcd, 0xe0, 0x55, 0xb3, 0x3f, 0x11, 0xa2, 0x22, 0x5e, 0x93, 0xb7, 0x92, 0xbd, 0x15, 0x98, 0xae, 0x5c, 0x8a, 0xcd, 0xd2, 0x57, 0x4e, 0x50, 0x3a, 0x3f, 0x4f, 0x25, 0x82, 0x2b, 0x63, 0x25, 0xe2, 0xe0, 0xb9, 0x17, 0x27, 0x3f, 0x97, 0x77, 0x27, 0x8, 0x77, 0xe5, 0xe3, 0xb8, 0xc7, 0x73, 0x98}
11+
keyshare := utils.KeyShare{}
12+
err := keyshare.Unmarshal(keyShareBytes)
13+
if err != nil {
14+
t.Errorf("Unmarshal failed: %v\n", err)
15+
}
16+
fmt.Printf("%+v\n", keyshare)
17+
if len(keyshare.KeyShareEntries) != 2 {
18+
t.Errorf("Unmarshal failed: length %v\n", len(keyshare.KeyShareEntries))
19+
}
20+
entry0 := keyshare.KeyShareEntries[0]
21+
expGroup := uint16(29)
22+
expKeyLength := uint16(32)
23+
if entry0.Group != expGroup || entry0.KeyLength != expKeyLength {
24+
t.Errorf("Unmarshal failed, entry #0: %v. Expected Group: %v, KeyLength: %v", entry0, expGroup, expKeyLength)
25+
}
26+
entry1 := keyshare.KeyShareEntries[1]
27+
expGroup = uint16(23)
28+
expKeyLength = uint16(65)
29+
if entry1.Group != expGroup || entry1.KeyLength != expKeyLength {
30+
t.Errorf("Unmarshal failed, entry #1: %v. Expected Group: %v, KeyLength: %v", entry1, expGroup, expKeyLength)
31+
}
32+
}
33+
34+
func TestUnmarshalFakeExt(t *testing.T) {
35+
extBytes := []byte{0x0, 0xf6, 0x0, 0x17, 0x0, 0x0, 0xff, 0x1, 0x0, 0x1, 0x0, 0x0, 0xa, 0x0, 0xc, 0x0, 0xa, 0x0, 0x1d, 0x0, 0x17, 0x0, 0x18, 0x1, 0x0, 0x1, 0x1, 0x0, 0xb, 0x0, 0x2, 0x1, 0x0, 0x0, 0x10, 0x0, 0x12, 0x0, 0x10, 0x6, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x8, 0x63, 0x2d, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x0, 0x22, 0x0, 0xa, 0x0, 0x8, 0x4, 0x3, 0x5, 0x3, 0x6, 0x3, 0x2, 0x3, 0x0, 0x33, 0x0, 0x6b, 0x0, 0x69, 0x0, 0x1d, 0x0, 0x20, 0x52, 0x61, 0xca, 0x3a, 0xa4, 0xa3, 0x2f, 0xac, 0xf3, 0xf7, 0x1e, 0xdb, 0x5e, 0xbb, 0x7f, 0xdf, 0xa8, 0x8d, 0x4, 0x31, 0x9e, 0x56, 0xe3, 0x81, 0x86, 0x32, 0x3c, 0x24, 0xe8, 0x44, 0x5e, 0xe, 0x0, 0x17, 0x0, 0x41, 0x4, 0x7b, 0xdd, 0xd9, 0xaa, 0xb2, 0xd6, 0x56, 0xb8, 0x23, 0x1e, 0xb0, 0xe3, 0x2c, 0xc7, 0xf2, 0x6a, 0xcd, 0xe0, 0x55, 0xb3, 0x3f, 0x11, 0xa2, 0x22, 0x5e, 0x93, 0xb7, 0x92, 0xbd, 0x15, 0x98, 0xae, 0x5c, 0x8a, 0xcd, 0xd2, 0x57, 0x4e, 0x50, 0x3a, 0x3f, 0x4f, 0x25, 0x82, 0x2b, 0x63, 0x25, 0xe2, 0xe0, 0xb9, 0x17, 0x27, 0x3f, 0x97, 0x77, 0x27, 0x8, 0x77, 0xe5, 0xe3, 0xb8, 0xc7, 0x73, 0x98, 0x0, 0x2b, 0x0, 0x7, 0x6, 0xfe, 0xfc, 0xfe, 0xfd, 0x3, 0x3, 0x0, 0xd, 0x0, 0x20, 0x0, 0x1e, 0x4, 0x3, 0x5, 0x3, 0x6, 0x3, 0x2, 0x3, 0x8, 0x4, 0x8, 0x5, 0x8, 0x6, 0x4, 0x1, 0x5, 0x1, 0x6, 0x1, 0x2, 0x1, 0x4, 0x2, 0x5, 0x2, 0x6, 0x2, 0x2, 0x2, 0x0, 0x1c, 0x0, 0x2, 0x40, 0x1, 0x0, 0xe, 0x0, 0xb, 0x0, 0x8, 0x0, 0x7, 0x0, 0x8, 0x0, 0x1, 0x0, 0x2, 0x0}
36+
exts, err := MimicExtensionsUnmarshal(extBytes)
37+
if err != nil {
38+
t.Errorf("Unmarshal failed: %v\n", err)
39+
}
40+
fmt.Printf("Extensions: %+v\n", exts)
41+
out, err := utils.ExtensionMarshal(exts)
42+
if err != nil {
43+
t.Errorf("Marshal failed: %v\n", err)
44+
}
45+
fmt.Printf("length: %v, out: %#v\n", len(out), out)
46+
}

0 commit comments

Comments
 (0)