Merge remote branch 'upstream/master'
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels.Security / SecureMessageDecryptor.cs
1 //
2 // SecureMessageDecryptor.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28
29 using System;
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;
44 using System.Text;
45 using System.Xml;
46 using System.Xml.XPath;
47
48 using ReqType = System.ServiceModel.Security.Tokens.ServiceModelSecurityTokenRequirement;
49
50 namespace System.ServiceModel.Channels.Security
51 {
52         internal class RecipientSecureMessageDecryptor : SecureMessageDecryptor
53         {
54                 RecipientMessageSecurityBindingSupport security;
55
56                 public RecipientSecureMessageDecryptor (
57                         Message source, RecipientMessageSecurityBindingSupport security)
58                         : base (source, security)
59                 {
60                         this.security = security;
61                 }
62
63                 public override MessageDirection Direction {
64                         get { return MessageDirection.Input; }
65                 }
66
67                 public override SecurityMessageProperty RequestSecurity {
68                         get { return null; }
69                 }
70
71                 public override SecurityTokenParameters Parameters {
72                         get { return security.RecipientParameters; }
73                 }
74
75                 public override SecurityTokenParameters CounterParameters {
76                         get { return security.InitiatorParameters; }
77                 }
78         }
79
80         internal class InitiatorSecureMessageDecryptor : SecureMessageDecryptor
81         {
82                 InitiatorMessageSecurityBindingSupport security;
83                 SecurityMessageProperty request_security;
84
85                 public InitiatorSecureMessageDecryptor (
86                         Message source, SecurityMessageProperty secprop, InitiatorMessageSecurityBindingSupport security)
87                         : base (source, security)
88                 {
89                         this.security = security;
90                         request_security = secprop;
91                 }
92
93                 public override SecurityMessageProperty RequestSecurity {
94                         get { return request_security; }
95                 }
96
97                 public override MessageDirection Direction {
98                         get { return MessageDirection.Output; }
99                 }
100
101                 public override SecurityTokenParameters Parameters {
102                         get { return security.InitiatorParameters; }
103                 }
104
105                 public override SecurityTokenParameters CounterParameters {
106                         get { return security.RecipientParameters; }
107                 }
108         }
109
110         internal abstract class SecureMessageDecryptor
111         {
112                 Message source_message;
113                 MessageBuffer buf;
114                 MessageSecurityBindingSupport security;
115
116                 XmlDocument doc;
117                 XmlNamespaceManager nsmgr; // for XPath query
118
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;
126
127                 protected SecureMessageDecryptor (
128                         Message source, MessageSecurityBindingSupport security)
129                 {
130                         source_message = source;
131                         this.security = security;
132
133                         // FIXME: use proper max buffer
134                         buf = source.CreateBufferedCopy (int.MaxValue);
135 Console.WriteLine ("####### " + buf.CreateMessage ());
136
137                         doc = new XmlDocument ();
138                         doc.PreserveWhitespace = true;
139
140                         nsmgr = new XmlNamespaceManager (doc.NameTable);
141                         nsmgr.AddNamespace ("s", "http://www.w3.org/2003/05/soap-envelope");
142                         nsmgr.AddNamespace ("c", Constants.WsscNamespace);
143                         nsmgr.AddNamespace ("o", Constants.WssNamespace);
144                         nsmgr.AddNamespace ("e", EncryptedXml.XmlEncNamespaceUrl);
145                         nsmgr.AddNamespace ("u", Constants.WsuNamespace);
146                         nsmgr.AddNamespace ("dsig", SignedXml.XmlDsigNamespaceUrl);
147
148                 }
149
150                 public abstract MessageDirection Direction { get; }
151                 public abstract SecurityTokenParameters Parameters { get; }
152                 public abstract SecurityTokenParameters CounterParameters { get; }
153                 public abstract SecurityMessageProperty RequestSecurity { get; }
154
155                 public SecurityTokenResolver TokenResolver {
156                         get { return token_resolver; }
157                 }
158
159                 public Message DecryptMessage ()
160                 {
161                         Message srcmsg = buf.CreateMessage ();
162                         if (srcmsg.Version.Envelope == EnvelopeVersion.None)
163                                 throw new ArgumentException ("The message to decrypt is not an expected SOAP envelope.");
164
165                         string action = GetAction ();
166                         if (action == null)
167                                 throw new ArgumentException ("SOAP action could not be retrieved from the message to decrypt.");
168
169                         XPathNavigator nav = doc.CreateNavigator ();
170                         using (XmlWriter writer = nav.AppendChild ()) {
171                                 buf.CreateMessage ().WriteMessage (writer);
172                         }
173 /*
174 doc.PreserveWhitespace = false;
175 doc.Save (Console.Out);
176 doc.PreserveWhitespace = true;
177 */
178
179                         // read and store headers, wsse:Security and setup in-band resolver.
180                         ReadHeaders (srcmsg);
181
182                         ExtractSecurity ();
183
184                         Message msg = Message.CreateMessage (new XmlNodeReader (doc), srcmsg.Headers.Count, srcmsg.Version);
185                         for (int i = 0; i < srcmsg.Headers.Count; i++) {
186                                 MessageHeaderInfo header = srcmsg.Headers [i];
187                                 if (header == wss_header) {
188                                         msg.Headers.RemoveAt (i);
189                                         msg.Headers.Add (wss_header);
190                                 }
191                         }
192
193                         // FIXME: when Local[Client|Service]SecuritySettings.DetectReplays
194                         // is true, reject such messages which don't have <wsu:Timestamp>
195
196                         msg.Properties.Add ("Security", sec_prop);
197
198                         return msg;
199                 }
200
201                 void ReadHeaders (Message srcmsg)
202                 {
203                         SecurityTokenSerializer serializer =
204                                 security.TokenSerializer;
205
206                         tokens = new List<SecurityToken> ();
207                         token_resolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver (
208                                 new ReadOnlyCollection <SecurityToken> (tokens),
209                                 true);
210                         token_resolver = new UnionSecurityTokenResolver (token_resolver, security.OutOfBandTokenResolver);
211
212                         // Add relevant protection token and supporting tokens.
213                         tokens.Add (security.EncryptionToken);
214                         // FIXME: this is just a workaround for symmetric binding to not require extra client certificate.
215                         if (security.Element is AsymmetricSecurityBindingElement)
216                                 tokens.Add (security.SigningToken);
217                         if (RequestSecurity != null && RequestSecurity.ProtectionToken != null)
218                                 tokens.Add (RequestSecurity.ProtectionToken.SecurityToken);
219                         // FIXME: handle supporting tokens
220
221                         for (int i = 0; i < srcmsg.Headers.Count; i++) {
222                                 MessageHeaderInfo header = srcmsg.Headers [i];
223                                 // FIXME: check SOAP Actor.
224                                 // MessageHeaderDescription.Actor needs to be accessible from here.
225                                 if (header.Namespace == Constants.WssNamespace &&
226                                     header.Name == "Security") {
227                                         wss_header = new WSSecurityMessageHeader (null);
228                                         wss_header_reader = new WSSecurityMessageHeaderReader (wss_header, serializer, token_resolver, doc, nsmgr, tokens);
229                                         wss_header_reader.ReadContents (srcmsg.Headers.GetReaderAtHeader (i));
230                                         headers.Add (wss_header);
231                                 }
232                                 else
233                                         headers.Add (header);
234                         }
235                         if (wss_header == null)
236                                 throw new InvalidOperationException ("In this service contract, a WS-Security header is required in the Message, but was not found.");
237                 }
238
239                 void ExtractSecurity ()
240                 {
241                         if (security.MessageProtectionOrder == MessageProtectionOrder.SignBeforeEncryptAndEncryptSignature &&
242                             wss_header.Find<SignedXml> () != null)
243                                 throw new MessageSecurityException ("The security binding element expects that the message signature is encrypted, while it isn't.");
244
245                         WrappedKeySecurityToken wk = wss_header.Find<WrappedKeySecurityToken> ();
246                         DerivedKeySecurityToken dk = wss_header.Find<DerivedKeySecurityToken> ();
247                         if (wk != null) {
248                                 if (Parameters.RequireDerivedKeys && dk == null)
249                                         throw new MessageSecurityException ("DerivedKeyToken is required in this contract, but was not found in the message");
250                         }
251                         else
252                                 // FIXME: this is kind of hack for symmetric reply processing.
253                                 wk = RequestSecurity.ProtectionToken != null ? RequestSecurity.ProtectionToken.SecurityToken as WrappedKeySecurityToken : null;
254
255                         SymmetricSecurityKey wkkey = wk != null ? wk.SecurityKeys [0] as SymmetricSecurityKey : null;
256
257                         wss_header_reader.DecryptSecurity (this, wkkey, RequestSecurity != null ? RequestSecurity.EncryptionKey : null);
258
259                         // signature confirmation
260                         WSSignedXml sxml = wss_header.Find<WSSignedXml> ();
261                         if (sxml == null)
262                                 throw new MessageSecurityException ("The the message signature is expected but not found.");
263
264                         bool confirmed = false;
265
266                         SecurityKeyIdentifierClause sigClause = null;
267                         foreach (KeyInfoClause kic in sxml.KeyInfo) {
268                                 SecurityTokenReferenceKeyInfo r = kic as SecurityTokenReferenceKeyInfo;
269                                 if (r != null)
270                                         sigClause = r.Clause;
271                         }
272                         if (sigClause == null)
273                                 throw new MessageSecurityException ("SecurityTokenReference was not found in dsig:Signature KeyInfo.");
274
275                         SecurityToken signToken;
276                         SecurityKey signKey;
277
278                         signToken = TokenResolver.ResolveToken (sigClause);
279                         signKey = signToken.ResolveKeyIdentifierClause (sigClause);
280                         SymmetricSecurityKey symkey = signKey as SymmetricSecurityKey;
281                         if (symkey != null) {
282                                 confirmed = sxml.CheckSignature (new HMACSHA1 (symkey.GetSymmetricKey ()));
283                                 if (wk != null)
284                                         // FIXME: authenticate token
285                                         sec_prop.ProtectionToken = new SecurityTokenSpecification (wk, null);
286                         } else {
287                                 AsymmetricAlgorithm alg = ((AsymmetricSecurityKey) signKey).GetAsymmetricAlgorithm (security.DefaultSignatureAlgorithm, false);
288                                 confirmed = sxml.CheckSignature (alg);
289                                 sec_prop.InitiatorToken = new SecurityTokenSpecification (
290                                         signToken,
291                                         security.TokenAuthenticator.ValidateToken (signToken));
292                         }
293                         if (!confirmed)
294                                 throw new MessageSecurityException ("Message signature is invalid.");
295
296                         // token authentication
297                         // FIXME: it might not be limited to recipient
298                         if (Direction == MessageDirection.Input)
299                                 ProcessSupportingTokens (sxml);
300
301                         sec_prop.EncryptionKey = ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
302                         sec_prop.ConfirmedSignatures.Add (Convert.ToBase64String (sxml.SignatureValue));
303                 }
304
305                 #region supporting token processing
306
307                 // authenticate and map supporting tokens to proper SupportingTokenSpecification list.
308                 void ProcessSupportingTokens (SignedXml sxml)
309                 {
310                         List<SupportingTokenInfo> tokens = new List<SupportingTokenInfo> ();
311                 
312                         // First, categorize those tokens in the Security
313                         // header:
314                         // - Endorsing          signing
315                         // - Signed                     signed
316                         // - SignedEncrypted            signed  encrypted
317                         // - SignedEndorsing    signing signed
318
319                         foreach (object obj in wss_header.Contents) {
320                                 SecurityToken token = obj as SecurityToken;
321                                 if (token == null)
322                                         continue;
323                                 bool signed = false, endorsing = false, encrypted = false;
324                                 // signed
325                                 foreach (Reference r in sxml.SignedInfo.References)
326                                         if (r.Uri.Substring (1) == token.Id) {
327                                                 signed = true;
328                                                 break;
329                                         }
330                                 // FIXME: how to get 'encrypted' state?
331                                 // FIXME: endorsing
332
333                                 SecurityTokenAttachmentMode mode =
334                                         signed ? encrypted ? SecurityTokenAttachmentMode.SignedEncrypted :
335                                         endorsing ? SecurityTokenAttachmentMode.SignedEndorsing :
336                                         SecurityTokenAttachmentMode.Signed :
337                                         SecurityTokenAttachmentMode.Endorsing;
338                                 tokens.Add (new SupportingTokenInfo (token, mode, false));
339                         }
340
341                         // then,
342                         // 1. validate every mandatory supporting token
343                         // parameters (Endpoint-, Operation-). To do that,
344                         // iterate all tokens in the header against every
345                         // parameter in the mandatory list.
346                         // 2. validate every token that is not validated.
347                         // To do that, iterate all supporting token parameters
348                         // and check if any of them can validate it.
349                         SupportingTokenParameters supp;
350                         string action = GetAction ();
351                         ValidateTokensByParameters (security.Element.EndpointSupportingTokenParameters, tokens, false);
352                         if (security.Element.OperationSupportingTokenParameters.TryGetValue (action, out supp))
353                                 ValidateTokensByParameters (supp, tokens, false);
354                         ValidateTokensByParameters (security.Element.OptionalEndpointSupportingTokenParameters, tokens, true);
355                         if (security.Element.OptionalOperationSupportingTokenParameters.TryGetValue (action, out supp))
356                                 ValidateTokensByParameters (supp, tokens, true);
357                 }
358
359                 void ValidateTokensByParameters (SupportingTokenParameters supp, List<SupportingTokenInfo> tokens, bool optional)
360                 {
361                         ValidateTokensByParameters (supp.Endorsing, tokens, optional, SecurityTokenAttachmentMode.Endorsing);
362                         ValidateTokensByParameters (supp.Signed, tokens, optional, SecurityTokenAttachmentMode.Signed);
363                         ValidateTokensByParameters (supp.SignedEndorsing, tokens, optional, SecurityTokenAttachmentMode.SignedEndorsing);
364                         ValidateTokensByParameters (supp.SignedEncrypted, tokens, optional, SecurityTokenAttachmentMode.SignedEncrypted);
365                 }
366
367                 void ValidateTokensByParameters (IEnumerable<SecurityTokenParameters> plist, List<SupportingTokenInfo> tokens, bool optional, SecurityTokenAttachmentMode attachMode)
368                 {
369                         foreach (SecurityTokenParameters p in plist) {
370                                 SecurityTokenResolver r;
371                                 SecurityTokenAuthenticator a =
372                                         security.CreateTokenAuthenticator (p, out r);
373                                 SupportingTokenSpecification spec = ValidateTokensByParameters (a, r, tokens);
374                                 if (spec == null) {
375                                         if (optional)
376                                                 continue;
377                                         else
378                                                 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                                 } else {
380                                         // For endorsing tokens, verify corresponding signatures.
381                                         switch (attachMode) {
382                                         case SecurityTokenAttachmentMode.Endorsing:
383                                         case SecurityTokenAttachmentMode.SignedEndorsing:
384                                                 WSSignedXml esxml = GetSignatureForToken (spec.SecurityToken);
385                                                 if (esxml == null)
386                                                         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));
387
388                                                 bool confirmed;
389                                                 SecurityAlgorithmSuite suite = security.Element.DefaultAlgorithmSuite;
390                                                 foreach (SecurityTokenReferenceKeyInfo kic in esxml.KeyInfo) {
391                                                         SecurityKey signKey = spec.SecurityToken.ResolveKeyIdentifierClause (kic.Clause);
392                                                         SymmetricSecurityKey symkey = signKey as SymmetricSecurityKey;
393                                                         if (symkey != null) {
394                                                                 confirmed = esxml.CheckSignature (symkey.GetKeyedHashAlgorithm (suite.DefaultSymmetricSignatureAlgorithm));
395                                                         } else {
396                                                                 AsymmetricAlgorithm alg = ((AsymmetricSecurityKey) signKey).GetAsymmetricAlgorithm (suite.DefaultAsymmetricSignatureAlgorithm, false);
397                                                                 confirmed = esxml.CheckSignature (alg);
398                                                         }
399                                                         if (!confirmed)
400                                                                 throw new MessageSecurityException (String.Format ("Signature for '{1}' token '{0}' is invalid.", spec.SecurityToken, attachMode));
401                                                         break;
402                                                 }
403
404                                                 sec_prop.ConfirmedSignatures.Insert (0, Convert.ToBase64String (esxml.SignatureValue));
405                                                 break;
406                                         }
407                                 }
408
409                                 sec_prop.IncomingSupportingTokens.Add (spec);
410                         }
411                 }
412
413                 WSSignedXml GetSignatureForToken (SecurityToken token)
414                 {
415                         int count = 0;
416                         foreach (WSSignedXml sxml in wss_header.FindAll<WSSignedXml> ()) {
417                                 if (count++ == 0)
418                                         continue; // primary signature
419                                 foreach (SecurityTokenReferenceKeyInfo r in sxml.KeyInfo)
420                                         if (token.MatchesKeyIdentifierClause (r.Clause))
421                                                 return sxml;
422                         }
423                         return null;
424                 }
425
426                 SupportingTokenSpecification ValidateTokensByParameters (SecurityTokenAuthenticator a, SecurityTokenResolver r, List<SupportingTokenInfo> tokens)
427                 {
428                         foreach (SupportingTokenInfo info in tokens)
429                                 if (a.CanValidateToken (info.Token))
430                                         return new SupportingTokenSpecification (
431                                                 info.Token,
432                                                 a.ValidateToken (info.Token),
433                                                 info.Mode);
434                         return null;
435                 }
436
437                 #endregion
438
439                 string GetAction ()
440                 {
441                         string ret = source_message.Headers.Action;
442                         if (ret == null) {
443                                 HttpRequestMessageProperty reqprop =
444                                         source_message.Properties ["Action"] as HttpRequestMessageProperty;
445                                 if (reqprop != null)
446                                         ret = reqprop.Headers ["Action"];
447                         }
448                         return ret;
449                 }
450         }
451 }