/******************************************************************************/
/* */
/* X r d C r y p t o s s l X 5 0 9 . c c */
/* */
/* (c) 2005 G. Ganis , CERN */
/* */
/* This file is part of the XRootD software suite. */
/* */
/* XRootD is free software: you can redistribute it and/or modify it under */
/* the terms of the GNU Lesser General Public License as published by the */
/* Free Software Foundation, either version 3 of the License, or (at your */
/* option) any later version. */
/* */
/* XRootD 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 Lesser General Public */
/* License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
/* COPYING (GPL license). If not, see . */
/* */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without */
/* specific prior written permission of the institution or contributor. */
/* */
/******************************************************************************/
/* ************************************************************************** */
/* */
/* OpenSSL implementation of XrdCryptoX509 */
/* */
/* ************************************************************************** */
#include
#include
#include
#include
#include "XrdCrypto/XrdCryptosslRSA.hh"
#include "XrdCrypto/XrdCryptosslX509.hh"
#include "XrdCrypto/XrdCryptosslAux.hh"
#include "XrdCrypto/XrdCryptosslTrace.hh"
#include
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey)
{
if (pkey->type != EVP_PKEY_RSA) {
return NULL;
}
return pkey->pkey.rsa;
}
#endif
#define BIO_PRINT(b,c) \
BUF_MEM *bptr; \
BIO_get_mem_ptr(b, &bptr); \
if (bptr) { \
char *s = new char[bptr->length+1]; \
memcpy(s, bptr->data, bptr->length); \
s[bptr->length] = '\0'; \
PRINT(c << s); \
delete [] s; \
} else { \
PRINT("ERROR: "<buffer),buck->size);
if (nw != buck->size) {
DEBUG("problems writing data to memory BIO (nw: "<ca) {
type = kCA;
DEBUG("CA certificate");
done = 1;
}
if (bc) BASIC_CONSTRAINTS_free(bc);
if (done) return;
// Is this a proxy?
idx = -1;
// Proxy names
XrdOucString common(subject, 0, subject.rfind("/CN=") - 1);
bool pxyname = 0;
if (issuer == common) {
pxyname = 1;
pxytype = 1;
}
if (pxyname) {
type = kUnknown;
if ((idx = X509_get_ext_by_NID(cert, NID_proxyCertInfo,-1)) == -1) {
int xcp = -1;
XrdOucString emsg;
if ((xcp = XrdCryptosslX509CheckProxy3(this, emsg)) == 0) {
type = kProxy;
pxytype = 3;
DEBUG("Found GSI 3 proxyCertInfo extension");
} else if (xcp == -1) {
PRINT("ERROR: "<proxyPolicy) != 0) {
if ((pci->proxyPolicy->policyLanguage) != 0) {
type = kProxy;
done = 1;
pxytype = 2;
DEBUG("Found RFC 382{0,1}compliant proxyCertInfo extension");
if (X509_get_ext_by_NID(cert, NID_proxyCertInfo, idx) != -1) {
PRINT("WARNING: multiple proxyCertInfo extensions found: taking the first");
}
} else {
PRINT("ERROR: accessing policy language from proxyCertInfo extension");
}
} else {
PRINT("ERROR: accessing policy from proxyCertInfo extension");
}
PROXY_CERT_INFO_EXTENSION_free(pci);
} else {
PRINT("ERROR: proxyCertInfo conversion error");
}
} else {
PRINT("ERROR: proxyCertInfo not flagged as critical");
}
}
if (!pxyname || done) return;
// Check if GSI 2 legacy proxy
XrdOucString lastcn(subject, subject.rfind("/CN=") + 4, -1);
if (lastcn == "proxy" || lastcn == "limited proxy") {
pxytype = 4;
type = kProxy;
}
// We are done
return;
}
//_____________________________________________________________________________
void XrdCryptosslX509::SetPKI(XrdCryptoX509data newpki)
{
// Set PKI
// Cleanup key first
if (pki)
delete pki;
if (newpki)
pki = new XrdCryptosslRSA((EVP_PKEY *)newpki, 1);
}
//_____________________________________________________________________________
time_t XrdCryptosslX509::NotBefore()
{
// Begin-validity time in secs since Epoch
// If we do not have it already, try extraction
if (notbefore < 0) {
// Make sure we have a certificate
if (cert)
// Extract UTC time in secs from Epoch
notbefore = XrdCryptosslASN1toUTC(X509_get_notBefore(cert));
}
// return what we have
return notbefore;
}
//_____________________________________________________________________________
time_t XrdCryptosslX509::NotAfter()
{
// End-validity time in secs since Epoch
// If we do not have it already, try extraction
if (notafter < 0) {
// Make sure we have a certificate
if (cert)
// Extract UTC time in secs from Epoch
notafter = XrdCryptosslASN1toUTC(X509_get_notAfter(cert));
}
// return what we have
return notafter;
}
//_____________________________________________________________________________
const char *XrdCryptosslX509::Subject()
{
// Return subject name
EPNAME("X509::Subject");
// If we do not have it already, try extraction
if (subject.length() <= 0) {
// Make sure we have a certificate
if (!cert) {
DEBUG("WARNING: no certificate available - cannot extract subject name");
return (const char *)0;
}
// Extract subject name
XrdCryptosslNameOneLine(X509_get_subject_name(cert), subject);
}
// return what we have
return (subject.length() > 0) ? subject.c_str() : (const char *)0;
}
//_____________________________________________________________________________
const char *XrdCryptosslX509::Issuer()
{
// Return issuer name
EPNAME("X509::Issuer");
// If we do not have it already, try extraction
if (issuer.length() <= 0) {
// Make sure we have a certificate
if (!cert) {
DEBUG("WARNING: no certificate available - cannot extract issuer name");
return (const char *)0;
}
// Extract issuer name
XrdCryptosslNameOneLine(X509_get_issuer_name(cert), issuer);
}
// return what we have
return (issuer.length() > 0) ? issuer.c_str() : (const char *)0;
}
//_____________________________________________________________________________
const char *XrdCryptosslX509::IssuerHash(int alg)
{
// Return hash of issuer name
// Use default algorithm (X509_NAME_hash) for alg = 0, old algorithm
// (for v>=1.0.0) when alg = 1
EPNAME("X509::IssuerHash");
#if (OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(__APPLE__))
if (alg == 1) {
// md5 based
if (issueroldhash.length() <= 0) {
// Make sure we have a certificate
if (cert) {
char chash[30] = {0};
snprintf(chash, sizeof(chash),
"%08lx.0",X509_NAME_hash_old(X509_get_issuer_name(cert)));
issueroldhash = chash;
} else {
DEBUG("WARNING: no certificate available - cannot extract issuer hash (md5)");
}
}
// return what we have
return (issueroldhash.length() > 0) ? issueroldhash.c_str() : (const char *)0;
}
#else
if (alg == 1) { }
#endif
// If we do not have it already, try extraction
if (issuerhash.length() <= 0) {
// Make sure we have a certificate
if (cert) {
char chash[30] = {0};
snprintf(chash, sizeof(chash),
"%08lx.0",X509_NAME_hash(X509_get_issuer_name(cert)));
issuerhash = chash;
} else {
DEBUG("WARNING: no certificate available - cannot extract issuer hash (default)");
}
}
// return what we have
return (issuerhash.length() > 0) ? issuerhash.c_str() : (const char *)0;
}
//_____________________________________________________________________________
const char *XrdCryptosslX509::SubjectHash(int alg)
{
// Return hash of subject name
// Use default algorithm (X509_NAME_hash) for alg = 0, old algorithm
// (for v>=1.0.0) when alg = 1
EPNAME("X509::SubjectHash");
#if (OPENSSL_VERSION_NUMBER >= 0x10000000L && !defined(__APPLE__))
if (alg == 1) {
// md5 based
if (subjectoldhash.length() <= 0) {
// Make sure we have a certificate
if (cert) {
char chash[30] = {0};
snprintf(chash, sizeof(chash),
"%08lx.0",X509_NAME_hash_old(X509_get_subject_name(cert)));
subjectoldhash = chash;
} else {
DEBUG("WARNING: no certificate available - cannot extract subject hash (md5)");
}
}
// return what we have
return (subjectoldhash.length() > 0) ? subjectoldhash.c_str() : (const char *)0;
}
#else
if (alg == 1) { }
#endif
// If we do not have it already, try extraction
if (subjecthash.length() <= 0) {
// Make sure we have a certificate
if (cert) {
char chash[30] = {0};
snprintf(chash, sizeof(chash),
"%08lx.0",X509_NAME_hash(X509_get_subject_name(cert)));
subjecthash = chash;
} else {
DEBUG("WARNING: no certificate available - cannot extract subject hash (default)");
}
}
// return what we have
return (subjecthash.length() > 0) ? subjecthash.c_str() : (const char *)0;
}
//_____________________________________________________________________________
kXR_int64 XrdCryptosslX509::SerialNumber()
{
// Return serial number as a kXR_int64
kXR_int64 sernum = -1;
if (cert && X509_get_serialNumber(cert)) {
BIGNUM *bn = BN_new();
ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), bn);
char *sn = BN_bn2dec(bn);
sernum = strtoll(sn, 0, 10);
BN_free(bn);
OPENSSL_free(sn);
}
return sernum;
}
//_____________________________________________________________________________
XrdOucString XrdCryptosslX509::SerialNumberString()
{
// Return serial number as a hex string
XrdOucString sernum;
if (cert && X509_get_serialNumber(cert)) {
BIGNUM *bn = BN_new();
ASN1_INTEGER_to_BN(X509_get_serialNumber(cert), bn);
char *sn = BN_bn2hex(bn);
sernum = sn;
BN_free(bn);
OPENSSL_free(sn);
}
return sernum;
}
//_____________________________________________________________________________
XrdCryptoX509data XrdCryptosslX509::GetExtension(const char *oid)
{
// Return pointer to extension with OID oid, if any, in
// opaque form
EPNAME("X509::GetExtension");
XrdCryptoX509data ext = 0;
// Make sure we got something to look for
if (!oid) {
DEBUG("OID string not defined");
return ext;
}
// Make sure we got something to look for
if (!cert) {
DEBUG("certificate is not initialized");
return ext;
}
// Are there any extension?
int numext = X509_get_ext_count(cert);
if (numext <= 0) {
DEBUG("certificate has got no extensions");
return ext;
}
DEBUG("certificate has "< 0);
// Loop to identify the one we would like
int i = 0;
X509_EXTENSION *wext = 0;
for (i = 0; i< numext; i++) {
wext = X509_get_ext(cert, i);
if (usenid) {
int enid = OBJ_obj2nid(X509_EXTENSION_get_object(wext));
if (enid == nid)
break;
} else {
// Try matching of the text
char s[256];
OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(wext), 1);
if (!strcmp(s, oid))
break;
}
// Do not free the extension: its owned by the certificate
wext = 0;
}
// We are done if nothing was found
if (!wext) {
DEBUG("Extension "<size<<" bytes)");
return bucket;
}
// Make sure we got something to export
if (!cert) {
DEBUG("certificate is not initialized");
return 0;
}
//
// Now we create a bio_mem to serialize the certificate
BIO *bmem = BIO_new(BIO_s_mem());
if (!bmem) {
DEBUG("unable to create BIO for memory operations");
return 0;
}
// Write certificate to BIO
if (!PEM_write_bio_X509(bmem, cert)) {
DEBUG("unable to write certificate to memory BIO");
return 0;
}
// Extract pointer to BIO data and length of segment
char *bdata = 0;
int blen = BIO_get_mem_data(bmem, &bdata);
DEBUG("BIO data: "<SetBuf(bdata, blen);
DEBUG("result of serialization: "<size<<" bytes");
} else {
DEBUG("unable to create bucket for serialized format");
BIO_free(bmem);
return 0;
}
//
// Free BIO
BIO_free(bmem);
//
// We are done
return bucket;
}
//_____________________________________________________________________________
bool XrdCryptosslX509::Verify(XrdCryptoX509 *ref)
{
// Verify certificate signature with pub key of ref cert
EPNAME("X509::Verify");
// We must have been initialized
if (!cert)
return 0;
// We must have something to check with
X509 *r = ref ? (X509 *)(ref->Opaque()) : 0;
EVP_PKEY *rk = r ? X509_get_pubkey(r) : 0;
if (!rk)
return 0;
// Ok: we can verify
int rc = X509_verify(cert, rk);
EVP_PKEY_free(rk);
if (rc <= 0) {
if (rc == 0) {
// Signatures are not OK
DEBUG("signature not OK");
} else {
// General failure
DEBUG("could not verify signature");
}
return 0;
}
// Success
return 1;
}
//____________________________________________________________________________
int XrdCryptosslX509::DumpExtensions(bool dumpunknown)
{
// Dump our extensions, if any
// Returns -1 on failure, 0 on success
EPNAME("DumpExtensions");
int rc = -1;
// Point to the cerificate
X509 *xpi = (X509 *) Opaque();
// Make sure we got the right inputs
if (!xpi) {
PRINT("we are empty! Do nothing");
return rc;
}
rc = 1;
// Go through the extensions
X509_EXTENSION *xpiext = 0;
int npiext = X509_get_ext_count(xpi);
PRINT("found "<data;
long length = X509_EXTENSION_get_data(xpiext)->length;
int ret = FillUnknownExt(&pp, length, dumpunknown);
PRINT("ret: " << ret);
}
// Done
return rc;
}
//____________________________________________________________________________
int XrdCryptosslX509::FillUnknownExt(XRDGSI_CONST unsigned char **pp, long length, bool dump)
{
// Do the actual filling of the bio; can be called recursevely
EPNAME("FillUnknownExt");
XRDGSI_CONST unsigned char *p,*ep,*tot,*op,*opp;
long len;
int tag, xclass, ret = 0;
int nl,hl,j,r;
ASN1_OBJECT *o = 0;
ASN1_OCTET_STRING *os = 0;
/* ASN1_BMPSTRING *bmp=NULL;*/
int dump_indent = 6;
int depth = 0;
int indent = 0;
p = *pp;
tot = p + length;
op = p - 1;
while ((p < tot) && (op < p)) {
op = p;
j = ASN1_get_object(&p, &len, &tag, &xclass, length);
#ifdef LINT
j = j;
#endif
if (j & 0x80) {
if (dump) PRINT("ERROR: error in encoding");
ret = 0;
goto end;
}
hl = (p-op);
length -= hl;
/* if j == 0x21 it is a constructed indefinite length object */
if (j != (V_ASN1_CONSTRUCTED | 1)) {
if (dump) PRINT("PRIM: d="< length) {
if (dump) PRINT("ERROR:CONST: length is greater than " <= tot))
break;
}
} else {
while (p < ep) {
r = FillUnknownExt(&p, (long)len, dump);
if (r == 0) {
ret = 0;
goto end;
}
}
}
} else if (xclass != 0) {
p += len;
if (dump) PRINT(" ");
} else {
nl = 0;
if ((tag == V_ASN1_PRINTABLESTRING) ||
(tag == V_ASN1_T61STRING) ||
(tag == V_ASN1_IA5STRING) ||
(tag == V_ASN1_VISIBLESTRING) ||
(tag == V_ASN1_NUMERICSTRING) ||
(tag == V_ASN1_UTF8STRING) ||
(tag == V_ASN1_UTCTIME) ||
(tag == V_ASN1_GENERALIZEDTIME)) {
if (len > 0) {
char *s = new char[len + 1];
memcpy(s, p, len);
s[len] = 0;
if (dump) PRINT("GENERIC:" << s <<" (len: "<<(int)len<<")");
delete [] s;
} else {
if (dump) PRINT("GENERIC: (len: "<<(int)len<<")");
}
} else if (tag == V_ASN1_OBJECT) {
opp = op;
if (d2i_ASN1_OBJECT(&o, &opp, len+hl)) {
BIO *mem = BIO_new(BIO_s_mem());
i2a_ASN1_OBJECT(mem, o);
XrdOucString objstr;
if (dump) { BIO_PRINT(mem, "AOBJ:"); }
} else {
if (dump) PRINT("ERROR:AOBJ: BAD OBJECT");
}
} else if (tag == V_ASN1_BOOLEAN) {
if (len != 1) {
if (dump) PRINT("ERROR:BOOL: Bad boolean");
goto end;
}
if (dump) PRINT("BOOL:"<< p[0]);
} else if (tag == V_ASN1_BMPSTRING) {
/* do the BMP thang */
} else if (tag == V_ASN1_OCTET_STRING) {
int i, printable = 1;
opp = op;
os = d2i_ASN1_OCTET_STRING(0, &opp, len + hl);
if (os && os->length > 0) {
opp = os->data;
/* testing whether the octet string is * printable */
for (i=0; ilength; i++) {
if (( (opp[i] < ' ') && (opp[i] != '\n') &&
(opp[i] != '\r') && (opp[i] != '\t')) || (opp[i] > '~')) {
printable = 0;
break;
}
}
if (printable) {
/* printable string */
char *s = new char[os->length + 1];
memcpy(s, opp, os->length);
s[os->length] = 0;
if (dump) PRINT("OBJS:" << s << " (len: "<length<<")");
delete [] s;
} else {
/* print the normal dump */
if (!nl) PRINT("OBJS:");
BIO *mem = BIO_new(BIO_s_mem());
if (BIO_dump_indent(mem, (const char *)opp, os->length, dump_indent) <= 0) {
if (dump) PRINT("ERROR:OBJS: problems dumping to BIO");
BIO_free(mem);
goto end;
}
if (dump) { BIO_PRINT(mem, "OBJS:"); }
nl = 1;
}
}
if (os) {
ASN1_OCTET_STRING_free(os);
os = 0;
}
} else if (tag == V_ASN1_INTEGER) {
ASN1_INTEGER *bs;
int i;
opp = op;
bs = d2i_ASN1_INTEGER(0, &opp, len+hl);
if (bs) {
if (dump) PRINT("AINT:");
if (bs->type == V_ASN1_NEG_INTEGER)
if (dump) PRINT("-");
BIO *mem = BIO_new(BIO_s_mem());
for (i = 0; i < bs->length; i++) {
if (BIO_printf(mem, "%02X", bs->data[i]) <= 0) {
if (dump) PRINT("ERROR:AINT: problems printf-ing to BIO");
BIO_free(mem);
goto end;
}
}
if (dump) { BIO_PRINT(mem, "AINT:"); }
if (bs->length == 0) PRINT("00");
} else {
if (dump) PRINT("ERROR:AINT: BAD INTEGER");
}
ASN1_INTEGER_free(bs);
} else if (tag == V_ASN1_ENUMERATED) {
ASN1_ENUMERATED *bs;
int i;
opp = op;
bs = d2i_ASN1_ENUMERATED(0, &opp, len+hl);
if (bs) {
if (dump) PRINT("AENU:");
if (bs->type == V_ASN1_NEG_ENUMERATED)
if (dump) PRINT("-");
BIO *mem = BIO_new(BIO_s_mem());
for (i = 0; i < bs->length; i++) {
if (BIO_printf(mem, "%02X", bs->data[i]) <= 0) {
if (dump) PRINT("ERROR:AENU: problems printf-ing to BIO");
BIO_free(mem);
goto end;
}
}
if (dump) { BIO_PRINT(mem, "AENU:"); }
if (bs->length == 0) PRINT("00");
} else {
if (dump) PRINT("ERROR:AENU: BAD ENUMERATED");
}
ASN1_ENUMERATED_free(bs);
}
if (!nl && dump) PRINT(" ");
p += len;
if ((tag == V_ASN1_EOC) && (xclass == 0)) {
ret = 2; /* End of sequence */
goto end;
}
}
length -= len;
}
ret = 1;
end:
if (o) ASN1_OBJECT_free(o);
if (os) ASN1_OCTET_STRING_free(os);
*pp = p;
if (dump) PRINT("ret: "< 30)
BIO_snprintf(str,sizeof str,"",tag);
else
p = ASN1_tag2str(tag);
if (p2) {
if (BIO_printf(bp,fmt2,tag,p2) <= 0)
goto err;
} else {
if (BIO_printf(bp, fmt, p) <= 0)
goto err;
}
BIO_PRINT(bp, "A1PI:");
return(1);
err:
BIO_free(bp);
return(0);
}
//____________________________________________________________________________
bool XrdCryptosslX509::MatchesSAN(const char *fqdn, bool &hasSAN)
{
EPNAME("MatchesSAN");
// Statically allocated array for hostname lengths. RFC1035 limits
// valid lengths to 255 characters.
char san_fqdn[256];
// Assume we have no SAN extension. Failure may allow the caller to try
// using the common name before giving up.
hasSAN = false;
GENERAL_NAMES *gens = static_cast(X509_get_ext_d2i(cert,
NID_subject_alt_name, NULL, NULL));
if (!gens)
return false;
// Only an EEC is usable as a host certificate.
if (type != kEEC)
return false;
// All failures are under the notion that we have a SAN extension.
hasSAN = true;
if (!fqdn)
return false;
bool success = false;
for (int idx = 0; idx < sk_GENERAL_NAME_num(gens); idx++) {
GENERAL_NAME *gen;
ASN1_STRING *cstr;
gen = sk_GENERAL_NAME_value(gens, idx);
if (gen->type != GEN_DNS)
continue;
cstr = gen->d.dNSName;
if (ASN1_STRING_type(cstr) != V_ASN1_IA5STRING)
continue;
int san_fqdn_len = ASN1_STRING_length(cstr);
if (san_fqdn_len > 255)
continue;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
memcpy(san_fqdn, ASN1_STRING_get0_data(cstr), san_fqdn_len);
#else
memcpy(san_fqdn, ASN1_STRING_data(cstr), san_fqdn_len);
#endif
san_fqdn[san_fqdn_len] = '\0';
if (strlen(san_fqdn) != static_cast(san_fqdn_len)) // Avoid embedded null's.
continue;
DEBUG("Comparing SAN " << san_fqdn << " with " << fqdn);
if (MatchHostnames(san_fqdn, fqdn)) {
DEBUG("SAN " << san_fqdn << " matches with " << fqdn);
success = true;
break;
}
}
sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
return success;
}