/******************************************************************************/ /* */ /* X r d C r y p t o X 5 0 9 C h a i n . 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. */ /* */ /******************************************************************************/ #include #include #include "XrdCrypto/XrdCryptoX509Chain.hh" #include "XrdCrypto/XrdCryptoTrace.hh" // ---------------------------------------------------------------------------// // // // XrdCryptoX509Chain // // // // Light single-linked list for managing stacks of XrdCryptoX509* objects // // // // ---------------------------------------------------------------------------// // For test dumps, to avoid interfering with the trace mutex #define LOCDUMP(y) { cerr << epname << ":" << y << endl; } // Description of errors static const char *X509ChainErrStr[] = { "no error condition occured", // 0 "chain is inconsistent", // 1 "size exceeds max allowed depth", // 2 "invalid or missing CA", // 3 "certificate missing", // 4 "unexpected certificate type", // 5 "names invalid or missing", // 6 "certificate has been revoked", // 7 "certificate expired", // 8 "extension not found", // 9 "signature verification failed", // 10 "issuer had no signing rights", // 11 "CA issued by another CA", // 12 "invalid or missing EEC", // 13 "too many EEC", // 14 "invalid proxy" // 15 }; //___________________________________________________________________________ XrdCryptoX509Chain::XrdCryptoX509Chain(XrdCryptoX509 *c) { // Constructor previous = 0; current = 0; begin = 0; end = 0; effca = 0; size = 0; lastError = ""; caname = ""; eecname = ""; cahash = ""; eechash = ""; statusCA = kUnknown; if (c) { XrdCryptoX509ChainNode *f = new XrdCryptoX509ChainNode(c,0); current = begin = end = f; size++; // // If CA verify it and save result if (c->type == XrdCryptoX509::kCA) { caname = c->Subject(); cahash = c->SubjectHash(); EX509ChainErr ecode = kNone; if (!Verify(ecode, "CA: ",XrdCryptoX509::kCA, 0, c, c)) statusCA = kInvalid; else statusCA = kValid; } // Search for the effective CA SetEffectiveCA(); } } //___________________________________________________________________________ XrdCryptoX509Chain::XrdCryptoX509Chain(XrdCryptoX509Chain *ch) { // Copy constructor previous = 0; current = 0; begin = 0; end = 0; effca = 0; size = 0; lastError = ch->LastError(); caname = ch->CAname(); eecname = ch->EECname(); cahash = ch->CAhash(); eechash = ch->EEChash(); statusCA = ch->StatusCA(); XrdCryptoX509 *c = ch->Begin(); while (c) { XrdCryptoX509ChainNode *nc = new XrdCryptoX509ChainNode(c,0); if (!begin) begin = nc; if (end) end->SetNext(nc); end = nc; if (c == ch->EffCA()) effca = nc; size++; // Go to Next c = ch->Next(); } } //___________________________________________________________________________ XrdCryptoX509Chain::~XrdCryptoX509Chain() { // Destructor XrdCryptoX509ChainNode *n = 0; XrdCryptoX509ChainNode *c = begin; while (c) { n = c->Next(); delete (c); c = n; } } //___________________________________________________________________________ void XrdCryptoX509Chain::Cleanup(bool keepCA) { // Destructs content of nodes AND their content // If keepCA is true, the top CA is kept XrdCryptoX509ChainNode *n = 0; XrdCryptoX509ChainNode *c = begin; while (c) { n = c->Next(); if (c->Cert() && (!keepCA || (c->Cert()->type != XrdCryptoX509::kCA))) delete (c->Cert()); delete (c); c = n; } // Reset previous = 0; current = 0; begin = 0; end = 0; effca = 0; size = 0; lastError = ""; caname = ""; eecname = ""; cahash = ""; eechash = ""; statusCA = kUnknown; } //___________________________________________________________________________ bool XrdCryptoX509Chain::CheckCA(bool checkselfsigned) { // Search the list for a valid CA and set it at top. // Search stops when a valid CA is found; an invalid CA is flagged. // A second CA is always ignored. // Signature check failures are accepted if 'checkselfsigned' is false. // Return 1 if found, 0 otherwise; lastError is filled with the reason of // failure, if any. XrdCryptoX509 *xc = 0; XrdCryptoX509ChainNode *n = 0; XrdCryptoX509ChainNode *c = begin; XrdCryptoX509ChainNode *p = 0; lastError = ""; while (c) { n = c->Next(); xc = c->Cert(); if (xc && xc->type == XrdCryptoX509::kCA) { caname = xc->Subject(); cahash = xc->SubjectHash(); EX509ChainErr ecode = kNone; bool CAok = Verify(ecode, "CA: ",XrdCryptoX509::kCA, 0, xc, xc); if (!CAok && (ecode != kVerifyFail || checkselfsigned)) { statusCA = kInvalid; lastError += X509ChainError(ecode); } else { statusCA = kValid; if (p) { // Move at top p->SetNext(c->Next()); c->SetNext(begin); if (end == c) end = p; begin = c; } return 1; } } p = c; // Previous node c = n; } // Found nothing return 0; } //___________________________________________________________________________ const char *XrdCryptoX509Chain::X509ChainError(EX509ChainErr e) { // Return error string return X509ChainErrStr[e]; } //___________________________________________________________________________ XrdCryptoX509ChainNode *XrdCryptoX509Chain::Find(XrdCryptoX509 *c) { // Find node containing bucket b XrdCryptoX509ChainNode *nd = begin; for (; nd; nd = nd->Next()) { if (nd->Cert() == c) return nd; } return (XrdCryptoX509ChainNode *)0; } //___________________________________________________________________________ void XrdCryptoX509Chain::PutInFront(XrdCryptoX509 *c) { // Add at the beginning of the list // Check to avoid duplicates if (!Find(c)) { XrdCryptoX509ChainNode *nc = new XrdCryptoX509ChainNode(c,begin); begin = nc; if (!end) end = nc; size++; } // Search for the effective CA (the last one, in case of subCAs) SetEffectiveCA(); } //___________________________________________________________________________ void XrdCryptoX509Chain::InsertAfter(XrdCryptoX509 *c, XrdCryptoX509 *cp) { // Add or move certificate 'c' after certificate 'cp'; if 'cp' is not // in the list, push-back XrdCryptoX509ChainNode *nc = Find(c); XrdCryptoX509ChainNode *ncp = Find(cp); if (ncp) { // Create a new node, if not there if (!nc) { nc = new XrdCryptoX509ChainNode(c,ncp->Next()); size++; } // Update pointers ncp->SetNext(nc); if (end == ncp) end = nc; } else { // Reference certificate not in the list // If new, add in last position; otherwise leave it where it is if (!nc) PushBack(c); } // Search for the effective CA (the last one, in case of subCAs) SetEffectiveCA(); } //___________________________________________________________________________ void XrdCryptoX509Chain::PushBack(XrdCryptoX509 *c) { // Add at the end of the list // Check to avoid duplicates if (!Find(c)) { XrdCryptoX509ChainNode *nc = new XrdCryptoX509ChainNode(c,0); if (!begin) begin = nc; if (end) end->SetNext(nc); end = nc; size++; } // Search for the effective CA (the last one, in case of subCAs) SetEffectiveCA(); } //___________________________________________________________________________ void XrdCryptoX509Chain::Remove(XrdCryptoX509 *c) { // Remove node containing bucket b XrdCryptoX509ChainNode *curr = current; XrdCryptoX509ChainNode *prev = previous; if (!curr || curr->Cert() != c || (prev && curr != prev->Next())) { // We need first to find the address curr = begin; prev = 0; for (; curr; curr = curr->Next()) { if (curr->Cert() == c) break; prev = curr; } } // The certificate is not in the list if (!curr) return; // // If this was the top CA update the related information if (c->type == XrdCryptoX509::kCA && curr == begin) { // There may be other CAs in the chain, but we will // check when needed statusCA = kUnknown; caname = ""; cahash = ""; } // Now we have all the information to remove if (prev) { if (curr != end) { current = curr->Next(); prev->SetNext(current); previous = prev; } else { end = prev; previous = end; current = 0; prev->SetNext(current); } } else if (curr == begin) { // First buffer current = curr->Next(); begin = current; previous = 0; } // Cleanup and update size delete curr; size--; // Search for the effective CA (the last one, in case of subCAs) SetEffectiveCA(); } //___________________________________________________________________________ XrdCryptoX509 *XrdCryptoX509Chain::Begin() { // Iterator functionality: init previous = 0; current = begin; if (current) return current->Cert(); return (XrdCryptoX509 *)0; } //___________________________________________________________________________ XrdCryptoX509 *XrdCryptoX509Chain::Next() { // Iterator functionality: get next previous = current; if (current) { current = current->Next(); if (current) return current->Cert(); } return (XrdCryptoX509 *)0; } //___________________________________________________________________________ XrdCryptoX509 *XrdCryptoX509Chain::SearchByIssuer(const char *issuer, ESearchMode mode) { // Return first certificate in the chain with issuer // Match according to mode. XrdCryptoX509ChainNode *cn = FindIssuer(issuer, mode); // We are done return ((cn) ? cn->Cert() : (XrdCryptoX509 *)0); } //___________________________________________________________________________ XrdCryptoX509 *XrdCryptoX509Chain::SearchBySubject(const char *subject, ESearchMode mode) { // Return first certificate in the chain with subject // Match according to mode. XrdCryptoX509ChainNode *cn = FindSubject(subject, mode); // We are done return ((cn) ? cn->Cert() : (XrdCryptoX509 *)0); } //___________________________________________________________________________ XrdCryptoX509ChainNode *XrdCryptoX509Chain::FindIssuer(const char *issuer, ESearchMode mode, XrdCryptoX509ChainNode **prev) { // Return first chain node with certificate having issuer // Match according to mode. // Make sure we got something to compare if (!issuer) return (XrdCryptoX509ChainNode *)0; XrdCryptoX509ChainNode *cp = 0; XrdCryptoX509ChainNode *n = 0; XrdCryptoX509ChainNode *cn = begin; XrdCryptoX509 *c = 0; while (cn) { n = cn->Next(); c = cn->Cert(); if(c) { const char *pi = c->Issuer(); if (pi) { if (mode == kExact) { if (!strcmp(pi, issuer)) break; } else if (mode == kBegin) { if (strstr(pi, issuer) == c->Issuer()) break; } else if (mode == kEnd) { int ibeg = strlen(pi) - strlen(issuer); if (!strcmp(pi + ibeg, issuer)) break; } } } c = 0; cp = cn; // previous cn = n; } // return previous, if requested if (prev) *prev = (cn) ? cp : 0; // We are done return ((cn) ? cn : (XrdCryptoX509ChainNode *)0); } //___________________________________________________________________________ XrdCryptoX509ChainNode *XrdCryptoX509Chain::FindSubject(const char *subject, ESearchMode mode, XrdCryptoX509ChainNode **prev) { // Return first chain node with certificate having subject // Match according to mode. // Make sure we got something to compare if (!subject) return (XrdCryptoX509ChainNode *)0; XrdCryptoX509ChainNode *cp = 0; XrdCryptoX509ChainNode *n = 0; XrdCryptoX509ChainNode *cn = begin; XrdCryptoX509 *c = 0; while (cn) { n = cn->Next(); c = cn->Cert(); const char *ps = c ? c->Subject() : 0; if (c && ps) { if (mode == kExact) { if (!strcmp(ps, subject)) break; } else if (mode == kBegin) { if (strstr(ps, subject) == ps) break; } else if (mode == kEnd) { int sbeg = strlen(ps) - strlen(subject); if (!strcmp(ps + sbeg, subject)) break; } } c = 0; cp = cn; // previous cn = n; } // return previous, if requested if (prev) *prev = (cn) ? cp : 0; // We are done return ((cn) ? cn : (XrdCryptoX509ChainNode *)0); } //___________________________________________________________________________ void XrdCryptoX509Chain::Dump() { // Dump content EPNAME("X509Chain::Dump"); LOCDUMP("//------------------Dumping X509 chain content ------------------//"); LOCDUMP("//"); LOCDUMP("// Chain instance: "<Next(); if (c->Cert()) { LOCDUMP("// Issuer: "<Cert()->IssuerHash()<< " Subject: "<Cert()->SubjectHash()<< " Type: "<Cert()->Type()); } c = n; } LOCDUMP("//"); LOCDUMP("//---------------------------- END ------------------------------//") } //___________________________________________________________________________ int XrdCryptoX509Chain::Reorder() { // Reorder certificates in such a way that certificate n is the // issuer of certificate n+1 . // Return -1 if inconsistencies are found. EPNAME("X509Chain::Reorder"); if (size < 2) { DEBUG("Nothing to reorder (size: "<Cert()->Issuer(),kExact,&npp)) || nn == nr) break; np = nr; nr = nr->Next(); } // Move it in first position if not yet there if (nr && nr != begin) { np->SetNext(nr->Next()); // short cut old position nr->SetNext(begin); // set our next to present begin if (end == nr) // Update end end = np; begin = nr; // set us as begin // Flag if not CA: we do not check validity here if (nr->Cert()->type != XrdCryptoX509::kCA) { statusCA = kAbsent; } else if (caname.length() <= 0) { // Set the CA properties only if not done already to avoid overwriting // the result of previous analysis caname = nr->Cert()->Subject(); cahash = nr->Cert()->SubjectHash(); statusCA = kUnknown; } } int left = size-1; np = begin; while (np) { if (np->Cert()) { const char *pi = np->Cert()->Subject(); // Set the EEC name, if not yet done if (np->Cert()->type == XrdCryptoX509::kEEC && eecname.length() <= 0) { eecname = pi; eechash = np->Cert()->SubjectHash(); } npp = np; nc = np->Next(); while (nc) { if (nc->Cert() && !strcmp(pi, nc->Cert()->Issuer())) { left--; if (npp != np) { npp->SetNext(nc->Next()); // drop child from previous pos nc->SetNext(np->Next()); // set child next as our present np->SetNext(nc); // set our next as child if (nc == end) end = npp; } break; } npp = nc; nc = nc->Next(); } } np = np->Next(); } // Search for the effective CA (the last one, in case of subCAs) SetEffectiveCA(); // Check consistency if (left > 0) { DEBUG("Inconsistency found: "<Cert()) { if (np->Cert()->type == XrdCryptoX509::kCA) { if (!effca || (effca && !(strcmp(effca->Cert()->SubjectHash(), np->Cert()->IssuerHash())))) effca = np; } } np = np->Next(); } if (effca && effca->Cert()) { caname = effca->Cert()->Subject(); cahash = effca->Cert()->SubjectHash(); } } //___________________________________________________________________________ bool XrdCryptoX509Chain::Verify(EX509ChainErr &errcode, x509ChainVerifyOpt_t *vopt) { // Verify cross signatures of the chain EPNAME("X509Chain::Verify"); errcode = kNone; // Do nothing if empty if (size < 1) { DEBUG("Nothing to verify (size: "<when : (int)time(0); int plen = (vopt) ? vopt->pathlen : -1; bool chkss = (vopt) ? (vopt->opt & kOptsCheckSelfSigned) : 1; // // Global path depth length consistency check if (plen > -1 && plen < size) { errcode = kTooMany; lastError = "checking path depth: "; lastError += X509ChainError(errcode); } // // Check the first certificate: it MUST be of CA type, valid, // self-signed if (!CheckCA(chkss)) { errcode = kNoCA; lastError = X509ChainError(errcode); return 0; } // // Analyse the rest XrdCryptoX509ChainNode *node = begin; XrdCryptoX509 *xsig = node->Cert(); // Signing certificate XrdCryptoX509 *xcer = 0; // Certificate under exam node = node->Next(); while (node) { // Attache to certificate xcer = node->Cert(); // Standard verification if (!Verify(errcode, "cert: ", XrdCryptoX509::kUnknown, when, xcer, xsig)) return 0; // Get next xsig = xcer; node = node->Next(); } // We are done (successfully!) return 1; } //___________________________________________________________________________ int XrdCryptoX509Chain::CheckValidity(bool outatfirst, int when) { // Check validity at 'when' of certificates in the chain and return // the number of invalid certificates. // If 'outatfirst' return after the first invalid has been // found. EPNAME("X509Chain::CheckValidity"); int ninv = 0; // Do nothing if empty if (size < 1) { DEBUG("Nothing to verify (size: "<Cert(); if (c) { if (!(c->IsValid(when))) { ninv++; DEBUG("invalid certificate found"); if (outatfirst) return ninv; } } else { ninv++; DEBUG("found node without certificate"); if (outatfirst) return ninv; } // Get next nc = nc->Next(); } // We are done return ninv; } //___________________________________________________________________________ bool XrdCryptoX509Chain::Verify(EX509ChainErr &errcode, const char *msg, XrdCryptoX509::EX509Type type, int when, XrdCryptoX509 *xcer, XrdCryptoX509 *xsig, XrdCryptoX509Crl *crl) { // Internal verification method // Certificate must be defined if (!xcer) { errcode = kNoCertificate; lastError = msg; lastError += X509ChainError(errcode); return 0; } // Type should be the one expected if (type != XrdCryptoX509::kUnknown && xcer->type != type) { errcode = kInvalidType; lastError = msg; lastError += X509ChainError(errcode); return 0; } // Must not be revoked (check only if required) if (crl) { // Get certificate serial number XrdOucString sn = xcer->SerialNumberString(); if (crl->IsRevoked(sn.c_str(), when)) { errcode = kRevoked; lastError = msg; lastError += X509ChainError(errcode); return 0; } } // Check validity in time if (when >= 0 && !(xcer->IsValid(when))) { errcode = kExpired; lastError = msg; lastError += X509ChainError(errcode); return 0; } // Check signature if (!xsig || !(xcer->Verify(xsig))) { errcode = kVerifyFail; lastError = msg; lastError += X509ChainError(errcode); return 0; } // We are done return 1; } //_____________________________________________________________________________ const char *XrdCryptoX509Chain::CAname() { // Return subject name of the CA in the chain EPNAME("X509Chain::CAname"); // If we do not have it already, try extraction if (caname.length() <= 0 && statusCA == kUnknown) { if (!CheckCA()) { DEBUG("CA not found in chain"); return (const char *)0; } } // return what we have return (caname.length() > 0) ? caname.c_str() : (const char *)0; } //_____________________________________________________________________________ const char *XrdCryptoX509Chain::EECname() { // Return subject name of the EEC in the chain EPNAME("X509Chain::EECname"); // If we do not have it already, try extraction if (eecname.length() <= 0) { XrdCryptoX509ChainNode *c = begin; while (c) { if (c->Cert()->type == XrdCryptoX509::kEEC) { eecname = c->Cert()->Subject(); break; } c = c->Next(); } if (eecname.length() <= 0) { DEBUG("EEC not found in chain"); return (const char *)0; } } // return what we have return (eecname.length() > 0) ? eecname.c_str() : (const char *)0; } //_____________________________________________________________________________ const char *XrdCryptoX509Chain::CAhash() { // Return the subject name hash of the CA in the chain EPNAME("X509Chain::CAhash"); // If we do not have it already, try extraction if (cahash.length() <= 0 && statusCA == kUnknown) { if (!CheckCA()) { DEBUG("CA not found in chain"); return (const char *)0; } } // return what we have return (cahash.length() > 0) ? cahash.c_str() : (const char *)0; } //_____________________________________________________________________________ const char *XrdCryptoX509Chain::EEChash() { // Return the subject name hash of the EEC in the chain EPNAME("X509Chain::EEChash"); // If we do not have it already, try extraction if (eechash.length() <= 0) { XrdCryptoX509ChainNode *c = begin; while (c) { if (c->Cert()->type == XrdCryptoX509::kEEC) { eechash = c->Cert()->SubjectHash(); break; } c = c->Next(); } if (eechash.length() <= 0) { DEBUG("EEC not found in chain"); return (const char *)0; } } // return what we have return (eechash.length() > 0) ? eechash.c_str() : (const char *)0; }