summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarun Gupta <tarun1995gupta@gmail.com>2015-07-22 22:21:20 (GMT)
committerNick Hudson <nick.hudson@isode.com>2015-08-03 14:06:04 (GMT)
commitaf3bb03053b9d83f4d38b31d66b292792206a327 (patch)
tree0e5826f6b1fbb638d899ee6aadca902a7c2fdb18 /src/com/isode
parent32ef37b9059e21de19209a9a1ab4ef2564051918 (diff)
downloadstroke-af3bb03053b9d83f4d38b31d66b292792206a327.zip
stroke-af3bb03053b9d83f4d38b31d66b292792206a327.tar.bz2
Make SASL equivalent with Swiften.
Adds DIGESTMD5ClientAuthenticator, DIGESTMD5Properties, ExternalClientAuthenticator, PLAINMessage. Updates Client, ClientSession, CoreClient, ConnectDisconnect, StrokeGUI to reflect changes made in SASL. Updates ClientAuthenticator, SCRAMSHA1ClientAuthenticator, PBKDF2. License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. Test-Information: Added tests for DIGESTMD5ClientAuthenticator, DIGESTMD5Properties, PLAINClientAuthenticator, PLAINMessage. Update test for SCRAMSHA1ClientAuthenticatorTest. All tests pass. Change-Id: I4fd38f922cab7e4c4548d9706f6ad3b9e1506e81
Diffstat (limited to 'src/com/isode')
-rw-r--r--src/com/isode/stroke/client/Client.java5
-rw-r--r--src/com/isode/stroke/client/ClientSession.java10
-rw-r--r--src/com/isode/stroke/client/CoreClient.java4
-rw-r--r--src/com/isode/stroke/examples/ConnectDisconnect.java3
-rw-r--r--src/com/isode/stroke/examples/gui/StrokeGUI.java2
-rw-r--r--src/com/isode/stroke/sasl/ClientAuthenticator.java8
-rw-r--r--src/com/isode/stroke/sasl/DIGESTMD5ClientAuthenticator.java115
-rw-r--r--src/com/isode/stroke/sasl/DIGESTMD5Properties.java146
-rw-r--r--src/com/isode/stroke/sasl/EXTERNALClientAuthenticator.java38
-rw-r--r--src/com/isode/stroke/sasl/PLAINMessage.java75
-rw-r--r--src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java40
-rw-r--r--src/com/isode/stroke/stringcodecs/PBKDF2.java8
12 files changed, 424 insertions, 30 deletions
diff --git a/src/com/isode/stroke/client/Client.java b/src/com/isode/stroke/client/Client.java
index 6938225..1d137c5 100644
--- a/src/com/isode/stroke/client/Client.java
+++ b/src/com/isode/stroke/client/Client.java
@@ -28,6 +28,7 @@ import com.isode.stroke.roster.XMPPRosterController;
import com.isode.stroke.roster.XMPPRosterImpl;
import com.isode.stroke.signals.Signal1;
import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.base.SafeByteArray;
/**
* Provides the core functionality for writing XMPP client software.
@@ -76,7 +77,7 @@ public class Client extends CoreClient {
* @param networkFactories An implementation of network interaction, must
* not be null.
*/
- public Client(final JID jid, final String password, final NetworkFactories networkFactories, Storages storages) {
+ public Client(final JID jid, final SafeByteArray password, final NetworkFactories networkFactories, Storages storages) {
super(jid, password, networkFactories);
this.storages = storages;
@@ -111,7 +112,7 @@ public class Client extends CoreClient {
pubSubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter());
}
- public Client(final JID jid, final String password, final NetworkFactories networkFactories) {
+ public Client(final JID jid, final SafeByteArray password, final NetworkFactories networkFactories) {
this(jid, password, networkFactories, null);
}
diff --git a/src/com/isode/stroke/client/ClientSession.java b/src/com/isode/stroke/client/ClientSession.java
index 7654630..fe9481d 100644
--- a/src/com/isode/stroke/client/ClientSession.java
+++ b/src/com/isode/stroke/client/ClientSession.java
@@ -50,6 +50,10 @@ import com.isode.stroke.tls.Certificate;
import com.isode.stroke.tls.CertificateTrustChecker;
import com.isode.stroke.tls.CertificateVerificationError;
import com.isode.stroke.tls.ServerIdentityVerifier;
+import com.isode.stroke.idn.IDNConverter;
+import com.isode.stroke.idn.ICUConverter;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.crypto.JavaCryptoProvider;
import java.util.List;
import java.util.UUID;
@@ -84,6 +88,8 @@ public class ClientSession {
private StanzaAckResponder stanzaAckResponder_;
private com.isode.stroke.base.Error error_;
private CertificateTrustChecker certificateTrustChecker;
+ private IDNConverter idnConverter = new ICUConverter(); //TOPORT: Accomodated later in Constructor.
+ private CryptoProvider crypto = new JavaCryptoProvider(); //TOPORT: Accomodated later in Constructor.
public enum State {
@@ -354,7 +360,7 @@ public class ClientSession {
stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray()));
}
else if (streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1") || streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS")) {
- final SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS"));
+ final SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS"), idnConverter, crypto);
if (stream.isTLSEncrypted()) {
scramAuthenticator.setTLSChannelBindingData(stream.getTLSFinishMessage());
}
@@ -508,7 +514,7 @@ public class ClientSession {
return true;
}
- public void sendCredentials(final String password) {
+ public void sendCredentials(final SafeByteArray password) {
if (!checkState(State.WaitingForCredentials)) {
throw new IllegalStateException("Asking for credentials when we shouldn't be asked.");
}
diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java
index efefa1c..39229a3 100644
--- a/src/com/isode/stroke/client/CoreClient.java
+++ b/src/com/isode/stroke/client/CoreClient.java
@@ -80,7 +80,7 @@ public class CoreClient {
*/
public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>();
private JID jid_;
- private String password_;
+ private SafeByteArray password_;
private ClientSessionStanzaChannel stanzaChannel_;
private IQRouter iqRouter_;
private Connector connector_;
@@ -117,7 +117,7 @@ public class CoreClient {
* @param networkFactories An implementation of network interaction, must
* not be null.
*/
- public CoreClient(final JID jid, final String password, final NetworkFactories networkFactories) {
+ public CoreClient(final JID jid, final SafeByteArray password, final NetworkFactories networkFactories) {
jid_ = jid;
password_ = password;
disconnectRequested_ = false;
diff --git a/src/com/isode/stroke/examples/ConnectDisconnect.java b/src/com/isode/stroke/examples/ConnectDisconnect.java
index 6b4cbc4..12b3c27 100644
--- a/src/com/isode/stroke/examples/ConnectDisconnect.java
+++ b/src/com/isode/stroke/examples/ConnectDisconnect.java
@@ -13,6 +13,7 @@ import com.isode.stroke.jid.JID;
import com.isode.stroke.network.JavaNetworkFactories;
import com.isode.stroke.signals.Slot;
import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.base.SafeByteArray;
/**
* Simple example.
@@ -66,7 +67,7 @@ public class ConnectDisconnect {
public void go(String args[]) {
jid = new JID(args[0]);
- String password = args[1];
+ SafeByteArray password = new SafeByteArray(args[1]);
DummyEventLoop eventLoop = new DummyEventLoop();
JavaNetworkFactories factories = new JavaNetworkFactories(eventLoop);
diff --git a/src/com/isode/stroke/examples/gui/StrokeGUI.java b/src/com/isode/stroke/examples/gui/StrokeGUI.java
index 6fc07de..5d0c1ca 100644
--- a/src/com/isode/stroke/examples/gui/StrokeGUI.java
+++ b/src/com/isode/stroke/examples/gui/StrokeGUI.java
@@ -158,7 +158,7 @@ public class StrokeGUI extends javax.swing.JFrame {
}
};
- client_ = new CoreClient(JID.fromString(loginJID_.getText()), loginPassword_.getText(), new JavaNetworkFactories(eventLoop));
+ client_ = new CoreClient(JID.fromString(loginJID_.getText()), new SafeByteArray(loginPassword_.getText()), new JavaNetworkFactories(eventLoop));
System.out.println("Connecting");
try {
client_.connect(new ClientOptions());
diff --git a/src/com/isode/stroke/sasl/ClientAuthenticator.java b/src/com/isode/stroke/sasl/ClientAuthenticator.java
index 2dc3756..450ef1b 100644
--- a/src/com/isode/stroke/sasl/ClientAuthenticator.java
+++ b/src/com/isode/stroke/sasl/ClientAuthenticator.java
@@ -21,11 +21,11 @@ public abstract class ClientAuthenticator {
return name;
}
- public void setCredentials(String authcid, String password) {
+ public void setCredentials(String authcid, SafeByteArray password) {
setCredentials(authcid, password, "");
}
- public void setCredentials(String authcid, String password, String authzid) {
+ public void setCredentials(String authcid, SafeByteArray password, String authzid) {
this.authcid = authcid;
this.password = password;
this.authzid = authzid;
@@ -43,11 +43,11 @@ public abstract class ClientAuthenticator {
return authzid;
}
- public String getPassword() {
+ public SafeByteArray getPassword() {
return password;
}
private String name;
private String authcid;
- private String password;
+ private SafeByteArray password;
private String authzid;
}
diff --git a/src/com/isode/stroke/sasl/DIGESTMD5ClientAuthenticator.java b/src/com/isode/stroke/sasl/DIGESTMD5ClientAuthenticator.java
new file mode 100644
index 0000000..639c353
--- /dev/null
+++ b/src/com/isode/stroke/sasl/DIGESTMD5ClientAuthenticator.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.sasl.ClientAuthenticator;
+import com.isode.stroke.sasl.DIGESTMD5Properties;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.stringcodecs.Hexify;
+
+public class DIGESTMD5ClientAuthenticator extends ClientAuthenticator {
+
+ private enum Step {
+ Initial,
+ Response,
+ Final
+ };
+ private Step step;
+ private String host = "";
+ private String cnonce = "";
+ private CryptoProvider crypto;
+ private DIGESTMD5Properties challenge = new DIGESTMD5Properties();
+
+ public DIGESTMD5ClientAuthenticator(final String host, final String nonce, CryptoProvider crypto) {
+ super("DIGEST-MD5");
+ this.step = Step.Initial;
+ this.host = host;
+ this.cnonce = nonce;
+ this.crypto = crypto;
+ }
+
+ public SafeByteArray getResponse() {
+ if (Step.Initial.equals(step)) {
+ return null;
+ }
+ else if (Step.Response.equals(step)) {
+ String realm = "";
+ if (challenge.getValue("realm") != null) {
+ realm = challenge.getValue("realm");
+ }
+ String qop = "auth";
+ String digestURI = "xmpp/" + host;
+ String nc = "00000001";
+
+ ByteArray A11 = crypto.getMD5Hash(new SafeByteArray().append(getAuthenticationID()).append(":").append(realm).append(":").append(getPassword()));
+ ByteArray A12 = new ByteArray().append(":").append(challenge.getValue("nonce")).append(":").append(cnonce);
+ // Compute the response value
+ ByteArray A1 = A11.append(A12);
+ if (!getAuthorizationID().isEmpty()) {
+ A1.append(new ByteArray(":" + getAuthenticationID()));
+ }
+ ByteArray A2 = new ByteArray("AUTHENTICATE:" + digestURI);
+
+ String responseValue = Hexify.hexify(crypto.getMD5Hash(new ByteArray(
+ Hexify.hexify(crypto.getMD5Hash(A1)) + ":"
+ + challenge.getValue("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":"
+ + Hexify.hexify(crypto.getMD5Hash(A2)))));
+
+
+ DIGESTMD5Properties response = new DIGESTMD5Properties();
+ response.setValue("username", getAuthenticationID());
+ if (!realm.isEmpty()) {
+ response.setValue("realm", realm);
+ }
+ response.setValue("nonce", challenge.getValue("nonce"));
+ response.setValue("cnonce", cnonce);
+ response.setValue("nc", "00000001");
+ response.setValue("qop", qop);
+ response.setValue("digest-uri", digestURI);
+ response.setValue("charset", "utf-8");
+ response.setValue("response", responseValue);
+ if (!getAuthorizationID().isEmpty()) {
+ response.setValue("authzid", getAuthorizationID());
+ }
+ return new SafeByteArray(response.serialize());
+ }
+ else {
+ return null;
+ }
+ }
+
+ public boolean setChallenge(final ByteArray challengeData) {
+ if (Step.Initial.equals(step)) {
+ if (challengeData == null) {
+ return false;
+ }
+ challenge = DIGESTMD5Properties.parse(challengeData);
+
+ // Sanity checks
+ if (challenge.getValue("nonce") == null) {
+ return false;
+ }
+ if (challenge.getValue("charset") ==null || !(challenge.getValue("charset").equals("utf-8"))) {
+ return false;
+ }
+ step = Step.Response;
+ return true;
+ }
+ else {
+ step = Step.Final;
+ // TODO: Check RSPAuth
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/sasl/DIGESTMD5Properties.java b/src/com/isode/stroke/sasl/DIGESTMD5Properties.java
new file mode 100644
index 0000000..0bc6a8d
--- /dev/null
+++ b/src/com/isode/stroke/sasl/DIGESTMD5Properties.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.sasl.ClientAuthenticator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.base.ByteArray;
+import java.util.TreeMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class DIGESTMD5Properties {
+
+ // Swiften uses std::multimap, but this is not required, so a TreeMap is used here.
+ private TreeMap<String, ByteArray> properties = new TreeMap<String, ByteArray>();
+
+ static boolean insideQuotes(final ByteArray v) {
+ if (v.isEmpty()) {
+ return false;
+ }
+ else if (v.getSize() == 1) {
+ return v.getData()[0] == '"';
+ }
+ else if (v.getData()[0] == '"') {
+ return v.getData()[v.getSize() - 1] != '"';
+ }
+ else {
+ return false;
+ }
+ }
+
+ static ByteArray stripQuotes(final ByteArray v) {
+ String s = new String(v.getData()); // possibly with a charset
+ int size = v.getSize();
+ int i = 0;
+ if(s.charAt(0) == '"') {
+ i++;
+ size--;
+ }
+ if(s.charAt(v.getSize() - 1) == '"') {
+ size--;
+ }
+ String data = s.substring(i, size+1);
+ return new ByteArray(data);
+ }
+
+ public DIGESTMD5Properties() {
+
+ }
+
+ public String getValue(final String key) {
+ if (properties.containsKey(key)) {
+ return properties.get(key).toString();
+ }
+ else {
+ return null;
+ }
+ }
+
+ public void setValue(final String key, final String value) {
+ if(!(properties.containsKey(key))) {
+ properties.put(key, new ByteArray(value));
+ }
+ }
+
+ public ByteArray serialize() {
+ ByteArray result = new ByteArray();
+ for(Map.Entry<String, ByteArray> entry : properties.entrySet()) {
+ if(entry.getKey() != properties.firstKey()) {
+ result.append((byte)(','));
+ }
+ result.append(new ByteArray(entry.getKey()));
+ result.append((byte)('='));
+ if (isQuoted(entry.getKey())) {
+ result.append(new ByteArray("\""));
+ result.append(entry.getValue()); //It does not iterate over all values possible for this key. So no need for MultiMap. Also serialize test also indicates the same.
+ result.append(new ByteArray("\""));
+ }
+ else {
+ result.append(entry.getValue());
+ }
+ }
+ return result;
+ }
+
+ public static DIGESTMD5Properties parse(final ByteArray data) {
+ DIGESTMD5Properties result = new DIGESTMD5Properties();
+ boolean inKey = true;
+ ByteArray currentKey = new ByteArray();
+ ByteArray currentValue = new ByteArray();
+ byte[] byteArrayData = data.getData();
+ for (int i = 0; i < data.getSize(); ++i) {
+ char c = (char)(byteArrayData[i]);
+ if (inKey) {
+ if (c == '=') {
+ inKey = false;
+ }
+ else {
+ currentKey.append((byte)(c));
+ }
+ }
+ else {
+ if (c == ',' && !insideQuotes(currentValue)) {
+ String key = currentKey.toString();
+ if (isQuoted(key)) {
+ result.setValue(key, stripQuotes(currentValue).toString());
+ }
+ else {
+ result.setValue(key, currentValue.toString());
+ }
+ inKey = true;
+ currentKey = new ByteArray();
+ currentValue = new ByteArray();
+ }
+ else {
+ currentValue.append((byte)(c));
+ }
+ }
+ }
+
+ if (!currentKey.isEmpty()) {
+ String key = currentKey.toString();
+ if (isQuoted(key)) {
+ result.setValue(key, stripQuotes(currentValue).toString());
+ }
+ else {
+ result.setValue(key, currentValue.toString());
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isQuoted(final String p) {
+ return p.equals("authzid") || p.equals("cnonce") || p.equals("digest-uri") || p.equals("nonce") || p.equals("realm") || p.equals("username");
+ }
+}
diff --git a/src/com/isode/stroke/sasl/EXTERNALClientAuthenticator.java b/src/com/isode/stroke/sasl/EXTERNALClientAuthenticator.java
new file mode 100644
index 0000000..9cd78d7
--- /dev/null
+++ b/src/com/isode/stroke/sasl/EXTERNALClientAuthenticator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012-2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.sasl.ClientAuthenticator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.base.ByteArray;
+
+public class EXTERNALClientAuthenticator extends ClientAuthenticator {
+
+ private boolean finished;
+
+ public EXTERNALClientAuthenticator() {
+ super("EXTERNAL");
+ this.finished = false;
+ }
+
+ public SafeByteArray getResponse() {
+ return null;
+ }
+
+ public boolean setChallenge(final ByteArray byteArray) {
+ if (finished) {
+ return false;
+ }
+ finished = true;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/sasl/PLAINMessage.java b/src/com/isode/stroke/sasl/PLAINMessage.java
new file mode 100644
index 0000000..5c1fe8b
--- /dev/null
+++ b/src/com/isode/stroke/sasl/PLAINMessage.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.sasl;
+
+import com.isode.stroke.sasl.ClientAuthenticator;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.base.ByteArray;
+
+public class PLAINMessage {
+
+ private String authcid = "";
+ private String authzid = "";
+ private SafeByteArray password = new SafeByteArray();
+
+ public PLAINMessage(final String authcid, final SafeByteArray password) {
+ this(authcid, password, "");
+ }
+
+ public PLAINMessage(final String authcid, final SafeByteArray password, final String authzid) {
+ this.authcid = authcid;
+ this.password = password;
+ this.authzid = authzid;
+ }
+
+ public PLAINMessage(final SafeByteArray value) {
+ int i = 0;
+ byte[] byteArrayValue = value.getData();
+ while (i < value.getSize() && byteArrayValue[i] != ((byte)0)) {
+ authzid += (char)(byteArrayValue[i]);
+ ++i;
+ }
+ if (i == value.getSize()) {
+ return;
+ }
+ ++i;
+ while (i < value.getSize() && byteArrayValue[i] != ((byte)0)) {
+ authcid += (char)(byteArrayValue[i]);
+ ++i;
+ }
+ if (i == value.getSize()) {
+ authcid = "";
+ return;
+ }
+ ++i;
+ while (i < value.getSize()) {
+ password.append(byteArrayValue[i]);
+ ++i;
+ }
+ }
+
+ public SafeByteArray getValue() {
+ return new SafeByteArray().append(authzid).append((byte)0).append(authcid).append((byte)0).append(password);
+ }
+
+ public String getAuthenticationID() {
+ return authcid;
+ }
+
+ public SafeByteArray getPassword() {
+ return password;
+ }
+
+ public String getAuthorizationID() {
+ return authzid;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java b/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java
index 29a37aa..9797e24 100644
--- a/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java
+++ b/src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java
@@ -10,10 +10,13 @@ package com.isode.stroke.sasl;
import com.isode.stroke.base.ByteArray;
import com.isode.stroke.base.SafeByteArray;
+import com.ibm.icu.text.StringPrepParseException;
import com.isode.stroke.stringcodecs.Base64;
import com.isode.stroke.stringcodecs.HMACSHA1;
import com.isode.stroke.stringcodecs.PBKDF2;
import com.isode.stroke.stringcodecs.SHA1;
+import com.isode.stroke.idn.IDNConverter;
+import com.isode.stroke.crypto.CryptoProvider;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.HashMap;
@@ -35,14 +38,13 @@ public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
return result;
}
- public SCRAMSHA1ClientAuthenticator(String nonce) {
- this(nonce, false);
- }
- public SCRAMSHA1ClientAuthenticator(String nonce, boolean useChannelBinding) {
+ public SCRAMSHA1ClientAuthenticator(String nonce, boolean useChannelBinding, IDNConverter idnConverter, CryptoProvider crypto) {
super(useChannelBinding ? "SCRAM-SHA-1-PLUS" : "SCRAM-SHA-1");
step = Step.Initial;
clientnonce = nonce;
this.useChannelBinding = useChannelBinding;
+ this.idnConverter = idnConverter;
+ this.crypto = crypto;
}
public void setTLSChannelBindingData(ByteArray channelBindingData) {
@@ -53,9 +55,9 @@ public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
if (step.equals(Step.Initial)) {
return new SafeByteArray(getGS2Header().append(getInitialBareClientMessage()));
} else if (step.equals(Step.Proof)) {
- ByteArray clientKey = HMACSHA1.getResult(saltedPassword, new ByteArray("Client Key"));
- ByteArray storedKey = SHA1.getHash(clientKey);
- ByteArray clientSignature = HMACSHA1.getResult(storedKey, authMessage);
+ ByteArray clientKey = crypto.getHMACSHA1(saltedPassword, new ByteArray("Client Key"));
+ ByteArray storedKey = crypto.getSHA1Hash(clientKey);
+ ByteArray clientSignature = crypto.getHMACSHA1(new SafeByteArray(storedKey), authMessage);
ByteArray clientProof = clientKey;
byte[] clientProofData = clientProof.getData();
for (int i = 0; i < clientProofData.length; ++i) {
@@ -104,16 +106,21 @@ public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
return false;
}
+ //Not Sure, why this here.
ByteArray channelBindData = new ByteArray();
if (useChannelBinding && tlsChannelBindingData != null) {
channelBindData = tlsChannelBindingData;
}
// Compute all the values needed for the server signature
- saltedPassword = PBKDF2.encode(new ByteArray(SASLPrep(getPassword())), salt, iterations);
+ try {
+ saltedPassword = PBKDF2.encode(idnConverter.getStringPrepared(getPassword(), IDNConverter.StringPrepProfile.SASLPrep), salt, iterations, crypto);
+ } catch (StringPrepParseException e) {
+
+ }
authMessage = getInitialBareClientMessage().append(",").append(initialServerMessage).append(",").append(getFinalMessageWithoutProof());
- ByteArray serverKey = HMACSHA1.getResult(saltedPassword, new ByteArray("Server Key"));
- serverSignature = HMACSHA1.getResult(serverKey, authMessage);
+ ByteArray serverKey = crypto.getHMACSHA1(saltedPassword, new ByteArray("Server Key"));
+ serverSignature = crypto.getHMACSHA1(serverKey, authMessage);
step = Step.Proof;
return true;
@@ -126,10 +133,6 @@ public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
}
}
- private String SASLPrep(String source) {
- return Normalizer.normalize(source, Form.NFKC); /* FIXME: Implement real SASLPrep */
- }
-
private Map<Character, String> parseMap(String s) {
HashMap<Character, String> result = new HashMap<Character, String>();
if (s.length() > 0) {
@@ -157,7 +160,12 @@ public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
}
private ByteArray getInitialBareClientMessage() {
- String authenticationID = SASLPrep(getAuthenticationID());
+ String authenticationID = "";
+ try {
+ authenticationID = idnConverter.getStringPrepared(getAuthenticationID(), IDNConverter.StringPrepProfile.SASLPrep);
+ } catch (StringPrepParseException e) {
+
+ }
return new ByteArray("n=" + escape(authenticationID) + ",r=" + clientnonce);
}
@@ -198,4 +206,6 @@ public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator {
private ByteArray serverSignature = new ByteArray();
private boolean useChannelBinding;
private ByteArray tlsChannelBindingData;
+ private IDNConverter idnConverter;
+ private CryptoProvider crypto;
}
diff --git a/src/com/isode/stroke/stringcodecs/PBKDF2.java b/src/com/isode/stroke/stringcodecs/PBKDF2.java
index 1392d2f..6147ee4 100644
--- a/src/com/isode/stroke/stringcodecs/PBKDF2.java
+++ b/src/com/isode/stroke/stringcodecs/PBKDF2.java
@@ -9,16 +9,18 @@
package com.isode.stroke.stringcodecs;
import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.base.SafeByteArray;
+import com.isode.stroke.crypto.CryptoProvider;
public class PBKDF2 {
- public static ByteArray encode(ByteArray password, ByteArray salt, int iterations) {
- ByteArray u = HMACSHA1.getResult(password, ByteArray.plus(salt, new ByteArray("\0\0\0\1")));
+ public static ByteArray encode(SafeByteArray password, ByteArray salt, int iterations, CryptoProvider crypto) {
+ ByteArray u = crypto.getHMACSHA1(password, ByteArray.plus(salt, new ByteArray("\0\0\0\1")));
ByteArray result = new ByteArray(u);
byte[] resultData = result.getData();
int i = 1;
while (i < iterations) {
- u = HMACSHA1.getResult(password, u);
+ u = crypto.getHMACSHA1(password, u);
for (int j = 0; j < u.getSize(); ++j) {
resultData[j] ^= u.getData()[j];
}