John is a software development manager at UWI.Com -- the Internet Forms Company. He can be contacted at [email protected]
The Microsoft Cryptographic API provides a standard function interface for adding data-security features to 32-bit Windows applications. The most appealing feature of the CryptoAPI is that the security needs of varying organizations can be met without changing the application software. Instead, security strengths are governed by the underlying DLLs that provide the CryptoAPI to applications. These DLLs are known as Cryptographic Service Providers (CSPs), and many have been developed.
The basic CSP is offered free from Microsoft as part of the Internet Explorer 4 installation or NT Service Pack 3 upgrade. The more-sophisticated CSPs enhance security without changing the function interface. This can be done by providing longer keys, different algorithms, and more-stringent identification requirements of users (such as requiring the user's SmartCard). Since the function interface provided by the DLLs doesn't change, existing applications can simply take advantage of stronger security as soon as a DLL is installed on the computer. This is analogous to the expectations we currently have of video, printer, and ODBC drivers.
For all its strengths, the CryptoAPI is complicated and still new. There is a simplified API and example code in the CryptoAPI documentation, but the simplified API alone is insufficient for solving even a standard digital-signature problem to a level consistent with a publicly released software product. For example, even the most recent CryptoAPI documentation available at the time of this writing contains a serious security hole in the example code for doing digital signatures with CryptSignMessage() and CryptVerifyMessageSignature().
I'll talk more about this security hole and other issues as I present the Digital-Signature Library for Microsoft CryptoAPI. This library offers half a dozen functions that hide over 2000 lines of code needed to produce a standard digital-signature solution with the CryptoAPI. This C function library is provided free of charge (and with no warranty) by UWI.Com, the company I work for, which permitted me to use the library in this article. (The library is available electronically; see "Resource Center," page 3.) The UWI.Com InternetForms System currently uses this library to help create legally binding, auditable web-based business transactions through the use of digital signatures that capture the whole context of the user experience, including a form's layout, content, user input, and even supporting documents.
Before you can test the digital-signature library, you'll need to install Service Pack 3 under Windows NT 4 or higher, or Internet Explorer 4 under Windows 95. You will also need to get the wincrypt.h and winerror.h files from the January 1998 release of the Microsoft Platform SDK. There are #if and #endif lines in wincrypt.h that restrict compilation to Windows NT4 or above. If you are using Windows 95, simply comment out these lines.
Next, you must obtain a certificate from a Certificate Authority (CA). Popular CAs include Verisign (http://www.verisign.com/) and Thawte (http://www.thawte.com/), which offer trial certificates that are compatible with the Microsoft Basic CSP. When you request a certificate, ask for one that's compatible with Internet Explorer. If you already have a Netscape certificate, then export it from Netscape and use Internet Explorer 4 to import the digital ID into the CryptoAPI.
When you apply for a certificate, a private/public-key pair is generated on your computer. You also provide certain basic information about yourself, such as the common name that you want to appear with your e-mail address. Your basic information and public key are sent to the CA, which uses them to create a certificate. The CA's private key is used to digitally sign your information, and that signature is added to the certificate. You then receive an e-mail notification that your certificate is ready.
When you download your certificate, it is placed into the CSP, which organizes certificates into certificate stores. Your certificate goes into one of the standard stores called MY. Other standard stores are named CA, ROOT, and SPC. Typically, the CA store holds intermediate certificates from your certificate authority, such as the one used to sign your certificate. The ROOT store holds top-level certificates. These are the most-trusted certificates and are used to sign the intermediate CA certificates: The root certificates for Thawte and Verisign, for example, are bundled with Internet Explorer. The certificates in SPC are also often signed by a ROOT certificate. They are typically used to verify digital signatures on executable code such as ActiveX components.
When a digital signature must be created for a block of data, a hash value is first calculated for the data using an algorithm such as MD5 or SHA-1. These algorithms have the property that any change to the input document results in some change to the hash value. Despite rigorous efforts, no one has yet found a way to create any two documents that have the same hash value, much less two documents that are recognizably similar. The hash value is then encrypted with the signer's private key.
Later, users wishing to verify the signature must first obtain the signer's public key. The public key is used to decrypt the hash value. The verifying user then computes the hash value of the given block of data and tests whether the computed hash value matches the decrypted hash value. If so, then the verifier knows two things: the identity of the signer, and the fact that the document has not been altered since it was signed. If the document had been altered, then the hash values will not match. Furthermore, the signer's private key must have been used to encrypt the hash value -- otherwise, the public key will not successfully decrypted the hash value, so again the decrypted hash value will not match the verifier's computed hash value. So goes the theory, anyway. Reality is in the details.
The Digital-Signature Library for the Microsoft CryptoAPI offers six functions that reduce the task of adding the digital-signature feature to your applications. The library's operation in valid cases and error cases has been tested by the UWI.Com quality-assurance team. The demo program testsign.c (available electronically) is intended solely to demonstrate how to call the key functions. The demo also includes project files for Visual C++ and Borland C++.
The public functions exported by the library begin with DS and are located in ds_lib.c. The comments for these functions provide detailed instructions on how to use each parameter. Table 1 lists the functions in the digital-signature library's public interface. These functions could not be written solely with the simplified CryptoAPI functions.
The first function, DSPrepCryptEnv(), takes no parameters and returns OK or NOTOK, depending on whether the CryptoAPI is available on the computer. Because this function loads the DLLs manually, there is no need to include crypt32.lib and advapi32.lib in your projects. The last function, DSFreeCryptEnv(), does little more than free the crypt32 and advapi32 DLLs.
DSGetCertList() produces a list of all certificates in a given certificate store. It is used mainly for exploration, learning, and determining what certificates are on a machine. The test program calls this function for all of the standard certificate stores defined by the CryptoAPI documentation as well as the system store named ADDRESSBOOK, which is where Microsoft Outlook Express stores the public-key certificates of those with whom the user corresponds.
Before a signature can be created, an application may need to provide the user with a list of identities from which to choose (if the user has more than one signing identity). Some users will have signing identities from multiple CAs, and some will have multiple identities from the same authority that are used for different reasons (for example, Bob, the employee signing his timesheet form versus Bob, the manager signing an employee's evaluation form). When a user only has one identity, the presentation of a list of identities should be skipped. Regardless, DSGetIdentityList() must always be called.
DSGetIdentityList() compiles a list of certificates matching a given substring. Typical choices for the substring are the user's e-mail address and the result of the Windows API function GetUserName(). The list contains strings of the form "Common Name, [email protected]," which I call the certificate's one-line identity. DSGetIdentityList() scans all certificates in MY store, including each certificate in the list if the given substring appears in the certificate's one-line identity. Before being added to the list, the certificate also must not be expired and its one-line identity must not already be in the list. The one-line identity string selected by the user can be passed as the "signer" identity to DSSignMessage().
DSSignMessage() takes a block of data (msg and msgLen) and creates a digital signature (stored at pSignature and pSignatureLen). The signature includes the hash value of the data encrypted with the signer's private key as well as the signer's public-key certificate. A duplicate of the data is not stored in the signature. DSSignMessage() takes three other parameters:
- csp identifies the Cryptographic Service Provider. Like all functions in this library, if you pass NULL, then the Microsoft Basic CSP is used regardless of the default CSP identified by the operating system.
- hashAlg is a string that tells which hashing algorithm to use; the library currently supports "md5" and "sha1."
- signer is a one-line identity string.
Once the message and the signature are transmitted to the target machine, the signature must be verified. Digital-signature verification of a message is performed to determine with high confidence who signed the message and that the message has not been altered since it was signed. This determination can be made by calling DSVerifySignature(), which takes as parameters the msg, msgLen, signature and signatureLen as expected. It also must receive the csp, so at the application level you must arrange to store the CSP identity along with the signature and the message. I recommend the csp identity be added to the message just before its hash value is computed.
DSVerifySignature() churns out a wealth of information. The return value is OK or NOTOK. NOTOK means that the function failed to operate properly and its output cannot be trusted. OK means only that the function succeeded, so the results it reports can be trusted. When DSVerifySignature() returns OK, you receive a string containing the identity of the signer, a second string giving the key length and expiration date of the certificate, and a third string giving a textual description of the certificates in the chain of issuance (starting with the signer certificate's full description). Finally you receive a verification status code. The verify status will be OK if all tests were passed, or it will have one or more bit flags raised to indicate the types of problems found.
You can "bitwise-and" the verify status with constants defined in ds_lib.h to determine which errors occurred (such as DS_MSG_ALTERED and DS_CERTEXPIRED). However, I recommend that applications either do not bother to provide this information, or clearly tell users that results should not be trusted regardless of which specific tests have failed. Test failure is for diagnostic purposes only. For example, I don't agree with the current method used by Microsoft Outlook Express for displaying digital-signature verification errors. To be fair, it does a good job of making it obvious to users that something is wrong. But the way Outlook Express reports the success and failure of various tests is technically incorrect and misleading to most users.
Consider, for example, what happens when a user receives a signed message in which the signing certificate has expired. You can do this easily by sending yourself a signed message, closing the mail program, setting your clock forward beyond the expiration of your certificate, then reopening the mail package to check your mail. A red X appears next to a red message stating that the certificate has expired. However, all other tests are passed, so they appear in a soft gray tone with snappy check marks that imply to the user that these aspects of the message tested okay and can therefore be trusted. One of the first messages says, "The message has not been altered." Technically, this is incorrect. It's the nontechnical way of saying that the computed hash matched the decrypted hash. But a certificate that has expired cannot be trusted since its key may have been around long enough to be broken. Thus, the message could indeed have been altered then signed with the recovered private key. In essence, an expired certificate prevents the accomplishment of the two verification goals just stated (to know with confidence who signed a message, and that it hasn't been altered). That the failure of one test implies the failure of another test is the kind of nuance the typical user will not understand.
The Security Hole
The most surprising discovery I made while building the Digital-Signature Library was that the example code in the CryptoAPI documentation contains a serious security hole. In the initialization code for CryptSignMessage(), the signer's certificate is placed in an array called MsgCertArray, which is then included in a signature parameters structure. This causes CryptSignMessage() to add the signer's certificate to the signature. When the signature must be verified, the example code for the CryptVerifyMessageSignature() callback function simply retrieves the signer's certificate from the message.
Now consider a signed message that has been intercepted by an attacker. The attacker can easily generate his own certificate bearing all of the signer's demographic data but issued by his own certificate server. Therefore, the attacker can delete the original signature, change the message, resign with the bogus certificate, then send the changed message to its destination. When the document signature is verified with the CryptVerifyMessageSignature() example code, the hash values will match and the certificate will outwardly appear to belong to the signer.
The problem is that the example code does not authenticate the certificate in the signature against the issuing certificate on the verifier's computer. Nor does the documentation for CryptVerifyMessageSignature() mention that this should be done. I can't guess the reason for this omission, but it is a serious point for programmers who look to the documentation for how to use the CryptoAPI. Naturally, the Digital-Signature Library performs the extra test using CertVerifySubjectCertificateContext(), which checks whether the subject certificate is expired and whether the subject certificate appears in the issuing certificate's certificate revocation list (CRL). Most importantly, it verifies the issuing CA's digital signature on the subject certificate. This defeats the attack described earlier because the attacker would need to have the private key of the issuing CA in order to produce the correct certificate signature.
Choosing Signer Identity
The verification callback function isn't the only place that the functionality of CertVerifySubjectCertificateContext() is needed. When the DSGetIdentityList() parameter named ValidCertsOnly is set to OK, the certificates listed will exclude those that are expired, have been revoked, or do not validate with the issuer's certificate. Thus, your application users will not be allowed to choose from outdated certificates. Furthermore, DSSignMessage() uses the same function during its search for the certificate matching its signer parameter. Thus, invalid certificates for a given user are not selected for signing, even if they have the same one-line identity as newer, valid certificates.
You will also find a commented out test for the AT_SIGNATURE property. According to the CryptoAPI documentation, the signer certificate should have this property. However, the CryptoAPI example code does not work. I have four signing certificates in MY store (from both Verisign and Thawte), and all of them are marked with AT_KEYEXCHANGE. Even Microsoft Outlook Express does not appear to test for AT_SIGNATURE because it successfully uses these certificates for digitally signing e-mail.
The CryptoAPI documentation suggests that a "search for name as well as key type" will usually be needed. The Digital-Signature Library looks for any given substring of the one-line identity described earlier, and the UWI.Com InternetForms System uses the e-mail address for this substring. Microsoft Outlook Express also uses the e-mail address to select which certificates are included in the list of possible signing identities for the user.
CryptAcquireContext(), the first function you call in the CryptoAPI, yields a handle to a CSP, and takes several parameters that help identify the CSP to which a handle is desired. The digital signature library functions call _DSCryptAcquireContext(), which calls CryptAcquireContext() but enhances the behavior of its input parameters.
The third parameter to CryptAcquireContext() identifies the CSP by name. If NULL is passed, the default CSP defined by the user's system is used. This is not acceptable behavior. The choice of CSP must be synchronized in the sign and verify phases of an application. In practice, these phases will often occur on the different computers and therefore should not be under user control. _DSCryptAcquireContext() defaults to the Microsoft Basic CSP as the default if NULL is given for the csp parameter.
The fourth parameter to CryptAcquireContext() is the provider type. Examples include PROV_RSA_FULL, PROV_RSA_SIG, PROV_DSS, and PROV_FORTEZZA. If the CSP you want to use is of a type other than full RSA, then you can tell the Digital-Signature Library the numeric value of the provider type by prepending it to the string you would normally send as the CSP parameter to the public functions in Table 1. DSCryptAcquireContext() looks for csptype=##\n at the start of its CSP string. If it is there, the specified provider type overrides the default of PROV_ RSA_ FULL. This feature is important because many U.S. government agencies are requiring digital signatures be created using only NIST-approved algorithms. Although Microsoft's DSS provider is not yet NIST approved, there are third-party DSS CSPs that are NIST approved. Also, in countries with strict legal requirements about encryption, an application that can use a signature-only CSP has an advantage.
The last parameter to CryptAcquireContext() is a flags variable. One of the problems with CryptAcquireContext() is that it tends to fail with the error NTE_BAD_KEYSET on the first run of a new CryptoAPI application on a new computer -- even though the user has just obtained that certificate from a CA. The problem appears to stem from the second parameter, which identifies the key container (also known as key set). As in the CryptoAPI example code, we use NULL for this parameter in order to get the default key container associated with the current user. However, when certificates are created, they may be placed in a key container other than the default, so the default container doesn't exist. Calling CryptAcquireContext() with the CRYPT_NEWKEYSET flag fixes the problem by creating the key container.
The Digital-Signature Library uses detached signatures rather than the simplified API's method of including a copy of the message in the signature. Detached signatures are much more useful in a general-purpose library because most digital-signature applications, especially those that capture a business transaction, will eventually place more than one signature on a document. For example, in one of UWI.Com's InternetForms applications for the Pentagon, a form and its enclosed word-processor documents may need to be signed over 50 times before reaching the Office of the Secretary of Defense. It is much easier to recover the original message text if it isn't buried in nested signatures.
Finding the Issuer Certificate
The Digital-Signature Library searches for issuing certificates in the MY certificate store if a search of the CA store fails to produce the issuer. Although searching CA works for Verisign certificates, it currently does not work with certificates from all certificate authorities. The problem stems from a bug in Internet Explorer 3, which apparently fails if the issuing certificates are not all in the MY store. To account for this, Microsoft's certenr3.dll placed the certificate chain in MY store. The newer xenroll DLL properly places the issuing certificates in CA and ROOT. Copies of the CA certificates are also placed in MY store for backwards compatibility. However, until all CAs switch from the older certificate enrollment DLL, libraries such as the one in this article must search the CA store first, but switch to MY if the issuing certificate is not found.
Just because a certificate is on your computer does not imply that you trust it (because the absence of a certificate cannot be used to imply that you do not trust a certificate). To prove this, use IE4 to delete a certificate authority's root certificate, then have someone with a certificate rooted with that CA send you a signed e-mail. Programs such as Microsoft Outlook Express will then indicate that "you have not yet decided whether to trust the digital ID," which is quite a different message from "you don't trust this certificate." Programs must allow end users to accept new CAs as trustworthy after the initial software installation. Therefore, any certificate that becomes untrusted must be in the computer system and explicitly marked as untrusted.
According to the CryptoAPI documentation, the issue of managing certificate trust is application specific. The Digital-Signature Library contains a function _DSTrustSigner() that currently simply returns "okay." UWI.Com uses an Internet Form to store the user's trust settings. There are several reasons for doing this. First, we require users to digitally sign the trust settings so they cannot be altered by others on a multiuser system. Secondly, the trust settings on CryptoAPI certificates can be bundled with trust settings on certificates stored in security systems other than the CryptoAPI. Due to the availability of the UFDL forms library and the GUI capabilities inherent in an Internet Form, managing the trust settings with an InternetForm was the most natural choice for UWI.Com, but you can design your own file format and GUI system for managing trust. The main requirement is that you should apply the user's signature to your trust file with each change, and you should verify the signature before using the trust settings.
I hope that this article and the accompanying library help you to build secure digital-signature applications. If your system design requires the application of digital signatures to preexisting document formats (such as word processing, spreadsheets, and the like), you may want to consider using commercially available solutions that include this digital-signature library, rather than building your own solution from scratch. Questions, comments, and suggestions on the library will be gratefully accepted via e-mail to [email protected] uwi.com.
Copyright © 1998, Dr. Dobb's Journal