commit 085188996c2e6d56e47e534e888768d4130fcfb1 Author: Chris Ross Date: Fri Mar 14 12:43:45 2014 -0700 Initial port of Secuirty base package from Katana. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..bdaa5ba982 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs text=auto diff=csharp +*.vb text=auto +*.resx text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto +*.vbproj text=auto +*.fsproj text=auto +*.dbproj text=auto +*.sln text=auto eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..8bc217058d --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +[Oo]bj/ +[Bb]in/ +TestResults/ +.nuget/ +_ReSharper.*/ +packages/ +artifacts/ +PublishProfiles/ +*.user +*.suo +*.cache +*.docstates +_ReSharper.* +nuget.exe +*net45.csproj +*k10.csproj +*.psess +*.vsp +*.pidb +*.userprefs +*DS_Store +*.ncrunchsolution +*.*sdf +*.ipch \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000000..a059188b09 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Security.sln b/Security.sln new file mode 100644 index 0000000000..5a80619fc3 --- /dev/null +++ b/Security.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30203.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Security.net45", "src\Microsoft.AspNet.Security\Microsoft.AspNet.Security.net45.csproj", "{70640501-CCBB-4FD7-8231-329B6A436DE4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D2B6A51-2F9F-44F5-8131-EA5CAC053652}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Security.k10", "src\Microsoft.AspNet.Security\Microsoft.AspNet.Security.k10.csproj", "{F2391907-4463-4650-AEA4-E1B5733105C4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70640501-CCBB-4FD7-8231-329B6A436DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70640501-CCBB-4FD7-8231-329B6A436DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70640501-CCBB-4FD7-8231-329B6A436DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70640501-CCBB-4FD7-8231-329B6A436DE4}.Release|Any CPU.Build.0 = Release|Any CPU + {F2391907-4463-4650-AEA4-E1B5733105C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2391907-4463-4650-AEA4-E1B5733105C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2391907-4463-4650-AEA4-E1B5733105C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2391907-4463-4650-AEA4-E1B5733105C4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {70640501-CCBB-4FD7-8231-329B6A436DE4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} + {F2391907-4463-4650-AEA4-E1B5733105C4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652} + EndGlobalSection +EndGlobal diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000000..7045ee1f84 --- /dev/null +++ b/build.cmd @@ -0,0 +1,23 @@ +@echo off +cd %~dp0 + +SETLOCAL +SET CACHED_NUGET=%LocalAppData%\NuGet\NuGet.exe + +IF EXIST %CACHED_NUGET% goto copynuget +echo Downloading latest version of NuGet.exe... +IF NOT EXIST %LocalAppData%\NuGet md %LocalAppData%\NuGet +@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://www.nuget.org/nuget.exe' -OutFile '%CACHED_NUGET%'" + +:copynuget +IF EXIST .nuget\nuget.exe goto restore +md .nuget +copy %CACHED_NUGET% .nuget\nuget.exe > nul + +:restore +IF EXIST packages\KoreBuild goto run +.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre +.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion + +:run +packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %* diff --git a/global.json b/global.json new file mode 100644 index 0000000000..840c36f6ad --- /dev/null +++ b/global.json @@ -0,0 +1,3 @@ +{ + "sources": ["src"] +} \ No newline at end of file diff --git a/makefile.shade b/makefile.shade new file mode 100644 index 0000000000..6357ea2841 --- /dev/null +++ b/makefile.shade @@ -0,0 +1,7 @@ + +var VERSION='0.1' +var FULL_VERSION='0.1' +var AUTHORS='Microsoft' + +use-standard-lifecycle +k-standard-goals diff --git a/src/Microsoft.AspNet.Security/AppBuilderSecurityExtensions.cs b/src/Microsoft.AspNet.Security/AppBuilderSecurityExtensions.cs new file mode 100644 index 0000000000..36c0e04ee2 --- /dev/null +++ b/src/Microsoft.AspNet.Security/AppBuilderSecurityExtensions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +/* TODO: +using System; +using Owin; + +namespace Microsoft.AspNet.Security +{ + /// + /// Provides extensions methods for app.Property values that are only needed by implementations of authentication middleware. + /// + public static class AppBuilderSecurityExtensions + { + /// + /// Returns the previously set AuthenticationType that external sign in middleware should use when the + /// browser navigates back to their return url. + /// + /// App builder passed to the application startup code + /// + public static string GetDefaultSignInAsAuthenticationType(this IAppBuilder app) + { + if (app == null) + { + throw new ArgumentNullException("app"); + } + object value; + if (app.Properties.TryGetValue(Constants.DefaultSignInAsAuthenticationType, out value)) + { + var authenticationType = value as string; + if (!string.IsNullOrEmpty(authenticationType)) + { + return authenticationType; + } + } + throw new InvalidOperationException(Resources.Exception_MissingDefaultSignInAsAuthenticationType); + } + + /// + /// Called by middleware to change the name of the AuthenticationType that external middleware should use + /// when the browser navigates back to their return url. + /// + /// App builder passed to the application startup code + /// AuthenticationType that external middleware should sign in as. + public static void SetDefaultSignInAsAuthenticationType(this IAppBuilder app, string authenticationType) + { + if (app == null) + { + throw new ArgumentNullException("app"); + } + if (authenticationType == null) + { + throw new ArgumentNullException("authenticationType"); + } + app.Properties[Constants.DefaultSignInAsAuthenticationType] = authenticationType; + } + } +} +*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/AuthenticationMode.cs b/src/Microsoft.AspNet.Security/AuthenticationMode.cs new file mode 100644 index 0000000000..ba7aff904d --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthenticationMode.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security +{ + /// + /// Controls the behavior of authentication middleware + /// + public enum AuthenticationMode + { + /// + /// In Active mode the authentication middleware will alter the user identity as the request arrives, and + /// will also alter a plain 401 as the response leaves. + /// + Active, + + /// + /// In Passive mode the authentication middleware will only provide user identity when asked, and will only + /// alter 401 responses where the authentication type named in the extra challenge data. + /// + Passive + } +} diff --git a/src/Microsoft.AspNet.Security/AuthenticationOptions.cs b/src/Microsoft.AspNet.Security/AuthenticationOptions.cs new file mode 100644 index 0000000000..262bce7f3e --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthenticationOptions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security +{ + /// + /// Base Options for all authentication middleware + /// + public abstract class AuthenticationOptions + { + private string _authenticationType; + + /// + /// Initialize properties of AuthenticationOptions base class + /// + /// Assigned to the AuthenticationType property + protected AuthenticationOptions(string authenticationType) + { + Description = new AuthenticationDescription(); + AuthenticationType = authenticationType; + AuthenticationMode = AuthenticationMode.Active; + } + + /// + /// The AuthenticationType in the options corresponds to the IIdentity AuthenticationType property. A different + /// value may be assigned in order to use the same authentication middleware type more than once in a pipeline. + /// + public string AuthenticationType + { + get { return _authenticationType; } + set + { + _authenticationType = value; + Description.AuthenticationType = value; + } + } + + /// + /// If Active the authentication middleware alter the request user coming in and + /// alter 401 Unauthorized responses going out. If Passive the authentication middleware will only provide + /// identity and alter responses when explicitly indicated by the AuthenticationType. + /// + public AuthenticationMode AuthenticationMode { get; set; } + + /// + /// Additional information about the authentication type which is made available to the application. + /// + public AuthenticationDescription Description { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/AuthenticationTicket.cs b/src/Microsoft.AspNet.Security/AuthenticationTicket.cs new file mode 100644 index 0000000000..ac2e31006f --- /dev/null +++ b/src/Microsoft.AspNet.Security/AuthenticationTicket.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Security.Claims; + +namespace Microsoft.AspNet.Security +{ + /// + /// Contains user identity information as well as additional authentication state. + /// + public class AuthenticationTicket + { + /// + /// Initializes a new instance of the class + /// + /// + /// + public AuthenticationTicket(ClaimsIdentity identity, AuthenticationProperties properties) + { + Identity = identity; + Properties = properties ?? new AuthenticationProperties(); + } + + /// + /// Gets the authenticated user identity. + /// + public ClaimsIdentity Identity { get; private set; } + + /// + /// Additional state values for the authentication session. + /// + public AuthenticationProperties Properties { get; private set; } + } +} diff --git a/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs b/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs new file mode 100644 index 0000000000..8c1ade928f --- /dev/null +++ b/src/Microsoft.AspNet.Security/CertificateSubjectKeyIdentifierValidator.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +/* TODO: +using System; +using System.Collections.Generic; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.AspNet.Security +{ + /// + /// Provides pinned certificate validation based on the subject key identifier of the certificate. + /// + public class CertificateSubjectKeyIdentifierValidator : ICertificateValidator + { + private readonly HashSet _validSubjectKeyIdentifiers; + + /// + /// Initializes a new instance of the class. + /// + /// A set of subject key identifiers which are valid for an HTTPS request. + public CertificateSubjectKeyIdentifierValidator(IEnumerable validSubjectKeyIdentifiers) + { + if (validSubjectKeyIdentifiers == null) + { + throw new ArgumentNullException("validSubjectKeyIdentifiers"); + } + + _validSubjectKeyIdentifiers = new HashSet(validSubjectKeyIdentifiers, StringComparer.OrdinalIgnoreCase); + + if (_validSubjectKeyIdentifiers.Count == 0) + { + throw new ArgumentOutOfRangeException("validSubjectKeyIdentifiers"); + } + } + + /// + /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication. + /// + /// An object that contains state information for this validation. + /// The certificate used to authenticate the remote party. + /// The chain of certificate authorities associated with the remote certificate. + /// One or more errors associated with the remote certificate. + /// A Boolean value that determines whether the specified certificate is accepted for authentication. + public bool Validate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors != SslPolicyErrors.None) + { + return false; + } + + if (chain == null) + { + throw new ArgumentNullException("chain"); + } + + if (chain.ChainElements.Count < 2) + { + // Self signed. + return false; + } + + foreach (var chainElement in chain.ChainElements) + { + string subjectKeyIdentifier = GetSubjectKeyIdentifier(chainElement.Certificate); + if (string.IsNullOrWhiteSpace(subjectKeyIdentifier)) + { + continue; + } + + if (_validSubjectKeyIdentifiers.Contains(subjectKeyIdentifier)) + { + return true; + } + } + + return false; + } + + private static string GetSubjectKeyIdentifier(X509Certificate2 certificate) + { + const string SubjectKeyIdentifierOid = "2.5.29.14"; + var extension = certificate.Extensions[SubjectKeyIdentifierOid] as X509SubjectKeyIdentifierExtension; + + return extension == null ? null : extension.SubjectKeyIdentifier; + } + } +} +*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs b/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs new file mode 100644 index 0000000000..119b553f21 --- /dev/null +++ b/src/Microsoft.AspNet.Security/CertificateSubjectPublicKeyInfoValidator.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +/* TODO: +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Net.Security; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Win32; + +namespace Microsoft.AspNet.Security +{ + /// + /// Implements a cert pinning validator passed on + /// http://datatracker.ietf.org/doc/draft-ietf-websec-key-pinning/?include_text=1 + /// + public class CertificateSubjectPublicKeyInfoValidator : ICertificateValidator + { + private readonly HashSet _validBase64EncodedSubjectPublicKeyInfoHashes; + + private readonly SubjectPublicKeyInfoAlgorithm _algorithm; + + /// + /// Initializes a new instance of the class. + /// + /// A collection of valid base64 encoded hashes of the certificate public key information blob. + /// The algorithm used to generate the hashes. + public CertificateSubjectPublicKeyInfoValidator(IEnumerable validBase64EncodedSubjectPublicKeyInfoHashes, SubjectPublicKeyInfoAlgorithm algorithm) + { + if (validBase64EncodedSubjectPublicKeyInfoHashes == null) + { + throw new ArgumentNullException("validBase64EncodedSubjectPublicKeyInfoHashes"); + } + + _validBase64EncodedSubjectPublicKeyInfoHashes = new HashSet(validBase64EncodedSubjectPublicKeyInfoHashes); + + if (_validBase64EncodedSubjectPublicKeyInfoHashes.Count == 0) + { + throw new ArgumentOutOfRangeException("validBase64EncodedSubjectPublicKeyInfoHashes"); + } + + if (_algorithm != SubjectPublicKeyInfoAlgorithm.Sha1 && _algorithm != SubjectPublicKeyInfoAlgorithm.Sha256) + { + throw new ArgumentOutOfRangeException("algorithm"); + } + + _algorithm = algorithm; + } + + /// + /// Validates at least one SPKI hash is known. + /// + /// An object that contains state information for this validation. + /// The certificate used to authenticate the remote party. + /// The chain of certificate authorities associated with the remote certificate. + /// One or more errors associated with the remote certificate. + /// A Boolean value that determines whether the specified certificate is accepted for authentication. + public bool Validate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors != SslPolicyErrors.None) + { + return false; + } + + if (chain == null) + { + throw new ArgumentNullException("chain"); + } + + if (chain.ChainElements.Count < 2) + { + return false; + } + + using (HashAlgorithm algorithm = CreateHashAlgorithm()) + { + foreach (var chainElement in chain.ChainElements) + { + X509Certificate2 chainedCertificate = chainElement.Certificate; + string base64Spki = Convert.ToBase64String(algorithm.ComputeHash(ExtractSpkiBlob(chainedCertificate))); + if (_validBase64EncodedSubjectPublicKeyInfoHashes.Contains(base64Spki)) + { + return true; + } + } + } + + return false; + } + + private static byte[] ExtractSpkiBlob(X509Certificate2 certificate) + { + // Get a native cert_context from the managed X590Certificate2 instance. + var certContext = (NativeMethods.CERT_CONTEXT)Marshal.PtrToStructure(certificate.Handle, typeof(NativeMethods.CERT_CONTEXT)); + + // Pull the CERT_INFO structure from the context. + var certInfo = (NativeMethods.CERT_INFO)Marshal.PtrToStructure(certContext.pCertInfo, typeof(NativeMethods.CERT_INFO)); + + // And finally grab the key information, public key, algorithm and parameters from it. + NativeMethods.CERT_PUBLIC_KEY_INFO publicKeyInfo = certInfo.SubjectPublicKeyInfo; + + // Now start encoding to ASN1. + // First see how large the ASN1 representation is going to be. + UInt32 blobSize = 0; + var structType = new IntPtr(NativeMethods.X509_PUBLIC_KEY_INFO); + if (!NativeMethods.CryptEncodeObject(NativeMethods.X509_ASN_ENCODING, structType, ref publicKeyInfo, null, ref blobSize)) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error); + } + + // Allocate enough space. + var blob = new byte[blobSize]; + + // Finally get the ASN1 representation. + if (!NativeMethods.CryptEncodeObject(NativeMethods.X509_ASN_ENCODING, structType, ref publicKeyInfo, blob, ref blobSize)) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error); + } + + return blob; + } + + [SuppressMessage("Microsoft.Security.Cryptography", "CA5354:SHA1CannotBeUsed", Justification = "Only used to verify cert hashes.")] + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposal.")] + private HashAlgorithm CreateHashAlgorithm() + { + return _algorithm == SubjectPublicKeyInfoAlgorithm.Sha1 ? (HashAlgorithm)new SHA1CryptoServiceProvider() : new SHA256CryptoServiceProvider(); + } + } +} +*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs b/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs new file mode 100644 index 0000000000..cc1fddf397 --- /dev/null +++ b/src/Microsoft.AspNet.Security/CertificateThumbprintValidator.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +/* TODO: +using System; +using System.Collections.Generic; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.AspNet.Security +{ + /// + /// Provides pinned certificate validation based on the certificate thumbprint. + /// + public class CertificateThumbprintValidator : ICertificateValidator + { + private readonly HashSet _validCertificateThumbprints; + + /// + /// Initializes a new instance of the class. + /// + /// A set of thumbprints which are valid for an HTTPS request. + public CertificateThumbprintValidator(IEnumerable validThumbprints) + { + if (validThumbprints == null) + { + throw new ArgumentNullException("validThumbprints"); + } + + _validCertificateThumbprints = new HashSet(validThumbprints, StringComparer.OrdinalIgnoreCase); + + if (_validCertificateThumbprints.Count == 0) + { + throw new ArgumentOutOfRangeException("validThumbprints"); + } + } + + /// + /// Validates that the certificate thumbprints in the signing chain match at least one whitelisted thumbprint. + /// + /// An object that contains state information for this validation. + /// The certificate used to authenticate the remote party. + /// The chain of certificate authorities associated with the remote certificate. + /// One or more errors associated with the remote certificate. + /// A Boolean value that determines whether the specified certificate is accepted for authentication. + public bool Validate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors != SslPolicyErrors.None) + { + return false; + } + + if (chain == null) + { + throw new ArgumentNullException("chain"); + } + + if (chain.ChainElements.Count < 2) + { + // Self signed. + return false; + } + + foreach (var chainElement in chain.ChainElements) + { + string thumbprintToCheck = chainElement.Certificate.Thumbprint; + + if (thumbprintToCheck == null) + { + continue; + } + + if (_validCertificateThumbprints.Contains(thumbprintToCheck)) + { + return true; + } + } + + return false; + } + } +} +*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Constants.cs b/src/Microsoft.AspNet.Security/Constants.cs new file mode 100644 index 0000000000..50d4dfe0af --- /dev/null +++ b/src/Microsoft.AspNet.Security/Constants.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security +{ + /// + /// String constants used only by the Security assembly + /// + internal static class Constants + { + /// + /// Used by middleware extension methods to coordinate the default value Options property SignInAsAuthenticationType + /// + public const string DefaultSignInAsAuthenticationType = "Microsoft.AspNet.Security.Constants.DefaultSignInAsAuthenticationType"; + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Encoder/Base64TextEncoder.cs b/src/Microsoft.AspNet.Security/DataHandler/Encoder/Base64TextEncoder.cs new file mode 100644 index 0000000000..29acfcb207 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Encoder/Base64TextEncoder.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Security.DataHandler.Encoder +{ + public class Base64TextEncoder : ITextEncoder + { + public string Encode(byte[] data) + { + return Convert.ToBase64String(data); + } + + public byte[] Decode(string text) + { + return Convert.FromBase64String(text); + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Encoder/Base64UrlTextEncoder.cs b/src/Microsoft.AspNet.Security/DataHandler/Encoder/Base64UrlTextEncoder.cs new file mode 100644 index 0000000000..ca377ae65e --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Encoder/Base64UrlTextEncoder.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Security.DataHandler.Encoder +{ + public class Base64UrlTextEncoder : ITextEncoder + { + public string Encode(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + return Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + } + + public byte[] Decode(string text) + { + if (text == null) + { + throw new ArgumentNullException("text"); + } + + return Convert.FromBase64String(Pad(text.Replace('-', '+').Replace('_', '/'))); + } + + private static string Pad(string text) + { + var padding = 3 - ((text.Length + 3) % 4); + if (padding == 0) + { + return text; + } + return text + new string('=', padding); + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Encoder/ITextEncoder.cs b/src/Microsoft.AspNet.Security/DataHandler/Encoder/ITextEncoder.cs new file mode 100644 index 0000000000..ce61e8fb9b --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Encoder/ITextEncoder.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.DataHandler.Encoder +{ + public interface ITextEncoder + { + string Encode(byte[] data); + byte[] Decode(string text); + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Encoder/TextEncodings.cs b/src/Microsoft.AspNet.Security/DataHandler/Encoder/TextEncodings.cs new file mode 100644 index 0000000000..45b29107d2 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Encoder/TextEncodings.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.DataHandler.Encoder +{ + public static class TextEncodings + { + private static readonly ITextEncoder Base64Instance = new Base64TextEncoder(); + private static readonly ITextEncoder Base64UrlInstance = new Base64UrlTextEncoder(); + + public static ITextEncoder Base64 + { + get { return Base64Instance; } + } + + public static ITextEncoder Base64Url + { + get { return Base64UrlInstance; } + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/ISecureDataFormat.cs b/src/Microsoft.AspNet.Security/DataHandler/ISecureDataFormat.cs new file mode 100644 index 0000000000..f239150bae --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/ISecureDataFormat.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security +{ + public interface ISecureDataFormat + { + string Protect(TData data); + TData Unprotect(string protectedText); + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs b/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs new file mode 100644 index 0000000000..54d69c4f57 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/PropertiesDataFormat.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Security.DataHandler.Encoder; +using Microsoft.AspNet.Security.DataHandler.Serializer; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet.Security.DataHandler +{ + public class PropertiesDataFormat : SecureDataFormat + { + public PropertiesDataFormat(IDataProtector protector) + : base(DataSerializers.Properties, protector, TextEncodings.Base64Url) + { + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/SecureDataFormat.cs b/src/Microsoft.AspNet.Security/DataHandler/SecureDataFormat.cs new file mode 100644 index 0000000000..fa045e780e --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/SecureDataFormat.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNet.Security.DataHandler.Encoder; +using Microsoft.AspNet.Security.DataHandler.Serializer; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet.Security.DataHandler +{ + public class SecureDataFormat : ISecureDataFormat + { + private readonly IDataSerializer _serializer; + private readonly IDataProtector _protector; + private readonly ITextEncoder _encoder; + + public SecureDataFormat(IDataSerializer serializer, IDataProtector protector, ITextEncoder encoder) + { + _serializer = serializer; + _protector = protector; + _encoder = encoder; + } + + public string Protect(TData data) + { + byte[] userData = _serializer.Serialize(data); + byte[] protectedData = _protector.Protect(userData); + string protectedText = _encoder.Encode(protectedData); + return protectedText; + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception will be traced")] + public TData Unprotect(string protectedText) + { + try + { + if (protectedText == null) + { + return default(TData); + } + + byte[] protectedData = _encoder.Decode(protectedText); + if (protectedData == null) + { + return default(TData); + } + + byte[] userData = _protector.Unprotect(protectedData); + if (userData == null) + { + return default(TData); + } + + TData model = _serializer.Deserialize(userData); + return model; + } + catch + { + // TODO trace exception, but do not leak other information + return default(TData); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs new file mode 100644 index 0000000000..59585f6f9b --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/DataSerializers.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.DataHandler.Serializer +{ + public static class DataSerializers + { + static DataSerializers() + { + Properties = new PropertiesSerializer(); + Ticket = new TicketSerializer(); + } + + public static IDataSerializer Properties { get; set; } + + public static IDataSerializer Ticket { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/IDataSerializer.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/IDataSerializer.cs new file mode 100644 index 0000000000..8b9b77ae01 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/IDataSerializer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.DataHandler.Serializer +{ + public interface IDataSerializer + { + byte[] Serialize(TModel model); + TModel Deserialize(byte[] data); + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs new file mode 100644 index 0000000000..2675932fc3 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/PropertiesSerializer.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace Microsoft.AspNet.Security.DataHandler.Serializer +{ + public class PropertiesSerializer : IDataSerializer + { + private const int FormatVersion = 1; + + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is idempotent")] + public byte[] Serialize(AuthenticationProperties model) + { + using (var memory = new MemoryStream()) + { + using (var writer = new BinaryWriter(memory)) + { + Write(writer, model); + writer.Flush(); + return memory.ToArray(); + } + } + } + + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is idempotent")] + public AuthenticationProperties Deserialize(byte[] data) + { + using (var memory = new MemoryStream(data)) + { + using (var reader = new BinaryReader(memory)) + { + return Read(reader); + } + } + } + + public static void Write(BinaryWriter writer, AuthenticationProperties properties) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (properties == null) + { + throw new ArgumentNullException("properties"); + } + + writer.Write(FormatVersion); + writer.Write(properties.Dictionary.Count); + foreach (var kv in properties.Dictionary) + { + writer.Write(kv.Key); + writer.Write(kv.Value); + } + } + + public static AuthenticationProperties Read(BinaryReader reader) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + + if (reader.ReadInt32() != FormatVersion) + { + return null; + } + int count = reader.ReadInt32(); + var extra = new Dictionary(count); + for (int index = 0; index != count; ++index) + { + string key = reader.ReadString(); + string value = reader.ReadString(); + extra.Add(key, value); + } + return new AuthenticationProperties(extra); + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs b/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs new file mode 100644 index 0000000000..4ca3c0a6af --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/Serializer/TicketSerializer.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Security.Claims; + +namespace Microsoft.AspNet.Security.DataHandler.Serializer +{ + public class TicketSerializer : IDataSerializer + { + private const int FormatVersion = 2; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is idempotent")] + public virtual byte[] Serialize(AuthenticationTicket model) + { + using (var memory = new MemoryStream()) + { + using (var compression = new GZipStream(memory, CompressionLevel.Optimal)) + { + using (var writer = new BinaryWriter(compression)) + { + Write(writer, model); + } + } + return memory.ToArray(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is idempotent")] + public virtual AuthenticationTicket Deserialize(byte[] data) + { + using (var memory = new MemoryStream(data)) + { + using (var compression = new GZipStream(memory, CompressionMode.Decompress)) + { + using (var reader = new BinaryReader(compression)) + { + return Read(reader); + } + } + } + } + + public static void Write(BinaryWriter writer, AuthenticationTicket model) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (model == null) + { + throw new ArgumentNullException("model"); + } + + writer.Write(FormatVersion); + ClaimsIdentity identity = model.Identity; + writer.Write(identity.AuthenticationType); + WriteWithDefault(writer, identity.NameClaimType, DefaultValues.NameClaimType); + WriteWithDefault(writer, identity.RoleClaimType, DefaultValues.RoleClaimType); + writer.Write(identity.Claims.Count()); + foreach (var claim in identity.Claims) + { + WriteWithDefault(writer, claim.Type, identity.NameClaimType); + writer.Write(claim.Value); + WriteWithDefault(writer, claim.ValueType, DefaultValues.StringValueType); + WriteWithDefault(writer, claim.Issuer, DefaultValues.LocalAuthority); + WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer); + } + PropertiesSerializer.Write(writer, model.Properties); + } + + public static AuthenticationTicket Read(BinaryReader reader) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + + if (reader.ReadInt32() != FormatVersion) + { + return null; + } + + string authenticationType = reader.ReadString(); + string nameClaimType = ReadWithDefault(reader, DefaultValues.NameClaimType); + string roleClaimType = ReadWithDefault(reader, DefaultValues.RoleClaimType); + int count = reader.ReadInt32(); + var claims = new Claim[count]; + for (int index = 0; index != count; ++index) + { + string type = ReadWithDefault(reader, nameClaimType); + string value = reader.ReadString(); + string valueType = ReadWithDefault(reader, DefaultValues.StringValueType); + string issuer = ReadWithDefault(reader, DefaultValues.LocalAuthority); + string originalIssuer = ReadWithDefault(reader, issuer); + claims[index] = new Claim(type, value, valueType, issuer, originalIssuer); + } + var identity = new ClaimsIdentity(claims, authenticationType, nameClaimType, roleClaimType); + AuthenticationProperties properties = PropertiesSerializer.Read(reader); + return new AuthenticationTicket(identity, properties); + } + + private static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue) + { + if (string.Equals(value, defaultValue, StringComparison.Ordinal)) + { + writer.Write(DefaultValues.DefaultStringPlaceholder); + } + else + { + writer.Write(value); + } + } + + private static string ReadWithDefault(BinaryReader reader, string defaultValue) + { + string value = reader.ReadString(); + if (string.Equals(value, DefaultValues.DefaultStringPlaceholder, StringComparison.Ordinal)) + { + return defaultValue; + } + return value; + } + + private static class DefaultValues + { + public const string DefaultStringPlaceholder = "\0"; + public const string NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"; + public const string RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"; + public const string LocalAuthority = "LOCAL AUTHORITY"; + public const string StringValueType = "http://www.w3.org/2001/XMLSchema#string"; + } + } +} diff --git a/src/Microsoft.AspNet.Security/DataHandler/TicketDataFormat.cs b/src/Microsoft.AspNet.Security/DataHandler/TicketDataFormat.cs new file mode 100644 index 0000000000..8d0d0c69f3 --- /dev/null +++ b/src/Microsoft.AspNet.Security/DataHandler/TicketDataFormat.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Security.DataHandler.Encoder; +using Microsoft.AspNet.Security.DataHandler.Serializer; +using Microsoft.AspNet.Security.DataProtection; + +namespace Microsoft.AspNet.Security.DataHandler +{ + public class TicketDataFormat : SecureDataFormat + { + public TicketDataFormat(IDataProtector protector) : base(DataSerializers.Ticket, protector, TextEncodings.Base64Url) + { + } + } +} diff --git a/src/Microsoft.AspNet.Security/ICertificateValidator.cs b/src/Microsoft.AspNet.Security/ICertificateValidator.cs new file mode 100644 index 0000000000..8d062aecfe --- /dev/null +++ b/src/Microsoft.AspNet.Security/ICertificateValidator.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. +/* TODO: +using System; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.AspNet.Security +{ + /// + /// Interface for providing pinned certificate validation, which checks HTTPS + /// communication against a known good list of certificates to protect against + /// compromised or rogue CAs issuing certificates for hosts without the + /// knowledge of the host owner. + /// + public interface ICertificateValidator + { + /// + /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication. + /// + /// An object that contains state information for this validation. + /// The certificate used to authenticate the remote party. + /// The chain of certificate authorities associated with the remote certificate. + /// One or more errors associated with the remote certificate. + /// A Boolean value that determines whether the specified certificate is accepted for authentication. + bool Validate(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors); + } +} +*/ \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs new file mode 100644 index 0000000000..39d6596d3c --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler.cs @@ -0,0 +1,252 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.Logging; +using Microsoft.AspNet.Security.DataHandler.Encoder; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + /// + /// Base class for the per-request work performed by most authentication middleware. + /// + public abstract class AuthenticationHandler + { + private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider(); + + private object _registration; + + private Task _authenticate; + private bool _authenticateInitialized; + private object _authenticateSyncLock; + + private Task _applyResponse; + private bool _applyResponseInitialized; + private object _applyResponseSyncLock; + + private AuthenticationOptions _baseOptions; + + protected HttpContext Context { get; private set; } + + protected HttpRequest Request + { + get { return Context.Request; } + } + + protected HttpResponse Response + { + get { return Context.Response; } + } + + protected PathString RequestPathBase { get; private set; } + protected SecurityHelper Helper { get; private set; } + + internal AuthenticationOptions BaseOptions + { + get { return _baseOptions; } + } + + protected async Task BaseInitializeAsync(AuthenticationOptions options, HttpContext context) + { + _baseOptions = options; + Context = context; + Helper = new SecurityHelper(context); + RequestPathBase = Request.PathBase; + + _registration = Request.RegisterAuthenticationHandler(this); + + Response.OnSendingHeaders(OnSendingHeaderCallback, this); + + await InitializeCoreAsync(); + + if (BaseOptions.AuthenticationMode == AuthenticationMode.Active) + { + AuthenticationTicket ticket = await AuthenticateAsync(); + if (ticket != null && ticket.Identity != null) + { + Helper.AddUserIdentity(ticket.Identity); + } + } + } + + private static void OnSendingHeaderCallback(object state) + { + AuthenticationHandler handler = (AuthenticationHandler)state; + handler.ApplyResponseAsync().Wait(); + } + + protected virtual Task InitializeCoreAsync() + { + return Task.FromResult(null); + } + + /// + /// Called once per request after Initialize and Invoke. + /// + /// async completion + internal async Task TeardownAsync() + { + await ApplyResponseAsync(); + await TeardownCoreAsync(); + Request.UnregisterAuthenticationHandler(_registration); + } + + protected virtual Task TeardownCoreAsync() + { + return Task.FromResult(null); + } + + /// + /// Called once by common code after initialization. If an authentication middleware responds directly to + /// specifically known paths it must override this virtual, compare the request path to it's known paths, + /// provide any response information as appropriate, and true to stop further processing. + /// + /// Returning false will cause the common code to call the next middleware in line. Returning true will + /// cause the common code to begin the async completion journey without calling the rest of the middleware + /// pipeline. + public virtual Task InvokeAsync() + { + return Task.FromResult(false); + } + + /// + /// Causes the authentication logic in AuthenticateCore to be performed for the current request + /// at most once and returns the results. Calling Authenticate more than once will always return + /// the original value. + /// + /// This method should always be called instead of calling AuthenticateCore directly. + /// + /// The ticket data provided by the authentication logic + public Task AuthenticateAsync() + { + return LazyInitializer.EnsureInitialized( + ref _authenticate, + ref _authenticateInitialized, + ref _authenticateSyncLock, + AuthenticateCoreAsync); + } + + /// + /// The core authentication logic which must be provided by the handler. Will be invoked at most + /// once per request. Do not call directly, call the wrapping Authenticate method instead. + /// + /// The ticket data provided by the authentication logic + protected abstract Task AuthenticateCoreAsync(); + + /// + /// Causes the ApplyResponseCore to be invoked at most once per request. This method will be + /// invoked either earlier, when the response headers are sent as a result of a response write or flush, + /// or later, as the last step when the original async call to the middleware is returning. + /// + /// + private Task ApplyResponseAsync() + { + return LazyInitializer.EnsureInitialized( + ref _applyResponse, + ref _applyResponseInitialized, + ref _applyResponseSyncLock, + ApplyResponseCoreAsync); + } + + /// + /// Core method that may be overridden by handler. The default behavior is to call two common response + /// activities, one that deals with sign-in/sign-out concerns, and a second to deal with 401 challenges. + /// + /// + protected virtual async Task ApplyResponseCoreAsync() + { + await ApplyResponseGrantAsync(); + await ApplyResponseChallengeAsync(); + } + + /// + /// Override this method to dela with sign-in/sign-out concerns, if an authentication scheme in question + /// deals with grant/revoke as part of it's request flow. (like setting/deleting cookies) + /// + /// + protected virtual Task ApplyResponseGrantAsync() + { + return Task.FromResult(null); + } + + /// + /// Override this method to dela with 401 challenge concerns, if an authentication scheme in question + /// deals an authentication interaction as part of it's request flow. (like adding a response header, or + /// changing the 401 result to 302 of a login page or external sign-in location.) + /// + /// + protected virtual Task ApplyResponseChallengeAsync() + { + return Task.FromResult(null); + } + + protected void GenerateCorrelationId(AuthenticationProperties properties) + { + if (properties == null) + { + throw new ArgumentNullException("properties"); + } + + string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationType; + + var nonceBytes = new byte[32]; + Random.GetBytes(nonceBytes); + string correlationId = TextEncodings.Base64Url.Encode(nonceBytes); + + var cookieOptions = new CookieOptions + { + HttpOnly = true, + Secure = Request.IsSecure + }; + + properties.Dictionary[correlationKey] = correlationId; + + Response.Cookies.Append(correlationKey, correlationId, cookieOptions); + } + + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", + MessageId = "Microsoft.Owin.Logging.LoggerExtensions.WriteWarning(Microsoft.Owin.Logging.ILogger,System.String,System.String[])", + Justification = "Logging is not Localized")] + protected bool ValidateCorrelationId(AuthenticationProperties properties, ILogger logger) + { + if (properties == null) + { + throw new ArgumentNullException("properties"); + } + + string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationType; + + string correlationCookie = Request.Cookies[correlationKey]; + if (string.IsNullOrWhiteSpace(correlationCookie)) + { + logger.WriteWarning("{0} cookie not found.", correlationKey); + return false; + } + + Response.Cookies.Delete(correlationKey); + + string correlationExtra; + if (!properties.Dictionary.TryGetValue( + correlationKey, + out correlationExtra)) + { + logger.WriteWarning("{0} state property not found.", correlationKey); + return false; + } + + properties.Dictionary.Remove(correlationKey); + + if (!string.Equals(correlationCookie, correlationExtra, StringComparison.Ordinal)) + { + logger.WriteWarning("{0} correlation cookie and state property mismatch.", correlationKey); + return false; + } + + return true; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler`1.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler`1.cs new file mode 100644 index 0000000000..ef69d5f023 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationHandler`1.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + /// + /// Base class for the per-request work performed by most authentication middleware. + /// + /// Specifies which type for of AuthenticationOptions property + public abstract class AuthenticationHandler : AuthenticationHandler where TOptions : AuthenticationOptions + { + protected TOptions Options { get; private set; } + + /// + /// Initialize is called once per request to contextualize this instance with appropriate state. + /// + /// The original options passed by the application control behavior + /// The utility object to observe the current request and response + /// async completion + internal Task Initialize(TOptions options, HttpContext context) + { + Options = options; + return BaseInitializeAsync(options, context); + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs new file mode 100644 index 0000000000..a77eb080e6 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationMiddleware.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + public abstract class AuthenticationMiddleware where TOptions : AuthenticationOptions + { + private readonly RequestDelegate _next; + + protected AuthenticationMiddleware(RequestDelegate next, TOptions options) + { + if (options == null) + { + throw new ArgumentNullException("options"); + } + + Options = options; + _next = next; + } + + public TOptions Options { get; set; } + + public override async Task Invoke(HttpContext context) + { + AuthenticationHandler handler = CreateHandler(); + await handler.Initialize(Options, context); + if (!await handler.InvokeAsync()) + { + await _next(context); + } + await handler.TeardownAsync(); + } + + protected abstract AuthenticationHandler CreateHandler(); + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenCreateContext.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenCreateContext.cs new file mode 100644 index 0000000000..228e01e1e5 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenCreateContext.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Security.Provider; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + public class AuthenticationTokenCreateContext : BaseContext + { + private readonly ISecureDataFormat _secureDataFormat; + + public AuthenticationTokenCreateContext( + HttpContext context, + ISecureDataFormat secureDataFormat, + AuthenticationTicket ticket) + : base(context) + { + if (secureDataFormat == null) + { + throw new ArgumentNullException("secureDataFormat"); + } + if (ticket == null) + { + throw new ArgumentNullException("ticket"); + } + _secureDataFormat = secureDataFormat; + Ticket = ticket; + } + + public string Token { get; protected set; } + + public AuthenticationTicket Ticket { get; protected set; } + + public string SerializeTicket() + { + return _secureDataFormat.Protect(Ticket); + } + + public void SetToken(string tokenValue) + { + if (tokenValue == null) + { + throw new ArgumentNullException("tokenValue"); + } + Token = tokenValue; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenProvider.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenProvider.cs new file mode 100644 index 0000000000..c8beb6f0b8 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenProvider.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + public class AuthenticationTokenProvider : IAuthenticationTokenProvider + { + public Action OnCreate { get; set; } + public Func OnCreateAsync { get; set; } + public Action OnReceive { get; set; } + public Func OnReceiveAsync { get; set; } + + public virtual void Create(AuthenticationTokenCreateContext context) + { + if (OnCreateAsync != null && OnCreate == null) + { + throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods); + } + if (OnCreate != null) + { + OnCreate.Invoke(context); + } + } + + public virtual async Task CreateAsync(AuthenticationTokenCreateContext context) + { + if (OnCreateAsync != null && OnCreate == null) + { + throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods); + } + if (OnCreateAsync != null) + { + await OnCreateAsync.Invoke(context); + } + else + { + Create(context); + } + } + + public virtual void Receive(AuthenticationTokenReceiveContext context) + { + if (OnReceiveAsync != null && OnReceive == null) + { + throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods); + } + + if (OnReceive != null) + { + OnReceive.Invoke(context); + } + } + + public virtual async Task ReceiveAsync(AuthenticationTokenReceiveContext context) + { + if (OnReceiveAsync != null && OnReceive == null) + { + throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods); + } + if (OnReceiveAsync != null) + { + await OnReceiveAsync.Invoke(context); + } + else + { + Receive(context); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenReceiveContext.cs b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenReceiveContext.cs new file mode 100644 index 0000000000..ccbc81f020 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/AuthenticationTokenReceiveContext.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Security.Provider; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + public class AuthenticationTokenReceiveContext : BaseContext + { + private readonly ISecureDataFormat _secureDataFormat; + + public AuthenticationTokenReceiveContext( + HttpContext context, + ISecureDataFormat secureDataFormat, + string token) : base(context) + { + if (secureDataFormat == null) + { + throw new ArgumentNullException("secureDataFormat"); + } + if (token == null) + { + throw new ArgumentNullException("token"); + } + _secureDataFormat = secureDataFormat; + Token = token; + } + + public string Token { get; protected set; } + + public AuthenticationTicket Ticket { get; protected set; } + + public void DeserializeTicket(string protectedData) + { + Ticket = _secureDataFormat.Unprotect(protectedData); + } + + public void SetTicket(AuthenticationTicket ticket) + { + if (ticket == null) + { + throw new ArgumentNullException("ticket"); + } + Ticket = ticket; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/Constants.cs b/src/Microsoft.AspNet.Security/Infrastructure/Constants.cs new file mode 100644 index 0000000000..73a8f7958b --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/Constants.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Infrastructure +{ + internal static class Constants + { + public static string SecurityAuthenticate = "security.Authenticate"; + internal const string CorrelationPrefix = ".AspNet.Correlation."; + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/IAuthenticationTokenProvider.cs b/src/Microsoft.AspNet.Security/Infrastructure/IAuthenticationTokenProvider.cs new file mode 100644 index 0000000000..7bda97d3ac --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/IAuthenticationTokenProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + public interface IAuthenticationTokenProvider + { + void Create(AuthenticationTokenCreateContext context); + Task CreateAsync(AuthenticationTokenCreateContext context); + void Receive(AuthenticationTokenReceiveContext context); + Task ReceiveAsync(AuthenticationTokenReceiveContext context); + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/OwinRequestExtensions.cs b/src/Microsoft.AspNet.Security/Infrastructure/OwinRequestExtensions.cs new file mode 100644 index 0000000000..6fafca551d --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/OwinRequestExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + // TODO: comment function documentations + using AuthenticateCallback = Action, IDictionary, object>; + using AuthenticateDelegate = Func, IDictionary, object>, object, Task>; + + internal static class OwinRequestExtensions + { + public static object RegisterAuthenticationHandler(this HttpRequest request, AuthenticationHandler handler) + { + var chained = request.Get(Constants.SecurityAuthenticate); + var hook = new Hook(handler, chained); + request.Set(Constants.SecurityAuthenticate, hook.AuthenticateAsync); + return hook; + } + + public static void UnregisterAuthenticationHandler(this HttpRequest request, object registration) + { + var hook = registration as Hook; + if (hook == null) + { + throw new InvalidOperationException(Resources.Exception_UnhookAuthenticationStateType); + } + request.Set(Constants.SecurityAuthenticate, hook.Chained); + } + + private class Hook + { + private readonly AuthenticationHandler _handler; + + public Hook(AuthenticationHandler handler, AuthenticateDelegate chained) + { + _handler = handler; + Chained = chained; + } + + public AuthenticateDelegate Chained { get; private set; } + + public async Task AuthenticateAsync( + string[] authenticationTypes, + AuthenticateCallback callback, + object state) + { + if (authenticationTypes == null) + { + callback(null, null, _handler.BaseOptions.Description.Properties, state); + } + else if (authenticationTypes.Contains(_handler.BaseOptions.AuthenticationType, StringComparer.Ordinal)) + { + AuthenticationTicket ticket = await _handler.AuthenticateAsync(); + if (ticket != null && ticket.Identity != null) + { + callback(ticket.Identity, ticket.Properties.Dictionary, _handler.BaseOptions.Description.Properties, state); + } + } + if (Chained != null) + { + await Chained(authenticationTypes, callback, state); + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs b/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs new file mode 100644 index 0000000000..a24b7245fd --- /dev/null +++ b/src/Microsoft.AspNet.Security/Infrastructure/SecurityHelper.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Infrastructure +{ + /// + /// Helper code used when implementing authentication middleware + /// + public struct SecurityHelper + { + private readonly HttpContext _context; + + /// + /// Helper code used when implementing authentication middleware + /// + /// + public SecurityHelper(HttpContext context) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + _context = context; + } + + /// + /// Add an additional ClaimsIdentity to the ClaimsPrincipal in the "server.User" environment key + /// + /// + public void AddUserIdentity(IIdentity identity) + { + if (identity == null) + { + throw new ArgumentNullException("identity"); + } + var newClaimsPrincipal = new ClaimsPrincipal(identity); + + IPrincipal existingPrincipal = _context.Request.User; + if (existingPrincipal != null) + { + var existingClaimsPrincipal = existingPrincipal as ClaimsPrincipal; + if (existingClaimsPrincipal == null) + { + IIdentity existingIdentity = existingPrincipal.Identity; + if (existingIdentity.IsAuthenticated) + { + newClaimsPrincipal.AddIdentity(existingIdentity as ClaimsIdentity ?? new ClaimsIdentity(existingIdentity)); + } + } + else + { + foreach (var existingClaimsIdentity in existingClaimsPrincipal.Identities) + { + if (existingClaimsIdentity.IsAuthenticated) + { + newClaimsPrincipal.AddIdentity(existingClaimsIdentity); + } + } + } + } + _context.Request.User = newClaimsPrincipal; + } + + /// + /// Find response challenge details for a specific authentication middleware + /// + /// The authentication type to look for + /// The authentication mode the middleware is running under + /// The information instructing the middleware how it should behave + public AuthenticationResponseChallenge LookupChallenge(string authenticationType, AuthenticationMode authenticationMode) + { + if (authenticationType == null) + { + throw new ArgumentNullException("authenticationType"); + } + + AuthenticationResponseChallenge challenge = _context.Authentication.AuthenticationResponseChallenge; + bool challengeHasAuthenticationTypes = challenge != null && challenge.AuthenticationTypes != null && challenge.AuthenticationTypes.Length != 0; + if (challengeHasAuthenticationTypes == false) + { + return authenticationMode == AuthenticationMode.Active ? (challenge ?? new AuthenticationResponseChallenge(null, null)) : null; + } + foreach (var challengeType in challenge.AuthenticationTypes) + { + if (string.Equals(challengeType, authenticationType, StringComparison.Ordinal)) + { + return challenge; + } + } + return null; + } + + /// + /// Find response sign-in details for a specific authentication middleware + /// + /// The authentication type to look for + /// The information instructing the middleware how it should behave + public AuthenticationResponseGrant LookupSignIn(string authenticationType) + { + if (authenticationType == null) + { + throw new ArgumentNullException("authenticationType"); + } + + AuthenticationResponseGrant grant = _context.Authentication.AuthenticationResponseGrant; + if (grant == null) + { + return null; + } + + foreach (var claimsIdentity in grant.Principal.Identities) + { + if (string.Equals(authenticationType, claimsIdentity.AuthenticationType, StringComparison.Ordinal)) + { + return new AuthenticationResponseGrant(claimsIdentity, grant.Properties ?? new AuthenticationProperties()); + } + } + + return null; + } + + /// + /// Find response sign-out details for a specific authentication middleware + /// + /// The authentication type to look for + /// The authentication mode the middleware is running under + /// The information instructing the middleware how it should behave + public AuthenticationResponseRevoke LookupSignOut(string authenticationType, AuthenticationMode authenticationMode) + { + if (authenticationType == null) + { + throw new ArgumentNullException("authenticationType"); + } + + AuthenticationResponseRevoke revoke = _context.Authentication.AuthenticationResponseRevoke; + if (revoke == null) + { + return null; + } + if (revoke.AuthenticationTypes == null || revoke.AuthenticationTypes.Length == 0) + { + return authenticationMode == AuthenticationMode.Active ? revoke : null; + } + for (int index = 0; index != revoke.AuthenticationTypes.Length; ++index) + { + if (String.Equals(authenticationType, revoke.AuthenticationTypes[index], StringComparison.Ordinal)) + { + return revoke; + } + } + return null; + } + + #region Value-type equality + + public bool Equals(SecurityHelper other) + { + return Equals(_context, other._context); + } + + public override bool Equals(object obj) + { + return obj is SecurityHelper && Equals((SecurityHelper)obj); + } + + public override int GetHashCode() + { + return (_context != null ? _context.GetHashCode() : 0); + } + + public static bool operator ==(SecurityHelper left, SecurityHelper right) + { + return left.Equals(right); + } + + public static bool operator !=(SecurityHelper left, SecurityHelper right) + { + return !left.Equals(right); + } + + #endregion + } +} diff --git a/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs new file mode 100644 index 0000000000..02bb1dbecc --- /dev/null +++ b/src/Microsoft.AspNet.Security/Notifications/AuthenticationFailedNotification.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Security.Notifications +{ + public class AuthenticationFailedNotification + { + public AuthenticationFailedNotification() + { + } + + public bool Cancel { get; set; } + public Exception Exception { get; set; } + public TMessage ProtocolMessage { get; set; } + public int StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs new file mode 100644 index 0000000000..6bf2fed5ff --- /dev/null +++ b/src/Microsoft.AspNet.Security/Notifications/MessageReceivedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Notifications +{ + public class MessageReceivedNotification + { + public MessageReceivedNotification() + { + } + + public bool Cancel { get; set; } + public TMessage ProtocolMessage { get; set; } + public int StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs b/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs new file mode 100644 index 0000000000..d31689aee1 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Notifications/RedirectFromIdentityProviderNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Notifications +{ + public class RedirectFromIdentityProviderNotification + { + public AuthenticationTicket AuthenticationTicket { get; set; } + + public string SignInAsAuthenticationType { get; set; } + + public bool Cancel { get; set; } + + public int StatusCode { get; set; } + + public bool IsRequestCompleted { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs b/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs new file mode 100644 index 0000000000..18d466d563 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Notifications/RedirectToIdentityProviderNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Notifications +{ + public class RedirectToIdentityProviderNotification + { + public RedirectToIdentityProviderNotification() + { + } + + public bool Cancel { get; set; } + public TMessage ProtocolMessage { get; set; } + public int StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs new file mode 100644 index 0000000000..ecd03f77ac --- /dev/null +++ b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenReceivedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Notifications +{ + public class SecurityTokenReceivedNotification + { + public SecurityTokenReceivedNotification() + { + } + + public bool Cancel { get; set; } + public string SecurityToken { get; set; } + public int StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs new file mode 100644 index 0000000000..55f56cad1f --- /dev/null +++ b/src/Microsoft.AspNet.Security/Notifications/SecurityTokenValidatedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security.Notifications +{ + public class SecurityTokenValidatedNotification + { + public SecurityTokenValidatedNotification() + { + } + + public AuthenticationTicket AuthenticationTicket { get; set; } + public bool Cancel { get; set; } + public int StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/Provider/BaseContext.cs b/src/Microsoft.AspNet.Security/Provider/BaseContext.cs new file mode 100644 index 0000000000..c73dbf6b36 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Provider/BaseContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Provider +{ + public abstract class BaseContext + { + protected BaseContext(HttpContext context) + { + HttpContext = context; + } + + public HttpContext HttpContext { get; private set; } + + public HttpRequest Request + { + get { return HttpContext.Request; } + } + + public HttpResponse Response + { + get { return HttpContext.Response; } + } + } +} diff --git a/src/Microsoft.AspNet.Security/Provider/BaseContext`1.cs b/src/Microsoft.AspNet.Security/Provider/BaseContext`1.cs new file mode 100644 index 0000000000..2fd3c8ae22 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Provider/BaseContext`1.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Provider +{ + /// + /// Base class used for certain event contexts + /// + public abstract class BaseContext + { + protected BaseContext(HttpContext context, TOptions options) + { + HttpContext = context; + Options = options; + } + + public HttpContext HttpContext { get; private set; } + + public TOptions Options { get; private set; } + + public HttpRequest Request + { + get { return HttpContext.Request; } + } + + public HttpResponse Response + { + get { return HttpContext.Response; } + } + } +} diff --git a/src/Microsoft.AspNet.Security/Provider/EndpointContext.cs b/src/Microsoft.AspNet.Security/Provider/EndpointContext.cs new file mode 100644 index 0000000000..e210296b9f --- /dev/null +++ b/src/Microsoft.AspNet.Security/Provider/EndpointContext.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Provider +{ + public abstract class EndpointContext : BaseContext + { + protected EndpointContext(HttpContext context) + : base(context) + { + } + + public bool IsRequestCompleted { get; private set; } + + public void RequestCompleted() + { + IsRequestCompleted = true; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Provider/EndpointContext`1.cs b/src/Microsoft.AspNet.Security/Provider/EndpointContext`1.cs new file mode 100644 index 0000000000..e3dd6997cd --- /dev/null +++ b/src/Microsoft.AspNet.Security/Provider/EndpointContext`1.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Provider +{ + /// + /// Base class used for certain event contexts + /// + public abstract class EndpointContext : BaseContext + { + /// + /// Creates an instance of this context + /// + protected EndpointContext(HttpContext context, TOptions options) + : base(context, options) + { + } + + /// + /// True if the request should not be processed further by other components. + /// + public bool IsRequestCompleted { get; private set; } + + /// + /// Prevents the request from being processed further by other components. + /// IsRequestCompleted becomes true after calling. + /// + public void RequestCompleted() + { + IsRequestCompleted = true; + } + } +} diff --git a/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs b/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs new file mode 100644 index 0000000000..6d3e16f671 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Provider/ReturnEndpointContext.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Security.Claims; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Security.Provider +{ + public abstract class ReturnEndpointContext : EndpointContext + { + protected ReturnEndpointContext( + HttpContext context, + AuthenticationTicket ticket) + : base(context) + { + if (ticket != null) + { + Identity = ticket.Identity; + Properties = ticket.Properties; + } + } + + public ClaimsIdentity Identity { get; set; } + public AuthenticationProperties Properties { get; set; } + + public string SignInAsAuthenticationType { get; set; } + + [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "By design")] + public string RedirectUri { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Security/Resources.Designer.cs b/src/Microsoft.AspNet.Security/Resources.Designer.cs new file mode 100644 index 0000000000..f7bfbfaf71 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Resources.Designer.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.AspNet.Security { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Security.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The AuthenticationTokenProvider's required synchronous events have not been registered.. + /// + internal static string Exception_AuthenticationTokenDoesNotProvideSyncMethods { + get { + return ResourceManager.GetString("Exception_AuthenticationTokenDoesNotProvideSyncMethods", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The default data protection provider may only be used when the IAppBuilder.Properties contains an appropriate 'host.AppName' key.. + /// + internal static string Exception_DefaultDpapiRequiresAppNameKey { + get { + return ResourceManager.GetString("Exception_DefaultDpapiRequiresAppNameKey", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A default value for SignInAsAuthenticationType was not found in IAppBuilder Properties. This can happen if your authentication middleware are added in the wrong order, or if one is missing.. + /// + internal static string Exception_MissingDefaultSignInAsAuthenticationType { + get { + return ResourceManager.GetString("Exception_MissingDefaultSignInAsAuthenticationType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The state passed to UnhookAuthentication may only be the return value from HookAuthentication.. + /// + internal static string Exception_UnhookAuthenticationStateType { + get { + return ResourceManager.GetString("Exception_UnhookAuthenticationStateType", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.AspNet.Security/Resources.resx b/src/Microsoft.AspNet.Security/Resources.resx new file mode 100644 index 0000000000..fdd0980662 --- /dev/null +++ b/src/Microsoft.AspNet.Security/Resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The default data protection provider may only be used when the IAppBuilder.Properties contains an appropriate 'host.AppName' key. + + + The state passed to UnhookAuthentication may only be the return value from HookAuthentication. + + + A default value for SignInAsAuthenticationType was not found in IAppBuilder Properties. This can happen if your authentication middleware are added in the wrong order, or if one is missing. + + + The AuthenticationTokenProvider's required synchronous events have not been registered. + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Security/SubjectPublicKeyInfoAlgorithm.cs b/src/Microsoft.AspNet.Security/SubjectPublicKeyInfoAlgorithm.cs new file mode 100644 index 0000000000..2987f68745 --- /dev/null +++ b/src/Microsoft.AspNet.Security/SubjectPublicKeyInfoAlgorithm.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Security +{ + /// + /// The algorithm used to generate the subject public key information blob hashes. + /// + public enum SubjectPublicKeyInfoAlgorithm + { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sha", Justification = "It is correct.")] Sha1, + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sha", Justification = "It is correct.")] Sha256 + } +} diff --git a/src/Microsoft.AspNet.Security/Win32.cs b/src/Microsoft.AspNet.Security/Win32.cs new file mode 100644 index 0000000000..fe26feb7bc --- /dev/null +++ b/src/Microsoft.AspNet.Security/Win32.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Microsoft.Win32 +{ + // TODO: ? [Localizable(false)] + internal static class NativeMethods + { + // ReSharper disable InconsistentNaming + public const int X509_ASN_ENCODING = 0x00000001; + public const int X509_PUBLIC_KEY_INFO = 8; + // ReSharper restore InconsistentNaming + + /// + /// Encodes a structure of the type indicated by the value of the lpszStructType parameter. + /// + /// Type of encoding used. + /// The high-order word is zero, the low-order word specifies the integer identifier for the type of the specified structure so + /// we can use the constants in http://msdn.microsoft.com/en-us/library/windows/desktop/aa378145%28v=vs.85%29.aspx + /// A pointer to the structure to be encoded. + /// A pointer to a buffer to receive the encoded structure. This parameter can be NULL to retrieve the size of this information for memory allocation purposes. + /// A pointer to a DWORD variable that contains the size, in bytes, of the buffer pointed to by the pbEncoded parameter. + /// + [DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptEncodeObject( + UInt32 dwCertEncodingType, + IntPtr lpszStructType, + ref CERT_PUBLIC_KEY_INFO pvStructInfo, + byte[] pbEncoded, + ref UInt32 pcbEncoded); + + // ReSharper disable InconsistentNaming + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct CRYPT_BLOB + { + public Int32 cbData; + public IntPtr pbData; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CERT_CONTEXT + { + public Int32 dwCertEncodingType; + public IntPtr pbCertEncoded; + public Int32 cbCertEncoded; + public IntPtr pCertInfo; + public IntPtr hCertStore; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal struct CRYPT_ALGORITHM_IDENTIFIER + { + public string pszObjId; + public CRYPT_BLOB Parameters; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal struct CRYPT_BIT_BLOB + { + public Int32 cbData; + public IntPtr pbData; + public Int32 cUnusedBits; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct CERT_PUBLIC_KEY_INFO + { + public CRYPT_ALGORITHM_IDENTIFIER Algorithm; + public CRYPT_BIT_BLOB PublicKey; + } + + [StructLayout(LayoutKind.Sequential)] + internal class CERT_INFO + { + public Int32 dwVersion; + public CRYPT_BLOB SerialNumber; + public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; + public CRYPT_BLOB Issuer; + public System.Runtime.InteropServices.ComTypes.FILETIME NotBefore; + public System.Runtime.InteropServices.ComTypes.FILETIME NotAfter; + public CRYPT_BLOB Subject; + public CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo; + public CRYPT_BIT_BLOB IssuerUniqueId; + public CRYPT_BIT_BLOB SubjectUniqueId; + public Int32 cExtension; + public IntPtr rgExtension; + } + + // ReSharper restore InconsistentNaming + } +} diff --git a/src/Microsoft.AspNet.Security/project.json b/src/Microsoft.AspNet.Security/project.json new file mode 100644 index 0000000000..2ef2d0bad6 --- /dev/null +++ b/src/Microsoft.AspNet.Security/project.json @@ -0,0 +1,40 @@ +{ + "version": "0.1-alpha-*", + "dependencies": { + "Microsoft.AspNet.DependencyInjection": "0.1-alpha-*", + "Microsoft.AspNet.ConfigurationModel": "0.1-alpha-*", + "Microsoft.AspNet.PipelineCore": "0.1-alpha-*", + "Microsoft.AspNet.Abstractions": "0.1-alpha-*", + "Microsoft.AspNet.FeatureModel": "0.1-alpha-*", + "Microsoft.AspNet.HttpFeature": "0.1-alpha-*", + "Microsoft.AspNet.Logging": "0.1-alpha-*", + "Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*" + }, + "configurations": { + "net45": {}, + "k10": { + "dependencies": { + "System.Collections": "4.0.0.0", + "System.Console": "4.0.0.0", + "System.ComponentModel": "4.0.0.0", + "System.Diagnostics.Debug": "4.0.10.0", + "System.Diagnostics.Tools": "4.0.0.0", + "System.Globalization": "4.0.10.0", + "System.IO": "4.0.0.0", + "System.IO.Compression": "4.0.0.0", + "System.IO.FileSystem": "4.0.0.0", + "System.IO.FileSystem.Primitives": "4.0.0.0", + "System.Linq": "4.0.0.0", + "System.Reflection": "4.0.10.0", + "System.Resources.ResourceManager": "4.0.0.0", + "System.Runtime": "4.0.20.0", + "System.Runtime.Extensions": "4.0.10.0", + "System.Runtime.InteropServices": "4.0.10.0", + "System.Security.Claims": "0.1-alpha-*", + "System.Security.Principal" : "4.0.0.0", + "System.Threading": "4.0.0.0", + "System.Threading.Tasks": "4.0.0.0" + } + } + } +} \ No newline at end of file