/* parse_certificate_files.c -- Parses PEM or PKCS12 formatted cert. files * * GPLv2 only - Copyright (C) 2008 - 2012 * David Sommerseth * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /** * @file parse_certificate_files.c * @author David Sommerseth * @date 2008-12-21 * * @brief Parses PEM/DER or PKCS#12 certificate files to retrieve information * needed by eurephia. This feature requires OpenSSL to be available. * */ #ifdef HAVE_OPENSSL #include #include #include #include #include #include #include #include #include #include "get_console_input.h" #define _PARSE_CERTFICIATE_FILES_C #include "parse_certificate_files.h" /** * Extracts a specific X.509 field from an X509 struct pointer. * * @param module String containing a module name, only used in error situations * @param cert Pointer to an X509 struct with the certificate information * @param fieldname X.509 field name to extract * * @return Returns a string (char *) to the field value if found, otherwise NULL. */ char *ExtractCertInfo(const char *module, X509 *cert, const char *fieldname) { unsigned char *buf = (unsigned char *)1; // Needs to be 1 to avoid OpenSSL 0.9.6b bug char resbuf[2050]; X509_NAME *name = NULL; X509_NAME_ENTRY *namentry = NULL; ASN1_STRING *asn1 = NULL; int nid, tmp = -1, pos = -1; // // Extract subject information // memset(resbuf, 0, 2050); nid = OBJ_txt2nid(fieldname); name = X509_get_subject_name(cert); do { pos = tmp; tmp = X509_NAME_get_index_by_NID(name, nid, pos); } while ( tmp > -1 ); if( pos == -1 ) { fprintf(stderr, "%s: Field '%s' not found\n", module, fieldname); return 0; } if( !(namentry = X509_NAME_get_entry(name, pos)) ) { fprintf(stderr, "%s: Failed to extract name entry from field '%s'\n", module, fieldname); return 0; } if( !(asn1 = X509_NAME_ENTRY_get_data(namentry)) ) { fprintf(stderr, "%s: Failed to extract data from name entry field '%s'\n", module, fieldname); return 0; } if( ASN1_STRING_to_UTF8(&buf, asn1) <= 0 ) { fprintf(stderr, "%s: Failed to convert ASN1 string to UTF-8 for '%s'\n", module, fieldname); return 0; } snprintf(resbuf, 2048, "%s%c", buf, '\0'); OPENSSL_free(buf); return strdup_nullsafe(resbuf); } /** * Internal function, usually called via the Cert_ParseFile(...) macro. Parses a certificate * file of a given format. * * @param module String containing a module name, only used in error situations * @param certfile File name of the input file * @param certfile_format Certificate format of the file * * @return Returns a certinfo struct pointer with the parsed result on success, otherwise NULL. */ certinfo *_Cert_ParseFile(const char *module, const char *certfile, int certfile_format) { BIO *bio_err = NULL; PKCS12 *p12 = NULL; EVP_PKEY *pkey = NULL; X509 *cert = NULL; FILE *fp; certinfo *ret = NULL; /* Needed to convert X509 digest into hex string */ unsigned char md_sha1[EVP_MAX_MD_SIZE]; unsigned int mdlen; if( !bio_err ) { SSL_library_init(); SSL_load_error_strings(); bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); } // Open file - according to defined format switch( certfile_format ) { case CERTFILE_PEM: // PEM/DER format fp = fopen(certfile, "r"); if( !(cert = PEM_read_X509(fp, NULL, NULL, NULL)) ) { fprintf(stderr, "%s: Failed to open certificate file\n", module); return NULL; } fclose(fp); break; case CERTFILE_PKCS12: // PKCS#12 format fp = fopen(certfile, "r"); p12 = d2i_PKCS12_fp(fp, NULL); fclose(fp); if( p12 == NULL ) { fprintf(stderr, "%s: Could not open PKCS#12 file\n", module); return NULL; } OpenSSL_add_all_ciphers(); // First, try without password if( !PKCS12_parse(p12, "", &pkey, &cert, NULL) ) { char pwd[130]; // If empty password failed, get password and try again memset(&pwd, 0, 130); if( get_console_input(pwd, 128, "PKCS12 password:", 1) < 0 ) { fprintf(stderr, "Could not retrieve password\n"); } if( !PKCS12_parse(p12, pwd, &pkey, &cert, NULL) ) { PKCS12_free(p12); p12 = NULL; fprintf(stderr, "%s: Could not open PKCS#12 file - wrong password\n", module); fprintf(stderr, "%s: %s\n", module, ERR_error_string(ERR_get_error(), NULL)); BIO_free(bio_err); return NULL; } } EVP_PKEY_free(pkey); pkey = NULL; PKCS12_free(p12); p12 = NULL; break; default: // Unknown fprintf(stderr, "%s: Unknown certificate file format\n", module); return NULL; } ret = (certinfo *) malloc_nullsafe(NULL, sizeof(certinfo)+2); assert( ret != NULL ); ret->digest = (char *) malloc_nullsafe(NULL, 66); assert(ret != NULL); // extract SHA1 digest from certificate if (X509_digest(cert, EVP_sha1(), md_sha1, &mdlen) && mdlen > 0) { static const char hexcodes[] = "0123456789ABCDEF"; int j; for (j = 0; j < (int) mdlen; j++) { ret->digest[j * 3] = hexcodes[(md_sha1[j] & 0xf0) >> 4U]; ret->digest[(j * 3) + 1] = hexcodes[(md_sha1[j] & 0x0f)]; if (j + 1 != (int) mdlen) { ret->digest[(j * 3) + 2] = ':'; } else { ret->digest[(j * 3) + 2] = '\0'; } } } // Extract the subject information we want ret->common_name = ExtractCertInfo(module, cert, "CN"); ret->org = ExtractCertInfo(module, cert, "O"); ret->email = ExtractCertInfo(module, cert, "emailAddress"); X509_free(cert); BIO_free(bio_err); return ret; } #endif