File: Base\System\Security\RightsManagement\CryptoProvider.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//
// <copyright file="CryptoProvider.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:  Crypto provider class which is a wrapper around unmanaged DRM SDK Bound License handle
//   provides ability to Encryt/Decrypt protected content, and enumerate granted rights. 
//
// History:
//  06/01/2005: IgorBel :   Initial Implementation 
//
//-----------------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Security;
using System.Security.Permissions;
using System.Windows;
using System.Collections.ObjectModel;
using MS.Internal.Security.RightsManagement; 
using MS.Internal;        
using SecurityHelper=MS.Internal.WindowsBase.SecurityHelper; 
 
using MS.Internal.WindowsBase;
 
namespace System.Security.RightsManagement 
{
    /// <summary>
    /// CryptoProvider class is built as a result of UseLicense.Bind call. This class represents a successful RightsManagement 
    /// Initialization, and as a result provides Encryption/Decryption services, and exposes list of Bound Grants, which are rights 
    /// that have been given by the publisher to the user, and were properly validated (expiration checks, secure environment, and so on)
    /// </summary>
    /// <SecurityNote>
    ///     Critical:    This class expose access to methods that eventually do one or more of the the following
    ///             1. call into unmanaged code 
    ///             2. affects state/data that will eventually cross over unmanaged code boundary
    ///             3. Return some RM related information which is considered private 
    ///
    ///     TreatAsSafe: This attrbiute automatically applied to all public entry points. All the public entry points have
    ///     Demands for RightsManagementPermission at entry to counter the possible attacks that do 
    ///     not lead to the unamanged code directly(which is protected by another Demand there) but rather leave 
    ///     some status/data behind which eventually might cross the unamanaged boundary. 
    /// </SecurityNote>
    [SecurityCritical(SecurityCriticalScope.Everything)]    
    public class CryptoProvider : IDisposable
    {
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        /// <summary>
        /// This method is responsible for tearing down crypto provider that was built as a result of UseLicense.Bind call.
        /// </summary>        
        ~CryptoProvider()
        {
            Dispose(false);
        }
 
        /// <summary>
        /// This method is responsible for tearing down crypto provider that was built as a result of UseLicense.Bind call.
        /// </summary>        
        public void Dispose()
        {
            SecurityHelper.DemandRightsManagementPermission();            
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
         /// <summary>    
        /// This function encrypts clear text content.
        /// An application is expected to create a padding scheme based on the value returned from 
        /// BlockSize property. BlockSize property should be used to determine the amount of extra 
        /// padding to be added to the clear text. The length, in bytes, of the buffer holding content to 
        /// be encrypted should be a multiple of the block cipher block size. 
        /// RMS system currently uses AES block cipher. All blocks are encrypted independently, so that 2 blocks 
        /// of identical clear text will produce identical results after encryption.  An application 
        /// is encouraged to either compress data prior to encryption or create some other scheme to mitigate 
        /// threats potentially arising from independent block encryption.
        /// </summary> 
        public byte[] Encrypt(byte[] clearText)
        {
            SecurityHelper.DemandRightsManagementPermission();
            CheckDisposed();
            
            if (clearText == null)
            {
                throw new ArgumentNullException("clearText");
            }
 
            // validation of the proper size of the clearText is done by the unmanaged libraries 
            
            if (!CanEncrypt)
            {
                throw new RightsManagementException(RightsManagementFailureCode.EncryptionNotPermitted);
            }
            
            // first get the size
            uint outputBufferSize=0;
            byte[] outputBuffer = null;
            int hr;
 
#if DEBUG
            hr= SafeNativeMethods.DRMEncrypt(
                            EncryptorHandle, 
                            0, 
                            (uint)clearText.Length, 
                            clearText,
                            ref outputBufferSize,
                            null);
 
            Errors.ThrowOnErrorCode(hr);
 
            // We do not expect Decryption to change the size of the buffer; otherwise it will break 
            // basic assumptions behind the encrypted compound file envelope format
            Invariant.Assert(outputBufferSize == clearText.Length); 
#else
            outputBufferSize = (uint)clearText.Length;
#endif
 
            outputBuffer = new byte[outputBufferSize];
 
            // This will decrypt content
            hr = SafeNativeMethods.DRMEncrypt(
                            EncryptorHandle,
                            0,
                            (uint)clearText.Length,
                            clearText,
                            ref outputBufferSize,
                            outputBuffer);
            Errors.ThrowOnErrorCode(hr);
 
            return outputBuffer;
        }
 
        /// <summary>    
        /// This function decrypts cipher text content.
        /// The length, in bytes, of the buffer holding content to be encrypted should be a multiple of the 
        /// block cipher block size. 
        /// </summary>    
        public byte[] Decrypt(byte[] cryptoText)
        {
            SecurityHelper.DemandRightsManagementPermission();
            CheckDisposed();
        
            if (cryptoText == null)
            {
                throw new ArgumentNullException("cryptoText");
            }
 
            // validation of the proper size of the cryptoText is done by the unmanaged libraries 
            
            if (!CanDecrypt)
            {
                throw new RightsManagementException(RightsManagementFailureCode.RightNotGranted);
            }
            
            // first get the size
            uint outputBufferSize=0;
            byte[] outputBuffer = null;
            int hr;
 
#if DEBUG
            hr= SafeNativeMethods.DRMDecrypt(
                            DecryptorHandle,
                            0, 
                            (uint)cryptoText.Length, 
                            cryptoText,
                            ref outputBufferSize,
                            null);
            Errors.ThrowOnErrorCode(hr);
 
            // We do not expect Decryption changing the size of the buffer; otherwise it will break 
            // basic assumptions behind the encrypted compound file envelope format
            Invariant.Assert(outputBufferSize == cryptoText.Length); 
#else
            outputBufferSize = (uint)cryptoText.Length;
#endif
 
            outputBuffer = new byte[outputBufferSize];
 
            // THis will decrypt content
            hr = SafeNativeMethods.DRMDecrypt(
                            DecryptorHandle,
                            0,
                            (uint)cryptoText.Length,
                            cryptoText,
                            ref outputBufferSize,
                            outputBuffer);
 
            Errors.ThrowOnErrorCode(hr);
            return outputBuffer;
        }
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        /// <summary>
        /// Dispose(bool)
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            // close bound license handle which is a DRMHANDLE 
            // Encryptor and Decryptor handles as well
            try
            {
                if (disposing)
                {
                    
                    if (_decryptorHandle != null && !_decryptorHandle.IsInvalid)
                        _decryptorHandle.Close();
                    
                    if (_encryptorHandle != null && !_encryptorHandle.IsInvalid)
                        _encryptorHandle.Close();
 
                    if (_boundLicenseOwnerViewRightsHandle != null && !_boundLicenseOwnerViewRightsHandle.IsInvalid)
                        _boundLicenseOwnerViewRightsHandle.Close();
                
                    // dispose collection of the bound licenses that we have 
                    if (_boundLicenseHandleList != null)
                    {
                        foreach(SafeRightsManagementHandle boundLicenseHandle in _boundLicenseHandleList)
                        {
                            if (boundLicenseHandle != null && !boundLicenseHandle.IsInvalid) 
                                boundLicenseHandle.Close(); 
                        }
                    }
                }
            }
            finally
            {
                _disposed = true;
                _boundLicenseHandleList = null;
                _boundLicenseOwnerViewRightsHandle = null;
                _decryptorHandle    = null;
                _encryptorHandle    = null;               
            }
        }
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        /// <summary>    
        /// This is bock size of the cypther that is currently in use. 
        /// </summary>    
        public int BlockSize
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
                CheckDisposed();
            
                if (_blockSize ==0)
                {
                    _blockSize = QueryBlockSize(); 
                }
                return _blockSize; 
            }
        }
 
        /// <summary>    
        /// This bollean indicates wheter Encrypt Decrypt calls can be made on buffers that are multiple of the BlockSize value.
        /// If the value is false Encrypt Decrypt calls must be made on buffers of the size that is exactly equal to the BlockSize.
        /// </summary>    
        public bool CanMergeBlocks 
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
                CheckDisposed();
            
                // convention is to return 1 for stream ciphers
                // Stream ciphers (although currently not used) return 1.
                // Block ciphers return size of the block that is always greater than 1.
                // Comparing block size to 1 is a conventional way to differentiate stream and 
                // block ciphers. Block Ciphers can accept data chunks (for both encryption and decryption) 
                // that fall on the block boundary and are a multiple of the block cipher block size. 
                return (BlockSize > 1);
            }
        }
        
        /// <summary>    
        /// This is a read only property. It enable client application to enumerate list of rights that were 
        /// granted to the user, and also passed all the verification checks.
        /// </summary>    
        public ReadOnlyCollection<ContentGrant> BoundGrants
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
                CheckDisposed();
 
                if (_boundGrantReadOnlyCollection == null)
                {
                    // we need to enumerate all the rights and keep the ones that we can translate into enum
                    // we ignore the rights that we can not translate for forward compatibility reasons 
                    List<ContentGrant> grantList = new List<ContentGrant>(_boundRightsInfoList.Count);
 
                    foreach(RightNameExpirationInfoPair rightsInfo in _boundRightsInfoList)
                    {
                        Nullable<ContentRight> contentRight = ClientSession.GetRightFromString(rightsInfo.RightName);
 
                        if (contentRight != null)
                        {
                            grantList.Add(new ContentGrant(_owner, contentRight.Value, rightsInfo.ValidFrom, rightsInfo.ValidUntil));
                        }
                    }
                    _boundGrantReadOnlyCollection = new ReadOnlyCollection<ContentGrant> (grantList);
                }
                return _boundGrantReadOnlyCollection;
            }
        }
 
 
        /// <summary>    
        /// Depending on the set of rights ggranted to the user he or she can do Encryption, Decryption or both.
        /// this property checks whether user was granted rights to encrypt, which means that he or she was granted 
        /// either an Edit or an Owner right.
        /// </summary>    
        public bool CanEncrypt 
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
                CheckDisposed();
 
                return (!EncryptorHandle.IsInvalid);
            }
        }
        
        /// <summary>    
        /// Depending on the set of rights granted to the user he or she can do Encryption, Decryption or both.
        /// this property checks whether user was granted rights to decrypt. Decryption is given to a user if he or 
        /// she was able to successfully bind any right (View, Edit, Print, Owner, ....)
        /// </summary>    
        public bool CanDecrypt 
        {
            get
            {
                SecurityHelper.DemandRightsManagementPermission();
                CheckDisposed();
 
                return (!DecryptorHandle.IsInvalid);
            }
        }
 
        //------------------------------------------------------
        //
        //  Internal Constructor 
        //
        //------------------------------------------------------
 
        /// <summary>
        /// Constructor.
        /// </summary>
        internal CryptoProvider(List<SafeRightsManagementHandle> boundLicenseHandleList, 
                                                        List<RightNameExpirationInfoPair> rightsInfoList,
                                                        ContentUser owner)
        {
            Invariant.Assert(boundLicenseHandleList != null);
            Invariant.Assert(boundLicenseHandleList.Count > 0);
 
            Invariant.Assert(rightsInfoList != null);
            Invariant.Assert(rightsInfoList.Count > 0);
 
            // we expect a match between lists of the Right Information and the bound license handles 
            // we will be mapping those lists based on indexes 
            Invariant.Assert(rightsInfoList.Count == boundLicenseHandleList.Count);
                
            Invariant.Assert(owner != null);
 
            _boundLicenseHandleList = boundLicenseHandleList;
            _boundRightsInfoList = rightsInfoList; 
 
            _owner = owner;
        }
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        internal UnsignedPublishLicense DecryptPublishLicense(string serializedPublishLicense)
        {
            Invariant.Assert(serializedPublishLicense != null);
 
            if ((BoundLicenseOwnerViewRightsHandle == null ) || BoundLicenseOwnerViewRightsHandle.IsInvalid)
            {
                throw new RightsManagementException(RightsManagementFailureCode.RightNotGranted);
            }            
            else
            {
                return new UnsignedPublishLicense(BoundLicenseOwnerViewRightsHandle, serializedPublishLicense);
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Methods  
        //
        //------------------------------------------------------
 
        /// <summary>
        /// Call this before accepting any API call 
        /// </summary>
        private void CheckDisposed()
        {
            if (_disposed)
                throw new ObjectDisposedException(null, SR.Get(SRID.CryptoProviderDisposed));
        }
 
 
        private int QueryBlockSize()
        {
            uint attributeSize = 0;
            byte[] dataBuffer = null;
            
            uint encodingType;
 
            int hr = SafeNativeMethods.DRMGetInfo(DecryptorHandle,
                                                    NativeConstants.QUERY_BLOCKSIZE,
                                                    out encodingType,
                                                    ref attributeSize,
                                                    null);
            Errors.ThrowOnErrorCode(hr);
 
            // it must return 4 bytes for block size 
            Invariant.Assert(attributeSize == 4);
 
            dataBuffer = new byte[(int)attributeSize];  // this cast is safe based on the Invariant.Assert above.
 
            hr = SafeNativeMethods.DRMGetInfo(DecryptorHandle,
                                                NativeConstants.QUERY_BLOCKSIZE,
                                                out encodingType,
                                                ref attributeSize,
                                                dataBuffer);
            Errors.ThrowOnErrorCode(hr);
 
            return BitConverter.ToInt32(dataBuffer,0);
        }
 
        //------------------------------------------------------
        //
        //  Private Properties 
        //
        //------------------------------------------------------
 
        private SafeRightsManagementHandle DecryptorHandle
        {
            get
            {
                if(!_decryptorHandleCalculated)
                {
                    for(int i=0; i< _boundLicenseHandleList.Count; i++)
                    {
                        SafeRightsManagementHandle decryptorHandle = null;
 
                        int hr = SafeNativeMethods.DRMCreateEnablingBitsDecryptor(
                            _boundLicenseHandleList[i],
                            _boundRightsInfoList[i].RightName,
                            0, 
                            null, 
                            out decryptorHandle);
 
                        if (hr == 0) 
                        {
                            Debug.Assert(decryptorHandle != null);
 
                            _decryptorHandle = decryptorHandle;
                             _decryptorHandleCalculated = true;
                             return _decryptorHandle;
                        }
                    }
                    _decryptorHandleCalculated = true; // if we got here it means we couldn't find anything; regardless 
                                                                         // we should still mark this calculation as complete  
                }
                return _decryptorHandle;
            }
        }
 
        private SafeRightsManagementHandle EncryptorHandle 
        {
            get
            {
                if(!_encryptorHandleCalculated)
                {
                    for(int i=0; i< _boundLicenseHandleList.Count; i++)
                    {
                        SafeRightsManagementHandle encryptorHandle = null;
                        int hr = SafeNativeMethods.DRMCreateEnablingBitsEncryptor(
                            _boundLicenseHandleList[i],
                            _boundRightsInfoList[i].RightName,
                            0,
                            null,
                            out encryptorHandle);
 
                        if (hr == 0) 
                        {
                            Debug.Assert(encryptorHandle != null);
 
                            _encryptorHandle = encryptorHandle;
                            _encryptorHandleCalculated = true;
                            return _encryptorHandle; 
                        }
                    }
                    _encryptorHandleCalculated = true; // if we got here it means we couldn't find anything; regardless 
                                                                             // we should still mark this calculation as complete  
                }
                return _encryptorHandle;
            }
        }
 
   
        private SafeRightsManagementHandle BoundLicenseOwnerViewRightsHandle 
        {
            get
            {
                if(!_boundLicenseOwnerViewRightsHandleCalculated)
                {
                    for(int i=0; i< _boundLicenseHandleList.Count; i++)
                    {
                        Nullable<ContentRight> right = 
                                        ClientSession.GetRightFromString(_boundRightsInfoList[i].RightName);
                            
                        if ((right != null) 
                                &&
                            ((right.Value == ContentRight.Owner) ||(right.Value== ContentRight.ViewRightsData)))
                        {
                            _boundLicenseOwnerViewRightsHandle = _boundLicenseHandleList[i];
                            _boundLicenseOwnerViewRightsHandleCalculated = true;
                            return _boundLicenseOwnerViewRightsHandle; 
                        }
                    }
                    _boundLicenseOwnerViewRightsHandleCalculated = true; // if we got here it means we couldn't find anything; regardless 
                                                                         // we should still mark this calculation as complete  
                }
                return _boundLicenseOwnerViewRightsHandle;
            }
        }
   
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        private int _blockSize;
 
        private SafeRightsManagementHandle _decryptorHandle = SafeRightsManagementHandle.InvalidHandle;            
        private bool _decryptorHandleCalculated;
 
        private SafeRightsManagementHandle _encryptorHandle = SafeRightsManagementHandle.InvalidHandle;
        private bool _encryptorHandleCalculated;
 
        private SafeRightsManagementHandle _boundLicenseOwnerViewRightsHandle = SafeRightsManagementHandle.InvalidHandle;
        private bool _boundLicenseOwnerViewRightsHandleCalculated;
 
        // if this is Invalid, we are disposed
        private List<SafeRightsManagementHandle> _boundLicenseHandleList;
        private List<RightNameExpirationInfoPair> _boundRightsInfoList;
                                                            
        private ReadOnlyCollection<ContentGrant> _boundGrantReadOnlyCollection;
        private ContentUser _owner;
 
        private bool _disposed;
    }
}