2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
22 * AbstractPKIXTrustEngine.cpp
24 * A trust engine that uses X.509 trust anchors and CRLs associated with a KeyInfoSource
25 * to perform PKIX validation of signatures and certificates.
30 #include "security/AbstractPKIXTrustEngine.h"
31 #include "signature/KeyInfo.h"
32 #include "signature/Signature.h"
33 #include "security/CredentialCriteria.h"
34 #include "security/CredentialResolver.h"
35 #include "security/KeyInfoResolver.h"
36 #include "security/OpenSSLCryptoX509CRL.h"
37 #include "security/OpenSSLPathValidator.h"
38 #include "security/PKIXPathValidatorParams.h"
39 #include "security/SecurityHelper.h"
40 #include "security/X509Credential.h"
41 #include "signature/SignatureValidator.h"
43 #include "util/PathResolver.h"
46 #include <xercesc/util/XMLUniDefs.hpp>
47 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
49 using namespace xmlsignature;
50 using namespace xmltooling::logging;
51 using namespace xmltooling;
55 namespace xmltooling {
56 // Adapter between TrustEngine and PathValidator
57 class XMLTOOL_DLLLOCAL PKIXParams : public PKIXPathValidatorParams
59 const AbstractPKIXTrustEngine& m_trust;
60 const AbstractPKIXTrustEngine::PKIXValidationInfoIterator& m_pkixInfo;
61 vector<XSECCryptoX509CRL*> m_crls;
64 const AbstractPKIXTrustEngine& t,
65 const AbstractPKIXTrustEngine::PKIXValidationInfoIterator& pkixInfo,
66 const vector<XSECCryptoX509CRL*>* inlineCRLs
67 ) : m_trust(t), m_pkixInfo(pkixInfo) {
68 if (inlineCRLs && !inlineCRLs->empty()) {
70 m_crls.insert(m_crls.end(), pkixInfo.getCRLs().begin(), pkixInfo.getCRLs().end());
74 virtual ~PKIXParams() {}
76 int getVerificationDepth() const {
77 return m_pkixInfo.getVerificationDepth();
79 bool isAnyPolicyInhibited() const {
80 return m_trust.m_anyPolicyInhibit;
82 bool isPolicyMappingInhibited() const {
83 return m_trust.m_policyMappingInhibit;
85 const set<string>& getPolicies() const {
86 return m_trust.m_policyOIDs;
88 const vector<XSECCryptoX509*>& getTrustAnchors() const {
89 return m_pkixInfo.getTrustAnchors();
91 PKIXPathValidatorParams::revocation_t getRevocationChecking() const {
92 if (m_trust.m_checkRevocation.empty() || m_trust.m_checkRevocation == "off")
93 return PKIXPathValidatorParams::REVOCATION_OFF;
94 else if (m_trust.m_checkRevocation == "entityOnly")
95 return PKIXPathValidatorParams::REVOCATION_ENTITYONLY;
96 else if (m_trust.m_checkRevocation == "fullChain")
97 return PKIXPathValidatorParams::REVOCATION_FULLCHAIN;
98 return PKIXPathValidatorParams::REVOCATION_OFF;
100 const vector<XSECCryptoX509CRL*>& getCRLs() const {
101 return m_crls.empty() ? m_pkixInfo.getCRLs() : m_crls;
106 static XMLCh fullCRLChain[] = UNICODE_LITERAL_12(f,u,l,l,C,R,L,C,h,a,i,n);
107 static XMLCh checkRevocation[] = UNICODE_LITERAL_15(c,h,e,c,k,R,e,v,o,c,a,t,i,o,n);
108 static XMLCh policyMappingInhibit[] = UNICODE_LITERAL_20(p,o,l,i,c,y,M,a,p,p,i,n,g,I,n,h,i,b,i,t);
109 static XMLCh anyPolicyInhibit[] = UNICODE_LITERAL_16(a,n,y,P,o,l,i,c,y,I,n,h,i,b,i,t);
110 static XMLCh _PathValidator[] = UNICODE_LITERAL_13(P,a,t,h,V,a,l,i,d,a,t,o,r);
111 static XMLCh PolicyOID[] = UNICODE_LITERAL_9(P,o,l,i,c,y,O,I,D);
112 static XMLCh TrustedName[] = UNICODE_LITERAL_11(T,r,u,s,t,e,d,N,a,m,e);
113 static XMLCh type[] = UNICODE_LITERAL_4(t,y,p,e);
116 AbstractPKIXTrustEngine::PKIXValidationInfoIterator::PKIXValidationInfoIterator()
120 AbstractPKIXTrustEngine::PKIXValidationInfoIterator::~PKIXValidationInfoIterator()
124 AbstractPKIXTrustEngine::AbstractPKIXTrustEngine(const xercesc::DOMElement* e)
126 m_checkRevocation(XMLHelper::getAttrString(e, nullptr, checkRevocation)),
127 m_fullCRLChain(XMLHelper::getAttrBool(e, false, fullCRLChain)),
128 m_policyMappingInhibit(XMLHelper::getAttrBool(e, false, policyMappingInhibit)),
129 m_anyPolicyInhibit(XMLHelper::getAttrBool(e, false, anyPolicyInhibit))
131 if (m_fullCRLChain) {
132 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").warn(
133 "fullCRLChain option is deprecated, setting checkRevocation to \"fullChain\""
135 m_checkRevocation = "fullChain";
137 else if (m_checkRevocation == "fullChain") {
138 m_fullCRLChain = true; // in case anything's using this
141 xercesc::DOMElement* c = XMLHelper::getFirstChildElement(e);
143 if (c->hasChildNodes()) {
144 auto_ptr_char v(c->getTextContent());
145 if (v.get() && *v.get()) {
146 if (XMLString::equals(c->getLocalName(), PolicyOID))
147 m_policyOIDs.insert(v.get());
148 else if (XMLString::equals(c->getLocalName(), TrustedName))
149 m_trustedNames.insert(v.get());
152 else if (XMLString::equals(c->getLocalName(), _PathValidator)) {
154 string t = XMLHelper::getAttrString(c, nullptr, type);
156 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").info(
157 "building PathValidator of type %s", t.c_str()
159 PathValidator* pv = XMLToolingConfig::getConfig().PathValidatorManager.newPlugin(t.c_str(), c);
160 OpenSSLPathValidator* ospv = dynamic_cast<OpenSSLPathValidator*>(pv);
163 throw XMLSecurityException("PathValidator doesn't support OpenSSL interface.");
165 m_pathValidators.push_back(ospv);
168 catch (exception& ex) {
169 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").error(
170 "error building PathValidator: %s", ex.what()
174 c = XMLHelper::getNextSiblingElement(c);
177 if (m_pathValidators.empty()) {
178 m_pathValidators.push_back(
179 dynamic_cast<OpenSSLPathValidator*>(
180 XMLToolingConfig::getConfig().PathValidatorManager.newPlugin(PKIX_PATHVALIDATOR, e)
186 AbstractPKIXTrustEngine::~AbstractPKIXTrustEngine()
188 for_each(m_pathValidators.begin(), m_pathValidators.end(), xmltooling::cleanup<PathValidator>());
191 bool AbstractPKIXTrustEngine::checkEntityNames(
192 X509* certEE, const CredentialResolver& credResolver, const CredentialCriteria& criteria
195 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
197 // We resolve to a set of trusted credentials.
198 vector<const Credential*> creds;
199 credResolver.resolve(creds,&criteria);
201 // Build a list of acceptable names.
202 set<string> trustednames = m_trustedNames;
203 if (log.isDebugEnabled()) {
204 for (set<string>::const_iterator n=m_trustedNames.begin(); n!=m_trustedNames.end(); n++) {
205 log.debug("adding to list of trusted names (%s)", n->c_str());
208 if (criteria.getPeerName()) {
209 trustednames.insert(criteria.getPeerName());
210 log.debug("adding to list of trusted names (%s)", criteria.getPeerName());
212 for (vector<const Credential*>::const_iterator cred = creds.begin(); cred!=creds.end(); ++cred) {
213 trustednames.insert((*cred)->getKeyNames().begin(), (*cred)->getKeyNames().end());
214 if (log.isDebugEnabled()) {
215 for (set<string>::const_iterator n=(*cred)->getKeyNames().begin(); n!=(*cred)->getKeyNames().end(); n++) {
216 log.debug("adding to list of trusted names (%s)", n->c_str());
221 X509_NAME* subject=X509_get_subject_name(certEE);
223 // One way is a direct match to the subject DN.
224 // Seems that the way to do the compare is to write the X509_NAME into a BIO.
225 BIO* b = BIO_new(BIO_s_mem());
226 BIO* b2 = BIO_new(BIO_s_mem());
227 // The flags give us LDAP order instead of X.500, with a comma separator.
228 X509_NAME_print_ex(b,subject,0,XN_FLAG_RFC2253);
230 // The flags give us LDAP order instead of X.500, with a comma plus space separator.
231 X509_NAME_print_ex(b2,subject,0,XN_FLAG_RFC2253 + XN_FLAG_SEP_CPLUS_SPC - XN_FLAG_SEP_COMMA_PLUS);
234 BUF_MEM* bptr=nullptr;
235 BUF_MEM* bptr2=nullptr;
236 BIO_get_mem_ptr(b, &bptr);
237 BIO_get_mem_ptr(b2, &bptr2);
239 if (bptr && bptr->length > 0 && log.isDebugEnabled()) {
240 string subjectstr(bptr->data, bptr->length);
241 log.debug("certificate subject: %s", subjectstr.c_str());
244 // Check each keyname.
245 for (set<string>::const_iterator n=trustednames.begin(); bptr && bptr2 && n!=trustednames.end(); n++) {
246 #ifdef HAVE_STRCASECMP
247 if ((n->length() == bptr->length && !strncasecmp(n->c_str(), bptr->data, bptr->length)) ||
248 (n->length() == bptr2->length && !strncasecmp(n->c_str(), bptr2->data, bptr2->length))) {
250 if ((n->length() == bptr->length && !strnicmp(n->c_str(), bptr->data, bptr->length)) ||
251 (n->length() == bptr2->length && !strnicmp(n->c_str(), bptr2->data, bptr2->length))) {
253 log.debug("matched full subject DN to a key name (%s)", n->c_str());
262 log.debug("unable to match DN, trying TLS subjectAltName match");
263 STACK_OF(GENERAL_NAME)* altnames=(STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(certEE, NID_subject_alt_name, nullptr, nullptr);
265 int numalts = sk_GENERAL_NAME_num(altnames);
266 for (int an=0; an<numalts; an++) {
267 const GENERAL_NAME* check = sk_GENERAL_NAME_value(altnames, an);
268 if (check->type==GEN_DNS || check->type==GEN_URI) {
269 const char* altptr = (char*)ASN1_STRING_data(check->d.ia5);
270 const int altlen = ASN1_STRING_length(check->d.ia5);
271 for (set<string>::const_iterator n=trustednames.begin(); n!=trustednames.end(); n++) {
272 #ifdef HAVE_STRCASECMP
273 if ((check->type==GEN_DNS && n->length()==altlen && !strncasecmp(altptr,n->c_str(),altlen))
275 if ((check->type==GEN_DNS && n->length()==altlen && !strnicmp(altptr,n->c_str(),altlen))
277 || (check->type==GEN_URI && n->length()==altlen && !strncmp(altptr,n->c_str(),altlen))) {
278 log.debug("matched DNS/URI subjectAltName to a key name (%s)", n->c_str());
279 GENERAL_NAMES_free(altnames);
286 GENERAL_NAMES_free(altnames);
288 log.debug("unable to match subjectAltName, trying TLS CN match");
290 // Fetch the last CN RDN.
291 char* peer_CN = nullptr;
293 while ((j=X509_NAME_get_index_by_NID(subject, NID_commonName, i)) >= 0)
296 ASN1_STRING* tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject, i));
297 // Copied in from libcurl.
298 /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input
299 is already UTF-8 encoded. We check for this case and copy the raw
300 string manually to avoid the problem. */
301 if(tmp && ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
302 j = ASN1_STRING_length(tmp);
304 peer_CN = (char*)OPENSSL_malloc(j + 1);
305 memcpy(peer_CN, ASN1_STRING_data(tmp), j);
309 else /* not a UTF8 name */ {
310 j = ASN1_STRING_to_UTF8(reinterpret_cast<unsigned char**>(&peer_CN), tmp);
313 for (set<string>::const_iterator n=trustednames.begin(); n!=trustednames.end(); n++) {
314 #ifdef HAVE_STRCASECMP
315 if (n->length() == j && !strncasecmp(peer_CN, n->c_str(), j)) {
317 if (n->length() == j && !strnicmp(peer_CN, n->c_str(), j)) {
319 log.debug("matched subject CN to a key name (%s)", n->c_str());
321 OPENSSL_free(peer_CN);
326 OPENSSL_free(peer_CN);
329 log.warn("no common name in certificate subject");
333 log.error("certificate has no subject?!");
339 bool AbstractPKIXTrustEngine::validateWithCRLs(
341 STACK_OF(X509)* certChain,
342 const CredentialResolver& credResolver,
343 CredentialCriteria* criteria,
344 const vector<XSECCryptoX509CRL*>* inlineCRLs
348 NDC ndc("validateWithCRLs");
350 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
353 log.error("X.509 credential was NULL, unable to perform validation");
357 if (criteria && criteria->getPeerName() && *(criteria->getPeerName())) {
358 log.debug("checking that the certificate name is acceptable");
359 if (criteria && criteria->getUsage()==Credential::UNSPECIFIED_CREDENTIAL)
360 criteria->setUsage(Credential::SIGNING_CREDENTIAL);
361 if (!checkEntityNames(certEE,credResolver,*criteria)) {
362 log.error("certificate name was not acceptable");
366 else if (!m_trustedNames.empty()) {
367 log.debug("checking that the certificate name is acceptable");
368 CredentialCriteria cc;
369 cc.setUsage(Credential::SIGNING_CREDENTIAL);
370 if (!checkEntityNames(certEE,credResolver,cc)) {
371 log.error("certificate name was not acceptable");
376 log.debug("performing certificate path validation...");
378 auto_ptr<PKIXValidationInfoIterator> pkix(getPKIXValidationInfoIterator(credResolver, criteria));
379 while (pkix->next()) {
380 PKIXParams params(*this, *pkix.get(), inlineCRLs);
381 for (vector<OpenSSLPathValidator*>::const_iterator v = m_pathValidators.begin(); v != m_pathValidators.end(); ++v) {
382 if ((*v)->validate(certEE, certChain, params)) {
388 log.debug("failed to validate certificate chain using supplied PKIX information");
392 bool AbstractPKIXTrustEngine::validate(
394 STACK_OF(X509)* certChain,
395 const CredentialResolver& credResolver,
396 CredentialCriteria* criteria
399 return validateWithCRLs(certEE,certChain,credResolver,criteria);
402 bool AbstractPKIXTrustEngine::validate(
403 XSECCryptoX509* certEE,
404 const vector<XSECCryptoX509*>& certChain,
405 const CredentialResolver& credResolver,
406 CredentialCriteria* criteria
413 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").error("X.509 credential was NULL, unable to perform validation");
416 else if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
417 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").error("only the OpenSSL XSEC provider is supported");
421 STACK_OF(X509)* untrusted=sk_X509_new_null();
422 for (vector<XSECCryptoX509*>::const_iterator i=certChain.begin(); i!=certChain.end(); ++i)
423 sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
425 bool ret = validate(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, credResolver, criteria);
426 sk_X509_free(untrusted);
430 bool AbstractPKIXTrustEngine::validate(
432 const CredentialResolver& credResolver,
433 CredentialCriteria* criteria
439 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
441 const KeyInfoResolver* inlineResolver = m_keyInfoResolver;
443 inlineResolver = XMLToolingConfig::getConfig().getKeyInfoResolver();
444 if (!inlineResolver) {
445 log.error("unable to perform PKIX validation, no KeyInfoResolver available");
449 // Pull the certificate chain out of the signature.
450 X509Credential* x509cred;
451 auto_ptr<Credential> cred(inlineResolver->resolve(&sig,X509Credential::RESOLVE_CERTS|X509Credential::RESOLVE_CRLS));
452 if (!cred.get() || !(x509cred=dynamic_cast<X509Credential*>(cred.get()))) {
453 log.error("unable to perform PKIX validation, signature does not contain any certificates");
456 const vector<XSECCryptoX509*>& certs = x509cred->getEntityCertificateChain();
458 log.error("unable to perform PKIX validation, signature does not contain any certificates");
462 log.debug("validating signature using certificate from within the signature");
464 // Find and save off a pointer to the certificate that unlocks the object.
465 // Most of the time, this will be the first one anyway.
466 XSECCryptoX509* certEE=nullptr;
467 SignatureValidator keyValidator;
468 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); !certEE && i!=certs.end(); ++i) {
470 auto_ptr<XSECCryptoKey> key((*i)->clonePublicKey());
471 keyValidator.setKey(key.get());
472 keyValidator.validate(&sig);
473 log.debug("signature verified with key inside signature, attempting certificate validation...");
476 catch (ValidationException& ex) {
477 log.debug(ex.what());
482 log.debug("failed to verify signature with embedded certificates");
485 else if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
486 log.error("only the OpenSSL XSEC provider is supported");
490 STACK_OF(X509)* untrusted=sk_X509_new_null();
491 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); i!=certs.end(); ++i)
492 sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
493 const vector<XSECCryptoX509CRL*>& crls = x509cred->getCRLs();
494 bool ret = validateWithCRLs(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, credResolver, criteria, &crls);
495 sk_X509_free(untrusted);
499 bool AbstractPKIXTrustEngine::validate(
500 const XMLCh* sigAlgorithm,
505 const CredentialResolver& credResolver,
506 CredentialCriteria* criteria
512 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
515 log.error("unable to perform PKIX validation, KeyInfo not present");
519 const KeyInfoResolver* inlineResolver = m_keyInfoResolver;
521 inlineResolver = XMLToolingConfig::getConfig().getKeyInfoResolver();
522 if (!inlineResolver) {
523 log.error("unable to perform PKIX validation, no KeyInfoResolver available");
527 // Pull the certificate chain out of the signature.
528 X509Credential* x509cred;
529 auto_ptr<Credential> cred(inlineResolver->resolve(keyInfo,X509Credential::RESOLVE_CERTS));
530 if (!cred.get() || !(x509cred=dynamic_cast<X509Credential*>(cred.get()))) {
531 log.error("unable to perform PKIX validation, KeyInfo does not contain any certificates");
534 const vector<XSECCryptoX509*>& certs = x509cred->getEntityCertificateChain();
536 log.error("unable to perform PKIX validation, KeyInfo does not contain any certificates");
540 log.debug("validating signature using certificate from within KeyInfo");
542 // Find and save off a pointer to the certificate that unlocks the object.
543 // Most of the time, this will be the first one anyway.
544 XSECCryptoX509* certEE=nullptr;
545 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); !certEE && i!=certs.end(); ++i) {
547 auto_ptr<XSECCryptoKey> key((*i)->clonePublicKey());
548 if (Signature::verifyRawSignature(key.get(), sigAlgorithm, sig, in, in_len)) {
549 log.debug("signature verified with key inside signature, attempting certificate validation...");
553 catch (SignatureException& ex) {
554 log.debug(ex.what());
559 log.debug("failed to verify signature with embedded certificates");
562 else if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
563 log.error("only the OpenSSL XSEC provider is supported");
567 STACK_OF(X509)* untrusted=sk_X509_new_null();
568 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); i!=certs.end(); ++i)
569 sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
570 const vector<XSECCryptoX509CRL*>& crls = x509cred->getCRLs();
571 bool ret = validateWithCRLs(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, credResolver, criteria, &crls);
572 sk_X509_free(untrusted);