From: Scott Cantor Date: Fri, 25 Aug 2006 00:48:13 +0000 (+0000) Subject: PKIX TrustEngine. X-Git-Tag: 2.0-alpha1~201 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=shibboleth%2Fcpp-opensaml.git;a=commitdiff_plain;h=cec17382f1fd55105c849e5967d39d5d25eb2c1f PKIX TrustEngine. --- diff --git a/saml/Makefile.am b/saml/Makefile.am index 9ef8991..a7457be 100644 --- a/saml/Makefile.am +++ b/saml/Makefile.am @@ -29,6 +29,7 @@ encinclude_HEADERS = \ encryption/EncryptedKeyResolver.h secinclude_HEADERS = \ + security/AbstractPKIXTrustEngine.h \ security/MetadataKeyInfoIterator.h \ security/TrustEngine.h \ security/X509TrustEngine.h diff --git a/saml/SAMLConfig.cpp b/saml/SAMLConfig.cpp index e154646..6f4bf76 100644 --- a/saml/SAMLConfig.cpp +++ b/saml/SAMLConfig.cpp @@ -1,3 +1,4 @@ + /* * Copyright 2001-2006 Internet2 * @@ -40,7 +41,7 @@ #include #include #include - +#include using namespace opensaml; using namespace xmlsignature; @@ -173,3 +174,19 @@ string SAMLInternalConfig::hashSHA1(const char* s, bool toHex) } throw XMLSecurityException("Unable to generate SHA-1 hash."); } + +void opensaml::log_openssl() +{ + const char* file; + const char* data; + int flags,line; + + unsigned long code=ERR_get_error_line_data(&file,&line,&data,&flags); + while (code) { + Category& log=Category::getInstance("OpenSSL"); + log.errorStream() << "error code: " << code << " in " << file << ", line " << line << CategoryStream::ENDLINE; + if (data && (flags & ERR_TXT_STRING)) + log.errorStream() << "error data: " << data << CategoryStream::ENDLINE; + code=ERR_get_error_line_data(&file,&line,&data,&flags); + } +} diff --git a/saml/internal.h b/saml/internal.h index 675a5e7..9be5928 100644 --- a/saml/internal.h +++ b/saml/internal.h @@ -98,6 +98,8 @@ namespace opensaml { std::string hashSHA1(const char* data, bool toHex=false); private: }; + + void log_openssl(); /// @endcond }; diff --git a/saml/saml.vcproj b/saml/saml.vcproj index 0485e28..e5aa7c0 100644 --- a/saml/saml.vcproj +++ b/saml/saml.vcproj @@ -62,7 +62,7 @@ /> + + @@ -491,6 +495,10 @@ Name="security" > + + diff --git a/saml/security/AbstractPKIXTrustEngine.h b/saml/security/AbstractPKIXTrustEngine.h new file mode 100644 index 0000000..0c3ade2 --- /dev/null +++ b/saml/security/AbstractPKIXTrustEngine.h @@ -0,0 +1,147 @@ +/* + * Copyright 2001-2006 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file saml/security/AbstractPKIXTrustEngine.h + * + * A trust engine that uses X.509 trust anchors and CRLs associated with a role + * to perform PKIX validation of signatures and certificates. + */ + +#ifndef __saml_pkixtrust_h__ +#define __saml_pkixtrust_h__ + +#include +#include + +namespace opensaml { + + /** + * A trust engine that uses X.509 trust anchors and CRLs associated with a role + * to perform PKIX validation of signatures and certificates. + */ + class SAML_API AbstractPKIXTrustEngine : public X509TrustEngine + { + protected: + /** + * Constructor. + * + * If a DOM is supplied, the following XML content is supported: + * + *
    + *
  • <KeyResolver> elements with a type attribute + *
+ * + * XML namespaces are ignored in the processing of this content. + * + * @param e DOM to supply configuration for provider + */ + AbstractPKIXTrustEngine(const DOMElement* e=NULL); + + /** + * Checks that either the ID for the entity with the given role or the key names + * for the given role match the subject or subject alternate names + * of the entity's certificate. + * + * @param certEE the credential for the entity to validate + * @param role the descriptor of the role the entity is supposed to be acting in + * + * @return true the name check succeeds, false if not + */ + bool checkEntityNames(XSECCryptoX509* certEE, const saml2md::RoleDescriptor& role) const; + + /** An inline KeyResolver for extracting certificates out of a signature. */ + xmlsignature::KeyResolver* m_inlineResolver; + + public: + virtual ~AbstractPKIXTrustEngine(); + + virtual bool validate( + xmlsignature::Signature& sig, + const saml2md::RoleDescriptor& role, + const xmlsignature::KeyResolver* keyResolver=NULL + ); + + virtual bool validate( + XSECCryptoX509* certEE, + const std::vector& certChain, + const saml2md::RoleDescriptor& role, + bool checkName=true, + const xmlsignature::KeyResolver* keyResolver=NULL + ); + + /** + * Stateful interface that supplies PKIX validation data to the trust engine. + * Applications can adapt this TrustEngine to their environment by returning + * implementations of this interface from the getPKIXValidationInfoIterator + * method. + */ + class SAML_API PKIXValidationInfoIterator { + MAKE_NONCOPYABLE(PKIXValidationInfoIterator); + protected: + PKIXValidationInfoIterator() {} + public: + virtual ~PKIXValidationInfoIterator() {} + + /** + * Advances to the next set of information, if any. + * + * @return true iff another set of information is available + */ + virtual bool next()=0; + + /** + * Returns the allowable trust chain verification depth for the + * validation data in the current position. + * + * @return allowable trust chain verification depth + */ + virtual int getVerificationDepth() const=0; + + /** + * Returns the set of trust anchors for the validation data in the + * current position. Keeping the certificates beyond the lifetime + * of the iterator or after advancing to the next position requires + * copying them. + * + * @return set of trust anchors + */ + virtual const std::vector& getTrustAnchors() const=0; + + /** + * Returns the set of CRLs for the validation data in the + * current position. Keeping the CRLs beyond the lifetime + * of the iterator or after advancing to the next position requires + * copying them. + * + * @return set of CRLs + */ + virtual const std::vector& getCRLs() const=0; + }; + + /** + * Provides access to the information necessary, for the given role, for + * PKIX validation of credentials. Each set of validation information returned + * will be tried, in turn, until one succeeds or no more remain. + * The caller must free the returned interface when finished with it. + * + * @return interface for obtaining validation data + */ + virtual PKIXValidationInfoIterator* getPKIXValidationInfoIterator(const saml2md::RoleDescriptor& role) const=0; + }; +}; + +#endif /* __saml_pkixtrust_h__ */ diff --git a/saml/security/TrustEngine.h b/saml/security/TrustEngine.h index 996286b..0cc211f 100644 --- a/saml/security/TrustEngine.h +++ b/saml/security/TrustEngine.h @@ -68,7 +68,7 @@ namespace opensaml { */ virtual bool validate( xmlsignature::Signature& sig, - saml2md::RoleDescriptor& role, + const saml2md::RoleDescriptor& role, const xmlsignature::KeyResolver* keyResolver=NULL )=0; }; diff --git a/saml/security/X509TrustEngine.h b/saml/security/X509TrustEngine.h index 1bc8bb6..92b6e82 100644 --- a/saml/security/X509TrustEngine.h +++ b/saml/security/X509TrustEngine.h @@ -67,7 +67,7 @@ namespace opensaml { virtual bool validate( XSECCryptoX509* certEE, const std::vector& certChain, - saml2md::RoleDescriptor& role, + const saml2md::RoleDescriptor& role, bool checkName=true, const xmlsignature::KeyResolver* keyResolver=NULL )=0; diff --git a/saml/security/impl/AbstractPKIXTrustEngine.cpp b/saml/security/impl/AbstractPKIXTrustEngine.cpp new file mode 100644 index 0000000..b06b3c4 --- /dev/null +++ b/saml/security/impl/AbstractPKIXTrustEngine.cpp @@ -0,0 +1,372 @@ +/* + * Copyright 2006 Internet2 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * AbstractPKIXTrustEngine.cpp + * + * A trust engine that uses X.509 trust anchors and CRLs associated with a role + * to perform PKIX validation of signatures and certificates. + */ + +#include "internal.h" +#include "security/AbstractPKIXTrustEngine.h" +#include "signature/SignatureProfileValidator.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace opensaml::saml2md; +using namespace opensaml; +using namespace xmlsignature; +using namespace xmltooling; +using namespace log4cpp; +using namespace std; + +AbstractPKIXTrustEngine::AbstractPKIXTrustEngine(const DOMElement* e) : X509TrustEngine(e), m_inlineResolver(NULL) +{ + m_inlineResolver = XMLToolingConfig::getConfig().KeyResolverManager.newPlugin(INLINE_KEY_RESOLVER,NULL); +} + +AbstractPKIXTrustEngine::~AbstractPKIXTrustEngine() +{ + delete m_inlineResolver; +} + +namespace { + static int SAML_DLLLOCAL error_callback(int ok, X509_STORE_CTX* ctx) + { + if (!ok) + Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error)); + return ok; + } + + static bool SAML_DLLLOCAL validate( + X509* EE, STACK_OF(X509)* untrusted, AbstractPKIXTrustEngine::PKIXValidationInfoIterator* pkixInfo + ) + { + Category& log=Category::getInstance(SAML_LOGCAT".TrustEngine"); + + // First we build a stack of CA certs. These objects are all referenced in place. + log.debug("building CA list from PKIX Validation information"); + + // We need this for CRL support. + X509_STORE* store=X509_STORE_new(); + if (!store) { + log_openssl(); + return false; + } + #if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + X509_STORE_set_flags(store,X509_V_FLAG_CRL_CHECK_ALL); + #endif + + STACK_OF(X509)* CAstack = sk_X509_new_null(); + + // This contains the state of the validate operation. + X509_STORE_CTX ctx; + + const vector& CAcerts = pkixInfo->getTrustAnchors(); + for (vector::const_iterator i=CAcerts.begin(); i!=CAcerts.end(); ++i) { + if ((*i)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL) { + sk_X509_push(CAstack,static_cast(*i)->getOpenSSLX509()); + } + } + + const vector& crls = pkixInfo->getCRLs(); + for (vector::const_iterator j=crls.begin(); j!=crls.end(); ++j) { + if ((*j)->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL) { + // owned by store + X509_STORE_add_crl( + store, + X509_CRL_dup(static_cast(*j)->getOpenSSLX509CRL()) + ); + } + } + + // AFAICT, EE and untrusted are passed in but not owned by the ctx. + #if (OPENSSL_VERSION_NUMBER >= 0x00907000L) + if (X509_STORE_CTX_init(&ctx,store,EE,untrusted)!=1) { + log_openssl(); + log.error("unable to initialize X509_STORE_CTX"); + sk_X509_free(CAstack); + X509_STORE_free(store); + return false; + } + #else + X509_STORE_CTX_init(&ctx,store,EE,untrusted); + #endif + + // Seems to be most efficient to just pass in the CA stack. + X509_STORE_CTX_trusted_stack(&ctx,CAstack); + X509_STORE_CTX_set_depth(&ctx,100); // we check the depth down below + X509_STORE_CTX_set_verify_cb(&ctx,error_callback); + + int ret=X509_verify_cert(&ctx); + if (ret==1) { + // Now see if the depth was acceptable by counting the number of intermediates. + int depth=sk_X509_num(ctx.chain)-2; + if (pkixInfo->getVerificationDepth() < depth) { + log.error( + "certificate chain was too long (%d intermediates, only %d allowed)", + (depth==-1) ? 0 : depth, + pkixInfo->getVerificationDepth() + ); + ret=0; + } + } + + // Clean up... + X509_STORE_CTX_cleanup(&ctx); + X509_STORE_free(store); + sk_X509_free(CAstack); + + if (ret==1) { + log.info("successfully validated certificate chain"); + return true; + } + + return false; + } +}; + +bool AbstractPKIXTrustEngine::checkEntityNames(XSECCryptoX509* certEE, const RoleDescriptor& role) const +{ + Category& log=Category::getInstance(SAML_LOGCAT".TrustEngine"); + + // Build a list of acceptable names. Transcode the possible key "names" to UTF-8. + // For some simple cases, this should handle UTF-8 encoded DNs in certificates. + vector keynames; + const vector& keydescs=role.getKeyDescriptors(); + for (vector::const_iterator kd_i=keydescs.begin(); kd_i!=keydescs.end(); ++kd_i) { + const XMLCh* use=(*kd_i)->getUse(); + const KeyInfo* keyInfo = (*kd_i)->getKeyInfo(); + if (keyInfo && use && XMLString::equals(use,KeyDescriptor::KEYTYPE_ENCRYPTION)) + continue; + const vector& knames=keyInfo->getKeyNames(); + for (vector::const_iterator kn_i=knames.begin(); kn_i!=knames.end(); ++kn_i) { + const XMLCh* n=(*kn_i)->getName(); + if (n && *n) { + char* kn=toUTF8(n); + keynames.push_back(kn); + delete[] kn; + } + } + } + + EntityDescriptor* parent=dynamic_cast(role.getParent()); + if (parent) { + const XMLCh* eid=parent->getEntityID(); + if (eid && *eid) { + char* kn=toUTF8(eid); + keynames.push_back(kn); + delete[] kn; + } + } + + char buf[256]; + X509* x=static_cast(certEE)->getOpenSSLX509(); + X509_NAME* subject=X509_get_subject_name(x); + if (subject) { + // One way is a direct match to the subject DN. + // Seems that the way to do the compare is to write the X509_NAME into a BIO. + BIO* b = BIO_new(BIO_s_mem()); + BIO* b2 = BIO_new(BIO_s_mem()); + BIO_set_mem_eof_return(b, 0); + BIO_set_mem_eof_return(b2, 0); + // The flags give us LDAP order instead of X.500, with a comma separator. + int len=X509_NAME_print_ex(b,subject,0,XN_FLAG_RFC2253); + string subjectstr,subjectstr2; + BIO_flush(b); + while ((len = BIO_read(b, buf, 255)) > 0) { + buf[len] = '\0'; + subjectstr+=buf; + } + log.infoStream() << "certificate subject: " << subjectstr << CategoryStream::ENDLINE; + // The flags give us LDAP order instead of X.500, with a comma plus space separator. + len=X509_NAME_print_ex(b2,subject,0,XN_FLAG_RFC2253 + XN_FLAG_SEP_CPLUS_SPC - XN_FLAG_SEP_COMMA_PLUS); + BIO_flush(b2); + while ((len = BIO_read(b2, buf, 255)) > 0) { + buf[len] = '\0'; + subjectstr2+=buf; + } + + // Check each keyname. + for (vector::const_iterator n=keynames.begin(); n!=keynames.end(); n++) { +#ifdef HAVE_STRCASECMP + if (!strcasecmp(n->c_str(),subjectstr.c_str()) || !strcasecmp(n->c_str(),subjectstr2.c_str())) { +#else + if (!stricmp(n->c_str(),subjectstr.c_str()) || !stricmp(n->c_str(),subjectstr2.c_str())) { +#endif + log.info("matched full subject DN to a key name (%s)", n->c_str()); + BIO_free(b); + BIO_free(b2); + return true; + } + } + BIO_free(b); + BIO_free(b2); + + log.debug("unable to match DN, trying TLS subjectAltName match"); + STACK_OF(GENERAL_NAME)* altnames=(STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + if (altnames) { + int numalts = sk_GENERAL_NAME_num(altnames); + for (int an=0; antype==GEN_DNS || check->type==GEN_URI) { + const char* altptr = (char*)ASN1_STRING_data(check->d.ia5); + const int altlen = ASN1_STRING_length(check->d.ia5); + + for (vector::const_iterator n=keynames.begin(); n!=keynames.end(); n++) { +#ifdef HAVE_STRCASECMP + if ((check->type==GEN_DNS && !strncasecmp(altptr,n->c_str(),altlen)) +#else + if ((check->type==GEN_DNS && !strnicmp(altptr,n->c_str(),altlen)) +#endif + || (check->type==GEN_URI && !strncmp(altptr,n->c_str(),altlen))) { + log.info("matched DNS/URI subjectAltName to a key name (%s)", n->c_str()); + GENERAL_NAMES_free(altnames); + return true; + } + } + } + } + } + GENERAL_NAMES_free(altnames); + + log.debug("unable to match subjectAltName, trying TLS CN match"); + memset(buf,0,sizeof(buf)); + if (X509_NAME_get_text_by_NID(subject,NID_commonName,buf,255)>0) { + for (vector::const_iterator n=keynames.begin(); n!=keynames.end(); n++) { +#ifdef HAVE_STRCASECMP + if (!strcasecmp(buf,n->c_str())) { +#else + if (!stricmp(buf,n->c_str())) { +#endif + log.info("matched subject CN to a key name (%s)", n->c_str()); + return true; + } + } + } + else + log.warn("no common name in certificate subject"); + } + else + log.error("certificate has no subject?!"); + + return false; +} + +bool AbstractPKIXTrustEngine::validate( + XSECCryptoX509* certEE, + const vector& certChain, + const RoleDescriptor& role, + bool checkName, + const KeyResolver* keyResolver + ) +{ +#ifdef _DEBUG + NDC ndc("validate"); +#endif + Category& log=Category::getInstance(SAML_LOGCAT".TrustEngine"); + + if (!certEE) { + log.error("X.509 credential was NULL, unable to perform validation"); + return false; + } + + if (checkName) { + log.debug("checking that the entity certificate name is acceptable"); + if (!checkEntityNames(certEE,role)) { + log.error("entity certificate name was not acceptable"); + return false; + } + } + + log.debug("performing certificate path validation..."); + + STACK_OF(X509)* untrusted=sk_X509_new_null(); + for (vector::const_iterator i=certChain.begin(); i!=certChain.end(); ++i) { + sk_X509_push(untrusted,static_cast(*i)->getOpenSSLX509()); + } + + auto_ptr pkix(getPKIXValidationInfoIterator(role)); + while (pkix->next()) { + if (::validate(static_cast(certEE)->getOpenSSLX509(),untrusted,pkix.get())) { + sk_X509_free(untrusted); + return true; + } + } + + sk_X509_free(untrusted); + log.error("failed to validate certificate chain using supplied PKIX information"); + return false; +} + +bool AbstractPKIXTrustEngine::validate(Signature& sig, const RoleDescriptor& role, const KeyResolver* keyResolver) +{ +#ifdef _DEBUG + NDC ndc("validate"); +#endif + Category& log=Category::getInstance(SAML_LOGCAT".TrustEngine"); + + log.debug("attempting to validate signature profile"); + SignatureProfileValidator sigValidator; + try { + sigValidator.validate(&sig); + log.debug("signature profile validated"); + } + catch (ValidationException& e) { + if (log.isDebugEnabled()) { + log.debug("signature profile failed to validate: %s", e.what()); + } + return false; + } + + // Pull the certificate chain out of the signature using an inline KeyResolver. + KeyResolver::ResolvedCertificates certs; + if (0==m_inlineResolver->resolveCertificates(&sig, certs)) { + log.error("unable to perform PKIX validation, signature does not contain any certificates"); + return false; + } + + log.debug("validating signature using certificate from within the signature"); + + // Find and save off a pointer to the certificate that unlocks the object. + // Most of the time, this will be the first one anyway. + XSECCryptoX509* certEE=NULL; + SignatureValidator keyValidator; + for (vector::const_iterator i=certs.v().begin(); !certEE && i!=certs.v().end(); ++i) { + try { + keyValidator.setKey((*i)->clonePublicKey()); + keyValidator.validate(&sig); + log.info("signature verified with key inside signature, attempting certificate validation..."); + certEE=(*i); + } + catch (ValidationException&) { + // trap failures + } + } + + if (certEE) + return validate(certEE,certs.v(),role,true,keyResolver); + + log.error("failed to verify signature with embedded certificates"); + return false; +} diff --git a/saml/security/impl/ExplicitKeyTrustEngine.cpp b/saml/security/impl/ExplicitKeyTrustEngine.cpp index 4219a90..e26f56d 100644 --- a/saml/security/impl/ExplicitKeyTrustEngine.cpp +++ b/saml/security/impl/ExplicitKeyTrustEngine.cpp @@ -56,13 +56,13 @@ namespace opensaml { virtual bool validate( Signature& sig, - RoleDescriptor& role, + const RoleDescriptor& role, const KeyResolver* keyResolver=NULL ); virtual bool validate( XSECCryptoX509* certEE, const vector& certChain, - RoleDescriptor& role, + const RoleDescriptor& role, bool checkName=true, const KeyResolver* keyResolver=NULL ); @@ -79,7 +79,7 @@ namespace opensaml { bool ExplicitKeyTrustEngine::validate( Signature& sig, - RoleDescriptor& role, + const RoleDescriptor& role, const KeyResolver* keyResolver ) { @@ -108,7 +108,7 @@ bool ExplicitKeyTrustEngine::validate( bool ExplicitKeyTrustEngine::validate( XSECCryptoX509* certEE, const vector& certChain, - RoleDescriptor& role, + const RoleDescriptor& role, bool checkName, const KeyResolver* keyResolver ) diff --git a/samltest/Makefile.am b/samltest/Makefile.am index 73e4212..8aab1f1 100644 --- a/samltest/Makefile.am +++ b/samltest/Makefile.am @@ -17,6 +17,7 @@ samltest_h = \ signature/SAML1RequestTest.h \ signature/SAML1ResponseTest.h \ signature/SAML2AssertionTest.h \ + security/AbstractPKIXTrustEngineTest.h \ security/ExplicitKeyTrustEngineTest.h \ saml1/core/impl/ActionTest.h \ saml1/core/impl/AdviceTest.h \ diff --git a/samltest/data/security/FilesystemKeyResolver.xml b/samltest/data/security/FilesystemKeyResolver.xml new file mode 100644 index 0000000..e37fb88 --- /dev/null +++ b/samltest/data/security/FilesystemKeyResolver.xml @@ -0,0 +1,6 @@ + + + + ../samltest/data/cert.pem + + diff --git a/samltest/data/security/example-metadata.xml b/samltest/data/security/example-metadata.xml index 5c88e41..830a691 100644 --- a/samltest/data/security/example-metadata.xml +++ b/samltest/data/security/example-metadata.xml @@ -1,11 +1,14 @@ - + + + sp.example.org MIICjzCCAfigAwIBAgIJAKk8t1hYcMkhMA0GCSqGSIb3DQEBBAUAMDoxCzAJBgNV @@ -31,3 +34,20 @@ + + + + + + + sp2.example.org + + + + + + + + + \ No newline at end of file diff --git a/samltest/samltest.vcproj b/samltest/samltest.vcproj index ba7d52f..7b160d5 100644 --- a/samltest/samltest.vcproj +++ b/samltest/samltest.vcproj @@ -526,6 +526,10 @@ Name="security" > + + @@ -2296,7 +2300,7 @@ Name="security" > + + + + + + + + +#include + +using namespace opensaml::saml2; +using namespace opensaml::saml2md; +using namespace xmlsignature; + +namespace { + class SampleTrustEngine : public AbstractPKIXTrustEngine { + public: + SampleTrustEngine() {} + ~SampleTrustEngine() {} + + class SampleIterator : public PKIXValidationInfoIterator { + vector m_crls; + KeyResolver::ResolvedCertificates m_certs; + KeyResolver* m_resolver; + bool m_done; + public: + SampleIterator() : m_resolver(NULL), m_done(false) { + string config = data_path + "security/FilesystemKeyResolver.xml"; + ifstream in(config.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + m_resolver = XMLToolingConfig::getConfig().KeyResolverManager.newPlugin( + FILESYSTEM_KEY_RESOLVER,doc->getDocumentElement() + ); + m_resolver->resolveCertificates((KeyInfo*)NULL,m_certs); + } + + ~SampleIterator() { + delete m_resolver; + } + + bool next() { + if (m_done) + return false; + m_done = true; + return true; + } + + int getVerificationDepth() const { + return 0; + } + + const vector& getTrustAnchors() const { + return m_certs.v(); + } + + const vector& getCRLs() const { + return m_crls; + } + }; + + PKIXValidationInfoIterator* getPKIXValidationInfoIterator(const RoleDescriptor& role) const { + return new SampleIterator(); + } + }; +}; + +class AbstractPKIXTrustEngineTest : public CxxTest::TestSuite, public SAMLObjectBaseTestCase { +public: + void setUp() { + SAMLObjectBaseTestCase::setUp(); + } + + void tearDown() { + SAMLObjectBaseTestCase::tearDown(); + } + + void testExplicitKeyTrustEngine() { + string config = data_path + "security/FilesystemMetadataProvider.xml"; + ifstream in(config.c_str()); + DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(in); + XercesJanitor janitor(doc); + + auto_ptr_XMLCh path("path"); + string s = data_path + "security/example-metadata.xml"; + auto_ptr_XMLCh file(s.c_str()); + doc->getDocumentElement()->setAttributeNS(NULL,path.get(),file.get()); + + // Build metadata provider. + auto_ptr metadataProvider( + SAMLConfig::getConfig().MetadataProviderManager.newPlugin(FILESYSTEM_METADATA_PROVIDER,doc->getDocumentElement()) + ); + try { + metadataProvider->init(); + } + catch (XMLToolingException& ex) { + TS_TRACE(ex.what()); + throw; + } + + // Build trust engine. + auto_ptr trustEngine(new SampleTrustEngine()); + + // Get signed assertion. + config = data_path + "signature/SAML2Assertion.xml"; + ifstream in2(config.c_str()); + DOMDocument* doc2=XMLToolingConfig::getConfig().getParser().parse(in2); + XercesJanitor janitor2(doc2); + auto_ptr assertion(dynamic_cast(XMLObjectBuilder::getBuilder(doc2->getDocumentElement())->buildFromDocument(doc2))); + janitor2.release(); + + Locker locker(metadataProvider.get()); + const EntityDescriptor* descriptor = metadataProvider->getEntityDescriptor("https://idp.example.org"); + TSM_ASSERT("Retrieved entity descriptor was null", descriptor!=NULL); + + RoleDescriptor* role=descriptor->getIDPSSODescriptors().front(); + TSM_ASSERT("Role not present", role!=NULL); + + Signature* sig=assertion->getSignature(); + TSM_ASSERT("Signature not present", sig!=NULL); + TSM_ASSERT("Signature failed to validate.", trustEngine->validate(*sig, *role, metadataProvider->getKeyResolver())); + + descriptor = metadataProvider->getEntityDescriptor("https://idp2.example.org"); + TSM_ASSERT("Retrieved entity descriptor was null", descriptor!=NULL); + + role=descriptor->getIDPSSODescriptors().front(); + TSM_ASSERT("Role not present", role!=NULL); + + TSM_ASSERT("Signature validated.", !trustEngine->validate(*sig, *role, metadataProvider->getKeyResolver())); + } +}; diff --git a/samltest/security/ExplicitKeyTrustEngineTest.h b/samltest/security/ExplicitKeyTrustEngineTest.h index 511de41..53416e9 100644 --- a/samltest/security/ExplicitKeyTrustEngineTest.h +++ b/samltest/security/ExplicitKeyTrustEngineTest.h @@ -72,12 +72,19 @@ public: const EntityDescriptor* descriptor = metadataProvider->getEntityDescriptor("https://idp.example.org"); TSM_ASSERT("Retrieved entity descriptor was null", descriptor!=NULL); - Signature* sig=assertion->getSignature(); - TSM_ASSERT("Signature not present", sig!=NULL); - RoleDescriptor* role=descriptor->getIDPSSODescriptors().front(); TSM_ASSERT("Role not present", role!=NULL); + Signature* sig=assertion->getSignature(); + TSM_ASSERT("Signature not present", sig!=NULL); TSM_ASSERT("Signature failed to validate.", trustEngine->validate(*sig, *role, metadataProvider->getKeyResolver())); + + descriptor = metadataProvider->getEntityDescriptor("https://idp2.example.org"); + TSM_ASSERT("Retrieved entity descriptor was null", descriptor!=NULL); + + role=descriptor->getIDPSSODescriptors().front(); + TSM_ASSERT("Role not present", role!=NULL); + + TSM_ASSERT("Signature validated.", !trustEngine->validate(*sig, *role, metadataProvider->getKeyResolver())); } };