/* * Copyrigth (C) 2010 Henrik Baastrup. * * Licensed under the GNU Lesser General Public License version 3; * you may not use this file except in compliance with the License. * You should have received a copy of the license together with this * file but can obtain a copy of the License at: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package javax.net.stun; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Used internaly by the @{link StunClient} * * @author Henrik Baastrup */ public class MessageHeader { public enum HeaderType { NOT_KNOWN, BINDING_REQUEST, BINDING_RESPONSE, BINDING_ERROR_RESPONSE, SHARED_SECRET_REQUEST, SHARED_SECRET_RESPONSE, SHARED_SECRET_ERROR_RESPONSE, SHARED_SECRET_VERIFY_REQUEST }; public static final int BINDING_REQUEST = 0x0001; public static final int BINDING_RESPONSE = 0x0101; public static final int BINDING_ERROR_RESPONSE = 0x0111; public static final int SHARED_SECRET_REQUEST = 0x0002; public static final int SHARED_SECRET_RESPONSE = 0x0102; public static final int SHARED_SECRET_ERROR_RESPONSE = 0x0112; public static final int SHARED_SECRET_VERIFY_REQUEST = 0x8102; private HeaderType type = HeaderType.NOT_KNOWN; byte[] tansactionId = new byte[16]; ArrayList<MessageAttribute> messageAttributes = new ArrayList<MessageAttribute>(); private boolean changePort = false; private boolean changeAddress = false; public MessageHeader() {} public MessageHeader(final HeaderType type) { this.type = type; } public MessageHeader(final int type) { setType(type); } public MessageHeader(MessageHeader head) { this(head.type); this.tansactionId = new byte[head.tansactionId.length]; for (int i=0; i<head.tansactionId.length; i++) this.tansactionId[i] = head.tansactionId[i]; for (MessageAttribute attr: head.messageAttributes) { MessageAttribute ma = new MessageAttribute(attr); this.addMessageAttribute(ma); } } public void setType(HeaderType arg0) {type = arg0;} public HeaderType getType() {return type;} public void setType(int arg0) { switch (arg0) { case BINDING_REQUEST: type = HeaderType.BINDING_REQUEST; break; case BINDING_RESPONSE: type = HeaderType.BINDING_RESPONSE; break; case BINDING_ERROR_RESPONSE: type = HeaderType.BINDING_ERROR_RESPONSE; break; case SHARED_SECRET_REQUEST: type = HeaderType.SHARED_SECRET_REQUEST; break; case SHARED_SECRET_RESPONSE: type = HeaderType.SHARED_SECRET_RESPONSE; break; case SHARED_SECRET_ERROR_RESPONSE: type = HeaderType.SHARED_SECRET_ERROR_RESPONSE; break; case SHARED_SECRET_VERIFY_REQUEST: type = HeaderType.SHARED_SECRET_VERIFY_REQUEST; break; } } public int getTypeAsInt() { switch (type) { case BINDING_REQUEST: return BINDING_REQUEST; case BINDING_RESPONSE: return BINDING_RESPONSE; case BINDING_ERROR_RESPONSE: return BINDING_ERROR_RESPONSE; case SHARED_SECRET_REQUEST: return SHARED_SECRET_REQUEST; case SHARED_SECRET_RESPONSE: return SHARED_SECRET_RESPONSE; case SHARED_SECRET_ERROR_RESPONSE: return SHARED_SECRET_ERROR_RESPONSE; case SHARED_SECRET_VERIFY_REQUEST: return SHARED_SECRET_VERIFY_REQUEST; } return 0; } public List<MessageAttribute> getMessageAttributes() {return messageAttributes;} public MessageAttribute getMessageAttribute(MessageAttribute.MessageAttributeType type) { for (MessageAttribute attr : messageAttributes) { if (attr.getType()==type) return attr; } return null; } public void addMessageAttribute(MessageAttribute attribute) { messageAttributes.add(attribute); } public void deleteMessageAttribute(MessageAttribute attribute) { messageAttributes.remove(attribute); } public void clearMessageAttributes() { messageAttributes.clear(); } public void genrateTransactionId() { long now = System.currentTimeMillis(); Random rand = new Random(); int r1 = rand.nextInt(); // int r2 = rand.nextInt(); // tansactionId[0] = (byte) ((0xff000000 & r2) >> 24); // tansactionId[1] = (byte) ((0x00ff0000 & r2) >> 16); // tansactionId[2] = (byte) ((0x0000ff00 & r2) >> 8); // tansactionId[3] = (byte) (0x000000ff & r2); //To not conflict with RFC-5389 we set the first fields to zerro tansactionId[0] = 0; tansactionId[1] = 0; tansactionId[2] = 0; tansactionId[3] = 0; // //RFC-5389 magic cookie // tansactionId[0] = 0x21; // tansactionId[1] = 0x12; // tansactionId[2] = (byte)0xa4; // tansactionId[3] = 0x42; tansactionId[4] = (byte) ((0xff00000000000000L & now) >> 60); tansactionId[5] = (byte) ((0x00ff000000000000L & now) >> 52); tansactionId[6] = (byte) ((0x0000ff0000000000L & now) >> 44); tansactionId[7] = (byte) ((0x000000ff00000000L & now) >> 34); tansactionId[8] = (byte) ((0x00000000ff000000L & now) >> 24); tansactionId[9] = (byte) ((0x0000000000ff0000L & now) >> 16); tansactionId[10] = (byte) ((0x000000000000ff00L & now) >> 8); tansactionId[11] = (byte) (0x00000000000000ffL & now); tansactionId[12] = (byte) ((0xff000000 & r1) >> 24); tansactionId[13] = (byte) ((0x00ff0000 & r1) >> 16); tansactionId[14] = (byte) ((0x0000ff00 & r1) >> 8); tansactionId[15] = (byte) (0x000000ff & r1); } public byte[] getTransactionId() { byte retArr[] = new byte[tansactionId.length]; for (int i=0; i<tansactionId.length; i++) retArr[i] = tansactionId[i]; return retArr; } public void setTransactionId(byte arg0[]) { int len = 16; if (arg0.length < 16) len = arg0.length; for (int i=0; i<len; i++) tansactionId[i] = arg0[i]; for (int i=len; i<16; i++) tansactionId[i] = 0; } public void setChangePort(boolean arg0) {changePort = arg0;} public boolean getChangePort() {return changePort;} public void setChangeAddress(boolean arg0) {changeAddress = arg0;} public boolean getChangeAddress() {return changeAddress;} public byte[] toBytes() throws IOException { return toBytesExcept(null); } public byte[] toBytesExcept(MessageAttribute.MessageAttributeType attributeType) throws IOException { int len = 0; for (MessageAttribute attr : messageAttributes) len += attr.getValueLength()+4; if (len > 0xffff) throw new IOException("To many attributes. Message length to long."); byte retBytes[] = new byte[len+20]; for (int i=0; i<retBytes.length; i++) retBytes[i] = 0; int intType = getTypeAsInt(); retBytes[0] = (byte) ((0xff00 & intType) >> 8); retBytes[1] = (byte) (0x00ff & intType); retBytes[2] = (byte) ((0xff00 & len) >> 8); retBytes[3] = (byte) (0x00ff & len); for (int i=0; i<16; i++) retBytes[i+4] = tansactionId[i]; int i = 20; for (MessageAttribute attr : messageAttributes) { if (attr.getType()==attributeType) continue; byte value[] = attr.toBytes(); for (int j=0; j<value.length; j++) retBytes[i++] = value[j]; } return retBytes; } public static MessageHeader create(byte buffer[]) throws IOException { if (buffer.length < 20) throw new IOException("Buffer to short to be a message."); int intType = (0x000000FF & ((int)buffer[0])) << 8; intType += (0x000000FF & ((int)buffer[1])); MessageHeader header = new MessageHeader(intType); byte transId[] = new byte[16]; for (int i=0; i<16; i++) transId[i] = buffer[4+i]; header.setTransactionId(transId); int length = (0x000000FF & ((int)buffer[2])) << 8; length += (0x000000FF & ((int)buffer[3])); int i = 20; while ((i-20) < (length)) { int t = (0x000000FF & ((int)buffer[i++])) << 8; t += (0x000000FF & ((int)buffer[i++])); int l = (0x000000FF & ((int)buffer[i++])) << 8; l += (0x000000FF & ((int)buffer[i++])); byte b[] = new byte[l]; for (int j=0; j<l; j++) b[j] = buffer[i++]; header.addMessageAttribute(new MessageAttribute(t, b)); } return header; } public int integrityCheck(byte password[]) { MessageAttribute messageIntegrity = getMessageAttribute(MessageAttribute.MessageAttributeType.MESSAGE_INTEGRITY); if (messageIntegrity==null) return 401; byte headerBytes[]; try { headerBytes = toBytes(); } catch (IOException ex) { return 400; } if (type==HeaderType.SHARED_SECRET_VERIFY_REQUEST) { //The origin was a Bindeing request headerBytes[0] = 0; headerBytes[1] = 1; } byte recievedHmac[] = messageIntegrity.toBytes(); //To calculate hmac we need the whole message except the MESSAGE-INTEGRITY attribute byte hmacHeaderPart[] = new byte[headerBytes.length-recievedHmac.length]; for (int i=0; i<hmacHeaderPart.length; i++) hmacHeaderPart[i] = headerBytes[i]; byte hmacCalc[] = Utils.hmac(password, hmacHeaderPart); if (hmacCalc.length+4 != recievedHmac.length) return 431; for (int i=0; i<hmacCalc.length; i++) if (hmacCalc[i]!=recievedHmac[i+4]) return 431; return 0; } }