DigestMd5.java

 1package eu.siacs.conversations.crypto.sasl;
 2
 3import android.util.Base64;
 4
 5import java.math.BigInteger;
 6import java.nio.charset.Charset;
 7import java.security.MessageDigest;
 8import java.security.NoSuchAlgorithmException;
 9import java.security.SecureRandom;
10
11import eu.siacs.conversations.entities.Account;
12import eu.siacs.conversations.utils.CryptoHelper;
13import eu.siacs.conversations.xml.TagWriter;
14
15public class DigestMd5 extends SaslMechanism {
16	public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
17		super(tagWriter, account, rng);
18	}
19
20	@Override
21	public int getPriority() {
22		return 10;
23	}
24
25	@Override
26	public String getMechanism() {
27		return "DIGEST-MD5";
28	}
29
30	private State state = State.INITIAL;
31
32	@Override
33	public String getResponse(final String challenge) throws AuthenticationException {
34		switch (state) {
35			case INITIAL:
36				state = State.RESPONSE_SENT;
37				final String encodedResponse;
38				try {
39					final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
40					String nonce = "";
41					for (final String token : tokenizer) {
42						final String[] parts = token.split("=", 2);
43						if (parts[0].equals("nonce")) {
44							nonce = parts[1].replace("\"", "");
45						} else if (parts[0].equals("rspauth")) {
46							return "";
47						}
48					}
49					final String digestUri = "xmpp/" + account.getServer();
50					final String nonceCount = "00000001";
51					final String x = account.getUsername() + ":" + account.getServer() + ":"
52						+ account.getPassword();
53					final MessageDigest md = MessageDigest.getInstance("MD5");
54					final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
55					final String cNonce = new BigInteger(100, rng).toString(32);
56					final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
57							(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
58					final String a2 = "AUTHENTICATE:" + digestUri;
59					final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
60					final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
61									.defaultCharset())));
62					final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
63						+ ":auth:" + ha2;
64					final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
65									.defaultCharset())));
66					final String saslString = "username=\"" + account.getUsername()
67						+ "\",realm=\"" + account.getServer() + "\",nonce=\""
68						+ nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
69						+ ",qop=auth,digest-uri=\"" + digestUri + "\",response="
70						+ response + ",charset=utf-8";
71					encodedResponse = Base64.encodeToString(
72							saslString.getBytes(Charset.defaultCharset()),
73							Base64.NO_WRAP);
74				} catch (final NoSuchAlgorithmException e) {
75					throw new AuthenticationException(e);
76				}
77
78				return encodedResponse;
79			case RESPONSE_SENT:
80				state = State.VALID_SERVER_RESPONSE;
81				break;
82			case VALID_SERVER_RESPONSE:
83				if (challenge==null) {
84					return null; //everything is fine
85				}
86			default:
87				throw new InvalidStateException(state);
88		}
89		return null;
90	}
91}