/* $Id: GermanHealthExamples2018.cs $ * Last Updated: * $Date: 2018-05-05 18:05:00 $ */ /* * Examples using CryptoSys PKI to create and read signed-and-enveloped PKCS7 (CMS) objects suitable for * the Security interface for data exchange in the health service [2018]. * Ref: "Security Schnittstelle (SECON) Anlage 16" * Security interface (SECON) Annex 16, Valid from: 01.01.2018 Status: 26.10.2017. */ /******************************************************************************** * Copyright (c) 2014-25 DI Management Services Pty Limited. All rights reserved. * Provided to illustrate the use of functions in the CryptoSys PKI Toolkit. * Not necessarily a good example of secure programming techniques. * Provided "as is" with no warranties. Use at your own risk. * The code in this module is licensed under the terms of the MIT license. * For a copy, see <http://opensource.org/licenses/MIT> ******************************************************************************** */ /* Changes from 2014 version: * -------------------------- * Content encryption algorithm changed from Triple-DES to AES-256. * Signature algorithm changed to RSA-PSS-SHA256 with mgf1SHA256. * Key encryption algorithm changed to RSA-OAEP with SHA-256 and mgf1SHA256. * Test RSA keys are 4096 bits. * Test certificates are signed using RSA-PSS-SHA256. * For reference, "RSA-PSS" == "RSASSA-PSS" and "RSA-OAEP" == "RSAES-OAEP". */ /* Required files in current working directory * ------------------------------------------- * 999009051b.cer * 999009051b_pri.p8e * 999009991b.cer * 999009991b_pri.p8e * CA_4096_Cert.cer * Int_4096_Cert.cer */ using System; using System.Text; using System.IO; using CryptoSysPKI; namespace GermanHealthExamples { class GermanHealthExamples { static void Main(string[] args) { int ver = General.Version(); Console.WriteLine("PKI Version={0}", ver); if (ver < 110300) { Console.WriteLine("Require CryptoSys PKI version 11.3.0 or greater"); return; } // Expecting at least one argument if (args.Length < 1) usage(); Console.WriteLine("Doing " + args[0] + "..."); // Act on first command-line argument switch (args[0]) { case "maketest": maketest(); break; case "readtest": readtest(); break; default: usage(); break; } } static void usage() { Console.WriteLine("ERROR: Expecting argument: maketest|readtest"); System.Environment.Exit(1); } static void display_error(int code) { Console.WriteLine("ERROR: " + General.ErrorLookup(code) + ": " + General.LastError()); } /// <summary> /// Make a test signed-and-enveloped-data object /// </summary> /// <returns></returns> static bool maketest() { // The message we want to send string msg = "Hallo Walt"; // Final enveloped-data file to send to recipient string outputFile = "To_999009051b.p7m"; // Our private key data string priKeyFile = "999009991b_pri.p8e"; StringBuilder sbPassword = new StringBuilder("password"); // Signer's certificate plus (optionally) the certs in the chain that signed it. // Separated by a semi-colon ";". // NOTE: the first cert in the list MUST be the signers // -- it's up to you to work out which is which (see Check_CertList_With_PrivateKey()) string signersCertList = "999009991b.cer" + ";" + "Int_4096_Cert.cer" + ";" + "CA_4096_Cert.cer"; // The certificate of the recipient -- this must be provided // (otherwise we wouldn't know whom to send it to) string recipCertFile = "999009051b.cer"; // Display input Console.WriteLine("Message to be signed and enveloped='" + msg + "'"); bool isok = Make_Signed_And_EnvelopedData(outputFile, msg, priKeyFile, sbPassword.ToString(), signersCertList, recipCertFile, true); if (isok) Console.WriteLine("OK, created output file '" + outputFile + "'"); else Console.WriteLine("FAILED!"); Wipe.String(sbPassword); return true; } /// <summary> /// Read a test signed-and-enveloped-data file /// </summary> /// <remarks> /// NOTE: we can only do this because we have the private key for the dummy user with id IK999009051, /// which, of course, you would not normally have. /// To test yourself, send a test message to yourself signed by yourself. /// </remarks> /// <returns></returns> static bool readtest() { string inputFile = "To_999009051b.p7m"; // Recipient's private key data string priKeyFile = "999009051b_pri.p8e"; StringBuilder sbPassword = new StringBuilder("password"); // Display input Console.WriteLine("Reading input file '" + inputFile + "'"); string msg = Read_Signed_and_Enveloped_Data(inputFile, priKeyFile, sbPassword.ToString(), true); Console.WriteLine("Result='" + msg + "'"); // Clean up password Wipe.String(sbPassword); return true; } /*********************/ /* GENERIC FUNCTIONS */ /*********************/ /// <summary> /// Creates a PKCS#7 signed-and-enveloped-data object /// </summary> /// <param name="outputFile">File to be created</param> /// <param name="msg">Message to be sent</param> /// <param name="priKeyFile">Sender's encrypted key file</param> /// <param name="password">Password for key file</param> /// <param name="signersCertList">List of signers' certificates, sender's first</param> /// <param name="recipCertFile">Filename of recipient's certificate</param> /// <param name="keepInterFile">True to keep intermediate file</param> /// <returns>True if successful; otherwise false</returns> static bool Make_Signed_And_EnvelopedData(string outputFile, string msg, string priKeyFile, string password, string signersCertList, string recipCertFile, bool keepInterFile) { int n; // Intermediate signed-data file we will create string sigFile = outputFile + ".int.tmp"; // 1. Read in the secret private key to a StringBuilder Console.WriteLine("Reading private key from PRI file..."); StringBuilder sbPriKey = Rsa.ReadPrivateKey(priKeyFile, password); // Check for success if (sbPriKey.Length == 0) { Console.WriteLine("ERROR: Cannot read private key"); return false; } else { Console.WriteLine("...OK, read in private key: key length=" + Rsa.KeyBits(sbPriKey) + " bits"); } // 2. Create a signed-data object with signed attributes and signing-time and all certificates. // using RSA-PSS-SHA256 for the signature n = Cms.MakeSigDataFromString(sigFile, msg, signersCertList, sbPriKey.ToString(), Cms.SigAlg.Rsa_Pss_Sha256, Cms.SigDataOptions.IncludeAttributes | Cms.SigDataOptions.AddSignTime); Console.WriteLine("CMS_MakeSigDataFromString returns " + n + " (expecting 0)"); // Clean up as we go Wipe.String(sbPriKey); // Check for success if (n != 0) { display_error(n); return false; } else { Console.WriteLine("OK, created signed-data file '" + sigFile + "'"); } // 3. Encrypt the signed-data object directly using the recipient's certificate // using RSA-OAEP with SHA-256 for the key-encryption algorithm // -- this produces a binary enveloped-data file n = Cms.MakeEnvData(outputFile, sigFile, recipCertFile, CipherAlgorithm.Aes256, Cms.KeyEncrAlgorithm.Rsa_Oaep, HashAlgorithm.Sha256, 0); // Clean up sensitive data if (!keepInterFile) { Wipe.File(sigFile); } // Check for success (NB expecting # of recipients, not zreo) if (n <= 0) { display_error(n); return false; } else { Console.WriteLine("OK, created enveloped-data file '" + outputFile + "'"); } // Now send the output file to the recipient... return true; } /// <summary> /// Read a signed-and-enveloped-data object using recipient's private key /// </summary> /// <param name="inputFile">signed-and-enveloped-data object file</param> /// <param name="priKeyFile">recipient's encrypted private key file</param> /// <param name="password">password for private key</param> /// <returns>String containing extracted message or empty string on error</returns> static string Read_Signed_and_Enveloped_Data(string inputFile, string priKeyFile, string password, bool keepInterFile) { string msg = ""; int n; string s, query; // 1. Read in the recipient's secret private key to a StringBuilder Console.WriteLine("Reading private key from PRI file..."); StringBuilder sbPriKey = Rsa.ReadPrivateKey(priKeyFile, password); // Check for success if (sbPriKey.Length == 0) { Console.WriteLine("ERROR: Cannot read private key"); return ""; } else { Console.WriteLine("...OK, read in private key: key length=" + Rsa.KeyBits(sbPriKey) + " bits"); } // 1a. While we're here, check the form of the enveloped-data object we received Console.WriteLine("For enveloped-data file: '" + inputFile + "'"); query = "keyEncryptionAlgorithm"; s = Cms.QueryEnvData(inputFile, query); Console.WriteLine(" " + query + "=" + s); query = "oaepParams"; s = Cms.QueryEnvData(inputFile, query); Console.WriteLine(" " + query + "=" + s); // Intermediate file we will create string sigFile = inputFile + ".i2.tmp"; // 2. Read the encrypted data from the enveloped-data file n = Cms.ReadEnvDataToFile(sigFile, inputFile, "", sbPriKey.ToString(), 0); Console.WriteLine("Cms.ReadEnvDataToFile returns " + n + " (expected 0)"); // Check for success if (n != 0) { display_error(n); return ""; } else { Console.WriteLine("Extracted signed-data file '" + sigFile + "'"); } // 2a. While we're here, get some info from the signed-data file Console.WriteLine("For signed-data file: '" + sigFile + "'"); s = Cms.QuerySigData(sigFile, "version"); Console.WriteLine(" Version=" + s); query = "signingTime"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); query = "messageDigest"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); query = "signatureAlgorithm"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); query = "digestAlgorithm"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); query = "pssParams"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); query = "countOfSignerInfos"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); query = "CountOfCertificates"; s = Cms.QuerySigData(sigFile, query); Console.WriteLine(" " + query + "=" + s); // 2b. And verify the signed data n = Cms.VerifySigData(sigFile); Console.WriteLine("Cms.VerifySigData returns " + n + " (expected 0)"); // 3. Extract the content msg = Cms.ReadSigDataToString(sigFile); // Clean up sensitive data if (!keepInterFile) { Wipe.File(sigFile); } // Check for success if (msg.Length == 0) { display_error(General.ErrorCode()); return ""; } return msg; } } }