Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate X.509 certificate's SHA-1 fingerprint in C/C++/Objective-C?

Tags:

c

openssl

x509

Background:

I am writing a client utility which is capable of connecting to a remote server using SSL/TLS. The client uses OpenSSL to perform the SSL/TLS transactions and I would like to allow users to specify authorized CA Certs (in the case of self signed certs or private CA setups) used to sign the server's certificate. I plan on using the cert's fingerprint, common name, and validity dates to allow the user to quickly view the certs the client uses to validate servers.

Question:

How do you calculate the SHA1 hash/fingerprint of an X509 cert stored within a PEM file using C/C++/Objective-C?

After days of search and experimenting I found a solution and will post it as an answer, however I welcome better or more correct solutions.

like image 471
David M. Syzdek Avatar asked Mar 17 '12 11:03

David M. Syzdek


2 Answers

Here is a solution I found using the OpenSSL libraries. I am posting the question and answer on stack overflow in the hopes that it will save others the trouble and time of figuring it out themselves.

#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/bio.h>


int main(int argc, char * argv[])
{
   struct stat           sb;
   unsigned char       * buff;
   int                   fd;
   ssize_t               len;
   BIO                 * bio;
   X509                * x;
   unsigned              err;
   int                   pos;
   char                  errmsg[1024];
   const EVP_MD        * digest;
   unsigned char         md[EVP_MAX_MD_SIZE];
   unsigned int          n;

   // checks arguments
   if (argc != 2)
   {
      fprintf(stderr, "Usage: peminfo <pemfile>\n");
      return(1);
   };

   // checks file
   if ((stat(argv[1], &sb)) == -1)
   {
      perror("peminfo: stat()");
      return(1);
   };
   len = (sb.st_size * 2);

   // allocates memory
   if (!(buff = malloc(len)))
   {
      fprintf(stderr, "peminfo: out of virtual memory\n");
      return(1);
   };

   // opens file for reading
   if ((fd = open(argv[1], O_RDONLY)) == -1)
   {
      perror("peminfo: open()");
      free(buff);
      return(1);
   };

   // reads file
   if ((len = read(fd, buff, len)) == -1)
   {
      perror("peminfo: read()");
      free(buff);
      return(1);
   };

   // closes file
   close(fd);

   // initialize OpenSSL
   SSL_load_error_strings();
   SSL_library_init();
   OpenSSL_add_all_algorithms();

   // creates BIO buffer
   bio = BIO_new_mem_buf(buff, len);

   // decodes buffer
   if (!(x = PEM_read_bio_X509(bio, NULL, 0L, NULL)))
   {
      while((err = ERR_get_error()))
      {
         errmsg[1023] = '\0';
         ERR_error_string_n(err, errmsg, 1023);
         fprintf(stderr, "peminfo: %s\n", errmsg);
      };
      BIO_free(bio);
      free(buff);
      return(1);
   };

   // prints x509 info
   printf("name:      %s\n",    x->name);
   printf("serial:    ");
   printf("%02X", x->cert_info->serialNumber->data[0]);
   for(pos = 1; pos < x->cert_info->serialNumber->length; pos++)
      printf(":%02X", x->cert_info->serialNumber->data[pos]);
   printf("\n");

   // calculate & print fingerprint
   digest = EVP_get_digestbyname("sha1");
   X509_digest(x, digest, md, &n);
   printf("Fingerprint: ");
   for(pos = 0; pos < 19; pos++)
      printf("%02x:", md[pos]);
   printf("%02x\n", md[19]);

   // frees memory
   BIO_free(bio);
   free(buff);

   return(0);
}

Here is the compiling and output of the above program:

$ cc -pedantic -W -Wall -Werror -O2  -Wno-deprecated -o peminfo  peminfo.c \
> -lcrypto -lssl
$ ./peminfo /usr/local/etc/openldap/keys/ca-certs.pem 
serial:      98:61:EB:C4:F2:C9:59:72
Fingerprint: 1d:59:d3:d4:4f:c9:e3:dc:f3:d7:66:b0:b8:7e:87:0b:01:73:c2:7e

Here is the output from the openssl utility:

$ openssl x509 -noout -in /usr/local/etc/openldap/keys/ca-certs.pem \
> -fingerprint -serial
SHA1 Fingerprint=1D:59:D3:D4:4F:C9:E3:DC:F3:D7:66:B0:B8:7E:87:0B:01:73:C2:7E
serial=9861EBC4F2C95972
like image 135
David M. Syzdek Avatar answered Nov 11 '22 14:11

David M. Syzdek


I found below to yield identical output to above:

+(NSData *)sha1:(SecCertificateRef) cert {
    // fingerprint is over canonical DER rep.
    CFDataRef data = SecCertificateCopyData(cert);
    NSData * out = [[NSData dataWithBytes:CFDataGetBytePtr(data) length:CFDataGetLength(data)] sha1Digest];
    CFRelease(data);
    return out;
}

which is a bit shorter in objective C. It needs the below extensions to NSData/NSString though to get the formatting close to Netscape, OSX or Windows.

- (NSData *)md5Digest
{
    unsigned char result[CC_MD5_DIGEST_LENGTH];

    CC_MD5([self bytes], (CC_LONG)[self length], result);
    return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}

- (NSData *)sha1Digest
{
    unsigned char result[CC_SHA1_DIGEST_LENGTH];

    CC_SHA1([self bytes], (CC_LONG)[self length], result);
    return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH];
}

- (NSString *)hexStringValue
{
    NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)];

    const unsigned char *dataBuffer = [self bytes];
    int i;

    for (i = 0; i < [self length]; ++i)
    {
        [stringBuffer appendFormat:@"%02lx", (unsigned long)dataBuffer[i]];
    }

    return [stringBuffer copy];
}


- (NSString *)hexColonSeperatedStringValue
{
    return [self hexColonSeperatedStringValueWithCapitals:YES];
}

- (NSString *)hexColonSeperatedStringValueWithCapitals:(BOOL)capitalize {
    NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 3)];

    const unsigned char *dataBuffer = [self bytes];
    NSString * format = capitalize ? @"%02X" : @"%02x";
    int i;

    for (i = 0; i < [self length]; ++i)
    {
        if (i) 
            [stringBuffer appendString:@":"];
        [stringBuffer appendFormat:format, (unsigned long)dataBuffer[i]];
    }

    return [stringBuffer copy];
}
like image 22
Dirk-Willem van Gulik Avatar answered Nov 11 '22 13:11

Dirk-Willem van Gulik