The Azure Vault, PGP, and other matters Part 3
Well if you are still hanging on… thanks and good job. We only have a bit more to go…. (you can go back to Part 1 or Part 2)
Time to regroup as to where we are in the Big Picture:
This is the flow of the Logic App. In Part 2 we just finished up the GetKeys block… so onto the Decryptor….
Some more code of course as this is another Azure Function….
public static class MHKPGPDecryptor { [FunctionName("MHKPGPDecryptor")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation($"C# HTTP trigger function {nameof(MHKPGPDecryptor)} processed a request."); string vaultKey = req.Query["privatekeysecretid"]; string useVault = req.Query["useVault"]; string privateKey = "";
A familiar start to our Azure Function. Here we are placing two Query parameters:
privatekeysecretid and useVault
The privatekeysecretid contains the full PGP key we got from the vault. The second parameter is just a flag to tell the function if the privatekeysecretid should used or not. I’ve made the private key value required, and the use vault is optional.
When useVault is something like a “Yes”, the function will assume to use the privatekeysecretid as the actual PGP key. This is what all of this code is about:
if (!String.IsNullOrEmpty(vaultKey)) { log.LogInformation($"Vault key found privatekeysecretid"); } else { log.LogInformation($"privatekeysecretid = is missing from the call. This must be provided!"); return new BadRequestObjectResult("Missing the paramenter privatekeysecretid. This must must have the private key."); } if (!string.IsNullOrEmpty(useVault)) { if (useVault.ToUpper().Contains("YES")) { privateKey = vaultKey; log.LogInformation($"Using Key from the Vault."); } else { privateKey = @"-----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.9 (MingW32) //something secret is here.... -----END PGP PRIVATE KEY BLOCK-----"; log.LogInformation($"Using Key from the internal store."); } }
(Yes that’s me hard-coding a key into the function… well it’s test code get over it 😉).
Now we have the key, what next? Let’s take a couple of steps back.
The encrypted content in our adventure is sitting in the request body when this function is call (yeap I did not make that obvious because I’m about to go on about it).
When the Decryptor function is called, this content is passed in. Dipping back to the Logic App:
Notice the Request Body gets the File Content from the sFTP content. Also notice what privatekeysecretid gets. It is the Body content from the GetKeys function that was just called. **Now that’s an important matter. This is Content Scope… one should pause and think about what is being said here. It should be obvious to most who have done any Dot Net Core, MVC, and even for that matter any Web API’s (ok I’ll stop beating on my drum about it, but put the wrong content in the wrong place and 😬).
Our next matter is the PGP parts… We have a Key… We have encrypted contain… Time to make it happen!
First thing, you will need to add a NuGet Package to the Azure Function code. At the time or this post I’m using PgpCore v1.4.1:
The Using are:
The rest of the main method is:
log.LogInformation("Now Decrypting the body"); Stream decryptedData = await DecryptAsync(req.Body, privateKey, log); return new OkObjectResult(decryptedData); }
Here we call DecryptAsync. Followed up with the decrypted content going back to the Logic App with a 200.
We have but one more method to do:
private static async Task<Stream> DecryptAsync(Stream inputStream, string privateKey, ILogger log) { log.LogInformation("So it begins..."); using (PGP pgp = new PGP()) { Stream outputStream = new MemoryStream(); Stream temp = new MemoryStream(); temp = GenerateStreamFromString("Place Holder for errors."); try { using (inputStream) using (Stream privateKeyStream = GenerateStreamFromString(privateKey)) { log.LogInformation("Now it is done..."); await pgp.DecryptStreamAsync(inputStream, outputStream, privateKeyStream, "MY SUPER SECRET PASSPHRASE"); outputStream.Seek(0, SeekOrigin.Begin); //log.Flush(); return outputStream; } } catch (Exception ex) { log.LogInformation("Error Throw"); log.LogInformation($"This was {ex.Message}"); //log.Flush(); temp = GenerateStreamFromString("Stupid PGP errored: " + ex.Message.ToString()); temp.Seek(0, SeekOrigin.Begin); return temp; } } } private static Stream GenerateStreamFromString(string s) { MemoryStream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream); writer.Write(s); writer.Flush(); stream.Position = 0; return stream; }
So PGP decrypt takes some content (as a stream), a key, and a passphrase for that key. Mostly super simple. This is all that the decyptor does.
Now, one last step in our Logic App:
We must output the content somewhere. Here I have a file share on a Azure storage account. This time the Body Content (which becomes our File Content) is the response from the PGPDecryptor.
Wrapping it up….
Can this be improved upon… But of course. For one matter the Pass Phrase should be passed into the Decryptor and should come from it’s own Vault possibly. This can be different (or at least it should be different) for each PGP key. There could be more error handling, and what about Encrypting? PGPCore the handy NuGet package also has methods for Encrypting and Encrypting/Signing.
But I will leave those Matters for you to explore….
If you need to go back Parts 1 & 2 are here Part 1 Part 2
I hope this has been a help….
~SG