diff options
author | Tarun Gupta <tarun1995gupta@gmail.com> | 2015-07-22 22:21:20 (GMT) |
---|---|---|
committer | Nick Hudson <nick.hudson@isode.com> | 2015-08-03 14:06:04 (GMT) |
commit | af3bb03053b9d83f4d38b31d66b292792206a327 (patch) | |
tree | 0e5826f6b1fbb638d899ee6aadca902a7c2fdb18 /src/com | |
parent | 32ef37b9059e21de19209a9a1ab4ef2564051918 (diff) | |
download | stroke-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')
-rw-r--r-- | src/com/isode/stroke/client/Client.java | 5 | ||||
-rw-r--r-- | src/com/isode/stroke/client/ClientSession.java | 10 | ||||
-rw-r--r-- | src/com/isode/stroke/client/CoreClient.java | 4 | ||||
-rw-r--r-- | src/com/isode/stroke/examples/ConnectDisconnect.java | 3 | ||||
-rw-r--r-- | src/com/isode/stroke/examples/gui/StrokeGUI.java | 2 | ||||
-rw-r--r-- | src/com/isode/stroke/sasl/ClientAuthenticator.java | 8 | ||||
-rw-r--r-- | src/com/isode/stroke/sasl/DIGESTMD5ClientAuthenticator.java | 115 | ||||
-rw-r--r-- | src/com/isode/stroke/sasl/DIGESTMD5Properties.java | 146 | ||||
-rw-r--r-- | src/com/isode/stroke/sasl/EXTERNALClientAuthenticator.java | 38 | ||||
-rw-r--r-- | src/com/isode/stroke/sasl/PLAINMessage.java | 75 | ||||
-rw-r--r-- | src/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java | 40 | ||||
-rw-r--r-- | src/com/isode/stroke/stringcodecs/PBKDF2.java | 8 |
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]; } |