From 0e550789d372a1a83caa432e93a4f969a0607c9a Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Wed, 12 Nov 2014 15:35:44 -0500 Subject: Add SCRAM-SHA1 support Factor out GS2 tokanization into own class Add authentication exception class Fixes #71 --- .../siacs/conversations/crypto/sasl/DigestMd5.java | 118 ++++++++++++--------- 1 file changed, 65 insertions(+), 53 deletions(-) (limited to 'src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java') diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java index f81bd0c5..bef76fef 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java @@ -13,60 +13,72 @@ import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.TagWriter; public class DigestMd5 extends SaslMechanism { - public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) { - super(tagWriter, account, rng); - } + public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) { + super(tagWriter, account, rng); + } - @Override - public String getMechanism() { - return "DIGEST-MD5"; - } + public static String getMechanism() { + return "DIGEST-MD5"; + } - @Override - public String getResponse(final String challenge) { - final String encodedResponse; - try { - final String[] challengeParts = new String(Base64.decode(challenge, - Base64.DEFAULT)).split(","); - String nonce = ""; - for (int i = 0; i < challengeParts.length; ++i) { - String[] parts = challengeParts[i].split("="); - 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) { - return ""; - } + private enum State { + INITIAL, + RESPONSE_SENT, + } - return encodedResponse; - } + 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("="); + 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) { + return ""; + } + + return encodedResponse; + case RESPONSE_SENT: + return ""; + } + return ""; + } } -- cgit v1.2.3 From 4b5d6f5b4fd29a4ee6d469f3b540dc5ba826f1a3 Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Sat, 15 Nov 2014 08:48:40 -0500 Subject: Improve auth error handling and state machine --- .../eu/siacs/conversations/crypto/sasl/DigestMd5.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java') diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java index bef76fef..b56d2a46 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java @@ -21,11 +21,6 @@ public class DigestMd5 extends SaslMechanism { return "DIGEST-MD5"; } - private enum State { - INITIAL, - RESPONSE_SENT, - } - private State state = State.INITIAL; @Override @@ -53,8 +48,7 @@ public class DigestMd5 extends SaslMechanism { 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())); + (":" + 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 @@ -72,13 +66,16 @@ public class DigestMd5 extends SaslMechanism { saslString.getBytes(Charset.defaultCharset()), Base64.NO_WRAP); } catch (final NoSuchAlgorithmException e) { - return ""; + throw new AuthenticationException(e); } return encodedResponse; case RESPONSE_SENT: - return ""; + state = State.VALID_SERVER_RESPONSE; + break; + default: + throw new InvalidStateException(state); } - return ""; + return null; } } -- cgit v1.2.3