| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- //
- // SecureMessageDecryptor.cs
- //
- // Author:
- // Atsushi Enomoto <[email protected]>
- //
- // Copyright (C) 2006-2007 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.
- //
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Globalization;
- using System.IdentityModel.Selectors;
- using System.IdentityModel.Tokens;
- using System.Runtime.Serialization;
- using System.Security.Cryptography;
- using System.Security.Cryptography.X509Certificates;
- using System.Security.Cryptography.Xml;
- using System.ServiceModel;
- using System.ServiceModel.Channels;
- using System.ServiceModel.Description;
- using System.ServiceModel.Security;
- using System.ServiceModel.Security.Tokens;
- using System.Text;
- using System.Xml;
- using System.Xml.XPath;
- using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement;
- namespace System.ServiceModel.Channels.Security
- {
- internal class RecipientSecureMessageDecryptor : SecureMessageDecryptor
- {
- RecipientMessageSecurityBindingSupport security;
- public RecipientSecureMessageDecryptor (
- Message source, RecipientMessageSecurityBindingSupport security)
- : base (source, security)
- {
- this.security = security;
- }
- public override MessageDirection Direction {
- get { return MessageDirection.Input; }
- }
- public override SecurityMessageProperty RequestSecurity {
- get { return null; }
- }
- public override SecurityTokenParameters Parameters {
- get { return security.RecipientParameters; }
- }
- public override SecurityTokenParameters CounterParameters {
- get { return security.InitiatorParameters; }
- }
- }
- internal class InitiatorSecureMessageDecryptor : SecureMessageDecryptor
- {
- InitiatorMessageSecurityBindingSupport security;
- SecurityMessageProperty request_security;
- public InitiatorSecureMessageDecryptor (
- Message source, SecurityMessageProperty secprop, InitiatorMessageSecurityBindingSupport security)
- : base (source, security)
- {
- this.security = security;
- request_security = secprop;
- }
- public override SecurityMessageProperty RequestSecurity {
- get { return request_security; }
- }
- public override MessageDirection Direction {
- get { return MessageDirection.Output; }
- }
- public override SecurityTokenParameters Parameters {
- get { return security.InitiatorParameters; }
- }
- public override SecurityTokenParameters CounterParameters {
- get { return security.RecipientParameters; }
- }
- }
- internal abstract class SecureMessageDecryptor
- {
- Message source_message;
- MessageBuffer buf;
- MessageSecurityBindingSupport security;
- XmlDocument doc;
- XmlNamespaceManager nsmgr; // for XPath query
- SecurityMessageProperty sec_prop =
- new SecurityMessageProperty ();
- WSSecurityMessageHeader wss_header = null;
- WSSecurityMessageHeaderReader wss_header_reader;
- List<MessageHeaderInfo> headers = new List<MessageHeaderInfo> ();
- SecurityTokenResolver token_resolver;
- List<SecurityToken> tokens;
- protected SecureMessageDecryptor (
- Message source, MessageSecurityBindingSupport security)
- {
- source_message = source;
- this.security = security;
- // FIXME: use proper max buffer
- buf = source.CreateBufferedCopy (int.MaxValue);
- //Console.WriteLine ("####### " + buf.CreateMessage ());
- doc = new XmlDocument ();
- doc.PreserveWhitespace = true;
- nsmgr = new XmlNamespaceManager (doc.NameTable);
- nsmgr.AddNamespace ("s", "http://www.w3.org/2003/05/soap-envelope");
- nsmgr.AddNamespace ("c", Constants.WsscNamespace);
- nsmgr.AddNamespace ("o", Constants.WssNamespace);
- nsmgr.AddNamespace ("e", EncryptedXml.XmlEncNamespaceUrl);
- nsmgr.AddNamespace ("u", Constants.WsuNamespace);
- nsmgr.AddNamespace ("dsig", SignedXml.XmlDsigNamespaceUrl);
- }
- public abstract MessageDirection Direction { get; }
- public abstract SecurityTokenParameters Parameters { get; }
- public abstract SecurityTokenParameters CounterParameters { get; }
- public abstract SecurityMessageProperty RequestSecurity { get; }
- public SecurityTokenResolver TokenResolver {
- get { return token_resolver; }
- }
- public Message DecryptMessage ()
- {
- Message srcmsg = buf.CreateMessage ();
- if (srcmsg.Version.Envelope == EnvelopeVersion.None)
- throw new ArgumentException ("The message to decrypt is not an expected SOAP envelope.");
-
- XPathNavigator nav = doc.CreateNavigator ();
- using (XmlWriter writer = nav.AppendChild ()) {
- buf.CreateMessage ().WriteMessage (writer);
- }
- /*
- doc.PreserveWhitespace = false;
- doc.Save (Console.Out);
- doc.PreserveWhitespace = true;
- */
- // read and store headers, wsse:Security and setup in-band resolver.
- ReadHeaders (srcmsg);
- ExtractSecurity ();
- Message msg = Message.CreateMessage (new XmlNodeReader (doc), srcmsg.Headers.Count, srcmsg.Version);
- for (int i = 0; i < srcmsg.Headers.Count; i++) {
- MessageHeaderInfo header = srcmsg.Headers [i];
- if (header == wss_header) {
- msg.Headers.RemoveAt (i);
- msg.Headers.Add (wss_header);
- }
- }
- // FIXME: when Local[Client|Service]SecuritySettings.DetectReplays
- // is true, reject such messages which don't have <wsu:Timestamp>
- msg.Properties.Add ("Security", sec_prop);
- return msg;
- }
- void ReadHeaders (Message srcmsg)
- {
- SecurityTokenSerializer serializer =
- security.TokenSerializer;
- tokens = new List<SecurityToken> ();
- token_resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver (
- new ReadOnlyCollection <SecurityToken> (tokens),
- true);
- token_resolver = new UnionSecurityTokenResolver (token_resolver, security.OutOfBandTokenResolver);
- // Add relevant protection token and supporting tokens.
- tokens.Add (security.EncryptionToken);
- // FIXME: this is just a workaround for symmetric binding to not require extra client certificate.
- if (security.Element is AsymmetricSecurityBindingElement)
- tokens.Add (security.SigningToken);
- if (RequestSecurity != null && RequestSecurity.ProtectionToken != null)
- tokens.Add (RequestSecurity.ProtectionToken.SecurityToken);
- // FIXME: handle supporting tokens
- for (int i = 0; i < srcmsg.Headers.Count; i++) {
- MessageHeaderInfo header = srcmsg.Headers [i];
- // FIXME: check SOAP Actor.
- // MessageHeaderDescription.Actor needs to be accessible from here.
- if (header.Namespace == Constants.WssNamespace &&
- header.Name == "Security") {
- wss_header = new WSSecurityMessageHeader (null);
- wss_header_reader = new WSSecurityMessageHeaderReader (wss_header, serializer, token_resolver, doc, nsmgr, tokens);
- wss_header_reader.ReadContents (srcmsg.Headers.GetReaderAtHeader (i));
- headers.Add (wss_header);
- }
- else
- headers.Add (header);
- }
- if (wss_header == null)
- throw new InvalidOperationException ("In this service contract, a WS-Security header is required in the Message, but was not found.");
- }
- void ExtractSecurity ()
- {
- if (security.MessageProtectionOrder == MessageProtectionOrder.SignBeforeEncryptAndEncryptSignature &&
- wss_header.Find<SignedXml> () != null)
- throw new MessageSecurityException ("The security binding element expects that the message signature is encrypted, while it isn't.");
- WrappedKeySecurityToken wk = wss_header.Find<WrappedKeySecurityToken> ();
- DerivedKeySecurityToken dk = wss_header.Find<DerivedKeySecurityToken> ();
- if (wk != null) {
- if (Parameters.RequireDerivedKeys && dk == null)
- throw new MessageSecurityException ("DerivedKeyToken is required in this contract, but was not found in the message");
- }
- else
- // FIXME: this is kind of hack for symmetric reply processing.
- wk = RequestSecurity.ProtectionToken != null ? RequestSecurity.ProtectionToken.SecurityToken as WrappedKeySecurityToken : null;
- SymmetricSecurityKey wkkey = wk != null ? wk.SecurityKeys [0] as SymmetricSecurityKey : null;
- wss_header_reader.DecryptSecurity (this, wkkey, RequestSecurity != null ? RequestSecurity.EncryptionKey : null);
- // signature confirmation
- WSSignedXml sxml = wss_header.Find<WSSignedXml> ();
- if (sxml == null)
- throw new MessageSecurityException ("The the message signature is expected but not found.");
- bool confirmed = false;
- SecurityKeyIdentifierClause sigClause = null;
- foreach (KeyInfoClause kic in sxml.KeyInfo) {
- SecurityTokenReferenceKeyInfo r = kic as SecurityTokenReferenceKeyInfo;
- if (r != null)
- sigClause = r.Clause;
- }
- if (sigClause == null)
- throw new MessageSecurityException ("SecurityTokenReference was not found in dsig:Signature KeyInfo.");
- SecurityToken signToken;
- SecurityKey signKey;
- signToken = TokenResolver.ResolveToken (sigClause);
- signKey = signToken.ResolveKeyIdentifierClause (sigClause);
- SymmetricSecurityKey symkey = signKey as SymmetricSecurityKey;
- if (symkey != null) {
- confirmed = sxml.CheckSignature (new HMACSHA1 (symkey.GetSymmetricKey ()));
- if (wk != null)
- // FIXME: authenticate token
- sec_prop.ProtectionToken = new SecurityTokenSpecification (wk, null);
- } else {
- AsymmetricAlgorithm alg = ((AsymmetricSecurityKey) signKey).GetAsymmetricAlgorithm (security.DefaultSignatureAlgorithm, false);
- confirmed = sxml.CheckSignature (alg);
- sec_prop.InitiatorToken = new SecurityTokenSpecification (
- signToken,
- security.TokenAuthenticator.ValidateToken (signToken));
- }
- if (!confirmed)
- throw new MessageSecurityException ("Message signature is invalid.");
- // token authentication
- // FIXME: it might not be limited to recipient
- if (Direction == MessageDirection.Input)
- ProcessSupportingTokens (sxml);
- sec_prop.EncryptionKey = ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
- sec_prop.ConfirmedSignatures.Add (Convert.ToBase64String (sxml.SignatureValue));
- }
- #region supporting token processing
- // authenticate and map supporting tokens to proper SupportingTokenSpecification list.
- void ProcessSupportingTokens (SignedXml sxml)
- {
- List<SupportingTokenInfo> tokens = new List<SupportingTokenInfo> ();
-
- // First, categorize those tokens in the Security
- // header:
- // - Endorsing signing
- // - Signed signed
- // - SignedEncrypted signed encrypted
- // - SignedEndorsing signing signed
- foreach (object obj in wss_header.Contents) {
- SecurityToken token = obj as SecurityToken;
- if (token == null)
- continue;
- bool signed = false, endorsing = false, encrypted = false;
- // signed
- foreach (Reference r in sxml.SignedInfo.References)
- if (r.Uri.Substring (1) == token.Id) {
- signed = true;
- break;
- }
- // FIXME: how to get 'encrypted' state?
- // FIXME: endorsing
- SecurityTokenAttachmentMode mode =
- signed ? encrypted ? SecurityTokenAttachmentMode.SignedEncrypted :
- endorsing ? SecurityTokenAttachmentMode.SignedEndorsing :
- SecurityTokenAttachmentMode.Signed :
- SecurityTokenAttachmentMode.Endorsing;
- tokens.Add (new SupportingTokenInfo (token, mode, false));
- }
- // then,
- // 1. validate every mandatory supporting token
- // parameters (Endpoint-, Operation-). To do that,
- // iterate all tokens in the header against every
- // parameter in the mandatory list.
- // 2. validate every token that is not validated.
- // To do that, iterate all supporting token parameters
- // and check if any of them can validate it.
- SupportingTokenParameters supp;
- string action = GetAction ();
- if (action == null)
- throw new ArgumentException ("SOAP action could not be retrieved from the message to decrypt.");
- ValidateTokensByParameters (security.Element.EndpointSupportingTokenParameters, tokens, false);
- if (security.Element.OperationSupportingTokenParameters.TryGetValue (action, out supp))
- ValidateTokensByParameters (supp, tokens, false);
- ValidateTokensByParameters (security.Element.OptionalEndpointSupportingTokenParameters, tokens, true);
- if (security.Element.OptionalOperationSupportingTokenParameters.TryGetValue (action, out supp))
- ValidateTokensByParameters (supp, tokens, true);
- }
- void ValidateTokensByParameters (SupportingTokenParameters supp, List<SupportingTokenInfo> tokens, bool optional)
- {
- ValidateTokensByParameters (supp.Endorsing, tokens, optional, SecurityTokenAttachmentMode.Endorsing);
- ValidateTokensByParameters (supp.Signed, tokens, optional, SecurityTokenAttachmentMode.Signed);
- ValidateTokensByParameters (supp.SignedEndorsing, tokens, optional, SecurityTokenAttachmentMode.SignedEndorsing);
- ValidateTokensByParameters (supp.SignedEncrypted, tokens, optional, SecurityTokenAttachmentMode.SignedEncrypted);
- }
- void ValidateTokensByParameters (IEnumerable<SecurityTokenParameters> plist, List<SupportingTokenInfo> tokens, bool optional, SecurityTokenAttachmentMode attachMode)
- {
- foreach (SecurityTokenParameters p in plist) {
- SecurityTokenResolver r;
- SecurityTokenAuthenticator a =
- security.CreateTokenAuthenticator (p, out r);
- SupportingTokenSpecification spec = ValidateTokensByParameters (a, r, tokens);
- if (spec == null) {
- if (optional)
- continue;
- else
- throw new MessageSecurityException (String.Format ("No security token could be validated for authenticator '{0}' which is indicated by the '{1}' supporting token parameters", a, attachMode));
- } else {
- // For endorsing tokens, verify corresponding signatures.
- switch (attachMode) {
- case SecurityTokenAttachmentMode.Endorsing:
- case SecurityTokenAttachmentMode.SignedEndorsing:
- WSSignedXml esxml = GetSignatureForToken (spec.SecurityToken);
- if (esxml == null)
- throw new MessageSecurityException (String.Format ("The '{1}' token '{0}' is expected to endorse the primary signature but no corresponding signature is found.", spec.SecurityToken, attachMode));
- bool confirmed;
- SecurityAlgorithmSuite suite = security.Element.DefaultAlgorithmSuite;
- foreach (SecurityTokenReferenceKeyInfo kic in esxml.KeyInfo) {
- SecurityKey signKey = spec.SecurityToken.ResolveKeyIdentifierClause (kic.Clause);
- SymmetricSecurityKey symkey = signKey as SymmetricSecurityKey;
- if (symkey != null) {
- confirmed = esxml.CheckSignature (symkey.GetKeyedHashAlgorithm (suite.DefaultSymmetricSignatureAlgorithm));
- } else {
- AsymmetricAlgorithm alg = ((AsymmetricSecurityKey) signKey).GetAsymmetricAlgorithm (suite.DefaultAsymmetricSignatureAlgorithm, false);
- confirmed = esxml.CheckSignature (alg);
- }
- if (!confirmed)
- throw new MessageSecurityException (String.Format ("Signature for '{1}' token '{0}' is invalid.", spec.SecurityToken, attachMode));
- break;
- }
- sec_prop.ConfirmedSignatures.Insert (0, Convert.ToBase64String (esxml.SignatureValue));
- break;
- }
- }
- sec_prop.IncomingSupportingTokens.Add (spec);
- }
- }
- WSSignedXml GetSignatureForToken (SecurityToken token)
- {
- int count = 0;
- foreach (WSSignedXml sxml in wss_header.FindAll<WSSignedXml> ()) {
- if (count++ == 0)
- continue; // primary signature
- foreach (SecurityTokenReferenceKeyInfo r in sxml.KeyInfo)
- if (token.MatchesKeyIdentifierClause (r.Clause))
- return sxml;
- }
- return null;
- }
- SupportingTokenSpecification ValidateTokensByParameters (SecurityTokenAuthenticator a, SecurityTokenResolver r, List<SupportingTokenInfo> tokens)
- {
- foreach (SupportingTokenInfo info in tokens)
- if (a.CanValidateToken (info.Token))
- return new SupportingTokenSpecification (
- info.Token,
- a.ValidateToken (info.Token),
- info.Mode);
- return null;
- }
- #endregion
- string GetAction ()
- {
- string ret = source_message.Headers.Action;
- if (ret == null) {
- HttpRequestMessageProperty reqprop =
- source_message.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
- if (reqprop != null)
- ret = reqprop.Headers ["Action"];
- }
- return ret;
- }
- }
- }
|