diff options
author | Robert Greig <rgreig@apache.org> | 2007-01-29 10:46:27 +0000 |
---|---|---|
committer | Robert Greig <rgreig@apache.org> | 2007-01-29 10:46:27 +0000 |
commit | 2bcc371558ce0659f53b86046cdf3d5de3b20910 (patch) | |
tree | d0c987cfa076eb90edb80620661d69a6e7354d3a /dotnet | |
parent | fe736211136b60bec61c1a22d3765be9142c6b39 (diff) | |
download | qpid-python-2bcc371558ce0659f53b86046cdf3d5de3b20910.tar.gz |
(Patch supplied by Tomas Restrepo) QPID-291-2.diff applied. Adds SASL capability to the .Net client.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@501001 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'dotnet')
46 files changed, 3307 insertions, 46 deletions
diff --git a/dotnet/Qpid.Client.Tests/App.config b/dotnet/Qpid.Client.Tests/App.config new file mode 100644 index 0000000000..64c6def5fd --- /dev/null +++ b/dotnet/Qpid.Client.Tests/App.config @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <configSections>
+ <sectionGroup name="qpid.client">
+ <section name="authentication" type="Qpid.Client.Configuration.AuthenticationConfigurationSectionHandler, Qpid.Client"/>
+ </sectionGroup>
+ </configSections>
+ <qpid.client>
+ <authentication>
+ <add key="TEST" value="Qpid.Client.Tests.Security.TestCallbackHandler, Qpid.Client.Tests"/>
+ </authentication>
+ </qpid.client>
+</configuration>
diff --git a/dotnet/Qpid.Client.Tests/Qpid.Client.Tests.csproj b/dotnet/Qpid.Client.Tests/Qpid.Client.Tests.csproj index b14f8405c9..5674174f69 100644 --- a/dotnet/Qpid.Client.Tests/Qpid.Client.Tests.csproj +++ b/dotnet/Qpid.Client.Tests/Qpid.Client.Tests.csproj @@ -20,6 +20,7 @@ <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <UseVSHostingProcess>true</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -53,6 +54,7 @@ <Compile Include="Common\BaseMessagingTestFixture.cs" />
<Compile Include="requestreply1\ServiceProvidingClient.cs" />
<Compile Include="requestreply1\ServiceRequestingClient.cs" />
+ <Compile Include="Security\CallbackHandlerRegistryTests.cs" />
<Compile Include="undeliverable\UndeliverableTest.cs" />
<Compile Include="url\ConnectionUrlTest.cs" />
</ItemGroup>
@@ -69,6 +71,10 @@ <Project>{77064C42-24D2-4CEB-9EA2-0EF481A43205}</Project>
<Name>Qpid.Common</Name>
</ProjectReference>
+ <ProjectReference Include="..\Qpid.Sasl\Qpid.Sasl.csproj">
+ <Project>{1465B0EE-6452-42A6-AB73-B2F9EABEEE75}</Project>
+ <Name>Qpid.Sasl</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="log4net.config">
@@ -76,6 +82,7 @@ </None>
</ItemGroup>
<ItemGroup>
+ <None Include="App.config" />
<None Include="Qpid.Common.DLL.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -88,4 +95,4 @@ <Target Name="AfterBuild">
</Target>
-->
-</Project>
\ No newline at end of file +</Project>
diff --git a/dotnet/Qpid.Client.Tests/Security/CallbackHandlerRegistryTests.cs b/dotnet/Qpid.Client.Tests/Security/CallbackHandlerRegistryTests.cs new file mode 100644 index 0000000000..ec0594263f --- /dev/null +++ b/dotnet/Qpid.Client.Tests/Security/CallbackHandlerRegistryTests.cs @@ -0,0 +1,55 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+using System;
+using NUnit.Framework;
+using Qpid.Client.Security;
+
+
+namespace Qpid.Client.Tests.Security
+{
+ [TestFixture]
+ public class CallbackRegistryHandlerTests
+ {
+ [Test]
+ public void ParsesConfiguration()
+ {
+ CallbackHandlerRegistry registry = CallbackHandlerRegistry.Instance;
+ Assert.AreEqual(3, registry.Mechanisms.Length);
+ Assert.Contains("TEST", registry.Mechanisms);
+
+ Type handlerType = registry.GetCallbackHandler("TEST");
+ Assert.IsNotNull(handlerType);
+ Assert.AreEqual(typeof(TestCallbackHandler), handlerType);
+ }
+ } // class CallbackRegistryHandlerTests
+
+ public class TestCallbackHandler : IAMQCallbackHandler
+ {
+ public void Initialize(Qpid.Client.Protocol.AMQProtocolSession session)
+ {
+ }
+ public void Handle(Qpid.Sasl.ISaslCallback[] callbacks)
+ {
+ }
+
+ } // class TestCallbackHandler
+
+} // namespace Qpid.Client.Tests.Connection
diff --git a/dotnet/Qpid.Client.Tests/connection/ConnectionTest.cs b/dotnet/Qpid.Client.Tests/connection/ConnectionTest.cs index 5aa6773e53..d5330898a0 100644 --- a/dotnet/Qpid.Client.Tests/connection/ConnectionTest.cs +++ b/dotnet/Qpid.Client.Tests/connection/ConnectionTest.cs @@ -23,13 +23,13 @@ using NUnit.Framework; using Qpid.Client.qms; using Qpid.Messaging; -namespace Qpid.Client.Tests.connection +namespace Qpid.Client.Tests.Connection { [TestFixture] public class ConnectionTest { [Test] - public void simpleConnection() + public void SimpleConnection() { ConnectionInfo connectionInfo = new QpidConnectionInfo(); connectionInfo.AddBrokerInfo(new AmqBrokerInfo("amqp", "localhost", 5672, false)); @@ -40,7 +40,7 @@ namespace Qpid.Client.Tests.connection } [Test] - public void passwordFailureConnection() + public void PasswordFailureConnection() { ConnectionInfo connectionInfo = new QpidConnectionInfo(); connectionInfo.SetPassword("rubbish"); @@ -50,16 +50,17 @@ namespace Qpid.Client.Tests.connection using (IConnection connection = new AMQConnection(connectionInfo)) { Console.WriteLine("connection = " + connection); + // wrong + Assert.Fail("Authentication succeeded but should've failed"); } } - catch (AMQException) + catch (AMQException e) { - Assert.Fail(); -// if (!(e is AMQAuthenticationException)) -// { -// Assert.Fail("Expected AMQAuthenticationException!"); -// } - } + if (!(e.InnerException is AMQAuthenticationException)) + { + Assert.Fail("Expected AMQAuthenticationException!"); + } + } } // // [Test] @@ -96,4 +97,4 @@ namespace Qpid.Client.Tests.connection // } // } } -}
\ No newline at end of file +} diff --git a/dotnet/Qpid.Client/Client/AMQAuthenticationException.cs b/dotnet/Qpid.Client/Client/AMQAuthenticationException.cs new file mode 100644 index 0000000000..68cacad1ef --- /dev/null +++ b/dotnet/Qpid.Client/Client/AMQAuthenticationException.cs @@ -0,0 +1,39 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+using System;
+using System.Runtime.Serialization;
+
+namespace Qpid.Client
+{
+ [Serializable]
+ public class AMQAuthenticationException : AMQException
+ {
+ public AMQAuthenticationException(int error, String message)
+ : base(error, message)
+ {
+ }
+
+ protected AMQAuthenticationException(SerializationInfo info, StreamingContext ctxt)
+ : base(info, ctxt)
+ {
+ }
+ }
+}
diff --git a/dotnet/Qpid.Client/Client/AMQConnection.cs b/dotnet/Qpid.Client/Client/AMQConnection.cs index 1da46f19fd..3192b0018d 100644 --- a/dotnet/Qpid.Client/Client/AMQConnection.cs +++ b/dotnet/Qpid.Client/Client/AMQConnection.cs @@ -730,7 +730,7 @@ namespace Qpid.Client catch (AMQException e) { _lastAMQException = e; - throw e; + throw; // rethrow } } diff --git a/dotnet/Qpid.Client/Client/Configuration/AuthenticationConfigurationSectionHandler.cs b/dotnet/Qpid.Client/Client/Configuration/AuthenticationConfigurationSectionHandler.cs new file mode 100644 index 0000000000..54ee2c6660 --- /dev/null +++ b/dotnet/Qpid.Client/Client/Configuration/AuthenticationConfigurationSectionHandler.cs @@ -0,0 +1,64 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Configuration;
+using System.Text;
+
+using Qpid.Client.Security;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Client.Configuration
+{
+ public class AuthenticationConfigurationSectionHandler
+ : IConfigurationSectionHandler
+ {
+
+ public object Create(object parent, object configContext, System.Xml.XmlNode section)
+ {
+ NameValueSectionHandler handler = new NameValueSectionHandler();
+ IDictionary schemes = new Hashtable();
+
+ NameValueCollection options = (NameValueCollection)
+ handler.Create(parent, configContext, section);
+
+ if ( options != null )
+ {
+ foreach ( string key in options.Keys )
+ {
+ Type type = Type.GetType(options[key]);
+ if ( type == null )
+ throw new ConfigurationException(string.Format("Type '{0}' not found", key));
+ if ( !typeof(IAMQCallbackHandler).IsAssignableFrom(type) )
+ throw new ConfigurationException(string.Format("Type '{0}' does not implement IAMQCallbackHandler", key));
+
+ schemes[key] = type;
+ }
+ }
+
+ return schemes;
+ }
+
+ } // class AuthenticationConfigurationSectionHandler
+
+} // namespace Qpid.Client.Configuration
diff --git a/dotnet/Qpid.Client/Client/Handler/ChannelCloseMethodHandler.cs b/dotnet/Qpid.Client/Client/Handler/ChannelCloseMethodHandler.cs index 1031f804a6..0ce8a393c9 100644 --- a/dotnet/Qpid.Client/Client/Handler/ChannelCloseMethodHandler.cs +++ b/dotnet/Qpid.Client/Client/Handler/ChannelCloseMethodHandler.cs @@ -21,6 +21,7 @@ using log4net; using Qpid.Client.Protocol; using Qpid.Client.State; +using Qpid.Protocol; using Qpid.Framing; namespace Qpid.Client.Handler @@ -43,9 +44,8 @@ namespace Qpid.Client.Handler AMQFrame frame = ChannelCloseOkBody.CreateAMQFrame(evt.ChannelId); evt.ProtocolSession.WriteFrame(frame); - //if (errorCode != AMQConstant.REPLY_SUCCESS.getCode()) // HACK - if (errorCode != 200) + if ( errorCode != AMQConstant.REPLY_SUCCESS.Code ) { _logger.Debug("Channel close received with errorCode " + errorCode + ", throwing exception"); evt.ProtocolSession.AMQConnection.ExceptionReceived(new AMQChannelClosedException(errorCode, "Error: " + reason)); @@ -55,3 +55,4 @@ namespace Qpid.Client.Handler } } + diff --git a/dotnet/Qpid.Client/Client/Handler/ConnectionCloseMethodHandler.cs b/dotnet/Qpid.Client/Client/Handler/ConnectionCloseMethodHandler.cs index c3acc0b098..dea5316d25 100644 --- a/dotnet/Qpid.Client/Client/Handler/ConnectionCloseMethodHandler.cs +++ b/dotnet/Qpid.Client/Client/Handler/ConnectionCloseMethodHandler.cs @@ -19,10 +19,12 @@ * */ using System; +using System.Threading; using log4net; using Qpid.Client.Protocol; using Qpid.Client.State; using Qpid.Framing; +using Qpid.Protocol; namespace Qpid.Client.Handler { @@ -38,16 +40,29 @@ namespace Qpid.Client.Handler int errorCode = method.ReplyCode; String reason = method.ReplyText; + // send CloseOK evt.ProtocolSession.WriteFrame(ConnectionCloseOkBody.CreateAMQFrame(evt.ChannelId)); - stateManager.ChangeState(AMQState.CONNECTION_CLOSED); - if (errorCode != 200) + + if ( errorCode != AMQConstant.REPLY_SUCCESS.Code ) { - _logger.Debug("Connection close received with error code " + errorCode); - throw new AMQConnectionClosedException(errorCode, "Error: " + reason); - } + if ( errorCode == AMQConstant.NOT_ALLOWED.Code ) + { + _logger.Info("Authentication Error: " + Thread.CurrentThread.Name); + evt.ProtocolSession.CloseProtocolSession(); + + //todo this is a bit of a fudge (could be conssidered such as each new connection needs a new state manager or at least a fresh state. + stateManager.ChangeState(AMQState.CONNECTION_NOT_STARTED); + throw new AMQAuthenticationException(errorCode, reason); + } else + { + _logger.Info("Connection close received with error code " + errorCode); + throw new AMQConnectionClosedException(errorCode, "Error: " + reason); + } + } // this actually closes the connection in the case where it is not an error. evt.ProtocolSession.CloseProtocolSession(); + stateManager.ChangeState(AMQState.CONNECTION_CLOSED); } } } diff --git a/dotnet/Qpid.Client/Client/Handler/ConnectionSecureMethodHandler.cs b/dotnet/Qpid.Client/Client/Handler/ConnectionSecureMethodHandler.cs index 7c0fbd8f40..fe123e6745 100644 --- a/dotnet/Qpid.Client/Client/Handler/ConnectionSecureMethodHandler.cs +++ b/dotnet/Qpid.Client/Client/Handler/ConnectionSecureMethodHandler.cs @@ -21,6 +21,7 @@ using Qpid.Client.Protocol; using Qpid.Client.State; using Qpid.Framing; +using Qpid.Sasl; namespace Qpid.Client.Handler { @@ -28,9 +29,31 @@ namespace Qpid.Client.Handler { public void MethodReceived(AMQStateManager stateManager, AMQMethodEvent evt) { - AMQFrame response = ConnectionSecureOkBody.CreateAMQFrame(evt.ChannelId, null); - evt.ProtocolSession.WriteFrame(response); + ISaslClient saslClient = evt.ProtocolSession.SaslClient; + if ( saslClient == null ) + { + throw new AMQException("No SASL client set up - cannot proceed with authentication"); + } + + + ConnectionSecureBody body = (ConnectionSecureBody)evt.Method; + + try + { + // Evaluate server challenge + byte[] response = saslClient.EvaluateChallenge(body.Challenge); + // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) + // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. + // Be aware of possible changes to parameter order as versions change. + AMQFrame responseFrame = ConnectionSecureOkBody.CreateAMQFrame( + evt.ChannelId, response); + evt.ProtocolSession.WriteFrame(responseFrame); + } catch ( SaslException e ) + { + throw new AMQException("Error processing SASL challenge: " + e, e); + } } } } + diff --git a/dotnet/Qpid.Client/Client/Handler/ConnectionStartMethodHandler.cs b/dotnet/Qpid.Client/Client/Handler/ConnectionStartMethodHandler.cs index e88ff3ddbd..1815bea152 100644 --- a/dotnet/Qpid.Client/Client/Handler/ConnectionStartMethodHandler.cs +++ b/dotnet/Qpid.Client/Client/Handler/ConnectionStartMethodHandler.cs @@ -19,11 +19,15 @@ * */ using System; +using System.Collections; using System.Text; using log4net; using Qpid.Client.Protocol; +using Qpid.Client.Security; using Qpid.Client.State; using Qpid.Framing; +using Qpid.Sasl; + namespace Qpid.Client.Handler { @@ -35,36 +39,22 @@ namespace Qpid.Client.Handler { ConnectionStartBody body = (ConnectionStartBody) evt.Method; AMQProtocolSession ps = evt.ProtocolSession; - string username = ps.Username; - string password = ps.Password; try { - if (body.Mechanisms == null) + if ( body.Mechanisms == null ) { throw new AMQException("mechanism not specified in ConnectionStart method frame"); } - string allMechanisms = Encoding.ASCII.GetString(body.Mechanisms); - string[] mechanisms = allMechanisms.Split(' '); - string selectedMechanism = null; - foreach (string mechanism in mechanisms) - { - if (mechanism.Equals("PLAIN")) - { - selectedMechanism = mechanism; - break; - } - } - - if (selectedMechanism == null) + string mechanisms = Encoding.UTF8.GetString(body.Mechanisms); + string selectedMechanism = ChooseMechanism(mechanisms); + if ( selectedMechanism == null ) { throw new AMQException("No supported security mechanism found, passed: " + mechanisms); } + + byte[] saslResponse = DoAuthentication(selectedMechanism, ps); - // we always write out a null authzid which we don't currently use - byte[] plainData = new byte[1 + ps.Username.Length + 1 + ps.Password.Length]; - Encoding.UTF8.GetBytes(username, 0, username.Length, plainData, 1); - Encoding.UTF8.GetBytes(password, 0, password.Length, plainData, username.Length + 2); if (body.Locales == null) { throw new AMQException("Locales is not defined in Connection Start method"); @@ -86,8 +76,9 @@ namespace Qpid.Client.Handler clientProperties["product"] = "Qpid.NET"; clientProperties["version"] = "1.0"; clientProperties["platform"] = GetFullSystemInfo(); - AMQFrame frame = ConnectionStartOkBody.CreateAMQFrame(evt.ChannelId, clientProperties, selectedMechanism, - plainData, selectedLocale); + AMQFrame frame = ConnectionStartOkBody.CreateAMQFrame( + evt.ChannelId, clientProperties, selectedMechanism, + saslResponse, selectedLocale); ps.WriteFrame(frame); } catch (Exception e) @@ -109,5 +100,51 @@ namespace Qpid.Client.Handler // TODO: add in details here return ".NET 1.1 Client"; } + + private string ChooseMechanism(string mechanisms) + { + foreach ( string mech in mechanisms.Split(' ') ) + { + if ( CallbackHandlerRegistry.Instance.IsSupportedMechanism(mech) ) + { + return mech; + } + } + return null; + } + + private byte[] DoAuthentication(string selectedMechanism, AMQProtocolSession ps) + { + ISaslClient saslClient = Sasl.Sasl.CreateClient( + new string[] { selectedMechanism }, null, "AMQP", "localhost", + new Hashtable(), CreateCallbackHandler(selectedMechanism, ps) + ); + if ( saslClient == null ) + { + throw new AMQException("Client SASL configuration error: no SaslClient could be created for mechanism " + + selectedMechanism); + } + ps.SaslClient = saslClient; + try + { + return saslClient.HasInitialResponse ? + saslClient.EvaluateChallenge(new byte[0]) : null; + } catch ( Exception ex ) + { + ps.SaslClient = null; + throw new AMQException("Unable to create SASL client", ex); + } + } + + private IAMQCallbackHandler CreateCallbackHandler(string mechanism, AMQProtocolSession session) + { + Type type = CallbackHandlerRegistry.Instance.GetCallbackHandler(mechanism); + IAMQCallbackHandler handler = + (IAMQCallbackHandler)Activator.CreateInstance(type); + if ( handler == null ) + throw new AMQException("Unable to create callback handler: " + mechanism); + handler.Initialize(session); + return handler; + } } } diff --git a/dotnet/Qpid.Client/Client/Protocol/AMQProtocolSession.cs b/dotnet/Qpid.Client/Client/Protocol/AMQProtocolSession.cs index 15696f38c5..42169f31b3 100644 --- a/dotnet/Qpid.Client/Client/Protocol/AMQProtocolSession.cs +++ b/dotnet/Qpid.Client/Client/Protocol/AMQProtocolSession.cs @@ -24,6 +24,7 @@ using log4net; using Qpid.Client.Message; using Qpid.Client.Transport; using Qpid.Framing; +using Qpid.Sasl; namespace Qpid.Client.Protocol { @@ -104,6 +105,13 @@ namespace Qpid.Client.Protocol con.MaximumFrameSize = value.FrameMax; } } + + private ISaslClient _saslClient; + public ISaslClient SaslClient + { + get { return _saslClient; } + set { _saslClient = value; } + } /// <summary> /// Callback invoked from the BasicDeliverMethodHandler when a message has been received. diff --git a/dotnet/Qpid.Client/Client/Security/CallbackHandlerRegistry.cs b/dotnet/Qpid.Client/Client/Security/CallbackHandlerRegistry.cs new file mode 100644 index 0000000000..78f13c9f42 --- /dev/null +++ b/dotnet/Qpid.Client/Client/Security/CallbackHandlerRegistry.cs @@ -0,0 +1,114 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Configuration;
+using System.Text;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+using Qpid.Client.Configuration;
+
+namespace Qpid.Client.Security
+{
+
+ /// <summary>
+ /// Helper class to map SASL mechanisms to our
+ /// internal ISaslCallbackHandler implementations.
+ /// </summary>
+ /// <remarks>
+ /// The set of configured callback handlers and their order
+ /// controls the selection of the SASL mechanism used for authentication.
+ /// <para>
+ /// You can either replace the default handler for CRAM-MD5 and PLAIN
+ /// authentication (the two default options) using the application
+ /// configuration file. Configuration is done by especifying the SASL
+ /// mechanism name (e.g PLAIN) and the type implementing the callback handler
+ /// used to provide any data required by the mechanism like username and password.
+ /// </para>
+ /// <para>
+ /// Callback handler types should implement the IAMQCallbackHandler interface.
+ /// </para>
+ /// <para>
+ /// New callbacks or authentication mechanisms can be configured like this:
+ /// </para>
+ /// <example><![CDATA[
+ /// <configuration>
+ /// <configSections>
+ /// <sectionGroup name="qpid.client">
+ /// <section name="authentication" type="Qpid.Client.Configuration.AuthenticationConfigurationSectionHandler, Qpid.Client"/>
+ /// </sectionGroup>
+ /// </configSections>
+ /// <qpid.client>
+ /// <authentication>
+ /// <add key="TEST" value="Qpid.Client.Tests.Security.TestCallbackHandler, Qpid.Client.Tests"/>
+ /// </authentication>
+ /// </qpid.client>
+ /// </configuration>
+ /// ]]></example>
+ /// </remarks>
+ public sealed class CallbackHandlerRegistry
+ {
+ private static CallbackHandlerRegistry _instance =
+ new CallbackHandlerRegistry();
+ private IDictionary _mechanism2HandlerMap;
+ private string[] _mechanisms;
+
+ public static CallbackHandlerRegistry Instance
+ {
+ get { return _instance; }
+ }
+
+ public string[] Mechanisms
+ {
+ get { return _mechanisms; }
+ }
+
+ private CallbackHandlerRegistry()
+ {
+ _mechanism2HandlerMap = (IDictionary)
+ ConfigurationSettings.GetConfig("qpid.client/authentication");
+
+ // configure default options if not available
+ if ( _mechanism2HandlerMap == null )
+ _mechanism2HandlerMap = new Hashtable();
+
+ if ( !_mechanism2HandlerMap.Contains(CramMD5SaslClient.Mechanism) )
+ _mechanism2HandlerMap.Add(CramMD5SaslClient.Mechanism, typeof(UsernamePasswordCallbackHandler));
+ if ( !_mechanism2HandlerMap.Contains(PlainSaslClient.Mechanism) )
+ _mechanism2HandlerMap.Add(PlainSaslClient.Mechanism, typeof(UsernamePasswordCallbackHandler));
+
+ _mechanisms = new string[_mechanism2HandlerMap.Keys.Count];
+ _mechanism2HandlerMap.Keys.CopyTo(_mechanisms, 0);
+ }
+
+ public bool IsSupportedMechanism(string mechanism)
+ {
+ return _mechanism2HandlerMap.Contains(mechanism);
+ }
+
+ public Type GetCallbackHandler(string mechanism)
+ {
+ return (Type)_mechanism2HandlerMap[mechanism];
+ }
+ }
+}
diff --git a/dotnet/Qpid.Client/Client/Security/IAMQCallbackHandler.cs b/dotnet/Qpid.Client/Client/Security/IAMQCallbackHandler.cs new file mode 100644 index 0000000000..6802b90cee --- /dev/null +++ b/dotnet/Qpid.Client/Client/Security/IAMQCallbackHandler.cs @@ -0,0 +1,34 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+using System;
+using System.Text;
+using Qpid.Client.Protocol;
+using Qpid.Sasl;
+
+namespace Qpid.Client.Security
+{
+ public interface IAMQCallbackHandler : ISaslCallbackHandler
+ {
+ void Initialize(AMQProtocolSession session);
+ }
+
+}
+
diff --git a/dotnet/Qpid.Client/Client/Security/UsernamePasswordCallbackHandler.cs b/dotnet/Qpid.Client/Client/Security/UsernamePasswordCallbackHandler.cs new file mode 100644 index 0000000000..a14139496c --- /dev/null +++ b/dotnet/Qpid.Client/Client/Security/UsernamePasswordCallbackHandler.cs @@ -0,0 +1,56 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+using Qpid.Client.Protocol;
+using Qpid.Sasl;
+
+namespace Qpid.Client.Security
+{
+ internal class UsernamePasswordCallbackHandler : IAMQCallbackHandler
+ {
+ private AMQProtocolSession _session;
+
+ public void Initialize(AMQProtocolSession session)
+ {
+ if ( session == null )
+ throw new ArgumentNullException("session");
+
+ _session = session;
+ }
+
+ public void Handle(ISaslCallback[] callbacks)
+ {
+ foreach ( ISaslCallback cb in callbacks )
+ {
+ if ( cb is NameCallback )
+ {
+ ((NameCallback)cb).Text = _session.Username;
+ } else if ( cb is PasswordCallback )
+ {
+ ((PasswordCallback)cb).Text = _session.Password;
+ }
+ }
+ }
+ }
+}
diff --git a/dotnet/Qpid.Client/Qpid.Client.csproj b/dotnet/Qpid.Client/Qpid.Client.csproj index 83609b0757..19d2180a09 100644 --- a/dotnet/Qpid.Client/Qpid.Client.csproj +++ b/dotnet/Qpid.Client/Qpid.Client.csproj @@ -42,6 +42,8 @@ <Compile Include="Client\AMQConnectionException.cs" />
<Compile Include="Client\AMQDestination.cs" />
<Compile Include="Client\AmqChannel.cs" />
+ <Compile Include="Client\AMQAuthenticationException.cs" />
+ <Compile Include="Client\Configuration\AuthenticationConfigurationSectionHandler.cs" />
<Compile Include="Client\Message\QpidHeaders.cs" />
<Compile Include="Client\QpidConnectionInfo.cs" />
<Compile Include="Client\BasicMessageConsumer.cs" />
@@ -81,6 +83,9 @@ <Compile Include="Client\Protocol\IConnectionCloser.cs" />
<Compile Include="Client\Protocol\ProtocolWriter.cs" />
<Compile Include="Client\Protocol\IProtocolListener.cs" />
+ <Compile Include="Client\Security\CallbackHandlerRegistry.cs" />
+ <Compile Include="Client\Security\IAMQCallbackHandler.cs" />
+ <Compile Include="Client\Security\UsernamePasswordCallbackHandler.cs" />
<Compile Include="Client\State\AMQState.cs" />
<Compile Include="Client\State\AMQStateChangedEvent.cs" />
<Compile Include="Client\State\AMQStateManager.cs" />
@@ -126,6 +131,10 @@ <Project>{77064C42-24D2-4CEB-9EA2-0EF481A43205}</Project>
<Name>Qpid.Common</Name>
</ProjectReference>
+ <ProjectReference Include="..\Qpid.Sasl\Qpid.Sasl.csproj">
+ <Project>{1465B0EE-6452-42A6-AB73-B2F9EABEEE75}</Project>
+ <Name>Qpid.Sasl</Name>
+ </ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
@@ -135,4 +144,4 @@ <Target Name="AfterBuild">
</Target>
-->
-</Project>
\ No newline at end of file +</Project>
diff --git a/dotnet/Qpid.Common/Protocol/AMQConstant.cs b/dotnet/Qpid.Common/Protocol/AMQConstant.cs new file mode 100644 index 0000000000..560ac97122 --- /dev/null +++ b/dotnet/Qpid.Common/Protocol/AMQConstant.cs @@ -0,0 +1,97 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+using System;
+using System.Collections;
+
+namespace Qpid.Protocol
+{
+ public sealed class AMQConstant
+ {
+ private int _code;
+ private string _name;
+ private static Hashtable _codeMap = new Hashtable();
+
+ public int Code
+ {
+ get { return _code; }
+ }
+
+ public string Name
+ {
+ get { return _name; }
+ }
+
+ private AMQConstant(int code, string name, bool map)
+ {
+ _code = code;
+ _name = name;
+
+ if ( map )
+ {
+ _codeMap.Add(code, this);
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0}: {1}", Code, Name);
+ }
+
+ public static AMQConstant GetConstant(int code)
+ {
+ AMQConstant c = (AMQConstant)_codeMap[code];
+ if ( c == null )
+ {
+ c = new AMQConstant(code, "unknown code", false);
+ }
+ return c;
+ }
+
+ #region Constants
+ //
+ // Constants
+ //
+ public static readonly AMQConstant FRAME_MIN_SIZE = new AMQConstant(4096, "frame min size", true);
+ public static readonly AMQConstant FRAME_END = new AMQConstant(206, "frame end", true);
+ public static readonly AMQConstant REPLY_SUCCESS = new AMQConstant(200, "reply success", true);
+ public static readonly AMQConstant NOT_DELIVERED = new AMQConstant(310, "not delivered", true);
+ public static readonly AMQConstant MESSAGE_TOO_LARGE = new AMQConstant(311, "message too large", true);
+ public static readonly AMQConstant NO_ROUTE = new AMQConstant(312, "no route", true);
+ public static readonly AMQConstant NO_CONSUMERS = new AMQConstant(313, "no consumers", true);
+ public static readonly AMQConstant CONTEXT_IN_USE = new AMQConstant(320, "context in use", true);
+ public static readonly AMQConstant CONTEXT_UNKNOWN = new AMQConstant(321, "context unknown", true);
+ public static readonly AMQConstant INVALID_SELECTOR = new AMQConstant(322, "selector invalid", true);
+ public static readonly AMQConstant INVALID_PATH = new AMQConstant(402, "invalid path", true);
+ public static readonly AMQConstant ACCESS_REFUSED = new AMQConstant(403, "access refused", true);
+ public static readonly AMQConstant NOT_FOUND = new AMQConstant(404, "not found", true);
+ public static readonly AMQConstant FRAME_ERROR = new AMQConstant(501, "frame error", true);
+ public static readonly AMQConstant SYNTAX_ERROR = new AMQConstant(502, "syntax error", true);
+ public static readonly AMQConstant COMMAND_INVALID = new AMQConstant(503, "command invalid", true);
+ public static readonly AMQConstant CHANNEL_ERROR = new AMQConstant(504, "channel error", true);
+ public static readonly AMQConstant RESOURCE_ERROR = new AMQConstant(506, "resource error", true);
+ public static readonly AMQConstant NOT_ALLOWED = new AMQConstant(530, "not allowed", true);
+ public static readonly AMQConstant NOT_IMPLEMENTED = new AMQConstant(540, "not implemented", true);
+ public static readonly AMQConstant INTERNAL_ERROR = new AMQConstant(541, "internal error", true);
+
+ #endregion // Constants
+
+ }
+}
diff --git a/dotnet/Qpid.Common/Qpid.Common.csproj b/dotnet/Qpid.Common/Qpid.Common.csproj index 0e32e3ba17..e0b5d22efb 100644 --- a/dotnet/Qpid.Common/Qpid.Common.csproj +++ b/dotnet/Qpid.Common/Qpid.Common.csproj @@ -168,6 +168,7 @@ <Compile Include="Collections\LinkedBlockingQueue.cs" />
<Compile Include="Collections\SynchronousQueue.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Protocol\AMQConstant.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Qpid.Buffer\Qpid.Buffer.csproj">
@@ -202,4 +203,4 @@ <Target Name="AfterBuild">
</Target>
-->
-</Project>
\ No newline at end of file +</Project>
diff --git a/dotnet/Qpid.NET.sln b/dotnet/Qpid.NET.sln index c4b518e51c..33abed66bd 100644 --- a/dotnet/Qpid.NET.sln +++ b/dotnet/Qpid.NET.sln @@ -14,6 +14,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qpid.Common", "Qpid.Common\ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qpid.Client.Tests", "Qpid.Client.Tests\Qpid.Client.Tests.csproj", "{BA1B0032-4CE6-40DD-A2DC-119F0FFA0A1D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qpid.Sasl", "Qpid.Sasl\Qpid.Sasl.csproj", "{1465B0EE-6452-42A6-AB73-B2F9EABEEE75}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qpid.Sasl.Tests", "Qpid.Sasl.Tests\Qpid.Sasl.Tests.csproj", "{587B3520-EBB9-41ED-B019-E96116B651CE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -48,6 +52,14 @@ Global {BA1B0032-4CE6-40DD-A2DC-119F0FFA0A1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA1B0032-4CE6-40DD-A2DC-119F0FFA0A1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA1B0032-4CE6-40DD-A2DC-119F0FFA0A1D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1465B0EE-6452-42A6-AB73-B2F9EABEEE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1465B0EE-6452-42A6-AB73-B2F9EABEEE75}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1465B0EE-6452-42A6-AB73-B2F9EABEEE75}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1465B0EE-6452-42A6-AB73-B2F9EABEEE75}.Release|Any CPU.Build.0 = Release|Any CPU
+ {587B3520-EBB9-41ED-B019-E96116B651CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {587B3520-EBB9-41ED-B019-E96116B651CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {587B3520-EBB9-41ED-B019-E96116B651CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {587B3520-EBB9-41ED-B019-E96116B651CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dotnet/Qpid.Sasl.Tests/App.config b/dotnet/Qpid.Sasl.Tests/App.config new file mode 100644 index 0000000000..0e7e903e02 --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/App.config @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <configSections>
+ <section name="qpid.sasl" type="Qpid.Sasl.Configuration.SaslConfigurationSectionHandler, Qpid.Sasl"/>
+ </configSections>
+
+ <qpid.sasl>
+ <clientFactories>
+ <add type="Qpid.Sasl.Tests.TestClientFactory, Qpid.Sasl.Tests"/>
+ </clientFactories>
+ </qpid.sasl>
+</configuration>
diff --git a/dotnet/Qpid.Sasl.Tests/Mechanisms/AnonymousSaslClientTests.cs b/dotnet/Qpid.Sasl.Tests/Mechanisms/AnonymousSaslClientTests.cs new file mode 100644 index 0000000000..6c906d7f1b --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/Mechanisms/AnonymousSaslClientTests.cs @@ -0,0 +1,72 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+using NUnit.Framework;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl.Tests.Mechanisms
+{
+ [TestFixture]
+ public class AnonymousSaslClientTests : ISaslCallbackHandler
+ {
+ private const string AUTHID = "nobody@nowhere.com";
+
+ [Test]
+ public void ReturnsRightMechanismName()
+ {
+ ISaslClient client = new AnonymousSaslClient(AUTHID, new Hashtable(), this);
+
+ Assert.AreEqual("ANONYMOUS", client.MechanismName);
+ }
+
+ [Test]
+ public void HasInitialResponseReturnsTrue()
+ {
+ ISaslClient client = new AnonymousSaslClient(AUTHID, new Hashtable(), this);
+
+ Assert.IsTrue(client.HasInitialResponse);
+ }
+
+ [Test]
+ public void CanEvaluateChallenge()
+ {
+ Hashtable props = new Hashtable();
+ ISaslClient client = new AnonymousSaslClient(AUTHID, props, this);
+
+ Assert.IsFalse(client.IsComplete);
+ byte[] response = client.EvaluateChallenge(new byte[0]);
+ Assert.AreEqual(AUTHID, Encoding.UTF8.GetString(response));
+
+ Assert.IsTrue(client.IsComplete);
+ }
+
+ void ISaslCallbackHandler.Handle(ISaslCallback[] callbacks)
+ {
+ }
+
+ } // class AnonymousSaslClientTests
+
+} // namespace Qpid.Sasl.Tests.Mechanisms
diff --git a/dotnet/Qpid.Sasl.Tests/Mechanisms/CramMD5SaslClientTests.cs b/dotnet/Qpid.Sasl.Tests/Mechanisms/CramMD5SaslClientTests.cs new file mode 100644 index 0000000000..b5c9935d10 --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/Mechanisms/CramMD5SaslClientTests.cs @@ -0,0 +1,90 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+using NUnit.Framework;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl.Tests.Mechanisms
+{
+ [TestFixture]
+ public class CramMD5SaslClientTests : ISaslCallbackHandler
+ {
+ private const string USERNAME = "testuser";
+ private const string PASSWORD = "tanstaaftanstaaf";
+ private const string AUTHID = "test";
+
+ [Test]
+ public void ReturnsRightMechanismName()
+ {
+ ISaslClient client = new CramMD5SaslClient(AUTHID, new Hashtable(), this);
+
+ Assert.AreEqual("CRAM-MD5", client.MechanismName);
+ }
+
+ [Test]
+ public void HasInitialResponseReturnsFalse()
+ {
+ ISaslClient client = new CramMD5SaslClient(AUTHID, new Hashtable(), this);
+
+ Assert.IsFalse(client.HasInitialResponse);
+ }
+
+ [Test]
+ public void CanEvaluateChallenge()
+ {
+ Hashtable props = new Hashtable();
+
+ ISaslClient client = new CramMD5SaslClient(AUTHID, props, this);
+
+ Assert.IsFalse(client.IsComplete);
+
+ byte[] challenge =
+ Encoding.UTF8.GetBytes("<1896.697170952@postoffice.reston.mci.net>");
+ byte[] response = client.EvaluateChallenge(challenge);
+ string[] parts = Encoding.UTF8.GetString(response).Split(' ');
+
+ Assert.AreEqual(2, parts.Length);
+ Assert.AreEqual(USERNAME, parts[0]);
+ Assert.AreEqual("b913a602c7eda7a495b4e6e7334d3890", parts[1]);
+ Assert.IsTrue(client.IsComplete);
+ }
+
+ void ISaslCallbackHandler.Handle(ISaslCallback[] callbacks)
+ {
+ foreach ( ISaslCallback cb in callbacks )
+ {
+ if ( cb is NameCallback )
+ {
+ ((NameCallback)cb).Text = USERNAME;
+ } else if ( cb is PasswordCallback )
+ {
+ ((PasswordCallback)cb).Text = PASSWORD;
+ }
+ }
+ }
+ } // class CramMD5SaslClientTests
+
+} // namespace Qpid.Sasl.Tests.Mechanisms
diff --git a/dotnet/Qpid.Sasl.Tests/Mechanisms/DigestSaslClientTests.cs b/dotnet/Qpid.Sasl.Tests/Mechanisms/DigestSaslClientTests.cs new file mode 100644 index 0000000000..4ed49d3806 --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/Mechanisms/DigestSaslClientTests.cs @@ -0,0 +1,249 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Text;
+
+using NUnit.Framework;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl.Tests.Mechanisms
+{
+ [TestFixture]
+ public class DigestSaslClientTests : ISaslCallbackHandler
+ {
+ private const string USERNAME = "chris";
+ private const string PASSWORD = "secret";
+ private const string AUTHID = null;
+ private const string PROTOCOL = "IMAP";
+ private const string SERVERNAME = "elwood.innosoft.com";
+
+ #region Digest Challenge Parsing Tests
+ //
+ // Digest Challenge Parsing Tests
+ //
+
+ [Test]
+ public void CanParseSimpleString()
+ {
+ string challenge = "realm=\"elwood.innosoft.com\", algorithm=md5-sess";
+ StringDictionary values = DigestChallenge.ParseParameters(challenge);
+ Assert.AreEqual(2, values.Count);
+ Assert.AreEqual("elwood.innosoft.com", values["realm"]);
+ Assert.AreEqual("md5-sess", values["algorithm"]);
+ }
+
+ [Test]
+ public void CanParseEscapedQuotes()
+ {
+ string challenge = "realm=\"elwood\\\".innosoft.com\", algorithm=md5-sess";
+ StringDictionary values = DigestChallenge.ParseParameters(challenge);
+ Assert.AreEqual(2, values.Count);
+ Assert.AreEqual("elwood\\\".innosoft.com", values["realm"]);
+ Assert.AreEqual("md5-sess", values["algorithm"]);
+ }
+
+ [Test]
+ public void CanParseEmbeddedDelimiter()
+ {
+ string challenge = "realm=\"elwood,innosoft.com\", algorithm=md5-sess";
+ StringDictionary values = DigestChallenge.ParseParameters(challenge);
+ Assert.AreEqual(2, values.Count);
+ Assert.AreEqual("elwood,innosoft.com", values["realm"]);
+ Assert.AreEqual("md5-sess", values["algorithm"]);
+ }
+
+ [Test]
+ public void CanParse1()
+ {
+ string challenge = "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8";
+ DigestChallenge parsed = DigestChallenge.Parse(challenge);
+
+ Assert.AreEqual("elwood.innosoft.com", parsed.Realm);
+ Assert.AreEqual("OA6MG9tEQGm2hh", parsed.Nonce);
+ Assert.Contains("auth", parsed.QopOptions);
+ Assert.AreEqual("md5-sess", parsed.Algorithm);
+ Assert.AreEqual("utf-8", parsed.Charset);
+ }
+
+ #endregion // Digest Challenge Parsing Tests
+
+
+ #region Digest Response Tests
+ //
+ // Digest Response Tests
+ //
+
+ [Test]
+ public void CanWriteResponse()
+ {
+ DigestResponse resp = new DigestResponse();
+ resp.Username = "user";
+ resp.Realm = "nowhere.com";
+ resp.Nonce = "OA9BSXrbuRhWay";
+ resp.Cnonce = "OA9BSuZWMSpW8m";
+ resp.NonceCount = 16;
+ resp.DigestUri = "acap/elwood.innosoft.com";
+ resp.Response = "6084c6db3fede7352c551284490fd0fc";
+ resp.Qop = "auth";
+ resp.MaxBuffer = 65536;
+ resp.Cipher = "3des";
+ resp.Authzid = "user2";
+ resp.AuthParam = "ap";
+ resp.Charset = "utf-8";
+
+ string expected = "username=\"user\",realm=\"nowhere.com\",nonce=\"OA9BSXrbuRhWay\",cnonce=\"OA9BSuZWMSpW8m\",nc=00000010,qop=auth,digest-uri=\"acap/elwood.innosoft.com\",response=\"6084c6db3fede7352c551284490fd0fc\",maxbuf=65536,charset=utf-8,cipher=3des,authzid=\"user2\",auth-param=\"ap\"";
+ Assert.AreEqual(expected, resp.ToString());
+ }
+
+ [Test]
+ public void CanWriteEscapedSecuence()
+ {
+ DigestResponse resp = new DigestResponse();
+ resp.Username = "us\"er";
+
+ string expected = "username=\"us\\\"er\",nc=00000000,maxbuf=0";
+ Assert.AreEqual(expected, resp.ToString());
+ }
+
+ #endregion // Digest Response Tests
+
+
+ #region Authentication Tests
+ //
+ // Authentication Tests
+ //
+
+ [Test]
+ public void ReturnsRightMechanismName()
+ {
+ ISaslClient client = CreateClient();
+
+ Assert.AreEqual("DIGEST-MD5", client.MechanismName);
+ }
+
+ [Test]
+ public void HasInitialResponseReturnsFalse()
+ {
+ ISaslClient client = CreateClient();
+
+ Assert.IsFalse(client.HasInitialResponse);
+ }
+
+ [Test]
+ public void CanAuthenticate()
+ {
+ string challenge = "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8";
+ DigestSaslClient client = CreateClient();
+ client.Cnonce = "OA6MHXh6VqTrRk";
+
+ byte[] bresp = client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
+ string response = Encoding.UTF8.GetString(bresp);
+ string expectedResp = "username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"" +
+ client.Cnonce + "\",nc=00000001,qop=auth,digest-uri=\"imap/elwood.innosoft.com\",response=\"d388dad90d4bbd760a152321f2143af7\",maxbuf=65536,charset=utf-8";
+
+ Assert.AreEqual(expectedResp, response);
+ Assert.IsFalse(client.IsComplete);
+
+ string challenge2 = "rspauth=ea40f60335c427b5527b84dbabcdfffd";
+ bresp = client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge2));
+ // client responds with zero-length array
+ Assert.AreEqual(0, bresp.Length);
+ Assert.IsTrue(client.IsComplete);
+ }
+
+ [Test]
+ [ExpectedException(typeof(ArgumentNullException))]
+ public void ThrowsExceptionWhenChallengeIsMissing()
+ {
+ DigestSaslClient client = CreateClient();
+ client.EvaluateChallenge(null);
+ }
+
+
+ [Test]
+ [ExpectedException(typeof(SaslException))]
+ public void ThrowsExceptionWhenNonceMissing()
+ {
+ string challenge = "realm=\"elwood.innosoft.com\"";
+ DigestSaslClient client = CreateClient();
+
+ client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
+ }
+
+ [Test]
+ [ExpectedException(typeof(SaslException))]
+ public void ThrowsExceptionWhenAlgorithmMissing()
+ {
+ string challenge = "realm=\"elwood.innosoft.com\",nonce=\"asdasadsad\"";
+ DigestSaslClient client = CreateClient();
+
+ client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
+ }
+
+ [Test]
+ [ExpectedException(typeof(SaslException))]
+ public void ThrowsExceptionWhenSecondChallengeInvalid()
+ {
+ string challenge = "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8";
+ DigestSaslClient client = CreateClient();
+
+ byte[] bresp = client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
+ string response = Encoding.UTF8.GetString(bresp);
+
+ // repeat challenge 1, which is incorrect
+ client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
+ }
+
+ private DigestSaslClient CreateClient()
+ {
+ return new DigestSaslClient(
+ AUTHID, SERVERNAME, PROTOCOL,
+ new Hashtable(), this
+ );
+ }
+
+ void ISaslCallbackHandler.Handle(ISaslCallback[] callbacks)
+ {
+ foreach ( ISaslCallback cb in callbacks )
+ {
+ if ( cb is NameCallback )
+ {
+ ((NameCallback)cb).Text = USERNAME;
+ } else if ( cb is PasswordCallback )
+ {
+ ((PasswordCallback)cb).Text = PASSWORD;
+ } else if ( cb is RealmCallback )
+ {
+ ((RealmCallback)cb).Text = SERVERNAME;
+ }
+ }
+ }
+
+ #endregion // Authentication Tests
+
+
+ } // class DigestSaslClientTests
+
+} // namespace Qpid.Sasl.Tests.Mechanisms
diff --git a/dotnet/Qpid.Sasl.Tests/Mechanisms/PlainSaslClientTests.cs b/dotnet/Qpid.Sasl.Tests/Mechanisms/PlainSaslClientTests.cs new file mode 100644 index 0000000000..3f39c1980d --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/Mechanisms/PlainSaslClientTests.cs @@ -0,0 +1,88 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+using NUnit.Framework;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl.Tests.Mechanisms
+{
+ [TestFixture]
+ public class PlainSaslClientTests : ISaslCallbackHandler
+ {
+ private const string USERNAME = "testuser";
+ private const string PASSWORD = "thepasswd";
+ private const string AUTHID = "theauth";
+
+ [Test]
+ public void ReturnsRightMechanismName()
+ {
+ ISaslClient client = new PlainSaslClient(AUTHID, new Hashtable(), this);
+
+ Assert.AreEqual("PLAIN", client.MechanismName);
+ }
+
+ [Test]
+ public void HasInitialResponseReturnsTrue()
+ {
+ ISaslClient client = new PlainSaslClient(AUTHID, new Hashtable(), this);
+
+ Assert.IsTrue(client.HasInitialResponse);
+ }
+
+ [Test]
+ public void CanEvaluateChallenge()
+ {
+ Hashtable props = new Hashtable();
+ ISaslClient client = new PlainSaslClient(AUTHID, props, this);
+
+ Assert.IsFalse(client.IsComplete);
+ byte[] response = client.EvaluateChallenge(new byte[0]);
+ string[] parts = Encoding.UTF8.GetString(response).Split('\0');
+
+ Assert.AreEqual(3, parts.Length);
+ Assert.AreEqual(AUTHID, parts[0]);
+ Assert.AreEqual(USERNAME, parts[1]);
+ Assert.AreEqual(PASSWORD, parts[2]);
+ Assert.IsTrue(client.IsComplete);
+ }
+
+ void ISaslCallbackHandler.Handle(ISaslCallback[] callbacks)
+ {
+ foreach ( ISaslCallback cb in callbacks )
+ {
+ if ( cb is NameCallback )
+ {
+ ((NameCallback)cb).Text = USERNAME;
+ } else if ( cb is PasswordCallback )
+ {
+ ((PasswordCallback)cb).Text = PASSWORD;
+ }
+ }
+ }
+
+ } // class PlainSaslClientTests
+
+} // namespace Qpid.Sasl.Tests.Mechanisms
diff --git a/dotnet/Qpid.Sasl.Tests/Properties/AssemblyInfo.cs b/dotnet/Qpid.Sasl.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f6728b1d1e --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Qpid.Sasl.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Qpid.Sasl.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2007")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("84cc3267-8019-4fad-a426-0a40155b3352")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/dotnet/Qpid.Sasl.Tests/Qpid.Sasl.Tests.csproj b/dotnet/Qpid.Sasl.Tests/Qpid.Sasl.Tests.csproj new file mode 100644 index 0000000000..50c2cfba6f --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/Qpid.Sasl.Tests.csproj @@ -0,0 +1,66 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.50727</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{587B3520-EBB9-41ED-B019-E96116B651CE}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Qpid.Sasl.Tests</RootNamespace>
+ <AssemblyName>Qpid.Sasl.Tests</AssemblyName>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <UseVSHostingProcess>true</UseVSHostingProcess>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="nunit.framework, Version=2.2.8.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\Qpid.Client.Tests\lib\nunit\nunit.framework.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="TestClientFactory.cs" />
+ <Compile Include="Mechanisms\AnonymousSaslClientTests.cs" />
+ <Compile Include="Mechanisms\DigestSaslClientTests.cs" />
+ <Compile Include="Mechanisms\CramMD5SaslClientTests.cs" />
+ <Compile Include="Mechanisms\PlainSaslClientTests.cs" />
+ <Compile Include="SaslTests.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Qpid.Sasl\Qpid.Sasl.csproj">
+ <Project>{1465B0EE-6452-42A6-AB73-B2F9EABEEE75}</Project>
+ <Name>Qpid.Sasl</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
diff --git a/dotnet/Qpid.Sasl.Tests/SaslTests.cs b/dotnet/Qpid.Sasl.Tests/SaslTests.cs new file mode 100644 index 0000000000..01bb676fb6 --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/SaslTests.cs @@ -0,0 +1,111 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+using NUnit.Framework;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl.Tests
+{
+ [TestFixture]
+ public class SaslTests : ISaslCallbackHandler
+ {
+
+ [Test]
+ public void CanCreatePlain()
+ {
+ Hashtable props = new Hashtable();
+ string[] mechanisms = new string[] { "PLAIN", "OTHER" };
+ ISaslClient client = Sasl.CreateClient(mechanisms, "", "", "", props, this);
+
+ Assert.IsNotNull(client);
+ Assert.IsInstanceOfType(typeof(PlainSaslClient), client);
+ }
+
+ [Test]
+ public void CanCreateCramMD5()
+ {
+ Hashtable props = new Hashtable();
+ string[] mechanisms = new string[] { "CRAM-MD5", "OTHER" };
+ ISaslClient client = Sasl.CreateClient(mechanisms, "", "", "", props, this);
+
+ Assert.IsNotNull(client);
+ Assert.IsInstanceOfType(typeof(CramMD5SaslClient), client);
+ }
+
+ [Test]
+ public void CanCreateAnonymous()
+ {
+ Hashtable props = new Hashtable();
+ string[] mechanisms = new string[] { "ANONYMOUS", "OTHER" };
+ ISaslClient client = Sasl.CreateClient(mechanisms, "", "", "", props, this);
+
+ Assert.IsNotNull(client);
+ Assert.IsInstanceOfType(typeof(AnonymousSaslClient), client);
+ }
+
+ [Test]
+ public void CanCreateDigest()
+ {
+ Hashtable props = new Hashtable();
+ string[] mechanisms = new string[] { "DIGEST-MD5", "OTHER" };
+ ISaslClient client = Sasl.CreateClient(mechanisms, "", "", "", props, this);
+
+ Assert.IsNotNull(client);
+ Assert.IsInstanceOfType(typeof(DigestSaslClient), client);
+ }
+
+ [Test]
+ public void ReturnsNullIfNoFactoryFound()
+ {
+ Hashtable props = new Hashtable();
+ props.Add(SaslProperties.PolicyNoPlainText, true);
+ string[] mechanisms = new string[] { "PLAIN", "OTHER" };
+ ISaslClient client = Sasl.CreateClient(mechanisms, "", "", "", props, this);
+
+ Assert.IsNull(client);
+ }
+
+ [Test]
+ public void ParsesConfigurationSection()
+ {
+ // if the TEST mechanism is available, then we know
+ // the configuration section worked!
+ Hashtable props = new Hashtable();
+ string[] mechanisms = new string[] { "TEST" };
+ ISaslClient client = Sasl.CreateClient(mechanisms, "", "", "", props, this);
+
+ Assert.IsNotNull(client);
+ Assert.IsInstanceOfType(typeof(TestSaslClient), client);
+ }
+
+
+ void ISaslCallbackHandler.Handle(ISaslCallback[] callbacks)
+ {
+ }
+
+ } // class SaslTests
+
+} // namespace Qpid.Sasl.Tests
diff --git a/dotnet/Qpid.Sasl.Tests/TestClientFactory.cs b/dotnet/Qpid.Sasl.Tests/TestClientFactory.cs new file mode 100644 index 0000000000..f7b68adfcb --- /dev/null +++ b/dotnet/Qpid.Sasl.Tests/TestClientFactory.cs @@ -0,0 +1,75 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+using NUnit.Framework;
+using Qpid.Sasl;
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl.Tests
+{
+ public class TestClientFactory : ISaslClientFactory
+ {
+ public string[] GetSupportedMechanisms(IDictionary props)
+ {
+ return new string[] { TestSaslClient.Mechanism };
+ }
+
+ public ISaslClient CreateClient(string[] mechanisms, string authorizationId, string protocol, string serverName, IDictionary props, ISaslCallbackHandler handler)
+ {
+ foreach ( string mech in mechanisms )
+ {
+ if ( mech == TestSaslClient.Mechanism )
+ return new TestSaslClient(props, handler);
+ }
+ return null;
+ }
+
+ } // class TestClientFactory
+
+ internal class TestSaslClient : SaslClient
+ {
+ public const string Mechanism = "TEST";
+
+ public override string MechanismName
+ {
+ get { return Mechanism; }
+ }
+ public override bool HasInitialResponse
+ {
+ get { return false; }
+ }
+
+ public TestSaslClient(IDictionary props, ISaslCallbackHandler handler)
+ : base("", "", "", props, handler)
+ {
+ }
+
+ public override byte[] EvaluateChallenge(byte[] challenge)
+ {
+ throw new NotImplementedException();
+ }
+ } // class TestSaslClient
+
+} // namespace Qpid.Sasl.Tests
diff --git a/dotnet/Qpid.Sasl/Callbacks.cs b/dotnet/Qpid.Sasl/Callbacks.cs new file mode 100644 index 0000000000..13ed6f784e --- /dev/null +++ b/dotnet/Qpid.Sasl/Callbacks.cs @@ -0,0 +1,105 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ /// <summary>
+ /// Marker interface for Sasl Callbacks
+ /// </summary>
+ public interface ISaslCallback
+ {
+ } // interface ISaslCallback
+
+ public abstract class TextSaslCallback : ISaslCallback
+ {
+ private string _prompt;
+ private string _text;
+ private string _defaultText;
+
+ public string Prompt
+ {
+ get { return _prompt; }
+ set { _prompt = value; }
+ }
+
+ public string Text
+ {
+ get {
+ if ( _text == null || _text.Length == 0 )
+ return DefaultText;
+ else
+ return _text;
+ }
+ set { _text = value; }
+ }
+
+ public string DefaultText
+ {
+ get { return _defaultText; }
+ set { _defaultText = value; }
+ }
+
+ protected TextSaslCallback(string prompt, string text, string defaultText)
+ {
+ _prompt = prompt;
+ _text = text;
+ _defaultText = defaultText;
+ }
+
+ } // class TextSaslCallback
+
+ public class NameCallback : TextSaslCallback
+ {
+ public NameCallback()
+ : this(Environment.UserName)
+ {
+ }
+ public NameCallback(string defaultText)
+ : base("username:", "", defaultText)
+ {
+ }
+ } // class NameCallback
+
+ public class PasswordCallback : TextSaslCallback
+ {
+ public PasswordCallback()
+ : base("password:", "", "")
+ {
+ }
+ } // class PasswordCallback
+
+ public class RealmCallback : TextSaslCallback
+ {
+ public RealmCallback()
+ : this("localhost")
+ {
+ }
+ public RealmCallback(string defaultText)
+ : base("realm:", "", defaultText)
+ {
+ }
+ } // class RealmCallback
+
+} // namespace Qpid.Sasl
+
diff --git a/dotnet/Qpid.Sasl/Configuration/SaslConfiguration.cs b/dotnet/Qpid.Sasl/Configuration/SaslConfiguration.cs new file mode 100644 index 0000000000..d5f428a52e --- /dev/null +++ b/dotnet/Qpid.Sasl/Configuration/SaslConfiguration.cs @@ -0,0 +1,89 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Configuration;
+using System.Text;
+using System.Xml;
+
+namespace Qpid.Sasl.Configuration
+{
+ /// <summary>
+ /// Represents an Sasl configuration section
+ /// in the config file
+ /// </summary>
+ internal class SaslConfiguration
+ {
+ private IList _clientFactories;
+
+ /// <summary>
+ /// Set of configured client factores
+ /// </summary>
+ public IList ClientFactories
+ {
+ get { return _clientFactories; }
+ }
+
+ internal SaslConfiguration(IList clientFactoryTypes)
+ {
+ _clientFactories = new ArrayList();
+ foreach ( Type type in clientFactoryTypes )
+ {
+ _clientFactories.Add(Activator.CreateInstance(type));
+ }
+ }
+
+ /// <summary>
+ /// Get the configuration for the library
+ /// </summary>
+ /// <returns>The configuration from app.config or a default configuration</returns>
+ internal static SaslConfiguration GetConfiguration()
+ {
+ // 'obsolete' warning, but needed for .NET 1.1 compatibility
+ SaslConfiguration config = (SaslConfiguration)
+ ConfigurationSettings.GetConfig("qpid.sasl");
+ if ( config == null )
+ {
+ // create default configuration
+ IList clientFactories = GetDefaultClientFactories();
+ config = new SaslConfiguration(clientFactories);
+ }
+ return config;
+ }
+
+ /// <summary>
+ /// Create a list filled with the default client
+ /// factories supported by the library
+ /// </summary>
+ /// <returns>The list of client factory types</returns>
+ internal static IList GetDefaultClientFactories()
+ {
+ IList clientFactories = new ArrayList();
+ clientFactories.Add(typeof(DefaultClientFactory));
+ return clientFactories;
+ }
+
+
+ } // class SaslConfiguration
+
+} // namespace Qpid.Sasl.Configuration
+
diff --git a/dotnet/Qpid.Sasl/Configuration/SaslConfigurationSectionHandler.cs b/dotnet/Qpid.Sasl/Configuration/SaslConfigurationSectionHandler.cs new file mode 100644 index 0000000000..c307c73eb1 --- /dev/null +++ b/dotnet/Qpid.Sasl/Configuration/SaslConfigurationSectionHandler.cs @@ -0,0 +1,83 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Configuration;
+using System.Text;
+using System.Xml;
+
+namespace Qpid.Sasl.Configuration
+{
+ /// <summary>
+ /// Defines the configuration section to configure extra
+ /// Sasl client factories
+ /// </summary>
+ public class SaslConfigurationSectionHandler
+ : IConfigurationSectionHandler
+ {
+ public object Create(object parent, object configContext, XmlNode section)
+ {
+ IList clientFactories = SaslConfiguration.GetDefaultClientFactories();
+
+ foreach ( XmlNode node in section.ChildNodes )
+ {
+ if ( node.LocalName == "clientFactories" )
+ {
+ ProcessFactories(node, clientFactories);
+ }
+ }
+
+ SaslConfiguration config = new SaslConfiguration(clientFactories);
+ return config;
+ }
+
+
+ private void ProcessFactories(XmlNode node, IList factories)
+ {
+ foreach ( XmlNode child in node.ChildNodes )
+ {
+ Type type;
+ switch ( child.LocalName )
+ {
+ case "add":
+ type = Type.GetType(child.Attributes["type"].Value);
+ if ( !factories.Contains(type) )
+ factories.Add(type);
+ break;
+ case "remove":
+ type = Type.GetType(child.Attributes["type"].Value);
+ if ( factories.Contains(type) )
+ factories.Remove(type);
+ break;
+ case "clear":
+ factories.Clear();
+ break;
+ default:
+ // gives obsolete warning but needed for .NET 1.1 support
+ throw new ConfigurationException(string.Format("Unknown element '{0}' in section '{0}'", child.LocalName, node.LocalName));
+ }
+ }
+ }
+ } // class SaslConfigurationSectionHandler
+
+} // namespace Qpid.Sasl.Configuration
+
diff --git a/dotnet/Qpid.Sasl/DefaultClientFactory.cs b/dotnet/Qpid.Sasl/DefaultClientFactory.cs new file mode 100644 index 0000000000..43f0470a21 --- /dev/null +++ b/dotnet/Qpid.Sasl/DefaultClientFactory.cs @@ -0,0 +1,96 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+using Qpid.Sasl.Mechanisms;
+
+namespace Qpid.Sasl
+{
+ public class DefaultClientFactory : ISaslClientFactory
+ {
+ private static readonly string[] SUPPORTED = new string[] {
+ DigestSaslClient.Mechanism,
+ CramMD5SaslClient.Mechanism,
+ PlainSaslClient.Mechanism,
+ AnonymousSaslClient.Mechanism,
+ };
+
+ public string[] GetSupportedMechanisms(IDictionary props)
+ {
+ if ( props == null )
+ throw new ArgumentNullException("props");
+
+ ArrayList vetoed = new ArrayList();
+
+ if ( props.Contains(SaslProperties.PolicyNoPlainText) ||
+ props.Contains(SaslProperties.PolicyNoDictionary) ||
+ props.Contains(SaslProperties.PolicyNoActive) ||
+ props.Contains(SaslProperties.PolicyForwardSecrecy) ||
+ props.Contains(SaslProperties.PolicyPassCredentials) )
+ {
+ vetoed.Add(CramMD5SaslClient.Mechanism);
+ vetoed.Add(PlainSaslClient.Mechanism);
+ vetoed.Add(AnonymousSaslClient.Mechanism);
+ }
+ if ( props.Contains(SaslProperties.PolicyNoAnonymous) )
+ {
+ vetoed.Add(AnonymousSaslClient.Mechanism);
+ }
+
+ ArrayList available = new ArrayList();
+ foreach ( string mech in SUPPORTED )
+ {
+ if ( !vetoed.Contains(mech) )
+ available.Add(mech);
+ }
+ return (string[])available.ToArray(typeof(string));
+ }
+
+ public ISaslClient CreateClient(
+ string[] mechanisms, string authorizationId,
+ string protocol, string serverName,
+ IDictionary props, ISaslCallbackHandler handler
+ )
+ {
+ foreach ( string mech in mechanisms )
+ {
+ switch ( mech )
+ {
+ case PlainSaslClient.Mechanism:
+ return new PlainSaslClient(authorizationId, props, handler);
+ case CramMD5SaslClient.Mechanism:
+ return new CramMD5SaslClient(authorizationId, props, handler);
+ case AnonymousSaslClient.Mechanism:
+ return new AnonymousSaslClient(authorizationId, props, handler);
+ case DigestSaslClient.Mechanism:
+ return new DigestSaslClient(authorizationId, serverName, protocol, props, handler);
+ }
+ }
+ // unknown mechanism
+ return null;
+ }
+ } // class DefaultClientFactory
+
+} // namespace Qpid.Sasl
+
diff --git a/dotnet/Qpid.Sasl/ISaslCallbackHandler.cs b/dotnet/Qpid.Sasl/ISaslCallbackHandler.cs new file mode 100644 index 0000000000..ec34755f3e --- /dev/null +++ b/dotnet/Qpid.Sasl/ISaslCallbackHandler.cs @@ -0,0 +1,34 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ public interface ISaslCallbackHandler
+ {
+ void Handle(ISaslCallback[] callbacks);
+
+ } // interface ISaslCallbackHandler
+
+} // namespace Qpid.Sasl
+
diff --git a/dotnet/Qpid.Sasl/ISaslClient.cs b/dotnet/Qpid.Sasl/ISaslClient.cs new file mode 100644 index 0000000000..3ecccc0fac --- /dev/null +++ b/dotnet/Qpid.Sasl/ISaslClient.cs @@ -0,0 +1,41 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ public interface ISaslClient
+ {
+ string MechanismName { get; }
+ bool HasInitialResponse { get; }
+ bool IsComplete { get; }
+
+ byte[] EvaluateChallenge(byte[] challenge);
+ object GetNegotiatedProperty(string propName);
+ byte[] Unwrap(byte[] buffer, int offset, int length);
+ byte[] Wrap(byte[] buffer, int offset, int lenght);
+
+ } // interface ISaslClient
+
+} // namespace Qpid.Sasl
+
diff --git a/dotnet/Qpid.Sasl/ISaslClientFactory.cs b/dotnet/Qpid.Sasl/ISaslClientFactory.cs new file mode 100644 index 0000000000..4a5d4d0da4 --- /dev/null +++ b/dotnet/Qpid.Sasl/ISaslClientFactory.cs @@ -0,0 +1,39 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ public interface ISaslClientFactory
+ {
+ string[] GetSupportedMechanisms(IDictionary props);
+ ISaslClient CreateClient(
+ string[] mechanisms, string authorizationId,
+ string protocol, string serverName,
+ IDictionary props, ISaslCallbackHandler handler
+ );
+ } // interface ISaslClientFactory
+
+} // namespace Qpid.Sasl
+
diff --git a/dotnet/Qpid.Sasl/MD5HMAC.cs b/dotnet/Qpid.Sasl/MD5HMAC.cs new file mode 100644 index 0000000000..0696487d10 --- /dev/null +++ b/dotnet/Qpid.Sasl/MD5HMAC.cs @@ -0,0 +1,115 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Security.Cryptography;
+
+namespace Qpid.Sasl
+{
+ /// <summary>
+ /// Rough HMAC MD5 implementation as presented in
+ /// RFC 2104. Used because the HMACMD5 class in the
+ /// .NET framework is not available in v1.1.
+ /// </summary>
+ public sealed class MD5HMAC : IDisposable
+ {
+ private const int BLOCK_LEN = 64;
+ private MD5 _hash;
+ private byte[] _key;
+ private byte[] _ipad;
+ private byte[] _opad;
+
+ public MD5HMAC(byte[] key)
+ {
+ if ( key == null || key.Length == 0 )
+ throw new ArgumentNullException("key");
+
+ _hash = new MD5CryptoServiceProvider();
+
+ byte[] theKey = key;
+ if ( theKey.Length > BLOCK_LEN )
+ {
+ theKey = _hash.ComputeHash(theKey);
+ }
+ // pad key with 0's up to BLOCK_LEN
+ _key = new byte[BLOCK_LEN];
+ Array.Copy(theKey, _key, theKey.Length);
+
+ CreatePads();
+ }
+
+ public byte[] ComputeHash(byte[] input)
+ {
+ // H(K XOR opad, H(K XOR ipad, text))
+ return H(_opad, H(_ipad, input));
+ }
+
+ public void Dispose()
+ {
+ if ( _hash != null )
+ {
+ ((IDisposable)_hash).Dispose();
+ _hash = null;
+ }
+ }
+
+ #region Private Methods
+ //
+ // Private Methods
+ //
+
+ private void CreatePads()
+ {
+ _ipad = new byte[BLOCK_LEN];
+ _opad = new byte[BLOCK_LEN];
+ for ( int i = 0; i < BLOCK_LEN; i++ )
+ {
+ _ipad[i] = 0x36;
+ _opad[i] = 0x5c;
+ }
+
+ XOR(_ipad, _key);
+ XOR(_opad, _key);
+ }
+
+ private static void XOR(byte[] dest, byte[] other)
+ {
+ // assume both are same size
+ for ( int i = 0; i < dest.Length; i++ )
+ {
+ dest[i] ^= other[i];
+ }
+ }
+
+ private byte[] H(byte[] v1, byte[] v2)
+ {
+ byte[] total = new byte[v1.Length + v2.Length];
+ Array.Copy(v1, total, v1.Length);
+ Array.Copy(v2, 0, total, v1.Length, v2.Length);
+
+ return _hash.ComputeHash(total);
+ }
+
+ #endregion // Private Methods
+
+ } // class MD5HMAC
+
+} // namespace Qpid.Sasl
diff --git a/dotnet/Qpid.Sasl/Mechanisms/AnonymousSaslClient.cs b/dotnet/Qpid.Sasl/Mechanisms/AnonymousSaslClient.cs new file mode 100644 index 0000000000..9adcfd9d13 --- /dev/null +++ b/dotnet/Qpid.Sasl/Mechanisms/AnonymousSaslClient.cs @@ -0,0 +1,69 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+namespace Qpid.Sasl.Mechanisms
+{
+ /// <summary>
+ /// Implements the ANONYMOUS authentication mechanism
+ /// as outlined in RFC 2245
+ /// </summary>
+ public class AnonymousSaslClient : SaslClient
+ {
+ public const string Mechanism = "ANONYMOUS";
+
+ public AnonymousSaslClient(
+ string authid, IDictionary properties,
+ ISaslCallbackHandler handler)
+ : base(authid, null, null, properties, handler)
+ {
+ }
+
+ #region ISaslClient Implementation
+ //
+ // ISaslClient Implementation
+ //
+
+ public override string MechanismName
+ {
+ get { return Mechanism; }
+ }
+
+ public override bool HasInitialResponse
+ {
+ get { return true; }
+ }
+
+ public override byte[] EvaluateChallenge(byte[] challenge)
+ {
+ // ignore challenge
+ SetComplete();
+ return Encoding.UTF8.GetBytes(AuthorizationId);
+ }
+
+ #endregion // ISaslClient Implementation
+
+ } // class AnonymousSaslClient
+
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/Mechanisms/CramMD5SaslClient.cs b/dotnet/Qpid.Sasl/Mechanisms/CramMD5SaslClient.cs new file mode 100644 index 0000000000..b5190daf0d --- /dev/null +++ b/dotnet/Qpid.Sasl/Mechanisms/CramMD5SaslClient.cs @@ -0,0 +1,91 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Qpid.Sasl.Mechanisms
+{
+ /// <summary>
+ /// Implements the CRAM-MD5 authentication mechanism as outlined
+ /// in RFC 2195
+ /// </summary>
+ public class CramMD5SaslClient : SaslClient
+ {
+ public const string Mechanism = "CRAM-MD5";
+ private const int MinPwdLen = 16;
+
+ public CramMD5SaslClient(
+ string authorizationId,
+ IDictionary properties,
+ ISaslCallbackHandler handler)
+ : base(authorizationId, null, null, properties, handler)
+ {
+ }
+
+ #region ISaslClient Implementation
+ //
+ // ISaslClient Implementation
+ //
+
+ public override string MechanismName
+ {
+ get { return Mechanism; }
+ }
+
+ public override bool HasInitialResponse
+ {
+ get { return false; }
+ }
+
+ public override byte[] EvaluateChallenge(byte[] challenge)
+ {
+ if ( challenge == null || challenge.Length == 0 )
+ throw new ArgumentNullException("challenge");
+
+ NameCallback nameCB = new NameCallback(AuthorizationId);
+ PasswordCallback pwdCB = new PasswordCallback();
+ ISaslCallback[] callbacks = { nameCB, pwdCB };
+ Handler.Handle(callbacks);
+
+ string username = nameCB.Text;
+ string passwd = pwdCB.Text.PadRight(MinPwdLen, '\0');
+
+ byte[] secret = Encoding.UTF8.GetBytes(passwd);
+
+ //using ( HMAC hmac = new HMACMD5(secret) )
+ using ( MD5HMAC hmac = new MD5HMAC(secret) )
+ {
+ byte[] value = hmac.ComputeHash(challenge);
+ string encoded = ToHex(value);
+ SetComplete();
+ return Encoding.UTF8.GetBytes(username + " " + encoded);
+ }
+
+ }
+
+ #endregion // ISaslClient Implementation
+
+ } // class CramMD5SaslClient
+
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/Mechanisms/DigestSaslClient.cs b/dotnet/Qpid.Sasl/Mechanisms/DigestSaslClient.cs new file mode 100644 index 0000000000..1f4b2cebdb --- /dev/null +++ b/dotnet/Qpid.Sasl/Mechanisms/DigestSaslClient.cs @@ -0,0 +1,576 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Qpid.Sasl.Mechanisms
+{
+
+ /// <summary>
+ /// Implements the DIGEST MD5 authentication mechanism
+ /// as outlined in RFC 2831
+ /// </summary>
+ public class DigestSaslClient : SaslClient
+ {
+ public const string Mechanism = "DIGEST-MD5";
+ private static readonly MD5 _md5 = new MD5CryptoServiceProvider();
+ private int _state;
+ private string _cnonce;
+ private Encoding _encoding = Encoding.UTF8;
+
+ public string Cnonce
+ {
+ get { return _cnonce; }
+ set { _cnonce = value; }
+ }
+
+ public DigestSaslClient(
+ string authid, string serverName, string protocol,
+ IDictionary properties, ISaslCallbackHandler handler)
+ : base(authid, serverName, protocol, properties, handler)
+ {
+ _cnonce = Guid.NewGuid().ToString("N");
+ }
+
+ #region ISaslClient Implementation
+ //
+ // ISaslClient Implementation
+ //
+
+ public override string MechanismName
+ {
+ get { return Mechanism; }
+ }
+
+ public override bool HasInitialResponse
+ {
+ get { return false; }
+ }
+
+ public override byte[] EvaluateChallenge(byte[] challenge)
+ {
+ if ( challenge == null || challenge.Length <= 0 )
+ throw new ArgumentNullException("challenge");
+
+ switch ( _state++ )
+ {
+ case 0: return OnInitialChallenge(challenge);
+ case 1: return OnFinalResponse(challenge);
+ }
+ throw new SaslException("Invalid State for authentication");
+ }
+
+ #endregion // ISaslClient Implementation
+
+
+ #region Private Methods
+ //
+ // Private Methods
+ //
+
+ /// <summary>
+ /// Process the first challenge from the server
+ /// and calculate a response
+ /// </summary>
+ /// <param name="challenge">The server issued challenge</param>
+ /// <returns>Client response</returns>
+ private byte[] OnInitialChallenge(byte[] challenge)
+ {
+ DigestChallenge dch =
+ DigestChallenge.Parse(_encoding.GetString(challenge));
+ // validate input challenge
+ if ( dch.Nonce == null || dch.Nonce.Length == 0 )
+ throw new SaslException("Nonce value missing in server challenge");
+ if ( dch.Algorithm != "md5-sess" )
+ throw new SaslException("Invalid or missing algorithm value in server challenge");
+
+
+ NameCallback nameCB = new NameCallback(AuthorizationId);
+ PasswordCallback pwdCB = new PasswordCallback();
+ RealmCallback realmCB = new RealmCallback(dch.Realm);
+ ISaslCallback[] callbacks = { nameCB, pwdCB, realmCB };
+ Handler.Handle(callbacks);
+
+ DigestResponse response = new DigestResponse();
+ response.Username = nameCB.Text;
+ response.Realm = realmCB.Text;
+ response.Nonce = dch.Nonce;
+ response.Cnonce = Cnonce;
+ response.NonceCount = 1;
+ response.Qop = DigestQop.Auth; // only auth supported for now
+ response.DigestUri = Protocol.ToLower() + "/" + ServerName;
+ response.MaxBuffer = dch.MaxBuffer;
+ response.Charset = dch.Charset;
+ response.Cipher = null; // not supported for now
+ response.Authzid = AuthorizationId;
+ response.AuthParam = dch.AuthParam;
+
+ response.Response = CalculateResponse(
+ nameCB.Text, realmCB.Text, pwdCB.Text,
+ dch.Nonce, response.NonceCount, response.Qop, response.DigestUri
+ );
+
+ return _encoding.GetBytes(response.ToString());
+ }
+
+ /// <summary>
+ /// Process the second server challenge
+ /// </summary>
+ /// <param name="challenge">Server issued challenge</param>
+ /// <returns>The client response</returns>
+ private byte[] OnFinalResponse(byte[] challenge)
+ {
+ DigestChallenge dch =
+ DigestChallenge.Parse(_encoding.GetString(challenge));
+
+ if ( dch.Rspauth == null || dch.Rspauth.Length == 0 )
+ throw new SaslException("Expected 'rspauth' in server challenge not found");
+
+ SetComplete();
+ return new byte[0];
+ }
+
+
+
+ /// <summary>
+ /// Calculate the response field of the client response
+ /// </summary>
+ /// <param name="username">The user name</param>
+ /// <param name="realm">The realm</param>
+ /// <param name="passwd">The user's password</param>
+ /// <param name="nonce">Server nonce value</param>
+ /// <param name="nc">Client nonce count (always 1)</param>
+ /// <param name="qop">Quality of Protection</param>
+ /// <param name="digestUri">Digest-URI</param>
+ /// <returns>The value for the response field</returns>
+ private string CalculateResponse(
+ string username, string realm, string passwd,
+ string nonce, int nc, string qop, string digestUri
+ )
+ {
+ string a1 = CalcHexA1(username, realm, passwd, nonce);
+ string a2 = CalcHexA2(digestUri, qop);
+
+ string ncs = nc.ToString("x8", CultureInfo.InvariantCulture);
+ StringBuilder prekd = new StringBuilder();
+ prekd.Append(a1).Append(':').Append(nonce).Append(':')
+ .Append(ncs).Append(':').Append(Cnonce)
+ .Append(':').Append(qop).Append(':').Append(a2);
+
+ return ToHex(CalcH(_encoding.GetBytes(prekd.ToString())));
+ }
+
+ private string CalcHexA1(
+ string username, string realm,
+ string passwd, string nonce
+ )
+ {
+ bool hasAuthId = AuthorizationId != null && AuthorizationId.Length > 0;
+
+ string premd = username + ":" + realm + ":" + passwd;
+ byte[] temp1 = CalcH(_encoding.GetBytes(premd));
+
+
+ int a1len = 16 + 1 + nonce.Length + 1 + Cnonce.Length;
+ if ( hasAuthId )
+ a1len += 1 + AuthorizationId.Length;
+
+ byte[] buffer = new byte[a1len];
+ Array.Copy(temp1, buffer, temp1.Length);
+
+ string p2 = ":" + nonce + ":" + Cnonce;
+ if ( hasAuthId )
+ p2 += ":" + AuthorizationId;
+
+ byte[] temp2 = _encoding.GetBytes(p2);
+ Array.Copy(temp2, 0, buffer, 16, temp2.Length);
+
+ return ToHex(CalcH(buffer));
+ }
+
+ private string CalcHexA2(string digestUri, string qop)
+ {
+ string a2 = "AUTHENTICATE:" + digestUri;
+ if ( qop != DigestQop.Auth )
+ a2 += ":00000000000000000000000000000000";
+ return ToHex(CalcH(_encoding.GetBytes(a2)));
+ }
+
+ private static byte[] CalcH(byte[] value)
+ {
+ return _md5.ComputeHash(value);
+ }
+
+ #endregion // Private Methods
+
+
+ } // class DigestSaslClient
+
+
+ /// <summary>
+ /// Available QOP options in the DIGEST scheme
+ /// </summary>
+ public sealed class DigestQop
+ {
+ public const string Auth = "auth";
+ public const string AuthInt = "auth-int";
+ public const string AuthConf = "auth-conf";
+ } // class DigestQop
+
+
+ /// <summary>
+ /// Represents and parses a digest server challenge
+ /// </summary>
+ public class DigestChallenge
+ {
+ private string _realm = "localhost";
+ private string _nonce;
+ private string[] _qopOptions = { DigestQop.Auth };
+ private bool _stale;
+ private int _maxBuffer = 65536;
+ private string _charset = "ISO 8859-1";
+ private string _algorithm;
+ private string[] _cipherOptions;
+ private string _authParam;
+ private string _rspauth;
+
+ #region Properties
+ //
+ // Properties
+ //
+
+ public string Realm
+ {
+ get { return _realm; }
+ }
+
+ public string Nonce
+ {
+ get { return _nonce; }
+ }
+
+ public string[] QopOptions
+ {
+ get { return _qopOptions; }
+ }
+
+ public bool Stale
+ {
+ get { return _stale; }
+ }
+
+ public int MaxBuffer
+ {
+ get { return _maxBuffer; }
+ set { _maxBuffer = value; }
+ }
+
+ public string Charset
+ {
+ get { return _charset; }
+ }
+
+ public string Algorithm
+ {
+ get { return _algorithm; }
+ }
+
+ public string[] CipherOptions
+ {
+ get { return _cipherOptions; }
+ }
+
+ public string AuthParam
+ {
+ get { return _authParam; }
+ }
+
+ public string Rspauth
+ {
+ get { return _rspauth; }
+ }
+
+ #endregion // Properties
+
+ public static DigestChallenge Parse(string challenge)
+ {
+ DigestChallenge parsed = new DigestChallenge();
+ StringDictionary parts = ParseParameters(challenge);
+ foreach ( string optname in parts.Keys )
+ {
+ switch ( optname )
+ {
+ case "realm":
+ parsed._realm = parts[optname];
+ break;
+ case "nonce":
+ parsed._nonce = parts[optname];
+ break;
+ case "qop-options":
+ parsed._qopOptions = GetOptions(parts[optname]);
+ break;
+ case "cipher-opts":
+ parsed._cipherOptions = GetOptions(parts[optname]);
+ break;
+ case "stale":
+ parsed._stale = Convert.ToBoolean(parts[optname], CultureInfo.InvariantCulture);
+ break;
+ case "maxbuf":
+ parsed._maxBuffer = Convert.ToInt32(parts[optname], CultureInfo.InvariantCulture);
+ break;
+ case "charset":
+ parsed._charset = parts[optname];
+ break;
+ case "algorithm":
+ parsed._algorithm = parts[optname];
+ break;
+ case "auth-param":
+ parsed._authParam = parts[optname];
+ break;
+ case "rspauth":
+ parsed._rspauth = parts[optname];
+ break;
+ }
+ }
+
+ return parsed;
+ }
+
+
+ public static StringDictionary ParseParameters(string source)
+ {
+ if ( source == null )
+ throw new ArgumentNullException("source");
+
+ StringDictionary ret = new StringDictionary();
+
+ string remaining = source.Trim();
+ while ( remaining.Length > 0 )
+ {
+ int equals = remaining.IndexOf('=');
+ if ( equals < 0 )
+ break;
+
+ string optname = remaining.Substring(0, equals).Trim();
+ remaining = remaining.Substring(equals + 1);
+
+ string value = ParseQuoted(ref remaining);
+ ret[optname] = value.Trim();
+ }
+ return ret;
+ }
+
+ private static string ParseQuoted(ref string str)
+ {
+ string ns = str.TrimStart();
+
+ int start = 0;
+ bool quoted = ns[0] == '\"';
+ if ( quoted ) start++;
+ bool inquotes = quoted;
+ bool escaped = false;
+
+ int pos = start;
+ for ( ; pos < ns.Length; pos++ )
+ {
+ if ( !inquotes && ns[pos] == ',' )
+ break;
+
+ // at end of quotes?
+ if ( quoted && !escaped && ns[pos] == '\"' )
+ inquotes = false;
+ // is this char an escape for the next one?
+ escaped = inquotes && ns[pos] == '\\';
+ }
+ // pos has end of string
+ string value = ns.Substring(start, pos-start).Trim();
+ if ( quoted )
+ {
+ // remove trailing quote
+ value = value.Substring(0, value.Length - 1);
+ }
+ str = ns.Substring(pos < ns.Length-1 ? pos+1 : pos);
+ return value;
+ }
+
+ private static string[] GetOptions(string value)
+ {
+ return value.Split(' ');
+ }
+
+ } // class DigestChallenge
+
+
+ /// <summary>
+ /// Represents and knows how to write a
+ /// digest client response
+ /// </summary>
+ public class DigestResponse
+ {
+ private string _username;
+ private string _realm;
+ private string _nonce;
+ private string _cnonce;
+ private int _nonceCount;
+ private string _qop;
+ private string _digestUri;
+ private string _response;
+ private int _maxBuffer;
+ private string _charset;
+ private string _cipher;
+ private string _authzid;
+ private string _authParam;
+
+ #region Properties
+ //
+ // Properties
+ //
+
+ public string Username
+ {
+ get { return _username; }
+ set { _username = value; }
+ }
+
+ public string Realm
+ {
+ get { return _realm; }
+ set { _realm = value; }
+ }
+
+ public string Nonce
+ {
+ get { return _nonce; }
+ set { _nonce = value; }
+ }
+
+ public string Cnonce
+ {
+ get { return _cnonce; }
+ set { _cnonce = value; }
+ }
+
+ public int NonceCount
+ {
+ get { return _nonceCount; }
+ set { _nonceCount = value; }
+ }
+
+ public string Qop
+ {
+ get { return _qop; }
+ set { _qop = value; }
+ }
+
+ public string DigestUri
+ {
+ get { return _digestUri; }
+ set { _digestUri = value; }
+ }
+
+ public string Response
+ {
+ get { return _response; }
+ set { _response = value; }
+ }
+
+ public int MaxBuffer
+ {
+ get { return _maxBuffer; }
+ set { _maxBuffer = value; }
+ }
+
+ public string Charset
+ {
+ get { return _charset; }
+ set { _charset = value; }
+ }
+
+ public string Cipher
+ {
+ get { return _cipher; }
+ set { _cipher = value; }
+ }
+
+ public string Authzid
+ {
+ get { return _authzid; }
+ set { _authzid = value; }
+ }
+
+ public string AuthParam
+ {
+ get { return _authParam; }
+ set { _authParam = value; }
+ }
+
+ #endregion // Properties
+
+
+ public override string ToString()
+ {
+ StringBuilder buffer = new StringBuilder();
+ Pair(buffer, "username", Username, true);
+ Pair(buffer, "realm", Realm, true);
+ Pair(buffer, "nonce", Nonce, true);
+ Pair(buffer, "cnonce", Cnonce, true);
+ string nc = NonceCount.ToString("x8", CultureInfo.InvariantCulture);
+ Pair(buffer, "nc", nc, false);
+ Pair(buffer, "qop", Qop, false);
+ Pair(buffer, "digest-uri", DigestUri, true);
+ Pair(buffer, "response", Response, true);
+ string maxBuffer = MaxBuffer.ToString(CultureInfo.InvariantCulture);
+ Pair(buffer, "maxbuf", maxBuffer, false);
+ Pair(buffer, "charset", Charset, false);
+ Pair(buffer, "cipher", Cipher, false);
+ Pair(buffer, "authzid", Authzid, true);
+ Pair(buffer, "auth-param", AuthParam, true);
+
+ return buffer.ToString().TrimEnd(',');
+ }
+
+ private static void Pair(StringBuilder buffer, string name, string value, bool quoted)
+ {
+ if ( value != null && value.Length > 0 )
+ {
+ buffer.Append(name);
+ buffer.Append('=');
+ if ( quoted )
+ {
+ buffer.Append('\"');
+ buffer.Append(value.Replace("\"", "\\\""));
+ buffer.Append('\"');
+ } else
+ {
+ buffer.Append(value);
+ }
+ buffer.Append(',');
+ }
+ }
+
+ } // class DigestResponse
+
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/Mechanisms/PlainSaslClient.cs b/dotnet/Qpid.Sasl/Mechanisms/PlainSaslClient.cs new file mode 100644 index 0000000000..a6813fa825 --- /dev/null +++ b/dotnet/Qpid.Sasl/Mechanisms/PlainSaslClient.cs @@ -0,0 +1,81 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+namespace Qpid.Sasl.Mechanisms
+{
+
+ /// <summary>
+ /// Implements the PLAIN authentication mechanism
+ /// as outlined in RFC 4616
+ /// </summary>
+ public class PlainSaslClient : SaslClient
+ {
+ public const string Mechanism = "PLAIN";
+
+ public PlainSaslClient(
+ string authid, IDictionary properties,
+ ISaslCallbackHandler handler)
+ : base(authid, null, null, properties, handler)
+ {
+ }
+
+ #region ISaslClient Implementation
+ //
+ // ISaslClient Implementation
+ //
+
+ public override string MechanismName
+ {
+ get { return Mechanism; }
+ }
+
+ public override bool HasInitialResponse
+ {
+ get { return true; }
+ }
+
+ public override byte[] EvaluateChallenge(byte[] challenge)
+ {
+ // ignore challenge
+
+ NameCallback nameCB = new NameCallback();
+ PasswordCallback pwdCB = new PasswordCallback();
+ ISaslCallback[] callbacks = { nameCB, pwdCB };
+ Handler.Handle(callbacks);
+
+ string username = nameCB.Text;
+ string authid = AuthorizationId;
+ string passwd = pwdCB.Text;
+
+ string response =
+ string.Format("{0}\0{1}\0{2}", authid, username, passwd);
+ SetComplete();
+ return Encoding.UTF8.GetBytes(response);
+ }
+
+ #endregion // ISaslClient Implementation
+
+ } // class PlainSaslClient
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/Properties/AssemblyInfo.cs b/dotnet/Qpid.Sasl/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9e422b106e --- /dev/null +++ b/dotnet/Qpid.Sasl/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Qpid.Sasl")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Apache Qpid")]
+[assembly: AssemblyProduct("Apache Qpid")]
+[assembly: AssemblyCopyright("Copyright © 2007 The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("27ea23e4-6f84-4a54-8f1f-5725e6d767cc")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: CLSCompliant(true)]
diff --git a/dotnet/Qpid.Sasl/Qpid.Sasl.csproj b/dotnet/Qpid.Sasl/Qpid.Sasl.csproj new file mode 100644 index 0000000000..fa7e91811f --- /dev/null +++ b/dotnet/Qpid.Sasl/Qpid.Sasl.csproj @@ -0,0 +1,63 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.50727</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{1465B0EE-6452-42A6-AB73-B2F9EABEEE75}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Qpid.Sasl</RootNamespace>
+ <AssemblyName>Qpid.Sasl</AssemblyName>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <UseVSHostingProcess>true</UseVSHostingProcess>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Callbacks.cs" />
+ <Compile Include="Configuration\SaslConfiguration.cs" />
+ <Compile Include="Configuration\SaslConfigurationSectionHandler.cs" />
+ <Compile Include="MD5HMAC.cs" />
+ <Compile Include="SaslException.cs" />
+ <Compile Include="Mechanisms\AnonymousSaslClient.cs" />
+ <Compile Include="Mechanisms\DigestSaslClient.cs" />
+ <Compile Include="Sasl.cs" />
+ <Compile Include="DefaultClientFactory.cs" />
+ <Compile Include="ISaslCallbackHandler.cs" />
+ <Compile Include="ISaslClientFactory.cs" />
+ <Compile Include="Mechanisms\CramMD5SaslClient.cs" />
+ <Compile Include="SaslProperties.cs" />
+ <Compile Include="ISaslClient.cs" />
+ <Compile Include="Mechanisms\PlainSaslClient.cs" />
+ <Compile Include="SaslClient.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
diff --git a/dotnet/Qpid.Sasl/Sasl.cs b/dotnet/Qpid.Sasl/Sasl.cs new file mode 100644 index 0000000000..baab5c40e6 --- /dev/null +++ b/dotnet/Qpid.Sasl/Sasl.cs @@ -0,0 +1,115 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Configuration;
+using System.Text;
+
+using Qpid.Sasl.Configuration;
+
+namespace Qpid.Sasl
+{
+ /// <summary>
+ /// Static class used to access the SASL functionality.
+ /// The core SASL mechanism is described in RFC 2222.
+ /// </summary>
+ /// <remarks>
+ /// Only client side mechanisms are implemented.
+ /// <para>
+ /// New client side factories can be added programatically using the
+ /// RegisterClientFactory method, or through the application
+ /// configuration file, like this:
+ /// </para>
+ /// <example><![CDATA[
+ /// <configuration>
+ /// <configSections>
+ /// <section name="qpid.sasl" type="Qpid.Sasl.Configuration.SaslConfigurationSectionHandler, Qpid.Sasl"/>
+ /// </configSections>
+ ///
+ /// <qpid.sasl>
+ /// <clientFactories>
+ /// <add type="Qpid.Sasl.Tests.TestClientFactory, Qpid.Sasl.Tests"/>
+ /// </clientFactories>
+ /// </qpid.sasl>
+ /// </configuration>
+ /// ]]></example>
+ /// </remarks>
+ public sealed class Sasl
+ {
+ private static IList _clientFactories;
+
+
+ static Sasl()
+ {
+ SaslConfiguration config = SaslConfiguration.GetConfiguration();
+ _clientFactories = config.ClientFactories;
+ }
+ private Sasl()
+ {
+ }
+
+ public static ISaslClient CreateClient(
+ string[] mechanisms, string authorizationId,
+ string protocol, string serverName,
+ IDictionary props, ISaslCallbackHandler handler
+ )
+ {
+ ISaslClientFactory factory = FindFactory(mechanisms, props);
+ if ( factory == null )
+ return null;
+
+ return factory.CreateClient (
+ mechanisms, authorizationId,
+ protocol, serverName, props, handler
+ );
+ }
+
+ public static void RegisterClientFactory(ISaslClientFactory factory)
+ {
+ lock ( _clientFactories )
+ {
+ _clientFactories.Add(factory);
+ }
+ }
+
+ private static ISaslClientFactory FindFactory(string[] mechanisms, IDictionary props)
+ {
+ lock ( _clientFactories )
+ {
+ foreach ( ISaslClientFactory factory in _clientFactories )
+ {
+ string[] mechs = factory.GetSupportedMechanisms(props);
+ foreach ( string m1 in mechs )
+ {
+ foreach (string m2 in mechanisms )
+ {
+ if ( m1 == m2 )
+ return factory;
+ }
+ }
+ }
+ return null;
+ }
+ }
+ } // class Sasl
+
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/SaslClient.cs b/dotnet/Qpid.Sasl/SaslClient.cs new file mode 100644 index 0000000000..79413e0077 --- /dev/null +++ b/dotnet/Qpid.Sasl/SaslClient.cs @@ -0,0 +1,145 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ public abstract class SaslClient : ISaslClient
+ {
+ private bool _isComplete;
+ private IDictionary _properties;
+ private string _authorizationId;
+ private string _serverName;
+ private string _protocol;
+ private ISaslCallbackHandler _handler;
+
+ protected string AuthorizationId
+ {
+ get { return _authorizationId; }
+ }
+ protected string ServerName
+ {
+ get { return _serverName; }
+ }
+
+ protected string Protocol
+ {
+ get { return _protocol; }
+ }
+
+ protected ISaslCallbackHandler Handler
+ {
+ get { return _handler; }
+ }
+
+ protected IDictionary Properties
+ {
+ get { return _properties; }
+ }
+
+ protected SaslClient(
+ string authid, string serverName,
+ string protocol, IDictionary properties,
+ ISaslCallbackHandler handler)
+ {
+ if ( properties == null )
+ throw new ArgumentNullException("properties");
+ if ( handler == null )
+ throw new ArgumentNullException("handler");
+
+ _authorizationId = authid==null ? "" : authid;
+ _serverName = serverName;
+ _protocol = protocol;
+ _properties = properties;
+ _handler = handler;
+
+ if ( _serverName == null || _serverName.Length == 0 )
+ {
+ _serverName = System.Net.Dns.GetHostName();
+ }
+ }
+
+
+
+
+ #region ISaslClient Implementation
+ //
+ // ISaslClient Implementation
+ //
+
+ public abstract string MechanismName { get; }
+
+ public abstract bool HasInitialResponse { get; }
+
+ public bool IsComplete
+ {
+ get { return _isComplete; }
+ }
+
+ public abstract byte[] EvaluateChallenge(byte[] challenge);
+
+ public virtual object GetNegotiatedProperty(string propName)
+ {
+ return null;
+ }
+
+ public virtual byte[] Unwrap(byte[] buffer, int offset, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual byte[] Wrap(byte[] buffer, int offset, int lenght)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion // ISaslClient Implementation
+
+
+ #region Helper Methods
+ //
+ // Helper Methods
+ //
+
+ protected void SetComplete()
+ {
+ _isComplete = true;
+ }
+
+ protected static string ToHex(byte[] buffer)
+ {
+ StringBuilder builder = new StringBuilder();
+ foreach ( byte b in buffer )
+ {
+ builder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
+ }
+ return builder.ToString();
+ }
+
+ #endregion // Helper Methods
+
+ } // class SaslClient
+
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/SaslException.cs b/dotnet/Qpid.Sasl/SaslException.cs new file mode 100644 index 0000000000..203c408c3f --- /dev/null +++ b/dotnet/Qpid.Sasl/SaslException.cs @@ -0,0 +1,56 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ /// <summary>
+ /// Reports an exception during the processing of an SASL
+ /// Operation. Only used for authentication-relared errors
+ /// </summary>
+ [Serializable]
+ public class SaslException : Exception
+ {
+ public SaslException()
+ {
+ }
+
+ public SaslException(string message)
+ : base(message)
+ {
+ }
+ public SaslException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ protected SaslException(SerializationInfo info, StreamingContext ctxt)
+ : base(info, ctxt)
+ {
+ }
+
+ } // class SaslException
+
+} // namespace Qpid.Sasl.Mechanisms
diff --git a/dotnet/Qpid.Sasl/SaslProperties.cs b/dotnet/Qpid.Sasl/SaslProperties.cs new file mode 100644 index 0000000000..0e59ce8f90 --- /dev/null +++ b/dotnet/Qpid.Sasl/SaslProperties.cs @@ -0,0 +1,42 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Text;
+
+namespace Qpid.Sasl
+{
+ public sealed class SaslProperties
+ {
+ public const string PolicyNoPlainText = "NOPLAINTEXT";
+ public const string PolicyNoActive = "NOACTIVE";
+ public const string PolicyNoDictionary = "NODICTIONARY";
+ public const string PolicyNoAnonymous = "NOANONYMOUS";
+ public const string PolicyForwardSecrecy = "FORWARD_SECRECY";
+ public const string PolicyPassCredentials = "PASS_CREDENTIALS";
+
+ public const string Qop = "QOP";
+ public const string Strength = "STRENGTH";
+
+ } // class SaslProperties
+
+} // namespace Qpid.Sasl.Mechanisms
|