From 8b9719a6929006a97c602cc720fbc7f0732c3d79 Mon Sep 17 00:00:00 2001 From: Taylor Buchanan Date: Fri, 2 Sep 2022 11:04:20 -0500 Subject: [PATCH] Fix incompatibility w/ Portable.BouncyCastle 1.9.0 This resolves the "Cannot Access a closed stream" error during decryption by delaying disposal of all streams returned from `GetDataStream`. Fixes #140 --- PgpCore/CompositeDisposable.cs | 27 ++ PgpCore/DisposableExtensions.cs | 22 ++ PgpCore/PGP.cs | 486 +++++++++++++++----------------- 3 files changed, 282 insertions(+), 253 deletions(-) create mode 100644 PgpCore/CompositeDisposable.cs create mode 100644 PgpCore/DisposableExtensions.cs diff --git a/PgpCore/CompositeDisposable.cs b/PgpCore/CompositeDisposable.cs new file mode 100644 index 0000000..8045816 --- /dev/null +++ b/PgpCore/CompositeDisposable.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Concurrent; + +namespace PgpCore +{ + /// + /// Simplified adaptation from System.Reactive. + /// + internal sealed class CompositeDisposable : IDisposable + { + private readonly ConcurrentQueue _disposables = new ConcurrentQueue(); + + public void Add(IDisposable disposable) + { + if (disposable == null) + throw new ArgumentNullException(nameof(disposable)); + + _disposables.Enqueue(disposable); + } + + public void Dispose() + { + while (_disposables.TryDequeue(out IDisposable disposable)) + disposable.Dispose(); + } + } +} diff --git a/PgpCore/DisposableExtensions.cs b/PgpCore/DisposableExtensions.cs new file mode 100644 index 0000000..cb8fd51 --- /dev/null +++ b/PgpCore/DisposableExtensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace PgpCore +{ + internal static class DisposableExtensions + { + /// + /// Adapted from ReactiveUI. + /// + public static T DisposeWith(this T @this, CompositeDisposable disposables) + where T : IDisposable + { + if (@this == null) + throw new ArgumentNullException(nameof(@this)); + if (disposables == null) + throw new ArgumentNullException(nameof(disposables)); + + disposables.Add(@this); + return @this; + } + } +} diff --git a/PgpCore/PGP.cs b/PgpCore/PGP.cs index 82fc822..6d21b5a 100644 --- a/PgpCore/PGP.cs +++ b/PgpCore/PGP.cs @@ -5001,81 +5001,76 @@ private async Task DecryptAsync(Stream inputStream, Stream outputStream) if (enc == null && message == null) throw new ArgumentException("Failed to detect encrypted content format.", nameof(inputStream)); - // decrypt - PgpPrivateKey privateKey = null; - PgpPublicKeyEncryptedData pbe = null; - if (enc != null) + using (CompositeDisposable disposables = new CompositeDisposable()) { - foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in enc.GetEncryptedDataObjects()) + // decrypt + PgpPrivateKey privateKey = null; + PgpPublicKeyEncryptedData pbe = null; + if (enc != null) { - privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - - if (privateKey != null) + foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in enc.GetEncryptedDataObjects()) { - pbe = publicKeyEncryptedData; - break; - } - } - - if (privateKey == null) - throw new ArgumentException("Secret key for message not found."); + privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - PgpObjectFactory plainFact; + if (privateKey != null) + { + pbe = publicKeyEncryptedData; + break; + } + } - using (Stream clear = pbe.GetDataStream(privateKey)) - { - plainFact = new PgpObjectFactory(clear); - } + if (privateKey == null) + throw new ArgumentException("Secret key for message not found."); - message = plainFact.NextPgpObject(); + Stream clear = pbe.GetDataStream(privateKey).DisposeWith(disposables); + PgpObjectFactory plainFact = new PgpObjectFactory(clear); - if (message is PgpOnePassSignatureList) - { message = plainFact.NextPgpObject(); - } - } - - if (message is PgpCompressedData pgpCompressedData) - { - PgpObjectFactory objectFactory; - using (Stream compDataIn = pgpCompressedData.GetDataStream()) - { - objectFactory = new PgpObjectFactory(compDataIn); - message = objectFactory.NextPgpObject(); + if (message is PgpOnePassSignatureList) + { + message = plainFact.NextPgpObject(); + } } - if (message is PgpOnePassSignatureList) + if (message is PgpCompressedData pgpCompressedData) { + Stream compDataIn = pgpCompressedData.GetDataStream().DisposeWith(disposables); + PgpObjectFactory objectFactory = new PgpObjectFactory(compDataIn); message = objectFactory.NextPgpObject(); - var literalData = (PgpLiteralData)message; - Stream unc = literalData.GetInputStream(); - await Streams.PipeAllAsync(unc, outputStream); + + if (message is PgpOnePassSignatureList) + { + message = objectFactory.NextPgpObject(); + var literalData = (PgpLiteralData)message; + Stream unc = literalData.GetInputStream(); + await Streams.PipeAllAsync(unc, outputStream); + } + else + { + PgpLiteralData literalData = (PgpLiteralData)message; + Stream unc = literalData.GetInputStream(); + await Streams.PipeAllAsync(unc, outputStream); + } } - else + else if (message is PgpLiteralData literalData) { - PgpLiteralData literalData = (PgpLiteralData)message; Stream unc = literalData.GetInputStream(); await Streams.PipeAllAsync(unc, outputStream); - } - } - else if (message is PgpLiteralData literalData) - { - Stream unc = literalData.GetInputStream(); - await Streams.PipeAllAsync(unc, outputStream); - if (pbe.IsIntegrityProtected()) - { - if (!pbe.Verify()) + if (pbe.IsIntegrityProtected()) { - throw new PgpException("Message failed integrity check."); + if (!pbe.Verify()) + { + throw new PgpException("Message failed integrity check."); + } } } + else if (message is PgpOnePassSignatureList) + throw new PgpException("Encrypted message contains a signed message - not literal data."); + else + throw new PgpException("Message is not a simple encrypted file."); } - else if (message is PgpOnePassSignatureList) - throw new PgpException("Encrypted message contains a signed message - not literal data."); - else - throw new PgpException("Message is not a simple encrypted file."); } #endregion DecryptAsync @@ -5114,81 +5109,76 @@ private void Decrypt(Stream inputStream, Stream outputStream) if (enc == null && message == null) throw new ArgumentException("Failed to detect encrypted content format.", nameof(inputStream)); - // decrypt - PgpPrivateKey privateKey = null; - PgpPublicKeyEncryptedData pbe = null; - if (enc != null) + using (CompositeDisposable disposables = new CompositeDisposable()) { - foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in enc.GetEncryptedDataObjects()) + // decrypt + PgpPrivateKey privateKey = null; + PgpPublicKeyEncryptedData pbe = null; + if (enc != null) { - privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - - if (privateKey != null) + foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in enc.GetEncryptedDataObjects()) { - pbe = publicKeyEncryptedData; - break; - } - } + privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - if (privateKey == null) - throw new ArgumentException("Secret key for message not found."); - - PgpObjectFactory plainFact; + if (privateKey != null) + { + pbe = publicKeyEncryptedData; + break; + } + } - using (Stream clear = pbe.GetDataStream(privateKey)) - { - plainFact = new PgpObjectFactory(clear); - } + if (privateKey == null) + throw new ArgumentException("Secret key for message not found."); - message = plainFact.NextPgpObject(); + Stream clear = pbe.GetDataStream(privateKey).DisposeWith(disposables); + PgpObjectFactory plainFact = new PgpObjectFactory(clear); - if (message is PgpOnePassSignatureList) - { message = plainFact.NextPgpObject(); - } - } - if (message is PgpCompressedData pgpCompressedData) - { - PgpObjectFactory objectFactory; - - using (Stream compDataIn = pgpCompressedData.GetDataStream()) - { - objectFactory = new PgpObjectFactory(compDataIn); - message = objectFactory.NextPgpObject(); + if (message is PgpOnePassSignatureList) + { + message = plainFact.NextPgpObject(); + } } - if (message is PgpOnePassSignatureList) + if (message is PgpCompressedData pgpCompressedData) { + Stream compDataIn = pgpCompressedData.GetDataStream().DisposeWith(disposables); + PgpObjectFactory objectFactory = new PgpObjectFactory(compDataIn); message = objectFactory.NextPgpObject(); - PgpLiteralData literalData = (PgpLiteralData)message; - Stream unc = literalData.GetInputStream(); - Streams.PipeAll(unc, outputStream); + + if (message is PgpOnePassSignatureList) + { + message = objectFactory.NextPgpObject(); + PgpLiteralData literalData = (PgpLiteralData)message; + Stream unc = literalData.GetInputStream(); + Streams.PipeAll(unc, outputStream); + } + else + { + PgpLiteralData literalData = (PgpLiteralData)message; + Stream unc = literalData.GetInputStream(); + Streams.PipeAll(unc, outputStream); + } } - else + else if (message is PgpLiteralData literalData) { - PgpLiteralData literalData = (PgpLiteralData)message; Stream unc = literalData.GetInputStream(); Streams.PipeAll(unc, outputStream); - } - } - else if (message is PgpLiteralData literalData) - { - Stream unc = literalData.GetInputStream(); - Streams.PipeAll(unc, outputStream); - if (pbe.IsIntegrityProtected()) - { - if (!pbe.Verify()) + if (pbe.IsIntegrityProtected()) { - throw new PgpException("Message failed integrity check."); + if (!pbe.Verify()) + { + throw new PgpException("Message failed integrity check."); + } } } + else if (message is PgpOnePassSignatureList) + throw new PgpException("Encrypted message contains a signed message - not literal data."); + else + throw new PgpException("Message is not a simple encrypted file."); } - else if (message is PgpOnePassSignatureList) - throw new PgpException("Encrypted message contains a signed message - not literal data."); - else - throw new PgpException("Message is not a simple encrypted file."); } #endregion Decrypt @@ -5221,100 +5211,95 @@ private async Task DecryptAndVerifyAsync(Stream inputStream, Stream outputStream if (encryptedDataList == null && message == null) throw new ArgumentException("Failed to detect encrypted content format.", nameof(inputStream)); - // decrypt - PgpPrivateKey privateKey = null; - PgpPublicKeyEncryptedData pbe = null; - if (encryptedDataList != null) + using (CompositeDisposable disposables = new CompositeDisposable()) { - foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in - encryptedDataList.GetEncryptedDataObjects()) + // decrypt + PgpPrivateKey privateKey = null; + PgpPublicKeyEncryptedData pbe = null; + if (encryptedDataList != null) { - privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - - if (privateKey != null) + foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in + encryptedDataList.GetEncryptedDataObjects()) { - pbe = publicKeyEncryptedData; - break; - } - } - - if (privateKey == null) - throw new ArgumentException("Secret key for message not found."); + privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - PgpObjectFactory plainFact; - - using (Stream clear = pbe.GetDataStream(privateKey)) - { - plainFact = new PgpObjectFactory(clear); - } - - message = plainFact.NextPgpObject(); + if (privateKey != null) + { + pbe = publicKeyEncryptedData; + break; + } + } - if (message is PgpOnePassSignatureList pgpOnePassSignatureList) - { - PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; - var keyIdToVerify = pgpOnePassSignature.KeyId; + if (privateKey == null) + throw new ArgumentException("Secret key for message not found."); - // var verified = EncryptionKeys.ValidationPublicKey.KeyId == pgpOnePassSignature.KeyId || - // EncryptionKeys.ValidationPublicKey.GetKeySignatures().Cast() - // .Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); - var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, - out PgpPublicKey _); - if (verified == false) - throw new PgpException("Failed to verify file."); + Stream clear = pbe.GetDataStream(privateKey).DisposeWith(disposables); + PgpObjectFactory plainFact = new PgpObjectFactory(clear); message = plainFact.NextPgpObject(); - } - else if (!(message is PgpCompressedData)) - throw new PgpException("File was not signed."); - } - if (message is PgpCompressedData cData) - { - PgpObjectFactory objectFactory; + if (message is PgpOnePassSignatureList pgpOnePassSignatureList) + { + PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; + var keyIdToVerify = pgpOnePassSignature.KeyId; + + // var verified = EncryptionKeys.ValidationPublicKey.KeyId == pgpOnePassSignature.KeyId || + // EncryptionKeys.ValidationPublicKey.GetKeySignatures().Cast() + // .Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); + var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, + out PgpPublicKey _); + if (verified == false) + throw new PgpException("Failed to verify file."); + + message = plainFact.NextPgpObject(); + } + else if (!(message is PgpCompressedData)) + throw new PgpException("File was not signed."); + } - using (Stream compDataIn = cData.GetDataStream()) + if (message is PgpCompressedData cData) { - objectFactory = new PgpObjectFactory(compDataIn); + Stream compDataIn = cData.GetDataStream().DisposeWith(disposables); + PgpObjectFactory objectFactory = new PgpObjectFactory(compDataIn); message = objectFactory.NextPgpObject(); - } - if (message is PgpOnePassSignatureList pgpOnePassSignatureList) + if (message is PgpOnePassSignatureList pgpOnePassSignatureList) + { + PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; + var keyIdToVerify = pgpOnePassSignature.KeyId; + + // var verified = EncryptionKeys.ValidationKeys.First().KeyId == pgpOnePassSignature.KeyId || EncryptionKeys.ValidationKeys.First().GetKeySignatures().Cast().Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); + var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, + out PgpPublicKey _); + if (verified == false) + throw new PgpException("Failed to verify file."); + + message = objectFactory.NextPgpObject(); + PgpLiteralData literalData = (PgpLiteralData)message; + Stream unc = literalData.GetInputStream(); + await Streams.PipeAllAsync(unc, outputStream); + } + else + { + throw new PgpException("File was not signed."); + } + } + else if (message is PgpLiteralData literalData) { - PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; - var keyIdToVerify = pgpOnePassSignature.KeyId; - - // var verified = EncryptionKeys.ValidationKeys.First().KeyId == pgpOnePassSignature.KeyId || EncryptionKeys.ValidationKeys.First().GetKeySignatures().Cast().Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); - var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, - out PgpPublicKey _); - if (verified == false) - throw new PgpException("Failed to verify file."); - - message = objectFactory.NextPgpObject(); - PgpLiteralData literalData = (PgpLiteralData)message; Stream unc = literalData.GetInputStream(); await Streams.PipeAllAsync(unc, outputStream); - } - else - { - throw new PgpException("File was not signed."); - } - } - else if (message is PgpLiteralData literalData) - { - Stream unc = literalData.GetInputStream(); - await Streams.PipeAllAsync(unc, outputStream); - if (pbe.IsIntegrityProtected()) - { - if (!pbe.Verify()) + if (pbe.IsIntegrityProtected()) { - throw new PgpException("Message failed integrity check."); + if (!pbe.Verify()) + { + throw new PgpException("Message failed integrity check."); + } } } + else + throw new PgpException("File was not signed."); } - else - throw new PgpException("File was not signed."); } #endregion DecryptAndVerifyAsync @@ -5347,101 +5332,96 @@ private void DecryptAndVerify(Stream inputStream, Stream outputStream) if (encryptedDataList == null && message == null) throw new ArgumentException("Failed to detect encrypted content format.", nameof(inputStream)); - // decrypt - PgpPrivateKey privateKey = null; - PgpPublicKeyEncryptedData pbe = null; - if (encryptedDataList != null) + using (CompositeDisposable disposables = new CompositeDisposable()) { - foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in - encryptedDataList.GetEncryptedDataObjects()) + // decrypt + PgpPrivateKey privateKey = null; + PgpPublicKeyEncryptedData pbe = null; + if (encryptedDataList != null) { - privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - - if (privateKey != null) + foreach (PgpPublicKeyEncryptedData publicKeyEncryptedData in + encryptedDataList.GetEncryptedDataObjects()) { - pbe = publicKeyEncryptedData; - break; - } - } - - if (privateKey == null) - throw new ArgumentException("Secret key for message not found."); - - PgpObjectFactory plainFact; - - using (Stream clear = pbe.GetDataStream(privateKey)) - { - plainFact = new PgpObjectFactory(clear); - } + privateKey = EncryptionKeys.FindSecretKey(publicKeyEncryptedData.KeyId); - message = plainFact.NextPgpObject(); + if (privateKey != null) + { + pbe = publicKeyEncryptedData; + break; + } + } - if (message is PgpOnePassSignatureList pgpOnePassSignatureList) - { - PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; - var keyIdToVerify = pgpOnePassSignature.KeyId; + if (privateKey == null) + throw new ArgumentException("Secret key for message not found."); - // var verified = EncryptionKeys.ValidationPublicKey.KeyId == pgpOnePassSignature.KeyId || - // EncryptionKeys.ValidationPublicKey.GetKeySignatures().Cast() - // .Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); - var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, - out PgpPublicKey _); - if (verified == false) - throw new PgpException("Failed to verify file."); + Stream clear = pbe.GetDataStream(privateKey).DisposeWith(disposables); + PgpObjectFactory plainFact = new PgpObjectFactory(clear); message = plainFact.NextPgpObject(); - } - else if (!(message is PgpCompressedData)) - throw new PgpException("File was not signed."); - } - if (message is PgpCompressedData cData) - { - PgpObjectFactory objectFactory; + if (message is PgpOnePassSignatureList pgpOnePassSignatureList) + { + PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; + var keyIdToVerify = pgpOnePassSignature.KeyId; + + // var verified = EncryptionKeys.ValidationPublicKey.KeyId == pgpOnePassSignature.KeyId || + // EncryptionKeys.ValidationPublicKey.GetKeySignatures().Cast() + // .Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); + var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, + out PgpPublicKey _); + if (verified == false) + throw new PgpException("Failed to verify file."); + + message = plainFact.NextPgpObject(); + } + else if (!(message is PgpCompressedData)) + throw new PgpException("File was not signed."); + } - using (Stream compDataIn = cData.GetDataStream()) + if (message is PgpCompressedData cData) { - objectFactory = new PgpObjectFactory(compDataIn); + Stream compDataIn = cData.GetDataStream().DisposeWith(disposables); + PgpObjectFactory objectFactory = new PgpObjectFactory(compDataIn); message = objectFactory.NextPgpObject(); - } - if (message is PgpOnePassSignatureList pgpOnePassSignatureList) + if (message is PgpOnePassSignatureList pgpOnePassSignatureList) + { + PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; + var keyIdToVerify = pgpOnePassSignature.KeyId; + // var verified = EncryptionKeys.ValidationPublicKey.KeyId == pgpOnePassSignature.KeyId || + // EncryptionKeys.ValidationPublicKey.GetKeySignatures().Cast() + // .Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); + var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, + out PgpPublicKey _); + if (verified == false) + throw new PgpException("Failed to verify file."); + + message = objectFactory.NextPgpObject(); + var literalData = (PgpLiteralData)message; + Stream unc = literalData.GetInputStream(); + Streams.PipeAll(unc, outputStream); + } + else + { + throw new PgpException("File was not signed."); + } + } + else if (message is PgpLiteralData literalData) { - PgpOnePassSignature pgpOnePassSignature = pgpOnePassSignatureList[0]; - var keyIdToVerify = pgpOnePassSignature.KeyId; - // var verified = EncryptionKeys.ValidationPublicKey.KeyId == pgpOnePassSignature.KeyId || - // EncryptionKeys.ValidationPublicKey.GetKeySignatures().Cast() - // .Select(x => x.KeyId).Contains(pgpOnePassSignature.KeyId); - var verified = Utilities.FindPublicKey(keyIdToVerify, EncryptionKeys.VerificationKeys, - out PgpPublicKey _); - if (verified == false) - throw new PgpException("Failed to verify file."); - - message = objectFactory.NextPgpObject(); - var literalData = (PgpLiteralData)message; Stream unc = literalData.GetInputStream(); Streams.PipeAll(unc, outputStream); - } - else - { - throw new PgpException("File was not signed."); - } - } - else if (message is PgpLiteralData literalData) - { - Stream unc = literalData.GetInputStream(); - Streams.PipeAll(unc, outputStream); - if (pbe.IsIntegrityProtected()) - { - if (!pbe.Verify()) + if (pbe.IsIntegrityProtected()) { - throw new PgpException("Message failed integrity check."); + if (!pbe.Verify()) + { + throw new PgpException("Message failed integrity check."); + } } } + else + throw new PgpException("File was not signed."); } - else - throw new PgpException("File was not signed."); } #endregion DecryptAndVerify