Možná stejně jako já před nedávnou dobou řešíte problémy s připojením na XMPP server pomocí SASL. Pokud studujete oficiální dokument na xmpp.org (nebo RFC), patrně jste se bez větších obtíží dostali až k části, ve které musíte odpovědět serveru na jeho challenge. V tuto chvíli narazíte nejspíš na problém, protože výpočet odpovědi na challenge není jednoduchý (resp. jednoduše pouze vypadá).
Problém je totiž v tom, že výpočty nutné k vytvoření odpovědi, nesmíte nijak upravovat. Nesmíte je převádět na ASCII, ani UTF-8. Musíte s nimi pracovat v jejich nejsurovější formě, ke které se dostanete – v Hexadecimální soustavě. Pouze při práci v hex se vám podaří vytvořit korektní odpověď.
Proč někdo v XMPP vymyslel tuto zbytečnost, nechápu (u jiných druhů SASL připojení toto vůbec nutné není), nicméně není žádný problém se tomu v Javě přízpůsobit.
Nejdříve napíšeme metodu byteArrayToHex, která jako parametr obdrží byte[] pole, které zpracuje a v hexa vrátí jako String:
private static String byteArrayToHex(byte[] array) { // returns hex representation of byte[] array
String resultStr = "";
for (int i=0; i < array.length; i++) {
resultStr += Integer.toString( ( array[i] & 0xff ) + 0x100, 16).substring( 1 );
}
return resultStr;
}
Další potřebnou metodou bude spojení dvou byte[] polí do jednoho (zde jsem byl líný a udělal jsem metodu pouze pro dvě vstupní pole, ačkoli budeme ke konci potřebovat kombinovat pole tři — bude ale stačit metodu zavolat dvakrát). Nazveme ji combineByteArrays, metoda bude pracovat se dvěma vstupními parametry typu pole byte[] a vracet bude také pole byte[]:
private static byte[] combineByteArrays(byte[] a, byte[] b) { // combines two byte[] arrays
byte[] result = new byte[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
for (int i = a.length; i < result.length; i++) {
result[i] = b[i-a.length];
}
return result;
}
Poslední, patrně nejjednodušší (nicméně nejpotřebnější) částí bude metoda computeResponse, která jako vstupní parametry obdrží veškeré potřebné údaje pro XMPP challenge response (username, password, reals, nonce, qop, cnonce, digest_uri a nc — kde je získat se dozvíte na konci článku) a to jako String. Vracet bude response připravenou k odeslání XMPP serveru, také jako String:
private static String computeResponse(String username, String password, String realm, String nonce, String qop, String cnonce, String digest_uri, String nc) throws NoSuchAlgorithmException { // computes response for challenge query of XMPP server
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[] part1 = md5.digest(combineByteArrays(md5.digest((username + ":" + realm + ":" + password).getBytes()), (":" + nonce + ":" + cnonce).getBytes()));
final byte[] part2 = md5.digest(combineByteArrays("AUTHENTICATE:".getBytes(), digest_uri.getBytes()));
final byte[] temp = combineByteArrays(byteArrayToHex(part1).getBytes(), (":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":").getBytes());
final byte[] part3 = md5.digest(combineByteArrays(temp, byteArrayToHex(part2).getBytes()));
return byteArrayToHex(part3);
}
Pro jistotu uvádím ještě kompletní kód (včetně ukázky použití):
private static byte[] combineByteArrays(byte[] a, byte[] b) { // combines two byte[] arrays
byte[] result = new byte[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
for (int i = a.length; i < result.length; i++) {
result[i] = b[i-a.length];
}
return result;
}
private static String byteArrayToHex(byte[] array) { // returns hex representation of byte[] array
String resultStr = "";
for (int i=0; i < array.length; i++) {
resultStr += Integer.toString( ( array[i] & 0xff ) + 0x100, 16).substring( 1 );
}
return resultStr;
}
private static String computeResponse(String username, String password, String realm, String nonce, String qop, String cnonce, String digest_uri, String nc) throws NoSuchAlgorithmException { // computes response for challenge query of XMPP server
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[] part1 = md5.digest(combineByteArrays(md5.digest((username + ":" + realm + ":" + password).getBytes()), (":" + nonce + ":" + cnonce).getBytes()));
final byte[] part2 = md5.digest(combineByteArrays("AUTHENTICATE:".getBytes(), digest_uri.getBytes()));
final byte[] temp = combineByteArrays(byteArrayToHex(part1).getBytes(), (":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":").getBytes());
final byte[] part3 = md5.digest(combineByteArrays(temp, byteArrayToHex(part2).getBytes()));
return byteArrayToHex(part3);
}
public static void main(String[] args) throws NoSuchAlgorithmException {
String username = "test";
String password = "secret";
String realm = "osXstream.local";
String nonce = "392616736";
String qop = "auth";
String cnonce = "05E0A6E7-0B7B-4430-9549-0FE1C244ABAB";
String digest_uri = "xmpp/osXstream.local";
String nc = "00000001";
// prints out "37991b870e0f6cc757ec74c47877472b"
System.out.println(computeResponse(username, password, realm, nonce, qop, cnonce, digest_uri, nc));
}
Doufám, že vám tento návod byl k něčemu dobrý, a hlavně že vám ušetřil několik hodin hledání odpovědí na Google.
Poznámka:
jak celý proces výpočtu response funguje, jsem se dozvěděl zde: http://deusty.blogspot.com/2007/09/example-please.html. Na zmíněném webu se dozvíte i jak rozkódovat challenge a další užitečné rady.
