/******************************************************************************/ /* */ /* 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; }