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;
53 using boost::ptr_vector;
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()
190 bool AbstractPKIXTrustEngine::checkEntityNames(
191 X509* certEE, const CredentialResolver& credResolver, const CredentialCriteria& criteria
194 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
196 // We resolve to a set of trusted credentials.
197 vector<const Credential*> creds;
198 credResolver.resolve(creds,&criteria);
200 // Build a list of acceptable names.
201 set<string> trustednames = m_trustedNames;
202 if (log.isDebugEnabled()) {
203 for (set<string>::const_iterator n=m_trustedNames.begin(); n!=m_trustedNames.end(); n++) {
204 log.debug("adding to list of trusted names (%s)", n->c_str());
207 if (criteria.getPeerName()) {
208 trustednames.insert(criteria.getPeerName());
209 log.debug("adding to list of trusted names (%s)", criteria.getPeerName());
211 for (vector<const Credential*>::const_iterator cred = creds.begin(); cred!=creds.end(); ++cred) {
212 trustednames.insert((*cred)->getKeyNames().begin(), (*cred)->getKeyNames().end());
213 if (log.isDebugEnabled()) {
214 for (set<string>::const_iterator n=(*cred)->getKeyNames().begin(); n!=(*cred)->getKeyNames().end(); n++) {
215 log.debug("adding to list of trusted names (%s)", n->c_str());
220 X509_NAME* subject=X509_get_subject_name(certEE);
222 // One way is a direct match to the subject DN.
223 // Seems that the way to do the compare is to write the X509_NAME into a BIO.
224 BIO* b = BIO_new(BIO_s_mem());
225 BIO* b2 = BIO_new(BIO_s_mem());
226 // The flags give us LDAP order instead of X.500, with a comma separator.
227 X509_NAME_print_ex(b,subject,0,XN_FLAG_RFC2253);
229 // The flags give us LDAP order instead of X.500, with a comma plus space separator.
230 X509_NAME_print_ex(b2,subject,0,XN_FLAG_RFC2253 + XN_FLAG_SEP_CPLUS_SPC - XN_FLAG_SEP_COMMA_PLUS);
233 BUF_MEM* bptr=nullptr;
234 BUF_MEM* bptr2=nullptr;
235 BIO_get_mem_ptr(b, &bptr);
236 BIO_get_mem_ptr(b2, &bptr2);
238 if (bptr && bptr->length > 0 && log.isDebugEnabled()) {
239 string subjectstr(bptr->data, bptr->length);
240 log.debug("certificate subject: %s", subjectstr.c_str());
243 // Check each keyname.
244 for (set<string>::const_iterator n=trustednames.begin(); bptr && bptr2 && n!=trustednames.end(); n++) {
245 #ifdef HAVE_STRCASECMP
246 if ((n->length() == bptr->length && !strncasecmp(n->c_str(), bptr->data, bptr->length)) ||
247 (n->length() == bptr2->length && !strncasecmp(n->c_str(), bptr2->data, bptr2->length))) {
249 if ((n->length() == bptr->length && !strnicmp(n->c_str(), bptr->data, bptr->length)) ||
250 (n->length() == bptr2->length && !strnicmp(n->c_str(), bptr2->data, bptr2->length))) {
252 log.debug("matched full subject DN to a key name (%s)", n->c_str());
261 log.debug("unable to match DN, trying TLS subjectAltName match");
262 STACK_OF(GENERAL_NAME)* altnames=(STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(certEE, NID_subject_alt_name, nullptr, nullptr);
264 int numalts = sk_GENERAL_NAME_num(altnames);
265 for (int an=0; an<numalts; an++) {
266 const GENERAL_NAME* check = sk_GENERAL_NAME_value(altnames, an);
267 if (check->type==GEN_DNS || check->type==GEN_URI) {
268 const char* altptr = (char*)ASN1_STRING_data(check->d.ia5);
269 const int altlen = ASN1_STRING_length(check->d.ia5);
270 for (set<string>::const_iterator n=trustednames.begin(); n!=trustednames.end(); n++) {
271 #ifdef HAVE_STRCASECMP
272 if ((check->type==GEN_DNS && n->length()==altlen && !strncasecmp(altptr,n->c_str(),altlen))
274 if ((check->type==GEN_DNS && n->length()==altlen && !strnicmp(altptr,n->c_str(),altlen))
276 || (check->type==GEN_URI && n->length()==altlen && !strncmp(altptr,n->c_str(),altlen))) {
277 log.debug("matched DNS/URI subjectAltName to a key name (%s)", n->c_str());
278 GENERAL_NAMES_free(altnames);
285 GENERAL_NAMES_free(altnames);
287 log.debug("unable to match subjectAltName, trying TLS CN match");
289 // Fetch the last CN RDN.
290 char* peer_CN = nullptr;
292 while ((j=X509_NAME_get_index_by_NID(subject, NID_commonName, i)) >= 0)
295 ASN1_STRING* tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject, i));
296 // Copied in from libcurl.
297 /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input
298 is already UTF-8 encoded. We check for this case and copy the raw
299 string manually to avoid the problem. */
300 if(tmp && ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
301 j = ASN1_STRING_length(tmp);
303 peer_CN = (char*)OPENSSL_malloc(j + 1);
304 memcpy(peer_CN, ASN1_STRING_data(tmp), j);
308 else /* not a UTF8 name */ {
309 j = ASN1_STRING_to_UTF8(reinterpret_cast<unsigned char**>(&peer_CN), tmp);
312 for (set<string>::const_iterator n=trustednames.begin(); n!=trustednames.end(); n++) {
313 #ifdef HAVE_STRCASECMP
314 if (n->length() == j && !strncasecmp(peer_CN, n->c_str(), j)) {
316 if (n->length() == j && !strnicmp(peer_CN, n->c_str(), j)) {
318 log.debug("matched subject CN to a key name (%s)", n->c_str());
320 OPENSSL_free(peer_CN);
325 OPENSSL_free(peer_CN);
328 log.warn("no common name in certificate subject");
332 log.error("certificate has no subject?!");
338 bool AbstractPKIXTrustEngine::validateWithCRLs(
340 STACK_OF(X509)* certChain,
341 const CredentialResolver& credResolver,
342 CredentialCriteria* criteria,
343 const vector<XSECCryptoX509CRL*>* inlineCRLs
347 NDC ndc("validateWithCRLs");
349 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
352 log.error("X.509 credential was NULL, unable to perform validation");
356 if (criteria && criteria->getPeerName() && *(criteria->getPeerName())) {
357 log.debug("checking that the certificate name is acceptable");
358 if (criteria && criteria->getUsage()==Credential::UNSPECIFIED_CREDENTIAL)
359 criteria->setUsage(Credential::SIGNING_CREDENTIAL);
360 if (!checkEntityNames(certEE,credResolver,*criteria)) {
361 log.error("certificate name was not acceptable");
365 else if (!m_trustedNames.empty()) {
366 log.debug("checking that the certificate name is acceptable");
367 CredentialCriteria cc;
368 cc.setUsage(Credential::SIGNING_CREDENTIAL);
369 if (!checkEntityNames(certEE,credResolver,cc)) {
370 log.error("certificate name was not acceptable");
375 log.debug("performing certificate path validation...");
377 auto_ptr<PKIXValidationInfoIterator> pkix(getPKIXValidationInfoIterator(credResolver, criteria));
378 while (pkix->next()) {
379 PKIXParams params(*this, *pkix.get(), inlineCRLs);
380 for (ptr_vector<OpenSSLPathValidator>::const_iterator v = m_pathValidators.begin(); v != m_pathValidators.end(); ++v) {
381 if (v->validate(certEE, certChain, params)) {
387 log.debug("failed to validate certificate chain using supplied PKIX information");
391 bool AbstractPKIXTrustEngine::validate(
393 STACK_OF(X509)* certChain,
394 const CredentialResolver& credResolver,
395 CredentialCriteria* criteria
398 return validateWithCRLs(certEE,certChain,credResolver,criteria);
401 bool AbstractPKIXTrustEngine::validate(
402 XSECCryptoX509* certEE,
403 const vector<XSECCryptoX509*>& certChain,
404 const CredentialResolver& credResolver,
405 CredentialCriteria* criteria
412 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").error("X.509 credential was NULL, unable to perform validation");
415 else if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
416 Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX").error("only the OpenSSL XSEC provider is supported");
420 STACK_OF(X509)* untrusted=sk_X509_new_null();
421 for (vector<XSECCryptoX509*>::const_iterator i=certChain.begin(); i!=certChain.end(); ++i)
422 sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
424 bool ret = validate(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, credResolver, criteria);
425 sk_X509_free(untrusted);
429 bool AbstractPKIXTrustEngine::validate(
431 const CredentialResolver& credResolver,
432 CredentialCriteria* criteria
438 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
440 const KeyInfoResolver* inlineResolver = m_keyInfoResolver;
442 inlineResolver = XMLToolingConfig::getConfig().getKeyInfoResolver();
443 if (!inlineResolver) {
444 log.error("unable to perform PKIX validation, no KeyInfoResolver available");
448 // Pull the certificate chain out of the signature.
449 X509Credential* x509cred;
450 auto_ptr<Credential> cred(inlineResolver->resolve(&sig,X509Credential::RESOLVE_CERTS|X509Credential::RESOLVE_CRLS));
451 if (!cred.get() || !(x509cred=dynamic_cast<X509Credential*>(cred.get()))) {
452 log.error("unable to perform PKIX validation, signature does not contain any certificates");
455 const vector<XSECCryptoX509*>& certs = x509cred->getEntityCertificateChain();
457 log.error("unable to perform PKIX validation, signature does not contain any certificates");
461 log.debug("validating signature using certificate from within the signature");
463 // Find and save off a pointer to the certificate that unlocks the object.
464 // Most of the time, this will be the first one anyway.
465 XSECCryptoX509* certEE=nullptr;
466 SignatureValidator keyValidator;
467 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); !certEE && i!=certs.end(); ++i) {
469 auto_ptr<XSECCryptoKey> key((*i)->clonePublicKey());
470 keyValidator.setKey(key.get());
471 keyValidator.validate(&sig);
472 log.debug("signature verified with key inside signature, attempting certificate validation...");
475 catch (ValidationException& ex) {
476 log.debug(ex.what());
481 log.debug("failed to verify signature with embedded certificates");
484 else if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
485 log.error("only the OpenSSL XSEC provider is supported");
489 STACK_OF(X509)* untrusted=sk_X509_new_null();
490 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); i!=certs.end(); ++i)
491 sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
492 const vector<XSECCryptoX509CRL*>& crls = x509cred->getCRLs();
493 bool ret = validateWithCRLs(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, credResolver, criteria, &crls);
494 sk_X509_free(untrusted);
498 bool AbstractPKIXTrustEngine::validate(
499 const XMLCh* sigAlgorithm,
504 const CredentialResolver& credResolver,
505 CredentialCriteria* criteria
511 Category& log=Category::getInstance(XMLTOOLING_LOGCAT".TrustEngine.PKIX");
514 log.error("unable to perform PKIX validation, KeyInfo not present");
518 const KeyInfoResolver* inlineResolver = m_keyInfoResolver;
520 inlineResolver = XMLToolingConfig::getConfig().getKeyInfoResolver();
521 if (!inlineResolver) {
522 log.error("unable to perform PKIX validation, no KeyInfoResolver available");
526 // Pull the certificate chain out of the signature.
527 X509Credential* x509cred;
528 auto_ptr<Credential> cred(inlineResolver->resolve(keyInfo,X509Credential::RESOLVE_CERTS));
529 if (!cred.get() || !(x509cred=dynamic_cast<X509Credential*>(cred.get()))) {
530 log.error("unable to perform PKIX validation, KeyInfo does not contain any certificates");
533 const vector<XSECCryptoX509*>& certs = x509cred->getEntityCertificateChain();
535 log.error("unable to perform PKIX validation, KeyInfo does not contain any certificates");
539 log.debug("validating signature using certificate from within KeyInfo");
541 // Find and save off a pointer to the certificate that unlocks the object.
542 // Most of the time, this will be the first one anyway.
543 XSECCryptoX509* certEE=nullptr;
544 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); !certEE && i!=certs.end(); ++i) {
546 auto_ptr<XSECCryptoKey> key((*i)->clonePublicKey());
547 if (Signature::verifyRawSignature(key.get(), sigAlgorithm, sig, in, in_len)) {
548 log.debug("signature verified with key inside signature, attempting certificate validation...");
552 catch (SignatureException& ex) {
553 log.debug(ex.what());
558 log.debug("failed to verify signature with embedded certificates");
561 else if (certEE->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
562 log.error("only the OpenSSL XSEC provider is supported");
566 STACK_OF(X509)* untrusted=sk_X509_new_null();
567 for (vector<XSECCryptoX509*>::const_iterator i=certs.begin(); i!=certs.end(); ++i)
568 sk_X509_push(untrusted,static_cast<OpenSSLCryptoX509*>(*i)->getOpenSSLX509());
569 const vector<XSECCryptoX509CRL*>& crls = x509cred->getCRLs();
570 bool ret = validateWithCRLs(static_cast<OpenSSLCryptoX509*>(certEE)->getOpenSSLX509(), untrusted, credResolver, criteria, &crls);
571 sk_X509_free(untrusted);