1package transfer
2
3import (
4	"encoding/binary"
5	"net"
6	"testing"
7	"time"
8
9	"github.com/golang/protobuf/proto"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12
13	hadoop "github.com/colinmarc/hdfs/v2/internal/protocol/hadoop_common"
14	hdfs "github.com/colinmarc/hdfs/v2/internal/protocol/hadoop_hdfs"
15	"github.com/colinmarc/hdfs/v2/internal/sasl"
16)
17
18func getTestDigest() *digestMD5Handshake {
19	return &digestMD5Handshake{
20		passwd:   "secret",
21		authID:   []byte("chris"),
22		hostname: "elwood.innosoft.com",
23		service:  "imap",
24	}
25}
26
27func TestMD5DigestResponse(t *testing.T) {
28	dgst := getTestDigest()
29
30	origGenCnonce := genCnonce
31	genCnonce = func() (string, error) {
32		return "OA6MHXh6VqTrRk", nil
33	}
34	defer func() {
35		genCnonce = origGenCnonce
36	}()
37
38	// example pulled from page 19 of RFC 2831
39	challenge := `realm="elwood.innosoft.com", nonce="OA6MG9tEQGm2hh", qop="auth", algorithm=md5-sess, charset=utf-8, cipher="rc4"`
40	ret, err := dgst.challengeStep1([]byte(challenge))
41	require.NoError(t, err)
42	assert.Equal(t, []byte(`username="chris", realm="elwood.innosoft.com", nonce="OA6MG9tEQGm2hh", cnonce="OA6MHXh6VqTrRk", nc=00000001, qop=auth, digest-uri="imap/elwood.innosoft.com", response=d388dad90d4bbd760a152321f2143af7, charset=utf-8, cipher=rc4`), ret)
43	assert.Equal(t, "rc4", dgst.cipher)
44}
45
46func TestMD5DigestRspAuth(t *testing.T) {
47	dgst := getTestDigest()
48
49	// setup state as it would be after the first challenge
50	dgst.token = &sasl.Challenge{
51		Algorithm: "md5-sess",
52		Charset:   "utf-8",
53		Nonce:     "OA6MG9tEQGm2hh",
54		Qop:       []string{sasl.QopAuthentication},
55		Realm:     "elwood.innosoft.com",
56	}
57	dgst.cnonce = "OA6MHXh6VqTrRk"
58
59	// evaluate the rspauth as per the example in RFC 2831
60	err := dgst.challengeStep2([]byte("rspauth=ea40f60335c427b5527b84dbabcdfffd"))
61	assert.NoError(t, err)
62}
63
64func TestDigestMD5Conn(t *testing.T) {
65	// This was captured from a test connection.
66	key := &hdfs.DataEncryptionKeyProto{}
67	key.EncryptionKey = []byte{
68		0x4d, 0xed, 0xaa, 0xd4, 0xf0, 0xf8, 0xec, 0x7d,
69		0xfd, 0xf7, 0x76, 0xaf, 0xbc, 0x93, 0xba, 0x8e,
70		0xd1, 0xc3, 0xb3, 0xb7}
71	key.Nonce = []byte{0x79, 0x0c, 0xc3, 0xa6, 0x31, 0x7f, 0x5b, 0xd7}
72	key.KeyId = proto.Uint32(388373981)
73	key.BlockPoolId = proto.String("BP-529865118-10.129.176.136-1582635112897")
74
75	empty := ""
76	blockKind := "HDFS_BLOCK_TOKEN"
77	token := &hadoop.TokenProto{}
78	token.Kind = &blockKind
79	token.Service = &empty
80
81	origGenCnonce := genCnonce
82	genCnonce = func() (string, error) {
83		return "dqNZ/hGooPsuK3iWPeDFeQ==", nil
84	}
85	defer func() {
86		genCnonce = origGenCnonce
87	}()
88
89	server, client := net.Pipe()
90	serverDone := make(chan struct{})
91	go func() {
92		defer server.Close()
93		defer close(serverDone)
94
95		// The handshake starts by first passing 0xDEADBEEF to the server, along
96		// with an empty message. We have to read as much as possible, since the
97		// code writes everything in one go, and net.Pipe is unbuffered.
98		b := make([]byte, 1024)
99		server.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
100		_, err := server.Read(b)
101		require.NoError(t, err)
102
103		d := binary.BigEndian.Uint32(b[:4])
104		assert.Equal(t, uint32(0xDEADBEEF), d)
105
106		// In practice, this chokes since the client sends an empty message with
107		// the handshake initialization.
108		// server.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
109		// msg := &hdfs.DataTransferEncryptorMessageProto{}
110		// err = readPrefixedMessage(bytes.NewReader(b[4:]), msg)
111		// require.NoError(t, err)
112		// assert.Equal(t, hdfs.DataTransferEncryptorMessageProto_SUCCESS.Enum(), msg.Status)
113
114		// the server then responds with the initial challenge
115		resp := &hdfs.DataTransferEncryptorMessageProto{}
116		resp.Status = hdfs.DataTransferEncryptorMessageProto_SUCCESS.Enum()
117		resp.Payload = []byte(`username="388373981 BP-529865118-10.129.176.136-1582635112897 eQzDpjF/W9c=", realm="0", nonce="8iQSCAmYohP0K4dBX4Z2cxYC4CFJjfVp3aATEHNN", qop="auth-conf", cipher="rc4"`)
118		data, err := makePrefixedMessage(resp)
119		require.NoError(t, err)
120
121		server.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
122		_, err = server.Write(data)
123		require.NoError(t, err)
124
125		server.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
126		msg := &hdfs.DataTransferEncryptorMessageProto{}
127		err = readPrefixedMessage(server, msg)
128		require.NoError(t, err)
129
130		// our client should respond appropriately with the correct challenge response
131		assert.Equal(t, `username="388373981 BP-529865118-10.129.176.136-1582635112897 eQzDpjF/W9c=", realm="0", nonce="8iQSCAmYohP0K4dBX4Z2cxYC4CFJjfVp3aATEHNN", cnonce="dqNZ/hGooPsuK3iWPeDFeQ==", nc=00000001, qop=auth-conf, digest-uri="hdfs/0", response=c4669d46e21197923d3e98e53e6dd543, charset=utf-8, cipher=rc4`,
132			string(msg.Payload))
133
134		// finally the server responds with a rspauth and the cipher information
135		msg.Status = hdfs.DataTransferEncryptorMessageProto_SUCCESS.Enum()
136		msg.Payload = []byte("rspauth=830abc648a95a91e9ff1d594cdbca222")
137		opt := &hdfs.CipherOptionProto{}
138		opt.Suite = hdfs.CipherSuiteProto_AES_CTR_NOPADDING.Enum()
139		// these are the encoded cipher keys, InKey and OutKey will need to be
140		// decoded by the client before they can be used
141		opt.InKey = []byte{
142			0xbb, 0x5e, 0xcf, 0x32, 0x55, 0xe7, 0x59, 0x5b,
143			0xe5, 0xf9, 0xd7, 0xd2, 0x1e, 0x29, 0xb8, 0xeb,
144			0x04, 0x93, 0x8b, 0x74, 0x58, 0xbd, 0x77, 0x79,
145			0x8f, 0xfd, 0xf2, 0xe3, 0xb9, 0xbd, 0x70, 0xa7,
146			0x3b, 0xbc, 0xf4, 0xa2, 0xf3, 0xa1, 0x8a, 0x51,
147			0x83, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}
148		opt.InIv = []byte{
149			0xc9, 0x50, 0x0c, 0xa0, 0xcc, 0x10, 0x13, 0x37,
150			0x06, 0x21, 0x1e, 0x76, 0xf8, 0x64, 0xea, 0x37,
151		}
152		opt.OutKey = []byte{
153			0x63, 0x50, 0x62, 0xfe, 0x18, 0xed, 0xb9, 0xf6,
154			0x27, 0x92, 0x45, 0x6f, 0xa6, 0xdc, 0x9c, 0x6e,
155			0x71, 0x5e, 0x4a, 0xcb, 0x92, 0x97, 0xa4, 0xcb,
156			0xa1, 0x56, 0xe3, 0x4f, 0x25, 0x5d, 0xfb, 0xd1,
157			0x65, 0x81, 0x12, 0xe5, 0xd9, 0xe0, 0x12, 0x33,
158			0x53, 0xef, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
159		}
160		opt.OutIv = []byte{
161			0xe2, 0xcb, 0xcd, 0xe2, 0x03, 0x20, 0x8e, 0x37,
162			0x74, 0x02, 0x11, 0x66, 0x66, 0x9c, 0xd9, 0xa0,
163		}
164		msg.CipherOption = []*hdfs.CipherOptionProto{opt}
165
166		data, _ = makePrefixedMessage(msg)
167		_, err = server.Write(data)
168		require.NoError(t, err)
169
170		actualInKey := []byte{
171			0xe6, 0xfb, 0x59, 0xb1, 0x7e, 0xd7, 0xdf, 0x11,
172			0x3a, 0xf3, 0xac, 0x62, 0xef, 0xc0, 0x86, 0x3d,
173			0x92, 0x74, 0x7d, 0xd9, 0x3f, 0xae, 0xbc, 0x62,
174			0xf2, 0xb5, 0x68, 0x7b, 0x10, 0x6f, 0xa3, 0x53,
175		}
176		actualInIv := opt.OutIv
177		actualOutKey := []byte{
178			0x7b, 0x91, 0xb6, 0x66, 0x60, 0xab, 0xff, 0x8c,
179			0x80, 0x48, 0xe2, 0x0c, 0xef, 0x24, 0x0c, 0xc9,
180			0x0b, 0xc5, 0xd7, 0x92, 0x14, 0x9c, 0x6f, 0xea,
181			0xb9, 0x12, 0x1a, 0x48, 0xc4, 0x85, 0x5f, 0x43,
182		}
183		actualOutIv := opt.InIv
184
185		wrapped, _ := newAesConn(server, actualInKey, actualOutKey, actualInIv, actualOutIv)
186
187		// Receive an encrypted value.
188		wrapped.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
189		b = make([]byte, 4)
190		_, err = wrapped.Read(b)
191		require.NoError(t, err)
192		assert.Equal(t, b, []byte{0xDE, 0xAD, 0xBE, 0xEF})
193	}()
194
195	wrapped, err := (&SaslDialer{Token: token, Key: key}).wrapDatanodeConn(client)
196	require.NoError(t, err)
197	defer wrapped.Close()
198
199	require.NoError(t, err)
200
201	// Send an encrypted value.
202	n, err := wrapped.Write([]byte{0xDE, 0xAD, 0xBE, 0xEF})
203	assert.NoError(t, err)
204	assert.Equal(t, 4, n)
205
206	<-serverDone
207}
208