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}