e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
167 lines
9.8 KiB
C#
167 lines
9.8 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="Purpose.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.Security.Cryptography {
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
// Represents a purpose that can be passed to a cryptographic routine to control key derivation / ciphertext modification.
|
|
// This is hardening the crypto routines to prevent playing ciphertext off of components that didn't generate them.
|
|
//
|
|
// !! IMPORTANT !!
|
|
// The built-in purposes do not contain privileged information and are not meant to be treated as secrets. Any external
|
|
// person can disassemble our code or look directly at our source to see what our Purpose objects are used for.
|
|
//
|
|
// PrimaryPurpose: This is a well-known string that identifies the reason for this Purpose. The pattern we use
|
|
// is that PrimaryPurpose is the name of the consumer, making each consumer's Purpose unique.
|
|
//
|
|
// SpecificPurposes: These are extra optional strings that further differentiate Purpose objects that might have the
|
|
// same PrimaryPurpose. The pattern we use is that if a single consumer has multiple Purposes, he can use
|
|
// SpecificPurposes to uniquely identify them. The information here is generally not secret (we can put the type of
|
|
// the currently executing Page here, for example), but it is valid to seed this property with a secret obtained
|
|
// at runtime (such as a nonce shared between two parties).
|
|
|
|
internal sealed class Purpose {
|
|
|
|
// predefined purposes
|
|
public static readonly Purpose AnonymousIdentificationModule_Ticket = new Purpose("AnonymousIdentificationModule.Ticket");
|
|
public static readonly Purpose AssemblyResourceLoader_WebResourceUrl = new Purpose("AssemblyResourceLoader.WebResourceUrl");
|
|
public static readonly Purpose FormsAuthentication_Ticket = new Purpose("FormsAuthentication.Ticket");
|
|
public static readonly Purpose WebForms_Page_PreviousPageID = new Purpose("WebForms.Page.PreviousPageID");
|
|
public static readonly Purpose RolePrincipal_Ticket = new Purpose("RolePrincipal.Ticket");
|
|
public static readonly Purpose ScriptResourceHandler_ScriptResourceUrl = new Purpose("ScriptResourceHandler.ScriptResourceUrl");
|
|
|
|
// predefined ViewState purposes; they won't be used as-is (they're combined with the page information)
|
|
public static readonly Purpose WebForms_ClientScriptManager_EventValidation = new Purpose("WebForms.ClientScriptManager.EventValidation");
|
|
public static readonly Purpose WebForms_DetailsView_KeyTable = new Purpose("WebForms.DetailsView.KeyTable");
|
|
public static readonly Purpose WebForms_GridView_DataKeys = new Purpose("WebForms.GridView.DataKeys");
|
|
public static readonly Purpose WebForms_GridView_SortExpression = new Purpose("WebForms.GridView.SortExpression");
|
|
public static readonly Purpose WebForms_HiddenFieldPageStatePersister_ClientState = new Purpose("WebForms.HiddenFieldPageStatePersister.ClientState");
|
|
public static readonly Purpose WebForms_ScriptManager_HistoryState = new Purpose("WebForms.ScriptManager.HistoryState");
|
|
public static readonly Purpose WebForms_SessionPageStatePersister_ClientState = new Purpose("WebForms.SessionPageStatePersister.ClientState");
|
|
|
|
// predefined miscellaneoous purposes; they won't be used as-is (they're combined with other specificPurposes)
|
|
public static readonly Purpose User_MachineKey_Protect = new Purpose("User.MachineKey.Protect"); // used by the MachineKey static class Protect / Unprotect methods
|
|
public static readonly Purpose User_ObjectStateFormatter_Serialize = new Purpose("User.ObjectStateFormatter.Serialize"); // used by ObjectStateFormatter.Serialize() if called manually
|
|
|
|
public readonly string PrimaryPurpose;
|
|
public readonly string[] SpecificPurposes;
|
|
|
|
private byte[] _derivedKeyLabel;
|
|
private byte[] _derivedKeyContext;
|
|
|
|
public Purpose(string primaryPurpose, params string[] specificPurposes)
|
|
: this(primaryPurpose, specificPurposes, null, null) {
|
|
}
|
|
|
|
// ctor for unit testing
|
|
internal Purpose(string primaryPurpose, string[] specificPurposes, CryptographicKey derivedEncryptionKey, CryptographicKey derivedValidationKey) {
|
|
PrimaryPurpose = primaryPurpose;
|
|
SpecificPurposes = specificPurposes ?? new string[0];
|
|
DerivedEncryptionKey = derivedEncryptionKey;
|
|
DerivedValidationKey = derivedValidationKey;
|
|
SaveDerivedKeys = (SpecificPurposes.Length == 0);
|
|
}
|
|
|
|
// The cryptographic keys that were derived from this Purpose.
|
|
internal CryptographicKey DerivedEncryptionKey { get; private set; }
|
|
internal CryptographicKey DerivedValidationKey { get; private set; }
|
|
|
|
// Whether the derived key should be saved back to this Purpose object by the ICryptoService,
|
|
// e.g. because this Purpose will be used over and over again. We assume that any built-in
|
|
// Purpose object that is passed without any specific purposes is intended for repeated use,
|
|
// hence the ICryptoService will try to cache cryptographic keys as a performance optimization.
|
|
// If specific purposes have been specified, they were likely generated at runtime, hence it
|
|
// is not appropriate for the keys to be cached in this instance.
|
|
internal bool SaveDerivedKeys { get; set; }
|
|
|
|
// Returns a new Purpose which is the specified Purpose plus the specified SpecificPurpose.
|
|
// Leaves the original Purpose unmodified.
|
|
internal Purpose AppendSpecificPurpose(string specificPurpose) {
|
|
// Append the specified specificPurpose to the existing list
|
|
string[] newSpecificPurposes = new string[SpecificPurposes.Length + 1];
|
|
Array.Copy(SpecificPurposes, newSpecificPurposes, SpecificPurposes.Length);
|
|
newSpecificPurposes[newSpecificPurposes.Length - 1] = specificPurpose;
|
|
return new Purpose(PrimaryPurpose, newSpecificPurposes);
|
|
}
|
|
|
|
// Returns a new Purpose which is the specified Purpose plus the specified SpecificPurposes.
|
|
// Leaves the original Purpose unmodified.
|
|
internal Purpose AppendSpecificPurposes(IList<string> specificPurposes) {
|
|
// No specific purposes to add
|
|
if (specificPurposes == null || specificPurposes.Count == 0) {
|
|
return this;
|
|
}
|
|
|
|
// Append the specified specificPurposes to the existing list
|
|
string[] newSpecificPurposes = new string[SpecificPurposes.Length + specificPurposes.Count];
|
|
Array.Copy(SpecificPurposes, newSpecificPurposes, SpecificPurposes.Length);
|
|
specificPurposes.CopyTo(newSpecificPurposes, SpecificPurposes.Length);
|
|
return new Purpose(PrimaryPurpose, newSpecificPurposes);
|
|
}
|
|
|
|
public CryptographicKey GetDerivedEncryptionKey(IMasterKeyProvider masterKeyProvider, KeyDerivationFunction keyDerivationFunction) {
|
|
// has a key already been stored?
|
|
CryptographicKey actualDerivedKey = DerivedEncryptionKey;
|
|
if (actualDerivedKey == null) {
|
|
CryptographicKey masterKey = masterKeyProvider.GetEncryptionKey();
|
|
actualDerivedKey = keyDerivationFunction(masterKey, this);
|
|
|
|
// only save the key back to storage if this Purpose is configured to do so
|
|
if (SaveDerivedKeys) {
|
|
DerivedEncryptionKey = actualDerivedKey;
|
|
}
|
|
}
|
|
|
|
return actualDerivedKey;
|
|
}
|
|
|
|
public CryptographicKey GetDerivedValidationKey(IMasterKeyProvider masterKeyProvider, KeyDerivationFunction keyDerivationFunction) {
|
|
// has a key already been stored?
|
|
CryptographicKey actualDerivedKey = DerivedValidationKey;
|
|
if (actualDerivedKey == null) {
|
|
CryptographicKey masterKey = masterKeyProvider.GetValidationKey();
|
|
actualDerivedKey = keyDerivationFunction(masterKey, this);
|
|
|
|
// only save the key back to storage if this Purpose is configured to do so
|
|
if (SaveDerivedKeys) {
|
|
DerivedValidationKey = actualDerivedKey;
|
|
}
|
|
}
|
|
|
|
return actualDerivedKey;
|
|
}
|
|
|
|
// Returns a label and context suitable for passing into the SP800-108 KDF.
|
|
internal void GetKeyDerivationParameters(out byte[] label, out byte[] context) {
|
|
// The primary purpose can just be used as the label directly, since ASP.NET
|
|
// is always in full control of the primary purpose (it's never user-specified).
|
|
if (_derivedKeyLabel == null) {
|
|
_derivedKeyLabel = CryptoUtil.SecureUTF8Encoding.GetBytes(PrimaryPurpose);
|
|
}
|
|
|
|
// The specific purposes (which can contain nonce, identity, etc.) are concatenated
|
|
// together to form the context. The BinaryWriter class prepends each element with
|
|
// a 7-bit encoded length to guarantee uniqueness.
|
|
if (_derivedKeyContext == null) {
|
|
using (MemoryStream stream = new MemoryStream())
|
|
using (BinaryWriter writer = new BinaryWriter(stream, CryptoUtil.SecureUTF8Encoding)) {
|
|
foreach (string specificPurpose in SpecificPurposes) {
|
|
writer.Write(specificPurpose);
|
|
}
|
|
_derivedKeyContext = stream.ToArray();
|
|
}
|
|
}
|
|
|
|
label = _derivedKeyLabel;
|
|
context = _derivedKeyContext;
|
|
}
|
|
|
|
}
|
|
}
|