Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Base / MS / Internal / IO / Packaging / CompoundFile / RightsManagementEncryptionTransform.cs / 1 / RightsManagementEncryptionTransform.cs
//------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // This class implements the RM data transform for a compound file. // // History: // 06/03/2002: IgorBel: Initial implementation. // 08/15/2002: LGolding: In the 07/17/2002 drop of the RM SDK, the server has // changed from ULTNGSTN01 to TungstenTest07, and the // activation URL has changed. // 05/29/2003: LGolding: Ported to WCP tree. // 06/10/2003: IgorBel: Ported to Krypton APIs // 04/07/2005: LGolding: Ported to managed wrappers around Promethium APIs. // //----------------------------------------------------------------------------- // Allow use of presharp warning numbers [6518] unknown to the compiler #pragma warning disable 1634, 1691 using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Packaging; using System.Text; using System.Windows; using System.Security.RightsManagement; using MS.Internal.IO.Packaging.CompoundFile; using MS.Internal.Utility; using CU = MS.Internal.IO.Packaging.CompoundFile.ContainerUtilities; namespace MS.Internal.IO.Packaging.CompoundFile { ////// This class implements the IDataTransform interface for the transform that /// implements RM encryption in a compound file. /// internal class RightsManagementEncryptionTransform : IDataTransform { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors ////// internal RightsManagementEncryptionTransform( TransformEnvironment transformEnvironment ) { Debug.Assert(transformEnvironment != null); Stream instanceDataStream = transformEnvironment.GetPrimaryInstanceData(); Debug.Assert(instanceDataStream != null, SR.Get(SRID.NoPublishLicenseStream)); _useLicenseStorage = transformEnvironment.GetInstanceDataStorage(); Debug.Assert(_useLicenseStorage != null, SR.Get(SRID.NoUseLicenseStorage)); // Create a wrapper that manages persistence and comparison of FormatVersion // in our InstanceData stream. We can read/write to this stream as needed (though CompressionTransform // does not because we don't house any non-FormatVersion data in the instance data stream). // We need to give out our current code version so it can compare with any file version as appropriate. _publishLicenseStream = new VersionedStreamOwner( instanceDataStream, new FormatVersion(FeatureName, MinimumReaderVersion, MinimumUpdaterVersion, CurrentFeatureVersion)); } #endregion Constructors //------------------------------------------------------ // // Public Methods // //----------------------------------------------------- #region Public Methods ////// Constructor. /// ////// Every transform class is required to have a constructor with this signature. /// The compound file code invokes this constructor via reflection for each transform /// when it builds the transform stack for a dataspace. The transform object will /// use the properties of the ///parameter /// to locate and extract the transform's "instance data" from the compound file. /// /// The instance data for a RightsManagementEncryptionTransform consists of a /// PublishLicense object and zero or more UseLicense objects, each associated with /// a user. /// ////// Read the publish license from the RM transform's primary instance data stream. /// ////// The publish license, or null if the compound file does not contain a publish /// license (as it will not, for example, when the compound file is first created). /// ////// If the stream is corrupt, or if the RM instance data in this file cannot be /// read by the current version of this class. /// internal PublishLicense LoadPublishLicense() { if (_publishLicenseStream.Length <= 0) return null; // We seek to position 0 but under the covers, the VersionedStream maintains a FormatVersion // structure before our logical position zero. _publishLicenseStream.Seek(0, SeekOrigin.Begin); // // Construct a BinaryReader to read the rest of the instance data. // // Although BinaryReader is IDisposable, we must not Close or Dispose it, // as that would close the underlying stream, which we do not own. Simply // allowing the BinaryReader to be finalized after it goes out of scope // does -not- close the underlying stream. // // Suppress 6518 Local IDisposable object not disposed: // Reason: The stream is not owned by the BlockManager, therefore we cannot // close the BinaryWriter, as that would Close the stream underneath. #pragma warning disable 6518 BinaryReader utf8Reader = new BinaryReader(_publishLicenseStream, Encoding.UTF8); #pragma warning restore 6518 // // There follows a variable-length header (not to be confused with the physical // stream header). This header allows future expansion, in case we want to store // something in addition to the publish license in the primary instance data stream // for this transform. The first field in the header is the header length in bytes // (including the headerLen field itself). // Int32 headerLen = utf8Reader.ReadInt32(); if (headerLen < CU.Int32Size) { throw new FileFormatException(SR.Get(SRID.PublishLicenseStreamCorrupt)); } if (headerLen > MaxPublishLicenseHeaderLen) { throw new FileFormatException( SR.Get(SRID.PublishLicenseStreamHeaderTooLong, headerLen, MaxPublishLicenseHeaderLen )); } // // Save any additional bytes in the header that we don't recognize, so we can // write them back out later if necessary. We've already read the headerLen field, // so subtract the size of that field from the amount we have to save. // // No need to use checked{} here since we already made sure that header length is greater than Int32Size Int32 numPublishLicenseHeaderExtraBytes = headerLen - CU.Int32Size; if (numPublishLicenseHeaderExtraBytes > 0) { _publishLicenseHeaderExtraBytes = new byte [numPublishLicenseHeaderExtraBytes]; if (PackagingUtilities.ReliableRead(_publishLicenseStream, _publishLicenseHeaderExtraBytes, 0, numPublishLicenseHeaderExtraBytes) != numPublishLicenseHeaderExtraBytes) { throw new FileFormatException(SR.Get(SRID.PublishLicenseStreamCorrupt)); } } // // Read the publish license as a length-prefixed UTF-8 string. If the stream // is shorter than the length prefix implies, an exception will be thrown. // _publishLicense = new PublishLicense( ReadLengthPrefixedString( utf8Reader, Encoding.UTF8, PublishLicenseLengthMax ) ); return _publishLicense; } ////// Save the publish license to the RM transform's instance data stream. /// /// /// The publish licence to be saved. The RM server returns a publish license as a string. /// ////// The stream is rewritten from the beginning, so any existing publish license is /// overwritten. /// ////// If ///is null. /// /// If the existing RM instance data in this file cannot be updated by the current version /// of this class. /// ////// If the transform settings are fixed. /// internal void SavePublishLicense( PublishLicense publishLicense ) { if (publishLicense == null) { throw new ArgumentNullException("publishLicense"); } if (_fixedSettings) { throw new InvalidOperationException(SR.Get(SRID.CannotChangePublishLicense)); } // We seek to position 0 but under the covers, the VersionedStream maintains a FormatVersion // structure before our logical position zero. _publishLicenseStream.Seek(0, SeekOrigin.Begin); // // Construct a BinaryWriter to write the rest of the instance data. // // Although BinaryWriter is IDisposable, we must not Close or Dispose it, // as that would close the underlying stream, which we do not own. Simply // allowing the BinaryWriter to be finalized after it goes out of scope // does -not- close the underlying stream. // // Suppress 6518 Local IDisposable object not disposed: // Reason: The stream is not owned by the BlockManager, therefore we cannot // close the BinaryWriter, as that would Close the stream underneath. #pragma warning disable 6518 BinaryWriter utf8Writer = new BinaryWriter(_publishLicenseStream, Encoding.UTF8); #pragma warning restore 6518 // // There follows a variable-length header (not to be confused with the physical // stream header). This header allows future expansion, in case we want to store // something in addition to the publish license in the primary instance data stream // for this transform. In this version, there is no additional information, so the // header consists only of the headerLen field itself, so its length is 4 bytes. // // // If we have previously read in the publish license stream from a file whose format // included extra header bytes that we didn't interpret, write those bytes back out // (and include them in the header length). // Int32 headerLen = CU.Int32Size; if (_publishLicenseHeaderExtraBytes != null) { checked { headerLen += _publishLicenseHeaderExtraBytes.Length; } } utf8Writer.Write(headerLen); if (_publishLicenseHeaderExtraBytes != null) { _publishLicenseStream.Write( _publishLicenseHeaderExtraBytes, 0, _publishLicenseHeaderExtraBytes.Length ); } // // Write out the publish license as a length-prefixed, UTF-8 encoded string. // WriteByteLengthPrefixedDwordPaddedString(publishLicense.ToString(), utf8Writer, Encoding.UTF8); utf8Writer.Flush(); _publishLicense = publishLicense; } ////// Load a use license for the specified user from the RM transform's instance data /// storage in the compound file. /// /// /// The user whose use license is desired. /// ////// The use license for the specified user, or null if the compound file does not /// contain a use license for the specified user. /// ////// If ///is null. /// /// If the RM information in this file cannot be read by the current version of /// this class. /// internal UseLicense LoadUseLicense( ContentUser user ) { if (user == null) { throw new ArgumentNullException("user"); } LoadUseLicenseForUserParams param = new LoadUseLicenseForUserParams(user); EnumUseLicenseStreams( new UseLicenseStreamCallback(this.LoadUseLicenseForUser), param ); return param.UseLicense; } ////// Save a use license for the specified user into the RM transform's instance data /// storage in the compound file. /// /// /// The user to whom the use license was issued. /// /// /// The use license issued to that user. /// ////// Any existing use license for the specified user is removed from the compound /// file before the new use license is saved. /// ////// If ///or is null. /// /// If the RM information in this file cannot be written by the current version of /// this class. /// internal void SaveUseLicense( ContentUser user, UseLicense useLicense ) { if (user == null) { throw new ArgumentNullException("user"); } if (useLicense == null) { throw new ArgumentNullException("useLicense"); } if (user.AuthenticationType != AuthenticationType.Windows && user.AuthenticationType != AuthenticationType.Passport) { throw new ArgumentException( SR.Get(SRID.OnlyPassportOrWindowsAuthenticatedUsersAreAllowed), "user" ); } // // Delete any existing use license for this user. // EnumUseLicenseStreams( new UseLicenseStreamCallback(this.DeleteUseLicenseForUser), user ); // // Save the new use license for this user in a new stream. // SaveUseLicenseForUser(user, useLicense); } ////// Delete the use license for the specified user from the RM transform's instance /// data storage in the compound file. /// /// /// The user whose use license is to be deleted. /// ////// If ///is null. /// /// If the RM information in this file cannot be updated by the current version of /// this class. /// internal void DeleteUseLicense( ContentUser user ) { if (user == null) { throw new ArgumentNullException("user"); } EnumUseLicenseStreams( new UseLicenseStreamCallback(this.DeleteUseLicenseForUser), user ); } ////// This method retrieves a reference to a dictionary with keys of type User and values /// of type UseLicense, containing one entry for each use license embedded in the compound /// file for this particular transform instance. The collection is a snapshot of the use /// licenses in the compound file at the time of the call. The term "Embedded" in the method /// name emphasizes that the dictionary returned by this method only includes those use /// licenses that are embedded in the compound file. It does not include any other use /// licenses that the application might have acquired from an RM server but not yet embedded /// into the compound file. /// internal IDictionaryGetEmbeddedUseLicenses() { UserUseLicenseDictionaryLoader loader = new UserUseLicenseDictionaryLoader (this); return new ReadOnlyDictionary (loader.LoadedDictionary); } #endregion Public Methods //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties #region IDataTransform Properties /// /// Returns a value indicating whether this transform is ready for use. /// public bool IsReady { get { return (_cryptoProvider != null) && (_cryptoProvider.CanDecrypt ||_cryptoProvider.CanEncrypt); } } ////// public bool FixedSettings { get { return _fixedSettings; } } ////// Returns true when the transform expects no further changes to its state. /// The contract is that if FixedSettings is false, an application can change /// any of the transform’s properties with the promise that an exception will /// not be thrown. /// ////// For the RightsManagementEncryptionTransform, FixedSettings becomes true the /// first time the compound file code calls the object’s GetTransformedStream method. /// After that, any attempt to set the CryptoProvider property, or to call /// SavePublicLicense, throws InvalidOperationException. /// ////// Returns a value that tells the Data Space Manager how to interpret the /// value of this transform's TransformIdentifierProperty. /// ////// The original design of the Data Space Manager allowed for 3rd parties to /// implement their own transform classes. These transforms might be identified /// by the assembly-qualified name of the managed class that implements the /// transform, or by the CLSID of the COM object that implements the transform. /// A transform's TransformIdentifierType property was intended to tell the Data /// Space Manager whether the transform's TransformIdentifier property was to /// be interpreted as a managed class name, a CLSID, or something else. The only /// value of TransformIdentifierType that the Data Space Manager actually supports /// is TransformIdentifierTypes_PredefinedTransformName, which tells the Data /// Space Manager that the TransformIdentifier is one of a small number of well- /// known strings that identify the built-in transforms (compression and encryption). /// internal int TransformIdentifierType { get { return DataSpaceManager.TransformIdentifierTypes_PredefinedTransformName; } } ////// Returns a value that identifies the transform implemented by this object. /// public object TransformIdentifier { get { return RightsManagementEncryptionTransform.ClassTransformIdentifier; } } #endregion IDataTransform Properties #region RightsManagementEncryptionTransform Properties ////// This property represents the CryptoProvider object that will be used to determine /// what operations the current user is allowed to perform on the encrypted content. /// internal CryptoProvider CryptoProvider { get { return _cryptoProvider; } set { if (_fixedSettings) { throw new InvalidOperationException(SR.Get(SRID.CannotChangeCryptoProvider)); } if (value == null) { throw new ArgumentNullException("value"); } if (!value.CanEncrypt && !value.CanDecrypt) { throw new ArgumentException(SR.Get(SRID.CryptoProviderIsNotReady), "value"); } _cryptoProvider = value; } } ////// Expose the transform identifier for the use of the DataSpaceManager. /// internal static string ClassTransformIdentifier { get { return "{C73DFACD-061F-43B0-8B64-0C620D2A8B50}"; } } #endregion RightsManagementEncryptionTransform Properties #endregion Public Properties //----------------------------------------------------- // // Interface Implementation Methods // //------------------------------------------------------ #region Interface Implementation Methods ////// Creates a stream into which the compound file code will write cleartext bytes, /// and from which it will read cleartext bytes. When the compound file code writes /// bytes to this stream, they will be encrypted and then written to the underlying /// stream (encodedStream). When the compound file code reads bytes from this /// stream, they will be read from the underlying stream and then decrypted. /// /// /// The underlying stream, into which encrypted bytes are written and from which /// encrypted bytes are read. /// /// /// No longer used. In the past, this dictionary was used to communicate information /// such as the contents of the publish license to clients. Arbitrary key/value pairs /// could be written into the dictionary. The Stream-derived class returned by this /// method was expected to expose IDictionary, and the dictionary methods were expected /// to access the information in the transformContext. This mechanism is no longer needed. /// The POR is that this parameter will be removed. /// ////// The stream into which the compound file code writes cleartext bytes. /// ////// This method is used only by the DataSpaceManager, so we declare it as an explicit /// interface implementation to hide it from the public interface. /// Stream IDataTransform.GetTransformedStream( Stream encodedStream, IDictionary transformContext ) { // // The compound file code shouldn't be calling us until it's made us ready. // Debug.Assert(this.IsReady); // // After a stream has been handed out, we can't change settings any more. // _fixedSettings = true; Stream s = new RightsManagementEncryptedStream(encodedStream, _cryptoProvider); // let the versioned stream update our FormatVersion information automatically return new VersionedStream(s, _publishLicenseStream); } #endregion Interface Implementation Methods //----------------------------------------------------- // // Internal Nested Types // //----------------------------------------------------- #region Internal Nested Types ////// Delegate type used by EnumUseLicenseStreams. /// internal delegate void UseLicenseStreamCallback( RightsManagementEncryptionTransform rmet, StreamInfo si, object param, ref bool stop ); #endregion Internal Nested Types //----------------------------------------------------- // // Internal Methods // //------------------------------------------------------ ////// Enumerate the use license streams in the compound file, invoking a caller- /// supplied delegate for each one. /// /// /// The delegate to be invoked for each use license stream. /// /// /// A caller-supplied parameter to be passed to the callback. Can be null if /// the callback requires no additional information. /// ////// The use license streams are all the streams in the use license storage /// whose names begin with a certain prefix. /// ////// If ///is null. /// /// If the RM information in this file cannot be read by the current version of /// this class. /// internal void EnumUseLicenseStreams( UseLicenseStreamCallback callback, object param ) { if (callback == null) { throw new ArgumentNullException("callback"); } bool stop = false; foreach (StreamInfo si in _useLicenseStorage.GetStreams()) { // Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) if (String.CompareOrdinal( LicenseStreamNamePrefix.ToUpperInvariant(), 0, si.Name.ToUpperInvariant(), 0, LicenseStreamNamePrefixLength ) == 0) { callback(this, si, param, ref stop); if (stop) { break; } } } } ////// Load the use license, and the user to whom it was issued, from the specified /// stream. /// /// /// The UTF 8 BinaryReader from which the use license and user are to be loaded. /// /// /// The user specified in the stream. /// ////// The use license from the stream. /// ////// This method is internal rather than private because it is used by /// UserUseLicenseDictionaryLoader.AddUseLicenseFromStreamToDictionary. /// internal UseLicense LoadUseLicenseAndUserFromStream( BinaryReader utf8Reader, out ContentUser user ) { utf8Reader.BaseStream.Seek(0, SeekOrigin.Begin); // // The stream begins with a header of the following format: // // Int32 headerLength // Int32 userNameLen // Byte userName[userNameLen] // // ... and then continues with: // // In32 useLicenseLen // Byte useLicense[useLicenseLen]; // Int32 headerLength = utf8Reader.ReadInt32(); if (headerLength < UseLicenseStreamLengthMin) { throw new FileFormatException(SR.Get(SRID.UseLicenseStreamCorrupt)); } // // The type-prefixed user name string (e.g., "windows:domain\alias") was // treated as a sequence of little-Endian UTF-16 characters. The octet // sequence representing those characters in that encoding were Base-64 // encoded. The resulting character string was then UTF-8 encoded. The // resulting byte-length-prefixed UTF-8 byte sequence is what was stored // in the use license stream. // string base64UserName = ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UserNameLengthMax); byte[] userNameBytes = Convert.FromBase64String(base64UserName); string typePrefixedUserName = new string( _unicodeEncoding.GetChars(userNameBytes) ); // // Create and return the user object specified by the type-prefixed name. // If the type-prefixed name is not in a valid format, a FileFormatException // will be thrown. // AuthenticationType authenticationType; string userName; ParseTypePrefixedUserName(typePrefixedUserName, out authenticationType, out userName); user = new ContentUser(userName, authenticationType); // // Read the use license as a length-prefixed string, and return it. If the stream // is shorter than the length prefix implies, an exception will be thrown. // return new UseLicense( ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UseLicenseLengthMax) ); } ////// Callback function used by LoadUseLicense. Called once for each use license /// stream in the compound file. Extracts the use license for the specified /// user. /// /// /// The object that knows how to extract license information from the compound file. /// /// /// A stream containing a user/user license pair. /// /// /// Caller-supplied parameter to EnumUseLicenseStreams. In this case, it is a /// LoadUseLicenseForUserParams object. /// /// /// Set to true if the callback function wants to stop the enumeration. This callback /// function never wants to stop the enumeration, so this parameter is not used. /// private void LoadUseLicenseForUser( RightsManagementEncryptionTransform rmet, StreamInfo si, object param, ref bool stop ) { LoadUseLicenseForUserParams lulfup = param as LoadUseLicenseForUserParams; if (lulfup == null) { throw new ArgumentException(SR.Get(SRID.CallbackParameterInvalid), "param"); } ContentUser userDesired = lulfup.User; Debug.Assert(userDesired != null); ContentUser userFromStream = null; using (Stream stream = si.GetStream(FileMode.Open, FileAccess.Read)) { using (BinaryReader utf8Reader = new BinaryReader(stream, Encoding.UTF8)) { userFromStream = rmet.LoadUserFromStream(utf8Reader); if (userFromStream.GenericEquals(userDesired)) { lulfup.UseLicense = rmet.LoadUseLicenseFromStream(utf8Reader); stop = true; } } } } ////// Callback function used by SaveUseLicense. Called once for each use license /// stream in the compound file. Deletes the use license for the specified /// user. /// /// /// The object that knows how the arrangement of license information in the compound file. /// /// /// A stream containing a User-UseLicense pair. /// /// /// Caller-supplied parameter to EnumUseLicenseStreams. In this case, it is a /// ContentUser object. /// /// /// Set to true if the callback function wants to stop the enumeration. This callback /// function never wants to stop the enumeration; it wants to located and delete /// -all- use license streams for the user specified by, /// even though, if the file was created using our APIs, there will never be /// more than one. /// private void DeleteUseLicenseForUser( RightsManagementEncryptionTransform rmet, StreamInfo si, object param, ref bool stop ) { ContentUser userToDelete = param as ContentUser; if (userToDelete == null) { throw new ArgumentException(SR.Get(SRID.CallbackParameterInvalid), "param"); } ContentUser userFromStream = null; using (Stream stream = si.GetStream(FileMode.Open, FileAccess.Read)) { using (BinaryReader utf8Reader = new BinaryReader(stream, Encoding.UTF8)) { userFromStream = rmet.LoadUserFromStream(utf8Reader); } } if (userFromStream.GenericEquals(userToDelete)) { si.Delete(); } } /// /// Create a use license stream containing the use license for the specified /// user. /// /// /// The user whose use license is to be saved. /// /// /// The use license for the specified user. /// ////// SaveUseLicense has removed any existing use licenses for this /// user before calling this internal function. /// internal void SaveUseLicenseForUser( ContentUser user, UseLicense useLicense ) { // // Generate a unique name for the use license stream, and create the stream. // string useLicenseStreamName = MakeUseLicenseStreamName(); StreamInfo si = new StreamInfo(_useLicenseStorage, useLicenseStreamName); // This guarantees a call to Stream.Dispose, which is equivalent to Stream.Close. using (Stream licenseStream = si.Create()) { // Create a BinaryWriter on the stream. using (BinaryWriter utf8Writer = new BinaryWriter(licenseStream, Encoding.UTF8)) { // // Construct a type-prefixed user name of the form // "Passport:[....]_smith@hotmail.com" or "Windows:domain\username", // depending on the authentication type of the user. // string typePrefixedUserName = MakeTypePrefixedUserName(user); // // For compatibility with Office, Base64 encode the type-prefixed user name // for the sake of some minimal obfuscation. The parameters to the // UnicodeEncoding ctor mean: "UTF-16 little-endian, no byte order mark". // Then convert the Base64 characters to UTF-8 encoding. // byte [] userNameBytes = _unicodeEncoding.GetBytes(typePrefixedUserName); string base64UserName = Convert.ToBase64String(userNameBytes); byte [] utf8Bytes = Encoding.UTF8.GetBytes(base64UserName); Int32 utf8ByteLength = utf8Bytes.Length; // // Write out a header preceding the use license. The header is of the form: // Int32 headerLength // Int32 userNameLength (in bytes) // Byte userName[userNameLength] // Byte paddings Int32 headerLength = checked ( 2 * CU.Int32Size + utf8ByteLength + CU.CalculateDWordPadBytesLength(utf8ByteLength)); utf8Writer.Write(headerLength); utf8Writer.Write(utf8ByteLength); utf8Writer.Write(utf8Bytes, 0, utf8ByteLength); WriteDwordPadding(utf8ByteLength, utf8Writer); // // Write out the use license itself. // WriteByteLengthPrefixedDwordPaddedString(useLicense.ToString(), utf8Writer, Encoding.UTF8); } } } //----------------------------------------------------- // // Private Nested Types // //------------------------------------------------------ #region Private Nested Types ////// This structure is passed by LoadUseLicense as the callback parameter to /// LoadUseLicenseForUser. LoadUseLicense initializes this structure with the /// user whose use license is desired. LoadUseLicenseForUser sets the UseLicense /// property when and if it encounters a use license for the specified user. /// private class LoadUseLicenseForUserParams { ////// Constructor. /// /// /// The user whose use license is desired. /// internal LoadUseLicenseForUserParams( ContentUser user ) { _user = user; _useLicense = null; } ////// The user whose use license is desired. /// internal ContentUser User { get { return _user; } } ////// The use license for the specified user. /// internal UseLicense UseLicense { get { return _useLicense; } set { _useLicense = value; } } private ContentUser _user; private UseLicense _useLicense; } #endregion Private Nested Types //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods ////// Convert a byte array into a string of printable characters by using each sequence /// of 5 bits as an index into a table of printable characters. /// /// /// Array containing the bytes to be base-32 encoded. /// ////// This function dose NOT produce a proper Base32Encoding since it will NOT produce proper padding. /// private static char[] Base32EncodeWithoutPadding( byte[] bytes ) { int numBytes = bytes.Length; int numBits = checked (numBytes * 8); int numChars = numBits / 5; // No need to do checked{} since numChars = numBits / 5 where numBits is int. if (numBits % 5 != 0) ++numChars; char[] chars = new char[numChars]; for (int iChar = 0; iChar < numChars; ++iChar) { // Starting bit offset from start of byte array. // No need to use checked{} here since iChar cannot be bigger than numChars which cannot be // bigger than (max int / 5) int iBitStart = iChar * 5; // Index into encoding table. int index = 0; for (int iBit = iBitStart; iBit - iBitStart < 5 && iBit < numBits; ++iBit) { int iByte = iBit / 8; int iBitWithinByte = iBit % 8; if ((bytes[iByte] & (1 << iBitWithinByte)) != 0) index += (1 << (iBit - iBitStart)); } chars[iChar] = Base32EncodingTable[index]; } return chars; } ////// Create a unique name for a stream to hold a use license, of the form /// "EUL-<Base32-encoded GUID>". /// private static string MakeUseLicenseStreamName() { return LicenseStreamNamePrefix + new string(Base32EncodeWithoutPadding(Guid.NewGuid().ToByteArray())); } ////// Construct a type-prefixed user name of the form "Passport:[....]_smith@hotmail.com" /// or "Windows:domain\username", depending on the authentication type of the User. /// /// /// The user whose type-prefixed name is to be constructed. /// private static string MakeTypePrefixedUserName( ContentUser user ) { // Use 9 since we don't do extra allocation for user.AuthenticationType.ToString() // to get the accurate length StringBuilder userName = new StringBuilder(9 /* for Windows: or Passport: */ + user.Name.Length); userName.Append(user.AuthenticationType.ToString()); userName.Append(':'); userName.Append(user.Name); return userName.ToString(); } ////// Parse a type-prefixed user name of the form "Passport:[....]_smith@hotmail.com" /// or "Windows:domain\username" into its "authentication type" and "user name" /// components (the parts before and after the colon, respectively). /// /// /// The string to be parsed. /// /// /// Specifies whether the string represents a Windows or Passport user ID. /// /// /// The user's ID. /// private static void ParseTypePrefixedUserName( string typePrefixedUserName, out AuthenticationType authenticationType, out string userName ) { // // We don't actually know the authentication type yet, and we might find that // the type-prefixed user name doesn't even specify a valid authentication // type. But we have to assign to authenticationType because it's an out // parameter. // authenticationType = AuthenticationType.Windows; int colonIndex = typePrefixedUserName.IndexOf(':'); if (colonIndex < 1 || colonIndex >= typePrefixedUserName.Length - 1) { throw new FileFormatException(SR.Get(SRID.InvalidTypePrefixedUserName)); } // No need to use checked{} here since colonIndex cannot be >= to (max int - 1) userName = typePrefixedUserName.Substring(colonIndex + 1); string authenticationTypeString = typePrefixedUserName.Substring(0, colonIndex); bool validEnum = false; // user names: case-insensitive comparison if (((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals( authenticationTypeString, Enum.GetName(typeof(AuthenticationType), AuthenticationType.Windows))) { authenticationType = AuthenticationType.Windows; validEnum = true; } else if (((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals( authenticationTypeString, Enum.GetName(typeof(AuthenticationType), AuthenticationType.Passport))) { authenticationType = AuthenticationType.Passport; validEnum = true; } // // Didn't find a matching enumeration constant. // if (!validEnum) { throw new FileFormatException( SR.Get( SRID.InvalidAuthenticationTypeString, typePrefixedUserName ) ); } } ////// Load the use license from the specified stream. /// /// /// The Utf 8 BinaryReader from which the use license is to be loaded. /// ////// The use license from the stream. /// ////// For details of the stream format, see the comments in LoadUseLicenseAndUserFromStream. /// private UseLicense LoadUseLicenseFromStream( BinaryReader utf8Reader ) { utf8Reader.BaseStream.Seek(0, SeekOrigin.Begin); Int32 headerLength = utf8Reader.ReadInt32(); if (headerLength < UseLicenseStreamLengthMin) { throw new FileFormatException(SR.Get(SRID.UseLicenseStreamCorrupt)); } // // Skip over the type-prefixed user name string, because we only want the // use license. // ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UserNameLengthMax); return new UseLicense( ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UseLicenseLengthMax) ); } ////// Load the user to whom a use license was issued from the specified stream. /// /// /// The Utf8 BinaryReader from which the user is to be loaded. /// ////// The user specified in the stream. /// ////// For details of the stream format, see the comments in LoadUseLicenseAndUserFromStream. /// private ContentUser LoadUserFromStream( BinaryReader utf8Reader ) { utf8Reader.BaseStream.Seek(0, SeekOrigin.Begin); Int32 headerLength = utf8Reader.ReadInt32(); if (headerLength < UseLicenseStreamLengthMin) { throw new FileFormatException(SR.Get(SRID.UseLicenseStreamCorrupt)); } string base64UserName = ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UserNameLengthMax); byte[] userNameBytes = Convert.FromBase64String(base64UserName); string typePrefixedUserName = new string( _unicodeEncoding.GetChars(userNameBytes) ); // // Create and return the user object specified by the type-prefixed name. // If the type-prefixed name is not in a valid format, a FileFormatException // will be thrown. // AuthenticationType authenticationType; string userName; ParseTypePrefixedUserName(typePrefixedUserName, out authenticationType, out userName); return new ContentUser(userName, authenticationType); } ////// Read a string, encoded according to the specified encoding, and prefixed by the /// length in bytes of the encoded string. /// /// /// Binary reader from which the string is read. /// /// /// Object that specifies how the string has been encoded. /// /// /// The maximum number of characters that the string can contain. This prevents a malformed /// file with a huge length prefix from making us allocate all our memory. /// private static string ReadLengthPrefixedString( BinaryReader reader, Encoding encoding, int maxLength ) { Int32 length = reader.ReadInt32(); if (length > maxLength) { throw new FileFormatException(SR.Get(SRID.ExcessiveLengthPrefix, length, maxLength)); } byte[] bytes = reader.ReadBytes(length); if (bytes.Length != length) { throw new FileFormatException(SR.Get(SRID.InvalidStringFormat)); } string s = encoding.GetString(bytes); SkipDwordPadding(bytes.Length, reader); return s; } ////// Skip past the DWORD padding bytes at the end of a string of the specified length. /// /// /// Length in bytes of the string that was read. /// /// /// Binary reader from which the string was read. /// private static void SkipDwordPadding( int length, BinaryReader reader ) { int extra = length % CU.Int32Size; if (extra != 0) { // No need to use checked{} here since we already made sure that extra is smaller than Int32Size byte[] bytes = reader.ReadBytes(CU.Int32Size - extra); if (bytes.Length != CU.Int32Size - extra) { throw new FileFormatException(SR.Get(SRID.InvalidStringFormat)); } } } ////// Write out the number of bytes needed to DWORD align a string of the specified /// length. Per the file format spec, the bytes must be 0s. /// private static void WriteDwordPadding( int length, BinaryWriter writer ) { int extra = length % CU.Int32Size; if (extra != 0) { // No need to use checked{} here since we already made sure that extra is smaller than Int32Size writer.Write(Padding, 0, CU.Int32Size - extra); } } ////// Write out a string in the specified encoding, preceded by the length in bytes /// of the encoded string. Pad the string with 0s to a DWORD boundary. The padding /// is not included in the length prefix. /// private static void WriteByteLengthPrefixedDwordPaddedString( string s, BinaryWriter writer, Encoding encoding ) { byte[] bytes = encoding.GetBytes(s); Int32 length = bytes.Length; writer.Write(length); // // NOTE: If we wrote out the string with "Write(string)", the // writer would precede the output with the UTF-8-encoded array // length, which we don't want since we need to write the length // ourselves. Use Encoding class to get the bytes and call "Write(bytes) // to avoid that problem. // writer.Write(bytes); WriteDwordPadding(length, writer); } #endregion Private Methods //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- #region Private Fields private CryptoProvider _cryptoProvider; private PublishLicense _publishLicense; private bool _fixedSettings; // // Text encoding object used to read or write other strings used in the file // format. (false = little-endian, false = no byte order mark). // // NOTE: It doesn't always matter which encoding object we use. It matters when // we're reading or writing character data, but not when we're writing numeric // or byte data. // private static readonly UnicodeEncoding _unicodeEncoding = new UnicodeEncoding(false, false); // // The stream in which the FormatVersion and publish license is stored. // private VersionedStreamOwner _publishLicenseStream; // // Uninterpreted bytes from the publish license stream header. // private byte[] _publishLicenseHeaderExtraBytes; // // The storage under which use licenses are stored. // private StorageInfo _useLicenseStorage; // // All use licenses reside in streams whose names begin with this prefix: // private const string LicenseStreamNamePrefix = "EUL-"; private static readonly int LicenseStreamNamePrefixLength = LicenseStreamNamePrefix.Length; // // The RM version information for the current version of this class. // private const string FeatureName = "Microsoft.Metadata.DRMTransform"; // // // Maximum permitted length, in characters, of a publish license. // private const int PublishLicenseLengthMax = 1000000; // // Maximum permitted length, in characters, of a use license. // private const int UseLicenseLengthMax = 1000000; // // Maximum permitted length, in characters, of a base-64-encoded type-prefixed user name. // private const int UserNameLengthMax = 1000; // // Minimum possible length, in bytes, of the header information in a user license // stream. The format of the header is: // Int32 headerLength In bytes // Int32 userNameLength In bytes // Byte userName[userNameLength] // So the shortest possible header, when userNameLength is 1 byte, is 2 Int32s // plus a Byte. // private static readonly int UseLicenseStreamLengthMin = 2 * CU.Int32Size + SizeofByte; private static readonly VersionPair CurrentFeatureVersion = new VersionPair(1,0); // // The minimum version number that can read the file format that this version of // the software writes. // private static readonly VersionPair MinimumReaderVersion = new VersionPair(1, 0); // // The minimum version number that can update the file format that this version of // the software writes. // private static readonly VersionPair MinimumUpdaterVersion = new VersionPair(1, 0); // // Maximum permitted length of the variable-length header in the publish // license stream. This limit is a security issue. Since the first 4 bytes // of the header specify the header length, and since we will allocate a // buffer to hold the header contents, we don't want somebody to give us // a malformed file that specifies a header of length 2^31 or so. // private const int MaxPublishLicenseHeaderLen = 4096; private static readonly char[] Base32EncodingTable = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '=' }; // // Used to DWORD-align a stream after writing a string to it: // private static readonly byte[] Padding = {0, 0, 0}; private const int SizeofByte = 1; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // This class implements the RM data transform for a compound file. // // History: // 06/03/2002: IgorBel: Initial implementation. // 08/15/2002: LGolding: In the 07/17/2002 drop of the RM SDK, the server has // changed from ULTNGSTN01 to TungstenTest07, and the // activation URL has changed. // 05/29/2003: LGolding: Ported to WCP tree. // 06/10/2003: IgorBel: Ported to Krypton APIs // 04/07/2005: LGolding: Ported to managed wrappers around Promethium APIs. // //----------------------------------------------------------------------------- // Allow use of presharp warning numbers [6518] unknown to the compiler #pragma warning disable 1634, 1691 using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Packaging; using System.Text; using System.Windows; using System.Security.RightsManagement; using MS.Internal.IO.Packaging.CompoundFile; using MS.Internal.Utility; using CU = MS.Internal.IO.Packaging.CompoundFile.ContainerUtilities; namespace MS.Internal.IO.Packaging.CompoundFile { ////// This class implements the IDataTransform interface for the transform that /// implements RM encryption in a compound file. /// internal class RightsManagementEncryptionTransform : IDataTransform { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors ////// internal RightsManagementEncryptionTransform( TransformEnvironment transformEnvironment ) { Debug.Assert(transformEnvironment != null); Stream instanceDataStream = transformEnvironment.GetPrimaryInstanceData(); Debug.Assert(instanceDataStream != null, SR.Get(SRID.NoPublishLicenseStream)); _useLicenseStorage = transformEnvironment.GetInstanceDataStorage(); Debug.Assert(_useLicenseStorage != null, SR.Get(SRID.NoUseLicenseStorage)); // Create a wrapper that manages persistence and comparison of FormatVersion // in our InstanceData stream. We can read/write to this stream as needed (though CompressionTransform // does not because we don't house any non-FormatVersion data in the instance data stream). // We need to give out our current code version so it can compare with any file version as appropriate. _publishLicenseStream = new VersionedStreamOwner( instanceDataStream, new FormatVersion(FeatureName, MinimumReaderVersion, MinimumUpdaterVersion, CurrentFeatureVersion)); } #endregion Constructors //------------------------------------------------------ // // Public Methods // //----------------------------------------------------- #region Public Methods ////// Constructor. /// ////// Every transform class is required to have a constructor with this signature. /// The compound file code invokes this constructor via reflection for each transform /// when it builds the transform stack for a dataspace. The transform object will /// use the properties of the ///parameter /// to locate and extract the transform's "instance data" from the compound file. /// /// The instance data for a RightsManagementEncryptionTransform consists of a /// PublishLicense object and zero or more UseLicense objects, each associated with /// a user. /// ////// Read the publish license from the RM transform's primary instance data stream. /// ////// The publish license, or null if the compound file does not contain a publish /// license (as it will not, for example, when the compound file is first created). /// ////// If the stream is corrupt, or if the RM instance data in this file cannot be /// read by the current version of this class. /// internal PublishLicense LoadPublishLicense() { if (_publishLicenseStream.Length <= 0) return null; // We seek to position 0 but under the covers, the VersionedStream maintains a FormatVersion // structure before our logical position zero. _publishLicenseStream.Seek(0, SeekOrigin.Begin); // // Construct a BinaryReader to read the rest of the instance data. // // Although BinaryReader is IDisposable, we must not Close or Dispose it, // as that would close the underlying stream, which we do not own. Simply // allowing the BinaryReader to be finalized after it goes out of scope // does -not- close the underlying stream. // // Suppress 6518 Local IDisposable object not disposed: // Reason: The stream is not owned by the BlockManager, therefore we cannot // close the BinaryWriter, as that would Close the stream underneath. #pragma warning disable 6518 BinaryReader utf8Reader = new BinaryReader(_publishLicenseStream, Encoding.UTF8); #pragma warning restore 6518 // // There follows a variable-length header (not to be confused with the physical // stream header). This header allows future expansion, in case we want to store // something in addition to the publish license in the primary instance data stream // for this transform. The first field in the header is the header length in bytes // (including the headerLen field itself). // Int32 headerLen = utf8Reader.ReadInt32(); if (headerLen < CU.Int32Size) { throw new FileFormatException(SR.Get(SRID.PublishLicenseStreamCorrupt)); } if (headerLen > MaxPublishLicenseHeaderLen) { throw new FileFormatException( SR.Get(SRID.PublishLicenseStreamHeaderTooLong, headerLen, MaxPublishLicenseHeaderLen )); } // // Save any additional bytes in the header that we don't recognize, so we can // write them back out later if necessary. We've already read the headerLen field, // so subtract the size of that field from the amount we have to save. // // No need to use checked{} here since we already made sure that header length is greater than Int32Size Int32 numPublishLicenseHeaderExtraBytes = headerLen - CU.Int32Size; if (numPublishLicenseHeaderExtraBytes > 0) { _publishLicenseHeaderExtraBytes = new byte [numPublishLicenseHeaderExtraBytes]; if (PackagingUtilities.ReliableRead(_publishLicenseStream, _publishLicenseHeaderExtraBytes, 0, numPublishLicenseHeaderExtraBytes) != numPublishLicenseHeaderExtraBytes) { throw new FileFormatException(SR.Get(SRID.PublishLicenseStreamCorrupt)); } } // // Read the publish license as a length-prefixed UTF-8 string. If the stream // is shorter than the length prefix implies, an exception will be thrown. // _publishLicense = new PublishLicense( ReadLengthPrefixedString( utf8Reader, Encoding.UTF8, PublishLicenseLengthMax ) ); return _publishLicense; } ////// Save the publish license to the RM transform's instance data stream. /// /// /// The publish licence to be saved. The RM server returns a publish license as a string. /// ////// The stream is rewritten from the beginning, so any existing publish license is /// overwritten. /// ////// If ///is null. /// /// If the existing RM instance data in this file cannot be updated by the current version /// of this class. /// ////// If the transform settings are fixed. /// internal void SavePublishLicense( PublishLicense publishLicense ) { if (publishLicense == null) { throw new ArgumentNullException("publishLicense"); } if (_fixedSettings) { throw new InvalidOperationException(SR.Get(SRID.CannotChangePublishLicense)); } // We seek to position 0 but under the covers, the VersionedStream maintains a FormatVersion // structure before our logical position zero. _publishLicenseStream.Seek(0, SeekOrigin.Begin); // // Construct a BinaryWriter to write the rest of the instance data. // // Although BinaryWriter is IDisposable, we must not Close or Dispose it, // as that would close the underlying stream, which we do not own. Simply // allowing the BinaryWriter to be finalized after it goes out of scope // does -not- close the underlying stream. // // Suppress 6518 Local IDisposable object not disposed: // Reason: The stream is not owned by the BlockManager, therefore we cannot // close the BinaryWriter, as that would Close the stream underneath. #pragma warning disable 6518 BinaryWriter utf8Writer = new BinaryWriter(_publishLicenseStream, Encoding.UTF8); #pragma warning restore 6518 // // There follows a variable-length header (not to be confused with the physical // stream header). This header allows future expansion, in case we want to store // something in addition to the publish license in the primary instance data stream // for this transform. In this version, there is no additional information, so the // header consists only of the headerLen field itself, so its length is 4 bytes. // // // If we have previously read in the publish license stream from a file whose format // included extra header bytes that we didn't interpret, write those bytes back out // (and include them in the header length). // Int32 headerLen = CU.Int32Size; if (_publishLicenseHeaderExtraBytes != null) { checked { headerLen += _publishLicenseHeaderExtraBytes.Length; } } utf8Writer.Write(headerLen); if (_publishLicenseHeaderExtraBytes != null) { _publishLicenseStream.Write( _publishLicenseHeaderExtraBytes, 0, _publishLicenseHeaderExtraBytes.Length ); } // // Write out the publish license as a length-prefixed, UTF-8 encoded string. // WriteByteLengthPrefixedDwordPaddedString(publishLicense.ToString(), utf8Writer, Encoding.UTF8); utf8Writer.Flush(); _publishLicense = publishLicense; } ////// Load a use license for the specified user from the RM transform's instance data /// storage in the compound file. /// /// /// The user whose use license is desired. /// ////// The use license for the specified user, or null if the compound file does not /// contain a use license for the specified user. /// ////// If ///is null. /// /// If the RM information in this file cannot be read by the current version of /// this class. /// internal UseLicense LoadUseLicense( ContentUser user ) { if (user == null) { throw new ArgumentNullException("user"); } LoadUseLicenseForUserParams param = new LoadUseLicenseForUserParams(user); EnumUseLicenseStreams( new UseLicenseStreamCallback(this.LoadUseLicenseForUser), param ); return param.UseLicense; } ////// Save a use license for the specified user into the RM transform's instance data /// storage in the compound file. /// /// /// The user to whom the use license was issued. /// /// /// The use license issued to that user. /// ////// Any existing use license for the specified user is removed from the compound /// file before the new use license is saved. /// ////// If ///or is null. /// /// If the RM information in this file cannot be written by the current version of /// this class. /// internal void SaveUseLicense( ContentUser user, UseLicense useLicense ) { if (user == null) { throw new ArgumentNullException("user"); } if (useLicense == null) { throw new ArgumentNullException("useLicense"); } if (user.AuthenticationType != AuthenticationType.Windows && user.AuthenticationType != AuthenticationType.Passport) { throw new ArgumentException( SR.Get(SRID.OnlyPassportOrWindowsAuthenticatedUsersAreAllowed), "user" ); } // // Delete any existing use license for this user. // EnumUseLicenseStreams( new UseLicenseStreamCallback(this.DeleteUseLicenseForUser), user ); // // Save the new use license for this user in a new stream. // SaveUseLicenseForUser(user, useLicense); } ////// Delete the use license for the specified user from the RM transform's instance /// data storage in the compound file. /// /// /// The user whose use license is to be deleted. /// ////// If ///is null. /// /// If the RM information in this file cannot be updated by the current version of /// this class. /// internal void DeleteUseLicense( ContentUser user ) { if (user == null) { throw new ArgumentNullException("user"); } EnumUseLicenseStreams( new UseLicenseStreamCallback(this.DeleteUseLicenseForUser), user ); } ////// This method retrieves a reference to a dictionary with keys of type User and values /// of type UseLicense, containing one entry for each use license embedded in the compound /// file for this particular transform instance. The collection is a snapshot of the use /// licenses in the compound file at the time of the call. The term "Embedded" in the method /// name emphasizes that the dictionary returned by this method only includes those use /// licenses that are embedded in the compound file. It does not include any other use /// licenses that the application might have acquired from an RM server but not yet embedded /// into the compound file. /// internal IDictionaryGetEmbeddedUseLicenses() { UserUseLicenseDictionaryLoader loader = new UserUseLicenseDictionaryLoader (this); return new ReadOnlyDictionary (loader.LoadedDictionary); } #endregion Public Methods //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties #region IDataTransform Properties /// /// Returns a value indicating whether this transform is ready for use. /// public bool IsReady { get { return (_cryptoProvider != null) && (_cryptoProvider.CanDecrypt ||_cryptoProvider.CanEncrypt); } } ////// public bool FixedSettings { get { return _fixedSettings; } } ////// Returns true when the transform expects no further changes to its state. /// The contract is that if FixedSettings is false, an application can change /// any of the transform’s properties with the promise that an exception will /// not be thrown. /// ////// For the RightsManagementEncryptionTransform, FixedSettings becomes true the /// first time the compound file code calls the object’s GetTransformedStream method. /// After that, any attempt to set the CryptoProvider property, or to call /// SavePublicLicense, throws InvalidOperationException. /// ////// Returns a value that tells the Data Space Manager how to interpret the /// value of this transform's TransformIdentifierProperty. /// ////// The original design of the Data Space Manager allowed for 3rd parties to /// implement their own transform classes. These transforms might be identified /// by the assembly-qualified name of the managed class that implements the /// transform, or by the CLSID of the COM object that implements the transform. /// A transform's TransformIdentifierType property was intended to tell the Data /// Space Manager whether the transform's TransformIdentifier property was to /// be interpreted as a managed class name, a CLSID, or something else. The only /// value of TransformIdentifierType that the Data Space Manager actually supports /// is TransformIdentifierTypes_PredefinedTransformName, which tells the Data /// Space Manager that the TransformIdentifier is one of a small number of well- /// known strings that identify the built-in transforms (compression and encryption). /// internal int TransformIdentifierType { get { return DataSpaceManager.TransformIdentifierTypes_PredefinedTransformName; } } ////// Returns a value that identifies the transform implemented by this object. /// public object TransformIdentifier { get { return RightsManagementEncryptionTransform.ClassTransformIdentifier; } } #endregion IDataTransform Properties #region RightsManagementEncryptionTransform Properties ////// This property represents the CryptoProvider object that will be used to determine /// what operations the current user is allowed to perform on the encrypted content. /// internal CryptoProvider CryptoProvider { get { return _cryptoProvider; } set { if (_fixedSettings) { throw new InvalidOperationException(SR.Get(SRID.CannotChangeCryptoProvider)); } if (value == null) { throw new ArgumentNullException("value"); } if (!value.CanEncrypt && !value.CanDecrypt) { throw new ArgumentException(SR.Get(SRID.CryptoProviderIsNotReady), "value"); } _cryptoProvider = value; } } ////// Expose the transform identifier for the use of the DataSpaceManager. /// internal static string ClassTransformIdentifier { get { return "{C73DFACD-061F-43B0-8B64-0C620D2A8B50}"; } } #endregion RightsManagementEncryptionTransform Properties #endregion Public Properties //----------------------------------------------------- // // Interface Implementation Methods // //------------------------------------------------------ #region Interface Implementation Methods ////// Creates a stream into which the compound file code will write cleartext bytes, /// and from which it will read cleartext bytes. When the compound file code writes /// bytes to this stream, they will be encrypted and then written to the underlying /// stream (encodedStream). When the compound file code reads bytes from this /// stream, they will be read from the underlying stream and then decrypted. /// /// /// The underlying stream, into which encrypted bytes are written and from which /// encrypted bytes are read. /// /// /// No longer used. In the past, this dictionary was used to communicate information /// such as the contents of the publish license to clients. Arbitrary key/value pairs /// could be written into the dictionary. The Stream-derived class returned by this /// method was expected to expose IDictionary, and the dictionary methods were expected /// to access the information in the transformContext. This mechanism is no longer needed. /// The POR is that this parameter will be removed. /// ////// The stream into which the compound file code writes cleartext bytes. /// ////// This method is used only by the DataSpaceManager, so we declare it as an explicit /// interface implementation to hide it from the public interface. /// Stream IDataTransform.GetTransformedStream( Stream encodedStream, IDictionary transformContext ) { // // The compound file code shouldn't be calling us until it's made us ready. // Debug.Assert(this.IsReady); // // After a stream has been handed out, we can't change settings any more. // _fixedSettings = true; Stream s = new RightsManagementEncryptedStream(encodedStream, _cryptoProvider); // let the versioned stream update our FormatVersion information automatically return new VersionedStream(s, _publishLicenseStream); } #endregion Interface Implementation Methods //----------------------------------------------------- // // Internal Nested Types // //----------------------------------------------------- #region Internal Nested Types ////// Delegate type used by EnumUseLicenseStreams. /// internal delegate void UseLicenseStreamCallback( RightsManagementEncryptionTransform rmet, StreamInfo si, object param, ref bool stop ); #endregion Internal Nested Types //----------------------------------------------------- // // Internal Methods // //------------------------------------------------------ ////// Enumerate the use license streams in the compound file, invoking a caller- /// supplied delegate for each one. /// /// /// The delegate to be invoked for each use license stream. /// /// /// A caller-supplied parameter to be passed to the callback. Can be null if /// the callback requires no additional information. /// ////// The use license streams are all the streams in the use license storage /// whose names begin with a certain prefix. /// ////// If ///is null. /// /// If the RM information in this file cannot be read by the current version of /// this class. /// internal void EnumUseLicenseStreams( UseLicenseStreamCallback callback, object param ) { if (callback == null) { throw new ArgumentNullException("callback"); } bool stop = false; foreach (StreamInfo si in _useLicenseStorage.GetStreams()) { // Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) if (String.CompareOrdinal( LicenseStreamNamePrefix.ToUpperInvariant(), 0, si.Name.ToUpperInvariant(), 0, LicenseStreamNamePrefixLength ) == 0) { callback(this, si, param, ref stop); if (stop) { break; } } } } ////// Load the use license, and the user to whom it was issued, from the specified /// stream. /// /// /// The UTF 8 BinaryReader from which the use license and user are to be loaded. /// /// /// The user specified in the stream. /// ////// The use license from the stream. /// ////// This method is internal rather than private because it is used by /// UserUseLicenseDictionaryLoader.AddUseLicenseFromStreamToDictionary. /// internal UseLicense LoadUseLicenseAndUserFromStream( BinaryReader utf8Reader, out ContentUser user ) { utf8Reader.BaseStream.Seek(0, SeekOrigin.Begin); // // The stream begins with a header of the following format: // // Int32 headerLength // Int32 userNameLen // Byte userName[userNameLen] // // ... and then continues with: // // In32 useLicenseLen // Byte useLicense[useLicenseLen]; // Int32 headerLength = utf8Reader.ReadInt32(); if (headerLength < UseLicenseStreamLengthMin) { throw new FileFormatException(SR.Get(SRID.UseLicenseStreamCorrupt)); } // // The type-prefixed user name string (e.g., "windows:domain\alias") was // treated as a sequence of little-Endian UTF-16 characters. The octet // sequence representing those characters in that encoding were Base-64 // encoded. The resulting character string was then UTF-8 encoded. The // resulting byte-length-prefixed UTF-8 byte sequence is what was stored // in the use license stream. // string base64UserName = ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UserNameLengthMax); byte[] userNameBytes = Convert.FromBase64String(base64UserName); string typePrefixedUserName = new string( _unicodeEncoding.GetChars(userNameBytes) ); // // Create and return the user object specified by the type-prefixed name. // If the type-prefixed name is not in a valid format, a FileFormatException // will be thrown. // AuthenticationType authenticationType; string userName; ParseTypePrefixedUserName(typePrefixedUserName, out authenticationType, out userName); user = new ContentUser(userName, authenticationType); // // Read the use license as a length-prefixed string, and return it. If the stream // is shorter than the length prefix implies, an exception will be thrown. // return new UseLicense( ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UseLicenseLengthMax) ); } ////// Callback function used by LoadUseLicense. Called once for each use license /// stream in the compound file. Extracts the use license for the specified /// user. /// /// /// The object that knows how to extract license information from the compound file. /// /// /// A stream containing a user/user license pair. /// /// /// Caller-supplied parameter to EnumUseLicenseStreams. In this case, it is a /// LoadUseLicenseForUserParams object. /// /// /// Set to true if the callback function wants to stop the enumeration. This callback /// function never wants to stop the enumeration, so this parameter is not used. /// private void LoadUseLicenseForUser( RightsManagementEncryptionTransform rmet, StreamInfo si, object param, ref bool stop ) { LoadUseLicenseForUserParams lulfup = param as LoadUseLicenseForUserParams; if (lulfup == null) { throw new ArgumentException(SR.Get(SRID.CallbackParameterInvalid), "param"); } ContentUser userDesired = lulfup.User; Debug.Assert(userDesired != null); ContentUser userFromStream = null; using (Stream stream = si.GetStream(FileMode.Open, FileAccess.Read)) { using (BinaryReader utf8Reader = new BinaryReader(stream, Encoding.UTF8)) { userFromStream = rmet.LoadUserFromStream(utf8Reader); if (userFromStream.GenericEquals(userDesired)) { lulfup.UseLicense = rmet.LoadUseLicenseFromStream(utf8Reader); stop = true; } } } } ////// Callback function used by SaveUseLicense. Called once for each use license /// stream in the compound file. Deletes the use license for the specified /// user. /// /// /// The object that knows how the arrangement of license information in the compound file. /// /// /// A stream containing a User-UseLicense pair. /// /// /// Caller-supplied parameter to EnumUseLicenseStreams. In this case, it is a /// ContentUser object. /// /// /// Set to true if the callback function wants to stop the enumeration. This callback /// function never wants to stop the enumeration; it wants to located and delete /// -all- use license streams for the user specified by, /// even though, if the file was created using our APIs, there will never be /// more than one. /// private void DeleteUseLicenseForUser( RightsManagementEncryptionTransform rmet, StreamInfo si, object param, ref bool stop ) { ContentUser userToDelete = param as ContentUser; if (userToDelete == null) { throw new ArgumentException(SR.Get(SRID.CallbackParameterInvalid), "param"); } ContentUser userFromStream = null; using (Stream stream = si.GetStream(FileMode.Open, FileAccess.Read)) { using (BinaryReader utf8Reader = new BinaryReader(stream, Encoding.UTF8)) { userFromStream = rmet.LoadUserFromStream(utf8Reader); } } if (userFromStream.GenericEquals(userToDelete)) { si.Delete(); } } /// /// Create a use license stream containing the use license for the specified /// user. /// /// /// The user whose use license is to be saved. /// /// /// The use license for the specified user. /// ////// SaveUseLicense has removed any existing use licenses for this /// user before calling this internal function. /// internal void SaveUseLicenseForUser( ContentUser user, UseLicense useLicense ) { // // Generate a unique name for the use license stream, and create the stream. // string useLicenseStreamName = MakeUseLicenseStreamName(); StreamInfo si = new StreamInfo(_useLicenseStorage, useLicenseStreamName); // This guarantees a call to Stream.Dispose, which is equivalent to Stream.Close. using (Stream licenseStream = si.Create()) { // Create a BinaryWriter on the stream. using (BinaryWriter utf8Writer = new BinaryWriter(licenseStream, Encoding.UTF8)) { // // Construct a type-prefixed user name of the form // "Passport:[....]_smith@hotmail.com" or "Windows:domain\username", // depending on the authentication type of the user. // string typePrefixedUserName = MakeTypePrefixedUserName(user); // // For compatibility with Office, Base64 encode the type-prefixed user name // for the sake of some minimal obfuscation. The parameters to the // UnicodeEncoding ctor mean: "UTF-16 little-endian, no byte order mark". // Then convert the Base64 characters to UTF-8 encoding. // byte [] userNameBytes = _unicodeEncoding.GetBytes(typePrefixedUserName); string base64UserName = Convert.ToBase64String(userNameBytes); byte [] utf8Bytes = Encoding.UTF8.GetBytes(base64UserName); Int32 utf8ByteLength = utf8Bytes.Length; // // Write out a header preceding the use license. The header is of the form: // Int32 headerLength // Int32 userNameLength (in bytes) // Byte userName[userNameLength] // Byte paddings Int32 headerLength = checked ( 2 * CU.Int32Size + utf8ByteLength + CU.CalculateDWordPadBytesLength(utf8ByteLength)); utf8Writer.Write(headerLength); utf8Writer.Write(utf8ByteLength); utf8Writer.Write(utf8Bytes, 0, utf8ByteLength); WriteDwordPadding(utf8ByteLength, utf8Writer); // // Write out the use license itself. // WriteByteLengthPrefixedDwordPaddedString(useLicense.ToString(), utf8Writer, Encoding.UTF8); } } } //----------------------------------------------------- // // Private Nested Types // //------------------------------------------------------ #region Private Nested Types ////// This structure is passed by LoadUseLicense as the callback parameter to /// LoadUseLicenseForUser. LoadUseLicense initializes this structure with the /// user whose use license is desired. LoadUseLicenseForUser sets the UseLicense /// property when and if it encounters a use license for the specified user. /// private class LoadUseLicenseForUserParams { ////// Constructor. /// /// /// The user whose use license is desired. /// internal LoadUseLicenseForUserParams( ContentUser user ) { _user = user; _useLicense = null; } ////// The user whose use license is desired. /// internal ContentUser User { get { return _user; } } ////// The use license for the specified user. /// internal UseLicense UseLicense { get { return _useLicense; } set { _useLicense = value; } } private ContentUser _user; private UseLicense _useLicense; } #endregion Private Nested Types //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods ////// Convert a byte array into a string of printable characters by using each sequence /// of 5 bits as an index into a table of printable characters. /// /// /// Array containing the bytes to be base-32 encoded. /// ////// This function dose NOT produce a proper Base32Encoding since it will NOT produce proper padding. /// private static char[] Base32EncodeWithoutPadding( byte[] bytes ) { int numBytes = bytes.Length; int numBits = checked (numBytes * 8); int numChars = numBits / 5; // No need to do checked{} since numChars = numBits / 5 where numBits is int. if (numBits % 5 != 0) ++numChars; char[] chars = new char[numChars]; for (int iChar = 0; iChar < numChars; ++iChar) { // Starting bit offset from start of byte array. // No need to use checked{} here since iChar cannot be bigger than numChars which cannot be // bigger than (max int / 5) int iBitStart = iChar * 5; // Index into encoding table. int index = 0; for (int iBit = iBitStart; iBit - iBitStart < 5 && iBit < numBits; ++iBit) { int iByte = iBit / 8; int iBitWithinByte = iBit % 8; if ((bytes[iByte] & (1 << iBitWithinByte)) != 0) index += (1 << (iBit - iBitStart)); } chars[iChar] = Base32EncodingTable[index]; } return chars; } ////// Create a unique name for a stream to hold a use license, of the form /// "EUL-<Base32-encoded GUID>". /// private static string MakeUseLicenseStreamName() { return LicenseStreamNamePrefix + new string(Base32EncodeWithoutPadding(Guid.NewGuid().ToByteArray())); } ////// Construct a type-prefixed user name of the form "Passport:[....]_smith@hotmail.com" /// or "Windows:domain\username", depending on the authentication type of the User. /// /// /// The user whose type-prefixed name is to be constructed. /// private static string MakeTypePrefixedUserName( ContentUser user ) { // Use 9 since we don't do extra allocation for user.AuthenticationType.ToString() // to get the accurate length StringBuilder userName = new StringBuilder(9 /* for Windows: or Passport: */ + user.Name.Length); userName.Append(user.AuthenticationType.ToString()); userName.Append(':'); userName.Append(user.Name); return userName.ToString(); } ////// Parse a type-prefixed user name of the form "Passport:[....]_smith@hotmail.com" /// or "Windows:domain\username" into its "authentication type" and "user name" /// components (the parts before and after the colon, respectively). /// /// /// The string to be parsed. /// /// /// Specifies whether the string represents a Windows or Passport user ID. /// /// /// The user's ID. /// private static void ParseTypePrefixedUserName( string typePrefixedUserName, out AuthenticationType authenticationType, out string userName ) { // // We don't actually know the authentication type yet, and we might find that // the type-prefixed user name doesn't even specify a valid authentication // type. But we have to assign to authenticationType because it's an out // parameter. // authenticationType = AuthenticationType.Windows; int colonIndex = typePrefixedUserName.IndexOf(':'); if (colonIndex < 1 || colonIndex >= typePrefixedUserName.Length - 1) { throw new FileFormatException(SR.Get(SRID.InvalidTypePrefixedUserName)); } // No need to use checked{} here since colonIndex cannot be >= to (max int - 1) userName = typePrefixedUserName.Substring(colonIndex + 1); string authenticationTypeString = typePrefixedUserName.Substring(0, colonIndex); bool validEnum = false; // user names: case-insensitive comparison if (((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals( authenticationTypeString, Enum.GetName(typeof(AuthenticationType), AuthenticationType.Windows))) { authenticationType = AuthenticationType.Windows; validEnum = true; } else if (((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals( authenticationTypeString, Enum.GetName(typeof(AuthenticationType), AuthenticationType.Passport))) { authenticationType = AuthenticationType.Passport; validEnum = true; } // // Didn't find a matching enumeration constant. // if (!validEnum) { throw new FileFormatException( SR.Get( SRID.InvalidAuthenticationTypeString, typePrefixedUserName ) ); } } ////// Load the use license from the specified stream. /// /// /// The Utf 8 BinaryReader from which the use license is to be loaded. /// ////// The use license from the stream. /// ////// For details of the stream format, see the comments in LoadUseLicenseAndUserFromStream. /// private UseLicense LoadUseLicenseFromStream( BinaryReader utf8Reader ) { utf8Reader.BaseStream.Seek(0, SeekOrigin.Begin); Int32 headerLength = utf8Reader.ReadInt32(); if (headerLength < UseLicenseStreamLengthMin) { throw new FileFormatException(SR.Get(SRID.UseLicenseStreamCorrupt)); } // // Skip over the type-prefixed user name string, because we only want the // use license. // ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UserNameLengthMax); return new UseLicense( ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UseLicenseLengthMax) ); } ////// Load the user to whom a use license was issued from the specified stream. /// /// /// The Utf8 BinaryReader from which the user is to be loaded. /// ////// The user specified in the stream. /// ////// For details of the stream format, see the comments in LoadUseLicenseAndUserFromStream. /// private ContentUser LoadUserFromStream( BinaryReader utf8Reader ) { utf8Reader.BaseStream.Seek(0, SeekOrigin.Begin); Int32 headerLength = utf8Reader.ReadInt32(); if (headerLength < UseLicenseStreamLengthMin) { throw new FileFormatException(SR.Get(SRID.UseLicenseStreamCorrupt)); } string base64UserName = ReadLengthPrefixedString(utf8Reader, Encoding.UTF8, UserNameLengthMax); byte[] userNameBytes = Convert.FromBase64String(base64UserName); string typePrefixedUserName = new string( _unicodeEncoding.GetChars(userNameBytes) ); // // Create and return the user object specified by the type-prefixed name. // If the type-prefixed name is not in a valid format, a FileFormatException // will be thrown. // AuthenticationType authenticationType; string userName; ParseTypePrefixedUserName(typePrefixedUserName, out authenticationType, out userName); return new ContentUser(userName, authenticationType); } ////// Read a string, encoded according to the specified encoding, and prefixed by the /// length in bytes of the encoded string. /// /// /// Binary reader from which the string is read. /// /// /// Object that specifies how the string has been encoded. /// /// /// The maximum number of characters that the string can contain. This prevents a malformed /// file with a huge length prefix from making us allocate all our memory. /// private static string ReadLengthPrefixedString( BinaryReader reader, Encoding encoding, int maxLength ) { Int32 length = reader.ReadInt32(); if (length > maxLength) { throw new FileFormatException(SR.Get(SRID.ExcessiveLengthPrefix, length, maxLength)); } byte[] bytes = reader.ReadBytes(length); if (bytes.Length != length) { throw new FileFormatException(SR.Get(SRID.InvalidStringFormat)); } string s = encoding.GetString(bytes); SkipDwordPadding(bytes.Length, reader); return s; } ////// Skip past the DWORD padding bytes at the end of a string of the specified length. /// /// /// Length in bytes of the string that was read. /// /// /// Binary reader from which the string was read. /// private static void SkipDwordPadding( int length, BinaryReader reader ) { int extra = length % CU.Int32Size; if (extra != 0) { // No need to use checked{} here since we already made sure that extra is smaller than Int32Size byte[] bytes = reader.ReadBytes(CU.Int32Size - extra); if (bytes.Length != CU.Int32Size - extra) { throw new FileFormatException(SR.Get(SRID.InvalidStringFormat)); } } } ////// Write out the number of bytes needed to DWORD align a string of the specified /// length. Per the file format spec, the bytes must be 0s. /// private static void WriteDwordPadding( int length, BinaryWriter writer ) { int extra = length % CU.Int32Size; if (extra != 0) { // No need to use checked{} here since we already made sure that extra is smaller than Int32Size writer.Write(Padding, 0, CU.Int32Size - extra); } } ////// Write out a string in the specified encoding, preceded by the length in bytes /// of the encoded string. Pad the string with 0s to a DWORD boundary. The padding /// is not included in the length prefix. /// private static void WriteByteLengthPrefixedDwordPaddedString( string s, BinaryWriter writer, Encoding encoding ) { byte[] bytes = encoding.GetBytes(s); Int32 length = bytes.Length; writer.Write(length); // // NOTE: If we wrote out the string with "Write(string)", the // writer would precede the output with the UTF-8-encoded array // length, which we don't want since we need to write the length // ourselves. Use Encoding class to get the bytes and call "Write(bytes) // to avoid that problem. // writer.Write(bytes); WriteDwordPadding(length, writer); } #endregion Private Methods //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- #region Private Fields private CryptoProvider _cryptoProvider; private PublishLicense _publishLicense; private bool _fixedSettings; // // Text encoding object used to read or write other strings used in the file // format. (false = little-endian, false = no byte order mark). // // NOTE: It doesn't always matter which encoding object we use. It matters when // we're reading or writing character data, but not when we're writing numeric // or byte data. // private static readonly UnicodeEncoding _unicodeEncoding = new UnicodeEncoding(false, false); // // The stream in which the FormatVersion and publish license is stored. // private VersionedStreamOwner _publishLicenseStream; // // Uninterpreted bytes from the publish license stream header. // private byte[] _publishLicenseHeaderExtraBytes; // // The storage under which use licenses are stored. // private StorageInfo _useLicenseStorage; // // All use licenses reside in streams whose names begin with this prefix: // private const string LicenseStreamNamePrefix = "EUL-"; private static readonly int LicenseStreamNamePrefixLength = LicenseStreamNamePrefix.Length; // // The RM version information for the current version of this class. // private const string FeatureName = "Microsoft.Metadata.DRMTransform"; // // // Maximum permitted length, in characters, of a publish license. // private const int PublishLicenseLengthMax = 1000000; // // Maximum permitted length, in characters, of a use license. // private const int UseLicenseLengthMax = 1000000; // // Maximum permitted length, in characters, of a base-64-encoded type-prefixed user name. // private const int UserNameLengthMax = 1000; // // Minimum possible length, in bytes, of the header information in a user license // stream. The format of the header is: // Int32 headerLength In bytes // Int32 userNameLength In bytes // Byte userName[userNameLength] // So the shortest possible header, when userNameLength is 1 byte, is 2 Int32s // plus a Byte. // private static readonly int UseLicenseStreamLengthMin = 2 * CU.Int32Size + SizeofByte; private static readonly VersionPair CurrentFeatureVersion = new VersionPair(1,0); // // The minimum version number that can read the file format that this version of // the software writes. // private static readonly VersionPair MinimumReaderVersion = new VersionPair(1, 0); // // The minimum version number that can update the file format that this version of // the software writes. // private static readonly VersionPair MinimumUpdaterVersion = new VersionPair(1, 0); // // Maximum permitted length of the variable-length header in the publish // license stream. This limit is a security issue. Since the first 4 bytes // of the header specify the header length, and since we will allocate a // buffer to hold the header contents, we don't want somebody to give us // a malformed file that specifies a header of length 2^31 or so. // private const int MaxPublishLicenseHeaderLen = 4096; private static readonly char[] Base32EncodingTable = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '=' }; // // Used to DWORD-align a stream after writing a string to it: // private static readonly byte[] Padding = {0, 0, 0}; private const int SizeofByte = 1; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- AnimationStorage.cs
- RegexCompilationInfo.cs
- ListViewDataItem.cs
- Inline.cs
- Transform.cs
- URLMembershipCondition.cs
- DeclarativeCatalogPart.cs
- ScrollChrome.cs
- ArrangedElementCollection.cs
- FilterableData.cs
- BrushValueSerializer.cs
- SmtpClient.cs
- SqlCommand.cs
- HttpProfileGroupBase.cs
- UpdateRecord.cs
- AudioFileOut.cs
- CodeSubDirectory.cs
- XmlNamespaceDeclarationsAttribute.cs
- OleDbConnectionPoolGroupProviderInfo.cs
- IPCCacheManager.cs
- Size.cs
- AnnotationDocumentPaginator.cs
- Query.cs
- TranslateTransform.cs
- StrongNamePublicKeyBlob.cs
- NumberAction.cs
- MouseActionConverter.cs
- Rect3DConverter.cs
- _RegBlobWebProxyDataBuilder.cs
- DoubleCollection.cs
- ScrollableControl.cs
- DockPattern.cs
- Stacktrace.cs
- CommandBinding.cs
- DiagnosticTraceSource.cs
- ListMarkerLine.cs
- SimpleRecyclingCache.cs
- SaveFileDialog.cs
- OptimalTextSource.cs
- AnchoredBlock.cs
- WindowsFormsHelpers.cs
- LinqDataSourceDisposeEventArgs.cs
- TextTreeInsertUndoUnit.cs
- WebContext.cs
- PropertyIDSet.cs
- ComboBoxRenderer.cs
- UpdateRecord.cs
- HttpCacheParams.cs
- InlineObject.cs
- LinqDataSourceInsertEventArgs.cs
- SemaphoreSecurity.cs
- _ConnectionGroup.cs
- FactoryId.cs
- Graphics.cs
- NotifyInputEventArgs.cs
- TTSEvent.cs
- BitmapPalette.cs
- XmlDataSourceDesigner.cs
- CommandCollectionEditor.cs
- HttpCapabilitiesBase.cs
- SessionEndingCancelEventArgs.cs
- PropertyRef.cs
- RelationshipType.cs
- SmiConnection.cs
- TextElementEnumerator.cs
- SHA384.cs
- TextCompositionEventArgs.cs
- ACE.cs
- WebServiceHandler.cs
- Figure.cs
- HandlerBase.cs
- XmlEncoding.cs
- RefreshPropertiesAttribute.cs
- DialogWindow.cs
- View.cs
- KeyValuePair.cs
- _FtpDataStream.cs
- UIPropertyMetadata.cs
- CollectionConverter.cs
- DynamicDataRoute.cs
- _SslState.cs
- CodeArgumentReferenceExpression.cs
- EmbeddedMailObject.cs
- RealizationContext.cs
- SerializationException.cs
- FastEncoderStatics.cs
- ManipulationPivot.cs
- IDictionary.cs
- SecurityDescriptor.cs
- D3DImage.cs
- ObjectParameterCollection.cs
- Span.cs
- Tokenizer.cs
- Nullable.cs
- UpdateExpressionVisitor.cs
- FixedHyperLink.cs
- ExceptionUtil.cs
- SecureStringHasher.cs
- Listbox.cs
- GridItemProviderWrapper.cs