diff --git a/DESCRIPTION b/DESCRIPTION index 35c2d5c..3b034d0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -5,7 +5,7 @@ Author: Simon Urbanek Maintainer: Simon Urbanek Depends: R (>= 2.9.0), base64enc Enhances: gmp -Description: This package provides PKI functions such as verifyig certificates, RSA encription and signing which can be used to build PKI infrastructure and perform cryptographic tasks. +Description: This package provides PKI functions such as verifying certificates, RSA encription and signing which can be used to build PKI infrastructure and perform cryptographic tasks. License: GPL-2 | GPL-3 | file LICENSE URL: http://www.rforge.net/PKI SystemRequirements: OpenSSL library diff --git a/NAMESPACE b/NAMESPACE index f03dd7d..dab53d2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,3 +1,3 @@ -useDynLib(PKI, PKI_load_DER_X509, PKI_verify_cert, PKI_cert_public_key, PKI_encrypt, PKI_decrypt, PKI_load_private_RSA, PKI_load_public_RSA, PKI_RSAkeygen, PKI_sign_RSA, PKI_verify_RSA, PKI_digest, PKI_extract_key, PKI_raw2hex, encode_ASN1, decode_ASN1, PKI_asBIGNUMint, PKI_sym_cipher, PKI_get_subject) -export(PKI.load.cert, PKI.verifyCA, PKI.verify, PKI.sign, PKI.pubkey, PKI.encrypt, PKI.decrypt, PKI.genRSAkey, PKI.digest, PKI.save.key, PKI.load.key, raw2hex, ASN1.encode, ASN1.decode, ASN1.item, ASN1.type, as.BIGNUMint, PKI.mkRSApubkey, PKI.load.OpenSSH.pubkey, PKI.sign.tar, PKI.verify.tar, PKI.get.subject) +useDynLib(PKI, PKI_load_DER_X509, PKI_verify_cert, PKI_cert_public_key, PKI_encrypt, PKI_decrypt, PKI_load_private_RSA, PKI_load_public_RSA, PKI_RSAkeygen, PKI_sign_RSA, PKI_verify_RSA, PKI_digest, PKI_extract_key, PKI_raw2hex, encode_ASN1, decode_ASN1, PKI_asBIGNUMint, PKI_sym_cipher, PKI_get_subject, PKI_get_notBefore, PKI_get_notAfter) +export(PKI.load.cert, PKI.verifyCA, PKI.verify, PKI.sign, PKI.pubkey, PKI.encrypt, PKI.decrypt, PKI.genRSAkey, PKI.digest, PKI.save.key, PKI.load.key, raw2hex, ASN1.encode, ASN1.decode, ASN1.item, ASN1.type, as.BIGNUMint, PKI.mkRSApubkey, PKI.load.OpenSSH.pubkey, PKI.sign.tar, PKI.verify.tar, PKI.get.subject, PKI.get.notBefore, PKI.get.notAfter) import(base64enc) diff --git a/R/x509.R b/R/x509.R index 1c4286b..15ef7a7 100644 --- a/R/x509.R +++ b/R/x509.R @@ -25,3 +25,19 @@ PKI.verifyCA <- function(certificate, ca) .Call(PKI_verify_cert, ca, certificate PKI.pubkey <- function(certificate) .Call(PKI_cert_public_key, certificate) PKI.get.subject <- function(certificate) .Call(PKI_get_subject, certificate) + +PKI.get.notBefore <- function(certificate) { + time_real <- .Call(PKI_get_notBefore, certificate) + + # To do the conversion in R, we would use + # t <- strptime(aTime, format="%y%m%d%H%M%SZ", tz="GMT") + # But that hardcodes the ASN1_TIME format, which won't always be correct + + return(as.POSIXct(time_real, tz="GMT", origin="1970-01-01")) +} + +PKI.get.notAfter <- function(certificate) { + time_real <- .Call(PKI_get_notAfter, certificate) + return(as.POSIXct(time_real, tz="GMT", origin="1970-01-01")) +} + diff --git a/man/X509.Rd b/man/X509.Rd index cc1475b..10abe68 100644 --- a/man/X509.Rd +++ b/man/X509.Rd @@ -4,6 +4,8 @@ \alias{PKI.verifyCA} \alias{PKI.pubkey} \alias{PKI.get.subject} +\alias{PKI.get.notBefore} +\alias{PKI.get.notAfter} \title{ Public Key Instraftructure (X509) functions } @@ -17,12 +19,19 @@ \code{PKI.pubkey} extracts public key from a certificate. \code{PKI.get.subject} extracts the subject name from the certificate. + + \code{PKI.get.notBefore} extracts the notBefore field from the certificate. + + \code{PKI.get.notAfter} extracts the notAfter field from the certificate. + } \usage{ PKI.load.cert(what, format = c("PEM", "DER"), file) PKI.verifyCA(certificate, ca) PKI.pubkey(certificate) PKI.get.subject(certificate) +PKI.get.notBefore(certificate) +PKI.get.notAfter(certificate) } \arguments{ \item{what}{string, raw vector or connection to load the certificate @@ -49,6 +58,14 @@ PKI.get.subject(certificate) one-line RFC2253 format but in UTF8 encoding instead of MBS escapes. NOTE: this is experimantal, we may choose to parse the contents and return it in native R form as a named vector instead. + + \code{PKI.get.notBefore}: POSIXct value containing the datetime 'notBefore' + field fromt the certificate. This represents the earliest time at which the + certificate is valid. + + \code{PKI.get.notAfter}: POSIXct value containing the datetime 'notAfter' + field fromt the certificate. This represents the latest time at which the + certificate is valid. } %\references{ %} @@ -65,5 +82,7 @@ PKI.get.subject(certificate) PKI.verifyCA(my.cert, ca) PKI.pubkey(my.cert) PKI.get.subject(my.cert) + PKI.get.notBefore(my.cert) + PKI.get.notAfter(my.cert) } \keyword{manip} diff --git a/src/asn1.c b/src/asn1.c index 8c665df..1b71253 100644 --- a/src/asn1.c +++ b/src/asn1.c @@ -1,6 +1,7 @@ #include #include +#include "asn1.h" static SEXP decode_ASN1_bytes(unsigned char *d, unsigned int l, unsigned int *ptr) { unsigned int i = 0; @@ -269,3 +270,110 @@ SEXP PKI_asBIGNUMint(SEXP sWhat, SEXP sScalar) { /* unreachable */ return R_NilValue; } + +/* +* Parse an ASN1_TIME value and return a time_t structure. According to RFC 5280 (http://www.rfc-editor.org/rfc/rfc5280.txt) +* conforming implementations "MUST always encode certificate validity dates through the year 2049 as UTCTime; +* certificate validity dates in 2050 or later MUST be encoded as GeneralizedTime." See section 4.1 of RFC 5280 for the +* definition of UTCTime and Generalized time, but the crux is that UTCTime uses a two digit year, whereas GeneralizedTime +* uses a 4 digit year. +* +* This algorithm is based on parsing the character +* string in ASN1_TIME and extracting the date and time components based on length and results in the string. It +* is derived from the publicly posted algorithm here: http://marc.info/?l=openssl-users&m=106781789300592&w=2 +* I use timegm() rather than mktime() to do the conversion to time_t in order to not assume a local time zone during +* the conversion, but its not clear if timegm() is portable to Windows. Need to look into that, +* possibly proving an implementation for Windows as outlined here: +* http://trac.rtmpd.com/browser/trunk/sources/common/src/platform/windows/timegm.cpp +*/ +time_t getTimeFromASN1(const ASN1_TIME * aTime) { + + time_t lResult = 0; + + char lBuffer[24]; + char * pBuffer = lBuffer; + + size_t lTimeLength = aTime->length; + + char * pString = (char *)aTime->data; + + if (aTime->type == V_ASN1_UTCTIME) { + if ((lTimeLength < 11) || (lTimeLength > 17)) { + return 0; + } + + memcpy(pBuffer, pString, 10); + pBuffer += 10; + pString += 10; + } else { + if (lTimeLength < 13) { + return 0; + } + + memcpy(pBuffer, pString, 12); + pBuffer += 12; + pString += 12; + } + + if ((*pString == 'Z') || (*pString == '-') || (*pString == '+')) { + *(pBuffer++) = '0'; + *(pBuffer++) = '0'; + } else { + *(pBuffer++) = *(pString++); + *(pBuffer++) = *(pString++); + // Skip any fractional seconds... + if (*pString == '.') { + pString++; + while ((*pString >= '0') && (*pString <= '9')) { + pString++; + } + } + } + + *(pBuffer++) = 'Z'; + *(pBuffer++) = '\0'; + + time_t lSecondsFromUCT; + if (*pString == 'Z') { + lSecondsFromUCT = 0; + } else { + if ((*pString != '+') && (pString[5] != '-')) { + return 0; + } + + lSecondsFromUCT = ((pString[1]-'0') * 10 + (pString[2]-'0')) * 60; + lSecondsFromUCT += (pString[3]-'0') * 10 + (pString[4]-'0'); + if (*pString == '-') { + lSecondsFromUCT = -lSecondsFromUCT; + } + } + + struct tm lTime; + lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0'); + lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0'); + lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0'); + lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0'); + lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1; + lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0'); + if (lTime.tm_year < 50) { + lTime.tm_year += 100; + // RFC 2459 + } + lTime.tm_wday = 0; + lTime.tm_yday = 0; + lTime.tm_isdst = 0; + + // No DST adjustment requested + lResult = timegm(&lTime); + if ((time_t)-1 != lResult) { + if (0 != lTime.tm_isdst) { + lResult -= 3600; + // mktime may adjust for DST (OS dependent) + } + lResult += lSecondsFromUCT; + } else { + lResult = 0; + } + + return lResult; +} diff --git a/src/asn1.h b/src/asn1.h new file mode 100644 index 0000000..277a623 --- /dev/null +++ b/src/asn1.h @@ -0,0 +1,4 @@ +#include + +/* Method prototypes */ +time_t getTimeFromASN1(const ASN1_TIME * aTime); diff --git a/src/pki-x509.c b/src/pki-x509.c index 4fc02bf..5b43613 100644 --- a/src/pki-x509.c +++ b/src/pki-x509.c @@ -608,7 +608,7 @@ SEXP PKI_get_subject(SEXP sCert) { char *txt = 0; PKI_init(); cert = retrieve_cert(sCert, ""); - if (X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, (XN_FLAG_ONELINE | ASN1_STRFLGS_UTF8_CONVERT) & ~ASN1_STRFLGS_ESC_MSB) < 0) { + if (X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_RFC2253) < 0) { BIO_free(mem); Rf_error("X509_NAME_print_ex failed with %s", ERR_error_string(ERR_get_error(), NULL)); } @@ -623,3 +623,33 @@ SEXP PKI_get_subject(SEXP sCert) { BIO_free(mem); return res; } + +/* Return the notBefore date of an X509 Certificate by wrapping the OpenSSL X509_get_notBefore() function. */ +SEXP PKI_get_notBefore(SEXP sCert) { + X509 *cert; + PKI_init(); + cert = retrieve_cert(sCert, ""); + ASN1_TIME *timeASN1; + timeASN1 = X509_get_notBefore(cert); + time_t result_time; + result_time = getTimeFromASN1(timeASN1); + SEXP timeSEXP; + timeSEXP = PROTECT(ScalarReal(result_time)); + UNPROTECT(1); + return timeSEXP; +} + +/* Return the notAfter date of an X509 Certificate by wrapping the OpenSSL X509_get_notAfter() function. */ +SEXP PKI_get_notAfter(SEXP sCert) { + X509 *cert; + PKI_init(); + cert = retrieve_cert(sCert, ""); + ASN1_TIME *timeASN1; + timeASN1 = X509_get_notAfter(cert); + time_t result_time; + result_time = getTimeFromASN1(timeASN1); + SEXP timeSEXP; + timeSEXP = PROTECT(ScalarReal(result_time)); + UNPROTECT(1); + return timeSEXP; +} diff --git a/src/pki.h b/src/pki.h index 260fd68..8d8fa9a 100644 --- a/src/pki.h +++ b/src/pki.h @@ -15,6 +15,7 @@ #include #include #include +#include "asn1.h" #if __APPLE__ #if defined MAC_OS_X_VERSION_10_7 && MAC_OS_X_VERSION_MIN_REQUIRED >= 1070