How to calculate X.509 certificate's SHA-1 fingerprint in C/C++/Objective-C?
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
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];
}