2 // SecureMessageDecryptor.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections.Generic;
31 using System.Collections.ObjectModel;
32 using System.Globalization;
33 using System.IdentityModel.Selectors;
34 using System.IdentityModel.Tokens;
35 using System.Runtime.Serialization;
36 using System.Security.Cryptography;
37 using System.Security.Cryptography.X509Certificates;
38 using System.Security.Cryptography.Xml;
39 using System.ServiceModel;
40 using System.ServiceModel.Channels;
41 using System.ServiceModel.Description;
42 using System.ServiceModel.Security;
43 using System.ServiceModel.Security.Tokens;
46 using System.Xml.XPath;
48 using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement;
50 namespace System.ServiceModel.Channels.Security
52 internal class RecipientSecureMessageDecryptor : SecureMessageDecryptor
54 RecipientMessageSecurityBindingSupport security;
56 public RecipientSecureMessageDecryptor (
57 Message source, RecipientMessageSecurityBindingSupport security)
58 : base (source, security)
60 this.security = security;
63 public override MessageDirection Direction {
64 get { return MessageDirection.Input; }
67 public override SecurityMessageProperty RequestSecurity {
71 public override SecurityTokenParameters Parameters {
72 get { return security.RecipientParameters; }
75 public override SecurityTokenParameters CounterParameters {
76 get { return security.InitiatorParameters; }
80 internal class InitiatorSecureMessageDecryptor : SecureMessageDecryptor
82 InitiatorMessageSecurityBindingSupport security;
83 SecurityMessageProperty request_security;
85 public InitiatorSecureMessageDecryptor (
86 Message source, SecurityMessageProperty secprop, InitiatorMessageSecurityBindingSupport security)
87 : base (source, security)
89 this.security = security;
90 request_security = secprop;
93 public override SecurityMessageProperty RequestSecurity {
94 get { return request_security; }
97 public override MessageDirection Direction {
98 get { return MessageDirection.Output; }
101 public override SecurityTokenParameters Parameters {
102 get { return security.InitiatorParameters; }
105 public override SecurityTokenParameters CounterParameters {
106 get { return security.RecipientParameters; }
110 internal abstract class SecureMessageDecryptor
112 Message source_message;
114 MessageSecurityBindingSupport security;
117 XmlNamespaceManager nsmgr; // for XPath query
119 SecurityMessageProperty sec_prop =
120 new SecurityMessageProperty ();
121 WSSecurityMessageHeader wss_header = null;
122 WSSecurityMessageHeaderReader wss_header_reader;
123 List<MessageHeaderInfo> headers = new List<MessageHeaderInfo> ();
124 SecurityTokenResolver token_resolver;
125 List<SecurityToken> tokens;
127 protected SecureMessageDecryptor (
128 Message source, MessageSecurityBindingSupport security)
130 source_message = source;
131 this.security = security;
133 // FIXME: use proper max buffer
134 buf = source.CreateBufferedCopy (int.MaxValue);
136 doc = new XmlDocument ();
137 doc.PreserveWhitespace = true;
139 nsmgr = new XmlNamespaceManager (doc.NameTable);
140 nsmgr.AddNamespace ("s", "http://www.w3.org/2003/05/soap-envelope");
141 nsmgr.AddNamespace ("c", Constants.WsscNamespace);
142 nsmgr.AddNamespace ("o", Constants.WssNamespace);
143 nsmgr.AddNamespace ("e", EncryptedXml.XmlEncNamespaceUrl);
144 nsmgr.AddNamespace ("u", Constants.WsuNamespace);
145 nsmgr.AddNamespace ("dsig", SignedXml.XmlDsigNamespaceUrl);
149 public abstract MessageDirection Direction { get; }
150 public abstract SecurityTokenParameters Parameters { get; }
151 public abstract SecurityTokenParameters CounterParameters { get; }
152 public abstract SecurityMessageProperty RequestSecurity { get; }
154 public SecurityTokenResolver TokenResolver {
155 get { return token_resolver; }
158 public Message DecryptMessage ()
160 Message srcmsg = buf.CreateMessage ();
161 if (srcmsg.Version.Envelope == EnvelopeVersion.None)
162 throw new ArgumentException ("The message to decrypt is not an expected SOAP envelope.");
164 string action = GetAction ();
166 throw new ArgumentException ("SOAP action could not be retrieved from the message to decrypt.");
168 XPathNavigator nav = doc.CreateNavigator ();
169 using (XmlWriter writer = nav.AppendChild ()) {
170 buf.CreateMessage ().WriteMessage (writer);
173 doc.PreserveWhitespace = false;
174 doc.Save (Console.Out);
175 doc.PreserveWhitespace = true;
178 // read and store headers, wsse:Security and setup in-band resolver.
179 ReadHeaders (srcmsg);
183 Message msg = Message.CreateMessage (new XmlNodeReader (doc), srcmsg.Headers.Count, srcmsg.Version);
184 for (int i = 0; i < srcmsg.Headers.Count; i++) {
185 MessageHeaderInfo header = srcmsg.Headers [i];
186 if (header == wss_header) {
187 msg.Headers.RemoveAt (i);
188 msg.Headers.Add (wss_header);
192 // FIXME: when Local[Client|Service]SecuritySettings.DetectReplays
193 // is true, reject such messages which don't have <wsu:Timestamp>
195 msg.Properties.Add ("Security", sec_prop);
200 void ReadHeaders (Message srcmsg)
202 SecurityTokenSerializer serializer =
203 security.TokenSerializer;
205 tokens = new List<SecurityToken> ();
206 token_resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver (
207 new ReadOnlyCollection <SecurityToken> (tokens),
209 token_resolver = new UnionSecurityTokenResolver (token_resolver, security.OutOfBandTokenResolver);
211 // Add relevant protection token and supporting tokens.
212 tokens.Add (security.EncryptionToken);
213 // FIXME: this is just a workaround for symmetric binding to not require extra client certificate.
214 if (security.Element is AsymmetricSecurityBindingElement)
215 tokens.Add (security.SigningToken);
216 if (RequestSecurity != null && RequestSecurity.ProtectionToken != null)
217 tokens.Add (RequestSecurity.ProtectionToken.SecurityToken);
218 // FIXME: handle supporting tokens
220 for (int i = 0; i < srcmsg.Headers.Count; i++) {
221 MessageHeaderInfo header = srcmsg.Headers [i];
222 // FIXME: check SOAP Actor.
223 // MessageHeaderDescription.Actor needs to be accessible from here.
224 if (header.Namespace == Constants.WssNamespace &&
225 header.Name == "Security") {
226 wss_header = new WSSecurityMessageHeader (null);
227 wss_header_reader = new WSSecurityMessageHeaderReader (wss_header, serializer, token_resolver, doc, nsmgr, tokens);
228 wss_header_reader.ReadContents (srcmsg.Headers.GetReaderAtHeader (i));
229 headers.Add (wss_header);
232 headers.Add (header);
234 if (wss_header == null)
235 throw new InvalidOperationException ("In this service contract, a WS-Security header is required in the Message, but was not found.");
238 void ExtractSecurity ()
240 if (security.MessageProtectionOrder == MessageProtectionOrder.SignBeforeEncryptAndEncryptSignature &&
241 wss_header.Find<SignedXml> () != null)
242 throw new MessageSecurityException ("The security binding element expects that the message signature is encrypted, while it isn't.");
244 WrappedKeySecurityToken wk = wss_header.Find<WrappedKeySecurityToken> ();
245 DerivedKeySecurityToken dk = wss_header.Find<DerivedKeySecurityToken> ();
247 if (Parameters.RequireDerivedKeys && dk == null)
248 throw new MessageSecurityException ("DerivedKeyToken is required in this contract, but was not found in the message");
251 // FIXME: this is kind of hack for symmetric reply processing.
252 wk = RequestSecurity.ProtectionToken != null ? RequestSecurity.ProtectionToken.SecurityToken as WrappedKeySecurityToken : null;
254 SymmetricSecurityKey wkkey = wk != null ? wk.SecurityKeys [0] as SymmetricSecurityKey : null;
256 wss_header_reader.DecryptSecurity (this, wkkey, RequestSecurity != null ? RequestSecurity.EncryptionKey : null);
258 // signature confirmation
259 WSSignedXml sxml = wss_header.Find<WSSignedXml> ();
261 throw new MessageSecurityException ("The the message signature is expected but not found.");
263 bool confirmed = false;
265 SecurityKeyIdentifierClause sigClause = null;
266 foreach (KeyInfoClause kic in sxml.KeyInfo) {
267 SecurityTokenReferenceKeyInfo r = kic as SecurityTokenReferenceKeyInfo;
269 sigClause = r.Clause;
271 if (sigClause == null)
272 throw new MessageSecurityException ("SecurityTokenReference was not found in dsig:Signature KeyInfo.");
274 SecurityToken signToken;
277 signToken = TokenResolver.ResolveToken (sigClause);
278 signKey = signToken.ResolveKeyIdentifierClause (sigClause);
279 SymmetricSecurityKey symkey = signKey as SymmetricSecurityKey;
280 if (symkey != null) {
281 confirmed = sxml.CheckSignature (new HMACSHA1 (symkey.GetSymmetricKey ()));
283 // FIXME: authenticate token
284 sec_prop.ProtectionToken = new SecurityTokenSpecification (wk, null);
286 AsymmetricAlgorithm alg = ((AsymmetricSecurityKey) signKey).GetAsymmetricAlgorithm (security.DefaultSignatureAlgorithm, false);
287 confirmed = sxml.CheckSignature (alg);
288 sec_prop.InitiatorToken = new SecurityTokenSpecification (
290 security.TokenAuthenticator.ValidateToken (signToken));
293 throw new MessageSecurityException ("Message signature is invalid.");
295 // token authentication
296 // FIXME: it might not be limited to recipient
297 if (Direction == MessageDirection.Input)
298 ProcessSupportingTokens (sxml);
300 sec_prop.EncryptionKey = ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
301 sec_prop.ConfirmedSignatures.Add (Convert.ToBase64String (sxml.SignatureValue));
304 #region supporting token processing
306 // authenticate and map supporting tokens to proper SupportingTokenSpecification list.
307 void ProcessSupportingTokens (SignedXml sxml)
309 List<SupportingTokenInfo> tokens = new List<SupportingTokenInfo> ();
311 // First, categorize those tokens in the Security
313 // - Endorsing signing
315 // - SignedEncrypted signed encrypted
316 // - SignedEndorsing signing signed
318 foreach (object obj in wss_header.Contents) {
319 SecurityToken token = obj as SecurityToken;
322 bool signed = false, endorsing = false, encrypted = false;
324 foreach (Reference r in sxml.SignedInfo.References)
325 if (r.Uri.Substring (1) == token.Id) {
329 // FIXME: how to get 'encrypted' state?
332 SecurityTokenAttachmentMode mode =
333 signed ? encrypted ? SecurityTokenAttachmentMode.SignedEncrypted :
334 endorsing ? SecurityTokenAttachmentMode.SignedEndorsing :
335 SecurityTokenAttachmentMode.Signed :
336 SecurityTokenAttachmentMode.Endorsing;
337 tokens.Add (new SupportingTokenInfo (token, mode, false));
341 // 1. validate every mandatory supporting token
342 // parameters (Endpoint-, Operation-). To do that,
343 // iterate all tokens in the header against every
344 // parameter in the mandatory list.
345 // 2. validate every token that is not validated.
346 // To do that, iterate all supporting token parameters
347 // and check if any of them can validate it.
348 SupportingTokenParameters supp;
349 string action = GetAction ();
350 ValidateTokensByParameters (security.Element.EndpointSupportingTokenParameters, tokens, false);
351 if (security.Element.OperationSupportingTokenParameters.TryGetValue (action, out supp))
352 ValidateTokensByParameters (supp, tokens, false);
353 ValidateTokensByParameters (security.Element.OptionalEndpointSupportingTokenParameters, tokens, true);
354 if (security.Element.OptionalOperationSupportingTokenParameters.TryGetValue (action, out supp))
355 ValidateTokensByParameters (supp, tokens, true);
358 void ValidateTokensByParameters (SupportingTokenParameters supp, List<SupportingTokenInfo> tokens, bool optional)
360 ValidateTokensByParameters (supp.Endorsing, tokens, optional, SecurityTokenAttachmentMode.Endorsing);
361 ValidateTokensByParameters (supp.Signed, tokens, optional, SecurityTokenAttachmentMode.Signed);
362 ValidateTokensByParameters (supp.SignedEndorsing, tokens, optional, SecurityTokenAttachmentMode.SignedEndorsing);
363 ValidateTokensByParameters (supp.SignedEncrypted, tokens, optional, SecurityTokenAttachmentMode.SignedEncrypted);
366 void ValidateTokensByParameters (IEnumerable<SecurityTokenParameters> plist, List<SupportingTokenInfo> tokens, bool optional, SecurityTokenAttachmentMode attachMode)
368 foreach (SecurityTokenParameters p in plist) {
369 SecurityTokenResolver r;
370 SecurityTokenAuthenticator a =
371 security.CreateTokenAuthenticator (p, out r);
372 SupportingTokenSpecification spec = ValidateTokensByParameters (a, r, tokens);
377 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));
379 // For endorsing tokens, verify corresponding signatures.
380 switch (attachMode) {
381 case SecurityTokenAttachmentMode.Endorsing:
382 case SecurityTokenAttachmentMode.SignedEndorsing:
383 WSSignedXml esxml = GetSignatureForToken (spec.SecurityToken);
385 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));
388 SecurityAlgorithmSuite suite = security.Element.DefaultAlgorithmSuite;
389 foreach (SecurityTokenReferenceKeyInfo kic in esxml.KeyInfo) {
390 SecurityKey signKey = spec.SecurityToken.ResolveKeyIdentifierClause (kic.Clause);
391 SymmetricSecurityKey symkey = signKey as SymmetricSecurityKey;
392 if (symkey != null) {
393 confirmed = esxml.CheckSignature (symkey.GetKeyedHashAlgorithm (suite.DefaultSymmetricSignatureAlgorithm));
395 AsymmetricAlgorithm alg = ((AsymmetricSecurityKey) signKey).GetAsymmetricAlgorithm (suite.DefaultAsymmetricSignatureAlgorithm, false);
396 confirmed = esxml.CheckSignature (alg);
399 throw new MessageSecurityException (String.Format ("Signature for '{1}' token '{0}' is invalid.", spec.SecurityToken, attachMode));
403 sec_prop.ConfirmedSignatures.Insert (0, Convert.ToBase64String (esxml.SignatureValue));
408 sec_prop.IncomingSupportingTokens.Add (spec);
412 WSSignedXml GetSignatureForToken (SecurityToken token)
415 foreach (WSSignedXml sxml in wss_header.FindAll<WSSignedXml> ()) {
417 continue; // primary signature
418 foreach (SecurityTokenReferenceKeyInfo r in sxml.KeyInfo)
419 if (token.MatchesKeyIdentifierClause (r.Clause))
425 SupportingTokenSpecification ValidateTokensByParameters (SecurityTokenAuthenticator a, SecurityTokenResolver r, List<SupportingTokenInfo> tokens)
427 foreach (SupportingTokenInfo info in tokens)
428 if (a.CanValidateToken (info.Token))
429 return new SupportingTokenSpecification (
431 a.ValidateToken (info.Token),
440 string ret = source_message.Headers.Action;
442 HttpRequestMessageProperty reqprop =
443 source_message.Properties ["Action"] as HttpRequestMessageProperty;
445 ret = reqprop.Headers ["Action"];