com.mysql.fabric.proto.xmlrpc.DigestAuthentication Maven / Gradle / Ivy
/*
Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FLOSS License Exception
.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.fabric.proto.xmlrpc;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* HTTP/1.1 Digest Authentication - RFC 2617
*/
public class DigestAuthentication {
/**
* Get the digest challenge header by connecting to the resource
* with no credentials.
*/
public static String getChallengeHeader(String url) throws IOException {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setDoOutput(true);
conn.getOutputStream().close();
try {
conn.getInputStream().close();
} catch (IOException ex) {
// we expect a 401-unauthorized response with the
// WWW-Authenticate header to create the request with the
// necessary auth data
if (401 != conn.getResponseCode()) {
throw ex;
}
String hdr = conn.getHeaderField("WWW-Authenticate");
if (hdr != null && !"".equals(hdr)) {
return hdr;
}
}
return null;
}
/**
* Calculate the request digest for algorithm=MD5.
*/
public static String calculateMD5RequestDigest(String uri, String username, String password, String realm, String nonce, String nc, String cnonce,
String qop) {
String reqA1 = username + ":" + realm + ":" + password;
// valid only for qop="auth"
String reqA2 = "POST:" + uri;
String hashA1 = checksumMD5(reqA1);
String hashA2 = checksumMD5(reqA2);
String requestDigest = digestMD5(hashA1, nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + hashA2);
return requestDigest;
}
/**
* MD5 version of the "H()" function from rfc2617.
*/
private static String checksumMD5(String data) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("Unable to create MD5 instance", ex);
}
// TODO encoding
return hexEncode(md5.digest(data.getBytes()));
}
/**
* MD5 version of the "KD()" function from rfc2617.
*/
private static String digestMD5(String secret, String data) {
return checksumMD5(secret + ":" + data);
}
/**
* hex-encode a byte array
*/
private static String hexEncode(byte data[]) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; ++i) {
sb.append(String.format("%02x", data[i]));
}
return sb.toString();
}
/**
* Serialize a parameter map into a digest response. This is used
* as the "Authorization" header in the request. All parameters in
* the supplied map will be added to the header.
*/
public static String serializeDigestResponse(Map paramMap) {
StringBuffer sb = new StringBuffer("Digest ");
boolean prefixComma = false;
for (Map.Entry entry : paramMap.entrySet()) {
if (!prefixComma) {
prefixComma = true;
} else {
sb.append(", ");
}
sb.append(entry.getKey());
sb.append("=");
sb.append(entry.getValue());
}
return sb.toString();
}
/**
* Parse a digest challenge from the WWW-Authenticate header
* return as the initial response during the authentication
* exchange.
*/
public static Map parseDigestChallenge(String headerValue) {
if (!headerValue.startsWith("Digest ")) {
throw new IllegalArgumentException("Header is not a digest challenge");
}
String params = headerValue.substring(7);
Map paramMap = new HashMap();
for (String param : params.split(",\\s*")) {
String pieces[] = param.split("=");
paramMap.put(pieces[0], pieces[1].replaceAll("^\"(.*)\"$", "$1"));
}
return paramMap;
}
/**
* Generate the cnonce value. This allows the client provide a
* value used in the digest calculation. Same as Python. (no
* motivation given for this algorithm)
*/
public static String generateCnonce(String nonce, String nc) {
// Random string, keep it in basic printable ASCII range
byte buf[] = new byte[8];
new Random().nextBytes(buf);
for (int i = 0; i < 8; ++i) {
buf[i] = (byte) (0x20 + (buf[i] % 95));
}
String combo = String.format("%s:%s:%s:%s", nonce, nc, new Date().toGMTString(), new String(buf));
MessageDigest sha1 = null;
try {
sha1 = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("Unable to create SHA-1 instance", ex);
}
return hexEncode(sha1.digest(combo.getBytes()));
}
/**
* Quote a parameter to be included in the header. Parameters with
* embedded quotes will be rejected.
*/
private static String quoteParam(String param) {
if (param.contains("\"") || param.contains("'")) {
throw new IllegalArgumentException("Invalid character in parameter");
}
return "\"" + param + "\"";
}
/**
* Generate the Authorization header to make the authenticated
* request.
*/
public static String generateAuthorizationHeader(Map digestChallenge, String username, String password) {
String nonce = digestChallenge.get("nonce");
String nc = "00000001";
String cnonce = generateCnonce(nonce, nc);
String qop = "auth";
String uri = "/RPC2";
String realm = digestChallenge.get("realm");
String opaque = digestChallenge.get("opaque");
String requestDigest = calculateMD5RequestDigest(uri, username, password, realm, nonce, nc, cnonce, qop);
Map digestResponseMap = new HashMap();
digestResponseMap.put("algorithm", "MD5");
digestResponseMap.put("username", quoteParam(username));
digestResponseMap.put("realm", quoteParam(realm));
digestResponseMap.put("nonce", quoteParam(nonce));
digestResponseMap.put("uri", quoteParam(uri));
digestResponseMap.put("qop", qop);
digestResponseMap.put("nc", nc);
digestResponseMap.put("cnonce", quoteParam(cnonce));
digestResponseMap.put("response", quoteParam(requestDigest));
digestResponseMap.put("opaque", quoteParam(opaque));
return serializeDigestResponse(digestResponseMap);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy