Public Key Infrastructure - Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Chapter 10. Public Key Infrastructure

In Recipe 7.1, we described an attack known as a man-in-the-middle attack, in which an attacker could intercept and even manipulate communications secured with public key cryptography. The attack is possible because public key cryptography provides no means of establishing trust when used on its own. Public key infrastructure (PKI) provides the means to establish trust by binding public keys and identities, thus giving reasonable assurance that we are communicating securely with whom we think we are.

In the real world, we often have no way of knowing firsthand who a public key belongs to, and that is a big problem. Unfortunately, there is no sure-fire way to know that we are communicating with whom we think we are. The best we can do is extend our trust to a third party to certify that a public key belongs to the party that is claiming ownership of it. That is where PKI fits in.

PKI is important to using public key cryptography effectively and is essential to understanding and using the SSL protocol. The recipes in this chapter provide an overview of PKI and how to use it effectively with both OpenSSL and CryptoAPI.

10.1. Understanding Public Key Infrastructure (PKI)

Problem

You want a fundamental understanding of PKI.

Solution

Read the following discussion for an overview of basic PKI concepts. For a more detailed treatment, we recommend the book Planning for PKI: Best Practices Guide for Deploying Public Key Infrastructure by Russ Housley and Tim Polk (John Wiley & Sons).

Discussion

One of the big motivators behind public key cryptography is that there is some hope for securely exchanging encryption keys in an insecure medium. However, that is not as easy as it sounds. If used in a naïve manner, the basic public key methods for communication are susceptible to a man-in-the-middle attack, in which the two parties end up talking to an attacker who relays messages, instead of to each other (we discuss this attack in Recipe 7.1).

Man-in-the-middle attacks are possible because public key cryptography in and of itself provides no means of establishing trust. PKI provides the means to establish trust by binding public keys and identities together in a way that gives reasonable assurance that you are communicating securely with the expected entity.

Using public key cryptography,[1] you can be sure that if you encrypt data with a public key, only someone with the corresponding private key can decrypt it. If you simply exchange public keys over an insecure medium, there is no easy way to be sure that the public keys you receive belong to the people you think they do. In other words, traditional public key cryptography does not establish trust between entities. That is where PKI comes in.

One solution to the trust problem is to exchange public keys over a secure medium (or to authenticate them in a secure medium by comparing cryptographic hashes of the key, often called a fingerprint ). The problem with this solution is that it is not very scalable. If parties need to exchange public keys offline to communicate securely, they might as well exchange symmetric keys and save themselves the computational effort.

The basic idea behind public key infrastructure is to introduce a trusted third party to the mix. The idea is that we somehow acquire the public key of the trusted third party over a secure medium. In addition, each entity registers its public key with that trusted party, along with information about that entity. Basically, the trusted party is expected to ensure that the public key really does belong to the registrant and all of the associated data is accurate. If the authority approves, it signs your certificate , which is a piece of data containing your public key along with other identifying information.

Once your certificate has been signed, you can hand that certificate to anyone, and as long as that person has securely obtained the authority's public key, he can take your certificate and validate it by checking the authority's signature. As a result, a client can authenticate a server, even when the server's public key is obtained over an insecure medium (see Figure 10-1).

Client-server key exchange

Figure 10-1. Client-server key exchange

For example, suppose you were to receive through an insecure medium a certificate purporting to belong to Microsoft. If that certificate is signed by VeriSign (the most popular trusted third party), and if you have previously obtained VeriSign's public key in a secure manner, you can determine whether the certificate really does belong to Microsoft. PKI allows you to make many secure connections by exchanging keys over an insecure medium after receiving a single key over a secure medium.

Certificates

Certificates contain a wealth of information that can be used to tie the public key inside the certificate to an entity (see Figure 10-2), either an individual or an organization. Certificates have the name of the entity, called the distinguished name in the PKI world. Server-side certificates also usually contain the fully qualified domain name of the server. They have an expiration date, which means you will have to go back and get a new certificate periodically (actually, another reason is to minimize windows of vulnerability).

Contents of a certificate

Figure 10-2. Contents of a certificate

A digital certificate contains information about the person or organization to whom it was issued (the subject) as well as information about the organization that issued the certificate (the issuer). The issuer signs the certificate with its private key, and the certificate may contain all of the information necessary to validate that signature, including its public key. However, such information should not actually be used to validate the signature on the certificate. After all, anyone could create a key pair to use in signing, place it in the certificate, and claim it is from the issuer.

Certificates also have a serial number that is unique, at least across all certificates from a given issuer. The serial number can be used to identify a certificate quickly.

The basic idea here is that the issuer signs the certificate with its private key, so anyone who has securely obtained the issuer's public key will be able to validate the authenticity of the entire certificate. The entity to whom the certificate was issued cannot change data in it, such as the expiration date. If she tries, the signature will not check out.

Clearly, the issuer is vouching that the information in the certificate is correct when it signs. If you trust the issuer's validation of the core information, you should be able to trust its signature.

Once a certificate has been issued, it is generally put into production. The entity with the certificate gives it to parties that wish to communicate. Other people can validate the certificate by checking the signature, assuming that they have securely obtained the public key of the issuer. They can encrypt data to the public key found in the certificate, and only the entity to which the certificate was issued should have the corresponding private key needed to decrypt the data.

The issuer does not even have a copy of the private key. Generally, the subject generates a key pair (a public key and an associated private key) and bundles the public key along with a bunch of information into a certificate-signing request. The certification authority (often called simply a CA) or its designate authenticates the data, perhaps requiring interaction from the subject. Then, when it is confident enough, the CA will create the final certificate, sign it, and give it back to the subject.

Certification authorities

A CA is an organization or company that issues certificates. A CA takes on the responsibility of ensuring that the certificates it issues are legitimate. Nonetheless, this does not mean that CAs are infallible. For example, there have been publicly documented instances where VeriSign has issued certificates in Microsoft's name to someone not affiliated with Microsoft.

There are two basic types of CA:

Public CAs

An example is VeriSign. Anyone that a public CA is able to validate can get a certificate.

Private CAs

Usually, private CAs are internal to a corporation or other organization, and they issue certificates internally. It is expected that people outside the organization won't be using the CA and therefore won't trust the certificates it issues.

Public CAs commonly issue certificates for public web sites requiring encryption and authentication, often for e-commerce. For such operations, it is important that the customer transmits her information to the site that is supposed to be receiving it, without worrying that someone else is obtaining the information. This is why server certificates generally store the domain name of the server: if you think you're buying a book from Amazon.com, it's important to see a certificate presented that includes Amazon's domain name. If you check only the CA's signature and don't check that the domain name is correct, you have no way to tell that you are using the right public key. Instead, you could have checked the signature on a valid certificate issued to Fred from Fred's Mattress Warehouse.

For a private CA, verifying the identity of a subject is often simple because the identity of employees can generally be established quite easily. The human resources department at a company generally has proof of identity and right to work for each of its employees.

In such a scenario, the human resources department is said to be acting as a registration authority (RA), which is the organization that actually does background validation. Sometimes, this is the same organization as the CA, and sometimes the CA will farm out the work to other people. For example, VeriSign uses a set of companies as RAs.

For a public CA (or its designated RA), verifying the identity of a subject is considerably more difficult than it is for a private CA. The information required from the subject to prove its identity to the CA depends on the type of certificate being issued, and on whether the subject is an individual or a business. For example, if you get an email digital certificate, a CA may only care that you can respond to email at the given address. On the other hand, for a server-side certificate, individuals should need to provide proof of identity, and businesses should need to provide various pieces of corporate paperwork.

Because most CAs are out to make money first and serve the public second, checks on identities are often not as thorough as they could be. In addition, CAs do not assume any liability for when they are wrong; in other words, they provide no concrete guarantees.

Running a private CA is quite appealing for applications that expect to see limited deployment that is explicitly controlled by the software vendor. OpenSSL can be used to run a CA, but doing so is outside the scope of this book (it's really a system administration task, not a programming task). Note, however, that the topic of running a small CA is covered in the book Network Security with OpenSSL (O'Reilly & Associates).

Certificate revocation

What happens if an attacker steals an entity's private key? When that happens, the attacker can decrypt anything intended for the entity. The attacker can also forge digital signatures as if they came from that entity. In short, the attacker can masquerade as the rightful owner of the certificate.

Once a certificate has been issued, it is normally put into production, where it will generally be distributed to many clients. If an attacker compromises the associated private key, the attacker now has the ability to use the certificate even though it doesn't belong to the attacker. Assuming that the proper owner is aware of the compromise, a new certificate with a new key pair should be obtained and put into use. Now there will be two certificates out there for the same entity, and both are technically valid because they both contain a valid CA signature. However, one of them clearly should not be trusted. The compromised certificate will eventually expire, but in the meantime, how will the world at large know not to trust it?

The answer lies in something called a certificate revocation list ( CRL). A CRL (shown in Figure 10-3) contains a list of all of the revoked certificates a CA has issued that have yet to expire. When a certificate is revoked, the CA is declaring that the certificate should not be trusted.

Clients should retrieve CRLs from the CA that issued a certificate

Figure 10-3. Clients should retrieve CRLs from the CA that issued a certificate

Bandwidth is a significant concern when distributing CRLs, because clients need to have reasonably current revocation information to properly validate a certificate. In an ideal world, the client would get up-to-date revocation information as soon as the CA gets the information. Unfortunately, many CAs distribute CRLs only as a huge list. Downloading a huge list before validating each certificate could easily add unacceptable latency and would place undue load on the server when there are many clients. As a result, CAs tend to update their CRLs regularly, but not immediately after they learn about key compromises. Included in the revocation list is the date and time that the next update will be published, so once an application has downloaded the list, it does not need to do so again until the one it has expires. Clients are encouraged to cache the information, but doing so may not be feasible if the client has limited storage space.

This scheme could leave a window of vulnerability during which the CA knows about a revoked certificate, yet the client does not. If a CA publishes the list too frequently, it will require massive amounts of bandwidth to sustain the frequent demand for the list. On the other hand, if a CA publishes the list too infrequently, certificates that need to be revoked will still be considered valid until the next list is published. Each CA needs to strike a balance with the community it is serving to determine how frequently to publish its list.

One solution to this problem is for the CA to break up its CRLs into segments. To do this, the CA specifies ranges of certificate serial numbers that each CRL would contain. For example, the CA could create a different CRL for each 1,000 serial numbers. Therefore, the first CRL would be for serial numbers 1 through ,1000; the second would be for serial numbers 1,001 through 2,000; and so on. This solution does require forethought and planning on the part of the CA, but it reduces the size of the CRLs that the CA issues. Another solution is to use "delta CRLs," where a CA periodically publishes incremental changes to its CRL list. Delta CRLs still require the client to cache CRL information or to download everything anew each time a certificate needs to be validated.

Another problem with CRLs is that while there is a standard means to publish them (formally specified in RFC 3280), that mechanism is optional, and many of the more common public CAs (e.g., VeriSign) do not distribute their CRLs this way. There are also other standard methods for distributing CRLs, but the overall problem is that there is no single method, so many software applications do not actually make use of CRLs at all. Of the various methods of distribution, LDAP is most commonly used as a repository for CRLs.

Yet another problem is that multiple applications on the same machine or even the local network could be interested in the same data and require it to be queried from the CA multiple times within a short period.

Problems with the distribution of CRLs currently make them difficult to manage, and what is worse, few applications even make the attempt. This essentially makes CRLs useless and leaves no way for a CA to revoke a certificate effectively once it has been issued. Ideally, CAs need to standardize on a method for distribution, and both CAs and applications need to start making use of it.

Another potentially serious problem that has not been addressed is what happens when a root CA's certificate needs to be revoked. A CRL is not suited to handle this, and neither are applications. The reason is that a parent (a CA) issues CRLs for its children, but a root CA has no parent. It is possible for a CA to revoke its own certificate as long as it still has its private key. For purposes of signing a CRL containing its own certificate, the CA's compromised key can still be trusted. Unfortunately, given the poor state of CRL handling in existing software in general, it is not too likely that this situation will be handled very well, if it is handled at all.

A classic example of how poorly CRLs are supported is what happened in early 2001 when VeriSign issued two class 3 code-signing certificates to Microsoft Corporation. The problem was that Microsoft never requested the certificates—someone claiming to represent Microsoft did. Given the process failure, VeriSign handled the situation in the appropriate manner and published the serial numbers of the certificates in a new CRL. Microsoft's handling of the situation really demonstrated the flaws with CRLs. It quickly became clear that Microsoft's software, while distributing VeriSign's root certificates and using their services, did not check VeriSign's CRLs. Microsoft issued a patch to deal with the problem of the revoked certificates, but the patch did nothing to fix the problem of their software not utilizing the CRLs at all; it simply special-cased the bad certificates. Had Microsoft's software made proper use (or, arguably, any use at all) of CRLs, no patch would have been necessary and the problem would have ended with VeriSign's publication of its CRL (minus the inherent window of vulnerability).

It could be argued that if a major software company like Microsoft can't handle CRLs properly, how can smaller software companies and individual software developers be expected to do so? While this argument may be faulty in a number of respects, it is still a question worth asking; the answer, at least for now, is not one that we would all like to hear. PKI is still relatively immature, and much work needs to be done to remedy not only the issues that we have discussed here, but also others that we leave as an exercise for the reader to explore. While CRLs may not be the ultimate answer to revoking a certificate, they are, for the time being, the most widely implemented means by which to do so. It is worth taking the time to ensure that your software is capable of dealing with the technology and provides for a reasonably safe and pleasant experience for your users.

To complicate matters more, the standard CRL specification has changed over time, and both the old format (Version 1) and the new format (Version 2) are still actively used. OpenSSL supports both Version 1 and Version 2 CRLs, but there is much software still in common use that does not yet support Version 2, and certainly old legacy applications that are no longer being developed or supported never will, even though they continue to be used. The major addition that Version 2 brings to the table is extensions. The standard defines four extensions that are used primarily to indicate the following:

§ When a certificate was revoked

§ Why a certificate was revoked

§ How to handle a certificate that has been revoked

§ How to deal with indirect CRLs

An indirect CRL is one that is not necessarily issued by a CA, but instead by a third party. Such a CRL can contain certificates from multiple CAs. The extension, then, is used to indicate which CA issued the certificate that has been revoked. Currently, indirect CRLs are not very common, particularly because CRLs in Version 2 format are not widely supported.

Online Certificate Status Protocol

The Online Certificate Status Protocol (OCSP), formally specified in RFC 2560, is a relatively new addition to PKI. Its primary aim is to address some of the distribution problems that have traditionally plagued CRLs.

Using OCSP, an application makes a connection to an OCSP responder and requests the status of a certificate by passing the certificate's serial number. The responder replies with one of these responses:

Good

Indicates that the certificate is valid, as far as the responder knows. This does not necessarily mean that the certificate was ever issued, just that it has not been revoked.

Revoked

Indicates that the certificate has indeed been issued and that it has also been revoked.

Unknown

Indicates that the responder does not know anything about the certificate. A typical reason for this response could be that a CA unknown to the responder issued the certificate.

An OCSP responder is typically operated by a CA or by a trusted third party that is authorized by the CAs for which it provides information. The client must trust the OCSP responder in a manner similar to a root CA. More importantly, there is only one way to revoke an OCSP's trusted status, and it is not pretty. If an OCSP responder is compromised, every client that makes use of that responder must be manually reconfigured either to not trust it or to use a new certificate that can be trusted. While it is theoretically possible to revoke an OCSP responder's certificate, it is essentially impossible to do so in practice.

A client's request includes information about the issuer of the certificate for which it is requesting status information, so it is possible for a single OCSP responder to provide certificate revocation information for more than a single CA. Unfortunately, one of the problems of OCSP responders when run by a third party is that the information they are serving can become stale. At the very least, a delay often occurs between the time that a CA revokes a certificate and the time the responder receives the information from the CA, particularly if the responder is relying on CRLs published by its serviceable CAs to supply its information.

Currently, OCSP is not nearly as widely recognized or implemented as CRLs are, so unless you know that all your users will have an OCSP responder available, it is generally best to use the technology to supplement CRLs rather than to replace them completely.

OCSP introduces a significant potential for three types of attacks:

Denial of service attacks

Most servers are vulnerable to denial of service attacks to some extent, but the nature of the service, the amount of information transferred, and the way requests are handled help determine just how vulnerable a given server is to such an attack. The details of denial of service attacks are beyond the scope of this book; note, however, that OCSP responders are typically more susceptible to these attacks than are other common services such as HTTP, for example.

Replay attacks

The OCSP Version 1 specification allows responders to preproduce signed responses in an effort to reduce the load on the responder required by signing definitive responses. Allowing for preproduced signed responses opens the door for replay attacks.

Man-in-the-middle attacks

Man-in-the-middle attacks are possible because error responses are not signed. Note that it is possible to consider this type of attack a denial of service attack.

Perhaps what is most disturbing about these vulnerabilities is the fact that although the RFC notes each one nothing was done to prevent them when formalizing the standard.

There are only a handful of public OCSP responders available at the time of this writing, as listed by OpenValidation.org. The small number of responders is a clear indication that OCSP is not widely deployed. While it is an attempt at resolving the problems of CRLs, we feel that the additional problems it creates, at least in its current state, outweigh the problems that it solves. Certainly, it cannot be reasonably considered a replacement for CRLs. In its defense, an IETF draft was submitted in March 2001 for Version 2 of the protocol, which addresses some of the issues, but this has not yet completed the standards process, and is far from being deployed.

We cover use of OCSP using OpenSSL in Recipe 10.12.

Certificate hierarchies

A certificate that is issued by a CA can be used to issue and sign another certificate, if the issued certificate is created with the appropriate permissions to do so. In this way, certificates can be chained. At the root of the chain is the root CA's certificate. Because it is at the root of the chain and there is no other authority to sign its certificate, the root CA signs its own certificate. Such a certificate is known as a self-signed certificate .

There is no way to digitally verify the authenticity of a self-signed certificate because the issuer and the subject are the same, which is why it has become common practice to provide these certificates with the software that uses them. When self-signed certificates are included with an application, the software author generally obtains them by some physical means. For example, Thawte (now a part of VeriSign) provides its root certificates on its web site, free and clear, but strongly advises anyone making use of them to confirm the certificate fingerprints with Thawte via telephone before using or distributing them.

To verify the authenticity and validity of a given certificate, each certificate in the chain must also be verified, from the issuer of the certificate all the way up to the root certificate. If any certificate in the chain is invalid, each certificate below it in the chain must also be considered invalid. Invalid certificates typically have either expired or been revoked (perhaps due to certificate theft). A certificate is also most certainly considered invalid if it has been tampered with or if the signatures on the certificate do not match the ones that should have been used to sign it, indicating that an attacker has tampered with the contents.

The decision about whether to employ a certificate hierarchy more complex than a single root CA depends on many factors. These factors and their trade-offs are well beyond the scope of this book. Entire books have been devoted to PKI, and we strongly recommend that you consult one or more of them to assist you in making an informed decision. Again, we strongly recommend Planning for PKI, cited at the beginning of this recipe.

X.509 certificates

The most widely accepted format for certificates is the X.509 format, first introduced in 1988. There are three versions of the format: X.509v1, X.509v2, and X.509v3. The most recent revision to the standard was introduced in 1996, and most, if not all, modern software now supports it. A large number of changes were made between X.509v1 and X.509v3, but perhaps the most significant feature introduced in the X.509v3 standard is its support for extensions.

Version 3 extensions allow a certificate to contain additional fields beyond those defined by previous versions of the X.509 standard. The additional fields may be standard in X.509v3, such as the basicConstraints or keyUsage extensions, or they may be completely nonstandard, perhaps recognized by only a single application. Each extension has a name for its field, a designation indicating whether the extension is critical or not, and a value to be associated with the extension field. When an extension is designated as being critical, software that does not recognize the extension must reject the certificate as being invalid. If the extension is noncritical and unknown to the certificate user, it may be ignored.

The X.509v3 standard defines numerous extensions in an effort to consolidate the more frequently appearing extensions implemented by third parties. One such example is the permissible uses for a certificate—for example, whether a certificate is allowed to sign another certificate or is usable in an SSL server. If each application were to create its own disparate extensions, the information in those extensions either would be unusable by other applications or would significantly complicate the process of validating a certificate because it would need to recognize a virtually unlimited number of different extensions that all mean essentially the same thing.

Of the standard extensions defined by X.509v3, there are only four that are well supported and in widespread use. Only one of them must be designated critical according to the standard, while the other three may or may not be. For now, we will not delve into the details of the X.509 format, but in Recipe 10.4 through Recipe 10.7 we will discuss what you need to know to properly validate a certificate.

See Also

§ Planning for PKI: Best Practices Guide for Deploying Public Key Infrastructure by Russ Housley and Tim Polk (John Wiley & Sons)

§ Network Security with OpenSSL by John Viega, Matt Messier, and Pravir Chandra (O'Reilly & Associates)

§ RFC 3280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile

§ RFC 2560: Online Certificate Status Protocol

§ Recipe 7.1, Recipe 10.4, Recipe 10.5, Recipe 10.6, Recipe 10.7, Recipe 10.12


[1] Specifically, RSA. Not all public key algorithms are capable of performing encryption. RSA supports encryption, key agreement, and digital signatures; DSA supports only digital signatures; and Diffie-Hellman supports only key agreement.

10.2. Obtaining a Certificate

Problem

You want an established PKI to issue a certificate to you.

Solution

Contact the CA that you wish to use. In this recipe, we focus on how to deal with VeriSign, which is the most popular CA. VeriSign sells several kinds of certificates from their web page (http://www.verisign.com).

In Recipe 10.3, we enumerate other CAs that have their root certificates in the popular browsers and thus are worthwhile to consider as alternatives.

Discussion

Before obtaining a certificate, you first need to determine what purpose the certificate will serve. There are many different types of certificates offered by a variety of CAs, both public and private. For the purposes of this discussion, we will investigate what is necessary to obtain three different types of certificates from a public CA. While VeriSign is certainly not the only public CA, it is perhaps the most established one and offers the widest variety of certificates for a variety of uses. VeriSign's offerings range from personal certificates for use with S/MIME to enterprise solutions that are more sophisticated. In this recipe, we'll find out how to get three types of certificates: a personal certificate for S/MIME, a code-signing certificate for signing your software so that users can verify it came from you, and a certificate for securing your web site for applications such as e-commerce. Figure 10-4 illustrates the process of obtaining a certificate from a CA.

Obtaining a certificate from a CA

Figure 10-4. Obtaining a certificate from a CA

Personal certificates

S/MIME email relies on personal certificates (as opposed to certificates granted to an organization), which VeriSign calls a Class 1 Digital ID . It is the easiest kind of certificate to obtain and is available for a modest price, but it is limited to use for securing your email only. You can get a Class 1 Digital ID that works with Netscape Messenger or one intended to work with Microsoft Outlook Express. If you use a different application to read and write your email, you should consult with that application's vendor to find out whether it interoperates with either of these certificate types.

The first step in obtaining a personal certificate is to visit VeriSign's web site at http://www.verisign.com and follow the links from the main page to Secure Messaging, which is listed under Retail Services on the Products/Services page, to the Digital ID enrollment form. We won't outline all of the links here; not only are they subject to change, but there is a wealth of information on the site that is well worth reading, including information on how to make use of the certificate once it has been issued. Once you have filled out and submitted the enrollment form, VeriSign will send an automated email to the address you included in the enrollment form; this email will contain instructions on how to "pick up" the certificate.

The first set of questions on the enrollment form is self-explanatory:

First and last name

The name you enter indicates how your Digital ID will be listed in VeriSign's directory service.

Email address

Enter the address you will be using with the Digital ID. It becomes the certificate's distinguished name. It is also listed alongside your first and last name in the directory. VeriSign will also use the address to verify its validity by sending an automated email to that address with instructions on how to retrieve the certificate that has been issued.

Challenge phrase

The challenge phrase used to protect the certificate will be available to both you and VeriSign. You should not share it with anyone else! VeriSign will use the phrase to verify that you are the owner of the certificate when you request that it be revoked, renewed, or replaced. Be sure to choose a phrase that you will be able to remember, but one that will not be easily guessed even by someone that knows you well.

VeriSign will choose a default key length for the certificate that it will issue you based upon the information it gets from your browser. You won't need to change the key length selected for you unless you're using something other than Netscape or Microsoft products to access your email; in that case, the documentation for your email software or the vendor of the software should have advised you on the proper setting to choose.

If you are using Microsoft Internet Explorer to retrieve the certificate, it will be unprotected by default. That is, once you install it in your email software, you will not be required to enter any password or passphrase to gain access to it. If you opt to keep your certificate unprotected in this manner, you must ensure that the private key for your certificate is not compromised. It is generally not a good idea to leave your certificate unprotected, so VeriSign offers two methods of protecting it:

Medium security

One step up from the default of low security is medium security, which requires your approval each time the private key is accessed. With medium security, you still are not required to enter a password or passphrase to unlock the private key.

High security

This level of security requires you to enter a password or passphrase to unlock the key each time it is accessed.

Remember that anybody gaining access to your private key will be able to use your certificate to masquerade as you. When an email is signed with your private key, people are going to trust it, so this can have disastrous effects if your key is compromised. Anyone with access to your private key will also be able to decrypt email that has been encrypted with your public key. Sure, your certificate can be revoked, but as we discussed earlier, revoking a certificate does not have any effect if its revocation status is not being checked. With this in mind, particularly for mobile users, we highly recommend that you choose high security.

Finally, you should read and must accept VeriSign's subscriber agreement and privacy policy. If you are using Microsoft Internet Explorer and you checked the checkbox for securing your certificate, a dialog will be presented to you to select the security level that you want to apply to the certificate. Within an hour or so, you will receive an email from VeriSign at the address you entered into the enrollment form containing instructions on how to "pick up" your certificate from VeriSign. Included in the email are a URL and a PIN, both of which you'll need to get the certificate from VeriSign. You should use the same machine and browser to retrieve the certificate as you did to request it.

That's all there is to it! Once you have retrieved your certificate from VeriSign, follow the directions presented on VeriSign's site to make use of the certificate in either Netscape or Microsoft Internet Explorer. Again, if you are using other software to access your email, follow the vendor's directions to enable the certificate. Now you are ready to start sending and receiving secure email!

Code-signing certificates

VeriSign offers code-signing certificates for use by software developers and software vendors. The purpose of such certificates is to sign your code that users download from the Internet. By signing your code, users can be assured that the code has not been tampered with or corrupted since it was digitally signed with your certificate. In the online world, where people are not only becoming increasingly aware of security issues but also worry about viruses and worms, signing your code provides a certain assurance to your users that they are getting the software they are expecting to get.

Obtaining a code-signing certificate is not nearly as quick and easy as obtaining a personal certificate. Code-signing certificates are also considerably more expensive, but then again, they are not really intended for everyday individual users. At the time of writing, VeriSign offered six different types of code-signing certificates for various types of programs. You must be sure to get the proper certificate for the code that you wish to sign, because the different types of certificates may not work properly with other types of code. For example, Microsoft Authenticode certificates only work for Microsoft's Internet Explorer browser. For Netscape browsers, you need to get a Netscape Object Signing certificate. The available types of code-signing certificates are listed as part of the process of obtaining a code-signing certificate, and you must choose a type as the first step in obtaining a certificate.

The type of code-signing certificate required determines the specific requirements for making the request to VeriSign to obtain it. For a Microsoft Authenticode Digital ID, for example, much of the work is automated through Microsoft's Internet Explorer, while a Sun Java Signing Digital ID requires you to generate a certificate request using Sun's Java tools to be submitted along with the request. For each type of certificate, VeriSign supplies full instructions on what information is needed and how to go about obtaining and supplying it to VeriSign.

While each type of code-signing certificate has its own specific requirements for making the request, they all also have common requirements that must be met as well. Most of the requirements are self-explanatory, such as contact and payment information. Each certificate must also have information about who owns the certificate. Such information includes the name of the company or organization and the location from which it does business. For example, a company doing business from the United States would be required to supply the city and state in which they're located.

There is also, of course, the very important need for the CA (VeriSign, in this case) to verify that they are issuing the certificate to someone that should legitimately have it. The quickest and easiest way for VeriSign to verify this information is with a Dun & Bradstreet DUNS number, a unique identifying number for businesses that is widely used. Supplying this information is optional, but the alternatives require more time and effort both on your part and VeriSign's. If you do not have or do not want to use a DUNS number, you can optionally mail or fax, along with your request for a code-signing certificate, copies of your business license, articles of incorporation, or partnership papers.

Once your request, including any appropriate documentation, has been submitted, VeriSign will review the submission. If everything is in order, VeriSign will issue a code-signing certificate, along with instructions on how to retrieve the certificate so that you may distribute and use it. In contrast to requests for personal certificates, requests for code-signing certificates are reviewed and verified by an actual living human being, so the certificate is not immediately available. Depending on VeriSign's workload, it may take several days for a certificate to be issued, although VeriSign will expedite requests for an additional fee.

Web site certificates

The process for obtaining a certificate for use in securing a web site, which VeriSign calls a secure server certificate , is very similar to the process for obtaining a certificate for code signing. Much of the same information is required, although there are some differences worth noting. Obviously, one of the primary differences is in the types of certificates offered. While code-signing certificates differ based on the type of code that will be signed (Netscape plug-ins versus Java applets, for example), secure server certificates are either 40-bit or 128-bit SSL certificates. That is, web site certificates explicitly restrict the size of the symmetric keys that should be used with the certificate. We recommend that you stick with 128-bit certificates, because 40-bit symmetric keys are widely regarded as unacceptably weak.

No matter what server software you plan to use, you must follow its instructions on how to generate a certificate signing request (CSR). Usually, you will generate a private key and use that private key to build a CSR. OpenSSL has the ability to do this using the req command. Unfortunately, there are plenty of different parameters that can be set, so it is difficult to provide a solution that works universally. Here is an example of using OpenSSL (and its default configuration file) to generate a 2,048-bit RSA key pair and build a certificate-signing request:

umask 077

openssl genrsa -des3 -out keyfile.pem 2048

openssl req -new -days 365 -key keyfile.pem -out csr.pem

You will be prompted for a passphrase when running the first command. With the third, you will be prompted for a wide variety of information that needs to be in the certificate. See Network Security with OpenSSL for a reference describing the set of parameters accepted by the OpenSSL reqcommand.

Unfortunately, the specific steps you will need to go through to build a CSR will vary for the kind of certificate you want and the CA you are using. VeriSign has instructions for many of the more popular servers available on its web site. The CSR you generate will also generate a key pair. While you must submit the CSR to VeriSign to have the certificate issued, you should keep the private key to yourself. It should not be sent to VeriSign or to anybody else.

As with code-signing certificates, you must also provide acceptable proof to VeriSign that you have a right to the certificate you are requesting. The options for providing this proof are the same—provide either a DUNS number or a copy of one of the aforementioned acceptable documents. In addition, a secure server certificate is bound to a domain name. VeriSign will issue certificates only to the registered owner of a domain. This means that if the domain is owned by a corporate entity, you must be an employee of that company.

Once your request, including any appropriate documentation, has been submitted, VeriSign will review your application. If everything is in order, a secure server certificate will be issued, and the certificate will be emailed to the technical contact that was provided when the request was submitted. As with code-signing certificates, an actual living human being reviews the information, so it may take several days for the certificate to be issued, depending on VeriSign's workload. Expedited processing is also available for an additional fee.

See Also

§ Network Security with OpenSSL by John Viega, Matt Messier, and Pravir Chandra (O'Reilly & Associates)

§ Recipe 10.3

10.3. Using Root Certificates

Problem

You want to do certificate validation, but you need the correct certificates from the certification authorities you intend to support.

Solution

The certificates that you need can be obtained from the authority themselves, but unfortunately, many CAs do not make them easy to get. OpenSSL includes several of the more common root CA certificates, but it is not a complete collection. Popular web browsers such as Internet Explorer for Windows also allow you to export the certificates they contain.

A much more in-depth survey of all the common root certificates (particularly the ones found in Microsoft's Internet Explorer) is available in the Root Report , available for sale from the PKI Laboratory (http://www.pkiclue.com).

Discussion

TIP

You should either obtain certificates directly from the CA over a trusted medium or check the fingerprints of certificates you find on the net or in your browser against fingerprints published in a trusted source. You can do this by calling the CA, or you can compare against the fingerprints published in this book.

Table 10-1 lists information about the root certificates for several prominent CAs. The information was collected from Internet Explorer for Windows, but it contains only those CAs that also publish CRLs. You can download these certificates (in PEM format) from the book's web site, but be sure to check the fingerprint of the certificate against the fingerprint listed in this book. To check the fingerprint using the OpenSSL command-line tool, use the command:

openssl x509 -fingerprint -noout -in cert.pem

where cert.pem is the name of the file containing the certificate that you wish to check.

Note that most CAs have multiple certificates, so you should figure out what type of certificate is right for your application. Generally, CAs will have at least one type of certificate intended for secure servers. They may also have "personal" certificates for user identification and even multiple types of personal certificates. Be sure to check the description to figure out which certificates are relevant to your application.

Because most certificates eventually expire, there may be multiple root certificates of the same type from the same CA at one time. For example, for a few years, VeriSign had three different valid root certificates for their "class 3" PKI, which was generally for server certificates. One of those has now expired, and another one will expire in 2004.

Here we detail only a subset of certificates that are distributed with Internet Explorer for Windows. Certificates in this list may expire, in which case you should go directly to the CA or to some other trusted source. At the time of writing, any valid certificate signed by one of the CAs listed inTable 10-1 is likely to be signed by one of the associated certificates.

WARNING

Usually, you should not simply trust all root certificates. For example, email certificates (class 1) do not really offer a guarantee about who is on the other end. In addition, you will want to validate other information about certificates, even if the CA's signature is valid (see Recipe 10.4 through Recipe 10.7).

The "use" column in the table indicates the kind of certificate the root CA certificate uses to sign. Generally, certificates are intended for one of the following purposes:

Secure email

The CA is rarely validating anything other than the fact that the person with the private key associated with the certificate has access to the email address listed in the certificate. Such certificates are used in the S/MIME secure email standard.

Client authentication

The CA (or its subordinate) has done reasonable validation on the identity of the entity to which the certificate is issued.

Server authentication

Used primarily for electronic commerce over the Web. The CA or its subordinate has done validation on the identity of the entity to which the certificate is issued.

Code signing

Used for validating the vendor that produced mobile code. The CA or its subordinate has done validation on the identity of the entity to which the certificate is issued.

Time stamping

Used for proving the existence of data at a specific date and time.

Table 10-1. CA certificates, their uses, expiration dates, and fingerprints

CA

Certificate

Use

Expires (GMT)

MD5 fingerprint

Equifax

Secure Certificate Authority

Secure email, server authentication, code signing

2018-08-22 16:41:51

67:CB:9D:C0:13:24:8A:82:9B:B2:17:1E:D1:1B:EC:D4

Equifax

Secure eBusiness CA-1

Secure email, server authentication, code signing

2020-06-21 04:00:00

64:9C:EF:2E:44:FC:C6:8F:52:07:D0:51:73:8F:CB:3D

Equifax

Secure eBusiness CA-2

Secure email, server authentication, code signing

2019-06-23 12:14:45

AA:BF:BF:64:97:DA:98:1D:6F:C6:08:3A:95:70:33:CA

Equifax

Secure Global eBusiness CA-1

Secure email, server authentication, code signing

2020-06-21 04:00:00

8F:5D:77:06:27:C4:98:3C:5B:93:78:E7:D7:7D:9B:CC

RSA Data Security

Secure Server

Server authentication

2010-01-07 23:59:59

74:7B:82:03:43:F0:00:9E:6B:B3:EC:47:BF:85:A5:93

Thawte

Server

Code signing, server authentication

2020-12-31 23:59:59

C5:70:C4:A2:ED:53:78:0C:C8:10:53:81:64:CB:D0:1D

TrustCenter

Class 1

Secure email, server authentication

2011-01-01 11:59:59

8D:26:FF:2F:31:6D:59:29:DD:E6:36:A7:E2:CE:64:25

TrustCenter

Class 2

Secure email, server authentication

2011-01-01 11:59:59

B8:16:33:4C:4C:4C:F2:D8:D3:4D:06:B4:A6:5B:40:03

TrustCenter

Class 3

Secure email, server authentication

2011-01-01 11:59:59

5F:94:4A:73:22:B8:F7:D1:31:EC:59:39:F7:8E:FE:6E

TrustCenter

Class 4

Secure email, server authentication

2011-01-01 11:59:59

0E:FA:4B:F7:D7:60:CD:65:F7:A7:06:88:57:98:62:39

UserTrust Network

UTN-UserFirst-Object

Code signing, time stamping

2019-07-09 18:40:36

A7:F2:E4:16:06:41:11:50:30:6B:9C:E3:B4:9C:B0:C9

UserTrust Network

UTN-UserFirst-Network Applications

Secure email, server authentication

2019-07-09 18:57:49

BF:60:59:A3:5B:BA:F6:A7:76:42:DA:6F:1A:7B:50:CF

UserTrust Network

UTN-UserFirst-Hardware

Server authentication

2019-07-09 18:19:22

4C:56:41:E5:0D:BB:2B:E8:CA:A3:ED:18:08:AD:43:39

UserTrust Network

UTN-UserFirst-Client Authentication and Email

Secure email

2019-07-09 17:36:58

D7:34:3D:EF:1D:27:09:28:E1:31:02:5B:13:2B:DD:F7

UserTrust Network

UTN-DataCorp SGC

Server authentication

2019-06-24 19:06:30

B3:A5:3E:77:21:6D:AC:4A:C0:C9:FB:D5:41:3D:CA:06

ValiCert

Class 1 Policy Validation Authority

Secure email, server authentication

2019-06-25 22:23:48

65:58:AB:15:AD:57:6C:1E:A8:A7:B5:69:AC:BF:FF:EB

VeriSign

Class 1 Public PCA

Secure email, client authentication

2020-01-07 23:59:59

51:86:E8:1F:BC:B1:C3:71:B5:18:10:DB:5F:DC:F6:20

VeriSign

Class 1 Public PCA

Secure email, client authentication

2028-01-08 23:59:59

97:60:E8:57:5F:D3:50:47:E5:43:0C:94:36:8A:B0:62

VeriSign

Class 1 Public PCA (2nd Generation)

Secure email, client authentication

2018-05-18 23:59:59

F2:7D:E9:54:E4:A3:22:0D:76:9F:E7:0B:BB:B3:24:2B

VeriSign

Class 1 Public PCA (2nd Generation)

Secure email, client authentication

2028-08-01 23:59:59

DB:23:3D:F9:69:FA:4B:B9:95:80:44:73:5E:7D:41:83

VeriSign

Class 2 Public PCA

Secure email, client authentication, code signing

2004-01-07 23:59:59

EC:40:7D:2B:76:52:67:05:2C:EA:F2:3A:4F:65:F0:D8

VeriSign

Class 2 Public PCA

Secure email, client authentication, code signing

2028-08-01 23:59:59

B3:9C:25:B1:C3:2E:32:53:80:15:30:9D:4D:02:77:3E

VeriSign

Class 2 Public PCA (2nd Generation)

Secure email, client authentication, code signing

2018-05-18 23:59:59

74:A8:2C:81:43:2B:35:60:9B:78:05:6B:58:F3:65:82

VeriSign

Class 2 Public PCA (2nd Generation)

Secure email, client authentication, code signing

2028-08-01 23:59:59

2D:BB:E5:25:D3:D1:65:82:3A:B7:0E:FA:E6:EB:E2:E1

VeriSign

Class 3 Public PCA

Secure email, client authentication, code signing, server authentication

2004-01-07 23:59:59

78:2A:02:DF:DB:2E:14:D5:A7:5F:0A:DF:B6:8E:9C:5D

VeriSign

Class 3 Public PCA

Secure email, client authentication, code signing, server authentication

2028-08-01 23:59:59

10:FC:63:5D:F6:26:3E:0D:F3:25:BE:5F:79:CD:67:67

VeriSign

Class 3 Public PCA (2nd Generation)

Secure email, client authentication, code signing, server authentication

2018-05-18 23:59:59

C4:63:AB:44:20:1C:36:E4:37:C0:5F:27:9D:0F:6F:6E

VeriSign

Class 3 Public PCA (2nd Generation)

Secure email, client authentication, code signing, server authentication

2028-08-01 23:59:59

A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9

VeriSign

Commercial Software Publishers

Secure email, code signing

2004-01-07 23:59:59

DD:75:3F:56:BF:BB:C5:A1:7A:15:53:C6:90:F9:FB:CC

VeriSign

Individual Software Publishers

Secure email, code signing

2004-01-07 23:59:59

71:1F:0E:21:E7:AA:EA:32:3A:66:23:D3:AB:50:D6:69

See Also

§ Root Report from the PKI Laboratory: http://www.pkiclue.com/

§ Recipe 10.4, Recipe 10.5, Recipe 10.6, Recipe 10.7

10.4. Understanding X.509 Certificate Verification Methodology

Problem

You have an X.509 certificate, and you want to determine whether the certificate should be considered "valid." While the requirements defining validity may be different from application to application, you will be interested in knowing whether the identity bound to that certificate ought to be trusted.

Solution

First, establish a trusted path from the certificate to an installed root certificate. Then, if you have a trusted path, use information in the certificate to determine the rights of the entity tied to that certificate. Finally, check to make sure the certificate presented has not been compromised or otherwise revoked.

Discussion

The specifics of how to do certificate verification vary depending on the library you are using. However, the methodology remains much the same no matter which library you use. Most libraries perform basic certificate verification for you but leave you to perform identity checks, such as ensuring that a certificate presented by a server is actually appropriate for that server to be presenting.

First, note that public key infrastructures tend to support "hierarchies" of certificates, although not all infrastructures do. That is, a root certificate from VeriSign might be used to sign a "signing" certificate at AT&T, which might then be used to sign individual certificates for AT&T employees. VeriSign may not sign the employee certificates directly, but we can establish a chain of trust, because the personal certificates are "trusted" by the AT&T signing certificate, and VeriSign trusts the AT&T signing certificate. There can be arbitrary levels of depth in a certificate hierarchy. For example, the AT&T company-wide signing certificate could be used to sign department-wide certificates, which may then sign individual certificates.

Second, just because a CA signs a certificate does not necessarily mean that the certificate should be trusted by the entity that is presenting it. For example, suppose that you want to perform an electronic commerce transaction with Amazon. When an SSL connection to Amazon's server is established, the server presents a certificate. The first thing you do is check to see that there is a trusted path to a root CA that you trust. Suppose that the certificate presented to you is signed by VeriSign and has not expired. Does that mean the transaction should go forward? No! You have no idea whether the certificate that has been presented to you was issued to Amazon or not. For all you know, it could have been issued to Fred's Mattress Warehouse or any other entity. If you get a certificate that is not from Amazon or its representative, it is probably an attacker's certificate. Therefore, you need to verify the information in the certificate to make sure that it really should be trusted. Remember that the signature on the certificate proves that the data in the certificate has not been altered. A certificate issued to attacker.org cannot be modified to look like a certificate issued to Amazon because the signature verification would fail.

Third, what happens if Amazon's private key is stolen? They will create a new private key and get a new certificate issued that is bound to that new key, but what about the old key? An attacker could present the old certificate, and you wouldn't be able to tell the difference between it and the new certificate until the old certificate expires.

One solution to this problem is to use a certificate revocation list (CRL), a list of certificate serial numbers signed by the CA that represent invalid certificates. These lists are updated periodically and should be downloaded frequently to avoid stale information. Most CAs issue CRLs. (SeeRecipe 10.10 and Recipe 10.11 for details on where to look for CRLs and how to obtain them.) Another solution is to interactively ask the CA using the OCSP. (We discuss this protocol in Recipe 10.12.)

In general, a certificate is verified against a collection of other certificate material—that is, CA certificates and CRLs. To verify a certificate, all of the certificates in the chain must be known. Trusted certificates are certificates that are known to be valid without having to perform signature verification on them; however, they could be invalid for other reasons, as we will soon see. Untrusted certificates can also be present in the hierarchy, in which case they must also be verified using trusted certificates. There must always be at least one trusted certificate at the root of the hierarchy. If there is not, the certificate cannot be considered valid.

All certificates in the certification path must be checked to ensure they are valid for their assigned date. Every certificate has a beginning and an ending date for their validity period, and if the current date is outside that range, the certificate cannot be considered valid. Most people who have any familiarity with certificates usually realize that certificates expire, but many do not realize that they can have validity dates into the future and will not necessarily be valid yet at the point when they are presented.

Finally, you must check every certificate in the certification path to ensure that it has not been revoked. Revocation status can be checked using a CRL or by consulting an OCSP responder. It is best to be able to handle both types of revocation checks because one or the other may not always be available or reliable.

Once the validity of every certificate in the certification path has been verified, the basic verification tests are complete, but you are not done yet! You have only established that the certificate was issued by a CA that you trust, is within its valid period, and has not been revoked. Nothing has been done to verify that the entity that presented it to you is actually the entity that owns it. The details of how to do this vary, but in the most common case of a server presenting a certificate to a client, the hostname of the server should be embedded in the certificate. The hostname in the certificate can be compared to the hostname of the server that presented it (see Recipe 10.8). If the hostnames do not match, the certificate should not be trusted.

In situations where it isn't feasible to perform full certificate verification, an alternative is to compare the certificate against a list of known good certificates. See Recipe 10.9 for a discussion of how to do this.

See Also

Recipe 10.8, Recipe 10.9, Recipe 10.10, Recipe 10.11, Recipe 10.12

10.5. Performing X.509 Certificate Verification with OpenSSL

Problem

You have an X.509 certificate and you want to verify its validity using OpenSSL.

Solution

OpenSSL represents an X.509 certificate using an X509 object. Another object, an X509_STORE, must be combined with the X509 object to be verified into an X509_STORE_CTX object. An X509_STORE object contains the certificates that OpenSSL will use to verify the certificate under scrutiny, as well as an optional CRL. The X509_STORE_CTX object simply combines the X509_STORE and X509 objects. The actual certificate verification is performed by calling X509_verify_cert( ) and passing it the X509_STORE_CTX object.

Discussion

Actually performing the certificate verification requires a significant amount of setup work. Much of the work should not really be necessary, but there are some issues with the current version of OpenSSL that need to be addressed. The OpenSSL team is aware of the problems we have encountered, and we anticipate that they will be fixed at some point in the future, but unfortunately, we do not know when that might be.

OpenSSL provides a set of functions for manipulating X509_STORE objects, and we will be using them, but in versions of OpenSSL up to and including the initial release of 0.9.7, no X.509 objects are reference counted while other OpenSSL objects (including EVP_PKEY, SSL_CTX, and many others) are. This presents a problem for us because much of the code that we will be presenting needs to have only a single X509_STORE object used for different purposes. If we attach the X509_STORE object to an SSL_CTX, for example, when the SSL_CTX is destroyed, so is the X509_STORE object. When trying to build a higher-level API on top of OpenSSL's API, things quickly get ugly.

The situation is complicated by the fact that OpenSSL provides no APIs to duplicate objects. Our solution to this problem as a whole is to create a new structure that contains everything we might need, then to create X509_STORE objects from that structure as we need them. It is obviously not optimal, and it is also not a perfect solution, but it is difficult to do any better. The proper solution is OpenSSL's to implement, but it's not a small task. Reference counting is often difficult to get right, and adding that kind of memory management into a large body of existing code is even harder.

We begin our solution by defining two data types. One is merely a convenience for a function pointer. The other is the core of our X509_STORE wrapper:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <openssl/evp.h>

#include <openssl/x509.h>

typedef int (*spc_x509verifycallback_t)(int, X509_STORE_CTX *);

typedef struct {

char *cafile;

char *capath;

char *crlfile;

spc_x509verifycallback_t callback;

STACK_OF(X509) *certs;

STACK_OF(X509_CRL) *crls;

char *use_certfile;

STACK_OF(X509) *use_certs;

char *use_keyfile;

EVP_PKEY *use_key;

int flags;

} spc_x509store_t;

We will not get into any detailed explanation of this structure here. Instead, we will provide a complete set of functions to manipulate the structure and explain as we go along. The first two functions are used to initialize and clean up an spc_x509store_t object. The caller is responsible for allocating memory for the object as necessary. Our API will only manage the object's contents.

void spc_init_x509store(spc_x509store_t *spc_store) {

spc_store->cafile = 0;

spc_store->capath = 0;

spc_store->crlfile = 0;

spc_store->callback = 0;

spc_store->certs = sk_X509_new_null( );

spc_store->crls = sk_X509_CRL_new_null( );

spc_store->use_certfile = 0;

spc_store->use_certs = sk_X509_new_null( );

spc_store->use_keyfile = 0;

spc_store->use_key = 0;

spc_store->flags = 0;

}

void spc_cleanup_x509store(spc_x509store_t *spc_store) {

if (spc_store->cafile) free(spc_store->cafile);

if (spc_store->capath) free(spc_store->capath);

if (spc_store->crlfile) free(spc_store->crlfile);

if (spc_store->use_certfile) free(spc_store->use_certfile);

if (spc_store->use_keyfile) free(spc_store->use_keyfile);

if (spc_store->use_key) EVP_PKEY_free(spc_store->use_key);

sk_X509_free(spc_store->certs);

sk_X509_free(spc_store->crls);

sk_X509_free(spc_store->use_certs);

}

The next three functions are used to set the locations from which trusted certificates and certificate revocation lists will be loaded:

spc_x509store_setcafile( )

Accepts a filename that specifies a single file containing any number of PEM- encoded certificates. (See Recipe 7.17 for a discussion of PEM files.)

spc_x509store_setcapath( )

Accepts a pathname that specifies the location of trusted certificates. Each file in the directory should contain only a single PEM-encoded certificate and should be named with the hash value of the certificate it contains, suffixed with ".0". The hash value of a certificate can be obtained by issuing the following command on the file containing the certificate:

openssl x509 -noout -hash -in cert.pem

spc_x509store_setcrlfile( )

Accepts a filename that specifies a single file containing any number of PEM- encoded CRLs.

For any of the functions, NULL may be specified for the filename or pathname, in which case the system defaults will be used.

void spc_x509store_setcafile(spc_x509store_t *spc_store, char *cafile) {

if (spc_store->cafile) free(spc_store->cafile);

spc_store->cafile = (cafile ? strdup(cafile) : 0);

}

void spc_x509store_setcapath(spc_x509store_t *spc_store, char *capath) {

if (spc_store->capath) free(spc_store->capath);

spc_store->capath = (capath ? strdup(capath) : 0);

}

void spc_x509store_setcrlfile(spc_x509store_t *spc_store, char *crlfile) {

if (spc_store->crlfile) free(spc_store->crlfile);

spc_store->crlfile = (crlfile ? strdup(crlfile) : 0);

}

Additional certificates and CRLs can be added to the store using one of the next two functions. Note that if duplicate certificates or CRLs are included in the spc_x509store_t object, spc_create_x509store( ) will not be able to successfully create an X509_STORE object. These two functions should only be used to add certificates and CRLs to the store that are not present in the certificate file, certificate path, or CRL file.

void spc_x509store_addcert(spc_x509store_t *spc_store, X509 *cert) {

sk_X509_push(spc_store->certs, cert);

}

void spc_x509store_addcrl(spc_x509store_t *spc_store, X509_CRL *crl) {

sk_X509_CRL_push(spc_store->crls, crl);

}

The last set of functions for manipulating spc_x509store_t objects is used for setting up a certificate verification callback function and for defining flags that control various aspects of the X509_STORE and certificate verification behavior. If no verification callback function is defined,spc_verify_callback( ) is the default; it simply prints any errors encountered out to stderr.

void spc_x509store_setcallback(spc_x509store_t *spc_store,

spc_x509verifycallback_t callback) {

spc_store->callback = callback;

}

#define SPC_X509STORE_NO_DEFAULT_CAFILE 0x01

#define SPC_X509STORE_NO_DEFAULT_CAPATH 0x02

void spc_x509store_setflags(spc_x509store_t *spc_store, int flags) {

spc_store->flags |= flags;

}

void spc_x509store_clearflags(spc_x509store_t *spc_store, int flags) {

spc_store->flags &= ~flags;

}

int spc_verify_callback(int ok, X509_STORE_CTX *store) {

if (!ok)

fprintf(stderr, "Error: %s\n", X509_verify_cert_error_string(store->error));

return ok;

}

Only two flags are defined here, leaving plenty of room to expand the implementation and add additional flags as needed:

SPC_X509STORE_NO_DEFAULT_CAFILE

If this flag is set and no file of trusted certificates has been specified, the system-wide default is used. This flag is checked when creating an X509_STORE object via spc_create_x509store( ).

SPC_X509STORE_NO_DEFAULT_CAPATH

If this flag is set and no path of trusted certificates has been specified, the system-wide default is not used. This flag is checked when creating an X509_STORE object via spc_create_x509store( ).

The last function, spc_create_x509store( ) , creates a new X509_STORE object from the information contained in the spc_x509store_t object that it accepts as its only argument. Attentive readers will notice at this point that we have omitted discussion of several fields in the spc_x509store_tstructure. We will address them in Recipe 10.7.

X509_STORE *spc_create_x509store(spc_x509store_t *spc_store) {

int i;

X509_STORE *store;

X509_LOOKUP *lookup;

store = X509_STORE_new( );

if (spc_store->callback)

X509_STORE_set_verify_cb_func(store, spc_store->callback);

else

X509_STORE_set_verify_cb_func(store, spc_verify_callback);

if (!(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file( ))))

goto error_exit;

if (!spc_store->cafile) {

if (!(spc_store->flags & SPC_X509STORE_NO_DEFAULT_CAFILE))

X509_LOOKUP_load_file(lookup, 0, X509_FILETYPE_DEFAULT);

} else if (!X509_LOOKUP_load_file(lookup, spc_store->cafile, X509_FILETYPE_PEM))

goto error_exit;

if (spc_store->crlfile) {

if (!X509_load_crl_file(lookup, spc_store->crlfile, X509_FILETYPE_PEM))

goto error_exit;

X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |

X509_V_FLAG_CRL_CHECK_ALL);

}

if (!(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir( ))))

goto error_exit;

if (!spc_store->capath) {

if (!(spc_store->flags & SPC_X509STORE_NO_DEFAULT_CAPATH))

X509_LOOKUP_add_dir(lookup, 0, X509_FILETYPE_DEFAULT);

} else if (!X509_LOOKUP_add_dir(lookup, spc_store->capath, X509_FILETYPE_PEM))

goto error_exit;

for (i = 0; i < sk_X509_num(spc_store->certs); i++)

if (!X509_STORE_add_cert(store, sk_X509_value(spc_store->certs, i)))

goto error_exit;

for (i = 0; i < sk_X509_CRL_num(spc_store->crls); i++)

if (!X509_STORE_add_crl(store, sk_X509_CRL_value(spc_store->crls, i)))

goto error_exit;

return store;

error_exit:

if (store) X509_STORE_free(store);

return 0;

}

We can now use the functions to manipulate spc_x509store_t objects in verifying an X.509 certificate's validity. The function spc_verify_cert( ) requires an X509 object and spc_x509store_t object. It creates an X509_STORE object from the information in the spc_x509store_t object, and combines it with the X509 object to create an X509_STORE_CTX object as required by X509_verify_cert( ). The return value from spc_verify_cert( ) will be -1 if some kind of error occurred that was not related to the validity of the certificate. If the certificate is valid, the return value will be 1; otherwise, the return value will be 0.

#include <openssl/x509.h>

int spc_verify_cert(X509 *cert, spc_x509store_t *spc_store) {

int result = -1;

X509_STORE *store = 0;

X509_STORE_CTX *ctx = 0;

if (!(store = spc_create_x509store(spc_store))) return -1;

if ((ctx = X509_STORE_CTX_new( )) != 0) {

if (X509_STORE_CTX_init(ctx, store, cert, 0) = = 1)

result = (X509_verify_cert(ctx) = = 1);

X509_STORE_CTX_free(ctx);

}

X509_STORE_free(store);

return result;

}

See Also

Recipe 7.17, Recipe 10.7

10.6. Performing X.509 Certificate Verification with CryptoAPI

Problem

You have an X.509 certificate, and you want to verify its validity using Microsoft's CryptoAPI on Windows.

Solution

CryptoAPI represents an X.509 certificate using a CERT_CONTEXT object. Another object, referenced by a HCERTSTORE handle, must be created to hold the certificates that will be required for verification, as well as any certificate revocation lists (CRLs) that may be necessary. The actual certificate verification is performed by calling the CertGetIssuerCertificateFromStore( ) function for each certificate in the hierarchy. This function will verify the signature, certificate validity times, and revocation status of each certificate as it obtains the issuer for each call. The last certificate in the hierarchy will have no issuing certificate and should be self-signed.

Discussion

Call the CertGetIssuerCertificateFromStore( ) function for each certificate in the hierarchy, beginning with the subject certificate at the end of the chain. Each time CertGetIssuerCertificateFromStore( ) is called, CryptoAPI will attempt to locate the issuer of the subject certificate passed into it. If the issuer certificate is found, the signature of the subject certificate will be verified with the public key of the issuer certificate. In addition, time validity checks will be performed on the subject certificate, and the subject certificate will be compared against the issuer's CRL if it is present in the store.

#include <windows.h>

#include <wincrypt.h>

BOOL SpcVerifyCert(HCERTSTORE hCertStore, PCCERT_CONTEXT pSubjectContext) {

DWORD dwFlags;

PCCERT_CONTEXT pIssuerContext;

if (!(pSubjectContext = CertDuplicateCertificateContext(pSubjectContext)))

return FALSE;

do {

dwFlags = CERT_STORE_REVOCATION_FLAG | CERT_STORE_SIGNATURE_FLAG |

CERT_STORE_TIME_VALIDITY_FLAG;

pIssuerContext = CertGetIssuerCertificateFromStore(hCertStore,

pSubjectContext, 0, &dwFlags);

CertFreeCertificateContext(pSubjectContext);

if (pIssuerContext) {

pSubjectContext = pIssuerContext;

if (dwFlags & CERT_STORE_NO_CRL_FLAG)

dwFlags &= ~(CERT_STORE_NO_CRL_FLAG | CERT_STORE_REVOCATION_FLAG);

if (dwFlags) break;

} else if (GetLastError( ) = = CRYPT_E_SELF_SIGNED) return TRUE;

} while (pIssuerContext);

return FALSE;

}

Every certificate returned by CertGetIssuerCertificateFromStore( ) must be freed with a call to CertFreeCertificateContext( ) . To make things a bit simpler, a copy of the original subject certificate is made so that the subject certificate can always be freed after the call toCertGetIssuerCertificateFromStore( ). If an issuer certificate is returned, the subject becomes the issuer for the next iteration through the loop.

When CertGetIssuerCertificateFromStore( ) cannot find the issuing certificate for the subject certificate in the store, it returns NULL. This could mean that the end of the certificate hierarchy has been reached, in which case GetLastError( ) will return CRYPT_E_SELF_SIGNED because the root certificate in any hierarchy must always be self-signed. A NULL return from CertGetIssuerCertificateFromStore( ) might also indicate that there may be an issuer certificate for the subject certificate, but that one wasn't present in the certificate store; this is an error condition that results in the verification failure of the subject certificate.

The call to CertGetIssuerCertificateFromStore( ) requires a set of flags to be passed into it that determines what verification checks are to be performed on the subject certificate. Upon return from the call, this set of flags is modified, leaving the bits set for the types of verification checks that failed. SpcVerifyCert( ) checks the set of flags after the successful return from CertGetIssuerCertificateFromStore( ) to see if CERT_STORE_NO_CRL_FLAG is set. If it is, this indicates that no CRL could be found in the store against which the subject certificate could be compared. At this point, the flags indicating failure as a result of there being no CRL are cleared. If any flags remain set, this means that verification of the subject certificate failed; the loop is terminated, and failure is returned.

CryptoAPI certificate stores

Several special certificate stores are available for use. In addition, private stores can be created that reside in memory, in the registry, or in a disk file. To use one of the special certificate stores, use the CryptoAPI function CertOpenSystemStore( ) . This function requires a handle to a Cryptographic Services Provider (CSP) and the name of the certificate store to open. In the majority of cases, the CSP handle can be passed as NULL, in which case the default CSP will be used. One of the names listed in Table 10-2 may be opened for use.

Table 10-2. System certificate stores and their contents

Certificate store name

Types of certificates in the store

MY

Contains certificates that are owned by the current user. For each certificate in this store, the associated private key is also available.

CA

Contains CA certificates that are not self-signed root certificates. These certificates are capable of issuing certificates.

ROOT

Contains root CA certificates that are trusted. All of the certificates in this store should be self-signed.

SPC

Contains trusted software publisher certificates. The certificates in this store are used by Microsoft's Authenticode.

For the purposes of verification using SpcVerifyCert( ) as presented, you'll need to create a temporary certificate store that contains all the certificates that will be needed to verify a subject certificate. At a minimum, the certificate that you want to verify must be in the store, but verification will only succeed if the only certificate in the store is the subject certificate and is self-signed, which in the vast majority of cases isn't all that useful.

If you do not have all the certificates and need to use certificates from one of the system stores, a copy of the needed certificate from the system store can be made for insertion into the temporary store being used for verification. Otherwise, certificates in memory as CERT_CONTEXT objects can be added to the temporary store, or encoded certificates residing in memory as a blob (binary large object) can be added.

#include <windows.h>

#include <wincrypt.h>

static PCCERT_CONTEXT FindIssuerInSystemStore(LPCTSTR pszStoreName,

PCCERT_CONTEXT pSubjectContext) {

HCERTSTORE hCertStore;

PCCERT_CONTEXT pIssuerContext

if (!(hCertStore = CertOpenSystemStore(0, pszStoreName))) return 0;

pIssuerContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0,

CERT_FIND_ISSUER_OF, pSubjectContext, 0);

CertCloseStore(hCertStore, 0);

return pIssuerContext;

}

static LPCTSTR SpcSystemStoreList[ ] = {

TEXT("MY"), TEXT("CA"), TEXT("ROOT"), TEXT("SPC"), 0

};

HCERTSTORE SpcNewStoreForCert(PCCERT_CONTEXT pSubjectContext) {

LPCTSTR pszStoreName;

HCERTSTORE hCertStore;

PCCERT_CONTEXT pIssuerContext;

/* First create an in-memory store, and add the subject certificate to it */

if (!(hCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, 0))) return 0;

if (!CertAddCertificateContextToStore(hCertStore, pSubjectContext,

CERT_STORE_ADD_REPLACE_EXISTING, 0)) {

CertCloseStore(hCertStore, 0);

return 0;

}

pSubjectContext = CertDuplicateCertificateContext(pSubjectContext);

while (!CertCompareCertificateName(X509_ASN_ENCODING,

pSubjectContext->pCertInfo->Issuer, pSubjectContext->pCertInfo->Subject)){

for (pszStoreName = SpcSystemStoreList; pszStoreName; pszStoreName++) {

pIssuerContext = FindIssuerInSystemStore(pszStoreName, pSubjectContext);

if (pIssuerContext) {

if (!CertAddCertificateContextToStore(hCertStore, pIssuerContext,

CERT_STORE_ADD_REPLACE_EXISTING, 0)) {

CertFreeCertificateContext(pSubjectContext);

CertFreeCertificateContext(pIssuerContext);

CertCloseStore(hCertStore, 0);

return 0;

}

CertFreeCertificateContext(pSubjectContext);

pSubjectContext = pIssuerContext;

break;

}

}

if (!pszStoreName) {

CertFreeCertificateContext(pSubjectContext);

CertCloseStore(hCertStore, 0);

return 0;

}

}

CertFreeCertificateContext(pSubjectContext);

return hCertStore;

}

The SpcNewStoreForCert( ) function creates a temporary in-memory certificate store that can be used with SpcVerifyCert( ) . Only a single argument is required: the subject certificate that is, presumably, at the end of a certificate hierarchy. The subject certificate is added to the new certificate store, and for each issuing certificate in the hierarchy, the system stores are searched for a copy of the certificate. If one cannot be found, the new certificate store is destroyed and SpcNewStoreForCert( ) returns NULL; otherwise, the found certificate will be added to the new certificate store.

Once the store has been created, it can now be passed directly into the SpcVerifyCert( ) function, along with the subject certificate to be verified. If there are CRLs for any of the certificates in the hierarchy, add them to the store before calling SpcVerifyCert( ) (see Recipe 10.11 for obtaining CRLs with CryptoAPI). You can enumerate the contents of the certificate store created by SpcNewStoreForCert( ) using CertEnumCertificatesInStore( ) :

BOOL bResult;

HCERTSTORE hCertStore;

PCCRL_CONTEXT pCRLContext;

PCCERT_CONTEXT pCertContext = 0;

if (!(hCertStore = SpcNewStoreForCert(pSubjectContext))) {

/* handle an error condition--could not create the store */

abort( );

}

while ((pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext))) {

/* do something with the certificate retrieved from the store.

* if an error occurs, and enumeration must be terminated prematurely, the last

* certificate retrieved must be freed manually.

*

* For example, attempt to retrieve the CRL for the certificate using the code

* the can be found in Recipe 10.11. If no CRL can be retrieved, or the CRL

* cannot be added to the certificate store, consider it a failure and break

* out of the enumeration.

*/

if (!(pCRLContext = SpcRetrieveCRL(pCertContext, 0)) ||

!CertAddCRLContextToStore(hCertStore, pCRLContext,

CERT_ADD_USE_EXISTING, 0)) {

if (pCRLContext) CertFreeCRLContext(pCRLContext);

break;

}

CertFreeCRLContext(pCRLContext);

}

if (pCertContext) {

CertFreeCertificateContext(pCertContext);

CertCloseStore(hCertStore, 0);

abort( );

}

bResult = SpcVerifyCert(hCertStore, pSubjectContext);

CertCloseStore(hCertStore, 0);

return bResult;

See Also

Recipe 10.11

10.7. Verifying an SSL Peer's Certificate

Problem

You are using OpenSSL to support SSL-enabled communication between a client and a server. You want to instruct OpenSSL to verify the certificate received from the peer.

Solution

Every SSL connection has an SSL object, which in turn has an SSL_CTX object, and that object, in turn, has an X509_STORE object. OpenSSL uses the X509_STORE object as a container for any certificates and CRLs required to verify another certificate. OpenSSL creates an X509_STORE_CTX object and calls X509_verify_cert( ) for you, but not by default.

OpenSSL's default behavior is to not verify peer certificates, which is the worst default behavior that any SSL implementation could possibly provide. By not verifying certificates in an SSL connection, the strength of the security provided by SSL is severely reduced, to the point where the two parties in the conversation might as well be using nothing more than a symmetric cipher with keys exchanged in the clear. Without verifying certificates, you will have security against passive eavesdroppers, but that is all. With a small amount of effort, anyone could hijack the TCP connection before the SSL session is established and act as a man-in-the-middle.

Discussion

To have OpenSSL verify a peer's certificate, you must issue a call to SSL_CTX_set_verify( ) . SSL_CTX_set_verify( ) accepts a bitmask of flags that tell OpenSSL how to deal with certificates. Depending on whether the SSL_CTX object is being used as a client or as a server, the meanings of the flags are somewhat different:

SSL_VERIFY_NONE

When the SSL_CTX object is being used in server mode, no request for a certificate is sent to the client, and the client should not send a certificate.

When the SSL_CTX object is being used in client mode, any certificate received from the server will be verified, but failure will not terminate the handshake.

This flag should never be combined with any of the others, and it should normally be used only in server mode (if it is ever used at all). When operating in client mode, you should always be verifying the server's certificate. When operating in server mode, you may not have any use for a client certificate, and requesting one may cause confusion for users. For example, if an SSL-enabled web site requests a certificate from a client, the user's browser may ask the user for a certificate to send to the server.

SSL_VERIFY_PEER

When the SSL_CTX object is being used in server mode, a request for a certificate will be sent to the client. The client may opt to ignore the request, but if a certificate is sent back, it will be verified. If the verification fails, the handshake will be terminated immediately.

When the SSL_CTX object is being used in client mode, if the server sends a certificate, it will be verified. If the verification fails, the handshake will be terminated immediately. The only time that a server would not send a certificate is when an anonymous cipher is in use. Anonymous ciphers are disabled by default. Any other flags combined with this one in client mode are ignored.

SSL_VERIFY_FAIL_IF_NO_PEER_CERT

If the SSL_CTX object is not being used in server mode or if SSL_VERIFY_PEER is not set, this flag is ignored. Use of this flag will cause the handshake to terminate immediately if the client provides no certificate.

SSL_VERIFY_CLIENT_ONCE

If the SSL_CTX object is not being used in server mode, or if SSL_VERIFY_PEER is not set, this flag is ignored. Use of this flag will prevent the server from requesting a certificate from the client in the case of a renegotiation. A certificate will still be requested during the initial handshake.

Using this knowledge of SSL_CTX_set_verify( ) and the code from Recipe 10.5, we'll build a new function, spc_create_sslctx( ) , that will create an SSL_CTX object and initialize it with secure settings. In addition to calling SSL_CTX_set_verify( ), we'll disable the SSLv2 protocol, leaving only SSLv3 and TLSv1 enabled. We want to disable SSLv2 because it is well known to be insecure. It was the first publicly released version of the protocol and was not designed or adequately reviewed by security experts before its deployment. SSLv3 was designed and reviewed by security experts, and it corrects all of the known problems in SSLv2. Finally, we'll call SSL_CTX_set_cipher_list( ) to ensure that only secure ciphers will be used.

Before we can build spc_create_sslctx( ), we need to extend and complete the implementation of the spc_x509store_t object introduced in Recipe 10.5. Some additional flags are necessary for spc_create_sslctx( ) , so we'll define those first:

SPC_X509STORE_USE_CERTIFICATE

If this flag is set, an SSL_CTX created by spc_create_sslctx( ) will be loaded with a private key and certificates to be sent to the peer if they're requested. This should always be set for a server context, but it may also be set for a client context.

SPC_X509STORE_SSL_VERIFY_NONE

This flag corresponds to OpenSSL's SSL_VERIFY_NONE flag and is used to construct the flags that are passed in the call to SSL_CTX_set_verify( ) by spc_create_sslctx( ).

SPC_X509STORE_SSL_VERIFY_PEER

This flag corresponds to OpenSSL's SSL_VERIFY_PEER flag and is used to construct the flags that are passed in the call to SSL_CTX_set_verify( ) by spc_create_sslctx( ).

SPC_X509STORE_SSL_VERIFY_FAIL_IF_NO_PEER_CERT

This flag corresponds to OpenSSL's SSL_VERIFY_FAIL_IF_NO_PEER_CERT flag and is used to construct the flags that are passed in the call to SSL_CTX_set_verify( ) by spc_create_sslctx( ).

SPC_X509STORE_SSL_VERIFY_CLIENT_ONCE

This flag corresponds to OpenSSL's SSL_VERIFY_CLIENT_ONCE flag and is used to construct the flags that are passed in the call to SSL_CTX_set_verify( ) by spc_create_sslctx( ).

SPC_X509STORE_SSL_VERIFY_MASK

This is simply a combination of all the SSL verification flags that is intended for internal use only.

We will also need an additional set of functions to add certificate and key information into the context for presenting to a peer when it is requested. The information will be used by spc_create_sslctx( ) when creating an SSL_CTX object, but only if SPC_X509STORE_USE_CERTIFICATE is set in thespc_x509store_t's flags.

void spc_x509store_setusecertfile(spc_x509store_t *spc_store, char *file) {

if (spc_store->use_certfile) free(spc_store->use_certfile);

spc_store->use_certfile = (file ? strdup(file) : 0);

}

void spc_x509store_addusecert(spc_x509store_t *spc_store, X509 *cert) {

sk_X509_push(spc_store->certs, cert);

}

void spc_x509store_setusekeyfile(spc_x509store_t *spc_store, char *file) {

if (spc_store->use_keyfile) free(spc_store->use_keyfile);

spc_store->use_keyfile = (file ? strdup(file) : 0);

}

void spc_x509store_setusekey(spc_x509store_t *spc_store, EVP_PKEY *key) {

if (spc_store->use_key) EVP_PKEY_free(key);

spc_store->use_key = key;

CRYPTO_add(&(key->references), 1, CRYPTO_LOCK_EVP_PKEY);

}

Both the certificates and the keys can be specified either as a file from which to load the information, or as preexisting OpenSSL objects of the appropriate type (X509 objects for certificates, and EVP_PKEY objects for keys). If a filename is specified, it will take precedence over a preexisting OpenSSL object. If a preexisting key object is used, it is the caller's responsibility to free it using EVP_PKEY_free( ) at any point after it is added into the spc_x509store_t object because it is reference counted, and spc_x509store_setusekey( ) increments its reference count.

When specifying the certificates to be sent to a peer (whether the peer will be a server or a client), multiple certificates may be specified. The first certificate specified should always be the certificate belonging to your program. Any additional certificates should be certificates in the chain that may be needed to verify the validity of your own certificate. This is true whether the certificates are loaded from a file and specified via spc_x509store_setusecertfile( ) , or are added to the spc_x509store_t one at a time using spc_x509store_addusecert( ) . Note also that the certificates and the required private key may be contained within the same file. For both certificate and key files, PEM format should be used, because the alternative binary ASN.1 format (also known as DER) does not allow multiple objects to be present in the same file.

At this point, spc_create_sslctx( ) has everything it needs. It takes a single argument—the spc_x509store_t object—to get its information from, and it returns a new SSL_CTX object that can be used to establish SSL-enabled connections.

#include <openssl/ssl.h>

#define SPC_X509STORE_USE_CERTIFICATE 0x04

#define SPC_X509STORE_SSL_VERIFY_NONE 0x10

#define SPC_X509STORE_SSL_VERIFY_PEER 0x20

#define SPC_X509STORE_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x40

#define SPC_X509STORE_SSL_VERIFY_CLIENT_ONCE 0x80

#define SPC_X509STORE_SSL_VERIFY_MASK 0xF0

SSL_CTX *spc_create_sslctx(spc_x509store_t *spc_store) {

int i, verify_flags = 0;

SSL_CTX *ctx = 0;

X509_STORE *store = 0;

spc_x509verifycallback_t verify_callback;

if (!(ctx = SSL_CTX_new(SSLv23_method( )))) goto error_exit;

if (!(store = spc_create_x509store(spc_store))) goto error_exit;

SSL_CTX_set_cert_store(ctx, store); store = 0;

SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);

SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");

if (!(verify_callback = spc_store->callback))

verify_callback = spc_verify_callback;

if (!(spc_store->flags & SPC_X509STORE_SSL_VERIFY_MASK))

verify_flags = SSL_VERIFY_NONE;

else {

if (spc_store->flags & SPC_X509STORE_SSL_VERIFY_NONE)

verify_flags |= SSL_VERIFY_NONE;

if (spc_store->flags & SPC_X509STORE_SSL_VERIFY_PEER)

verify_flags |= SSL_VERIFY_PEER;

if (spc_store->flags & SPC_X509STORE_SSL_VERIFY_FAIL_IF_NO_PEER_CERT)

verify_flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;

if (spc_store->flags & SPC_X509STORE_SSL_VERIFY_CLIENT_ONCE)

verify_flags |= SSL_VERIFY_CLIENT_ONCE;

}

SSL_CTX_set_verify(ctx, verify_flags, verify_callback);

if (spc_store->flags & SPC_X509STORE_USE_CERTIFICATE) {

if (spc_store->use_certfile)

SSL_CTX_use_certificate_chain_file(ctx, spc_store->use_certfile);

else {

SSL_CTX_use_certificate(ctx, sk_X509_value(spc_store->use_certs, 0));

for (i = 1; i < sk_X509_num(spc_store->use_certs); i++) {

SSL_CTX_add_extra_chain_cert(ctx, sk_X509_value(spc_store->use_certs, i));

}

}

if (spc_store->use_keyfile) {

SSL_CTX_use_PrivateKey_file(ctx, spc_store->use_keyfile, SSL_FILETYPE_PEM);

} else {

if (spc_store->use_key)

SSL_CTX_use_PrivateKey(ctx, spc_store->use_key);

}

}

SSL_CTX_set_app_data(ctx, spc_store);

return ctx;

error_exit:

if (store) X509_STORE_free(store); /* not ref counted */

if (ctx) SSL_CTX_free(ctx); /* ref counted */

return 0;

}

See Also

Recipe 10.5

10.8. Adding Hostname Checking to Certificate Verification

Problem

You have a certificate that has passed initial verification checks as described in Recipe 10.4. Now you want to make sure that it was issued to the host that is claiming ownership of it.

Solution

A certificate often contains a commonName field, and many certificates contain a subjectAltName extension, although neither is required. Normally, when a server presents a certificate, the commonly accepted convention is for either the commonName or the subjectAltName to contain the hostname of the server that is presenting it. Often, if both fields are present, they will contain the same information. If both fields are present and they contain different information, it is most likely because the commonName field contains some information other than a hostname. Even if both fields contain hostnames, the subjectAltName field should always take precedence over the commonName field. Certificate extensions were added to the X.509 standard in Version 3, so older certificates use the commonName field, while newer ones use the subjectAltName extension.

Discussion

The basic certificate verification, as described in Recipe 10.4, is the hard part of verifying a certificate. It ensures that the certificate is valid for the dates it was issued (i.e., the current date is within the certificate's start and end dates), it has not been revoked (provided that you have the relevant CRL), and it was signed by a trusted CA. Now you must make sure that the certificate is valid for the site that is claiming ownership of it. If you do not, any site could present you with Microsoft's certificate, claiming it as their own, and it would successfully verify.

When new certificates are issued, use of the subjectAltName extension is preferred over use of the commonName field, so that should be checked first. If no subjectAltName extension is present, the commonName field should be checked instead. When a subjectAltName is present but does not match, verification of the certificate should fail. Likewise, if the commonName field is checked and it does not match, verification of the certificate should fail. In either case, communication with the peer should be terminated if verification of its certificate fails.

TIP

What we have described thus far, particularly in regard to the subjectAltName extension, is simplified a great deal. The subjectAltName extension is actually a container that may contain several different fields, each one responsible for different information. For our purposes, and the purposes of verifying the hostname within a certificate, we are only interested in the dnsName field. When we say that a subjectAltName extension is either present or absent, we are actually concerned with the presence or absence of the dnsName field within the subjectAltName field. In other words, if a subjectAltName extension is present but does not contain a dnsName field, we say that the subjectAltName extension is absent.

If you are using OpenSSL, you will normally have a certificate as an X509 object. The following code will check the hostname in that object:

#include <string.h>

#include <openssl/conf.h>

#include <openssl/x509v3.h>

int spc_verify_cert_hostname(X509 *cert, char *hostname) {

int extcount, i, j, ok = 0;

char name[256];

X509_NAME *subj;

const char *extstr;

CONF_VALUE *nval;

unsigned char *data;

X509_EXTENSION *ext;

X509V3_EXT_METHOD *meth;

STACK_OF(CONF_VALUE) *val;

if ((extcount = X509_get_ext_count(cert)) > 0) {

for (i = 0; !ok && i < extcount; i++) {

ext = X509_get_ext(cert, i);

extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));

if (!strcasecmp(extstr, "subjectAltName")) {

if (!(meth = X509V3_EXT_get(ext))) break;

data = ext->value->data;

val = meth->i2v(meth, meth->d2i(0, &data, ext->value->length), 0);

for (j = 0; j < sk_CONF_VALUE_num(val); j++) {

nval = sk_CONF_VALUE_value(val, j);

if (!strcasecmp(nval->name, "DNS") && !strcasecmp(nval->value, hostname)) {

ok = 1;

break;

}

}

}

}

}

if (!ok && (subj = X509_get_subject_name(cert)) &&

X509_NAME_get_text_by_NID(subj, NID_commonName, name, sizeof(name)) > 0) {

name[sizeof(name) - 1] = '\0';

if (!strcasecmp(name, hostname)) ok = 1;

}

return ok;

}

If you are using CryptoAPI on Windows, you will normally have a certificate as a CERT_CONTEXT object. The following code checks the hostname in that object:

#include <windows.h>

#include <wincrypt.h>

static LPWSTR fold_wide(LPWSTR str) {

int len;

LPWSTR wstr;

if (!(len = FoldStringW(MAP_PRECOMPOSED, str, -1, 0, 0))) return 0;

if (!(wstr = (LPWSTR)LocalAlloc(LMEM_FIXED, len * sizeof(WCHAR))))

return 0;

if (!FoldStringW(MAP_PRECOMPOSED, str, -1, wstr, len)) {

LocalFree(wstr);

return 0;

}

return wstr;

}

static LPWSTR make_wide(LPCTSTR str) {

#ifndef UNICODE

int len;

LPWSTR wstr;

if (!(len = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0)))

return 0;

if (!(wstr = (LPWSTR)LocalAlloc(LMEM_FIXED, len * sizeof(WCHAR))))

return 0;

if (!MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len)) {

LocalFree(wstr);

return 0;

}

return wstr;

#else

return fold_wide(str);

#endif

}

BOOL SpcVerifyCertHostName(PCCERT_CONTEXT pCertContext, LPCTSTR hostname) {

BOOL bResult = FALSE;

DWORD cbStructInfo, dwCommonNameLength, i;

LPSTR szOID;

LPVOID pvStructInfo;

LPWSTR lpszCommonName, lpszDNSName, lpszHostName, lpszTemp;

CERT_EXTENSION *pExtension;

CERT_ALT_NAME_INFO *pNameInfo;

if (!(lpszHostName = make_wide(hostname))) return FALSE;

/* Try SUBJECT_ALT_NAME2 first - it supercedes SUBJECT_ALT_NAME */

szOID = szOID_SUBJECT_ALT_NAME2;

pExtension = CertFindExtension(szOID, pCertContext->pCertInfo->cExtension,

pCertContext->pCertInfo->rgExtension);

if (!pExtension) {

szOID = szOID_SUBJECT_ALT_NAME;

pExtension = CertFindExtension(szOID, pCertContext->pCertInfo->cExtension,

pCertContext->pCertInfo->rgExtension);

}

if (pExtension && CryptDecodeObject(X509_ASN_ENCODING, szOID,

pExtension->Value.pbData, pExtension->Value.cbData, 0, 0, &cbStructInfo)) {

if ((pvStructInfo = LocalAlloc(LMEM_FIXED, cbStructInfo)) != 0) {

CryptDecodeObject(X509_ASN_ENCODING, szOID, pExtension->Value.pbData,

pExtension->Value.cbData, 0, pvStructInfo, &cbStructInfo);

pNameInfo = (CERT_ALT_NAME_INFO *)pvStructInfo;

for (i = 0; !bResult && i < pNameInfo->cAltEntry; i++) {

if (pNameInfo->rgAltEntry[i].dwAltNameChoice = = CERT_ALT_NAME_DNS_NAME) {

if (!(lpszDNSName = fold_wide(pNameInfo->rgAltEntry[i].pwszDNSName)))

break;

if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, lpszDNSName,

-1, lpszHostName, -1) = = CSTR_EQUAL)

bResult = TRUE;

LocalFree(lpszDNSName);

}

}

LocalFree(pvStructInfo);

LocalFree(lpszHostName);

return bResult;

}

}

/* No subjectAltName extension -- check commonName */

dwCommonNameLength = CertGetNameStringW(pCertContext, CERT_NAME_ATTR_TYPE, 0,

szOID_COMMON_NAME, 0, 0);

if (!dwCommonNameLength) {

LocalFree(lpszHostName);

return FALSE;

}

lpszTemp = (LPWSTR)LocalAlloc(LMEM_FIXED, dwCommonNameLength * sizeof(WCHAR));

if (lpszTemp) {

CertGetNameStringW(pCertContext, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME,

lpszTemp, dwCommonNameLength);

if ((lpszCommonName = fold_wide(lpszTemp)) != 0) {

if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, lpszCommonName,

-1, lpszHostName, -1) = = CSTR_EQUAL)

bResult = TRUE;

LocalFree(lpszCommonName);

}

LocalFree(lpszTemp);

}

LocalFree(lpszHostName);

return bResult;

}

Unfortunately, if you are using a version of the Microsoft Windows Platform SDK older than the .NET version, you will experience difficulties compiling and linking this code into your program. The older wincrypt.h header file and crypt32.lib import library are missing the definitions required to use CertGetNameStringW( ) , even though they are documented to be available in versions prior to .NET. The definitions required for your code are:

#ifndef CERT_NAME_ATTR_TYPE

WINCRYPT32API

DWORD

WINAPI

CertGetNameStringW(

IN PCCERT_CONTEXT pCertIntext,

IN DWORD dwType,

IN DWORD dwFlags,

IN void *pvTypePara,

OUT OPTIONAL LPWSTR pszNameString,

IN DWORD cchNameString

);

#define CERT_NAME_ATTR_TYPE 3

#endif

CertGetNameStringW( ) is exported from all versions of crypt32.dll that are included with Microsoft Internet Explorer 3.02 or later. You may run into problems linking, however, because the import is missing from crypt32.lib. In our testing, we have experienced no problems using thecrypt32.lib distributed with the latest Microsoft Windows Platform SDK. Unfortunately, we have been unable to find an alternative method of obtaining the contents of the commonName field in a certificate other than using this function.

See Also

Recipe 10.4

10.9. Using a Whitelist to Verify Certificates

Problem

You have a certificate that you want to compare against a list of known good certificates.

Solution

The average certificate is generally small, often under 2 KB in size. Because a certificate is both reasonably small and cannot be undetectably modified once it has been signed by a CA, it might seem reasonable to do a byte-for-byte comparison of the certificate with a list of certificates. One problem with this approach is that if you are comparing a certificate against a sizable list, performing the comparisons can become a time-consuming operation. The other problem is that of storing all the certificates in the list against which the certificate to verify will be compared. A better way is to compute the fingerprint of each certificate and store the fingerprint instead of the entire certificate. Fingerprints are generally only 16 or 20 bytes in size, depending on the message digest algorithm used to compute them.

Discussion

In OpenSSL, computing the fingerprint of a certificate is as simple as a single call to X509_digest( ) . Comparing fingerprints is done with a byte-for-byte comparison. The only work you really need to do is to decide on which message digest algorithm to use. MD5 is still the most popular algorithm, but we recommend using something stronger, such as SHA1. MD5 only has a 16-byte output, and there are known attacks against it, whereas SHA1 has a 20-byte output, and there are no known attacks against it.

#include <string.h>

#include <openssl/evp.h>

#include <openssl/ssl.h>

#include <openssl/x509.h>

int spc_fingerprint_cert(X509 *cert, EVP_MD *digest, unsigned char *fingerprint,

int *fingerprint_length) {

if (*fingerprint_length < EVP_MD_size(digest))

return 0;

if (!X509_digest(cert, digest, fingerprint, fingerprint_length))

return 0;

return *fingerprint_length;

}

int spc_fingerprint_equal(unsigned char *fp1, int fp1len, unsigned char *fp2,

int fp2len) {

return (fp1len = = fp2len && !memcmp(fp1, fp2, fp1len));

}

Using CryptoAPI on Windows, computing the fingerprint of a certificate is also very simple. A single call to CryptHashCertificate( ) with the certificate's CERT_CONTEXT object is all that's necessary. The following implementation of SpcFingerPrintCert( ) makes two calls so that it can verify that the buffer is big enough to hold the hash.

#include <windows.h>

#include <wincrypt.h>

DWORD SpcFingerPrintCert(PCCERT_CONTEXT pCertContext, ALG_ID Algid,

BYTE *pbFingerPrint, DWORD *pcbFingerPrint) {

DWORD cbComputedHash;

if (!CryptHashCertificate(0, Algid, 0, pCertContext->pbCertEncoded,

pCertContext->cbCertEncoded, 0, &cbComputedHash))

return 0;

if (*pcbFingerPrint < cbComputedHash) return 0;

CryptHashCertificate(0, Algid, 0, pCertContext->pbCertEncoded,

pCertContext->cbCertEncoded, pbFingerPrint,

pcbFingerPrint);

return *pcbFingerPrint;

}

int SpcFingerPrintEqual(BYTE *pbFingerPrint1, DWORD cbFingerPrint1,

BYTE *pbFingerPrint2, DWORD cbFingerPrint2) {

return (cbFingerPrint1 = = cbFingerPrint2 &&

!memcmp(pbFingerPrint1, pbFingerPrint2, cbFingerPrint1));

}

You can use a whitelist in place of normal certificate verification routines. Whitelists are most often useful in servers that want to authenticate clients, rather than the other way around, but they can be used either way. In server mode, you can use the SSL_VERIFY_PEER flag to request a certificate from the client, but remember that the client does not have to supply a certificate in response to a request. If you want to require that the client respond, you also need to use the SSL_VERIFY_FAIL_IF_NO_PEER_CERT flag so that the connection is terminated if the client does not send a certificate.

The downside to using these flags is that OpenSSL will attempt to verify the certificate on its own. With a little trickery, we can short-circuit OpenSSL's certificate verification routines and do a little post-connection verification of our own. We will do this by setting up a verify callback function that always returns success. The verify callback is called for each certificate in the chain when verifying a certificate. It is called with the X509_STORE_CTX containing everything relevant, as well as a boolean indicator of whether OpenSSL has determined the certificate to be valid or not. Typically, the callback will return the same verification status, but it is not required. The callback can reverse the decision that OpenSSL has made.

int spc_whitelist_callback(int ok, X509_STORE_CTX *store) {

return 1;

}

Once the connection has been established, we can get a copy of the peer's certificate, compute its fingerprint, and compare it against the fingerprints we have in our list. The list can be stored in memory, in a disk file, on a flash memory card, or on some other medium. How the list is stored is irrelevant; what is important is the comparison of fingerprints. The functions shown in the previous code are flexible in that they allow you to choose any message digest algorithm you like. Note, though, that if you are always using the same ones, the functions can be simplified, and you need not keep track of the fingerprint length because you know that a message digest is a fixed size (MD5 is 16 bytes; SHA1 is 20 bytes). The following snippet of code roughly demonstrates the work that needs to be done to employ whitelist-based certificate verification:

int fingerprint_length;

SSL *ssl;

EVP_MD *digest;

SSL_CTX *ctx;

unsigned char fingerprint[EVP_MAX_MD_SIZE];

spc_x509store_t spc_store;

spc_init_x509store(&spc_store);

spc_x509store_setcallback(&spc_store, spc_whitelist_callback);

spc_x509store_setflags(&spc_store, SPC_X509STORE_SSL_VERIFY_PEER |

SPC_X509STORE_SSL_VERIFY_FAIL_IF_NO_PEER_CERT);

ctx = spc_create_sslctx(&spc_store);

/* use the ctx to establish a connection. This will yield an SSL object */

cert = SSL_get_peer_certificate(ssl);

digest = EVP_sha1( );

fingerprint_length = sizeof(fingerprint);

spc_fingerprint_cert(cert, digest, fingerprint, &fingerprint_length);

/* use the fingerprint to compare against the list of known cert fingerprints */

10.10. Obtaining Certificate Revocation Lists with OpenSSL

Problem

You have a certificate that you want to verify, as well as the certificate that was used to issue it, but you need to check the issuing authority's CRL to make sure that the certificate has not been revoked. We cover how to use a CRL once you have it in Recipe 10.5—but how do you get it in the first place?

Solution

All CAs should publish a CRL for each certificate used for issuing certificates, but many do not seem to. In fact, most CAs make it very difficult to find the CRLs they do publish, so it is easy to come to the conclusion that they do not publish a CRL at all. It turns out that some CAs do not publish a CRL, but the most prominent of CAs all do. Unfortunately, the CAs that do make it easy to find their CRLs are in the minority. We have spent a sizable amount of time attempting to track down CRLs for each of the certificates we have listed in Recipe 10.3, as well as numerous others with which we had no success. We have also managed to find many CRLs for which we were unable to find matching issuing certificates, but we have omitted them here. Many of them can be found at http://www.openvalidation.org.

Note that many CAs require acceptance of a licensing agreement before you're allowed to download their CRLs. You should make sure to check the information that we provide here before you use it, to ensure that you have the legal right to use the data and that the CA has not changed the location of their URLs since this book went to press. We have found many certificates that contain cRLDistributionPoints extensions in them where the URL was no longer valid. It may be that the URLs are invalid because no CRL has ever been issued; however, to avoid any possible confusion, it would be better for these CAs to issue an empty CRL.

Discussion

To obtain a CRL, first check the certificate and its issuing certificate for a cRLDistributionPoints extension that contains a URI GeneralName. This extension is defined in RFC 3280, and it specifies a way for CAs to communicate the location of the CRL that corresponds to the certificate used to issue another certificate. Unfortunately, this extension is defined as being optional, and most root CAs do not use it.

#include <string.h>

#include <unistd.h>

#include <sys/time.h>

#include <openssl/conf.h>

#include <openssl/ocsp.h>

#include <openssl/ssl.h>

#include <openssl/x509v3.h>

typedef struct {

char *name;

unsigned char *fingerprint;

unsigned int fingerprint_length;

char *crl_uri;

char *ocsp_uri;

} spc_cacert_t;

spc_cacert_t *spc_lookup_cacert(X509 *cert);

static char *get_distribution_point(X509 *cert) {

int extcount, i, j;

const char *extstr;

CONF_VALUE *nval;

unsigned char *data;

X509_EXTENSION *ext;

X509V3_EXT_METHOD *meth;

STACK_OF(CONF_VALUE) *val;

if ((extcount = X509_get_ext_count(cert)) > 0) {

for (i = 0; i < extcount; i++) {

ext = X509_get_ext(cert, i);

extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));

if (strcasecmp(extstr, "crlDistributionPoints")) continue;

if (!(meth = X509V3_EXT_get(ext))) break;

data = ext->value->data;

val = meth->i2v(meth, meth->d2i(0, &data, ext->value->length), 0);

for (j = 0; j < sk_CONF_VALUE_num(val); j++) {

nval = sk_CONF_VALUE_value(val, j);

if (!strcasecmp(nval->name, "URI"))

return strdup(nval->value);

}

}

}

return 0;

}

char *spc_getcert_crlurl(X509 *cert, X509 *issuer, int lookup_only) {

char *uri;

spc_cacert_t *cacert;

if (!lookup_only) {

if (cert && (uri = get_distribution_point(cert)) != 0) return uri;

if (issuer && (uri = get_distribution_point(issuer)) != 0) return uri;

}

/* Get the fingerprint of the cert's issuer, and look it up in a table */

if (issuer) {

if (!(cacert = spc_lookup_cacert(issuer))) return 0;

return (cacert->crl_uri ? strdup(cacert->crl_uri) : 0);

}

return 0;

}

If neither the certificate we are checking nor the certificate's issuing certificate contains a cRLDistributionPoints extension that we can use, we will fall back to looking up the issuing certificate's fingerprint in a table that we have built from the information presented in Recipe 10.3:

static spc_cacert_t lookup_table[ ] = {

{ "Equifax Secure Certificate Authority",

"\x67\xcb\x9d\xc0\x13\x24\x8a\x82\x9b\xb2\x17\x1e\xd1\x1b\xec\xd4", 16,

"http://crl.geotrust.com/crls/secureca.crl",

},

{ "Equifax Secure Global eBusiness CA-1",

"\x8f\x5d\x77\x06\x27\xc4\x98\x3c\x5b\x93\x78\xe7\xd7\x7d\x9b\xcc", 16,

"http://crl.geotrust.com/crls/globalca1.crl",

},

{ "Equifax Secure eBusiness CA-1",

"\x64\x9c\xef\x2e\x44\xfc\xc6\x8f\x52\x07\xd0\x51\x73\x8f\xcb\x3d", 16,

"http://crl.geotrust.com/crls/ebizca1.crl",

},

{ "Equifax Secure eBusiness CA-2",

"\xaa\xbf\xbf\x64\x97\xda\x98\x1d\x6f\xc6\x08\x3a\x95\x70\x33\xca", 16,

"http://crl.geotrust.com/crls/ebiz.crl",

},

{ "RSA Data Security Secure Server CA (VeriSign)",

"\x74\x7b\x82\x03\x43\xf0\x00\x9e\x6b\xb3\xec\x47\xbf\x85\xa5\x93", 16,

"http://crl.verisign.com/RSASecureServer.crl", "http://ocsp.verisign.com/",

},

{ "Thawte Server CA",

"\xc5\x70\xc4\xa2\xed\x53\x78\x0c\xc8\x10\x53\x81\x64\xcb\xd0\x1d", 16,

"https://www.thawte.com/cgi/lifecycle/getcrl.crl?skeyid=%07%15%28mps%AA"

"%B2%8A%7C%0F%86%CE8%93%008%05%8A%B1",

},

{ "TrustCenter Class 1 CA",

"\x8d\x26\xff\x2f\x31\x6d\x59x\29\xdd\xe6\x36\xa7\xe2\xce\x64\x25", 16,

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class1.crl?Page=GetCrl"

"&crl=2",

},

{ "TrustCenter Class 2 CA",

"\xb8\x16\x33\x4c\x4c\x4c\xf2\xd8\xd3\x4d\x06\xb4\xa6\x58\x40\x03", 16,

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class2.crl?Page=GetCrl"

"&crl=3",

},

{ "TrustCenter Class 3 CA",

"\x5f\x94\x4a\x73\x22\xb8\xf7\xd1\x31\xec\x59\x39\xf7\x8e\xfe\x6e", 16,

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class3.crl?Page=GetCrl"

"&crl=4",

},

{ "TrustCenter Class 4 CA",

"\x0e\xfa\x4b\xf7\xd7\x60\xcd\x65\xf7\xa7\x06\x88\x57\x98\x62\x39", 16,

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class4.crl?Page=GetCrl"

"&crl=5",

},

{ "The USERTRUST Network - UTN-UserFirst-Object",

"\xa7\xf2\xe4\x16\x06\x41\x11\x60\x30\x6b\x9c\xe3\xb4\x9c\xb0\xc9", 16,

"http://crl.usertrust.com/UTN-UserFirst-Object.crl",

},

{ "The USERTRUST Network - UTN-UserFirst-Network Applications",

"\xbf\x60\x59\xa3\x5b\xba\xf6\xa7\x76\x42\xda\x6f\x1a\x7b\x50\xcf", 16,

"http://crl.usertrust.com/UTN-UserFirst-NetworkApplications.crl",

},

{ "The USERTRUST Network - UTN-UserFirst-Hardware",

"\x4c\x56\x41\xe5\x0d\xbb\x2b\xe8\xca\xa3\xed\x18\x08\xad\x43\x39", 16,

"http://crl.usertrust.com/UTN-UserFirst-Hardware.crl",

},

{ "The USERTRUST Network - UTN-UserFirst-Client Authentication and Email",

"\xd7\x34\x3d\xef\x1d\x27\x09\x28\xe1\x31\x02\x5b\x13\x2b\xdd\xf7", 16,

"http://crl.usertrust.com/UTN-UserFirst-ClientAuthenticationandEmail.crl",

},

{ "The USERTRUST Network - UTN - DataCorp SGC",

"\xb3\xa5\x3e\x77\x21\x6d\xac\x4a\xc0\xc9\xfb\xd5\x41\x3d\xca\x06", 16,

"http://crl.usertrust.com/UTN-DataCorpSGC.crl",

},

{ "ValiCert Class 1 Policy Validation Authority",

"\x65\x58\xab\x15\xad\x57\x6c\x1e\xa8\xa7\xb5\x69\xac\xbf\xff\xeb", 16,

"http://www.valicert.com/repository/ValiCert%20Calss%201%20Policy%20Val"

"idation%20Authority.crl",

},

{ "VeriSign Class 1 Public PCA (2020-01-07)",

"\x51\x86\xe8\x1f\xbc\xb1\xc3\x71\xb5\x18\x10\xdb\x5f\xdc\xf6\x20", 16,

"http://crl.verisign.com/pca1.1.1.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 1 Public PCA (2028-08-01)",

"\x97\x60\xe8\x57\x5f\xd3\x50\x47\xe5\x43\x0c\x94\x36\x8a\xb0\x62", 16,

"http://crl.verisign.com/pca1.1.1.crl",

"http://ocsp.verisign.com/",

},

{ "VeriSign Class 1 Public PCA G2 (2018-05-18)",

"\xf2\x7d\xe9\x54\xe4\xa3\x22\x0d\x76\x9f\xe7\x0b\xbb\xb3\x24\x2b", 16,

"http://crl.verisign.com/pca1-g2.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 1 Public PCA G2 (2028-08-01)",

"\xdb\x23\x3d\xf9\x69\xfa\x4b\xb9\x95\x80\x44\x73\x5e\x7d\x41\x83", 16,

"http://crl.verisign.com/pca1-g2.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 2 Public PCA (2004-01-07)",

"\xec\x40\x7d\x2b\x76\x52\x67\x05\x2c\xea\xf2\x3a\x4f\x65\xf0\xd8", 16,

"http://crl.verisign.com/pca2.1.1.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 2 Public PCA (2028-08-01)",

"\xb3\x9c\x25\xb1\xc3\x2e\x32\x53\x80\x15\x30\x9d\x4d\x02\x77\x3e", 16,

"http://crl.verisign.com/pca2.1.1.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 2 Public PCA G2 (2018-05-18)",

"\x74\xa8\x2c\x81\x43\x2b\x35\x60\x9b\x78\x05\x6b\x58\xf3\x65\x82", 16,

"http://crl.verisign.com/pca2-g2.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 2 Public PCA G2 (2028-08-01)",

"\x2d\xbb\xe5\x25\xd3\xd1\x65\x82\x3a\xb7\x0e\xfa\xe6\xeb\xe2\xe1", 16,

"http://crl.verisign.com/pca2-g2.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 3 Public PCA (2004-01-07)",

"\x78\x2a\x02\xdf\xdb\x2e\x14\xd5\xa7\x5f\x0a\xdf\xb6\x8e\x9c\x5d", 16,

"http://crl.verisign.com/pca3.1.1.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 3 Public PCA (2028-08-01)",

"\x10\xfc\x63\x5d\xf6\x26\x3e\x0d\xf3\x25\xbe\x5f\x79\xcd\x67\x67", 16,

"http://crl.verisign.com/pca3.1.1.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 3 Public PCA G2 (2018-05-18)",

"\xc4\x63\xab\x44\x20\x1c\x36\xe4\x37\xc0\x5f\x27\x9d\x0f\x6f\x6e", 16,

"http://crl.verisign.com/pca3-g2.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Class 3 Public PCA G2 (2028-08-01)",

"\xa2\x33\x9b\x4c\x74\x78\x73\xd4\x6c\xe7\xc1\xf3\x8d\xcb\x5c\xe9", 16,

"http://crl.verisign.com/pca3-g2.crl", "http://ocsp.verisign.com/",

},

{ "VeriSign Commercial Software Publishers CA",

"\xdd\x75\x3f\x56\xbf\xbb\xc5\xa1\x7a\x15\x53\xc6\x90\xf9\xfb\xcc", 16,

"http://crl.verisign.com/Class3SoftwarePublishers.crl",

"http://ocsp.verisign.com/",

},

{ "VeriSign Individual Software Publishers CA",

"\x71\x1f\x0e\x21\xe7\xaa\xea\x32\x3a\x66\x23\xd3\xab\x50\xd6\x69", 16,

"http://crl.verisign.com/Class2SoftwarePublishers.crl",

"http://ocsp.verisign.com/",

},

{ 0, 0, 0, 0, 0 },

};

spc_cacert_t *spc_lookup_cacert(X509 *cert) {

spc_cacert_t *entry;

unsigned int fingerprint_length;

unsigned char fingerprint[EVP_MAX_MD_SIZE];

fingerprint_length = EVP_MAX_MD_SIZE;

if (!X509_digest(cert, EVP_md5( ), fingerprint, &fingerprint_length)) return 0;

for (entry = lookup_table; entry->name; entry++) {

if (entry->fingerprint_length != fingerprint_length) continue;

if (!memcmp(entry->fingerprint, fingerprint, fingerprint_length)) return entry;

}

return 0;

}

Once we have the URL of the CRL we want, it is a simple matter to retrieve it using the HTTP protocol. OpenSSL does not provide even the simplest of HTTP clients, so we must speak the bare minimum ourselves to connect to the server and retrieve the data.

static void *retrieve_webdata(char *uri, int *datalen, spc_x509store_t *store) {

int bytes, content_length = 0, headerlen = 0, sd, ssl;

BIO *conn = 0;

SSL *ssl_ptr;

char buffer[1024];

char *headers = 0, *host = 0, *path = 0, *port = 0, *tmp;

void *data = 0;

fd_set rmask, wmask;

SSL_CTX *ctx = 0;

*datalen = 0;

if (!OCSP_parse_url(uri, &host, &port, &path, &ssl)) goto end_error;

if (!(conn = spc_connect(host, atoi(port), ssl, store, &ctx))) goto end_error;

/* Send the request for the data */

BIO_printf(conn, "GET %s HTTP/1.0\r\nConnection: close\r\n\r\n", path);

/* Put the socket into non-blocking mode */

BIO_get_fd(conn, &sd);

BIO_socket_nbio(sd, 1);

if (ssl) {

BIO_get_ssl(conn, &ssl_ptr);

SSL_set_mode(ssl_ptr, SSL_MODE_ENABLE_PARTIAL_WRITE |

SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);

}

/* Loop reading data from the socket until we've got all of the headers */

for (;;) {

FD_ZERO(&rmask);

FD_SET(sd, &rmask);

FD_ZERO(&wmask);

if (BIO_should_write(conn)) FD_SET(sd, &wmask);

if (select(FD_SETSIZE, &rmask, &wmask, 0, 0) <= 0) continue;

if (FD_ISSET(sd, &wmask)) BIO_write(conn, buffer, 0);

if (FD_ISSET(sd, &rmask)) {

if ((bytes = BIO_read(conn, buffer, sizeof(buffer))) <= 0) {

if (BIO_should_retry(conn)) continue;

goto end_error;

}

if (!(headers = (char *)realloc((tmp = headers), headerlen + bytes))) {

headers = tmp;

goto end_error;

}

memcpy(headers + headerlen, buffer, bytes);

headerlen += bytes;

if ((tmp = strstr(headers, "\r\n\r\n")) != 0) {

*(tmp + 2) = '\0';

*datalen = headerlen - ((tmp + 4) - headers);

headerlen -= (*datalen + 2);

if (*datalen > 0) {

if (!(data = (char *)malloc(*datalen))) goto end_error;

memcpy(data, tmp + 4, *datalen);

}

break;

}

}

}

/* Examine the headers to determine whether or not to continue. If we are to

* continue, look for a content-length header to find out how much data we're

* going to get. If there is no content-length header, we'll have to read

* until the remote server closes the connection.

*/

if (!strncasecmp(headers, "HTTP/1.", 7)) {

if (!(tmp = strchr(headers, ' '))) goto end_error;

if (strncmp(tmp + 1, "200 ", 4) && strncmp(tmp + 1, "200\r\n", 5))

goto end_error;

for (tmp = strstr(headers, "\r\n"); tmp; tmp = strstr(tmp + 2, "\r\n")) {

if (strncasecmp(tmp + 2, "content-length: ", 16)) continue;

content_length = atoi(tmp + 18);

break;

}

} else goto end_error;

/* Continuously read and accumulate data from the remote server. Finish when

* we've read up to the content-length that we received. If we didn't receive

* a content-length, read until the remote server closes the connection.

*/

while (!content_length || *datalen < content_length) {

FD_ZERO(&rmask);

FD_SET(sd, &rmask);

FD_ZERO(&wmask);

if (BIO_should_write(conn)) FD_SET(sd, &wmask);

if (select(FD_SETSIZE, &rmask, &wmask, 0, 0) <= 0) continue;

if (FD_ISSET(sd, &wmask)) BIO_write(conn, buffer, 0);

if (FD_ISSET(sd, &rmask))

if ((bytes = BIO_read(conn, buffer, sizeof(buffer))) <= 0) {

if (BIO_should_retry(conn)) continue;

break;

}

if (!(data = realloc((tmp = data), *datalen + bytes))) {

data = tmp;

goto end_error;

}

memcpy((char *)data + *datalen, buffer, bytes);

*datalen += bytes;

}

if (content_length && *datalen != content_length) goto end_error;

goto end;

end_error:

if (data) { free(data); data = 0; *datalen = 0; }

end:

if (headers) free(headers);

if (conn) BIO_free_all(conn);

if (host) OPENSSL_free(host);

if (port) OPENSSL_free(port);

if (path) OPENSSL_free(path);

if (ctx) SSL_CTX_free(ctx);

return data;

}

With the data that has been retrieved from the server, we can create an OpenSSL X509_CRL object. We assume that the data retrieved from the server will be in DER format, which is the format returned by every server we have encountered (see Recipe 7.16). The DER format is more portable because not everyone supports PEM format. It is also a more compact format for transfer because it does not include any headers or base64 encoding. The OpenSSL function d2i_X509_CRL_bio( ) is used to create the X509_CRL object using a memory base BIO object created with BIO_new_mem_buf( ) .

X509_CRL *spc_retrieve_crl(X509 *cert, X509 *issuer, spc_x509store_t *store) {

BIO *bio = 0;

int datalen, our_store;

char *uri = 0, *uri2 = 0;

void *data = 0;

X509_CRL *crl = 0;

if ((our_store = (!store)) != 0) {

if (!(store = (spc_x509store_t *)malloc(sizeof(spc_x509store_t)))) return 0;

spc_init_x509store(store);

spc_x509store_addcert(store, issuer);

}

if (!(uri = spc_getcert_crlurl(cert, issuer, 0))) goto end;

if (!(data = retrieve_webdata(uri, &datalen, store))) {

uri2 = spc_getcert_crlurl(cert, issuer, 1);

if (!uri2 || !strcmp(uri, uri2)) goto end;

if (!(data = retrieve_webdata(uri2, &datalen, store))) goto end;

}

bio = BIO_new_mem_buf(data, datalen);

crl = d2i_X509_CRL_bio(bio, 0);

end:

if (bio) BIO_free(bio);

if (data) free(data);

if (uri) free(uri);

if (uri2) free(uri2);

if (store && our_store) {

spc_cleanup_x509store(store);

free(store);

}

return crl;

}

In this recipe, we have used a number of functions from Recipe 9.1, Recipe 10.5, Recipe 10.7, and Recipe 10.8. These functions provide us with network connectivity and certificate verification. We will only need the latter if we need to connect to an SSL-enabled web server to retrieve the CRL, and it will all be handled by the network connectivity functions.

Note that we construct an X509_STORE object that contains any system-wide trusted certificates as well as the issuing certificate for which we're getting the CRL. For simplicity, we assume that an SSL-enabled server that is serving the CRL will present this same certificate. In practice, however, that is not always a safe assumption. Our testing indicates that this assumption frequently holds true, but there is a problem: if we are retrieving the CRL from an SSL-enabled server, we have to trust that the peer's certificate has not been revoked. Fortunately, this is a reasonably safe assumption for us to make here because if a CA's signing certificate has been revoked for some reason, there are much bigger problems.[2]

We have provided code here to retrieve CRLs using HTTP because it is simple to implement and is commonly used by CAs to distribute their CRLs; however, LDAP is also commonly used for CRL distribution. Unfortunately, owing to the complexity of the solution, we don't include a detailed discussion of that topic in this book.

LDAP is commonly used instead of the Directory Access Protocol (DAP) simply because it is less cumbersome. Unfortunately, it lacks some of the features that make storing CRLs and other PKI objects in a directory attractive. In particular, LDAP does not support location transparency and uses referrals instead, but few LDAP client implementations actually support referrals correctly. Because of the lack of location transparency, LDAP does not scale as well as DAP, and it makes it more difficult for CAs to interoperate.

From the standpoint of the client, using LDAP to retrieve CRLs adds complexity without much benefit over other, simpler protocols such as HTTP. We feel that it's important to be aware of how common the use of LDAP is, and we leave it to you to decide whether to include support for it in your own programs.

See Also

§ RFC 3280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile

§ Recipe 7.16, Recipe 9.1, Recipe 10.1, Recipe 10.3, Recipe 10.5, Recipe 10.7, Recipe 10.8


[2] If the CA's signing certificate has been revoked, it is still acceptable to trust the signature on the CRL if and only if the signing certificate is also in the list of revoked certificates. Unfortunately, if it is not, there is no way to know that the certificate has been revoked, so there is no choice but to accept it. If the CA's signing certificate has been revoked because of a compromise of the certificate's corresponding private key, the party responsible for the compromise could likely issue an invalid CRL. As you can see, this is a vicious circle and only serves to demonstrate the flaws in CRLs that we discuss in Recipe 10.1.

10.11. Obtaining CRLs with CryptoAPI

Problem

You have a certificate that you want to verify, as well as the certificate that was used to issue it, but you need to check the issuing authority's CRL to make sure that the certificate has not been revoked. We cover how to use a CRL once you have it in Recipe 10.6—but how do you get it in the first place?

Solution

Obtaining a CRL with CryptoAPI follows the same basic procedure as doing so with OpenSSL (see Recipe 10.10); the only difference is in the functions used to perform the work. We only provide support for retrieving CRLs via HTTP in this recipe and in Recipe 10.10. We will use theWinInet API (see Recipe 9.4) and the relevant CryptoAPI functions to create a CryptoAPI CRL_CONTEXT object from data retrieved from a CA.

Discussion

For Windows, we mostly duplicate the table that was built in Recipe 10.10, but for simplicity, we strip from the data structure some members we will not be using. The name of the CA, the length of the fingerprint, and the URL to the OCSP for the CA are all omitted, leaving only the fingerprint and URL to retrieve the CRL.

#include <windows.h>

#include <wincrypt.h>

#include <wininet.h>

typedef struct {

BYTE *pbFingerPrint;

LPSTR lpszCRLURL;

} SPC_CACERT;

static SPC_CACERT rgLookupTable[ ] = {

{ "\x67\xcb\x9d\xc0\x13\x24\x8a\x82\x9b\xb2\x17\x1e\xd1\x1b\xec\xd4",

"http://crl.geotrust.com/crls/secureca.crl" },

{ "\x8f\x5d\x77\x06\x27\xc4\x98\x3c\x5b\x93\x78\xe7\xd7\x7d\x9b\xcc",

"http://crl.geotrust.com/crls/globalca1.crl" },

{ "\x64\x9c\xef\x2e\x44\xfc\xc6\x8f\x52\x07\xd0\x51\x73\x8f\xcb\x3d",

"http://crl.geotrust.com/crls/ebizca1.crl" },

{ "\xaa\xbf\xbf\x64\x97\xda\x98\x1d\x6f\xc6\x08\x3a\x95\x70\x33\xca",

"http://crl.geotrust.com/crls/ebiz.crl" },

{ "\x74\x7b\x82\x03\x43\xf0\x00\x9e\x6b\xb3\xec\x47\xbf\x85\xa5\x93",

"http://crl.verisign.com/RSASecureServer.crl" },

{ "\xc5\x70\xc4\xa2\xed\x53\x78\x0c\xc8\x10\x53\x81\x64\xcb\xd0\x1d",

"https://www.thawte.com/cgi/lifecycle/getcrl.crl?skeyid=%07%15%28mps%AA"

"%B2%8A%7C%0F%86%CE8%93%008%05%8A%B1" },

{ "\x8d\x26\xff\x2f\x31\x6d\x59x\29\xdd\xe6\x36\xa7\xe2\xce\x64\x25",

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class1.crl?Page=GetCrl"

"&crl=2" },

{ "\xb8\x16\x33\x4c\x4c\x4c\xf2\xd8\xd3\x4d\x06\xb4\xa6\x58\x40\x03",

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class2.crl?Page=GetCrl"

"&crl=3" },

{ "\x5f\x94\x4a\x73\x22\xb8\xf7\xd1\x31\xec\x59\x39\xf7\x8e\xfe\x6e",

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class3.crl?Page=GetCrl"

"&crl=4" },

{ "\x0e\xfa\x4b\xf7\xd7\x60\xcd\x65\xf7\xa7\x06\x88\x57\x98\x62\x39",

"https://www.trustcenter.de:443/cgi-bin/CRL.cgi/TC_Class4.crl?Page=GetCrl"

"&crl=5" },

{ "\xa7\xf2\xe4\x16\x06\x41\x11\x60\x30\x6b\x9c\xe3\xb4\x9c\xb0\xc9",

"http://crl.usertrust.com/UTN-UserFirst-Object.crl" },

{ "\xbf\x60\x59\xa3\x5b\xba\xf6\xa7\x76\x42\xda\x6f\x1a\x7b\x50\xcf",

"http://crl.usertrust.com/UTN-UserFirst-NetworkApplications.crl" },

{ "\x4c\x56\x41\xe5\x0d\xbb\x2b\xe8\xca\xa3\xed\x18\x08\xad\x43\x39",

"http://crl.usertrust.com/UTN-UserFirst-Hardware.crl" },

{ "\xd7\x34\x3d\xef\x1d\x27\x09\x28\xe1\x31\x02\x5b\x13\x2b\xdd\xf7",

"http://crl.usertrust.com/UTN-UserFirst-ClientAuthenticationandEmail.crl" },

{ "\xb3\xa5\x3e\x77\x21\x6d\xac\x4a\xc0\xc9\xfb\xd5\x41\x3d\xca\x06",

"http://crl.usertrust.com/UTN-DataCorpSGC.crl" },

{ "\x65\x58\xab\x15\xad\x57\x6c\x1e\xa8\xa7\xb5\x69\xac\xbf\xff\xeb",

"http://www.valicert.com/repository/ValiCert%20Calss%201%20Policy%20Val"

"idation%20Authority.crl" },

{ "\x51\x86\xe8\x1f\xbc\xb1\xc3\x71\xb5\x18\x10\xdb\x5f\xdc\xf6\x20",

"http://crl.verisign.com/pca1.1.1.crl" },

{ "\x97\x60\xe8\x57\x5f\xd3\x50\x47\xe5\x43\x0c\x94\x36\x8a\xb0\x62",

"http://crl.verisign.com/pca1.1.1.crl" },

{ "\xf2\x7d\xe9\x54\xe4\xa3\x22\x0d\x76\x9f\xe7\x0b\xbb\xb3\x24\x2b",

"http://crl.verisign.com/pca1-g2.crl" },

{ "\xdb\x23\x3d\xf9\x69\xfa\x4b\xb9\x95\x80\x44\x73\x5e\x7d\x41\x83",

"http://crl.verisign.com/pca1-g2.crl" },

{ "\xec\x40\x7d\x2b\x76\x52\x67\x05\x2c\xea\xf2\x3a\x4f\x65\xf0\xd8",

"http://crl.verisign.com/pca2.1.1.crl" },

{ "\xb3\x9c\x25\xb1\xc3\x2e\x32\x53\x80\x15\x30\x9d\x4d\x02\x77\x3e",

"http://crl.verisign.com/pca2.1.1.crl" },

{ "\x74\xa8\x2c\x81\x43\x2b\x35\x60\x9b\x78\x05\x6b\x58\xf3\x65\x82",

"http://crl.verisign.com/pca2-g2.crl" },

{ "\x2d\xbb\xe5\x25\xd3\xd1\x65\x82\x3a\xb7\x0e\xfa\xe6\xeb\xe2\xe1",

"http://crl.verisign.com/pca2-g2.crl" },

{ "\x78\x2a\x02\xdf\xdb\x2e\x14\xd5\xa7\x5f\x0a\xdf\xb6\x8e\x9c\x5d",

"http://crl.verisign.com/pca3.1.1.crl" },

{ "\x10\xfc\x63\x5d\xf6\x26\x3e\x0d\xf3\x25\xbe\x5f\x79\xcd\x67\x67",

"http://crl.verisign.com/pca3.1.1.crl" },

{ "\xc4\x63\xab\x44\x20\x1c\x36\xe4\x37\xc0\x5f\x27\x9d\x0f\x6f\x6e",

"http://crl.verisign.com/pca3-g2.crl" },

{ "\xa2\x33\x9b\x4c\x74\x78\x73\xd4\x6c\xe7\xc1\xf3\x8d\xcb\x5c\xe9",

"http://crl.verisign.com/pca3-g2.crl" },

{ "\xdd\x75\x3f\x56\xbf\xbb\xc5\xa1\x7a\x15\x53\xc6\x90\xf9\xfb\xcc",

"http://crl.verisign.com/Class3SoftwarePublishers.crl" },

{ "\x71\x1f\x0e\x21\xe7\xaa\xea\x32\x3a\x66\x23\xd3\xab\x50\xd6\x69",

"http://crl.verisign.com/Class2SoftwarePublishers.crl" },

{ 0, 0 }

};

The worker function GetDistributionPoint( ) will look for a cRLDistributionPoints extension in a certificate that has a URL. If the extension is present, CryptoAPI will return the data in Unicode, so we need to convert it back down to the single-byte OEM codepage.

static LPSTR make_thin(LPWSTR wstr) {

int len;

DWORD dwFlags;

LPSTR str;

dwFlags = WC_COMPOSITECHECK | WC_DISCARDNS;

if (!(len = WideCharToMultiByte(CP_OEMCP, dwFlags, wstr, -1, 0, 0, 0, 0)))

return 0;

if (!(str = (LPSTR)LocalAlloc(LMEM_FIXED, len))) return 0;

WideCharToMultiByte(CP_OEMCP, dwFlags, wstr, -1, str, len, 0, 0);

return str;

}

static LPSTR GetDistributionPoint(PCCERT_CONTEXT pCertContext) {

DWORD cbStructInfo, i, j;

LPSTR lpszURL;

LPVOID pvStructInfo;

CERT_EXTENSION *pExtension;

CERT_ALT_NAME_INFO *pNameInfo;

CRL_DIST_POINTS_INFO *pInfo;

pExtension = CertFindExtension(szOID_CRL_DIST_POINTS,

pCertContext->pCertInfo->cExtension,

pCertContext->pCertInfo->rgExtension);

if (!pExtension) return 0;

if (!CryptDecodeObject(X509_ASN_ENCODING, szOID_CRL_DIST_POINTS,

pExtension->Value.pbData, pExtension->Value.cbData, 0, 0, &cbStructInfo))

return 0;

if (!(pvStructInfo = LocalAlloc(LMEM_FIXED, cbStructInfo))) return 0;

CryptDecodeObject(X509_ASN_ENCODING, szOID_CRL_DIST_POINTS,

pExtension->Value.pbData, pExtension->Value.cbData, 0,

pvStructInfo, &cbStructInfo);

pInfo = (CRL_DIST_POINTS_INFO *)pvStructInfo;

for (i = 0; i < pInfo->cDistPoint; i++) {

if (pInfo->rgDistPoint[i].DistPointName.dwDistPointNameChoice = =

CRL_DIST_POINT_FULL_NAME) {

pNameInfo = &pInfo->rgDistPoint[i].DistPointName.FullName;

for (j = 0; j < pNameInfo->cAltEntry; i++) {

if (pNameInfo->rgAltEntry[j].dwAltNameChoice = = CERT_ALT_NAME_URL) {

if (!(lpszURL = make_thin(pNameInfo->rgAltEntry[i].pwszURL))) break;

LocalFree(pvStructInfo);

return lpszURL;

}

}

}

}

LocalFree(pvStructInfo);

return 0;

}

The SpcLookupCACert( ) function computes the fingerprint of the specified certificate and tries to match it with a fingerprint in the table of CA certificates and CRL URLs that we've already defined. If a match is found, the function returns a pointer to the matching entry. We will be using MD5 for computing the fingerprint, so we know that the size of the fingerprint will always be 16 bytes. (Note that we have essentially taken the SpcFingerPrintCert( ) and SpcFingerPrintEqual( ) functions from Recipe 10.9, stripped them down a bit, and combined them here.)

SPC_CACERT *SpcLookupCACert(PCCERT_CONTEXT pCertContext) {

SPC_CACERT *pCACert;

BYTE pbFingerPrint[16]; /* MD5 is 128 bits or 16 bytes */

DWORD cbFingerPrint;

/* Compute the fingerprint of the certificate */

cbFingerPrint = sizeof(pbFingerPrint);

CryptHashCertificate(0, CALG_MD5, 0, pCertContext->pbCertEncoded,

pCertContext->cbCertEncoded, pbFingerPrint,

&cbFingerPrint);

/* Compare the computed certificate against those in our lookup table */

for (pCACert = rgLookupTable; pCACert->pbFingerPrint; pCACert++) {

if (!memcmp(pCACert->pbFingerPrint, pbFingerPrint, cbFingerPrint))

return pCACert;

}

return 0;

}

SpcGetCertCRLURL( ) attempts to find the URL for the CRL for a certificate. It first checks the subject's certificate for an RFC 3280 cRLDistributionPoints extension using the GetDistributionPoint( ) worker function. If the subject certificate does not have one, the function checks the issuer's certificate. If neither certificate contains a cRLDistributionPoints extension, it checks the issuer certificate's fingerprint against the table of CA fingerprints and CRL URLs using SpcLookupCACert( ) . If a URL cannot be determined, SpcGetCertCRLURL( ) returns NULL.

LPSTR SpcGetCertCRLURL(PCCERT_CONTEXT pSubject, PCCERT_CONTEXT pIssuer,

BOOL bLookupOnly) {

LPSTR lpszURL;

SPC_CACERT *pCACert;

if (!bLookupOnly) {

if (pSubject && (lpszURL = GetDistributionPoint(pSubject)) != 0)

return lpszURL;

if (pIssuer && (lpszURL = GetDistributionPoint(pIssuer)) != 0)

return lpszURL;

}

/* Get the fingerprint of the cert's issuer, and look it up in a table */

if (pIssuer) {

if (!(pCACert = SpcLookupCACert(pIssuer))) return 0;

if (pCACert->lpszCRLURL) {

lpszURL = (LPSTR)LocalAlloc(LMEM_FIXED, lstrlenA(pCACert->lpszCRLURL) + 1);

if (!lpszURL) return 0;

lstrcpy(lpszURL, pCACert->lpszCRLURL);

return lpszURL;

}

}

return 0;

}

The worker function RetrieveWebData( ) is a wrapper around the WinInet API to retrieve the CRL data from an HTTP or FTP server, depending on the URL. It simply establishes a connection to the server, retrieves the data if it can, and returns the data that was retrieved to the caller. The CRL data is dynamically allocated with LocalAlloc( ), and it is expected that the caller will free the data with LocalFree( ) when it is no longer needed. (The WinInet API is discussed in detail in Recipe 9.4.)

static BYTE *RetrieveWebData(LPSTR lpszURL, DWORD *lpdwDataLength) {

DWORD dwContentLength, dwFlags, dwNumberOfBytesRead,

dwNumberOfBytesToRead;

LPVOID lpBuffer, lpFullBuffer, lpNewBuffer;

HINTERNET hRequest, hSession;

hSession = InternetOpen(TEXT("Secure Programming Cookbook Recipe 10.11"),

INTERNET_OPEN_TYPE_PROXY, 0, 0, 0);

if (!hSession) return 0;

dwFlags = INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_NO_COOKIES |

INTERNET_FLAG_NO_UI | INTERNET_FLAG_PASSIVE;

hRequest = InternetOpenUrl(hSession, lpszURL, 0, 0, dwFlags, 0);

if (!hRequest) {

InternetCloseHandle(hSession);

return 0;

}

dwContentLength = 0;

dwNumberOfBytesToRead = 1024;

lpFullBuffer = lpBuffer = LocalAlloc(LMEM_FIXED, dwNumberOfBytesToRead);

while (InternetReadFile(hRequest, lpBuffer, dwNumberOfBytesToRead,

&dwNumberOfBytesRead)) {

dwContentLength = dwContentLength + dwNumberOfBytesRead;

if (dwNumberOfBytesRead != dwNumberOfBytesToRead) break;

if (!(lpNewBuffer = LocalReAlloc(lpFullBuffer, dwContentLength +

dwNumberOfBytesToRead, 0))) {

LocalFree(lpFullBuffer);

InternetCloseHandle(hRequest);

InternetCloseHandle(hSession);

return 0;

}

lpFullBuffer = lpNewBuffer;

lpBuffer = (LPVOID)((LPBYTE)lpFullBuffer + dwContentLength);

}

if ((lpNewBuffer = LocalReAlloc(lpFullBuffer, dwContentLength, 0)) != 0)

lpFullBuffer = lpNewBuffer;

InternetCloseHandle(hRequest);

InternetCloseHandle(hSession);

*lpdwDataLength = dwContentLength;

return (BYTE *)lpFullBuffer;

}

The primary function used in this recipe is SpcRetrieveCRL( ) . It ties all of the other functions together in a neat little package, returning a CRL_CONTEXT object to the caller if a CRL can be successfully obtained using the information from the subject and issuer certificates that are required as arguments. SpcRetrieveCRL( ) uses the URL information from cRLDistributionPoints extensions in either certificate before consulting the internal table of CA fingerprints and CRL URLs. Unfortunately, the cRLDistributionPoints extension often contains a URL that is invalid, so this case is handled by falling back on the table lookup if the data cannot be retrieved from the cRLDistributionPoints information.

If the function is successful, it returns a CRL_CONTEXT object created using CryptoAPI. When the object is no longer needed, it should be destroyed using CertFreeCRLContext( ) . If a CRL cannot be created for some reason, NULL is returned, and the Win32 function GetLastError( ) can be used to determine what went wrong.

PCCRL_CONTEXT SpcRetrieveCRL(PCCERT_CONTEXT pSubject, PCCERT_CONTEXT pIssuer) {

BYTE *pbData;

DWORD cbData;

LPSTR lpszURL, lpszSecondURL;

PCCRL_CONTEXT pCRL;

if (!(lpszURL = SpcGetCertCRLURL(pSubject, pIssuer, FALSE))) return 0;

if (!(pbData = RetrieveWebData(lpszURL, &cbData))) {

lpszSecondURL = SpcGetCertCRLURL(pSubject, pIssuer, TRUE);

if (!lpszSecondURL || !lstrcmpA(lpszURL, lpszSecondURL)) {

if (lpszSecondURL) LocalFree(lpszSecondURL);

LocalFree(lpszURL);

return 0;

}

pbData = RetrieveWebData(lpszSecondURL, &cbData);

LocalFree(lpszSecondURL);

}

if (pbData) {

pCRL = CertCreateCRLContext(X509_ASN_ENCODING, pbData, cbData);

LocalFree(pbData);

}

LocalFree(lpszURL);

return pCRL;

}

See Also

§ RFC 3280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile

§ Recipe 9.4, Recipe 10.1, Recipe 10.6, Recipe 10.9, Recipe 10.10

10.12. Checking Revocation Status via OCSP with OpenSSL

Problem

You have a certificate that you want to verify, as well as the certificate used to issue it (and any others that may be in the certification path), but you need to check that the certificates have not been revoked. One way to do this is to download the CRL from the issuing CA, but an alternative is to check an OCSP responder for an immediate response. Using OCSP allows you to avoid the overhead of downloading a potentially very large CRL file.

Solution

Most CAs publish CRLs, but most do not run OCSP responders. A number of public OCSP responders collect CRLs from a number of different CAs and are capable of responding for each of them. Such responders are known as chain responders , and they should only be trusted if their certificate can be verified or if it is trusted and it contains the extKeyUsage extension with the OCSPSigning bit enabled. A reasonably up-to-date list of these public responders is available from http://www.openvalidation.org. For those CAs that run their own OCSP responders, it's best to contact them directly rather than relying on a chain responder, because the information from a CA's responder is more likely to be the most up-to-date.

In Recipe 10.10, we built a lookup table of various CAs that contains information about where their CRLs can be found. You will notice that OCSP responder information is also present for those CAs that have their own. At the time of this writing, the only CA that has its own responder (so far as we have been able to determine) is VeriSign.

Discussion

Checking a certificate's revocation status using an OCSP responder requires three things: the address of the OCSP responder, the certificate to be checked, and the certificate that issued the certificate you want to check. With these three items, OpenSSL makes quick work of communicating with an OCSP responder. A number of tunable variables that affect the verification process are supported, so we have created a data structure to hold this information:

#include <openssl/ocsp.h>

#include <openssl/ssl.h>

typedef struct {

char *url;

X509 *cert;

X509 *issuer;

spc_x509store_t *store;

X509 *sign_cert;

EVP_PKEY *sign_key;

long skew;

long maxage;

} spc_ocsprequest_t;

The fields in this structure are as follows:

url

Address of the OCSP responder to which to connect; this should always be a URL that specifies either HTTP or HTTPS as the service. For example, VeriSign's OCSP responder address is http://ocsp.verisign.com.

cert

Pointer to the certificate whose revocation status you want to check. In many cases, this will likely come from the peer when establishing or renegotiating an SSL session.

issuer

Pointer to the certificate that issued the certificate whose revocation status you want to check. This should be a trusted root certificate.

store

Any information required for building an X509_STORE object internally. This object will be used for verifying the OCSP responder's certificate. A full discussion of this object can be found in Recipe 10.5, but basically it contains trusted certificates and CRLs that OpenSSL can use to verify the validity of the certificate received from the OCSP responder.

sign_cert

An OCSP request can optionally be signed. Some servers require signed requests. Any server will accept a signed request provided that the server is able to verify the signature. If you want the request to be signed, this field should be non-NULL and should be a pointer to the certificate to use to sign the request. If you are going to sign your request, you should use a certificate that has been issued by a CA that is trusted by the OCSP responder so that the responder will be able to verify its validity.

sign_key

If the sign_cert member is non-NULL, this member must be filled in with a pointer to the private key to use in signing the request. It is ignored if the sign_cert member is NULL.

skew

An OCSP response contains three time fields: thisUpdate, nextUpdate, and producedAt. These fields must be checked to determine how reliable the results from the responder are. For example, under no circumstance should thisUpdate ever be greater than nextUpdate. However, it is likely that there will be some amount of clock skew between the server and the client. skew defines an acceptable amount of skew in units of seconds. It should be set to a reasonably low value. In most cases, five seconds should work out fine.

maxage

RFC 2560 OCSP responders are allowed to precompute responses to improve response time by eliminating the need to sign a response for every request. There are obvious security implications if a server opts to do this, as we discussed in Recipe 10.1. The producedAt field in the response will contain the time at which the response was computed, whether or not it was precomputed. The maxage member specifies the maximum age in seconds of responses that should be considered acceptable. Setting maxage to 0 will effectively cause the producedAt field in the response to be ignored and any otherwise acceptable response to be accepted, regardless of its age. OpenSSL's command-line ocsp command defaults to ignoring the producedAt field. However, we think it is too risky to accept precomputed responses. Unfortunately, there is no way to completely disable the acceptance of precomputed responses. The closest we can get is to set this value to one second, which is what we recommend you do.

Querying an OCSP responder is actually a complex operation, even though we are effectively reducing the amount of work necessary for you to a single function call. Because of the complexity of the operation, a number of things can go wrong, and so we have defined a sizable number of possible error codes. In some cases, we have lumped a number of finer-grained errors into a single error code, but the code presented here can easily be expanded to provide more detailed error information.

typedef enum {

SPC_OCSPRESULT_ERROR_INVALIDRESPONSE = -12,

SPC_OCSPRESULT_ERROR_CONNECTFAILURE = -11,

SPC_OCSPRESULT_ERROR_SIGNFAILURE = -10,

SPC_OCSPRESULT_ERROR_BADOCSPADDRESS = -9,

SPC_OCSPRESULT_ERROR_OUTOFMEMORY = -8,

SPC_OCSPRESULT_ERROR_UNKNOWN = -7,

SPC_OCSPRESULT_ERROR_UNAUTHORIZED = -6,

SPC_OCSPRESULT_ERROR_SIGREQUIRED = -5,

SPC_OCSPRESULT_ERROR_TRYLATER = -3,

SPC_OCSPRESULT_ERROR_INTERNALERROR = -2,

SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST = -1,

SPC_OCSPRESULT_CERTIFICATE_VALID = 0,

SPC_OCSPRESULT_CERTIFICATE_REVOKED = 1

} spc_ocspresult_t;

You will notice that any nonzero result code is an error of some kind—whether it is an error resulting in a failure to obtain the revocation status of the certificate in question, or one indicating that the certificate has been revoked. When checking the error codes, do not assume that zero means failure, as is the norm. You should always use these constants, instead of simple boolean tests, when checking the result of an OCSP operation.

The following result codes have special meaning:

SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST through SPC_OCSPRESULT_ERROR_UNKNOWN

Result codes starting with SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST and ending with SPC_OCSPRESULT_ERROR_UNKNOWN come directly from the OCSP responder. If you receive any of these error codes, you can assume that communications with the OCSP responder were successfully established, but the responder was unable to satisfy the request for one of the reasons given.

SPC_OCSPRESULT_ERROR_INVALIDRESPONSE

Indicates that there was some failure in verifying the response received from the OCSP responder. In this case, it is a good idea not to trust the certificate for which you were attempting to discover the revocation status. It is safe to assume that communications with the OCSP responder were never established if you receive any of the other error codes.

SPC_OCSPRESULT_CERTIFICATE_VALID or SPC_OCSPRESULT_CERTIFICATE_REVOKED

If the request was successfully sent to the OCSP responder, and a valid response was received, the result code will be one of these codes.

Once an spc_ocsprequest_t structure is created and appropriately initialized, communicating with the OCSP responder is a simple matter of calling spc_verify_via_ocsp( ) and checking the result code.

spc_ocspresult_t spc_verify_via_ocsp(spc_ocsprequest_t *data) {

BIO *bio = 0;

int rc, reason, ssl, status;

char *host = 0, *path = 0, *port = 0;

SSL_CTX *ctx = 0;

X509_STORE *store = 0;

OCSP_CERTID *id;

OCSP_REQUEST *req = 0;

OCSP_RESPONSE *resp = 0;

OCSP_BASICRESP *basic = 0;

spc_ocspresult_t result;

ASN1_GENERALIZEDTIME *producedAt, *thisUpdate, *nextUpdate;

result = SPC_OCSPRESULT_ERROR_UNKNOWN;

if (!OCSP_parse_url(data->url, &host, &port, &path, &ssl)) {

result = SPC_OCSPRESULT_ERROR_BADOCSPADDRESS;

goto end;

}

if (!(req = OCSP_REQUEST_new( ))) {

result = SPC_OCSPRESULT_ERROR_OUTOFMEMORY;

goto end;

}

id = OCSP_cert_to_id(0, data->cert, data->issuer);

if (!id || !OCSP_request_add0_id(req, id)) goto end;

OCSP_request_add1_nonce(req, 0, -1);

/* sign the request */

if (data->sign_cert && data->sign_key &&

!OCSP_request_sign(req, data->sign_cert, data->sign_key, EVP_sha1( ), 0, 0)) {

result = SPC_OCSPRESULT_ERROR_SIGNFAILURE;

goto end;

}

/* establish a connection to the OCSP responder */

if (!(bio = spc_connect(host, atoi(port), ssl, data->store, &ctx))) {

result = SPC_OCSPRESULT_ERROR_CONNECTFAILURE;

goto end;

}

/* send the request and get a response */

resp = OCSP_sendreq_bio(bio, path, req);

if ((rc = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) {

switch (rc) {

case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST:

result = SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST; break;

case OCSP_RESPONSE_STATUS_INTERNALERROR:

result = SPC_OCSPRESULT_ERROR_INTERNALERROR; break;

case OCSP_RESPONSE_STATUS_TRYLATER:

result = SPC_OCSPRESULT_ERROR_TRYLATER; break;

case OCSP_RESPONSE_STATUS_SIGREQUIRED:

result = SPC_OCSPRESULT_ERROR_SIGREQUIRED; break;

case OCSP_RESPONSE_STATUS_UNAUTHORIZED:

result = SPC_OCSPRESULT_ERROR_UNAUTHORIZED; break;

}

goto end;

}

/* verify the response */

result = SPC_OCSPRESULT_ERROR_INVALIDRESPONSE;

if (!(basic = OCSP_response_get1_basic(resp))) goto end;

if (OCSP_check_nonce(req, basic) <= 0) goto end;

if (data->store && !(store = spc_create_x509store(data->store))) goto end;

if ((rc = OCSP_basic_verify(basic, 0, store, 0)) <= 0) goto end;

if (!OCSP_resp_find_status(basic, id, &status, &reason, &producedAt,

&thisUpdate, &nextUpdate))

goto end;

if (!OCSP_check_validity(thisUpdate, nextUpdate, data->skew, data->maxage))

goto end;

/* All done. Set the return code based on the status from the response. */

if (status = = V_OCSP_CERTSTATUS_REVOKED)

result = SPC_OCSPRESULT_CERTIFICATE_REVOKED;

else

result = SPC_OCSPRESULT_CERTIFICATE_VALID;

end:

if (bio) BIO_free_all(bio);

if (host) OPENSSL_free(host);

if (port) OPENSSL_free(port);

if (path) OPENSSL_free(path);

if (req) OCSP_REQUEST_free(req);

if (resp) OCSP_RESPONSE_free(resp);

if (basic) OCSP_BASICRESP_free(basic);

if (ctx) SSL_CTX_free(ctx);

if (store) X509_STORE_free(store);

return result;

}

See Also

§ RFC 2560: Online Certificate Status Protocol

§ Recipe 10.1, Recipe 10.5, Recipe 10.10