1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
//------------------------------------------------------------------------------
// <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;
}
}
}
|