| File: System\Security\Cryptography\Pkcs\EnvelopedCms.cs | Web Access |
| Project: src\runtime\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.Diagnostics; using System.Security.Cryptography.X509Certificates; using Internal.Cryptography; namespace System.Security.Cryptography.Pkcs { public sealed class EnvelopedCms { // // Constructors // public EnvelopedCms() : this(new ContentInfo(Array.Empty<byte>())) { } public EnvelopedCms(ContentInfo contentInfo) : this(contentInfo, new AlgorithmIdentifier(Oids.Aes256CbcOid.CopyOid())) { } public EnvelopedCms(ContentInfo contentInfo, AlgorithmIdentifier encryptionAlgorithm) { ArgumentNullException.ThrowIfNull(contentInfo); ArgumentNullException.ThrowIfNull(encryptionAlgorithm); Version = 0; // It makes little sense to ask for a version before you've decoded, but since the .NET Framework returns 0 in that case, we will too. ContentInfo = contentInfo; ContentEncryptionAlgorithm = encryptionAlgorithm; Certificates = new X509Certificate2Collection(); UnprotectedAttributes = new CryptographicAttributeObjectCollection(); _decryptorPal = null; _lastCall = LastCall.Ctor; } // // Latched properties. // - For senders, the caller sets their values and they act as implicit inputs to the Encrypt() method. // - For recipients, the Decode() and Decrypt() methods reset their values and the caller can inspect them if desired. // public int Version { get; private set; } public ContentInfo ContentInfo { get; private set; } public AlgorithmIdentifier ContentEncryptionAlgorithm { get; private set; } public X509Certificate2Collection Certificates { get; private set; } public CryptographicAttributeObjectCollection UnprotectedAttributes { get; private set; } // // Recipients invoke this property to retrieve the recipient information after a Decode() operation. // Senders should not invoke this property. // public RecipientInfoCollection RecipientInfos { get { switch (_lastCall) { case LastCall.Ctor: return new RecipientInfoCollection(); case LastCall.Encrypt: throw PkcsPal.Instance.CreateRecipientInfosAfterEncryptException(); case LastCall.Decode: case LastCall.Decrypt: Debug.Assert(_decryptorPal != null); return _decryptorPal.RecipientInfos; default: Debug.Fail($"Unexpected _lastCall value: {_lastCall}"); throw new InvalidOperationException(); } } } // // Encrypt() overloads. Senders invoke this to encrypt and encode a CMS. Afterward, invoke the Encode() method to retrieve the actual encoding. // public void Encrypt(CmsRecipient recipient) { ArgumentNullException.ThrowIfNull(recipient); Encrypt(new CmsRecipientCollection(recipient)); } public void Encrypt(CmsRecipientCollection recipients) { ArgumentNullException.ThrowIfNull(recipients); // .NET Framework compat note: Unlike the desktop, we don't provide a free UI to select the recipient. The app must give it to us programmatically. if (recipients.Count == 0) throw new PlatformNotSupportedException(SR.Cryptography_Cms_NoRecipients); if (_decryptorPal != null) { _decryptorPal.Dispose(); _decryptorPal = null; } _encodedMessage = PkcsPal.Instance.Encrypt(recipients, ContentInfo, ContentEncryptionAlgorithm, Certificates, UnprotectedAttributes); _lastCall = LastCall.Encrypt; } // // Senders invoke Encode() after a successful Encrypt() to retrieve the RFC-compliant on-the-wire representation. // public byte[] Encode() { if (_encodedMessage == null) throw new InvalidOperationException(SR.Cryptography_Cms_MessageNotEncrypted); return _encodedMessage.CloneByteArray(); } // // Recipients invoke Decode() to turn the on-the-wire representation into a usable EnvelopedCms instance. Next step is to call Decrypt(). // public void Decode(byte[] encodedMessage) { ArgumentNullException.ThrowIfNull(encodedMessage); Decode(new ReadOnlySpan<byte>(encodedMessage)); } /// <summary> /// Decodes the provided data as a CMS/PKCS#7 EnvelopedData message. /// </summary> /// <param name="encodedMessage"> /// The data to decode. /// </param> /// <exception cref="CryptographicException"> /// The <paramref name="encodedMessage"/> parameter was not successfully decoded. /// </exception> #if NET || NETSTANDARD2_1 public #else internal #endif void Decode(ReadOnlySpan<byte> encodedMessage) { if (_decryptorPal != null) { _decryptorPal.Dispose(); _decryptorPal = null; } int version; ContentInfo contentInfo; AlgorithmIdentifier contentEncryptionAlgorithm; X509Certificate2Collection originatorCerts; CryptographicAttributeObjectCollection unprotectedAttributes; _decryptorPal = PkcsPal.Instance.Decode(encodedMessage, out version, out contentInfo, out contentEncryptionAlgorithm, out originatorCerts, out unprotectedAttributes); Version = version; ContentInfo = contentInfo; ContentEncryptionAlgorithm = contentEncryptionAlgorithm; Certificates = originatorCerts; UnprotectedAttributes = unprotectedAttributes; // .NET Framework compat: Encode() after a Decode() returns you the same thing that ContentInfo.Content does. _encodedMessage = contentInfo.Content.CloneByteArray(); _lastCall = LastCall.Decode; } // // Decrypt() overloads: Recipients invoke Decrypt() after a successful Decode(). Afterwards, invoke ContentInfo to get the decrypted content. // public void Decrypt() { DecryptContent(RecipientInfos, null); } public void Decrypt(RecipientInfo recipientInfo) { ArgumentNullException.ThrowIfNull(recipientInfo); DecryptContent(new RecipientInfoCollection(recipientInfo), null); } public void Decrypt(RecipientInfo recipientInfo, X509Certificate2Collection extraStore) { ArgumentNullException.ThrowIfNull(recipientInfo); ArgumentNullException.ThrowIfNull(extraStore); DecryptContent(new RecipientInfoCollection(recipientInfo), extraStore); } public void Decrypt(X509Certificate2Collection extraStore) { ArgumentNullException.ThrowIfNull(extraStore); DecryptContent(RecipientInfos, extraStore); } #if NET || NETSTANDARD2_1 public #else internal #endif void Decrypt(RecipientInfo recipientInfo, AsymmetricAlgorithm? privateKey) { ArgumentNullException.ThrowIfNull(recipientInfo); CheckStateForDecryption(); X509Certificate2Collection extraStore = new X509Certificate2Collection(); ContentInfo? contentInfo = _decryptorPal!.TryDecrypt( recipientInfo, null, privateKey, Certificates, extraStore, out Exception? exception); if (exception != null) throw exception; SetContentInfo(contentInfo!); } private void DecryptContent(RecipientInfoCollection recipientInfos, X509Certificate2Collection? extraStore) { CheckStateForDecryption(); extraStore ??= new X509Certificate2Collection(); X509Certificate2Collection certs = new X509Certificate2Collection(); PkcsPal.Instance.AddCertsFromStoreForDecryption(certs); certs.AddRange(extraStore); X509Certificate2Collection originatorCerts = Certificates; ContentInfo? newContentInfo = null; Exception? exception = PkcsPal.Instance.CreateRecipientsNotFoundException(); foreach (RecipientInfo recipientInfo in recipientInfos) { X509Certificate2? cert = certs.TryFindMatchingCertificate(recipientInfo.RecipientIdentifier); if (cert == null) { exception = PkcsPal.Instance.CreateRecipientsNotFoundException(); continue; } newContentInfo = _decryptorPal!.TryDecrypt( recipientInfo, cert, null, originatorCerts, extraStore, out exception); if (exception != null) continue; break; } if (exception != null) throw exception; SetContentInfo(newContentInfo!); } private void CheckStateForDecryption() { switch (_lastCall) { case LastCall.Ctor: throw new InvalidOperationException(SR.Cryptography_Cms_MessageNotEncrypted); case LastCall.Encrypt: throw PkcsPal.Instance.CreateDecryptAfterEncryptException(); case LastCall.Decrypt: throw PkcsPal.Instance.CreateDecryptTwiceException(); case LastCall.Decode: break; // This is the expected state. default: Debug.Fail($"Unexpected _lastCall value: {_lastCall}"); throw new InvalidOperationException(); } } private void SetContentInfo(ContentInfo contentInfo) { ContentInfo = contentInfo; // .NET Framework compat: Encode() after a Decrypt() returns you the same thing that ContentInfo.Content does. _encodedMessage = contentInfo.Content.CloneByteArray(); _lastCall = LastCall.Decrypt; } // // Instance fields // private DecryptorPal? _decryptorPal; private byte[]? _encodedMessage; private LastCall _lastCall; private enum LastCall { Ctor = 1, Encrypt = 2, Decode = 3, Decrypt = 4, } } }