214
Without delving into the details of the DER and PEM encodings, we need to know some of the properties of each. The biggest difference, as weve stated above, is that DER is a binary encoding
and PEM is text-based. Due partially to this fact, a file may contain only a single DER-encoded object, but can contain many PEM objects. In general, if we need to write data to disk, we should
use PEM; however, many third-party applications accept objects only in the DER encoding. For objects stored in files, the command-line utility allows for encoding conversion for most common
object types.
8.6.1 Writing and Reading DER-Encoded Objects
OpenSSL provides functions for many types of objects that write the DER representation of the object into a buffer. Each of the functions has a similar signature: the OpenSSL object as the first
argument and a buffer as the second. The functions return the number of bytes written to the buffer or, if the buffer is specified as
NULL
, the number of bytes that would have been written to the buffer is returned. In addition, if a buffer is specified, it is advanced to the byte after the last
byte written in order to facilitate writing multiple DER objects to the same buffer.
int i2d_OBJNAMEOBJTYPE obj, unsigned char pp;
Youll note that the second parameter is specified as a pointer to the buffer. This is done so that the pointer can be advanced after the data is written to it.
Example 8-2 demonstrates how these
functions can be used by writing an RSA public key into a dynamically allocated buffer. The function returns the buffer that was allocated to hold the DER-encoded key and stores the length
of the buffer in the second argument.
Example 8-2. DER-encoding an RSA public key
unsigned char DER_encode_RSA_publicRSA rsa, int len {
unsigned char buf, next; len = i2d_RSAPublicKeyrsa, NULL;
buf = next = unsigned char malloclen; i2d_RSAPublicKeyrsa, next;
return buf; }
Likewise, a function is provided for each type of object that reads the DER representation of the object from a buffer and creates the appropriate object in memory. Again, each of the functions
has a similar signature. The first argument is an object to populate with the data obtained from the buffer, which is specified as the second argument. The third argument specifies the number of
bytes contained in the buffer that should be used for constructing the object.
OBJTYPE d2i_OBJNAMEOBJTYPE obj, unsigned char pp, long length;
If the first argument is specified as
NULL
, a new object of the appropriate type is created and populated with the data recovered from the buffer. If it is specified as a pointer to
NULL
, a new object of the appropriate type is created and populated in the same manner, but the argument is
updated to receive the newly created object. Finally, if a pointer to an existing object is specified, the existing object is populated with the data recovered from the buffer. In all cases, the return
value of the function is the object that was populated, or
NULL
if an error occurred in recovering the data.
Example 8-3 demonstrates DER-decoding an RSA public key.
Example 8-3. DER-decoding an RSA public key
RSA DER_decode_RSA_publicunsigned char buf, long len
215
{ RSA rsa;
rsa = d2i_RSAPublicKeyNULL, buf, len; return rsa;
}
The two functions
d2i_PublicKey
and
d2i_PrivateKey
have a different signature from the others. The first argument to these two functions is an integer that specifies the type of key DH,
DSA, or RSA, which is encoded in the buffer. One of the constants
EVP_PKEY_DH
,
EVP_PKEY_DSA
, or
EVP_PKEY_RSA
must be specified to indicate the type of key to expect. The rest of the arguments to these two functions are the same, except they are shifted to make room for
the argument that specifies the type of key. The function
d2i_AutoPrivateKey
is provided to allow OpenSSL to attempt to guess the type of private key that is stored in the buffer.
Table 8-1 lists some DER-encoding functions.
Table 8-1. Functions for reading and writing DER encodings of public key objects Type of object
OpenSSL object type
Function to write the DER representation
Function to read the DER representation
Diffie-Hellman parameters
DH i2d_DHparams
d2i_DHparams
DSA parameters
DSA i2d_DSAparams
d2i_DSAparams
DSA public key
DSA i2d_DSAPublicKey
d2i_DSAPublicKey
DSA private key
DSA i2d_DSAPrivateKey
d2i_DSAPrivateKey
RSA public key
RSA i2d_RSAPublicKey
d2i_RSAPublicKey
RSA private key
RSA i2d_RSAPrivateKey
d2i_RSAPrivateKey
EVP_PKEY public key
EVP_PKEY i2d_PublicKey
d2i_PublicKey
EVP_PKEY private key
EVP_PKEY i2d_PrivateKey
d2i_PrivateKey
EVP_PKEY private key
EVP_PKEY
NA
d2i_AutoPrivateKey
The two classes of functions above are useful for reading and writing structures to flat memory, but they still require us to write them to or read them from either a file or BIO. To help with this
task, OpenSSL provides functions and macros that handle writing to files and streams. Each of the function names listed in
Table 8-1 can be used as a base for building a new name to read or write
DER encodings to or from a file or BIO. By appending
_bio
or
_fp
to the names of the functions, names for functions for writing to or reading from a BIO or file can be made. For example, the
function
i2d_DSAparams_bio
will write the DER representation of DSA parameters to the specified BIO. The definitions for the BIO and file interface functions are in the header file
opensslx509.h .
There are three exceptions to this rule. The first is that there is no BIO or file equivalent function for
i2d_PublicKey
or
d2i_PublicKey
. The second exception is that there is no equivalently named function for the
d2i_AutoPrivateKey
function. The third exception is that
d2i_PrivateKey_bio
and
d2i_PrivateKey_fp
both behave as though they were named
d2i_AutoPrivateKey_bio
and
d2i_AutoPrivateKey_fp
. For the functions that write DER representations to a BIO or a file, the first argument is either a
BIO
or
FILE
object, depending on the function that is used. The second argument is the object that will be DER-encoded and written to the BIO or file. The return value from these functions is
zero if an error occurs; otherwise, it is nonzero to indicate success.
216
The functions that read DER representations also require a
BIO
or
FILE
object as their first argument, depending on which function is used. The second argument is a pointer to an object of
the type that is being read, which is treated just the same as the first argument to the base functions. That is, when
NULL
or a pointer to
NULL
is specified, a new object of the appropriate type is created; otherwise, the specified object is populated. The return value from these functions is
NULL
if an error occurs; otherwise, the return is the object that was populated. Example 8-4
demonstrates.
Example 8-4. Reading and writing DER-encoded objects using the BIO and file functions
BIO bio = BIO_newBIO_s_memory; RSA rsa = RSA_generate_key1024, RSA_F4, NULL, NULL;
i2d_RSAPrivateKey_biobio, rsa; FILE fp = fopenrsakey.der, rb;
RSA rsa = NULL; d2i_RSAPrivateKey_fpfp, rsa;
8.6.2 Writing and Reading PEM-Encoded Objects