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