File: Internal\Cryptography\Pal\AnyOS\ManagedPal.Encrypt.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography.Pkcs\src\System.Security.Cryptography.Pkcs.csproj (System.Security.Cryptography.Pkcs)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Formats.Asn1;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Asn1;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.Pkcs.Asn1;
using System.Security.Cryptography.X509Certificates;
 
namespace Internal.Cryptography.Pal.AnyOS
{
    internal sealed partial class ManagedPkcsPal : PkcsPal
    {
        public override unsafe byte[] Encrypt(
            CmsRecipientCollection recipients,
            ContentInfo contentInfo,
            AlgorithmIdentifier contentEncryptionAlgorithm,
            X509Certificate2Collection originatorCerts,
            CryptographicAttributeObjectCollection unprotectedAttributes)
        {
            byte[] encryptedContent = EncryptContent(
                contentInfo,
                contentEncryptionAlgorithm,
                out byte[] cek,
                out byte[] parameterBytes);
 
            // Pin the CEK to prevent it from getting copied during heap compaction.
            fixed (byte* pinnedCek = cek)
            {
                try
                {
                    return Encrypt(
                        recipients,
                        contentInfo,
                        contentEncryptionAlgorithm,
                        originatorCerts,
                        unprotectedAttributes,
                        encryptedContent,
                        cek,
                        parameterBytes);
                }
                finally
                {
                    Array.Clear(cek, 0, cek.Length);
                }
            }
        }
 
        private byte[] Encrypt(
            CmsRecipientCollection recipients,
            ContentInfo contentInfo,
            AlgorithmIdentifier contentEncryptionAlgorithm,
            X509Certificate2Collection originatorCerts,
            CryptographicAttributeObjectCollection unprotectedAttributes,
            byte[] encryptedContent,
            byte[] cek,
            byte[] parameterBytes)
        {
            EnvelopedDataAsn envelopedData = new EnvelopedDataAsn
            {
                EncryptedContentInfo =
                {
                    ContentType = contentInfo.ContentType.Value!,
 
                    ContentEncryptionAlgorithm =
                    {
                        Algorithm = contentEncryptionAlgorithm.Oid.Value!,
                        Parameters = parameterBytes,
                    },
 
                    EncryptedContent = encryptedContent,
                },
            };
 
            if (unprotectedAttributes != null && unprotectedAttributes.Count > 0)
            {
                List<AttributeAsn> attrList = CmsSigner.BuildAttributes(unprotectedAttributes);
 
                envelopedData.UnprotectedAttributes = PkcsHelpers.NormalizeAttributeSet(attrList.ToArray());
            }
 
            if (originatorCerts != null && originatorCerts.Count > 0)
            {
                CertificateChoiceAsn[] certs = new CertificateChoiceAsn[originatorCerts.Count];
 
                for (int i = 0; i < originatorCerts.Count; i++)
                {
                    certs[i].Certificate = originatorCerts[i].RawData;
                }
 
                envelopedData.OriginatorInfo = new OriginatorInfoAsn
                {
                    CertificateSet = certs,
                };
            }
 
            envelopedData.RecipientInfos = new RecipientInfoAsn[recipients.Count];
 
            bool allRecipientsVersion0 = true;
 
            for (var i = 0; i < recipients.Count; i++)
            {
                CmsRecipient recipient = recipients[i];
                bool v0Recipient;
 
                envelopedData.RecipientInfos[i].Ktri = recipient.Certificate.GetKeyAlgorithm() switch
                {
                    Oids.Rsa => MakeKtri(cek, recipient, out v0Recipient),
                    _ => throw new CryptographicException(
                            SR.Cryptography_Cms_UnknownAlgorithm,
                            recipient.Certificate.GetKeyAlgorithm()),
                };
                allRecipientsVersion0 = allRecipientsVersion0 && v0Recipient;
            }
 
            // https://tools.ietf.org/html/rfc5652#section-6.1
            //
            // v4 (RFC 3852):
            //   * OriginatorInfo contains certificates with type other (not supported)
            //   * OriginatorInfo contains crls with type other (not supported)
            // v3 (RFC 3369):
            //   * OriginatorInfo contains v2 attribute certificates (not supported)
            //   * Any PWRI (password) recipients are present (not supported)
            //   * Any ORI (other) recipients are present (not supported)
            // v2 (RFC 2630):
            //   * OriginatorInfo is present
            //   * Any RecipientInfo has a non-zero version number
            //   * UnprotectedAttrs is present
            // v1 (not defined for EnvelopedData)
            // v0 (RFC 2315):
            //   * Anything not already matched
 
            if (envelopedData.OriginatorInfo != null ||
                !allRecipientsVersion0 ||
                envelopedData.UnprotectedAttributes != null)
            {
                envelopedData.Version = 2;
            }
 
            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
            envelopedData.Encode(writer);
            return PkcsHelpers.EncodeContentInfo(writer.Encode(), Oids.Pkcs7Enveloped);
        }
 
        private static byte[] EncryptContent(
            ContentInfo contentInfo,
            AlgorithmIdentifier contentEncryptionAlgorithm,
            out byte[] cek,
            out byte[] parameterBytes)
        {
            using (SymmetricAlgorithm alg = OpenAlgorithm(contentEncryptionAlgorithm))
            {
                cek = alg.Key;
 
                if (alg is RC2)
                {
                    Rc2CbcParameters rc2Params = new Rc2CbcParameters(alg.IV, alg.KeySize);
 
                    AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
                    rc2Params.Encode(writer);
                    parameterBytes = writer.Encode();
                }
                else
                {
                    parameterBytes = PkcsHelpers.EncodeOctetString(alg.IV);
                }
 
                byte[] toEncrypt = contentInfo.Content;
 
                if (contentInfo.ContentType.Value == Oids.Pkcs7Data || contentInfo.Content.Length == 0)
                {
                    return EncryptOneShot(alg, contentInfo.Content);
                }
                else
                {
                    try
                    {
                        AsnDecoder.ReadEncodedValue(
                            contentInfo.Content,
                            AsnEncodingRules.BER,
                            out int contentOffset,
                            out int contentLength,
                            out _);
 
                        ReadOnlySpan<byte> content = contentInfo.Content.AsSpan(contentOffset, contentLength);
                        return EncryptOneShot(alg, content);
                    }
                    catch (AsnContentException e)
                    {
                        throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
                    }
                }
            }
 
            static byte[] EncryptOneShot(SymmetricAlgorithm alg, ReadOnlySpan<byte> plaintext)
            {
#if NET
                return alg.EncryptCbc(plaintext, alg.IV);
#else
                using (ICryptoTransform encryptor = alg.CreateEncryptor())
                {
                    return encryptor.OneShot(plaintext.ToArray());
                }
#endif
            }
        }
    }
}