/* $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;
}
}
}