aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/thedevstack/conversationsplus/crypto/sasl
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/crypto/sasl')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java91
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java30
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java30
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java62
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java234
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java78
6 files changed, 0 insertions, 525 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java
deleted file mode 100644
index f4c3f28e..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public class DigestMd5 extends SaslMechanism {
- public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- super(tagWriter, account, rng);
- }
-
- @Override
- public int getPriority() {
- return 10;
- }
-
- @Override
- public String getMechanism() {
- return "DIGEST-MD5";
- }
-
- private State state = State.INITIAL;
-
- @Override
- public String getResponse(final String challenge) throws AuthenticationException {
- switch (state) {
- case INITIAL:
- state = State.RESPONSE_SENT;
- final String encodedResponse;
- try {
- final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
- String nonce = "";
- for (final String token : tokenizer) {
- final String[] parts = token.split("=", 2);
- if (parts[0].equals("nonce")) {
- nonce = parts[1].replace("\"", "");
- } else if (parts[0].equals("rspauth")) {
- return "";
- }
- }
- final String digestUri = "xmpp/" + account.getServer();
- final String nonceCount = "00000001";
- final String x = account.getUsername() + ":" + account.getServer() + ":"
- + account.getPassword();
- final MessageDigest md = MessageDigest.getInstance("MD5");
- final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
- final String cNonce = new BigInteger(100, rng).toString(32);
- final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
- (":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
- final String a2 = "AUTHENTICATE:" + digestUri;
- final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
- final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
- .defaultCharset())));
- final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
- + ":auth:" + ha2;
- final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
- .defaultCharset())));
- final String saslString = "username=\"" + account.getUsername()
- + "\",realm=\"" + account.getServer() + "\",nonce=\""
- + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
- + ",qop=auth,digest-uri=\"" + digestUri + "\",response="
- + response + ",charset=utf-8";
- encodedResponse = Base64.encodeToString(
- saslString.getBytes(Charset.defaultCharset()),
- Base64.NO_WRAP);
- } catch (final NoSuchAlgorithmException e) {
- throw new AuthenticationException(e);
- }
-
- return encodedResponse;
- case RESPONSE_SENT:
- state = State.VALID_SERVER_RESPONSE;
- break;
- case VALID_SERVER_RESPONSE:
- if (challenge==null) {
- return null; //everything is fine
- }
- default:
- throw new InvalidStateException(state);
- }
- return null;
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java
deleted file mode 100644
index dc50cbbe..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public class External extends SaslMechanism {
-
- public External(TagWriter tagWriter, Account account, SecureRandom rng) {
- super(tagWriter, account, rng);
- }
-
- @Override
- public int getPriority() {
- return 25;
- }
-
- @Override
- public String getMechanism() {
- return "EXTERNAL";
- }
-
- @Override
- public String getClientFirstMessage() {
- return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(),Base64.NO_WRAP);
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java
deleted file mode 100644
index 179c12de..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-
-import java.nio.charset.Charset;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public class Plain extends SaslMechanism {
- public Plain(final TagWriter tagWriter, final Account account) {
- super(tagWriter, account, null);
- }
-
- @Override
- public int getPriority() {
- return 10;
- }
-
- @Override
- public String getMechanism() {
- return "PLAIN";
- }
-
- @Override
- public String getClientFirstMessage() {
- final String sasl = '\u0000' + account.getUsername() + '\u0000' + account.getPassword();
- return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java
deleted file mode 100644
index 1f6d2bde..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public abstract class SaslMechanism {
-
- final protected TagWriter tagWriter;
- final protected Account account;
- final protected SecureRandom rng;
-
- protected enum State {
- INITIAL,
- AUTH_TEXT_SENT,
- RESPONSE_SENT,
- VALID_SERVER_RESPONSE,
- }
-
- public static class AuthenticationException extends Exception {
- public AuthenticationException(final String message) {
- super(message);
- }
-
- public AuthenticationException(final Exception inner) {
- super(inner);
- }
- }
-
- public static class InvalidStateException extends AuthenticationException {
- public InvalidStateException(final String message) {
- super(message);
- }
-
- public InvalidStateException(final State state) {
- this("Invalid state: " + state.toString());
- }
- }
-
- public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- this.tagWriter = tagWriter;
- this.account = account;
- this.rng = rng;
- }
-
- /**
- * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
- * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
- * attacks).
- * @return An arbitrary int representing the priority
- */
- public abstract int getPriority();
-
- public abstract String getMechanism();
- public String getClientFirstMessage() {
- return "";
- }
- public String getResponse(final String challenge) throws AuthenticationException {
- return "";
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java
deleted file mode 100644
index 3540d2cf..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java
+++ /dev/null
@@ -1,234 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-import android.util.LruCache;
-
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.macs.HMac;
-import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.security.InvalidKeyException;
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public class ScramSha1 extends SaslMechanism {
- // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
- final private static String GS2_HEADER = "n,,";
- private String clientFirstMessageBare;
- final private String clientNonce;
- private byte[] serverSignature = null;
- private static HMac HMAC;
- private static Digest DIGEST;
- private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
- private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
-
- public static class KeyPair {
- final public byte[] clientKey;
- final public byte[] serverKey;
-
- public KeyPair(final byte[] clientKey, final byte[] serverKey) {
- this.clientKey = clientKey;
- this.serverKey = serverKey;
- }
- }
-
- private static final LruCache<String, KeyPair> CACHE;
-
- static {
- DIGEST = new SHA1Digest();
- HMAC = new HMac(new SHA1Digest());
- CACHE = new LruCache<String, KeyPair>(10) {
- protected KeyPair create(final String k) {
- // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
- // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
- // is applied to prevent commas in the strings breaking things.
- final String[] kparts = k.split(",", 4);
- try {
- final byte[] saltedPassword, serverKey, clientKey;
- saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(),
- Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3]));
- serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
- clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
-
- return new KeyPair(clientKey, serverKey);
- } catch (final InvalidKeyException | NumberFormatException e) {
- return null;
- }
- }
- };
- }
-
- private State state = State.INITIAL;
-
- public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- super(tagWriter, account, rng);
-
- // This nonce should be different for each authentication attempt.
- clientNonce = new BigInteger(100, this.rng).toString(32);
- clientFirstMessageBare = "";
- }
-
- @Override
- public int getPriority() {
- return 20;
- }
-
- @Override
- public String getMechanism() {
- return "SCRAM-SHA-1";
- }
-
- @Override
- public String getClientFirstMessage() {
- if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
- clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
- ",r=" + this.clientNonce;
- state = State.AUTH_TEXT_SENT;
- }
- return Base64.encodeToString(
- (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
- Base64.NO_WRAP);
- }
-
- @Override
- public String getResponse(final String challenge) throws AuthenticationException {
- switch (state) {
- case AUTH_TEXT_SENT:
- if (challenge == null) {
- throw new AuthenticationException("challenge can not be null");
- }
- byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
- final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
- String nonce = "";
- int iterationCount = -1;
- String salt = "";
- for (final String token : tokenizer) {
- if (token.charAt(1) == '=') {
- switch (token.charAt(0)) {
- case 'i':
- try {
- iterationCount = Integer.parseInt(token.substring(2));
- } catch (final NumberFormatException e) {
- throw new AuthenticationException(e);
- }
- break;
- case 's':
- salt = token.substring(2);
- break;
- case 'r':
- nonce = token.substring(2);
- break;
- case 'm':
- /*
- * RFC 5802:
- * m: This attribute is reserved for future extensibility. In this
- * version of SCRAM, its presence in a client or a server message
- * MUST cause authentication failure when the attribute is parsed by
- * the other end.
- */
- throw new AuthenticationException("Server sent reserved token: `m'");
- }
- }
- }
-
- if (iterationCount < 0) {
- throw new AuthenticationException("Server did not send iteration count");
- }
- if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
- throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
- }
- if (salt.isEmpty()) {
- throw new AuthenticationException("Server sent empty salt");
- }
-
- final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
- GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
- final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
- + clientFinalMessageWithoutProof).getBytes();
-
- // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
- final KeyPair keys = CACHE.get(
- CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + ","
- + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + ","
- + CryptoHelper.bytesToHex(salt.getBytes()) + ","
- + String.valueOf(iterationCount)
- );
- if (keys == null) {
- throw new AuthenticationException("Invalid keys generated");
- }
- final byte[] clientSignature;
- try {
- serverSignature = hmac(keys.serverKey, authMessage);
- final byte[] storedKey = digest(keys.clientKey);
-
- clientSignature = hmac(storedKey, authMessage);
-
- } catch (final InvalidKeyException e) {
- throw new AuthenticationException(e);
- }
-
- final byte[] clientProof = new byte[keys.clientKey.length];
-
- for (int i = 0; i < clientProof.length; i++) {
- clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
- }
-
-
- final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
- Base64.encodeToString(clientProof, Base64.NO_WRAP);
- state = State.RESPONSE_SENT;
- return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
- case RESPONSE_SENT:
- final String clientCalculatedServerFinalMessage = "v=" +
- Base64.encodeToString(serverSignature, Base64.NO_WRAP);
- if (challenge == null || !clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
- throw new AuthenticationException("Server final message does not match calculated final message");
- }
- state = State.VALID_SERVER_RESPONSE;
- return "";
- default:
- throw new InvalidStateException(state);
- }
- }
-
- public static synchronized byte[] hmac(final byte[] key, final byte[] input)
- throws InvalidKeyException {
- HMAC.init(new KeyParameter(key));
- HMAC.update(input, 0, input.length);
- final byte[] out = new byte[HMAC.getMacSize()];
- HMAC.doFinal(out, 0);
- return out;
- }
-
- public static synchronized byte[] digest(byte[] bytes) {
- DIGEST.reset();
- DIGEST.update(bytes, 0, bytes.length);
- final byte[] out = new byte[DIGEST.getDigestSize()];
- DIGEST.doFinal(out, 0);
- return out;
- }
-
- /*
- * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
- * pseudorandom function (PRF) and with dkLen == output length of
- * HMAC() == output length of H().
- */
- private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
- throws InvalidKeyException {
- byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
- byte[] out = u.clone();
- for (int i = 1; i < iterations; i++) {
- u = hmac(key, u);
- for (int j = 0; j < u.length; j++) {
- out[j] ^= u[j];
- }
- }
- return out;
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java
deleted file mode 100644
index cc2805de..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-/**
- * A tokenizer for GS2 header strings
- */
-public final class Tokenizer implements Iterator<String>, Iterable<String> {
- private final List<String> parts;
- private int index;
-
- public Tokenizer(final byte[] challenge) {
- final String challengeString = new String(challenge);
- parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
- // Trim parts.
- for (int i = 0; i < parts.size(); i++) {
- parts.set(i, parts.get(i).trim());
- }
- index = 0;
- }
-
- /**
- * Returns true if there is at least one more element, false otherwise.
- *
- * @see #next
- */
- @Override
- public boolean hasNext() {
- return parts.size() != index + 1;
- }
-
- /**
- * Returns the next object and advances the iterator.
- *
- * @return the next object.
- * @throws java.util.NoSuchElementException if there are no more elements.
- * @see #hasNext
- */
- @Override
- public String next() {
- if (hasNext()) {
- return parts.get(index++);
- } else {
- throw new NoSuchElementException("No such element. Size is: " + parts.size());
- }
- }
-
- /**
- * Removes the last object returned by {@code next} from the collection.
- * This method can only be called once between each call to {@code next}.
- *
- * @throws UnsupportedOperationException if removing is not supported by the collection being
- * iterated.
- * @throws IllegalStateException if {@code next} has not been called, or {@code remove} has
- * already been called after the last call to {@code next}.
- */
- @Override
- public void remove() {
- if(index <= 0) {
- throw new IllegalStateException("You can't delete an element before first next() method call");
- }
- parts.remove(--index);
- }
-
- /**
- * Returns an {@link java.util.Iterator} for the elements in this object.
- *
- * @return An {@code Iterator} instance.
- */
- @Override
- public Iterator<String> iterator() {
- return parts.iterator();
- }
-}