Click here to Skip to main content
13,705,065 members
Click here to Skip to main content
Add your own
alternative version

Stats

9.5K views
14 bookmarked
Posted 19 Aug 2018
Licenced CPOL

The *AdES Trilogy, Part 1: CAdES implementation for Windows in C++

, 13 Sep 2018
Rate this:
Please Sign up or sign in to vote.
A standard-compliant library for secure signing

Introduction

This is a three part article about Advanced Electronic Signatures. This part talks about CAdES, the second part talks about XAdES and the final part considers ASiC.

CAdES (CMS Advanced Electronic Signatures) is a set of extensions to Cryptographic Message Syntax (CMS) signed data making it suitable for advanced electronic signatures.

In order for a digital signature to be valid in the EU and elsewhere, it has to be in one of the CAdES profiles. These profiles define the way that certificates, CLRs, timestamps, etc. are added to the standard CMS.

The extensions to CMS are added either as authenticated attributes (those that are co-signed with the rest of the message) or unauthenticated attributes that are added after the signature.

Similar extensions exist for specialized forms: XAdES for XML signing (extensions to XML DSIG), PAdES for PDF signing (extensions to PDF) and ASiC, an extension to BDOC which defines how a digital container is structured to contain all data related to the digital signature.

Each of these forms has different levels of information included:

  • The basic form (B). This extends the basic CMS format with:
    • Four mandatory signed attributes:
      • An attribute containing the mine type of the content, always PKCS#7 data
      • An attribute containing the hash of the message signed
      • An attribute containing the certificate used for signing
      • An attribute containing the time the message was signed
    • Some optional signed attributes:
      • An attribute containing a signing policy
      • An attribute containing a commitment reason
  • The timestamp form (T) which also contains an unauthenticated attribute containing a counter signature from a trusted time signing provider.
  • The C form which contains references to chain certificates and CRLs.
  • The X form which appends a time stamp to the C form, either Type 1 or Type 2.
  • The XL form which contains complete certificate chains and CRL
  • The XL Type 1 or XL Type 2 which also contain timestamps.
  • The A and LT forms for periodical timestamping.

Our library will, at this time, create CAdES-B , CAdES-T, CAdES-CCAdES-X Type 2 and CAdES-XL Type 2 forms, and it is able to verify up to CAdES-T level. In the future, more forms may be added.

Coding Considerations

Let's first see why the new protocol adds these attributes. When there are no signed attributes, then the hash of the content is encrypted with the private key of the certificate and this is the digital signature. The CMS can just be that information, without even the certificate information that was used for signature (although Windows API puts the certificate nevertheless). This means that a simple CMS might only contain an encrypted hash.

When signed attributes exist, then it's these's hash that is actually encrypted. That is why that two signed attributes are then mandatory - the type of the content and the hash of the content. CAdES also needs us to contain the certificate used for signing, so the signature can be instantly verified and this also copes with the case that the public key was used to generate more than one certificate (say, with a different policy). 

CAdES also forces the CMS to contain a timestamp (not from an external server like the -T forms) but from the signers‘ computer. This provides an indication of the time when the signature was put no matter if it is considered trusted or not.

Finally, CAdES allows to specify signing policies. A policy is just a parameter string. Policies allow external verifiers to find out the reason for signing and other parameters, depending on how the signing provider defines them.

To build a standard CMS, the low level message functions are used:

  • CryptMsgOpenToEncode
  • CryptMsgUpdate
  • CryptMsgOpenToDecode
  • CryptMsgGetParam
  • CryptMsgControl
  • CryptEncodeObjectEx

To add the attributes, our job is easy or hard, depending on what Windows will do for us automatically. The type of the content and the hash of the signed message are added automatically without the need to do anything.

Adding a timestamp is also easy, because CryptEncodeObjectEx can automatically encode it for us:

// Add the timestamp
FILETIME ft = { 0 };
SYSTEMTIME sT = { 0 };
GetSystemTime(&sT);
SystemTimeToFileTime(&sT, &ft);
char buff[1000] = { 0 };
DWORD buffsize = 1000;
CryptEncodeObjectEx(PKCS_7_ASN_ENCODING, szOID_RSA_signingTime, (void*)&ft, 0, 0, buff, &buffsize);

char* bb = AddMem<char>(mem, buffsize);
memcpy(bb, buff, buffsize);
CRYPT_ATTR_BLOB* b0 = AddMem<CRYPT_ATTR_BLOB>(mem);
b0->cbData = buffsize;
b0->pbData = (BYTE*)bb;
ca[0].pszObjId = szOID_RSA_signingTime;
ca[0].cValue = 1;
ca[0].rgValue = b0;

Our helper, AddMem<>, allocates memory within a vector<vector<char>> for any sort of data that we want to be visible in the entire function. CryptEncodeObjectEx supports szOID_RSA_signingTime so it can automatically encode in ASN.1 format the timestamp for us.

Our problems start when we need to encode a SigningCertificateV2, which CryptEncodeObjectEx does not support:

SigningCertificateV2 ::=  SEQUENCE 
    {
    certs        SEQUENCE OF ESSCertIDv2,
    policies     SEQUENCE OF PolicyInformation OPTIONAL
    }

To cope with this, we have to use an ASN.1 compiler such as ASN1C (I've put it also in the repository). But the above ASN.1 definition is not enough, for we also have to define ESSCertIDv2, PolicyInformation and lots of other structures. Fortunately for you, I've put everything into the cades.asn1 file.

The ASN.1 compiler will, based on the ASN.1 definitions, generate a set of .C and .H files for us to use them in encoding, so we can then build a DER message and put it in our CMS:

// Hash of the cert
vector<BYTE> dhash;
HASH hash(BCRYPT_SHA256_ALGORITHM);
hash.hash(c->pbCertEncoded, c->cbCertEncoded);
hash.get(dhash);
BYTE* hashbytes = AddMem<BYTE>(mem, dhash.size());
memcpy(hashbytes, dhash.data(), dhash.size());

SigningCertificateV2* v = AddMem<SigningCertificateV2>(mem,sizeof(SigningCertificateV2));
v->certs.list.size = 1;
v->certs.list.count = 1;
v->certs.list.array = AddMem<ESSCertIDv2*>(mem);
v->certs.list.array[0] = AddMem<ESSCertIDv2>(mem);
v->certs.list.array[0]->certHash.buf = hashbytes;
v->certs.list.array[0]->certHash.size = (DWORD)dhash.size();
// SHA-256 is the default

// Encode it as DER
vector<char> buff3;
auto ec2 = der_encode(&asn_DEF_SigningCertificateV2,
    v, [](const void *buffer, size_t size, void *app_key) ->int
{
    vector<char>* x = (vector<char>*)app_key;
    auto es = x->size();
    x->resize(x->size() + size);
    memcpy(x->data() + es, buffer, size);
    return 0;
}, (void*)&buff3);
char* ooodb = AddMem<char>(mem, buff3.size());
memcpy(ooodb, buff3.data(), buff3.size());
::CRYPT_ATTR_BLOB bd1 = { 0 };
bd1.cbData = (DWORD)buff3.size();
bd1.pbData = (BYTE*)ooodb;
ca[1].pszObjId = "1.2.840.113549.1.9.16.2.47";
ca[1].cValue = 1;
ca[1].rgValue = &bd1;

The same nasty thing occurs when we want to add a specific signature Policy (OID 1.2.840.113549.1.9.16.2.15). Our helpers also include an OID class, created by using parts of code from this project. The same thing occurs when we add another optional attribute, the commitment type.

After calling CryptMsgUpdate to generate the signed message, we can now add any unauthenticated attributes. CryptEncodeObjectEx supports the PKCS_ATTRIBUTE format, which can contain a timestamp. To get the timestamp, Windows provides us with the function CryptRetrieveTimeStamp.

To add extra certificates to the message, we could use the ASN.1 compiler, but including all the X.509 type declarations is a pain. Instead, we only encode a simple ASN.1 sequence manually, then we get the encoded certificate or CRL directly from the PCCERT_CONTEXT or PCCRL_CONTEXT structure.

Using the Library

Our Sign() function looks like this:

struct CERTANDCRL
    {
        PCCERT_CONTEXT cert;
        std::vector<PCCRL_CONTEXT> Crls;
    };
struct CERT
    {
        CERTANDCRL cert;
        std::vector<CERTANDCRL> More;
    };

HRESULT Sign(LEVEL lev,const char* data,DWORD sz,const std::vector<CERT>& Certificates, SIGNPARAMETERS& Params,std::vector<char>& Signature);
    

here:

  • lev is LEVEL::CMS,B,T,C,X,XL
  • data and sz is the data and the size
  • Certificates contain all the certificates that are used to sign the message. A message can be signed by more than one certificate. Each entry also contains an optional list of CRLs, and extra certificates and their CRLs to be added. If you specify a level less than CAdES-C, no CRLs or extra certificates are added.
  • Params is an optional structure that defines:
    • Hashing algorithm (default SHA-256)
    • Whether the message is attached or detached
    • Optional signing Policy
    • Timestamp parameters (URL, Policy, Nonce, Extensions)
    • The OID of an optional commitment type (1.2.840.113549.1.9.16.6.1 to 6)
  • Signature receives the signature

Our Verify function looks like this:

HRESULT AdES::Verify(const char* data, DWORD sz, LEVEL& lev,const char* omsg, 
          DWORD len,std::vector<char>* msg,std::vector<PCCERT_CONTEXT>* Certs,
          std::vector<string>* Policies)

Where:

  • data and sz is the signature to verify
  • lev receives the detected level (currently up to T level)
  • omsg and len contain the original message in case the signature is detached
  • msg (optional) receives the original message if the signature is attached
  • Certs (optional) receive an array with the certificates used to sign the message
  • Policies (optional) receive an array of detected signing policies, if found, per signature

Our project contains the library and a test project. It also includes a binary copy of the ASN.1 Compiler and the required include files. The library also provides a XAdES-T implementation and an ASiC-S implementation.

Acks

History

  • 15th September, 2018: Added CAdES-XL Type 2
  • 14th September, 2018: Added CAdES-C and CAdES-X Type 2
  • 1st September, 2018: Added commitment types
  • 31st August, 2018: Added tech info about CMS
  • 28th August, 2018: Typos fixed
  • 19th August, 2018: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Chourdakis
Engineer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS and Android.

I 've a PhD in Digital Signal Processing and Artificial Intelligence and I specialize in Pro Audio and AI applications.

My home page: http://www.michaelchourdakis.com

You may also be interested in...

Comments and Discussions

 
QuestionMessage Closed Pin
13-Sep-18 18:51
memberAmit Maurya13-Sep-18 18:51 
QuestionGreat article Pin
John Klinner19-Aug-18 7:10
memberJohn Klinner19-Aug-18 7:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04-2016 | 2.8.180920.1 | Last Updated 13 Sep 2018
Article Copyright 2018 by Michael Chourdakis
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid