1
0
mirror of https://github.com/stefanocasazza/ULib.git synced 2025-09-28 19:05:55 +08:00
ULib/src/ulib/ssl/certificate.cpp
stefanocasazza 5c1a3c49ea sync
2016-10-06 16:09:35 +02:00

758 lines
21 KiB
C++

// ============================================================================
//
// = LIBRARY
// ULib - c++ library
//
// = FILENAME
// certificate.cpp
//
// = AUTHOR
// Stefano Casazza
//
// ============================================================================
#include <ulib/file.h>
#include <ulib/utility/base64.h>
#include <ulib/ssl/certificate.h>
#include <ulib/utility/services.h>
#include <ulib/utility/string_ext.h>
#include <ulib/container/hash_map.h>
#ifdef _MSWINDOWS_
# undef X509_NAME
#else
# include <openssl/x509v3.h>
#endif
#include <openssl/pem.h>
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# define ASN1_STRING_data ASN1_STRING_get0_data
#endif
bool UCertificate::verify_result;
X509_STORE_CTX* UCertificate::csc;
UVector<UCertificate*>* UCertificate::vcert;
X509* UCertificate::readX509(const UString& x, const char* format)
{
U_TRACE(1, "UCertificate::readX509(%V,%S)", x.rep, format)
BIO* in;
X509* _x509 = 0;
UString tmp = x;
if (format == 0) format = (x.isBinary() ? "DER" : "PEM");
if (strncmp(format, U_CONSTANT_TO_PARAM("PEM")) == 0 &&
strncmp(x.data(), U_CONSTANT_TO_PARAM("-----BEGIN CERTIFICATE-----")) != 0)
{
unsigned length = x.size();
UString buffer(length);
UBase64::decode(x.data(), length, buffer);
if (buffer &&
u_base64_errors == 0)
{
tmp = buffer;
format = "DER";
}
}
in = (BIO*) U_SYSCALL(BIO_new_mem_buf, "%p,%d", U_STRING_TO_PARAM(tmp));
_x509 = (X509*) (strncmp(format, U_CONSTANT_TO_PARAM("PEM")) == 0 ? U_SYSCALL(PEM_read_bio_X509, "%p,%p,%p,%p", in, 0, 0, 0)
: U_SYSCALL(d2i_X509_bio, "%p,%p", in, 0));
(void) U_SYSCALL(BIO_free, "%p", in);
U_RETURN_POINTER(_x509, X509);
}
UString UCertificate::getName(X509_NAME* n, bool bldap)
{
U_TRACE(1, "UCertificate::getName(%p,%b)", n, bldap)
U_INTERNAL_ASSERT_POINTER(n)
#ifdef DEBUG // Get X509_NAME information from Issuer || Subject name
# ifndef NID_uniqueIdentifier
# define NID_uniqueIdentifier 102
# endif
char buf[256];
buf[255] = '\0';
# define U_X509_NAME_DUMP(id,str) if (X509_NAME_get_text_by_NID(n,id,buf,256)>0) U_INTERNAL_DUMP(str" = %S",buf)
U_X509_NAME_DUMP(NID_commonName, "commonName (CN)") // CN - commonName
U_X509_NAME_DUMP(NID_countryName, "countryName (C)") // C - countryName
U_X509_NAME_DUMP(NID_localityName, "localityName (L)") // L - localityName
U_X509_NAME_DUMP(NID_stateOrProvinceName, "stateOrProvinceName (ST)") // ST - stateOrProvinceName
U_X509_NAME_DUMP(NID_organizationName, "organizationName (O)") // O - organizationName
U_X509_NAME_DUMP(NID_organizationalUnitName,"organizationalUnitName (OU)") // OU - organizationalUnitName
U_X509_NAME_DUMP(NID_title, "title (T)") // T - title
U_X509_NAME_DUMP(NID_initials, "initials (I)") // I - initials
U_X509_NAME_DUMP(NID_givenName, "givenName (G)") // G - givenName
U_X509_NAME_DUMP(NID_surname, "surname (S)") // S - surname
U_X509_NAME_DUMP(NID_description, "description (D)") // D - description
U_X509_NAME_DUMP(NID_uniqueIdentifier, "uniqueIdentifier (UID)") // UID - uniqueIdentifier
U_X509_NAME_DUMP(NID_pkcs9_emailAddress, "emailAddress (Email)") // Email - emailAddress
#endif
if (bldap)
{
BIO* bio = (BIO*) U_SYSCALL(BIO_new, "%p", BIO_s_mem());
int res = U_SYSCALL(X509_NAME_print_ex, "%p,%p,%d,%ld", bio, n, 0, XN_FLAG_COMPAT);
if (res == -1) (void) U_SYSCALL(BIO_free, "%p", bio);
else
{
UString name = UStringExt::BIOtoString(bio);
U_RETURN_STRING(name);
}
return UString::getStringNull();
}
unsigned len = U_SYSCALL(i2d_X509_NAME, "%p,%p", n, 0);
UString name(len);
char* ptr = name.data();
(void) U_SYSCALL(X509_NAME_oneline, "%p,%p,%d", n, ptr, name.capacity());
len = u__strlen(ptr, __PRETTY_FUNCTION__);
name.size_adjust(len);
U_RETURN_STRING(name);
}
bool UCertificate::isIssued(const UCertificate& ca) const
{
U_TRACE(1, "UCertificate::isIssued(%p)", &ca)
U_INTERNAL_ASSERT_POINTER(x509)
int ok = U_SYSCALL(X509_check_issued, "%p,%p", ca.x509, x509);
if (ok == X509_V_OK)
{
EVP_PKEY* pkey = ca.getSubjectPublicKey();
if (pkey == 0) ok = X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
else
{
// signature verification
if (U_SYSCALL(X509_verify, "%p,%p", x509, pkey) <= 0) ok = X509_V_ERR_CERT_SIGNATURE_FAILURE;
U_SYSCALL_VOID(EVP_PKEY_free, "%p", pkey);
}
U_INTERNAL_DUMP("ok = %d", ok)
U_RETURN(ok == X509_V_OK);
}
U_RETURN(false);
}
bool UCertificate::isSameIssuerAndSubject() const
{
U_TRACE_NO_PARAM(1, "UCertificate::isSameIssuerAndSubject()")
U_INTERNAL_ASSERT_POINTER(x509)
X509_NAME* issuer = (X509_NAME*) U_SYSCALL(X509_get_issuer_name, "%p", x509);
X509_NAME* subject = (X509_NAME*) U_SYSCALL(X509_get_subject_name, "%p", x509);
if (U_SYSCALL(X509_NAME_cmp, "%p,%p", issuer, subject) == 0) U_RETURN(true);
U_RETURN(false);
}
UString UCertificate::checkForSerialNumber(long number)
{
U_TRACE(1, "UCertificate::checkForSerialNumber(%ld)", number)
if (number == 0) U_ERROR("Serial number certificate not valid");
ASN1_INTEGER* a = ASN1_INTEGER_new();
(void) U_SYSCALL(ASN1_INTEGER_set, "%p,%ld", a, number);
BIGNUM* bn = (BIGNUM*) U_SYSCALL(ASN1_INTEGER_to_BN, "%p,%p", a, 0);
char* itmp = (char*) U_SYSCALL(BN_bn2hex, "%p", bn);
U_SYSCALL_VOID(BN_free, "%p", bn);
U_SYSCALL_VOID(ASN1_INTEGER_free, "%p", a);
UString serial((void*)itmp);
U_SYSCALL_VOID(OPENSSL_free, "%p", itmp);
U_RETURN_STRING(serial);
}
bool UCertificate::verify(STACK_OF(X509)* chain, time_t certsVerificationTime) const
{
U_TRACE(1, "UCertificate::verify(%p,%ld)", chain, certsVerificationTime)
U_INTERNAL_ASSERT_POINTER(UServices::store)
if (csc == 0) csc = (X509_STORE_CTX*) U_SYSCALL_NO_PARAM(X509_STORE_CTX_new); // create an X509 store context
U_INTERNAL_ASSERT_POINTER(csc)
U_INTERNAL_ASSERT_POINTER(x509)
/**
* The fourth parameter is a collection of any certificates that might help the verify process.
* It will normally be searched for untrusted CAs. It can contain other certificates in the expected path,
* unrelated certificates or none at all
*/
// initialize an X509 STORE context
#ifdef HAVE_OPENSSL_97
(void) U_SYSCALL(X509_STORE_CTX_init, "%p,%p,%p,%p", csc, UServices::store, x509, chain);
#else
U_SYSCALL_VOID( X509_STORE_CTX_init, "%p,%p,%p,%p", csc, UServices::store, x509, chain);
#endif
/**
* certsVerificationTime: the time to use for X509 certificates verification ("not valid before" and
* "not valid after" checks); if certsVerificationTime is equal to 0 (default)
* then we verify certificates against the system's clock "now"
*/
if (certsVerificationTime > 0) U_SYSCALL_VOID(X509_STORE_CTX_set_time, "%p,%ld,%ld", csc, 0, certsVerificationTime);
int rc = U_SYSCALL(X509_verify_cert, "%p", csc);
if (certsVerificationTime > 0) U_SYSCALL_VOID(X509_STORE_CTX_cleanup, "%p", csc);
U_RETURN(rc == 1);
}
/**
* Retrieves the signer's certificates from this certificate,
* it does check their validity and whether any signatures are valid
*/
int UCertificate::verifyCallback(int ok, X509_STORE_CTX* ctx)
{
U_TRACE(0, "UCertificate::verifyCallback(%d,%p)", ok, ctx)
U_INTERNAL_ASSERT_POINTER(vcert)
(void) UServices::X509Callback(ok, ctx);
if (ok == 0) verify_result = false;
else
{
U_INTERNAL_DUMP("UServices::verify_depth = %d", UServices::verify_depth)
if (UServices::verify_depth > 0)
{
UCertificate* cert;
U_NEW(UCertificate, cert, UCertificate(UServices::verify_current_cert));
cert->duplicate();
vcert->push_back(cert);
}
}
U_RETURN(ok);
}
unsigned UCertificate::getSignerCertificates(UVector<UCertificate*>& vec, STACK_OF(X509)* chain, time_t certsVerificationTime) const
{
U_TRACE(0, "UCertificate::getSignerCertificates(%p,%p,%ld)", &vec, chain, certsVerificationTime)
U_INTERNAL_ASSERT_POINTER(x509)
verify_result = true;
UServices::verify_depth = 20;
UServices::setVerifyCallback(UCertificate::verifyCallback);
vcert = &vec;
unsigned l = vec.size();
(void) verify(chain, certsVerificationTime);
unsigned result = vec.size() - l;
U_RETURN(result);
}
U_NO_EXPORT UString UCertificate::getRevocationURI(const void* gens)
{
U_TRACE(0, "UCertificate::getRevocationURI(%p)", gens)
UString uri(1000U);
#ifdef GENERAL_NAMES
GENERAL_NAME* gen;
for (int j = 0, k = sk_GENERAL_NAME_num((GENERAL_NAMES*)gens); j < k; ++j)
{
gen = sk_GENERAL_NAME_value((GENERAL_NAMES*)gens, j);
// try to grab the first one that matches
if (gen->type == GEN_URI)
{
uri = (char*) U_SYSCALL(ASN1_STRING_data, "%p", gen->d.uniformResourceIdentifier);
}
else if (gen->type == GEN_DIRNAME)
{
(void) U_SYSCALL(X509_NAME_oneline, "%p,%p,%d", gen->d.directoryName, uri.data(), uri.capacity());
uri.size_adjust();
}
/* unsupported generalName decoding...
else
{
}
*/
}
#endif
U_RETURN_STRING(uri);
}
uint32_t UCertificate::getRevocationURL(UVector<UString>& vec) const
{
U_TRACE(0, "UCertificate::getRevocationURL(%p)", &vec)
/**
* X509v3 CRL Distribution Points
*
* This is a multi-valued extension whose options can be either in name:value pair
* using the same form as subject alternative name or a single value representing a
* section name containing all the distribution point fields.
*
* For a name:value pair a new DistributionPoint with the fullName field set to the
* given value both the cRLissuer and reasons fields are omitted in this case.
*
* In the single option case the section indicated contains values for each field.
* In this section:
*
* If the name is ``fullname'' the value field should contain the full name of the
* distribution point in the same format as subject alternative name.
*
* If the name is ``relativename'' then the value field should contain a section
* name whose contents represent a DN fragment to be placed in this field.
*
* The name ``CRLIssuer'' if present should contain a value for this field in
* subject alternative name format.
*
* If the name is ``reasons'' the value field should consist of a comma separated
* field containing the reasons. Valid reasons are:
*
* ``keyCompromise'', * ``CACompromise'', ``affiliationChanged'', ``superseded'',
* ``cessationOfOperation'', ``certificateHold'', ``privilegeWithdrawn'' and
* ``AACompromise''.
*
* Simple examples:
*
* crlDistributionPoints=URI:http://myhost.com/myca.crl
* crlDistributionPoints=URI:http://my.com/my.crl,URI:http://oth.com/my.crl
*
* Full distribution point example:
*
* crlDistributionPoints=crldp1_section
*
* [crldp1_section]
*
* fullname=URI:http://myhost.com/myca.crl
* CRLissuer=dirName:issuer_sect
* reasons=keyCompromise, CACompromise
*
* [issuer_sect]
* C=UK
* O=Organisation
* CN=Some Name
*/
UString uri;
X509_NAME* base;
X509_NAME* merge;
GENERAL_NAME* nm;
uint32_t result, n = vec.size();
// CRLDistributionPoints ::= SEQUENCE OF DistributionPoint
STACK_OF(DIST_POINT)* crld = (STACK_OF(DIST_POINT)*) U_SYSCALL(X509_get_ext_d2i, "%p,%d,%p,%p", x509, NID_crl_distribution_points, 0, 0);
for (int i = 0, j = sk_DIST_POINT_num(crld); i < j; ++i)
{
DIST_POINT* point = sk_DIST_POINT_value(crld, i);
U_INTERNAL_DUMP("point->distpoint = %p point->CRLissuer = %p", point->distpoint, point->CRLissuer)
if (point->distpoint)
{
// in case distributionPoint is set it can be either fullName (0) or nameRelativeToCRLIssuer (1)...
U_INTERNAL_DUMP("point->distpoint->type = %d", point->distpoint->type)
if (point->distpoint->type == 0) // fullName
{
uri = getRevocationURI(point->distpoint->name.fullname);
}
else // 1 nameRelativeToCRLIssuer
{
U_INTERNAL_ASSERT_EQUALS(sk_X509_NAME_ENTRY_num(point->distpoint->name.relativename),1)
if (point->CRLissuer)
{
nm = sk_GENERAL_NAME_value(point->CRLissuer, 0);
U_INTERNAL_ASSERT_EQUALS(nm->type,GEN_DIRNAME) // NB: it MUST be a directory name...
base = nm->d.directoryName;
}
else
{
base = (X509_NAME*) U_SYSCALL(X509_get_issuer_name, "%p", x509);
}
merge = X509_NAME_dup(base);
(void) X509_NAME_add_entry(merge, sk_X509_NAME_ENTRY_value(point->distpoint->name.relativename, 0), -1, 0);
uri.setBuffer(1000U);
(void) U_SYSCALL(X509_NAME_oneline, "%p,%p,%d", merge, uri.data(), uri.capacity());
uri.size_adjust();
(void) X509_NAME_free(merge);
}
}
else
{
/**
* RFC3280: If the distributionPoint field is omitted, cRLIssuer MUST be present
* and include a Name corresponding to an X.500 or LDAP directory entry where the CRL is located
*/
if (point->CRLissuer) uri = getRevocationURI(point->CRLissuer);
// either distributionPoint or cRLIssuer MUST be present..
// this distributionPoint is sick, let's try another one
}
if (uri)
{
vec.push_back(uri);
uri.clear();
}
}
result = (vec.size() - n);
U_RETURN(result);
}
uint32_t UCertificate::getCAIssuers(UVector<UString>& vec) const
{
U_TRACE(0, "UCertificate::getCAIssuers(%p)", &vec)
/**
* Authority Info Access
*
* The authority information access extension gives details about how to access
* certain information relating to the CA. Its syntax is accessOID;location where
* location has the same syntax as subject alternative name (except that email:copy
* is not supported). accessOID can be any valid OID but only certain values are meaningful,
* for example OCSP and caIssuers.
*
* Example:
*
* authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
* authorityInfoAccess = caIssuers;URI:http://my.ca/ca.html
*/
UString uri;
// try to extract an AuthorityInfoAccessSyntax extension from x509
AUTHORITY_INFO_ACCESS* aia = (AUTHORITY_INFO_ACCESS*) U_SYSCALL(X509_get_ext_d2i, "%p,%d,%p,%p", x509, NID_info_access, 0, 0);
// AuthorityInfoAccessSyntax ::= SEQUENCE OF AccessDescription
for (int i = 0, j = sk_ACCESS_DESCRIPTION_num(aia); i < j; ++i)
{
ACCESS_DESCRIPTION* ad = sk_ACCESS_DESCRIPTION_value(aia, i);
U_INTERNAL_DUMP("ad->method = %d ad->location->type = %d", ad->method, ad->location->type)
/**
* we are interested in id-ad-caIssuers accessMethod only,
* extract general name from accessLocation. RFC3280 states:
* "where the information is available via HTTP, FTP, or LDAP, accessLocation MUST be a uniformResourceIdentifier"
*/
if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers &&
ad->location->type == GEN_URI)
{
uri = (char*) U_SYSCALL(ASN1_STRING_data, "%p", ad->location->d.uniformResourceIdentifier);
if (uri) vec.push_back(uri);
}
}
uint32_t result, n = vec.size();
result = (vec.size() - n);
U_RETURN(result);
}
UString UCertificate::getEncoded(const char* format) const
{
U_TRACE(1, "UCertificate::getEncoded(%S)", format)
U_INTERNAL_ASSERT_POINTER(x509)
if (strncmp(format, U_CONSTANT_TO_PARAM("DER")) == 0 ||
strncmp(format, U_CONSTANT_TO_PARAM("BASE64")) == 0)
{
unsigned len = U_SYSCALL(i2d_X509, "%p,%p", x509, 0);
UString encoding(len);
unsigned char* temp = (unsigned char*) encoding.data();
(void) U_SYSCALL(i2d_X509, "%p,%p", x509, &temp);
encoding.size_adjust(len);
if (strncmp(format, U_CONSTANT_TO_PARAM("BASE64")) == 0)
{
UString x(len * 3 + 32U);
UBase64::encode(encoding, x);
U_RETURN_STRING(x);
}
U_RETURN_STRING(encoding);
}
else if (strncmp(format, U_CONSTANT_TO_PARAM("PEM")) == 0)
{
BIO* bio = (BIO*) U_SYSCALL(BIO_new, "%p", BIO_s_mem());
(void) U_SYSCALL(PEM_write_bio_X509, "%p,%p", bio, x509);
UString encoding = UStringExt::BIOtoString(bio);
U_RETURN_STRING(encoding);
}
return UString::getStringNull();
}
UString UCertificate::getModulus() const
{
U_TRACE_NO_PARAM(1, "UCertificate::getModulus()")
U_INTERNAL_ASSERT_POINTER(x509)
UString result;
const BIGNUM* bn;
char buffer[4096];
EVP_PKEY* pkey = getSubjectPublicKey();
#if OPENSSL_VERSION_NUMBER < 0x10100000L
bn = (pkey->type == EVP_PKEY_RSA ? pkey->pkey.rsa->n
: pkey->pkey.dsa->pub_key); // EVP_PKEY_DSA
#else
RSA* rsa = EVP_PKEY_get1_RSA(pkey);
RSA_get0_key(rsa, &bn, 0, 0);
RSA_free(rsa);
#endif
int len = U_SYSCALL(BN_bn2bin, "%p,%p", bn, (unsigned char*)buffer);
if (len > 0)
{
UString x(len * 3 + 32U);
UBase64::encode(buffer, len, x);
result = x;
}
U_SYSCALL_VOID(EVP_PKEY_free, "%p", pkey);
U_RETURN_STRING(result);
}
UString UCertificate::getExponent() const
{
U_TRACE_NO_PARAM(1, "UCertificate::getExponent()")
U_INTERNAL_ASSERT_POINTER(x509)
UString result;
char buffer[16];
const BIGNUM* bn;
EVP_PKEY* pkey = getSubjectPublicKey();
#if OPENSSL_VERSION_NUMBER < 0x10100000L
bn = (pkey->type == EVP_PKEY_RSA ? pkey->pkey.rsa->e
: pkey->pkey.dsa->pub_key); // EVP_PKEY_DSA
#else
RSA* rsa = EVP_PKEY_get1_RSA(pkey);
RSA_get0_key(rsa, 0, &bn, 0);
RSA_free(rsa);
#endif
int len = U_SYSCALL(BN_bn2bin, "%p,%p", bn, (unsigned char*)buffer);
if (len > 0)
{
UString x(len * 3 + 32U);
UBase64::encode(buffer, len, x);
result = x;
}
U_SYSCALL_VOID(EVP_PKEY_free, "%p", pkey);
U_RETURN_STRING(result);
}
UString UCertificate::getFileName(long hash, bool crl, bool* exist)
{
U_TRACE(0, "UCertificate::getFileName(%08x,%b,%p)", hash, crl, exist)
UString name = UServices::getFileName(hash, crl);
if (name)
{
UString buffer = UFile::getRealPath(name.c_str());
if (buffer)
{
if (exist) *exist = true;
U_RETURN_STRING(buffer);
}
}
if (exist) *exist = false;
U_RETURN_STRING(name);
}
STACK_OF(X509)* UCertificate::loadCerts(const UString& x)
{
U_TRACE(1, "UCertificate::loadCerts(%V)", x.rep)
BIO* certs = (BIO*) U_SYSCALL(BIO_new_mem_buf, "%p,%d", U_STRING_TO_PARAM(x));
STACK_OF(X509)* othercerts = sk_X509_new_null();
STACK_OF(X509_INFO)* allcerts = (STACK_OF(X509_INFO)*) U_SYSCALL(PEM_X509_INFO_read_bio, "%p,%p,%p,%p", certs, 0, 0, 0);
int n = U_SYSCALL(sk_X509_INFO_num, "%p", allcerts);
for (int i = 0; i < n; ++i)
{
X509_INFO* xi = sk_X509_INFO_value(allcerts, i);
if (xi->x509)
{
sk_X509_push(othercerts, xi->x509);
xi->x509 = 0;
}
}
if (allcerts) sk_X509_INFO_pop_free(allcerts, X509_INFO_free);
(void) U_SYSCALL(BIO_free, "%p", certs);
U_RETURN_POINTER(othercerts, STACK_OF(X509));
}
// STREAMS
UString UCertificate::print(unsigned long nmflag, unsigned long cflag) const
{
U_TRACE(1, "UCertificate::print(%lu,%lu)", nmflag, cflag)
BIO* bio = (BIO*) U_SYSCALL(BIO_new, "%p", BIO_s_mem());
#ifdef HAVE_OPENSSL_97
(void) U_SYSCALL(X509_print_ex, "%p,%p,%lu,%lu", bio, x509, nmflag, cflag);
#else
(void) U_SYSCALL(X509_print, "%p,%p", bio, x509);
#endif
UString text = UStringExt::BIOtoString(bio);
U_RETURN_STRING(text);
}
#ifdef U_STDCPP_ENABLE
U_EXPORT ostream& operator<<(ostream& os, const UCertificate& c)
{
U_TRACE(0+256, "UCertificate::operator<<(%p,%p)", &os, &c)
os.put('{');
os.put(' ');
UString text = c.print();
(void) os.write(text.data(), text.size());
os.put(' ');
os.put('}');
return os;
}
# ifdef DEBUG
const char* UCertificate::dump(bool reset) const
{
*UObjectIO::os << "x509 " << x509;
if (reset)
{
UObjectIO::output();
return UObjectIO::buffer_output;
}
return 0;
}
# endif
#endif