View Javadoc
1   package com.ericsson.research.transport.ws.spi;
2   
3   /*
4    * ##_BEGIN_LICENSE_##
5    * Transport Abstraction Package (trap)
6    * ----------
7    * Copyright (C) 2014 Ericsson AB
8    * ----------
9    * Redistribution and use in source and binary forms, with or without modification,
10   * are permitted provided that the following conditions are met:
11   * 
12   * 1. Redistributions of source code must retain the above copyright notice, this
13   *    list of conditions and the following disclaimer.
14   * 
15   * 2. Redistributions in binary form must reproduce the above copyright notice,
16   *    this list of conditions and the following disclaimer in the documentation
17   *    and/or other materials provided with the distribution.
18   * 
19   * 3. Neither the name of the Ericsson AB nor the names of its contributors
20   *    may be used to endorse or promote products derived from this software without
21   *    specific prior written permission.
22   * 
23   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26   * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
31   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
32   * OF THE POSSIBILITY OF SUCH DAMAGE.
33   * ##_END_LICENSE_##
34   */
35  
36  import java.io.IOException;
37  import java.io.OutputStream;
38  import java.io.UnsupportedEncodingException;
39  import java.security.MessageDigest;
40  import java.security.NoSuchAlgorithmException;
41  
42  import com.ericsson.research.transport.ws.WSException;
43  
44  public class WSHixie76Handshake extends WSAbstractHandshake implements WSConstants {
45  
46  	private int num1,num2;
47  	private String key3;
48  	
49  	public WSHixie76Handshake(WSAbstractProtocol protocol) {
50  		super(protocol);
51  		if(protocol.client)
52  			expectedHandshakeBodyLength = 16;
53  		else
54  			expectedHandshakeBodyLength = 8;
55  	}
56  
57  	public void sendHandshake(OutputStream os) throws IOException {
58  		StringBuffer h = new StringBuffer();
59  		if(protocol.client) {
60  			h.append(GET);
61  			h.append(" ");
62  			h.append(protocol.resource);
63  			h.append(" ");
64  			h.append(HTTP11);
65  			h.append(WSHixie75Handshake.COMMON_PART);
66  			h.append(HOST_HEADER);
67  			h.append(COLON_SPACE);
68  			h.append(protocol.host);
69  			if ((protocol.securityContext==null && protocol.port!=80) || (protocol.securityContext!=null && protocol.port!=443)) {
70  				h.append(":");
71  				h.append(protocol.port);
72  			}
73  			h.append(CRLF);
74  			h.append(ORIGIN_HEADER);
75  			h.append(COLON_SPACE);
76  			h.append(protocol.origin);
77  			int spaces1 = (int) (Math.random()*12+1);
78  			int spaces2 = (int) (Math.random()*12+1);
79  			int max1 = Integer.MAX_VALUE / spaces1;
80  			int max2 = Integer.MAX_VALUE / spaces2;
81  			num1 = (int) (Math.random() * max1 + 1);
82  			num2 = (int) (Math.random() * max2 + 1);
83  			String spc1 = generateString(spaces1, 0x20, 0x20);
84  			String spc2 = generateString(spaces2, 0x20, 0x20);
85  			String gbg1 = generateString((int) (Math.random()*12+1), 0x3A, 0x7E);
86  			String gbg2 = generateString((int) (Math.random()*12+1), 0x3A, 0x7E);
87  			String key1 = splice(splice(String.valueOf(num1*spaces1), spc1), gbg1);
88  			String key2 = splice(splice(String.valueOf(num2*spaces2), spc2), gbg2);
89  			h.append(CRLF);
90  			h.append(SEC_WEBSOCKET_KEY1_HEADER);
91  			h.append(COLON_SPACE);
92  			h.append(key1);
93  			h.append(CRLF);
94  			h.append(SEC_WEBSOCKET_KEY2_HEADER);
95  			h.append(COLON_SPACE);
96  			h.append(key2);
97  			h.append(CRLFCRLF);
98  			key3 = generateString(8, 0x00, 0xFF);
99  			h.append(key3);
100 		} else {
101 			h.append(WSHixie75Handshake.SERVER_PREAMBLE);
102 			h.append(WSHixie75Handshake.COMMON_PART);
103 			h.append(SEC_WEBSOCKET_ORIGIN_HEADER);
104 			h.append(COLON_SPACE);
105 			h.append(protocol.origin);
106 			h.append(CRLF);
107 			h.append(SEC_WEBSOCKET_LOCATION_HEADER);
108 			h.append(COLON_SPACE);
109 			h.append(protocol.location);
110 			h.append(CRLFCRLF);
111 		}
112 		synchronized(os) {
113 			os.write(h.toString().getBytes(ISO_8859_1));
114 			if(!protocol.client)
115 				os.write(body);
116 			os.flush();
117 		}
118 	}
119 
120 	private String splice(String src1, String src2) {
121 		StringBuffer sb = new StringBuffer(src1);
122 		for (int i = 0; i < src2.length(); i++) {
123 			int pos = (int) Math.round(Math.random() * sb.length());
124 			sb.insert(pos, src2.charAt(i));
125 		}
126 		return sb.toString();
127 	}
128 
129 	private String generateString(int length, int startChar, int endChar) {
130 		StringBuffer sb = new StringBuffer();
131 		for (int i = 0; i < length; i++) {
132 			int j = (int) (Math.floor(Math.random() * (endChar - startChar)) + startChar);
133 			sb.append((char)j);
134 		}
135 		return sb.toString();
136 	}
137 
138 	public void headersRead() throws WSException {
139 		if(protocol.client) {
140 			if (!protocol.origin.equals(getHeader(SEC_WEBSOCKET_ORIGIN_HEADER)))
141 				throw new WSException("Failed to match origin ("+protocol.origin+" != "+getHeader(SEC_WEBSOCKET_ORIGIN_HEADER));
142 			StringBuffer location = new StringBuffer();
143 			if(protocol.securityContext!=null)
144 				location.append(WSS_SCHEMA);
145 			else
146 				location.append(WS_SCHEMA);
147 			location.append(protocol.host);
148 			if((protocol.securityContext==null && protocol.port != 80) || (protocol.securityContext!=null && protocol.port != 443)) {
149 				location.append(":");
150 				location.append(protocol.port);
151 			}
152 			location.append(protocol.resource);
153 			if (!location.toString().equals(getHeader(SEC_WEBSOCKET_LOCATION_HEADER)))
154 				throw new WSException("Failed to match location ("+location+" != "+getHeader(SEC_WEBSOCKET_LOCATION_HEADER)+")");
155 		} else {
156 			if (getHeader(SEC_WEBSOCKET_KEY1_HEADER) == null || getHeader(SEC_WEBSOCKET_KEY2_HEADER) == null)
157 				throw new WSException("One of Sec-WebSocket-Key* headers was null");
158 			protocol.origin = getHeader(ORIGIN_HEADER);
159 			StringBuffer loc = new StringBuffer();
160 			if (protocol.securityContext!=null)
161 				loc.append(WSS_SCHEMA);
162 			else
163 				loc.append(WS_SCHEMA);
164 			loc.append(getHeader(HOST_HEADER));
165 			loc.append(protocol.resource);
166 			protocol.location = loc.toString();
167 		}
168 	}
169 
170 	public void bodyRead() throws WSException {
171 		if(protocol.client) {
172 			try {
173 				byte[] bs = key3.getBytes("ISO-8859-1");
174 				bs = createDigest(num1, num2, bs);
175 				for(int i = 0; i<bs.length; i++)
176 					if(body.length < i+1 || bs[i] != body[i])
177 						throw new WSException("Handshake result doesn't match");
178 			} catch (UnsupportedEncodingException e) {
179 				throw new WSException(e);
180 			}
181 		} else {
182 			// Count spaces
183 			int key1Spaces = countSpaces(getHeader(SEC_WEBSOCKET_KEY1_HEADER));
184 			int key2Spaces = countSpaces(getHeader(SEC_WEBSOCKET_KEY2_HEADER));
185 			if (key1Spaces <= 0 || key2Spaces <= 0)
186 				throw new WSException("Invalid number of spaces in handshake");
187 			// Remove non-number characters from the keys and parse them as integers
188 			long key1Num = Long.parseLong(getHeader(SEC_WEBSOCKET_KEY1_HEADER).replaceAll("[^0-9]", ""));
189 			long key2Num = Long.parseLong(getHeader(SEC_WEBSOCKET_KEY2_HEADER).replaceAll("[^0-9]", ""));
190 			long maxUint = Integer.MAX_VALUE;
191 			maxUint <<= 1;
192 			maxUint += 2;
193 			if (key1Num > maxUint || key2Num > maxUint)
194 				throw new WSException("Input values exceed permitted buffer size");
195 			// Divide the numbers as part of the spec.
196 			key1Num = key1Num / key1Spaces;
197 			key2Num = key2Num / key2Spaces;
198 			body = createDigest(key1Num, key2Num, body);
199 		}
200 	}
201 	
202 	private byte[] createDigest(long n1, long n2, byte[] n3) throws WSException {
203 		try {
204 			byte[] q = new byte[16];
205 			q[0] = (byte)((n1 & 0xFF000000) >> 24);
206 			q[1] = (byte)((n1 & 0xFF0000) >> 16);
207 			q[2] = (byte)((n1 & 0xFF00) >> 8);
208 			q[3] = (byte)((n1 & 0xFF));
209 			q[4] = (byte)((n2 & 0xFF000000) >> 24);
210 			q[5] = (byte)((n2 & 0xFF0000) >> 16);
211 			q[6] = (byte)((n2 & 0xFF00) >> 8);
212 			q[7] = (byte)((n2 & 0xFF));
213 			System.arraycopy(n3, 0, q, 8, 8);
214 			MessageDigest md = MessageDigest.getInstance("MD5");
215 			return md.digest(q);
216 		} catch(NoSuchAlgorithmException e) {
217 			throw new WSException(e);
218 		}
219 	}
220 	
221 	private int countSpaces(String source) {
222 		int j = 0;
223 		for (int i = 0; i < source.length(); i++)
224 			if (source.charAt(i) == ' ')
225 				j++;
226 		return j;
227 	}
228 	
229 }