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