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}