DigestMd5.java

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