For document reading applications, where storage of large, digitally signed
documents is not desirable, preference is for detaching digital signatures and
storing them separately from a document's sizeable contents. In this article, we
look at how this can be done through X.509 certificates using the .NET 2.0
framework. We'll concentrate on features that developers should use to include
file signing functionalities within their applications using the new classes in
.NET 2.0. You can download the complete source code that's been explained in
this article from forums.pcquest.com under the Developer thread.
Support for PKCS#7 in .NET
X.509 certificates represent a bond between a public key and the name of its
owner, as certified by trusted third party Certifying Authorities such as iCERT
CA. An X.509 certificate follows specifications provided in RFC 2459 and ensures
non-repudiation by the message sender. Messages signed and encrypted using X.509
certificates are legally acceptable under India's IT Act 2000.
Cryptographic support for X.509 certificates has been available under Windows
operating systems primarily through the CryptoAPIs since Windows NT days.
However, Microsoft introduced CAPICOM in 2001 as a wrapper for useful Crypto API
functions to reduce the complexity in implementing solutions requiring digital
signs and encryption associated with X.509 certificates. Support for public key
cryptography in .NET 1.1 required extraction of keys from X.509 certificates in
order to complete the process of generating digital signature. Cryptographic
capabilities of .NET 1.1 were available in the following namespaces:
|
- System.Security.Cryptography
- System.Security.Cryptography.Xml
- System.Security.Cryptography.X509-Certificates
However, for rapid application development, developers preferred to use
functionalities of signing, enveloped messages, encryption, hashing and
certificate store access through CAPICOM. Functionalities which were not
available through CAPICOM were supplemented using P/Invoke with CryptoAPI
libraries.
However, with the introduction of in .NET 2.0, necessary classes have
been provided to create objects. This allows for the use of certificates and
helps create PKCS#7 enveloped or signed messages directly. Developers need not
use CAPICOM to extend support for digital signatures within their .NET
applications.
Signing a file
Certificates in Windows are maintained in Crypto API-managed certificate
stores (MY, AddressBook, Root, etc.) that are organized according to the
intended use. In .NET 2.0 we can manage the default key store of Windows
certificate stores, which is used to store X.509 certificates and certificate
chains of trusted signers. The .NET 2.0 classes nicely wrap the key management
functionalities of the Crypto API and also provide extra functionalities of
their own.
The System.Security.Cryptography. Pkcs namespace provides the SignedCms and
CmsSigner classes which expose underlying Windows Crypto API functionalities and
help us extend the digital signing capability to our application. Those familiar
with CAPICOM may note that the two classes encapsulate similar objects that
CAPICOM provides through its SignedData and Signer objects. Let's see how it's
done.
STEP 1:
Open the 'My' store.
X509Store store = new X509Store();
Set it to read only property.
store.Open(OpenFlags.ReadOnly);
Construct a Signer Object
First set the content for the signer object. We read the file into a byte
array called buffer.
ContentInfo contentInfo = new
ContentInfo(buffer);
Use the constructor to initialize a CmsSigner object which stores PKCS#7
signatures along with the signing X.509 certificate in addition to other
properties. The SignedCms constructor creates an instance of the SignedCms class
by using the specified content information. The SignedCms constructor also takes
a bool value that specifies whether the object is for a detached signature. If
we keep the value as true, the signature is detached, otherwise it is attached.
Remember that this figures as the SignedData object in CAPICOM.
SignedCms signedCms = new
SignedCms(contentInfo,true);
Now we create a CmsSigner object that takes the specified certificate in its
constructor.
CmsSigner cmsSigner = new CmsSigner(
signerCert );
STEP 2:
We use the SignedCms.ComputeSignature method to create a signature using the
specified CmsSigner. This overloaded method also takes a bool value and if the
CmsSigner.Certificate property of the CmsSigner object is not set to a valid
certificate, it presents a dialog box where the user can select the appropriate
signer's certificate.
Thus, the certificate selection functionalities are also provided by this
method and make the task of selection of a valid certificate from the
certificate store very easy. Now, specify whether the signer's certificate chain
should be included in whole or in part within the CmsSigner.IncludeOption
property. We can set the option that controls whether the root and entire chain
associated with the signing certificate are included with the created CMS/PKCS
#7 message.
cmsSigner.IncludeOption =
X509IncludeOption.WholeChain;
Now we create a detached digital signature using the cmsSigner and add the
signature to the CMS/PKCS #7 message. We set the value of the silent parameter
to False and the CmsSigner.Certificate property of the CmsSigner object to a
valid certificate to get the prompts to select a signing certificate.
signedCms.ComputeSignature(cmsSigner, false);
Encode the CMS/PKCS #7 message as a byte array.
byte<> encodedSignedCms = signedCms.Encode();
Now save the byte array into a file with .p7s extension which can be read by
the windows machine.
File.WriteAllBytes(OutputFileName,
encodedSignedCms);
When you double click on the .p7s file, Windows automatically shows the
certificates contained within the signature. You can view the signing
certificate information when you double click on the certificate icon.
Verifying signatures
To verify the message, first associate the content of the message with the
SignedCms message by constructing a ContentInfo object with the file byte
content. Use that to construct a SignedCms object by using, for example, the
SignedCms constructor.
Set the second parameter to true to indicate that the message is detached.
Decode the encoded SignedCms message to be verified, using the Decode method.
Finally, check the signature as previously described.
Now convert the stored signature file into a byte array as follows:
byte<> bufferfile = File.ReadAllBytes(FileBase);
byte<> buffersignature =
File.ReadAllBytes(FileToVerify);
Place signature buffer in a ContentInfo object.
ContentInfo contentInfo = new
ContentInfo(bufferfile);
Now Instantiate a SignedCms object with the ContentInfo above. Set the
detached content file upon which the signature is based.
SignedCms signedCms = new
SignedCms(contentInfo, true);
Decode buffersignature bytes into the pkcs7 object.
signedCms.Decode(buffersignature);
Now check for the detached signature; the CheckSignature function should
return a 'true' value.
signedCms.CheckSignature(true);
Display the first signing certificate.
signedCms.Certificates<0>.Display();
The verification method can be viewed in the source code.
Enveloping and decrypting a file using digital signature certificates
Enveloping a file involves the use of a message encryption key with a
symmetric encryption algorithm such as triple DES. Then the public key extracted
from the X.509 certificate of the receiver is used to encrypt the encryption key
of the encrypted file. The resulting encrypted file can only be decrypted after
the receiver, who alone has access to the X.509 certificate's private keys,
decrypts the symmetrical key. So, the sender just needs to have the certificate
of the receiver installed in his key store. Typically, a certificate belonging
to other individuals, installed in a Windows machine, is found in the
'AddressBook' store. We can search for certificates belonging to the recipient
in our 'AddressBook' store:
X509Certificate2Collection certColl =
storeAddressBook. Certificates.Find(X509FindType.FindBySubjectName,
recipientName, false);
In case certificates of the receiver are found in the machine store by the
above function then we choose the first certificate from the returned array,
'certColl<0>.' We can now instantiate an EnvelopedCms object with the required
content.
EnvelopedCms envelopedCms =
new EnvelopedCms(contentInfo);
We then set the CmsRecipient object through the following commands:
CmsRecipient recipient1 = new
CmsRecipient(SubjectIdentifierType.IssuerAndSerialNumber, recipientCert);
The EnvelopedCms.Encrypt(CmsRecipientCollection) method encrypts the contents
of the CMS/PKCS #7 message using the information for the specified list of
recipients. The method then automatically extracts the public key from the
certificate and uses that key to encrypt the symmetric encryption keys.
envelopedCms.Encrypt(recipient1);
The method returns a byte array which can be serialized and stored as an
encrypted file on the disk. The file can then be sent to the receiver who would
decrypt the message using his private key, which corresponds to the public key
used to encrypt the file. The enveloped object can then be encoded as a byte
array for serialization and sent to the sender. At the receiver's end the
received enveloped object would be decoded. The EnvelopedCms.Decode (System.Byte<>)
method decodes the specified enveloped CMS/PKCS #7 message and resets all member
variables in the EnvelopedCms object.
Then the EnvelopedCms.Decrypt() method decrypts the contents of decoded
enveloped messages. The EnvelopedCms.Decrypt() method searches the current user
and computer 'MY stores' for the appropriate certificate and private key. The
method searches for private keys in the 'MY' certificate store for the
certificate and uses the associated private key to decrypt the message. In case
no private keys are found the message is not decrypted and an exception is
thrown.
envelopedCms.Decode(encodedEnvelopedCms);
envelopedCms.Decrypt(envelopedCms.RecipientInfos<0>);
The decrypted byte array can then be saved as a file.
Conclusion
.NET 2.0 provides comprehensive support for digital signature certificate
based signing and encryption than .NET 1.1. The new classes provided by the pkcs
namespace provides comprehensive out of the box functionalities that enables
developers to build very secure applications utilizing the PKI technologies more
quickly. With the features exposed in the article the developers should be able
to integrate rapidly the file signing capabilities with the new classes in .NET
2.0.
Suvir Misra, Indian Revenue Service (Customs and Central Excise)