/// <summary> /// Create PGP-decrypting stream around a PGP-encrypted input source stream /// </summary> /// <param name="privatekeysource">Input stream containing private keyring bundle</param> /// <param name="privatekeypassphrase">Passphrase to access private key (if required)</param> /// <param name="encryptedinput">Input stream containing source data to be decrypted</param> /// <returns> /// A StreamStack object, from which decrypted data can be read /// </returns> /// <remarks> /// - Caller is responsible for disposing of returned StreamStack (BEFORE disposing of original encryptedinput Stream) /// - Caller is also still responsible for disposing of encryptedinput stream (AFTER disposing of returned StreamStack) /// - Source data stream will be read synchronously (due to limitation in PGP library; all source data must be available to perform decryption, /// but source library does not provide async option). If this is a concern, caller should consider asynchronously pulling source data into local /// stream first and passing local stream to this function (if increased use of memory outweighs waiting for blocked thread to perform I/O) /// </remarks> public static async Task <StreamStack> GetDecryptionStream(Stream privatekeysource, string privatekeypassphrase, Stream encryptedinput) { var decryptionstream = new StreamStack(); try { // Open decoder stream and push on to return value stack: decryptionstream.PushStream(PgpUtilities.GetDecoderStream(encryptedinput)); // Create object factory (using stream at top of stack) to extract encrypted data: var factory = new PgpObjectFactory(decryptionstream.GetStream()); var encrypteddata = factory.GetEncryptedData() ?? throw new ArgumentException("No PGP-encrypted data found"); // Extract private key from key source (using key ID required by encrypted data object), use to open // clear stream and push on to return value stack: decryptionstream.PushStream(encrypteddata.GetDataStream(await ExtractPrivateKey(privatekeysource, privatekeypassphrase, encrypteddata.KeyId))); // Create new factory from clear stream and extract first PGP object: factory = new PgpObjectFactory(decryptionstream.GetStream()); var message = factory.NextPgpObject(); while (message != null) { // If object is literal data, push de-literalization stream on to return value stack and return: if (message is PgpLiteralData cleardata) { decryptionstream.PushStream(cleardata.GetInputStream()); return(decryptionstream); } // Otherwise if this is compressed data, we need to push decompression stream on to return value // stack, then create a new object factory to extract data from decompressed source: else if (message is PgpCompressedData compresseddata) { decryptionstream.PushStream(compresseddata.GetDataStream()); factory = new PgpObjectFactory(decryptionstream.GetStream()); } // Extract next message object from factory and continue: message = factory.NextPgpObject(); } // If this point is reached, no literal packet was ever found throw new ArgumentException("Invalid PGP data (no payload found)"); } catch { decryptionstream.Dispose(); throw; } }