In this blog post I am going to discuss about Digital signature and PGP encryption for txt/xml data. This is generally required for payment processing files being sent out of D365 FO.
As a pre-requisite, you must have a PGP encrypted public key in format as
-----BEGIN PGP PUBLIC KEY BLOCK-----
key content
-----END PGP PUBLIC KEY BLOCK-----
, a private key (if you don't have, then generate it using kleopatra tool) in format as
-----BEGIN PGP ARMORED FILE-----
key content
-----END PGP ARMORED FILE-----
and a passphrase for private key.
These keys and passphrase must be securely stored. I have stored it over Azure key vault and is using D365 Key vault parameters to retrieve that using azure client app. (for Dev box, Same is possible via txt files stored locally too, Or as a resource file for Dev and other environments, Or as a txt file over azure storage account).
Although its the safest way to use azure key vault, but there is a minor problem as to how the whole key is stored. Generally key must include special characters '\r\n' (Not directly visible unless read via code or using tools such as notepad++), when pasting it over key vault it ignores the new lines and considers whole data as a single line. To overcome this, do use the line ending characters before pasting it to key vault as
-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: PGP Command Line v10.1.0 (Build 52) (Linux)\r\n\r\nkey content\r\n-----END PGP PUBLIC KEY BLOCK----
Once you are finalized with all these, lets proceed for code.
We will start with the PGP helper class/
using Org.BouncyCastle.Bcpg.OpenPgp; using Org.BouncyCastle.Security; using Org.BouncyCastle.Bcpg; class PGPHelper { ////// SignFile /// /// System.IO.Stream /// System.IO.Stream /// str ///System.IO.Stream public System.IO.Stream SignFile(System.IO.Stream _inputFile, System.IO.Stream _privateKeyStream, Str _passphrase) { PgpSecretKey secretKey = this.ReadSecretKey(_privateKeyStream); System.Byte[] passPhraseByte = System.Text.Encoding::UTF8.GetBytes(_passphrase); System.Char[] passPhraseChar = System.Text.Encoding::UTF8.GetChars(passPhraseByte); PgpPrivateKey privateKey = secretKey.ExtractPrivateKey(passPhraseChar); System.IO.Stream outputStream = new System.IO.MemoryStream(); PgpSignatureGenerator pgpSignatureGenerator = new PgpSignatureGenerator(privateKey.PublicKeyPacket.Algorithm, Org.BouncyCastle.Bcpg.HashAlgorithmTag::Sha256); pgpSignatureGenerator.InitSign(0, privateKey); PgpLiteralDataGenerator literalData = new PgpLiteralDataGenerator(); System.Char format = Global::toanytype('b'); var literalOut = literalData.Open(outputStream, format, "filename", _inputFile.Length, DateTimeUtil::utcNow()); var signatureOut = pgpSignatureGenerator.GenerateOnePassVersion(false); signatureOut.Encode(literalOut); _inputFile.CopyTo(literalOut); var signature = pgpSignatureGenerator.Generate(); signature.Encode(literalOut); literalOut.Close(); outputStream.Position = 0; return outputStream; } ////// SignAndEncryptData /// /// System.IO.Stream /// System.IO.Stream /// str /// System.byte ///system.byte public System.Byte[] SignAndEncryptData(System.IO.Stream _publicKeyFileStream, System.IO.Stream _secretKeyFileStream, Str _privateKeyPassword, System.Byte[] _messageDataArray) { try { PgpPublicKey pubKey = this.ReadPublicKey(_publicKeyFileStream); PgpSecretKey secretKey = this.ReadSecretKey(_secretKeyFileStream); System.Byte[] passPhraseByte = System.Text.Encoding::UTF8.GetBytes(_privateKeyPassword); System.Char[] passPhraseChar = System.Text.Encoding::UTF8.GetChars(passPhraseByte); using (var outputStream = new System.IO.MemoryStream()) { this.SignAndEncrypt(outputStream, _messageDataArray, secretKey, passPhraseChar, pubKey, true, true); return outputStream.ToArray(); } } catch { throw error('Error during sign and encrypt process'); } } private void SignAndEncrypt(System.IO.Stream _outputStream, System.Byte[] _clearData, PgpSecretKey _secretKey, System.Char[] _secretPwd, PgpPublicKey _publicKey, Boolean _armored, Boolean _withIntegrityCheck) { if (_armored) { _outputStream = new ArmoredOutputStream(_outputStream); } try { PgpEncryptedDataGenerator encryptedDataGenerator = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag::Cast5, _withIntegrityCheck, new SecureRandom()); encryptedDataGenerator.AddMethod(_publicKey); System.Byte[] buffer = new System.Byte[1 << 16](); using (System.IO.Stream encryptedOut = encryptedDataGenerator.Open(_outputStream, buffer)) { var compressedDataGenerator = new PgpCompressedDataGenerator(CompressionAlgorithmTag::Uncompressed); using (System.IO.Stream compressedOut = compressedDataGenerator.Open(encryptedOut)) { PgpPrivateKey privateKey = _secretKey.ExtractPrivateKey(_secretPwd); var signatureGenerator = new PgpSignatureGenerator(_secretKey.PublicKey.Algorithm, HashAlgorithmTag::Sha256); signatureGenerator.InitSign(0, privateKey); System.Collections.IEnumerable enumUserLiterals = _secretKey.PublicKey.GetUserIds(); //str userId;enumUserLiterals.getnext(); System.Collections.IEnumerator inumerator = enumUserLiterals.GetEnumerator(); while (inumerator.moveNext()) { PgpSignatureSubpacketGenerator subpacketGenerator = new PgpSignatureSubpacketGenerator(); //subpacketGenerator.AddSignerUserId(false, userId); //subpacketGenerator.SetIssuerFingerprint(false, _secretKey.PublicKey); subpacketGenerator.SetSignerUserId(false, inumerator.Current); subpacketGenerator.SetIssuerKeyID(false, _secretKey.PublicKey.KeyId); signatureGenerator.SetHashedSubpackets(subpacketGenerator.Generate()); break; } signatureGenerator.GenerateOnePassVersion(false).Encode(compressedOut); var literalDataGenerator = new PgpLiteralDataGenerator(); System.Char binaryformat = Global::toanytype('b'); using (System.IO.Stream literalOut = literalDataGenerator.Open(compressedOut, binaryformat , "embeddedFileName", _clearData.Length, DateTimeUtil::utcNow())) { using (System.IO.Stream inputData = new System.IO.MemoryStream(_clearData)) { int ch = inputData.ReadByte(); while (ch >= 0) { //literalOut.WriteByte((byte)ch); //signatureGenerator.Update((byte)ch); literalOut.WriteByte(ch); signatureGenerator.Update(ch); ch = inputData.ReadByte(); } } } signatureGenerator.Generate().Encode(compressedOut); } } if (_armored) { _outputStream.Close(); } } catch { throw error("Error occurred during sign and encrypt process."); } } private PgpPublicKey ReadPublicKey(System.IO.Stream _publicKeyFileStream) { System.IO.Stream decodedStream = PgpUtilities::GetDecoderStream(_publicKeyFileStream); PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(decodedStream); PgpPublicKeyRing kRing; System.Collections.IEnumerator kRingenumerator = pgpPub.GetKeyRings().GetEnumerator(); while (kRingenumerator.MoveNext()) { kRing = kRingenumerator.Current; PgpPublicKey k; System.Collections.IEnumerator kenumerator = kRing.GetPublicKeys().GetEnumerator(); while (kenumerator.MoveNext()) { k = kenumerator.Current; if (k.IsEncryptionKey) { return k; } } } throw error('No encryption key found in public key ring.'); } private PgpSecretKey ReadSecretKey(System.IO.Stream _secretFileKey) { System.IO.Stream decodedStream = PgpUtilities::GetDecoderStream(_secretFileKey); PgpSecretKeyRingBundle pgpSec = new PgpSecretKeyRingBundle(decodedStream); PgpSecretKey secretKey; PgpSecretKeyRing kRing; System.Collections.IEnumerator kRingenumerator = pgpSec.GetKeyRings().GetEnumerator(); while (kRingenumerator.MoveNext()) { kRing = kRingenumerator.Current; PgpSecretKey k; System.Collections.IEnumerator kenumerator = kRing.GetSecretKeys().GetEnumerator(); while (kenumerator.MoveNext()) { k = kenumerator.Current; if (k.IsSigningKey) { secretKey = k; break; } } if (secretKey != null) { break; } } if (secretKey == null) { throw error('No signing key found in secret key ring.'); } return secretKey; } ////// EncryptFile /// /// System.IO.Stream /// System.IO.Stream ///System.IO.Stream public System.IO.Stream EncryptFile(System.IO.Stream _inputFile, System.IO.Stream _publicKeyStream) { PgpPublicKey publicKey = this.ReadPublicKey(_publicKeyStream); System.IO.MemoryStream outputStream = new System.IO.MemoryStream(); // Armored output ArmoredOutputStream armoredStream = new ArmoredOutputStream(outputStream); // Encryption generator PgpEncryptedDataGenerator encryptedData = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag::Cast5, true, new SecureRandom()); encryptedData.AddMethod(publicKey); using (System.IO.Stream encryptedOut = encryptedData.Open(armoredStream, new System.Byte[1 << 16]())) { // Optional compression PgpCompressedDataGenerator compressedData = new PgpCompressedDataGenerator(CompressionAlgorithmTag::Uncompressed); using (System.IO.Stream compressedOut = compressedData.Open(encryptedOut)) { // Literal data PgpLiteralDataGenerator literalData = new PgpLiteralDataGenerator(); System.Char binaryformat = Global::toanytype('b'); using (System.IO.Stream literalOut = literalData.Open(compressedOut, binaryformat, "data", _inputFile.Length, DateTimeUtil::utcNow())) { _inputFile.CopyTo(literalOut); } } } armoredStream.Close(); outputStream.Position = 0; return outputStream; } }
internal final class RunnableClass1 { public static void main(Args _args) { KeyVaultParameters keyVaultParameters; KeyVaultCertificateTable certTable; //FinTechDotNetLib.PgpHelper pgpHelper = new FinTechDotNetLib.PgpHelper(); PGPHelper pgpHelper = new PGPHelper(); System.IO.Stream encryptedStream; System.IO.Stream signedStream; select keyVaultParameters where keyVaultParameters.Name = 'keyVaulttest'; System.IO.Stream streamName = new System.IO.MemoryStream(System.Text.Encoding::UTF8.GetBytes(System.IO.File::ReadAllText(@"D:\Test.xml"))); str publicKey = KeyVaultCertificateHelper::getManualSecretValue(KeyVaultCertificateTable::findByName('publickey').RecId); str privateKey = KeyVaultCertificateHelper::getManualSecretValue(KeyVaultCertificateTable::findByName('privatekey').RecId); str formattedPublicKey; formattedPublicKey = strReplace(publicKey, "\\r\\n", '\r\n'); str formattedPrivateKey; formattedPrivateKey = strReplace(privateKey, "\\r\\n", '\r\n'); System.IO.Stream publicKeyStream = new System.IO.MemoryStream(System.Text.Encoding::UTF8.GetBytes(formattedPublicKey)); System.IO.Stream privateKeyStream = new System.IO.MemoryStream(System.Text.Encoding::UTF8.GetBytes(formattedPrivateKey)); if (keyVaultParameters.DigitalSignature && keyVaultParameters.Encryption) //These are custom No/Yes field { System.Byte[] signedAndEncryptedByte = pgpHelper.SignAndEncryptData(publicKeyStream, privateKeyStream, KeyVaultCertificateHelper::getManualSecretValue(KeyVaultCertificateTable::findByName('passphrase').RecId), dataByte); signedAndEncryptedStream = new System.IO.MemoryStream(signedAndEncryptedByte); } else if (keyVaultParameters.DigitalSignature && !keyVaultParameters.Encryption) //This is custom No/Yes field { signedStream = pgpHelper.SignFile(streamName, formattedPrivateKey, KeyVaultCertificateHelper::getManualSecretValue(KeyVaultCertificateTable::findByName('passphrase').RecId)); } else if (keyVaultParameters.Encryption && !keyVaultParameters.DigitalSignature) //These are custom No/Yes field { encryptedStream = pgpHelper.EncryptFile(streamName, formattedPublicKey); } if(keyVaultParameters.DigitalSignature && keyVaultParameters.Encryption) { System.IO.StreamReader streamReader = new System.IO.StreamReader(signedAndEncryptedStream); info (streamReader.ReadToEnd()); } else if (keyVaultParameters.DigitalSignature && !keyVaultParameters.Encryption) { System.IO.StreamReader streamReader = new System.IO.StreamReader(signedStream); info (streamReader.ReadToEnd()); } else { System.IO.StreamReader streamReader = new System.IO.StreamReader(encryptedStream); info (streamReader.ReadToEnd()); } } }
using System; using System.Linq; using System.Text; using System.IO; using Azure.Identity; using Azure.Security.KeyVault.Secrets; using Org.BouncyCastle.Bcpg.OpenPgp; using Org.BouncyCastle.Security; namespace FinTechDotNetLib { public class PgpHelper { public Stream EncryptFile(Stream inputFile, string publicKeyString) { PgpPublicKey publicKey = ReadPublicKey(publicKeyString); MemoryStream outputStream = new MemoryStream(); PgpEncryptedDataGenerator encryptedDataGenerator = new PgpEncryptedDataGenerator(Org.BouncyCastle.Bcpg.SymmetricKeyAlgorithmTag.Cast5, true, new SecureRandom()); encryptedDataGenerator.AddMethod(publicKey); using (Org.BouncyCastle.Bcpg.ArmoredOutputStream armoredOut = new Org.BouncyCastle.Bcpg.ArmoredOutputStream(outputStream)) { // Open the encrypted stream using (Stream encryptedOut = encryptedDataGenerator.Open(armoredOut, new byte[1 << 16])) { //PgpCompressedDataGenerator compressedDataGenerator = new PgpCompressedDataGenerator(Org.BouncyCastle.Bcpg.CompressionAlgorithmTag.Uncompressed); //Stream compressedOut = compressedDataGenerator.Open(encryptedOut); // Write the input file data to the encrypted stream byte[] buffer = new byte[1 << 16]; int len; while ((len = inputFile.Read(buffer, 0, buffer.Length)) > 0) { encryptedOut.Write(buffer, 0, len); } } } outputStream.Position = 0; return outputStream; } public Stream SignFile(Stream inputFile, string privateKeyString, string passphrase) { PgpPrivateKey privateKey = ReadPrivateKey(privateKeyString, passphrase); MemoryStream outputStream = new MemoryStream(); PgpSignatureGenerator signatureGenerator = new PgpSignatureGenerator(Org.BouncyCastle.Bcpg.PublicKeyAlgorithmTag.RsaGeneral, Org.BouncyCastle.Bcpg.HashAlgorithmTag.Sha256); signatureGenerator.InitSign(PgpSignature.BinaryDocument, privateKey); PgpLiteralDataGenerator literalDataGenerator = new PgpLiteralDataGenerator(); Stream literalOut = literalDataGenerator.Open(outputStream, PgpLiteralData.Binary, "filename", inputFile.Length, DateTime.UtcNow); byte[] buffer = new byte[1 << 16]; int len; while ((len = inputFile.Read(buffer, 0, buffer.Length)) > 0) { literalOut.Write(buffer, 0, len); signatureGenerator.Update(buffer, 0, len); } //signatureGenerator.Generate().Encode(literalOut); PgpSignature signature = signatureGenerator.Generate(); // Write the signature to the output stream using (Stream signatureStream = new MemoryStream()) { signature.Encode(signatureStream); signatureStream.Position = 0; signatureStream.CopyTo(outputStream); } literalOut.Close(); outputStream.Position = 0; return outputStream; } private PgpPublicKey ReadPublicKey(string publicKeyString) { using (Stream keyIn = new MemoryStream(Encoding.UTF8.GetBytes(publicKeyString))) using (Stream inputStream = PgpUtilities.GetDecoderStream(keyIn)) { PgpPublicKeyRingBundle publicKeyRingBundle = new PgpPublicKeyRingBundle(inputStream); foreach (PgpPublicKeyRing keyRing in publicKeyRingBundle.GetKeyRings()) { foreach (PgpPublicKey key in keyRing.GetPublicKeys()) { if (key.IsEncryptionKey) { return key; } } } } throw new ArgumentException("Can't find encryption key in key ring."); } private PgpPrivateKey ReadPrivateKey(string privateKeyString, string passphrase) { using (Stream keyIn = new MemoryStream(Encoding.UTF8.GetBytes(privateKeyString))) using (Stream inputStream = PgpUtilities.GetDecoderStream(keyIn)) { PgpSecretKeyRingBundle secretKeyRingBundle = new PgpSecretKeyRingBundle(inputStream); foreach (PgpSecretKeyRing keyRing in secretKeyRingBundle.GetKeyRings()) { foreach (PgpSecretKey key in keyRing.GetSecretKeys()) { if (key.IsSigningKey) { PgpPrivateKey privateKey = key.ExtractPrivateKey(passphrase.ToCharArray()); if (privateKey != null) { return privateKey; } } } } } throw new ArgumentException("Can't find signing key in key ring."); } } }
No comments:
Post a Comment