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;
  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}