X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fxwt%2FHTTP.java;h=84e5abb7c01f6472818ba185450cac423505385b;hb=9d07963a45f2147a62d8897e9c4245c224d98ccb;hp=22a07611e7777ca5bc9a99e6b09e42c9b0855ccf;hpb=d60f4e3515367bcbc1d234b0d601fc3aefaa91bc;p=org.ibex.core.git diff --git a/src/org/xwt/HTTP.java b/src/org/xwt/HTTP.java index 22a0761..84e5abb 100644 --- a/src/org/xwt/HTTP.java +++ b/src/org/xwt/HTTP.java @@ -1,11 +1,11 @@ -// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] +// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] package org.xwt; import java.net.*; import java.io.*; import java.util.*; +import org.xwt.js.*; import org.xwt.util.*; -import org.mozilla.javascript.*; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.crypto.digests.*; @@ -51,15 +51,18 @@ public class HTTP { */ Semaphore okToRecieve = null; + /** true iff this is the first request to be made on this socket */ + boolean firstRequest = true; + /** cache for resolveAndCheckIfFirewalled() */ static Hashtable resolvedHosts = new Hashtable(); - /** if any request encounters an IOException, the entire HTTP connection is invalidated */ - boolean invalid = false; - /** true iff we are allowed to skip the resolve check (only allowed when we're downloading the PAC script) */ boolean skipResolveCheck = false; + /** true iff we're using a proxy */ + boolean proxied = false; + // Public Methods //////////////////////////////////////////////////////////////////////////////////////// @@ -70,27 +73,26 @@ public class HTTP { } /** Performs an HTTP GET request */ - public HTTPInputStream GET() throws IOException { return makeRequest(null, null); } + public InputStream GET() throws IOException { return makeRequest(null, null); } /** Performs an HTTP POST request; content is appended to the headers (so it should include a blank line to delimit the beginning of the body) */ - public HTTPInputStream POST(String contentType, String content) throws IOException { return makeRequest(contentType, content); } + public InputStream POST(String contentType, String content) throws IOException { return makeRequest(contentType, content); } /** * This method isn't synchronized; however, only one thread can be in the inner synchronized block at a time, and the rest of * the method is protected by in-order one-at-a-time semaphore lock-steps */ - private HTTPInputStream makeRequest(String contentType, String content) throws IOException { + private InputStream makeRequest(String contentType, String content) throws IOException { // Step 1: send the request and establish a semaphore to stop any requests that pipeline after us Semaphore blockOn = null; Semaphore releaseMe = null; synchronized(this) { - if (invalid) throw new HTTPException("connection failed on a previous pipelined call"); try { connect(); sendRequest(contentType, content); } catch (IOException e) { - invalid = true; + reset(); throw e; } blockOn = okToRecieve; @@ -101,12 +103,18 @@ public class HTTP { boolean doRelease = true; try { if (blockOn != null) blockOn.block(); - if (invalid) throw new HTTPException("connection failed on a previous pipelined call"); + + // previous call wrecked the socket connection, but we already sent our request, so we can't just retry -- + // this could cause the server to receive the request twice, which could be bad (think of the case where the + // server call causes Amazon.com to ship you an item with one-click purchasing). + if (sock == null) + throw new HTTPException("a previous pipelined call messed up the socket"); Hashtable h = in == null ? null : parseHeaders(in); if (h == null) { + if (firstRequest) throw new HTTPException("server closed the socket with no response"); // sometimes the server chooses to close the stream between requests - in = null; sock = null; + reset(); releaseMe.release(); return makeRequest(contentType, content); } @@ -120,7 +128,7 @@ public class HTTP { if (h.get("HTTP").equals("1.0") && h.get("content-length") == null) { if (Log.on) Log.log(this, "proxy returned an HTTP/1.0 reply with no content-length..."); - in = null; sock = null; + reset(); } else { int cl = h.get("content-length") == null ? -1 : Integer.parseInt(h.get("content-length").toString()); new HTTPInputStream(in, cl, releaseMe).close(); @@ -132,7 +140,8 @@ public class HTTP { if (h.get("HTTP").equals("1.0") && h.get("content-length") == null) throw new HTTPException("XWT does not support HTTP/1.0 servers which fail to return the Content-Length header"); int cl = h.get("content-length") == null ? -1 : Integer.parseInt(h.get("content-length").toString()); - HTTPInputStream ret = new HTTPInputStream(in, cl, releaseMe); + InputStream ret = new HTTPInputStream(in, cl, releaseMe); + if ("gzip".equals(h.get("content-encoding"))) ret = new java.util.zip.GZIPInputStream(ret); doRelease = false; return ret; @@ -141,7 +150,7 @@ public class HTTP { } - } catch (IOException e) { invalid = true; throw e; + } catch (IOException e) { reset(); throw e; } finally { if (doRelease) releaseMe.release(); } } @@ -150,14 +159,11 @@ public class HTTP { // Safeguarded DNS Resolver /////////////////////////////////////////////////////////////////////////// /** - * resolves the hostname and returns it as a string in the form "x.y.z.w", except for the special case "xmlrpc.xwt.org". + * resolves the hostname and returns it as a string in the form "x.y.z.w" * @throws HTTPException if the host falls within a firewalled netblock */ private void resolveAndCheckIfFirewalled(String host) throws HTTPException { - // special case - if (host.equals("xmlrpc.xwt.org")) return; - // cached if (resolvedHosts.get(host) != null) return; @@ -175,17 +181,8 @@ public class HTTP { return; } catch (UnknownHostException uhe) { } - // resolve using xmlrpc.xwt.org - if (Platform.detectProxy() == null) throw new HTTPException("could not resolve hostname \"" + host + "\" and no proxy configured"); - if (Log.on) Log.log(this, " could not resolve host " + host + "; using xmlrpc.xwt.org to ensure security"); - try { - Object ret = new XMLRPC("http://xmlrpc.xwt.org/RPC2/", "dns.resolve").call(new Object[] { host }); - if (ret == null || !(ret instanceof String)) throw new Exception(" xmlrpc.xwt.org returned non-String: " + ret); - resolvedHosts.put(host, ret); - return; - } catch (Throwable e) { - throw new HTTPException("exception while attempting to use xmlrpc.xwt.org to resolve " + host + ": " + e); - } + if (Platform.detectProxy() == null) + throw new HTTPException("could not resolve hostname \"" + host + "\" and no proxy configured"); } @@ -279,11 +276,14 @@ public class HTTP { } /** executes the PAC script and dispatches a call to one of the other attempt methods based on the result */ - public Socket attemptPAC(Function pacFunc) { + public Socket attemptPAC(org.xwt.js.JSCallable pacFunc) { if (Log.verbose) Log.log(this, "evaluating PAC script"); String pac = null; try { - Object obj = pacFunc.call(Context.enter(), Proxy.proxyAutoConfigRootScope, null, new Object[] { url.toString(), url.getHost() }); + org.xwt.js.JSArray args = new org.xwt.js.JSArray(); + args.addElement(url.toString()); + args.addElement(url.getHost()); + Object obj = pacFunc.call(args); if (Log.verbose) Log.log(this, " PAC script returned \"" + obj + "\""); pac = obj.toString(); } catch (Throwable e) { @@ -318,6 +318,10 @@ public class HTTP { // Everything Else //////////////////////////////////////////////////////////////////////////// private synchronized void connect() throws IOException { + if (originalUrl.equals("stdio:")) { + in = new BufferedInputStream(System.in); + return; + } if (sock != null) { if (in == null) in = new BufferedInputStream(sock.getInputStream()); return; @@ -349,10 +353,16 @@ public class HTTP { if (Log.verbose) Log.log(this, "creating HTTP object for connection to " + host + ":" + port); Proxy pi = Platform.detectProxy(); - if (sock == null && pi != null && pi.proxyAutoConfigFunction != null) sock = attemptPAC(pi.proxyAutoConfigFunction); - if (sock == null && pi != null && ssl && pi.httpsProxyHost != null) sock = attemptHttpProxy(pi.httpsProxyHost, pi.httpsProxyPort); - if (sock == null && pi != null && pi.httpProxyHost != null) sock = attemptHttpProxy(pi.httpProxyHost, pi.httpProxyPort); - if (sock == null && pi != null && pi.socksProxyHost != null) sock = attemptSocksProxy(pi.socksProxyHost, pi.socksProxyPort); + OUTER: do { + if (pi != null) { + for(int i=0; i length) len = length; - int ret = super.read(b, off, len); - length -= ret; - good = true; + int ret = b == null ? (int)super.skip(len) : super.read(b, off, len); + if (ret >= 0) { + length -= ret; + good = true; + } return ret; } finally { - if (!good) invalid = true; + if (!good) reset(); } } @@ -530,6 +584,12 @@ public class HTTP { } } + void reset() { + firstRequest = true; + in = null; + sock = null; + } + // Misc Helpers /////////////////////////////////////////////////////////////////////////////////// @@ -609,4 +669,715 @@ public class HTTP { return ret; } + + // Proxy /////////////////////////////////////////////////////////// + + /** encapsulates most of the proxy logic; some is shared in HTTP.java */ + public static class Proxy { + + public Proxy() { } + + /** the HTTP Proxy host to use */ + public String httpProxyHost = null; + + /** the HTTP Proxy port to use */ + public int httpProxyPort = -1; + + /** if a seperate proxy should be used for HTTPS, this is the hostname; otherwise, httpProxyHost is used */ + public String httpsProxyHost = null; + + /** if a seperate proxy should be used for HTTPS, this is the port */ + public int httpsProxyPort = -1; + + /** the SOCKS Proxy Host to use */ + public String socksProxyHost = null; + + /** the SOCKS Proxy Port to use */ + public int socksProxyPort = -1; + + /** hosts to be excluded from proxy use; wildcards permitted */ + public String[] excluded = null; + + /** the PAC script */ + public JSCallable proxyAutoConfigJSFunction = null; + + public static Proxy detectProxyViaManual() { + Proxy ret = new Proxy(); + + ret.httpProxyHost = Platform.getEnv("http_proxy"); + if (ret.httpProxyHost != null) { + if (ret.httpProxyHost.startsWith("http://")) ret.httpProxyHost = ret.httpProxyHost.substring(7); + if (ret.httpProxyHost.endsWith("/")) ret.httpProxyHost = ret.httpProxyHost.substring(0, ret.httpProxyHost.length() - 1); + if (ret.httpProxyHost.indexOf(':') != -1) { + ret.httpProxyPort = Integer.parseInt(ret.httpProxyHost.substring(ret.httpProxyHost.indexOf(':') + 1)); + ret.httpProxyHost = ret.httpProxyHost.substring(0, ret.httpProxyHost.indexOf(':')); + } else { + ret.httpProxyPort = 80; + } + } + + ret.httpsProxyHost = Platform.getEnv("https_proxy"); + if (ret.httpsProxyHost != null) { + if (ret.httpsProxyHost.startsWith("https://")) ret.httpsProxyHost = ret.httpsProxyHost.substring(7); + if (ret.httpsProxyHost.endsWith("/")) ret.httpsProxyHost = ret.httpsProxyHost.substring(0, ret.httpsProxyHost.length() - 1); + if (ret.httpsProxyHost.indexOf(':') != -1) { + ret.httpsProxyPort = Integer.parseInt(ret.httpsProxyHost.substring(ret.httpsProxyHost.indexOf(':') + 1)); + ret.httpsProxyHost = ret.httpsProxyHost.substring(0, ret.httpsProxyHost.indexOf(':')); + } else { + ret.httpsProxyPort = 80; + } + } + + ret.socksProxyHost = Platform.getEnv("socks_proxy"); + if (ret.socksProxyHost != null) { + if (ret.socksProxyHost.startsWith("socks://")) ret.socksProxyHost = ret.socksProxyHost.substring(7); + if (ret.socksProxyHost.endsWith("/")) ret.socksProxyHost = ret.socksProxyHost.substring(0, ret.socksProxyHost.length() - 1); + if (ret.socksProxyHost.indexOf(':') != -1) { + ret.socksProxyPort = Integer.parseInt(ret.socksProxyHost.substring(ret.socksProxyHost.indexOf(':') + 1)); + ret.socksProxyHost = ret.socksProxyHost.substring(0, ret.socksProxyHost.indexOf(':')); + } else { + ret.socksProxyPort = 80; + } + } + + String noproxy = Platform.getEnv("no_proxy"); + if (noproxy != null) { + StringTokenizer st = new StringTokenizer(noproxy, ","); + ret.excluded = new String[st.countTokens()]; + for(int i=0; st.hasMoreTokens(); i++) ret.excluded[i] = st.nextToken(); + } + + if (ret.httpProxyHost == null && ret.socksProxyHost == null) return null; + return ret; + } + + public static JSScope proxyAutoConfigRootJSScope = new ProxyAutoConfigRootJSScope(); + public static JSCallable getProxyAutoConfigJSFunction(String url) { + try { + BufferedReader br = new BufferedReader(new InputStreamReader(new HTTP(url, true).GET())); + String s = null; + String script = ""; + while((s = br.readLine()) != null) script += s + "\n"; + if (Log.on) Log.log(Proxy.class, "successfully retrieved WPAD PAC:"); + if (Log.on) Log.log(Proxy.class, script); + + // MS CARP hack + Vector carpHosts = new Vector(); + for(int i=0; i= arr.length) return true; + for(int i=0; i= d1 && day <= d2) || + (d1 > d2 && (day >= d1 || day <= d2))) ? + Boolean.TRUE : Boolean.FALSE; + } + }; + + private static final JSCallable dateRange = new JSCallable() { + public Object call(org.xwt.js.JSArray args) throws JS.Exn { + throw new JS.Exn("XWT does not support dateRange() in PAC scripts"); + } + }; + + private static final JSCallable timeRange = new JSCallable() { + public Object call(org.xwt.js.JSArray args) throws JS.Exn { + throw new JS.Exn("XWT does not support timeRange() in PAC scripts"); + } + }; + + } + + /** + * An implementation of Microsoft's proprietary NTLM authentication protocol. This code was derived from Eric + * Glass's work, and is copyright as follows: + * + * Copyright (c) 2003 Eric Glass (eglass1 at comcast.net). + * + * Permission to use, copy, modify, and distribute this document for any purpose and without any fee is hereby + * granted, provided that the above copyright notice and this list of conditions appear in all copies. + * The most current version of this document may be obtained from http://davenport.sourceforge.net/ntlm.html . + */ + public static class NTLM { + + public static final byte[] type1 = new byte[] { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00 }; + + /** + * Calculates the NTLM Response for the given challenge, using the + * specified password. + * + * @param password The user's password. + * @param challenge The Type 2 challenge from the server. + * + * @return The NTLM Response. + */ + public static byte[] getNTLMResponse(String password, byte[] challenge) + throws Exception { + byte[] ntlmHash = ntlmHash(password); + return lmResponse(ntlmHash, challenge); + } + + /** + * Calculates the LM Response for the given challenge, using the specified + * password. + * + * @param password The user's password. + * @param challenge The Type 2 challenge from the server. + * + * @return The LM Response. + */ + public static byte[] getLMResponse(String password, byte[] challenge) + throws Exception { + byte[] lmHash = lmHash(password); + return lmResponse(lmHash, challenge); + } + + /** + * Calculates the NTLMv2 Response for the given challenge, using the + * specified authentication target, username, password, target information + * block, and client challenge. + * + * @param target The authentication target (i.e., domain). + * @param user The username. + * @param password The user's password. + * @param targetInformation The target information block from the Type 2 + * message. + * @param challenge The Type 2 challenge from the server. + * @param clientChallenge The random 8-byte client challenge. + * + * @return The NTLMv2 Response. + */ + public static byte[] getNTLMv2Response(String target, String user, + String password, byte[] targetInformation, byte[] challenge, + byte[] clientChallenge) throws Exception { + byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); + byte[] blob = createBlob(targetInformation, clientChallenge); + return lmv2Response(ntlmv2Hash, blob, challenge); + } + + /** + * Calculates the LMv2 Response for the given challenge, using the + * specified authentication target, username, password, and client + * challenge. + * + * @param target The authentication target (i.e., domain). + * @param user The username. + * @param password The user's password. + * @param challenge The Type 2 challenge from the server. + * @param clientChallenge The random 8-byte client challenge. + * + * @return The LMv2 Response. + */ + public static byte[] getLMv2Response(String target, String user, + String password, byte[] challenge, byte[] clientChallenge) + throws Exception { + byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); + return lmv2Response(ntlmv2Hash, clientChallenge, challenge); + } + + /** + * Calculates the NTLM2 Session Response for the given challenge, using the + * specified password and client challenge. + * + * @param password The user's password. + * @param challenge The Type 2 challenge from the server. + * @param clientChallenge The random 8-byte client challenge. + * + * @return The NTLM2 Session Response. This is placed in the NTLM + * response field of the Type 3 message; the LM response field contains + * the client challenge, null-padded to 24 bytes. + */ + public static byte[] getNTLM2SessionResponse(String password, + byte[] challenge, byte[] clientChallenge) throws Exception { + byte[] ntlmHash = ntlmHash(password); + MD5Digest md5 = new MD5Digest(); + md5.update(challenge, 0, challenge.length); + md5.update(clientChallenge, 0, clientChallenge.length); + byte[] sessionHash = new byte[8]; + byte[] md5_out = new byte[md5.getDigestSize()]; + md5.doFinal(md5_out, 0); + System.arraycopy(md5_out, 0, sessionHash, 0, 8); + return lmResponse(ntlmHash, sessionHash); + } + + /** + * Creates the LM Hash of the user's password. + * + * @param password The password. + * + * @return The LM Hash of the given password, used in the calculation + * of the LM Response. + */ + private static byte[] lmHash(String password) throws Exception { + /* + byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII"); + int length = java.lang.Math.min(oemPassword.length, 14); + byte[] keyBytes = new byte[14]; + System.arraycopy(oemPassword, 0, keyBytes, 0, length); + Key lowKey = createDESKey(keyBytes, 0); + Key highKey = createDESKey(keyBytes, 7); + byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII"); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + byte[] lowHash = des.doFinal(magicConstant); + des.init(Cipher.ENCRYPT_MODE, highKey); + byte[] highHash = des.doFinal(magicConstant); + byte[] lmHash = new byte[16]; + System.arraycopy(lowHash, 0, lmHash, 0, 8); + System.arraycopy(highHash, 0, lmHash, 8, 8); + return lmHash; + */ + return null; + } + + /** + * Creates the NTLM Hash of the user's password. + * + * @param password The password. + * + * @return The NTLM Hash of the given password, used in the calculation + * of the NTLM Response and the NTLMv2 and LMv2 Hashes. + */ + private static byte[] ntlmHash(String password) throws Exception { + byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); + MD4Digest md4 = new MD4Digest(); + md4.update(unicodePassword, 0, unicodePassword.length); + byte[] ret = new byte[md4.getDigestSize()]; + return ret; + } + + /** + * Creates the NTLMv2 Hash of the user's password. + * + * @param target The authentication target (i.e., domain). + * @param user The username. + * @param password The password. + * + * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 + * and LMv2 Responses. + */ + private static byte[] ntlmv2Hash(String target, String user, + String password) throws Exception { + byte[] ntlmHash = ntlmHash(password); + String identity = user.toUpperCase() + target.toUpperCase(); + return hmacMD5(identity.getBytes("UnicodeLittleUnmarked"), ntlmHash); + } + + /** + * Creates the LM Response from the given hash and Type 2 challenge. + * + * @param hash The LM or NTLM Hash. + * @param challenge The server challenge from the Type 2 message. + * + * @return The response (either LM or NTLM, depending on the provided + * hash). + */ + private static byte[] lmResponse(byte[] hash, byte[] challenge) + throws Exception { + /* + byte[] keyBytes = new byte[21]; + System.arraycopy(hash, 0, keyBytes, 0, 16); + Key lowKey = createDESKey(keyBytes, 0); + Key middleKey = createDESKey(keyBytes, 7); + Key highKey = createDESKey(keyBytes, 14); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + byte[] lowResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, middleKey); + byte[] middleResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, highKey); + byte[] highResponse = des.doFinal(challenge); + byte[] lmResponse = new byte[24]; + System.arraycopy(lowResponse, 0, lmResponse, 0, 8); + System.arraycopy(middleResponse, 0, lmResponse, 8, 8); + System.arraycopy(highResponse, 0, lmResponse, 16, 8); + return lmResponse; + */ + return null; + } + + /** + * Creates the LMv2 Response from the given hash, client data, and + * Type 2 challenge. + * + * @param hash The NTLMv2 Hash. + * @param clientData The client data (blob or client challenge). + * @param challenge The server challenge from the Type 2 message. + * + * @return The response (either NTLMv2 or LMv2, depending on the + * client data). + */ + private static byte[] lmv2Response(byte[] hash, byte[] clientData, + byte[] challenge) throws Exception { + byte[] data = new byte[challenge.length + clientData.length]; + System.arraycopy(challenge, 0, data, 0, challenge.length); + System.arraycopy(clientData, 0, data, challenge.length, + clientData.length); + byte[] mac = hmacMD5(data, hash); + byte[] lmv2Response = new byte[mac.length + clientData.length]; + System.arraycopy(mac, 0, lmv2Response, 0, mac.length); + System.arraycopy(clientData, 0, lmv2Response, mac.length, + clientData.length); + return lmv2Response; + } + + /** + * Creates the NTLMv2 blob from the given target information block and + * client challenge. + * + * @param targetInformation The target information block from the Type 2 + * message. + * @param clientChallenge The random 8-byte client challenge. + * + * @return The blob, used in the calculation of the NTLMv2 Response. + */ + private static byte[] createBlob(byte[] targetInformation, + byte[] clientChallenge) { + byte[] blobSignature = new byte[] { + (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 + }; + byte[] reserved = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }; + byte[] unknown1 = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }; + byte[] unknown2 = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }; + long time = System.currentTimeMillis(); + time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. + time *= 10000; // tenths of a microsecond. + // convert to little-endian byte array. + byte[] timestamp = new byte[8]; + for (int i = 0; i < 8; i++) { + timestamp[i] = (byte) time; + time >>>= 8; + } + byte[] blob = new byte[blobSignature.length + reserved.length + + timestamp.length + clientChallenge.length + + unknown1.length + targetInformation.length + + unknown2.length]; + int offset = 0; + System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); + offset += blobSignature.length; + System.arraycopy(reserved, 0, blob, offset, reserved.length); + offset += reserved.length; + System.arraycopy(timestamp, 0, blob, offset, timestamp.length); + offset += timestamp.length; + System.arraycopy(clientChallenge, 0, blob, offset, + clientChallenge.length); + offset += clientChallenge.length; + System.arraycopy(unknown1, 0, blob, offset, unknown1.length); + offset += unknown1.length; + System.arraycopy(targetInformation, 0, blob, offset, + targetInformation.length); + offset += targetInformation.length; + System.arraycopy(unknown2, 0, blob, offset, unknown2.length); + return blob; + } + + /** + * Calculates the HMAC-MD5 hash of the given data using the specified + * hashing key. + * + * @param data The data for which the hash will be calculated. + * @param key The hashing key. + * + * @return The HMAC-MD5 hash of the given data. + */ + private static byte[] hmacMD5(byte[] data, byte[] key) throws Exception { + byte[] ipad = new byte[64]; + byte[] opad = new byte[64]; + for (int i = 0; i < 64; i++) { + ipad[i] = (byte) 0x36; + opad[i] = (byte) 0x5c; + } + for (int i = key.length - 1; i >= 0; i--) { + ipad[i] ^= key[i]; + opad[i] ^= key[i]; + } + byte[] content = new byte[data.length + 64]; + System.arraycopy(ipad, 0, content, 0, 64); + System.arraycopy(data, 0, content, 64, data.length); + MD5Digest md5 = new MD5Digest(); + md5.update(content, 0, content.length); + data = new byte[md5.getDigestSize()]; + md5.doFinal(data, 0); + content = new byte[data.length + 64]; + System.arraycopy(opad, 0, content, 0, 64); + System.arraycopy(data, 0, content, 64, data.length); + md5 = new MD5Digest(); + md5.update(content, 0, content.length); + byte[] ret = new byte[md5.getDigestSize()]; + md5.doFinal(ret, 0); + return ret; + } + + /** + * Creates a DES encryption key from the given key material. + * + * @param bytes A byte array containing the DES key material. + * @param offset The offset in the given byte array at which + * the 7-byte key material starts. + * + * @return A DES encryption key created from the key material + * starting at the specified offset in the given byte array. + */ + /* + private static Key createDESKey(byte[] bytes, int offset) { + byte[] keyBytes = new byte[7]; + System.arraycopy(bytes, offset, keyBytes, 0, 7); + byte[] material = new byte[8]; + material[0] = keyBytes[0]; + material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); + material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); + material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); + material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); + material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); + material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); + material[7] = (byte) (keyBytes[6] << 1); + oddParity(material); + return new SecretKeySpec(material, "DES"); + } + */ + + /** + * Applies odd parity to the given byte array. + * + * @param bytes The data whose parity bits are to be adjusted for + * odd parity. + */ + private static void oddParity(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ + (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ + (b >>> 1)) & 0x01) == 0; + if (needsParity) { + bytes[i] |= (byte) 0x01; + } else { + bytes[i] &= (byte) 0xfe; + } + } + } + + } + } }