1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4 
5 namespace System.ServiceModel.Security
6 {
7     using System.Runtime.InteropServices;
8     using System.ServiceModel.Channels;
9     using System.ServiceModel;
10     using System.ServiceModel.Diagnostics;
11     using System.Diagnostics;
12     using System.Collections.Generic;
13     using System.Text;
14     using System.Threading;
15     using System.Globalization;
16     using System.ComponentModel;
17     using System.Security.Principal;
18     using System.IdentityModel.Tokens;
19     using System.Net;
20     using System.IdentityModel;
21     using System.IdentityModel.Selectors;
22     using System.Security.Authentication.ExtendedProtection;
23     using IMD = System.IdentityModel.Diagnostics;
24 
25     using DiagnosticUtility = System.ServiceModel.DiagnosticUtility;
26     using SR = System.ServiceModel.SR;
27 
28     internal sealed class WindowsSspiNegotiation : ISspiNegotiation
29     {
30         const int DefaultMaxPromptAttempts = 1;
31         SspiContextFlags contextFlags;
32         SafeFreeCredentials credentialsHandle;
33         bool disposed = false;
34         bool doMutualAuth;
35         TokenImpersonationLevel impersonationLevel;
36         bool isCompleted;
37         bool isServer;
38         LifeSpan lifespan;
39         string protocolName;
40         SafeDeleteContext securityContext;
41         string servicePrincipalName;
42         SecSizes sizes;
43         Object syncObject = new Object();
44         int tokenSize;
45         bool interactiveNegoLogonEnabled = true;
46         string clientPackageName;
47         bool saveClientCredentialsOnSspiUi = true;
48         bool allowNtlm;
49         int MaxPromptAttempts = 0;
50 
51         /// <summary>
52         /// Client side ctor
53         /// </summary>
WindowsSspiNegotiation(string package, SafeFreeCredentials credentialsHandle, TokenImpersonationLevel impersonationLevel, string servicePrincipalName, bool doMutualAuth, bool interactiveLogonEnabled, bool ntlmEnabled)54         internal WindowsSspiNegotiation(string package, SafeFreeCredentials credentialsHandle, TokenImpersonationLevel impersonationLevel, string servicePrincipalName, bool doMutualAuth, bool interactiveLogonEnabled, bool ntlmEnabled)
55             : this(false, package, credentialsHandle, impersonationLevel, servicePrincipalName, doMutualAuth, interactiveLogonEnabled, ntlmEnabled)
56         { }
57 
58         /// <summary>
59         /// Server side ctor
60         /// </summary>
WindowsSspiNegotiation(string package, SafeFreeCredentials credentialsHandle, string defaultServiceBinding)61         internal WindowsSspiNegotiation(string package, SafeFreeCredentials credentialsHandle, string defaultServiceBinding)
62             : this(true, package, credentialsHandle, TokenImpersonationLevel.Delegation, defaultServiceBinding, false, false, true)
63         { }
64 
WindowsSspiNegotiation(bool isServer, string package, SafeFreeCredentials credentialsHandle, TokenImpersonationLevel impersonationLevel, string servicePrincipalName, bool doMutualAuth, bool interactiveLogonEnabled, bool ntlmEnabled)65         WindowsSspiNegotiation(bool isServer, string package, SafeFreeCredentials credentialsHandle, TokenImpersonationLevel impersonationLevel, string servicePrincipalName, bool doMutualAuth, bool interactiveLogonEnabled, bool ntlmEnabled)
66         {
67             this.tokenSize = SspiWrapper.GetVerifyPackageInfo(package).MaxToken;
68             this.isServer = isServer;
69             this.servicePrincipalName = servicePrincipalName;
70             this.securityContext = null;
71             if (isServer)
72             {
73                 this.impersonationLevel = TokenImpersonationLevel.Delegation;
74                 this.doMutualAuth = false;
75             }
76             else
77             {
78                 this.impersonationLevel = impersonationLevel;
79                 this.doMutualAuth = doMutualAuth;
80                 this.interactiveNegoLogonEnabled = interactiveLogonEnabled;
81                 this.clientPackageName = package;
82                 this.allowNtlm = ntlmEnabled;
83             }
84             this.credentialsHandle = credentialsHandle;
85         }
86 
87         public DateTime ExpirationTimeUtc
88         {
89             get
90             {
91                 ThrowIfDisposed();
92                 if (this.LifeSpan == null)
93                 {
94                     return SecurityUtils.MaxUtcDateTime;
95                 }
96                 else
97                 {
98                     return this.LifeSpan.ExpiryTimeUtc;
99                 }
100             }
101         }
102 
103         public bool IsCompleted
104         {
105             get
106             {
107                 ThrowIfDisposed();
108                 return this.isCompleted;
109             }
110         }
111 
112         public bool IsDelegationFlag
113         {
114             get
115             {
116                 ThrowIfDisposed();
117                 return (this.contextFlags & SspiContextFlags.Delegate) != 0;
118             }
119         }
120 
121         public bool IsIdentifyFlag
122         {
123             get
124             {
125                 ThrowIfDisposed();
126                 return (this.contextFlags & (this.isServer ? SspiContextFlags.AcceptIdentify : SspiContextFlags.InitIdentify)) != 0;
127             }
128         }
129 
130         public bool IsMutualAuthFlag
131         {
132             get
133             {
134                 ThrowIfDisposed();
135                 return (this.contextFlags & SspiContextFlags.MutualAuth) != 0;
136             }
137         }
138 
139         public bool IsValidContext
140         {
141             get
142             {
143                 return (this.securityContext != null && this.securityContext.IsInvalid == false);
144             }
145         }
146 
147         public string KeyEncryptionAlgorithm
148         {
149             get
150             {
151                 return SecurityAlgorithms.WindowsSspiKeyWrap;
152             }
153         }
154 
155         public LifeSpan LifeSpan
156         {
157             get
158             {
159                 ThrowIfDisposed();
160                 if (this.lifespan == null)
161                 {
162                     LifeSpan tmpLifeSpan = (LifeSpan)SspiWrapper.QueryContextAttributes(this.securityContext, ContextAttribute.Lifespan);
163 
164                     if (IsCompleted)
165                     {
166                         // cache it only when it's completed
167                         this.lifespan = tmpLifeSpan;
168                     }
169 
170                     return tmpLifeSpan;
171                 }
172 
173                 return this.lifespan;
174             }
175         }
176 
177         public string ProtocolName
178         {
179             get
180             {
181                 ThrowIfDisposed();
182                 if (this.protocolName == null)
183                 {
184                     NegotiationInfoClass negotiationInfo = SspiWrapper.QueryContextAttributes(this.securityContext, ContextAttribute.NegotiationInfo) as NegotiationInfoClass;
185 
186                     if (IsCompleted)
187                     {
188                         // cache it only when it's completed
189                         this.protocolName = negotiationInfo.AuthenticationPackage;
190                     }
191 
192                     return negotiationInfo.AuthenticationPackage;
193                 }
194 
195                 return this.protocolName;
196             }
197         }
198 
199         public string ServicePrincipalName
200         {
201             get
202             {
203                 ThrowIfDisposed();
204                 return this.servicePrincipalName;
205             }
206         }
207 
208         SecSizes SecuritySizes
209         {
210             get
211             {
212                 ThrowIfDisposed();
213                 if (this.sizes == null)
214                 {
215                     SecSizes tmpSizes = (SecSizes)SspiWrapper.QueryContextAttributes(this.securityContext, ContextAttribute.Sizes);
216 
217                     if (IsCompleted)
218                     {
219                         // cache it only when it's completed
220                         this.sizes = tmpSizes;
221                     }
222 
223                     return tmpSizes;
224                 }
225 
226                 return this.sizes;
227             }
228         }
229 
GetRemoteIdentityName()230         public string GetRemoteIdentityName()
231         {
232             if (!this.isServer)
233             {
234                 return this.servicePrincipalName;
235             }
236 
237             if (IsValidContext)
238             {
239                 using (SafeCloseHandle contextToken = GetContextToken())
240                 {
241                     using (WindowsIdentity windowsIdentity = new WindowsIdentity(contextToken.DangerousGetHandle(), this.ProtocolName))
242                     {
243                         return windowsIdentity.Name;
244                     }
245                 }
246             }
247             return String.Empty;
248         }
249 
Decrypt(byte[] encryptedContent)250         public byte[] Decrypt(byte[] encryptedContent)
251         {
252             if (encryptedContent == null)
253                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encryptedContent");
254             ThrowIfDisposed();
255 
256             SecurityBuffer[] securityBuffer = new SecurityBuffer[2];
257             securityBuffer[0] = new SecurityBuffer(encryptedContent, 0, encryptedContent.Length, BufferType.Stream);
258             securityBuffer[1] = new SecurityBuffer(0, BufferType.Data);
259             int errorCode = SspiWrapper.DecryptMessage(this.securityContext, securityBuffer, 0, true);
260             if (errorCode != 0)
261             {
262                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(errorCode));
263             }
264 
265             for (int i = 0; i < securityBuffer.Length; ++i)
266             {
267                 if (securityBuffer[i].type == BufferType.Data)
268                 {
269                     return securityBuffer[i].token;
270                 }
271             }
272             OnBadData();
273             return null;
274         }
275 
Dispose()276         public void Dispose()
277         {
278             Dispose(true);
279             GC.SuppressFinalize(this);
280         }
281 
Encrypt(byte[] input)282         public byte[] Encrypt(byte[] input)
283         {
284             if (input == null)
285                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("input");
286             ThrowIfDisposed();
287             SecurityBuffer[] securityBuffer = new SecurityBuffer[3];
288 
289             byte[] tokenBuffer = DiagnosticUtility.Utility.AllocateByteArray(SecuritySizes.SecurityTrailer);
290             securityBuffer[0] = new SecurityBuffer(tokenBuffer, 0, tokenBuffer.Length, BufferType.Token);
291             byte[] dataBuffer = DiagnosticUtility.Utility.AllocateByteArray(input.Length);
292             Buffer.BlockCopy(input, 0, dataBuffer, 0, input.Length);
293             securityBuffer[1] = new SecurityBuffer(dataBuffer, 0, dataBuffer.Length, BufferType.Data);
294             byte[] paddingBuffer = DiagnosticUtility.Utility.AllocateByteArray(SecuritySizes.BlockSize);
295             securityBuffer[2] = new SecurityBuffer(paddingBuffer, 0, paddingBuffer.Length, BufferType.Padding);
296 
297             int errorCode = SspiWrapper.EncryptMessage(this.securityContext, securityBuffer, 0);
298             if (errorCode != 0)
299             {
300                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(errorCode));
301             }
302 
303             int tokenLen = 0;
304             int paddingLen = 0;
305             for (int i = 0; i < securityBuffer.Length; ++i)
306             {
307                 if (securityBuffer[i].type == BufferType.Token)
308                     tokenLen = securityBuffer[i].size;
309                 else if (securityBuffer[i].type == BufferType.Padding)
310                     paddingLen = securityBuffer[i].size;
311             }
312             byte[] encryptedData = DiagnosticUtility.Utility.AllocateByteArray(checked(tokenLen + dataBuffer.Length + paddingLen));
313 
314             Buffer.BlockCopy(tokenBuffer, 0, encryptedData, 0, tokenLen);
315             Buffer.BlockCopy(dataBuffer, 0, encryptedData, tokenLen, dataBuffer.Length);
316             Buffer.BlockCopy(paddingBuffer, 0, encryptedData, tokenLen + dataBuffer.Length, paddingLen);
317 
318             return encryptedData;
319         }
320 
GetOutgoingBlob(byte[] incomingBlob, ChannelBinding channelbinding, ExtendedProtectionPolicy protectionPolicy)321         public byte[] GetOutgoingBlob(byte[] incomingBlob, ChannelBinding channelbinding, ExtendedProtectionPolicy protectionPolicy)
322         {
323             ThrowIfDisposed();
324             int statusCode = 0;
325 
326             // use the confidentiality option to ensure we can encrypt messages
327             SspiContextFlags requestedFlags = SspiContextFlags.Confidentiality
328                                             | SspiContextFlags.ReplayDetect
329                                             | SspiContextFlags.SequenceDetect;
330 
331             if (this.doMutualAuth)
332             {
333                 requestedFlags |= SspiContextFlags.MutualAuth;
334             }
335 
336             if (this.impersonationLevel == TokenImpersonationLevel.Delegation)
337             {
338                 requestedFlags |= SspiContextFlags.Delegate;
339             }
340             else if (this.isServer == false && this.impersonationLevel == TokenImpersonationLevel.Identification)
341             {
342                 requestedFlags |= SspiContextFlags.InitIdentify;
343             }
344             else if (this.isServer == false && this.impersonationLevel == TokenImpersonationLevel.Anonymous)
345             {
346                 requestedFlags |= SspiContextFlags.InitAnonymous;
347             }
348 
349             ExtendedProtectionPolicyHelper policyHelper = new ExtendedProtectionPolicyHelper(channelbinding, protectionPolicy);
350 
351             if (isServer)
352             {
353                 if (policyHelper.PolicyEnforcement == PolicyEnforcement.Always && policyHelper.ChannelBinding == null && policyHelper.ProtectionScenario != ProtectionScenario.TrustedProxy)
354                 {
355                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.SecurityChannelBindingMissing)));
356                 }
357 
358                 if (policyHelper.PolicyEnforcement == PolicyEnforcement.WhenSupported)
359                 {
360                     requestedFlags |= SspiContextFlags.ChannelBindingAllowMissingBindings;
361                 }
362 
363                 if (policyHelper.ProtectionScenario == ProtectionScenario.TrustedProxy)
364                 {
365                     requestedFlags |= SspiContextFlags.ChannelBindingProxyBindings;
366                 }
367             }
368 
369             List<SecurityBuffer> list = new List<SecurityBuffer>(2);
370 
371             if (incomingBlob != null)
372             {
373                 list.Add(new SecurityBuffer(incomingBlob, BufferType.Token));
374             }
375 
376             // when deciding if the channel binding should be added to the security buffer
377             // it is necessary to differentiate between  client and server.
378             // Server rules were added to policyHelper as they are shared with Kerb and I want them consistent
379             // Client adds if not null.
380             if (this.isServer)
381             {
382                 if (policyHelper.ShouldAddChannelBindingToASC())
383                 {
384                     list.Add(new SecurityBuffer(policyHelper.ChannelBinding));
385                 }
386             }
387             else
388             {
389                 if (policyHelper.ChannelBinding != null)
390                 {
391                     list.Add(new SecurityBuffer(policyHelper.ChannelBinding));
392                 }
393             }
394 
395             SecurityBuffer[] inSecurityBuffer = null;
396             if (list.Count > 0)
397             {
398                 inSecurityBuffer = list.ToArray();
399             }
400 
401             SecurityBuffer outSecurityBuffer = new SecurityBuffer(this.tokenSize, BufferType.Token);
402 
403             if (!this.isServer)
404             {
405                 //client session
406                 statusCode = SspiWrapper.InitializeSecurityContext(this.credentialsHandle,
407                                                                     ref this.securityContext,
408                                                                     this.servicePrincipalName,
409                                                                     requestedFlags,
410                                                                     Endianness.Network,
411                                                                     inSecurityBuffer,
412                                                                     outSecurityBuffer,
413                                                                     ref this.contextFlags);
414             }
415             else
416             {
417                 // server session
418                 //This check is to save an unnecessary ASC call.
419                 bool isServerSecurityContextNull = this.securityContext == null;
420                 SspiContextFlags serverContextFlags = this.contextFlags;
421 
422                 statusCode = SspiWrapper.AcceptSecurityContext(this.credentialsHandle,
423                                                                 ref this.securityContext,
424                                                                 requestedFlags,
425                                                                 Endianness.Network,
426                                                                 inSecurityBuffer,
427                                                                 outSecurityBuffer,
428                                                                 ref this.contextFlags);
429 
430                 if (statusCode == (int)SecurityStatus.InvalidToken && !isServerSecurityContextNull)
431                 {
432                     // Call again into ASC after deleting the Securitycontext. If this securitycontext is not deleted
433                     // then when the client sends NTLM blob the service will treat it as Nego2blob and will fail to authenticate the client.
434                     this.contextFlags = serverContextFlags;
435                     CloseContext();
436                     statusCode = SspiWrapper.AcceptSecurityContext(this.credentialsHandle,
437                                                                     ref this.securityContext,
438                                                                     requestedFlags,
439                                                                     Endianness.Network,
440                                                                     inSecurityBuffer,
441                                                                     outSecurityBuffer,
442                                                                     ref this.contextFlags);
443                 }
444             }
445 
446             if (DiagnosticUtility.ShouldTraceInformation)
447             {
448                 IMD.SecurityTraceRecordHelper.TraceChannelBindingInformation(policyHelper, this.isServer, channelbinding);
449             }
450 
451             if ((statusCode & unchecked((int)0x80000000)) != 0)
452             {
453                 if (!this.isServer
454                     && this.interactiveNegoLogonEnabled
455                     && SecurityUtils.IsOSGreaterThanOrEqualToWin7()
456                     && SspiWrapper.IsSspiPromptingNeeded((uint)statusCode)
457                     && SspiWrapper.IsNegotiateExPackagePresent())
458                 {
459                     // If we have prompted enough number of times (DefaultMaxPromptAttempts) with wrong credentials, then we do not prompt again and throw.
460                     if (MaxPromptAttempts >= DefaultMaxPromptAttempts)
461                     {
462                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(statusCode, SR.GetString(SR.InvalidClientCredentials)));
463                     }
464 
465                     IntPtr ppAuthIdentity = IntPtr.Zero;
466                     uint errorCode = SspiWrapper.SspiPromptForCredential(this.servicePrincipalName, this.clientPackageName, out ppAuthIdentity, ref this.saveClientCredentialsOnSspiUi);
467                     if (errorCode == (uint)CredentialStatus.Success)
468                     {
469                         IntPtr ppNewAuthIdentity = IntPtr.Zero;
470 
471                         if (!this.allowNtlm)
472                         {
473                             // When Ntlm is  explicitly disabled we don't want the collected
474                             //creds from the Kerb/NTLM tile to be used for NTLM auth.
475 
476                             uint status = UnsafeNativeMethods.SspiExcludePackage(ppAuthIdentity, "NTLM", out ppNewAuthIdentity);
477                         }
478                         else
479                         {
480                             ppNewAuthIdentity = ppAuthIdentity;
481                         }
482 
483                         this.credentialsHandle = SspiWrapper.AcquireCredentialsHandle(this.clientPackageName, CredentialUse.Outbound, ref ppNewAuthIdentity);
484 
485                         if (IntPtr.Zero != ppNewAuthIdentity)
486                         {
487                             UnsafeNativeMethods.SspiFreeAuthIdentity(ppNewAuthIdentity);
488                         }
489 
490                         CloseContext();
491 
492                         MaxPromptAttempts++;
493                         return this.GetOutgoingBlob(null, channelbinding, protectionPolicy);
494                     }
495                     else
496                     {
497                         // Call into SspiPromptForCredential had an error. Time to throw.
498                         if (IntPtr.Zero != ppAuthIdentity)
499                         {
500                             UnsafeNativeMethods.SspiFreeAuthIdentity(ppAuthIdentity);
501                         }
502 
503                         CloseContext();
504                         this.isCompleted = true;
505                         throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)errorCode, SR.GetString(SR.SspiErrorOrInvalidClientCredentials)));
506                     }
507                 }
508 
509                 CloseContext();
510                 this.isCompleted = true;
511                 if (!this.isServer && (statusCode == (int)SecurityStatus.TargetUnknown
512                     || statusCode == (int)SecurityStatus.WrongPrincipal))
513                 {
514                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(statusCode, SR.GetString(SR.IncorrectSpnOrUpnSpecified, this.servicePrincipalName)));
515                 }
516                 else
517                 {
518                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(statusCode, SR.GetString(SR.InvalidSspiNegotiation)));
519                 }
520             }
521 
522             if (DiagnosticUtility.ShouldTraceInformation)
523             {
524                 if (this.isServer)
525                 {
526                     SecurityTraceRecordHelper.TraceServiceOutgoingSpnego(this);
527                 }
528                 else
529                 {
530                     SecurityTraceRecordHelper.TraceClientOutgoingSpnego(this);
531                 }
532             }
533 
534             if (statusCode == (int)SecurityStatus.OK)
535             {
536                 // we're done
537                 this.isCompleted = true;
538 
539                 // These must all be true to check service binding
540                 // 1. we are the service (listener)
541                 // 2. caller is not anonymous
542                 // 3. protocol is not Kerberos
543                 // 4. policy is set to check service binding
544                 //
545                 if (isServer && ((this.contextFlags & SspiContextFlags.AcceptAnonymous) == 0) && (string.Compare(this.ProtocolName, NegotiationInfoClass.Kerberos, StringComparison.OrdinalIgnoreCase) != 0) && policyHelper.ShouldCheckServiceBinding)
546                 {
547                     // in the server case the servicePrincipalName is the defaultServiceBinding
548 
549                     if (DiagnosticUtility.ShouldTraceInformation)
550                     {
551                         string serviceBindingNameSentByClient;
552                         SspiWrapper.QuerySpecifiedTarget(securityContext, out serviceBindingNameSentByClient);
553                         IMD.SecurityTraceRecordHelper.TraceServiceNameBindingOnServer( serviceBindingNameSentByClient, this.servicePrincipalName, policyHelper.ServiceNameCollection);
554                     }
555 
556                     policyHelper.CheckServiceBinding(this.securityContext, this.servicePrincipalName);
557                 }
558             }
559             else
560             {
561                 // we need to continue
562             }
563 
564             return outSecurityBuffer.token;
565         }
566 
ImpersonateContext()567         public void ImpersonateContext()
568         {
569             ThrowIfDisposed();
570             if (!IsValidContext)
571             {
572                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)SecurityStatus.InvalidHandle));
573             }
574 
575             SspiWrapper.ImpersonateSecurityContext(this.securityContext);
576         }
577 
CloseContext()578         internal void CloseContext()
579         {
580             ThrowIfDisposed();
581             try
582             {
583                 if (this.securityContext != null)
584                 {
585                     this.securityContext.Close();
586                 }
587             }
588             finally
589             {
590                 this.securityContext = null;
591             }
592         }
593 
Dispose(bool disposing)594         private void Dispose(bool disposing)
595         {
596             lock (this.syncObject)
597             {
598                 if (this.disposed == false)
599                 {
600                     if (disposing)
601                     {
602                         this.CloseContext();
603                     }
604 
605                     // set to null any references that aren't finalizable
606                     this.protocolName = null;
607                     this.servicePrincipalName = null;
608                     this.sizes = null;
609                     this.disposed = true;
610                 }
611             }
612         }
613 
GetContextToken()614         internal SafeCloseHandle GetContextToken()
615         {
616             if (!IsValidContext)
617             {
618                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)SecurityStatus.InvalidHandle));
619             }
620 
621             SafeCloseHandle token;
622             SecurityStatus status = (SecurityStatus)SspiWrapper.QuerySecurityContextToken(this.securityContext, out token);
623             if (status != SecurityStatus.OK)
624             {
625                 Utility.CloseInvalidOutSafeHandle(token);
626                 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)status));
627             }
628             return token;
629         }
630 
OnBadData()631         void OnBadData()
632         {
633             throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.BadData)));
634         }
635 
ThrowIfDisposed()636         void ThrowIfDisposed()
637         {
638             lock (this.syncObject)
639             {
640                 if (this.disposed)
641                 {
642                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(null));
643                 }
644             }
645         }
646     }
647 }
648