/* $Id: TestSc14nPKI.c $
* Last updated:
* $Date: 2019-12-13 21:23 $
* $Version: 2.1.0 $
*/
/* Some tests using the Sc14n C/C++ interface with CryptoSys PKI.
* Please report any bugs to <http://cryptosys.net/contact/>
*/
/******************************* LICENSE ***********************************
* Copyright (C) 2017-19 David Ireland, DI Management Services Pty Limited.
* All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
* The code in this module is licensed under the terms of the MIT license,
* unless otherwise marked.
* For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/
#if _MSC_VER >= 1100
/* Detect memory leaks in MSVC++ */
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#else
#include <stdlib.h>
#endif
#include <stdio.h>
#include <string.h>
#include "diSc14n.h"
#include "diCrPKI.h"
/*
* Requires `Sc14n` and `CryptoSys PKI` to be installed on your system,
* available from <http://cryptosys.net/sc14n/> and <http://cryptosys.net/pki/>.
* In particular that `diSc14n.dll` and `diCrPKI.dll` are in your library path.
* You must link to `diSc14n.lib` and `diCrPKI.lib`. In MSVC++ IDE, use
* Project > Configuration Properties > Linker > Input > Additional Dependencies
* and add the .lib file paths, e.g.
* Additional Dependencies = $(OutDir)diSc14n.lib;$(OutDir)diCrPKI.lib;%(AdditionalDependencies)
* Using command-line:
* CL TestSc14nPKI.c /link ..\Release\diSc14n.lib diCrPKI.lib
*
* Test files, e.g. `olamundo.xml`, are in `sc14n-testfiles.zip`. These must be in the CWD.
*/
#ifdef NDEBUG
/* Make sure assertion testing is turned on */
#undef NDEBUG
#endif
#include <assert.h>
// DEBUGGING UTILS
// Comment/uncomment next line to turn on/off debugging output
#define NO_DPRINTF
#if (defined(_DEBUG) && !(defined(NO_DPRINTF)))
#define DPRINTF0(s) printf(s)
#define DPRINTF1(s, a1) printf(s, a1)
#define DPRINTF2(s, a1, a2) printf(s, a1, a2)
#else
#define DPRINTF0(s)
#define DPRINTF1(s, a1)
#define DPRINTF2(s, a1, a2)
#endif
// HARD-CODED PRIVATE KEY AND CERTIFICATE (FOR OUR CONVENIENCE IN TESTING)
// Alice's PKCS8 encrypted key and X.509 certificate
// from RFC 4134 "Examples of S/MIME Messages"
// Private key password is "password"
static const char *myPassword = "password";
static const char *myPriKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----"
"MIICojAcBgoqhkiG9w0BDAEDMA4ECFleZ90vhGrRAgIEAASCAoA9rti16XVH"
"K4AJVe1CNf61NIpIogu/Xs4Yn4hXflvewiOwe6/9FkxBXLbhKdbQWn1Z4p3C"
"njVns2VYEO/qpJR3LciHMwp5dsqedUVVia//CqFHtEV9WfvCKWgmlkkT1YEm"
"1aChZnPP5i6IhwVT9qvFluTZhvVmjW0YyF86OrOp0uxxVic7phPbnPrOMelf"
"ZPc3A3EGpzDPkxN+o0obw87tUgCL+s0KtUOr3c6Si4KQ3IQjrjZxQF4Se3t/"
"4PEpqUl5EpYiCx9q5uqb0Lr1kWiiQ5/inZm5ETc+qO+ENcp0KjnX523CATYd"
"U5iOjl/X9XZeJrMpOCXogEuhmLPRauYP1HEWnAY/hLW93v10QJXY6ALlbkL0"
"sd5WU8Ces7T04b/p4/12yxqYqV68QePyfHpegdraDq3vRfopSwrUxtL9cisP"
"jsQcJ5FL/SfloFbmld4CKIjMsromsEWqo6rfo3JqNizgTVIIWExy3jDT9VvK"
"d9ADH0g3JCbuFzaWVOZMmZ0wlo28PKkLQ8FkW8CG/Lq/Q/bHLPM+sPdLN+ke"
"gpA6fvL4wpku4ST7hmeN1vWbRLlCfuFijux77hdM7knO9/MawICsA4XdzR78"
"p0C2hJlc6p46IWZaINQXGstTbJMh+mJ7i1lrbG2kvZ2Twf9R+RaLp2mPHjb1"
"+P+3f2L3tOoC31oJ18u/L1MXEWxLEZHB0+ANg+N/0/icwImcI0D+wVN2puU4"
"m58j81sGZUEAB3aFEbPxoX3y+qYlOnt1OfdY7WnNdyr9ZzI09fkrTvujF4LU"
"nycqE+MXerf0PxkNu1qv9bQvCoH8x3J2EVdMxPBtH1Fb7SbE66cNyh//qzZo"
"B9Je"
"-----END ENCRYPTED PRIVATE KEY-----";
static const char *myCert = "-----BEGIN CERTIFICATE-----"
"MIICLDCCAZWgAwIBAgIQRjRrx4AAVrwR024uxBCzsDANBgkqhkiG9w0BAQUFADAS"
"MRAwDgYDVQQDEwdDYXJsUlNBMB4XDTk5MDkxOTAxMDg0N1oXDTM5MTIzMTIzNTk1"
"OVowEzERMA8GA1UEAxMIQWxpY2VSU0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ"
"AoGBAOCJczmN2PX16Id2OX9OsAW7U4PeD7er3H3HdSkNBS5tEt+mhibU0m+qWCn8"
"l+z6glEPMIC+sVCeRkTxLLvYMs/GaG8H2bBgrL7uNAlqE/X3BQWT3166NVbZYf8Z"
"f8mB5vhs6odAcO+sbSx0ny36VTq5mXcCpkhSjE7zVzhXdFdfAgMBAAGjgYEwfzAM"
"BgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIGwDAfBgNVHSMEGDAWgBTp4JAnrHgg"
"eprTTPJCN04irp44uzAdBgNVHQ4EFgQUd9K00bdMioqjzkWdzuw8oDrj/1AwHwYD"
"VR0RBBgwFoEUQWxpY2VSU0FAZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADgYEA"
"PnBHqEjME1iPylFxa042GF0EfoCxjU3MyqOPzH1WyLzPbrMcWakgqgWBqE4lradw"
"FHUv9ceb0Q7pY9Jkt8ZmbnMhVN/0uiVdfUnTlGsiNnRzuErsL2Tt0z3Sp0LF6DeK"
"tNufZ+S9n/n+dO/q+e5jatg/SyUJtdgadq7rm9tJsCI="
"-----END CERTIFICATE-----";
/* PKI HELPER FUNCTIONS */
/** Read private key into internal key string, valid only for this session.
@returns Pointer to string buffer containing key string or NULL on error
@remark Caller to free allocated memory
*/
char *pki_rsaReadPrivateKey(const char *prikey, const char *password, int *pstatus)
{
long nchars;
char *buf;
nchars = RSA_ReadAnyPrivateKey(NULL, 0, prikey, password, 0);
if (nchars < 0) {
*pstatus = nchars;
return NULL;
}
buf = malloc(nchars + 1);
nchars = RSA_ReadAnyPrivateKey(buf, nchars, prikey, password, 0);
*pstatus = 0;
return buf;
}
/** Compute digest value in base64 form.
@param digestbuf Buffer to receive digest value
@param bufsize Size of buffer in bytes
@param s String to be digested
@param digalg Digest algorithm flag (0 = SHA-1)
@return Number of characters in digestbuf or negative error code
*/
int pki_hashStringToBase64(char *digestbuf, size_t bufsize, const char *s, long digalg)
{
unsigned char b[PKI_MAX_HASH_BYTES];
long nb, nc;
// Compute digest value in raw bytes
nb = HASH_Bytes(b, sizeof(b), s, strlen(s), digalg);
if (nb < 0) return nb;
// Convert to base64 encoded value
nc = CNV_B64StrFromBytes(digestbuf, bufsize - 1, b, nb);
return nc;
}
/** Compute base64-encoded signature value from base64-encoded digest value. */
char *pki_SigValFromDigVal(const char *digval, const char *prikey, const char *password, int *pstatus)
{
unsigned char b[PKI_MAX_HASH_BYTES];
long nb, nchars;
char *buf;
// Convert base64-encoded value to raw bytes
nb = CNV_BytesFromB64Str(b, sizeof(b), digval);
// Sign the digest value
nchars = SIG_SignData(NULL, 0, b, nb, prikey, password, "sha1WithRSAEncryption", PKI_SIG_USEDIGEST);
if (nchars < 0) {
*pstatus = nchars;
return NULL;
}
buf = malloc(nchars + 1);
nchars = SIG_SignData(buf, nchars, b, nb, prikey, password, "sha1WithRSAEncryption", PKI_SIG_USEDIGEST);
*pstatus = 0;
return buf;
}
/** Extract XML-style <RSAKeyValue> from RSA private key. */
char *pki_KeyValFromPriKey(const char *prikey, const char *password, int *pstatus)
{
char *buf;
char *keystr;
long nchars;
keystr = pki_rsaReadPrivateKey(prikey, password, pstatus);
if (!keystr) return NULL;
// Form XML RSAKeyValue:
// CAUTION: do not include your private key data, just the public key
nchars = RSA_ToXMLString(NULL, 0, keystr, PKI_XML_EXCLPRIVATE);
if (nchars < 0) {
free(keystr);
*pstatus = nchars;
return NULL;
}
buf = malloc(nchars + 1);
nchars = RSA_ToXMLString(buf, nchars, keystr, PKI_XML_EXCLPRIVATE);
free(keystr);
*pstatus = 0;
return buf;
}
/* FILE UTILITIES */
/** Read a binary file into a null-terminated string.
* @return Pointer to allocated string buffer or NULL on error
* @remark Caller must free allocated memory
*/
static unsigned char *file_to_string(const char *fname)
{
FILE *fp;
char *buf;
long flen;
size_t nread;
fp = fopen(fname, "rb");
if (!fp) return NULL;
fseek(fp, 0, SEEK_END);
flen = ftell(fp);
if (flen < 0) return NULL;
buf = malloc(flen + 1);
rewind(fp);
nread = fread(buf, 1, flen, fp);
fclose(fp);
buf[nread] = '\0';
return buf;
}
/** Write a new binary file from a string. */
static int file_from_string(const char *fname, const char *s)
{
FILE *fpo;
size_t nwritten;
int r;
fpo = fopen(fname, "wb");
if (!fpo) return -1;
nwritten = fwrite(s, 1, strlen(s), fpo);
r = fclose(fpo);
return r; /* 0 = success */
}
/// <summary>
/// Create a XML-DSIG signed file given proforma XML document
/// </summary>
/// <param name="outFile">Name of outfile to create</param>
/// <param name="baseFile">Name of input XML document</param>
/// <param name="priKey">PKCS8 encrypted private key file or PEM-string</param>
/// <param name="password">Password for private key</param>
/// <returns>Zero (0) on success otherwise nonzero error code</returns>
/// <remarks>Input XML document is expected to be enveloped-signature with single reference URI="",
/// C14N method REC-xml-c14n-20010315, signature method xmldsig#rsa-sha1, and digest method xmldsig#sha1.
/// KeyValue is expected to be in RSAKeyValue form.
/// Items to be replaced should be marked "%digval%", "%sigval%" and "%keyval%".
/// </remarks>
int MakeSignedXml(const char *outfile, const char *basefile, const char *prikey, const char *password)
{
/* declaration for 3rd-party function with code below */
char *replace_str(const char *str, const char *old, const char *news);
int status;
long r, nchars;
char *buf = NULL;
char *newsi = NULL;
char digval[SC14N_MAX_DIGEST_CHARS + 1];
char digval_si[SC14N_MAX_DIGEST_CHARS + 1];
char *sigval = NULL;
char *keyval = NULL;
char *xmlstr = NULL;
char *newxml1 = NULL;
char *newxml2 = NULL;
char *newxml3 = NULL;
// 1. Compute digest value of body excluding <Signature> element
// (this assumes Reference URI="" and DigestMethod is SHA-1)
r = C14N_File2Digest(digval, sizeof(digval) - 1, basefile, "Signature", "", SC14N_TRAN_OMITBYTAG);
if (r < 0) {
status = r;
goto clean_up;
}
DPRINTF1("DIGVAL=%s\n", digval);
// 2. Extract the SignedInfo element into memory
// Note %digval% parameter to be completed
nchars = C14N_File2String(NULL, 0, basefile, "SignedInfo", "", SC14N_TRAN_SUBSETBYTAG);
if (nchars < 0) {
status = nchars;
goto clean_up;
}
buf = malloc(nchars + 1);
nchars = C14N_File2String(buf, nchars, basefile, "SignedInfo", "", SC14N_TRAN_SUBSETBYTAG);
DPRINTF1("SIGNEDINFO (BASE):\n%s\n", buf);
// 3. Insert the required DigestValue we prepared earlier
newsi = replace_str(buf, "%digval%", digval);
DPRINTF0("SIGNEDINFO (COMPLETED):\n");
DPRINTF1("%s\n", newsi);
// 4. Compute the digest value of this string
pki_hashStringToBase64(digval_si, sizeof(digval_si), newsi, PKI_HASH_SHA1);
DPRINTF1("SHA1(signedinfo)=%s\n", digval_si);
// 5. Compute signature from this digest value
sigval = pki_SigValFromDigVal(digval_si, prikey, password, &status);
if (!sigval) {
fprintf(stderr, "ERROR: failed to create signature value");
goto clean_up;
}
DPRINTF1("SIGVAL:\n%s\n", sigval);
// 6. Get the RSA Key Value in required XML form
// NB We can extract the public key value from the RSA private key
keyval = pki_KeyValFromPriKey(prikey, password, &status);
if (!keyval) {
fprintf(stderr, "ERROR: failed to create Key value");
goto clean_up;
}
DPRINTF1("KEYVAL:\n%s\n", keyval);
// 7. Compose the output file by substituting the correct values
// (Note we make no other checks of the input XML - that's up to you)
// 7.1 Read in the base XML file
xmlstr = file_to_string(basefile);
if (!xmlstr) {
fprintf(stderr, "ERROR: cannot read file '%s'", basefile);
status = -1;
goto clean_up;
}
// 7.2 Substitute %% values
newxml1 = replace_str(xmlstr, "%digval%", digval);
newxml2 = replace_str(newxml1, "%sigval%", sigval);
newxml3 = replace_str(newxml2, "%keyval%", keyval);
// 7.3 Write out new string to file
r = file_from_string(outfile, newxml3);
DPRINTF1("file_from_string() returns %d (expected 0)\n", r);
if (r != 0) {
fprintf(stderr, "ERROR: failed to create file '%s'", outfile);
status = r;
goto clean_up;
}
// If we got here all is well
status = 0;
clean_up:
if (buf) free(buf);
if (newsi) free(newsi);
if (sigval) free(sigval);
if (keyval) free(keyval);
if (xmlstr) free(xmlstr);
if (newxml1) free(newxml1);
if (newxml2) free(newxml2);
if (newxml3) free(newxml3);
return status;
}
/* DO THE BUSINESS... */
int main(void)
{
long n;
char *fname, *oname;
/* MSVC memory leak checking stuff */
#if _MSC_VER >= 1100
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);
#endif
/* General information about the core library DLLs */
// If either of these fail, the package is not installed properly...
printf("Sc14n Version=%ld\n", SC14N_Gen_Version());
printf("CrPKI Version=%ld\n", PKI_Version(0, 0));
fname = "olamundo-base.xml";
oname = "olamundo-new-signed.xml";
printf("FILE: %s\n", fname);
n = MakeSignedXml(oname, fname, myPriKey, myPassword);
printf("MakeSignedXML->'%s' returns %ld (expecting 0)\n", oname, n);
// Input XML contains Chinese characters UTF-8-encoded
fname = "daiwei-base.xml";
oname = "daiwei-new-signed.xml";
printf("FILE: %s\n", fname);
n = MakeSignedXml(oname, fname, myPriKey, myPassword);
printf("MakeSignedXML->'%s' returns %ld (expecting 0)\n", oname, n);
// Input XML contains Chinese characters as character entities
// Note that digest value and signature value should be identical to previous one
fname = "daiwei-ents-base.xml";
oname = "daiwei-ents-new-signed.xml";
printf("FILE: %s\n", fname);
n = MakeSignedXml(oname, fname, myPriKey, myPassword);
printf("MakeSignedXML->'%s' returns %ld (expecting 0)\n", oname, n);
printf("\nALL DONE.\n");
}
/* THIRD-PARTY CODE ********************************************************************************/
/* Ref: http://creativeandcritical.net/str-replace-c/
* Description: Replaces in the string str all the occurrences of the source string old
* with the destination string new. The parameters old and new may be of any length,
* and their lengths are allowed to differ.
* None of the three parameters may be NULL.
*
* Returns: The post-replacement string, or NULL if memory for the new string could not be allocated.
* Does not modify the original string. The memory for the returned post-replacement
* string may be deallocated with the standard library function free() when it is no longer required.
*
* Licence: Public domain. You may use this code in any way you see fit,
* optionally crediting its author (me, Laird Shaw, with assistance from comp.lang.c).
* http://creativeandcritical.net/contact/
*/
char *replace_str(const char *str, const char *old, const char *news)
{
char *ret, *r;
const char *p, *q;
size_t oldlen = strlen(old);
size_t count, retlen, newlen = strlen(news);
if (oldlen != newlen) {
for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen)
count++;
/* this is undefined if p - str > PTRDIFF_MAX */
retlen = p - str + strlen(p) + count * (newlen - oldlen);
}
else
retlen = strlen(str);
if ((ret = malloc(retlen + 1)) == NULL)
return NULL;
for (r = ret, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) {
/* this is undefined if q - p > PTRDIFF_MAX */
ptrdiff_t l = q - p;
memcpy(r, p, l);
r += l;
memcpy(r, news, newlen);
r += newlen;
}
strcpy(r, p);
return ret;
}
/*****************************************************************************************************/