//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace System.IdentityModel { /// /// Provides cookie integrity using signature. /// /// /// /// adds an RSA MAC to /// the cookie data. This provides integrity but not confidentiality. By /// default the MAC uses SHA-256, but SHA-1 may be requested. /// /// /// Cookies signed with this transform may be read /// by any machine that shares the same RSA private key (generally /// associated with an X509 certificate). /// /// public class RsaSignatureCookieTransform : CookieTransform { RSA _signingKey; List _verificationKeys = new List(); string _hashName = "SHA256"; /// /// Creates a new instance of . /// /// The provided key will be used as the signing and verification key by default. /// When the key is null. public RsaSignatureCookieTransform(RSA key) { if (null == key) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("key"); } _signingKey = key; _verificationKeys.Add(_signingKey); } /// /// Creates a new instance of /// /// Certificate whose private key is used to sign and verify. /// When certificate is null. /// When the certificate has no private key. /// When the certificate's key is not RSA. public RsaSignatureCookieTransform(X509Certificate2 certificate) { if (null == certificate) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate"); } _signingKey = X509Util.EnsureAndGetPrivateRSAKey(certificate); _verificationKeys.Add(_signingKey); } /// /// Gets or sets the name of the hash algorithm to use. /// /// /// SHA256 is the default algorithm. This may require a minimum platform of Windows Server 2003 and .NET 3.5 SP1. /// If SHA256 is not supported, set HashName to "SHA1". /// public string HashName { get { return _hashName; } set { using (HashAlgorithm algorithm = CryptoHelper.CreateHashAlgorithm(value)) { if (algorithm == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID6034, value)); } _hashName = value; } } } /// /// Creates a new instance of . /// The instance created by this constructor is not usable until the signing and verification keys are set. /// internal RsaSignatureCookieTransform() { } /// /// Gets or sets the RSA key used for signing /// public virtual RSA SigningKey { get { return _signingKey; } set { _signingKey = value; _verificationKeys = new List(new RSA[] { _signingKey }); } } /// /// Gets the collection of keys used for signature verification. /// By default, this property returns a list containing only the signing key. /// protected virtual ReadOnlyCollection VerificationKeys { get { return _verificationKeys.AsReadOnly(); } } // Format: // SignatureLength : 4-byte big-endian integer // Signature : Octet stream, length is SignatureLength // CookieValue : Octet stream, remainder of message /// /// Verifies the signature. All keys in the collection VerificationKeys will be attempted. /// /// Data previously returned from /// The originally signed data. /// The argument 'encoded' is null. /// The argument 'encoded' contains zero bytes. /// The data is in the wrong format. /// The signature is invalid. /// The platform does not support the requested algorithm. /// There are no verification keys. public override byte[] Decode(byte[] encoded) { if (null == encoded) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("encoded"); } if (0 == encoded.Length) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("encoded", SR.GetString(SR.ID6045)); } ReadOnlyCollection verificationKeys = VerificationKeys; if (0 == verificationKeys.Count) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6036)); } // Decode the message ... int currentIndex = 0; // SignatureLength : 4-byte big-endian integer if (encoded.Length < sizeof(Int32)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1012))); } Int32 signatureLength = BitConverter.ToInt32(encoded, currentIndex); if (signatureLength < 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1005, signatureLength))); } if (signatureLength >= encoded.Length - sizeof(Int32)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(SR.ID1013))); } currentIndex += sizeof(Int32); // Signature : Octet stream, length is SignatureLength byte[] signature = new byte[signatureLength]; Array.Copy(encoded, currentIndex, signature, 0, signature.Length); currentIndex += signature.Length; // CookieValue : Octet stream, remainder of message byte[] cookieValue = new byte[encoded.Length - currentIndex]; Array.Copy(encoded, currentIndex, cookieValue, 0, cookieValue.Length); bool verified = false; try { // Verify the signature using (HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm(HashName)) { hash.ComputeHash(cookieValue); foreach (RSA rsa in verificationKeys) { AsymmetricSignatureDeformatter verifier = GetSignatureDeformatter(rsa); if ((isSha256() && CryptoHelper.VerifySignatureForSha256(verifier, hash, signature)) || verifier.VerifySignature(hash, signature)) { verified = true; break; } } } } // Not all algorithms are supported on all OS catch (CryptographicException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ID6035, HashName, verificationKeys[0].GetType().FullName), e)); } if (!verified) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.ID1014))); } return cookieValue; } /// /// Signs data. /// /// Data to be signed. /// Signed data. /// The argument 'value' is null. /// The argument 'value' contains zero bytes. /// The SigningKey is null. /// The platform does not support the requested algorithm. /// The SigningKey is null, is not an RSACryptoServiceProvider, or does not contain a private key. /// The SigningKey must include the private key in order to sign. public override byte[] Encode(byte[] value) { if (null == value) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value"); } if (0 == value.Length) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID6044)); } RSA signingKey = SigningKey; if (null == signingKey) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6042)); } RSACryptoServiceProvider rsaCryptoServiceProvider = signingKey as RSACryptoServiceProvider; if (rsaCryptoServiceProvider == null && LocalAppContextSwitches.DisableCngCertificates) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6042)); } if (rsaCryptoServiceProvider != null && rsaCryptoServiceProvider.PublicOnly) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID6046)); } // Compute the signature byte[] signature; using (HashAlgorithm hash = CryptoHelper.CreateHashAlgorithm(HashName)) { try { hash.ComputeHash(value); AsymmetricSignatureFormatter signer = GetSignatureFormatter(signingKey); if (isSha256()) { signature = CryptoHelper.CreateSignatureForSha256(signer, hash); } else { signature = signer.CreateSignature(hash); } } // Not all algorithms are supported on all OS catch (CryptographicException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ID6035, HashName, signingKey.GetType().FullName), e)); } } // Get the signature length as a big-endian integer byte[] signatureLength = BitConverter.GetBytes(signature.Length); // Assemble the message ... int currentIndex = 0; byte[] message = new byte[signatureLength.Length + signature.Length + value.Length]; // SignatureLength : 4-byte big endian integer Array.Copy(signatureLength, 0, message, currentIndex, signatureLength.Length); currentIndex += signatureLength.Length; // Signature : Octet stream, length is SignatureLength Array.Copy(signature, 0, message, currentIndex, signature.Length); currentIndex += signature.Length; // CookieValue : Octet stream, remainder of message Array.Copy(value, 0, message, currentIndex, value.Length); return message; } /// /// The default RSACryptoServiceProvider does not support signatures for SHA256. If this is desired, it's necessary to construct a new one. /// AsymmetricSignatureFormatter GetSignatureFormatter(RSA rsa) { RSACryptoServiceProvider rsaProvider = rsa as RSACryptoServiceProvider; if (isSha256() && null != rsaProvider) { return CryptoHelper.GetSignatureFormatterForSha256(rsaProvider); } else { // // If it's SHA-1 or the RSA is not an RsaCSP, just create a formatter using the original RSA. // return new RSAPKCS1SignatureFormatter(rsa); } } AsymmetricSignatureDeformatter GetSignatureDeformatter(RSA rsa) { RSACryptoServiceProvider rsaProvider = rsa as RSACryptoServiceProvider; if (isSha256() && null != rsaProvider) { return CryptoHelper.GetSignatureDeFormatterForSha256(rsaProvider); } else { // // If it's SHA-1 or the RSA is not an RsaCSP, just create a deformatter using the original RSA. // return new RSAPKCS1SignatureDeformatter(rsa); } } /// /// Returns true if the hash algorithm is set to SHA256, false otherwise. /// /// bool isSha256() { return (StringComparer.OrdinalIgnoreCase.Equals(HashName, "SHA256") || StringComparer.OrdinalIgnoreCase.Equals(HashName, "SHA-256") || StringComparer.OrdinalIgnoreCase.Equals(HashName, "System.Security.Cryptography.SHA256")); } } }