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;
8
9import eu.siacs.conversations.entities.Account;
10import eu.siacs.conversations.utils.CryptoHelper;
11
12public class DigestMd5 extends SaslMechanism {
13
14 public static final String MECHANISM = "DIGEST-MD5";
15 private State state = State.INITIAL;
16
17 public DigestMd5(final Account account) {
18 super(account);
19 }
20
21 @Override
22 public int getPriority() {
23 return 10;
24 }
25
26 @Override
27 public String getMechanism() {
28 return MECHANISM;
29 }
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 =
39 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 =
52 account.getUsername()
53 + ":"
54 + account.getServer()
55 + ":"
56 + account.getPassword();
57 final MessageDigest md = MessageDigest.getInstance("MD5");
58 final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
59 final String cNonce = CryptoHelper.random(100);
60 final byte[] a1 =
61 CryptoHelper.concatenateByteArrays(
62 y,
63 (":" + nonce + ":" + cNonce)
64 .getBytes(Charset.defaultCharset()));
65 final String a2 = "AUTHENTICATE:" + digestUri;
66 final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
67 final String ha2 =
68 CryptoHelper.bytesToHex(
69 md.digest(a2.getBytes(Charset.defaultCharset())));
70 final String kd =
71 ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce + ":auth:" + ha2;
72 final String response =
73 CryptoHelper.bytesToHex(
74 md.digest(kd.getBytes(Charset.defaultCharset())));
75 final String saslString =
76 "username=\""
77 + account.getUsername()
78 + "\",realm=\""
79 + account.getServer()
80 + "\",nonce=\""
81 + nonce
82 + "\",cnonce=\""
83 + cNonce
84 + "\",nc="
85 + nonceCount
86 + ",qop=auth,digest-uri=\""
87 + digestUri
88 + "\",response="
89 + response
90 + ",charset=utf-8";
91 encodedResponse =
92 Base64.encodeToString(
93 saslString.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
94 } catch (final NoSuchAlgorithmException e) {
95 throw new AuthenticationException(e);
96 }
97
98 return encodedResponse;
99 case RESPONSE_SENT:
100 state = State.VALID_SERVER_RESPONSE;
101 break;
102 case VALID_SERVER_RESPONSE:
103 if (challenge == null) {
104 return null; // everything is fine
105 }
106 default:
107 throw new InvalidStateException(state);
108 }
109 return null;
110 }
111}