File: system\security\cryptography\pkcs\pkcsmisc.cs
Project: ndp\clr\src\managedlibraries\security\System.Security.csproj (System.Security)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
 
//
// PkcsMisc.cs
//
// 02/09/2003
// 
 
namespace System.Security.Cryptography.Pkcs {
    using System.Collections;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Cryptography.Xml;
 
    public enum KeyAgreeKeyChoice {
        Unknown      = 0,
        EphemeralKey = 1,
        StaticKey    = 2,
    }
 
    public enum SubjectIdentifierType {
        Unknown                = 0,  // Use any of the following as appropriate
        IssuerAndSerialNumber  = 1,  // X509IssuerSerial
        SubjectKeyIdentifier   = 2,  // SKI hex string
        NoSignature            = 3  // NoSignature
    }
 
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class SubjectIdentifier {
        private SubjectIdentifierType m_type;
        private Object                m_value;
 
        private SubjectIdentifier () {}
        [SecurityCritical]
        internal SubjectIdentifier (CAPI.CERT_INFO certInfo) : this(certInfo.Issuer, certInfo.SerialNumber) {}
        [SecurityCritical]
        internal SubjectIdentifier (CAPI.CMSG_SIGNER_INFO signerInfo) : this(signerInfo.Issuer, signerInfo.SerialNumber) {}
 
        internal SubjectIdentifier (SubjectIdentifierType type, Object value) {
            Reset(type, value);
        }
 
        [SecurityCritical]
        internal unsafe SubjectIdentifier (CAPI.CRYPTOAPI_BLOB issuer, CAPI.CRYPTOAPI_BLOB serialNumber) {
            // If serial number is 0, then it is the special SKI encoding or NoSignature
            bool isSKIorHashOnly = true;
            byte * pb = (byte *) serialNumber.pbData;
            for (uint i = 0; i < serialNumber.cbData; i++) {
                if (*pb++ != (byte) 0) {
                    isSKIorHashOnly = false;
                    break;
                }
            }
 
            if (isSKIorHashOnly) {
                byte[] issuerBytes = new byte[issuer.cbData];
                Marshal.Copy(issuer.pbData, issuerBytes, 0, issuerBytes.Length);
                X500DistinguishedName dummyName = new X500DistinguishedName(issuerBytes);
                if (String.Compare(CAPI.DummySignerCommonName, dummyName.Name, StringComparison.OrdinalIgnoreCase) == 0) {
                    Reset(SubjectIdentifierType.NoSignature, null);
                    return;
                }
            }
 
            if (isSKIorHashOnly) {
                // Decode disguised SKI in issuer field (See WinCrypt.h for more info).  Note that some certificates may contain
                // an all-zero serial number but not be encoded with an szOID_KEYID_RDN.  In order to allow use of signatures created
                // using these certificates, we will first try to find the szOID_KEYID_RDN, but if it does not exist, fall back to just
                // decoding the incoming issuer and serial number.
                m_type = SubjectIdentifierType.SubjectKeyIdentifier;
                m_value = String.Empty;
 
                uint cbCertNameInfo = 0;
                SafeLocalAllocHandle pbCertNameInfo = SafeLocalAllocHandle.InvalidHandle;
 
                if (CAPI.DecodeObject(new IntPtr(CAPI.X509_NAME),
                                      issuer.pbData,
                                      issuer.cbData,
                                      out pbCertNameInfo,
                                      out cbCertNameInfo)) {
                    using (pbCertNameInfo) {
                        checked {
                            CAPI.CERT_NAME_INFO certNameInfo = (CAPI.CERT_NAME_INFO) Marshal.PtrToStructure(pbCertNameInfo.DangerousGetHandle(), typeof(CAPI.CERT_NAME_INFO));
                            for (uint i = 0; i < certNameInfo.cRDN; i++) {
                                CAPI.CERT_RDN certRdn = (CAPI.CERT_RDN) Marshal.PtrToStructure(new IntPtr((long) certNameInfo.rgRDN + (long) (i * Marshal.SizeOf(typeof(CAPI.CERT_RDN)))), typeof(CAPI.CERT_RDN));
 
                                for (uint j = 0; j < certRdn.cRDNAttr; j++)
                                {
                                    CAPI.CERT_RDN_ATTR certRdnAttr = (CAPI.CERT_RDN_ATTR)Marshal.PtrToStructure(new IntPtr((long)certRdn.rgRDNAttr + (long)(j * Marshal.SizeOf(typeof(CAPI.CERT_RDN_ATTR)))), typeof(CAPI.CERT_RDN_ATTR));
 
                                    if (String.Compare(CAPI.szOID_KEYID_RDN, certRdnAttr.pszObjId, StringComparison.OrdinalIgnoreCase) == 0)
                                    {
                                        if (certRdnAttr.dwValueType == CAPI.CERT_RDN_OCTET_STRING)
                                        {
                                            byte[] ski = new byte[certRdnAttr.Value.cbData];
                                            Marshal.Copy(certRdnAttr.Value.pbData, ski, 0, ski.Length);
                                            Reset(SubjectIdentifierType.SubjectKeyIdentifier, X509Utils.EncodeHexString(ski));
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
 
            CAPI.CERT_ISSUER_SERIAL_NUMBER IssuerAndSerial;
            IssuerAndSerial.Issuer = issuer;
            IssuerAndSerial.SerialNumber = serialNumber;
            X509IssuerSerial issuerSerial = PkcsUtils.DecodeIssuerSerial(IssuerAndSerial);
            Reset(SubjectIdentifierType.IssuerAndSerialNumber, issuerSerial);
        }
 
        [SecurityCritical]
        internal SubjectIdentifier (CAPI.CERT_ID certId) {
            switch (certId.dwIdChoice) {
            case CAPI.CERT_ID_ISSUER_SERIAL_NUMBER:
                X509IssuerSerial issuerSerial = PkcsUtils.DecodeIssuerSerial(certId.Value.IssuerSerialNumber);
                Reset(SubjectIdentifierType.IssuerAndSerialNumber, issuerSerial);
                break;
            case CAPI.CERT_ID_KEY_IDENTIFIER:
                byte[] ski = new byte[certId.Value.KeyId.cbData];
                Marshal.Copy(certId.Value.KeyId.pbData, ski, 0, ski.Length);
                Reset(SubjectIdentifierType.SubjectKeyIdentifier, X509Utils.EncodeHexString(ski));
                break;
            default:
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type"), certId.dwIdChoice.ToString(CultureInfo.InvariantCulture));
            }
        }
 
        public SubjectIdentifierType Type {
            get {
                return m_type;
            }
        }
 
        public Object Value {
            get {
                return m_value;
            }
        }
 
        //
        // Internal methods.
        //
 
        internal void Reset (SubjectIdentifierType type, Object value) {
            switch (type) {
            case SubjectIdentifierType.NoSignature:
            case SubjectIdentifierType.Unknown:
                break;
            case SubjectIdentifierType.IssuerAndSerialNumber:
                if (value.GetType() != typeof(X509IssuerSerial)) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type_Value_Mismatch"), value.GetType().ToString());
                }
                break;
            case SubjectIdentifierType.SubjectKeyIdentifier:
                if (!PkcsUtils.CmsSupported()) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Not_Supported"));
                }
                if (value.GetType() != typeof(string)) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type_Value_Mismatch"), value.GetType().ToString());
                }
                break;
            default:
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type"), type.ToString());
            }
 
            m_type = type;
            m_value = value;
        }
    }
 
    public enum SubjectIdentifierOrKeyType {
        Unknown                = 0,  // Use any of the following as appropriate
        IssuerAndSerialNumber  = 1,  // X509IssuerSerial
        SubjectKeyIdentifier   = 2,  // SKI hex string
        PublicKeyInfo          = 3,  // PublicKeyInfo
    }
 
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class PublicKeyInfo {
        private AlgorithmIdentifier m_algorithm;
        private byte[]              m_keyValue;
 
        private PublicKeyInfo () {}
 
        [SecurityCritical]
        internal PublicKeyInfo (CAPI.CERT_PUBLIC_KEY_INFO keyInfo) {
            m_algorithm = new AlgorithmIdentifier(keyInfo);
            m_keyValue = new byte[keyInfo.PublicKey.cbData];
            if (m_keyValue.Length > 0) {
                Marshal.Copy(keyInfo.PublicKey.pbData, m_keyValue, 0, m_keyValue.Length);
            }
        }
 
        public AlgorithmIdentifier Algorithm {
            get {
                return m_algorithm;
            }
        }
 
        public byte[] KeyValue {
            get {
                return m_keyValue;
            }
        }
    }
 
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class SubjectIdentifierOrKey {
        private SubjectIdentifierOrKeyType m_type;
        private Object                     m_value;
 
        private SubjectIdentifierOrKey () {}
 
        internal SubjectIdentifierOrKey (SubjectIdentifierOrKeyType type, Object value) {
            Reset(type, value);
        }
 
        [SecurityCritical]
        internal SubjectIdentifierOrKey (CAPI.CERT_ID certId) {
            switch (certId.dwIdChoice) {
            case CAPI.CERT_ID_ISSUER_SERIAL_NUMBER:
                X509IssuerSerial issuerSerial = PkcsUtils.DecodeIssuerSerial(certId.Value.IssuerSerialNumber);
                Reset(SubjectIdentifierOrKeyType.IssuerAndSerialNumber, issuerSerial);
                break;
            case CAPI.CERT_ID_KEY_IDENTIFIER:
                byte[] ski = new byte[certId.Value.KeyId.cbData];
                Marshal.Copy(certId.Value.KeyId.pbData, ski, 0, ski.Length);
                Reset(SubjectIdentifierOrKeyType.SubjectKeyIdentifier, X509Utils.EncodeHexString(ski));
                break;
            default:
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type"), certId.dwIdChoice.ToString(CultureInfo.InvariantCulture));
            }
        }
 
        [SecurityCritical]
        internal SubjectIdentifierOrKey (CAPI.CERT_PUBLIC_KEY_INFO publicKeyInfo) {
            Reset(SubjectIdentifierOrKeyType.PublicKeyInfo, new PublicKeyInfo(publicKeyInfo));
        }
        
        public SubjectIdentifierOrKeyType Type {
            get {
                return m_type;
            }
        }
 
        public Object Value {
            get {
                return m_value;
            }
        }
 
        //
        // Internal methods.
        //
 
        internal void Reset (SubjectIdentifierOrKeyType type, Object value) {
            switch (type) {
            case SubjectIdentifierOrKeyType.Unknown:
                break;
            case SubjectIdentifierOrKeyType.IssuerAndSerialNumber:
                if (value.GetType() != typeof(X509IssuerSerial)) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type_Value_Mismatch"), value.GetType().ToString());
                }
                break;
            case SubjectIdentifierOrKeyType.SubjectKeyIdentifier:
                if (!PkcsUtils.CmsSupported()) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Not_Supported"));
                }
                if (value.GetType() != typeof(string)) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type_Value_Mismatch"), value.GetType().ToString());
                }
                break;
            case SubjectIdentifierOrKeyType.PublicKeyInfo:
                if (!PkcsUtils.CmsSupported()) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Not_Supported"));
                }
                if (value.GetType() != typeof(PublicKeyInfo)) {
                    throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type_Value_Mismatch"), value.GetType().ToString());
                }
                break;
            default:
                throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Cms_Invalid_Subject_Identifier_Type"), type.ToString());
            }
 
            m_type = type;
            m_value = value;
        }
    }
 
 
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class AlgorithmIdentifier {
        private Oid     m_oid;
        private int     m_keyLength;
        private byte[]  m_parameters;
 
        public AlgorithmIdentifier () {
            Reset(Oid.FromOidValue(CAPI.szOID_RSA_DES_EDE3_CBC, OidGroup.EncryptionAlgorithm), 0, new byte[0]);
        }
 
        public AlgorithmIdentifier (Oid oid) {
            Reset(oid, 0, new byte[0]);
        }
 
        public AlgorithmIdentifier (Oid oid, int keyLength) {
            Reset(oid, keyLength, new byte[0]);
        }
 
        [SecurityCritical]
        internal AlgorithmIdentifier (CAPI.CERT_PUBLIC_KEY_INFO keyInfo) {
            SafeLocalAllocHandle pKeyInfo = CAPI.LocalAlloc(CAPI.LPTR, new IntPtr(Marshal.SizeOf(typeof(CAPI.CERT_PUBLIC_KEY_INFO))));
            Marshal.StructureToPtr(keyInfo, pKeyInfo.DangerousGetHandle(), false);
            int keyLength = (int) CAPI.CAPISafe.CertGetPublicKeyLength(CAPI.X509_ASN_ENCODING | CAPI.PKCS_7_ASN_ENCODING, pKeyInfo.DangerousGetHandle());
            byte[] parameters = new byte[keyInfo.Algorithm.Parameters.cbData];
            if (parameters.Length > 0) {
                Marshal.Copy(keyInfo.Algorithm.Parameters.pbData, parameters, 0, parameters.Length);
            }
            Marshal.DestroyStructure(pKeyInfo.DangerousGetHandle(), typeof(CAPI.CERT_PUBLIC_KEY_INFO));
            pKeyInfo.Dispose();
            Reset(Oid.FromOidValue(keyInfo.Algorithm.pszObjId, OidGroup.PublicKeyAlgorithm), keyLength, parameters);
        }
 
        [SecurityCritical]
        internal AlgorithmIdentifier (CAPI.CRYPT_ALGORITHM_IDENTIFIER algorithmIdentifier) {
            int keyLength = 0;
            uint cbParameters = 0;
            SafeLocalAllocHandle pbParameters = SafeLocalAllocHandle.InvalidHandle;
            byte[] parameters = new byte[0];
 
            uint algId = X509Utils.OidToAlgId(algorithmIdentifier.pszObjId);
 
            if (algId == CAPI.CALG_RC2) {
                if (algorithmIdentifier.Parameters.cbData > 0) {
                    if (!CAPI.DecodeObject(new IntPtr(CAPI.PKCS_RC2_CBC_PARAMETERS),
                                           algorithmIdentifier.Parameters.pbData,
                                           algorithmIdentifier.Parameters.cbData,
                                           out pbParameters,
                                           out cbParameters))
                        throw new CryptographicException(Marshal.GetLastWin32Error());
 
                    CAPI.CRYPT_RC2_CBC_PARAMETERS rc2Parameters = (CAPI.CRYPT_RC2_CBC_PARAMETERS) Marshal.PtrToStructure(pbParameters.DangerousGetHandle(), typeof(CAPI.CRYPT_RC2_CBC_PARAMETERS));
                    switch (rc2Parameters.dwVersion) {
                    case CAPI.CRYPT_RC2_40BIT_VERSION:
                        keyLength = 40;
                        break;
                    case CAPI.CRYPT_RC2_56BIT_VERSION:
                        keyLength = 56;
                        break;
                    case CAPI.CRYPT_RC2_128BIT_VERSION:
                        keyLength = 128;
                        break;
                    }
                    // Retrieve IV if available.
                    if (rc2Parameters.fIV) {
                        parameters = (byte[]) rc2Parameters.rgbIV.Clone();
                    }
                }
            }
            else if (algId == CAPI.CALG_RC4 || algId == CAPI.CALG_DES || algId == CAPI.CALG_3DES) {
                // Retrieve the IV if available. For non RC2, the parameter contains the IV 
                // (for RC4 the IV is really the salt). There are (128 - KeyLength) / 8
                // bytes of RC4 salt.
                if (algorithmIdentifier.Parameters.cbData > 0) {
                    if (!CAPI.DecodeObject(new IntPtr(CAPI.X509_OCTET_STRING),
                                           algorithmIdentifier.Parameters.pbData,
                                           algorithmIdentifier.Parameters.cbData,
                                           out pbParameters,
                                           out cbParameters))
                        throw new CryptographicException(Marshal.GetLastWin32Error());
 
                    if (cbParameters > Marshal.SizeOf(typeof(CAPI.CRYPTOAPI_BLOB))) {
                        CAPI.CRYPTOAPI_BLOB blob = (CAPI.CRYPTOAPI_BLOB)Marshal.PtrToStructure(pbParameters.DangerousGetHandle(), typeof(CAPI.CRYPTOAPI_BLOB));
 
                        if (algId == CAPI.CALG_RC4) {
                            if (blob.cbData > 0) {
                                parameters = new byte[blob.cbData];
                                Marshal.Copy(blob.pbData, parameters, 0, parameters.Length);
                            }
                        }
                        else {
                            // This should be the same as the RC4 code, but for compatibility
                            // * Allocate an array as big as the CRYPTOAPI_BLOB
                            // * Copy in the cbData value
                            // * Copy in the (pbData) value
                            //
                            // But don't copy in the pbData pointer value (or, rather, clear it out),
                            // among other things it makes decoding the same contents into two
                            // different EnvelopedCms objects say the parameters were different.
                            parameters = new byte[cbParameters];
                            Marshal.Copy(pbParameters.DangerousGetHandle(), parameters, 0, parameters.Length);
                            Array.Clear(parameters, sizeof(uint), (int)(parameters.Length - blob.cbData - sizeof(uint)));
                        }
                    }
                }
 
                // Determine key length.
                if (algId == CAPI.CALG_RC4) {
                    // For RC4, keyLength = 128 - (salt length * 8).
                    keyLength = 128 - ((int) parameters.Length * 8);
                }
                else if (algId == CAPI.CALG_DES) {
                    // DES key length is fixed at 64 (or 56 without the parity bits).
                    keyLength = 64;
                }
                else {
                    // 3DES key length is fixed at 192 (or 168 without the parity bits).
                    keyLength = 192;
                }
            }
            else {
                // Everything else, don't decode it as CAPI may not expose or know how.
                if (algorithmIdentifier.Parameters.cbData > 0) {
                    parameters = new byte[algorithmIdentifier.Parameters.cbData];
                    Marshal.Copy(algorithmIdentifier.Parameters.pbData, parameters, 0, parameters.Length);
                }
            }
 
            Reset(Oid.FromOidValue(algorithmIdentifier.pszObjId, OidGroup.All), keyLength, parameters);
            pbParameters.Dispose();
        }
 
        public Oid Oid {
            get {
                return m_oid;
            }
            set {
                m_oid = value;
            }
        }
 
        public int KeyLength {
            get {
                return m_keyLength;
            }
            set {
                m_keyLength = value;
            }
        }
 
        public byte[] Parameters {
            get {
                return m_parameters;
            }
            set {
                m_parameters = value;
            }
        }
 
        //
        // Private methods.
        //
 
        private void Reset (Oid oid, int keyLength, byte[] parameters) {
            m_oid = oid;
            m_keyLength = keyLength;
            m_parameters = parameters;
        }
    }
 
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class ContentInfo {
        private Oid      m_contentType;
        private byte[]   m_content;
        private IntPtr   m_pContent = IntPtr.Zero;
        private GCHandle m_gcHandle;
        //
        // Constructors
        //
 
        private ContentInfo () :
            this(Oid.FromOidValue(CAPI.szOID_RSA_data, OidGroup.ExtensionOrAttribute), new byte[0]) {
        }
 
        public ContentInfo (byte[] content) :
            this(Oid.FromOidValue(CAPI.szOID_RSA_data, OidGroup.ExtensionOrAttribute), content) {
        }
 
        public ContentInfo (Oid contentType, byte[] content) {
            if (contentType == null)
                throw new ArgumentNullException("contentType");
            if (content == null)
                throw new ArgumentNullException("content");
 
            m_contentType = contentType;
            m_content = content;
        }
 
        public Oid ContentType {
            get {
                return m_contentType;
            }
        }
 
        public byte[] Content {
            get {
                return m_content;
            }
        }
 
        [SecuritySafeCritical]
        ~ContentInfo()
        {
            if (m_gcHandle.IsAllocated) {
                m_gcHandle.Free();
            }
        }
 
        internal IntPtr pContent {
            [SecurityCritical]
            get {
                if (IntPtr.Zero == m_pContent) {
                    if (m_content != null && m_content.Length != 0) {
                        m_gcHandle = GCHandle.Alloc(m_content, GCHandleType.Pinned);
                        //m_pContent = handle.AddrOfPinnedObject();
                        m_pContent = Marshal.UnsafeAddrOfPinnedArrayElement(m_content, 0);
                    }
                }
                return m_pContent;
            }
        }
 
        [SecuritySafeCritical]
        public static Oid GetContentType (byte[] encodedMessage) {
            if (encodedMessage == null)
                throw new ArgumentNullException("encodedMessage");
 
            SafeCryptMsgHandle safeCryptMsgHandle = CAPI.CAPISafe.CryptMsgOpenToDecode(
                                                            CAPI.X509_ASN_ENCODING | CAPI.PKCS_7_ASN_ENCODING,
                                                            0,
                                                            0,
                                                            IntPtr.Zero,
                                                            IntPtr.Zero,
                                                            IntPtr.Zero);
            if (safeCryptMsgHandle == null || safeCryptMsgHandle.IsInvalid)
                throw new CryptographicException(Marshal.GetLastWin32Error());
 
            if (!CAPI.CAPISafe.CryptMsgUpdate(safeCryptMsgHandle, encodedMessage, (uint) encodedMessage.Length, true))
                throw new CryptographicException(Marshal.GetLastWin32Error());
 
            Oid contentType;
            switch (PkcsUtils.GetMessageType(safeCryptMsgHandle)) {
            case CAPI.CMSG_DATA:
                contentType = Oid.FromOidValue(CAPI.szOID_RSA_data, OidGroup.ExtensionOrAttribute);
                break;
            case CAPI.CMSG_SIGNED:
                contentType = Oid.FromOidValue(CAPI.szOID_RSA_signedData, OidGroup.ExtensionOrAttribute);
                break;
            case CAPI.CMSG_ENVELOPED:
                contentType = Oid.FromOidValue(CAPI.szOID_RSA_envelopedData, OidGroup.ExtensionOrAttribute);
                break;
            case CAPI.CMSG_SIGNED_AND_ENVELOPED:
                contentType = Oid.FromOidValue(CAPI.szOID_RSA_signEnvData, OidGroup.ExtensionOrAttribute);
                break;
            case CAPI.CMSG_HASHED:
                contentType = Oid.FromOidValue(CAPI.szOID_RSA_hashedData, OidGroup.ExtensionOrAttribute);
                break;
            case CAPI.CMSG_ENCRYPTED:
                contentType = Oid.FromOidValue(CAPI.szOID_RSA_encryptedData, OidGroup.ExtensionOrAttribute);
                break;
            default:
                throw new CryptographicException(CAPI.CRYPT_E_INVALID_MSG_TYPE);
            }
 
            safeCryptMsgHandle.Dispose();
 
            return contentType;
        }
    }
}