1ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistpackage com.android.hotspot2.utils; 2ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 3ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport android.util.Base64; 4ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 5ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.ByteArrayInputStream; 6ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.IOException; 7ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.InputStream; 8ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.io.OutputStream; 9ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.net.URL; 10ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.nio.ByteBuffer; 11ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.nio.charset.Charset; 12ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.nio.charset.StandardCharsets; 13ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.security.GeneralSecurityException; 14ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.security.MessageDigest; 15ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.security.SecureRandom; 16ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.Collections; 17ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.HashMap; 18ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.HashSet; 19ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.LinkedHashMap; 20ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.Map; 21ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistimport java.util.Set; 22ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 23ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvistpublic class HTTPRequest implements HTTPMessage { 24ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static final Charset HeaderCharset = StandardCharsets.US_ASCII; 25ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static final int HTTPS_PORT = 443; 26ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 27ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private final String mMethodLine; 28ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private final Map<String, String> mHeaderFields; 29ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private final byte[] mBody; 30ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 31ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public HTTPRequest(Method method, URL url) { 32ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist this(null, null, method, url, null, false); 33ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 34ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 35ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public HTTPRequest(String payload, Charset charset, Method method, URL url, String contentType, 36ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist boolean base64) { 37ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mBody = payload != null ? payload.getBytes(charset) : null; 38ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 39ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields = new LinkedHashMap<>(); 40ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(AgentHeader, AgentName); 41ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (url.getPort() != HTTPS_PORT) { 42ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(HostHeader, url.getHost() + ':' + url.getPort()); 43ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } else { 44ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(HostHeader, url.getHost()); 45ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 46ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(AcceptHeader, "*/*"); 47ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (payload != null) { 48ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (base64) { 49ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(ContentTypeHeader, contentType); 50ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(ContentEncodingHeader, "base64"); 51ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } else { 52ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(ContentTypeHeader, contentType + "; charset=" + 53ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist charset.displayName().toLowerCase()); 54ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 55ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(ContentLengthHeader, Integer.toString(mBody.length)); 56ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 57ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 58ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mMethodLine = method.name() + ' ' + url.getPath() + ' ' + HTTPVersion + CRLF; 59ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 60ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 61ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public void doAuthenticate(HTTPResponse httpResponse, String userName, byte[] password, 62ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist URL url, int sequence) throws IOException, GeneralSecurityException { 63ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist mHeaderFields.put(HTTPMessage.AuthorizationHeader, 64ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist generateAuthAnswer(httpResponse, userName, password, url, sequence)); 65ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 66ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 67ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static String generateAuthAnswer(HTTPResponse httpResponse, String userName, 68ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] password, URL url, int sequence) 69ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throws IOException, GeneralSecurityException { 70ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 71ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String authRequestLine = httpResponse.getHeader(HTTPMessage.AuthHeader); 72ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (authRequestLine == null) { 73ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throw new IOException("Missing auth line"); 74ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 75ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String[] tokens = authRequestLine.split("[ ,]+"); 76ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("Tokens: " + Arrays.toString(tokens)); 77ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (tokens.length < 3 || !tokens[0].equalsIgnoreCase("digest")) { 78ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throw new IOException("Bad " + HTTPMessage.AuthHeader + ": '" + authRequestLine + "'"); 79ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 80ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 81ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist Map<String, String> itemMap = new HashMap<>(); 82ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist for (int n = 1; n < tokens.length; n++) { 83ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String s = tokens[n]; 84ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist int split = s.indexOf('='); 85ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (split < 0) { 86ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist continue; 87ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 88ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist itemMap.put(s.substring(0, split).trim().toLowerCase(), 89ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist unquote(s.substring(split + 1).trim())); 90ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 91ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 92ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist Set<String> qops = splitValue(itemMap.remove("qop")); 93ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (!qops.contains("auth")) { 94ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throw new IOException("Unsupported quality of protection value(s): '" + qops + "'"); 95ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 96ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String algorithm = itemMap.remove("algorithm"); 97ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (algorithm != null && !algorithm.equalsIgnoreCase("md5")) { 98ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throw new IOException("Unsupported algorithm: '" + algorithm + "'"); 99ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 100ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String realm = itemMap.remove("realm"); 101ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String nonceText = itemMap.remove("nonce"); 102ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (realm == null || nonceText == null) { 103ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throw new IOException("realm and/or nonce missing: '" + authRequestLine + "'"); 104ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 105ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("Remaining tokens: " + itemMap); 106ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 107ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] cnonce = new byte[16]; 108ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist SecureRandom prng = new SecureRandom(); 109ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist prng.nextBytes(cnonce); 110ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 111ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist /* 112ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * H(data) = MD5(data) 113ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * KD(secret, data) = H(concat(secret, ":", data)) 114ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * 115ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * A1 = unq(username-value) ":" unq(realm-value) ":" passwd 116ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * A2 = Method ":" digest-uri-value 117ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * 118ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * response = KD ( H(A1), unq(nonce-value) ":" nc-value ":" unq(cnonce-value) ":" 119ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * unq(qop-value) ":" H(A2) ) 120ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist */ 121ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 122ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String nc = String.format("%08d", sequence); 123ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 124ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist /* 125ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * This bears witness to the ingenuity of the emerging "web generation" and the authors of 126ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * RFC-2617: Strings are treated as a sequence of octets in blind ignorance of character 127ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * encoding, whereas octets strings apparently aren't "good enough" and expanded to 128ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * "hex strings"... 129ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist * As a wild guess I apply UTF-8 below. 130ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist */ 131ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String passwordString = new String(password, StandardCharsets.UTF_8); 132ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String cNonceString = bytesToHex(cnonce); 133ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 134ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] a1 = hash(userName, realm, passwordString); 135ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] a2 = hash("POST", url.getPath()); 136ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] response = hash(a1, nonceText, nc, cNonceString, "auth", a2); 137ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 138ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist StringBuilder authLine = new StringBuilder(); 139ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist authLine.append("Digest ") 140ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("username=\"").append(userName).append("\", ") 141ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("realm=\"").append(realm).append("\", ") 142ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("nonce=\"").append(nonceText).append("\", ") 143ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("uri=\"").append(url.getPath()).append("\", ") 144ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("qop=\"auth\", ") 145ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("nc=").append(nc).append(", ") 146ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("cnonce=\"").append(cNonceString).append("\", ") 147ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("response=\"").append(bytesToHex(response)).append('"'); 148ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String opaque = itemMap.get("opaque"); 149ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (opaque != null) { 150ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist authLine.append(", \"").append(opaque).append('"'); 151ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 152ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 153ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return authLine.toString(); 154ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 155ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 156ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static Set<String> splitValue(String value) { 157ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist Set<String> result = new HashSet<>(); 158ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (value != null) { 159ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist for (String s : value.split(",")) { 160ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist result.add(s.trim()); 161ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 162ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 163ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return result; 164ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 165ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 166ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static byte[] hash(Object... objects) throws GeneralSecurityException { 167ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist MessageDigest hash = MessageDigest.getInstance("MD5"); 168ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 169ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("<Hash>"); 170ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist boolean first = true; 171ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist for (Object object : objects) { 172ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] octets; 173ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (object.getClass() == String.class) { 174ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("+= '" + object + "'"); 175ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist octets = ((String) object).getBytes(StandardCharsets.UTF_8); 176ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } else { 177ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist octets = bytesToHexBytes((byte[]) object); 178ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("+= " + new String(octets, StandardCharsets.ISO_8859_1)); 179ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 180ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (first) { 181ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist first = false; 182ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } else { 183ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist hash.update((byte) ':'); 184ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 185ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist hash.update(octets); 186ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 187ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("</Hash>"); 188ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return hash.digest(); 189ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 190ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 191ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static String unquote(String s) { 192ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return s.startsWith("\"") ? s.substring(1, s.length() - 1) : s; 193ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 194ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 195ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static byte[] bytesToHexBytes(byte[] octets) { 196ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return bytesToHex(octets).getBytes(StandardCharsets.ISO_8859_1); 197ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 198ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 199ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static String bytesToHex(byte[] octets) { 200ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist StringBuilder sb = new StringBuilder(octets.length * 2); 201ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist for (byte b : octets) { 202ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist sb.append(String.format("%02x", b & 0xff)); 203ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 204ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return sb.toString(); 205ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 206ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 207ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private byte[] buildHeader() { 208ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist StringBuilder header = new StringBuilder(); 209ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist header.append(mMethodLine); 210ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist for (Map.Entry<String, String> entry : mHeaderFields.entrySet()) { 211ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist header.append(entry.getKey()).append(": ").append(entry.getValue()).append(CRLF); 212ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 213ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist header.append(CRLF); 214ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 215ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("HTTP Request:"); 216ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist StringBuilder sb2 = new StringBuilder(); 217ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist sb2.append(header); 218ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (mBody != null) { 219ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist sb2.append(new String(mBody, StandardCharsets.ISO_8859_1)); 220ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 221ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println(sb2); 222ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist //System.out.println("End HTTP Request."); 223ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 224ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return header.toString().getBytes(HeaderCharset); 225ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 226ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 227ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public void send(OutputStream out) throws IOException { 228ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist out.write(buildHeader()); 229ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist if (mBody != null) { 230ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist out.write(mBody); 231ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 232ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist out.flush(); 233ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 234ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 235ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist @Override 236ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public Map<String, String> getHeaders() { 237ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return Collections.unmodifiableMap(mHeaderFields); 238ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 239ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 240ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist @Override 241ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public InputStream getPayloadStream() { 242ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return mBody != null ? new ByteArrayInputStream(mBody) : null; 243ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 244ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 245ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist @Override 246ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public ByteBuffer getPayload() { 247ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return mBody != null ? ByteBuffer.wrap(mBody) : null; 248ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 249ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 250ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist @Override 251ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public ByteBuffer getBinaryPayload() { 252ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] binary = Base64.decode(mBody, Base64.DEFAULT); 253ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist return ByteBuffer.wrap(binary); 254ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 255ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 256ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist public static void main(String[] args) throws GeneralSecurityException { 257ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist test("Mufasa", "testrealm@host.com", "Circle Of Life", "GET", "/dir/index.html", 258ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "auth", 259ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist "6629fae49393a05397450978507c4ef1"); 260ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 261ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // WWW-Authenticate: Digest realm="wi-fi.org", qop="auth", 262ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // nonce="MTQzMTg1MTIxMzUyNzo0OGFhNGU5ZTg4Y2M4YmFhYzM2MzAwZDg5MGNiYTJlNw==" 263ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // Authorization: Digest 264ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // username="1c7e1582-604d-4c00-b411-bb73735cbcb0" 265ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // realm="wi-fi.org" 266ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // nonce="MTQzMTg1MTIxMzUyNzo0OGFhNGU5ZTg4Y2M4YmFhYzM2MzAwZDg5MGNiYTJlNw==" 267ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // uri="/.well-known/est/simpleenroll" 268ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // cnonce="NzA3NDk0" 269ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // nc=00000001 270ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // qop="auth" 271ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist // response="2c485d24076452e712b77f4e70776463" 272ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 273ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String nonce = "MTQzMTg1MTIxMzUyNzo0OGFhNGU5ZTg4Y2M4YmFhYzM2MzAwZDg5MGNiYTJlNw=="; 274ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String cnonce = "NzA3NDk0"; 275ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist test("1c7e1582-604d-4c00-b411-bb73735cbcb0", "wi-fi.org", "ruckus1234", "POST", 276ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist "/.well-known/est/simpleenroll", 277ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist /*new String(Base64.getDecoder().decode(nonce), StandardCharsets.ISO_8859_1)*/ 278ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist nonce, 279ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist /*new String(Base64.getDecoder().decode(cnonce), StandardCharsets.ISO_8859_1)*/ 280ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist cnonce, "00000001", "auth", "2c485d24076452e712b77f4e70776463"); 281ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 282ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 283ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist private static void test(String user, String realm, String password, String method, String path, 284ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String nonce, String cnonce, String nc, String qop, String expect) 285ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist throws GeneralSecurityException { 286ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] a1 = hash(user, realm, password); 287ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist System.out.println("HA1: " + bytesToHex(a1)); 288ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] a2 = hash(method, path); 289ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist System.out.println("HA2: " + bytesToHex(a2)); 290ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist byte[] response = hash(a1, nonce, nc, cnonce, qop, a2); 291ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 292ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist StringBuilder authLine = new StringBuilder(); 293ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist String responseString = bytesToHex(response); 294ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist authLine.append("Digest ") 295ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("username=\"").append(user).append("\", ") 296ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("realm=\"").append(realm).append("\", ") 297ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("nonce=\"").append(nonce).append("\", ") 298ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("uri=\"").append(path).append("\", ") 299ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("qop=\"").append(qop).append("\", ") 300ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("nc=").append(nc).append(", ") 301ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("cnonce=\"").append(cnonce).append("\", ") 302ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist .append("response=\"").append(responseString).append('"'); 303ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist 304ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist System.out.println(authLine); 305ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist System.out.println("Success: " + responseString.equals(expect)); 306ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist } 307ee699a61a5687d7c8518b639a940c8e9d1b384ddJan Nordqvist}