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