Writing and Reading DER-Encoded Objects

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