// // IssuedSecurityTokenProviderTest.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2006 Novell, Inc. http://www.novell.com // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #if !MOBILE && !XAMMAC_4_5 using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Xml; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Security; using System.ServiceModel.Security.Tokens; using System.IdentityModel.Tokens; using System.Text; using System.Xml; using NUnit.Framework; using MonoTests.System.ServiceModel.Channels; namespace MonoTests.System.ServiceModel.Security.Tokens { [TestFixture] public class IssuedSecurityTokenProviderTest { [Test] public void DefaultValues () { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); Assert.AreEqual (true, p.CacheIssuedTokens, "#1"); Assert.AreEqual (TimeSpan.FromMinutes (1), p.DefaultOpenTimeout, "#2"); Assert.AreEqual (TimeSpan.FromMinutes (1), p.DefaultCloseTimeout, "#3"); Assert.IsNotNull (p.IdentityVerifier, "#4"); Assert.AreEqual (60, p.IssuedTokenRenewalThresholdPercentage, "#5"); Assert.IsNull (p.IssuerAddress, "#6"); Assert.AreEqual (0, p.IssuerChannelBehaviors.Count, "#7"); Assert.AreEqual (SecurityKeyEntropyMode.CombinedEntropy, p.KeyEntropyMode, "#8"); Assert.AreEqual (TimeSpan.MaxValue, p.MaxIssuedTokenCachingTime, "#9"); Assert.AreEqual (MessageSecurityVersion.Default, p.MessageSecurityVersion, "#10"); Assert.IsNull (p.SecurityAlgorithmSuite, "#11"); Assert.IsNull (p.SecurityTokenSerializer, "#12"); Assert.IsNull (p.TargetAddress, "#13"); Assert.AreEqual (true, p.SupportsTokenCancellation, "#14"); Assert.AreEqual (0, p.TokenRequestParameters.Count, "#15"); Assert.IsNull (p.IssuerBinding, "#16"); } [Test] [ExpectedException (typeof (InvalidOperationException))] public void OpenWithoutSerializer () { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); p.Open (); } [Test] [ExpectedException (typeof (InvalidOperationException))] public void OpenWithoutIssuerAddress () { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); p.SecurityTokenSerializer = WSSecurityTokenSerializer.DefaultInstance; p.Open (); } [Test] [ExpectedException (typeof (InvalidOperationException))] public void OpenWithoutBinding () { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); p.SecurityTokenSerializer = WSSecurityTokenSerializer.DefaultInstance; p.IssuerAddress = new EndpointAddress ("http://localhost:8080"); p.Open (); } [Test] [ExpectedException (typeof (InvalidOperationException))] public void OpenWithoutTargetAddress () { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); p.SecurityTokenSerializer = WSSecurityTokenSerializer.DefaultInstance; p.IssuerAddress = new EndpointAddress ("http://localhost:8080"); p.IssuerBinding = new BasicHttpBinding (); // wiithout it indigo causes NRE p.SecurityAlgorithmSuite = SecurityAlgorithmSuite.Default; p.Open (); } [Test] [Category ("NotWorking")] public void Open () { IssuedSecurityTokenProvider p = SetupProvider (new BasicHttpBinding ()); try { p.Open (); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } [Test] [ExpectedException (typeof (InvalidOperationException))] public void GetTokenWithoutOpen () { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); p.GetToken (TimeSpan.FromSeconds (10)); } // From WinFX beta2: // System.ServiceModel.Security.SecurityNegotiationException : // SOAP security negotiation with 'stream:dummy' for target // 'stream:dummy' failed. See inner exception for more details. // ----> System.InvalidOperationException : The request // message must be protected. This is required by an operation // of the contract ('IWsTrustFeb2005SecurityTokenService', // 'http://tempuri.org/'). The protection must be provided by // the binding ('BasicHttpBinding','http://tempuri.org/'). [Test] [ExpectedException (typeof (SecurityNegotiationException))] [Category ("NotWorking")] public void GetTokenNoSecureBinding () { IssuedSecurityTokenProvider p = SetupProvider (new BasicHttpBinding ()); try { p.Open (); p.GetToken (TimeSpan.FromSeconds (10)); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } [Test] // SymmetricSecurityBindingElement requires protection // token parameters to build a channel or listener factory. [ExpectedException (typeof (SecurityNegotiationException))] [Category ("NotWorking")] public void GetTokenWithoutProtectionTokenParameters () { IssuedSecurityTokenProvider p = SetupProvider (CreateIssuerBinding (null, false)); try { p.Open (); p.GetToken (TimeSpan.FromSeconds (10)); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } // SecurityNegotiationException (InvalidOperationException ( // "The service certificate is not provided for target // 'stream:dummy'. Specify a service certificate in // ClientCredentials.")) [Test] [ExpectedException (typeof (SecurityNegotiationException))] [Category ("NotWorking")] public void GetTokenWithoutServiceCertificate () { IssuedSecurityTokenProvider p = SetupProvider (CreateIssuerBinding (null, true)); p.IssuerAddress = new EndpointAddress ("stream:dummy"); try { p.Open (TimeSpan.FromSeconds (5)); p.GetToken (TimeSpan.FromSeconds (10)); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } [Test] [Category ("NotWorking")] [ExpectedException (typeof (MyException))] public void GetTokenWrongResponse () { IssuedSecurityTokenProvider p = SetupProvider (CreateIssuerBinding (new RequestSender (OnGetTokenWrongResponse), true)); try { p.Open (TimeSpan.FromSeconds (5)); p.GetToken (TimeSpan.FromSeconds (10)); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } [Test] [Category ("NotWorking")] [ExpectedException (typeof (MessageSecurityException))] public void GetTokenUnsignedReply () { IssuedSecurityTokenProvider p = SetupProvider (CreateIssuerBinding (new RequestSender (OnGetTokenUnsignedReply), true)); try { p.Open (TimeSpan.FromSeconds (5)); p.GetToken (TimeSpan.FromSeconds (10)); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } // InnerException: System.InvalidOperationException: // The issuer must provide a computed key in key entropy mode // 'CombinedEntropy'. [Test] [Ignore ("todo")] [ExpectedException (typeof (SecurityNegotiationException))] public void GetTokenNoEntropyInResponseInCombinedMode () { // FIXME: implement it after we get working token issuer. // In the reply, do not include Nonce } // on the other hand, in Client entropy mode it must not // provide entropy. [Test] [Ignore ("todo")] [ExpectedException (typeof (SecurityNegotiationException))] public void GetTokenIncludesEntropyInResponseInClientMode () { // FIXME: implement it after we get working token issuer. // specify SecurityKeyEntropyMode.ClientEntropy on // client side. And in the reply, include Nonce. } [Test] [Ignore ("need to implement response")] [Category ("NotWorking")] public void GetToken () { IssuedSecurityTokenProvider p = SetupProvider (CreateIssuerBinding (new RequestSender (OnGetToken), true)); try { p.Open (TimeSpan.FromSeconds (5)); p.GetToken (TimeSpan.FromSeconds (10)); } finally { if (p.State == CommunicationState.Opened) p.Close (); } } class MyException : Exception { } Message OnGetTokenWrongResponse (Message input) { VerifyInput (input.CreateBufferedCopy (10000)); throw new MyException (); } Message OnGetTokenUnsignedReply (Message input) { XmlDocument doc = new XmlDocument (); doc.LoadXml ("RESPONSE"); Message msg = Message.CreateMessage (input.Version, "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/IssueResponse", doc.DocumentElement); msg.Headers.Add (MessageHeader.CreateHeader ( "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", null, true)); return msg; } Message OnGetToken (Message input) { MessageBuffer buf = input.CreateBufferedCopy (10000); VerifyInput2 (buf); // FIXME: create response message (when I understand what I should return.) // throw new MyException (); //* XmlDocument doc = new XmlDocument (); doc.LoadXml ("RESPONSE"); X509Certificate2 cert = new X509Certificate2 ("Test/Resources/test.pfx", "mono"); SignedXml sxml = new SignedXml (doc); MemoryStream ms = new MemoryStream (new byte [] {1, 2, 3}); sxml.AddReference (new Reference (ms)); sxml.SigningKey = cert.PrivateKey; sxml.ComputeSignature (); Message msg = Message.CreateMessage (input.Version, "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue", sxml.GetXml ()); msg.Headers.Add (MessageHeader.CreateHeader ( "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", null, true)); return msg; //*/ } void VerifyInput (MessageBuffer buf) { Message input = buf.CreateMessage (); /* XmlWriterSettings settings = new XmlWriterSettings (); settings.Indent = true; using (XmlWriter w = XmlWriter.Create (Console.Error, settings)) { buf.CreateMessage ().WriteMessage (w); } Console.Error.WriteLine ("******************** DONE ********************"); Console.Error.Flush (); */ Assert.AreEqual ("http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue", input.Headers.Action, "GetToken.Request.Action"); Assert.IsNotNull (input.Headers.MessageId, "GetToken.Request.MessageID"); // in the raw Message it is "http://www.w3.org/2005/08/addressing/anonymous", but it is replaced by MessageHeaders implementation. Assert.AreEqual (new EndpointAddress ("http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous"), input.Headers.ReplyTo, "GetToken.Request.ReplyTo"); // o:Security // FIXME: test WSSecurity more // // // ... // ... // // ... // // // // // // // ... // // // [ // // // ... // ... // ... // // ] // // [ // // ] // // // // {{....}} // {{....}} // // int i = input.Headers.FindHeader ("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); Assert.IsTrue (i >= 0, "Security header existence"); MessageHeaderInfo info = input.Headers [i]; Assert.IsNotNull (info, "Security header item"); XmlReader r = input.Headers.GetReaderAtHeader (i); // FIXME: test WSSecurity more // r.MoveToContent (); r.ReadStartElement ("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); // r.MoveToContent (); r.ReadStartElement ("Timestamp", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); // ... r.MoveToContent (); r.ReadStartElement ("Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); r.ReadString (); r.MoveToContent (); r.ReadEndElement (); // ... r.MoveToContent (); r.ReadStartElement ("Expires", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); r.ReadString (); r.MoveToContent (); r.ReadEndElement (); // r.MoveToContent (); r.ReadEndElement (); // ... r.MoveToContent (); r.ReadStartElement ("BinarySecurityToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); byte [] rawcert = Convert.FromBase64String (r.ReadString ()); r.ReadEndElement (); X509Certificate2 cert = new X509Certificate2 (rawcert); // FIXME: test EncryptedKey r.MoveToContent (); r.Skip (); // // // // // // // ... // // // FIXME: test DerivedKeyTokens r.MoveToContent (); while (r.LocalName == "DerivedKeyToken") { r.Skip (); r.MoveToContent (); } // [ // // // ... // ... // ... // // ] // // [ // // ] // // // // {{....}} // {{....}} // // // SOAP Body r = input.GetReaderAtBodyContents (); // just verifying itself ;) } XmlElement VerifyInput2 (MessageBuffer buf) { Message msg2 = buf.CreateMessage (); StringWriter sw = new StringWriter (); using (XmlDictionaryWriter w = XmlDictionaryWriter.CreateDictionaryWriter (XmlWriter.Create (sw))) { msg2.WriteMessage (w); } XmlDocument doc = new XmlDocument (); doc.PreserveWhitespace = true; doc.LoadXml (sw.ToString ()); // decrypt the key with service certificate privkey PaddingMode mode = PaddingMode.PKCS7; // not sure which is correct ... ANSIX923, ISO10126, PKCS7, Zeros, None. EncryptedXml encXml = new EncryptedXml (doc); encXml.Padding = mode; X509Certificate2 cert2 = new X509Certificate2 ("Test/Resources/test.pfx", "mono"); XmlNamespaceManager nsmgr = new XmlNamespaceManager (doc.NameTable); nsmgr.AddNamespace ("s", "http://www.w3.org/2003/05/soap-envelope"); nsmgr.AddNamespace ("c", "http://schemas.xmlsoap.org/ws/2005/02/sc"); nsmgr.AddNamespace ("o", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); nsmgr.AddNamespace ("e", "http://www.w3.org/2001/04/xmlenc#"); nsmgr.AddNamespace ("dsig", "http://www.w3.org/2000/09/xmldsig#"); XmlNode n = doc.SelectSingleNode ("//o:Security/e:EncryptedKey/e:CipherData/e:CipherValue", nsmgr); Assert.IsNotNull (n, "premise: enckey does not exist"); string raw = n.InnerText; byte [] rawbytes = Convert.FromBase64String (raw); RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) cert2.PrivateKey; byte [] decryptedKey = EncryptedXml.DecryptKey (rawbytes, rsa, true);//rsa.Decrypt (rawbytes, true); #if false // create derived keys Dictionary keys = new Dictionary (); InMemorySymmetricSecurityKey skey = new InMemorySymmetricSecurityKey (decryptedKey); foreach (XmlElement el in doc.SelectNodes ("//o:Security/c:DerivedKeyToken", nsmgr)) { n = el.SelectSingleNode ("c:Offset", nsmgr); int offset = (n == null) ? 0 : int.Parse (n.InnerText, CultureInfo.InvariantCulture); n = el.SelectSingleNode ("c:Length", nsmgr); int length = (n == null) ? 32 : int.Parse (n.InnerText, CultureInfo.InvariantCulture); n = el.SelectSingleNode ("c:Label", nsmgr); byte [] label = (n == null) ? decryptedKey : Convert.FromBase64String (n.InnerText); n = el.SelectSingleNode ("c:Nonce", nsmgr); byte [] nonce = (n == null) ? new byte [0] : Convert.FromBase64String (n.InnerText); byte [] derkey = skey.GenerateDerivedKey ( //SecurityAlgorithms.Psha1KeyDerivation, "http://schemas.xmlsoap.org/ws/2005/02/sc/dk/p_sha1", // FIXME: maybe due to the label, this key resolution somehow does not seem to work. label, nonce, length * 8, offset); keys [el.GetAttribute ("Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")] = derkey; } #endif // decrypt the signature with the decrypted key #if true n = doc.SelectSingleNode ("//o:Security/e:EncryptedData/e:CipherData/e:CipherValue", nsmgr); Assert.IsNotNull (n, "premise: encdata does not exist"); raw = n.InnerText; rawbytes = Convert.FromBase64String (raw); Rijndael aes = RijndaelManaged.Create (); // aes.Key = keys [n.SelectSingleNode ("../../dsig:KeyInfo/o:SecurityTokenReference/o:Reference/@URI", nsmgr).InnerText.Substring (1)]; aes.Key = decryptedKey; aes.Mode = CipherMode.CBC; aes.Padding = mode; MemoryStream ms = new MemoryStream (); CryptoStream cs = new CryptoStream (ms, aes.CreateDecryptor (), CryptoStreamMode.Write); cs.Write (rawbytes, 0, rawbytes.Length); cs.Close (); byte [] decryptedSignature = ms.ToArray (); #else Rijndael aes = RijndaelManaged.Create (); // aes.Key = keys [n.SelectSingleNode ("../../dsig:KeyInfo/o:SecurityTokenReference/o:Reference/@URI", nsmgr).InnerText.Substring (1)]; aes.Key = decryptedKey; aes.Mode = CipherMode.CBC; aes.Padding = mode; EncryptedData ed = new EncryptedData (); n = doc.SelectSingleNode ("//o:Security/e:EncryptedData", nsmgr); Assert.IsNotNull (n, "premise: encdata does not exist"); ed.LoadXml (n as XmlElement); byte [] decryptedSignature = encXml.DecryptData (ed, aes); #endif //Console.Error.WriteLine (Encoding.UTF8.GetString (decryptedSignature)); //Console.Error.WriteLine ("============= Decrypted Signature End ==========="); // decrypt the body with the decrypted key #if true n = doc.SelectSingleNode ("//s:Body/e:EncryptedData/e:CipherData/e:CipherValue", nsmgr); Assert.IsNotNull (n, "premise: encdata does not exist"); raw = n.InnerText; rawbytes = Convert.FromBase64String (raw); // aes.Key = keys [n.SelectSingleNode ("../../dsig:KeyInfo/o:SecurityTokenReference/o:Reference/@URI", nsmgr).InnerText.Substring (1)]; aes.Key = decryptedKey; ms = new MemoryStream (); cs = new CryptoStream (ms, aes.CreateDecryptor (), CryptoStreamMode.Write); cs.Write (rawbytes, 0, rawbytes.Length); cs.Close (); byte [] decryptedBody = ms.ToArray (); #else // decrypt the body with the decrypted key EncryptedData ed2 = new EncryptedData (); XmlElement el = doc.SelectSingleNode ("/s:Envelope/s:Body/e:EncryptedData", nsmgr) as XmlElement; ed2.LoadXml (el); // aes.Key = keys [n.SelectSingleNode ("../../dsig:KeyInfo/o:SecurityTokenReference/o:Reference/@URI", nsmgr).InnerText.Substring (1)]; aes.Key = decryptedKey; byte [] decryptedBody = encXml.DecryptData (ed2, aes); #endif //foreach (byte b in decryptedBody) Console.Error.Write ("{0:X02} ", b); Console.Error.WriteLine (Encoding.UTF8.GetString (decryptedBody)); Console.Error.WriteLine ("============= Decrypted Body End ==========="); // FIXME: find out what first 16 bytes mean. for (int mmm = 0; mmm < 16; mmm++) decryptedBody [mmm] = 0x20; doc.LoadXml (Encoding.UTF8.GetString (decryptedBody)); Assert.AreEqual ("RequestSecurityToken", doc.DocumentElement.LocalName, "#b-1"); Assert.AreEqual ("http://schemas.xmlsoap.org/ws/2005/02/trust", doc.DocumentElement.NamespaceURI, "#b-2"); return doc.DocumentElement; } Binding CreateIssuerBinding (RequestSender handler, bool tokenParams) { SymmetricSecurityBindingElement sbe = new SymmetricSecurityBindingElement (); if (tokenParams) sbe.ProtectionTokenParameters = new X509SecurityTokenParameters (); sbe.LocalServiceSettings.NegotiationTimeout = TimeSpan.FromSeconds (5); sbe.KeyEntropyMode = SecurityKeyEntropyMode.ClientEntropy; //sbe.IncludeTimestamp = false; //sbe.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt; // for ease of decryption, let's remove DerivedKeyToken. sbe.SetKeyDerivation (false); return new CustomBinding ( // new DebugBindingElement (), sbe, new TextMessageEncodingBindingElement (), new HandlerTransportBindingElement (handler)); } EndpointAddress GetSecureEndpointAddress (string uri) { return new EndpointAddress (new Uri (uri), new X509CertificateEndpointIdentity ( new X509Certificate2 ("Test/Resources/test.pfx", "mono"))); } IssuedSecurityTokenProvider SetupProvider (Binding binding) { IssuedSecurityTokenProvider p = new IssuedSecurityTokenProvider (); p.SecurityTokenSerializer = WSSecurityTokenSerializer.DefaultInstance; p.IssuerAddress = GetSecureEndpointAddress ("stream:dummy"); p.IssuerBinding = binding; // wiithout it indigo causes NRE p.SecurityAlgorithmSuite = SecurityAlgorithmSuite.Default; p.TargetAddress = new EndpointAddress ("http://localhost:9090"); return p; } } } #endif