GUACAMOLE-136: Implement basic support for verifying user identity using Duo.

This commit is contained in:
Michael Jumper
2016-12-01 21:36:26 -08:00
parent 718e4dab00
commit 48af3ef45d
22 changed files with 3158 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
package com.duosecurity.duoweb;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public final class DuoWeb {
private static final String DUO_PREFIX = "TX";
private static final String APP_PREFIX = "APP";
private static final String AUTH_PREFIX = "AUTH";
private static final int DUO_EXPIRE = 300;
private static final int APP_EXPIRE = 3600;
private static final int IKEY_LEN = 20;
private static final int SKEY_LEN = 40;
private static final int AKEY_LEN = 40;
public static final String ERR_USER = "ERR|The username passed to sign_request() is invalid.";
public static final String ERR_IKEY = "ERR|The Duo integration key passed to sign_request() is invalid.";
public static final String ERR_SKEY = "ERR|The Duo secret key passed to sign_request() is invalid.";
public static final String ERR_AKEY = "ERR|The application secret key passed to sign_request() must be at least " + AKEY_LEN + " characters.";
public static final String ERR_UNKNOWN = "ERR|An unknown error has occurred.";
public static String signRequest(final String ikey, final String skey, final String akey, final String username) {
return signRequest(ikey, skey, akey, username, System.currentTimeMillis() / 1000);
}
public static String signRequest(final String ikey, final String skey, final String akey, final String username, final long time) {
final String duo_sig;
final String app_sig;
if (username.equals("")) {
return ERR_USER;
}
if (username.indexOf('|') != -1) {
return ERR_USER;
}
if (ikey.equals("") || ikey.length() != IKEY_LEN) {
return ERR_IKEY;
}
if (skey.equals("") || skey.length() != SKEY_LEN) {
return ERR_SKEY;
}
if (akey.equals("") || akey.length() < AKEY_LEN) {
return ERR_AKEY;
}
try {
duo_sig = signVals(skey, username, ikey, DUO_PREFIX, DUO_EXPIRE, time);
app_sig = signVals(akey, username, ikey, APP_PREFIX, APP_EXPIRE, time);
} catch (Exception e) {
return ERR_UNKNOWN;
}
return duo_sig + ":" + app_sig;
}
public static String verifyResponse(final String ikey, final String skey, final String akey, final String sig_response)
throws DuoWebException, NoSuchAlgorithmException, InvalidKeyException, IOException {
return verifyResponse(ikey, skey, akey, sig_response, System.currentTimeMillis() / 1000);
}
public static String verifyResponse(final String ikey, final String skey, final String akey, final String sig_response, final long time)
throws DuoWebException, NoSuchAlgorithmException, InvalidKeyException, IOException {
String auth_user = null;
String app_user = null;
final String[] sigs = sig_response.split(":");
final String auth_sig = sigs[0];
final String app_sig = sigs[1];
auth_user = parseVals(skey, auth_sig, AUTH_PREFIX, ikey, time);
app_user = parseVals(akey, app_sig, APP_PREFIX, ikey, time);
if (!auth_user.equals(app_user)) {
throw new DuoWebException("Authentication failed.");
}
return auth_user;
}
private static String signVals(final String key, final String username, final String ikey, final String prefix, final int expire, final long time)
throws InvalidKeyException, NoSuchAlgorithmException {
final long expire_ts = time + expire;
final String exp = Long.toString(expire_ts);
final String val = username + "|" + ikey + "|" + exp;
final String cookie = prefix + "|" + Base64.encodeBytes(val.getBytes());
final String sig = Util.hmacSign(key, cookie);
return cookie + "|" + sig;
}
private static String parseVals(final String key, final String val, final String prefix, final String ikey, final long time)
throws InvalidKeyException, NoSuchAlgorithmException, IOException, DuoWebException {
final String[] parts = val.split("\\|");
if (parts.length != 3) {
throw new DuoWebException("Invalid response");
}
final String u_prefix = parts[0];
final String u_b64 = parts[1];
final String u_sig = parts[2];
final String sig = Util.hmacSign(key, u_prefix + "|" + u_b64);
if (!Util.hmacSign(key, sig).equals(Util.hmacSign(key, u_sig))) {
throw new DuoWebException("Invalid response");
}
if (!u_prefix.equals(prefix)) {
throw new DuoWebException("Invalid response");
}
final byte[] decoded = Base64.decode(u_b64);
final String cookie = new String(decoded);
final String[] cookie_parts = cookie.split("\\|");
if (cookie_parts.length != 3) {
throw new DuoWebException("Invalid response");
}
final String username = cookie_parts[0];
final String u_ikey = cookie_parts[1];
final String expire = cookie_parts[2];
if (!u_ikey.equals(ikey)) {
throw new DuoWebException("Invalid response");
}
final long expire_ts = Long.parseLong(expire);
if (time >= expire_ts) {
throw new DuoWebException("Transaction has expired. Please check that the system time is correct.");
}
return username;
}
}

View File

@@ -0,0 +1,8 @@
package com.duosecurity.duoweb;
public class DuoWebException extends Exception {
public DuoWebException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,26 @@
package com.duosecurity.duoweb;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Util {
public static String hmacSign(String skey, String data)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec key = new SecretKeySpec(skey.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(key);
byte[] raw = mac.doFinal(data.getBytes());
return bytesToHex(raw);
}
public static String bytesToHex(byte[] b) {
String result = "";
for (int i = 0; i < b.length; i++) {
result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
}
return result;
}
}