2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* ShibbolethTrust.cpp - a trust implementation that relies solely on standard SAML metadata
27 #include <openssl/err.h>
28 #include <openssl/x509_vfy.h>
29 #include <openssl/x509v3.h>
30 #include <xsec/dsig/DSIGKeyInfoX509.hpp>
31 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
33 using namespace shibboleth;
35 using namespace log4cpp;
45 unsigned long code=ERR_get_error_line_data(&file,&line,&data,&flags);
47 Category& log=Category::getInstance("OpenSSL");
48 log.errorStream() << "error code: " << code << " in " << file << ", line " << line << CategoryStream::ENDLINE;
49 if (data && (flags & ERR_TXT_STRING))
50 log.errorStream() << "error data: " << data << CategoryStream::ENDLINE;
51 code=ERR_get_error_line_data(&file,&line,&data,&flags);
55 X509* B64_to_X509(const char* buf)
57 BIO* bmem = BIO_new_mem_buf((void*)buf,-1);
58 BIO* b64 = BIO_new(BIO_f_base64());
59 b64 = BIO_push(b64, bmem);
68 X509_CRL* B64_to_CRL(const char* buf)
70 BIO* bmem = BIO_new_mem_buf((void*)buf,-1);
71 BIO* b64 = BIO_new(BIO_f_base64());
72 b64 = BIO_push(b64, bmem);
74 d2i_X509_CRL_bio(b64,&x);
81 class ShibbolethTrust : public BasicTrust
84 ShibbolethTrust(const DOMElement* e);
87 bool validate(void* certEE, const Iterator<void*>& certChain, const IRoleDescriptor* role, bool checkName=true);
88 bool validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role, ITrust* certValidator=NULL);
91 bool validate(X509* EE, STACK_OF(X509)* untrusted, const IKeyAuthority* rule);
93 vector<IMetadata*> m_metas;
97 IPlugIn* ShibbolethTrustFactory(const DOMElement* e)
99 return new ShibbolethTrust(e);
102 ShibbolethTrust::ShibbolethTrust(const DOMElement* e) : BasicTrust(e)
104 static const XMLCh MetadataProvider[] =
105 { chLatin_M, chLatin_e, chLatin_t, chLatin_a, chLatin_d, chLatin_a, chLatin_t, chLatin_a,
106 chLatin_P, chLatin_r, chLatin_o, chLatin_v, chLatin_i, chLatin_d, chLatin_e, chLatin_r, chNull
108 static const XMLCh _type[] = { chLatin_t, chLatin_y, chLatin_p, chLatin_e, chNull };
111 saml::NDC ndc("ShibbolethTrust");
113 Category& log=Category::getInstance(SHIB_LOGCAT".Trust.Shibboleth");
115 // Check for embedded trust metadata.
116 e=saml::XML::getFirstChildElement(e);
118 if (!XMLString::compareString(e->getLocalName(),MetadataProvider) && e->hasAttributeNS(NULL,_type)) {
119 auto_ptr_char type(e->getAttributeNS(NULL,_type));
120 log.info("trust provider building embedded metadata provider of type %s...",type.get());
122 IPlugIn* plugin=SAMLConfig::getConfig().getPlugMgr().newPlugin(type.get(),e);
123 IMetadata* md=dynamic_cast<IMetadata*>(plugin);
125 m_metas.push_back(md);
128 log.error("plugin was not a metadata provider");
131 catch (SAMLException& ex) {
132 log.error("caught SAML exception building embedded metadata provider: %s", ex.what());
136 log.error("caught unknown exception building embedded metadata provider");
140 e=saml::XML::getNextSiblingElement(e);
144 ShibbolethTrust::~ShibbolethTrust()
146 for (vector<IMetadata*>::iterator i=m_metas.begin(); i!=m_metas.end(); i++)
150 static int error_callback(int ok, X509_STORE_CTX* ctx)
153 Category::getInstance("OpenSSL").error("path validation failure: %s", X509_verify_cert_error_string(ctx->error));
157 bool ShibbolethTrust::validate(X509* EE, STACK_OF(X509)* untrusted, const IKeyAuthority* rule)
159 Category& log=Category::getInstance(SHIB_LOGCAT".Trust.Shibboleth");
161 // First we build a stack of CA certs. These objects are all referenced in place.
162 log.debug("building CA list from KeyAuthority extension");
164 // We need this for CRL support.
165 X509_STORE* store=X509_STORE_new();
170 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
171 X509_STORE_set_flags(store,X509_V_FLAG_CRL_CHECK_ALL);
174 STACK_OF(X509)* CAstack = sk_X509_new_null();
176 // This contains the state of the validate operation.
179 Iterator<DSIGKeyInfoList*> iKIL=rule->getKeyInfos();
180 while (iKIL.hasNext()) {
181 DSIGKeyInfoList* KIL=iKIL.next();
183 // Try and locate a certificate.
184 Iterator<KeyInfoResolver*> resolvers(m_resolvers);
185 while (resolvers.hasNext()) {
186 XSECCryptoX509* cert=resolvers.next()->resolveCert(KIL);
187 if (cert && cert->getProviderName()==DSIGConstants::s_unicodeStrPROVOpenSSL) {
188 sk_X509_push(CAstack,static_cast<OpenSSLCryptoX509*>(cert)->getOpenSSLX509());
193 // Try and locate one or more CRLs.
194 for (size_t s=0; s<KIL->getSize(); s++) {
195 DSIGKeyInfo* KI=KIL->item(s);
196 if (KI->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509) {
197 const XMLCh* raw=static_cast<DSIGKeyInfoX509*>(KI)->getX509CRL();
199 auto_ptr_char blob(raw);
200 X509_CRL* crl=B64_to_CRL(blob.get());
202 X509_STORE_add_crl(store,crl); // owned by store
204 log.error("unable to create CRL from X509CRL data");
210 // AFAICT, EE and untrusted are passed in but not owned by the ctx.
211 #if (OPENSSL_VERSION_NUMBER >= 0x00907000L)
212 if (X509_STORE_CTX_init(&ctx,store,EE,untrusted)!=1) {
214 log.error("unable to initialize X509_STORE_CTX");
215 sk_X509_free(CAstack);
216 X509_STORE_free(store);
220 X509_STORE_CTX_init(&ctx,store,EE,untrusted);
223 // Seems to be most efficient to just pass in the CA stack.
224 X509_STORE_CTX_trusted_stack(&ctx,CAstack);
225 X509_STORE_CTX_set_depth(&ctx,100); // we check the depth down below
226 X509_STORE_CTX_set_verify_cb(&ctx,error_callback);
228 int ret=X509_verify_cert(&ctx);
230 // Now see if the depth was acceptable by counting the number of intermediates.
231 int depth=sk_X509_num(ctx.chain)-2;
232 if (rule->getVerifyDepth() < depth) {
234 "certificate chain was too long (%d intermediates, only %d allowed)",
235 (depth==-1) ? 0 : depth,
236 rule->getVerifyDepth()
243 X509_STORE_CTX_cleanup(&ctx);
244 X509_STORE_free(store);
245 sk_X509_free(CAstack);
248 log.info("successfully validated certificate chain");
255 bool ShibbolethTrust::validate(void* certEE, const Iterator<void*>& certChain, const IRoleDescriptor* role, bool checkName)
257 if (BasicTrust::validate(certEE,certChain,role))
261 saml::NDC ndc("validate");
263 Category& log=Category::getInstance(SHIB_LOGCAT".Trust.Shibboleth");
268 // The extended trust implementation supports metadata extensions to validate
269 // signing certificates found inside the signature.
272 // Before we do the cryptogprahy, check that the EE certificate "name" matches
273 // one of the acceptable key "names" for the signer.
274 vector<string> keynames;
276 // Build a list of acceptable names. Transcode the possible key "names" to UTF-8.
277 // For some simple cases, this should handle UTF-8 encoded DNs in certificates.
278 Iterator<const IKeyDescriptor*> kd_i=role->getKeyDescriptors();
279 while (kd_i.hasNext()) {
280 const IKeyDescriptor* kd=kd_i.next();
281 if (kd->getUse()!=IKeyDescriptor::signing)
283 DSIGKeyInfoList* KIL=kd->getKeyInfo();
286 for (size_t s=0; s<KIL->getSize(); s++) {
287 const XMLCh* n=KIL->item(s)->getKeyName();
289 auto_ptr<char> kn(toUTF8(n));
290 keynames.push_back(kn.get());
294 auto_ptr<char> kn(toUTF8(role->getEntityDescriptor()->getId()));
295 keynames.push_back(kn.get());
298 X509* x=(X509*)certEE;
299 X509_NAME* subject=X509_get_subject_name(x);
301 // One way is a direct match to the subject DN.
302 // Seems that the way to do the compare is to write the X509_NAME into a BIO.
303 BIO* b = BIO_new(BIO_s_mem());
304 BIO* b2 = BIO_new(BIO_s_mem());
305 BIO_set_mem_eof_return(b, 0);
306 BIO_set_mem_eof_return(b2, 0);
307 // The flags give us LDAP order instead of X.500, with a comma separator.
308 int len=X509_NAME_print_ex(b,subject,0,XN_FLAG_RFC2253);
309 string subjectstr,subjectstr2;
311 while ((len = BIO_read(b, buf, 255)) > 0) {
315 log.infoStream() << "certificate subject: " << subjectstr << CategoryStream::ENDLINE;
316 // The flags give us LDAP order instead of X.500, with a comma plus space separator.
317 len=X509_NAME_print_ex(b2,subject,0,XN_FLAG_RFC2253 + XN_FLAG_SEP_CPLUS_SPC - XN_FLAG_SEP_COMMA_PLUS);
319 while ((len = BIO_read(b2, buf, 255)) > 0) {
324 // Check each keyname.
325 for (vector<string>::const_iterator n=keynames.begin(); n!=keynames.end(); n++) {
326 #ifdef HAVE_STRCASECMP
327 if (!strcasecmp(n->c_str(),subjectstr.c_str()) || !strcasecmp(n->c_str(),subjectstr2.c_str())) {
329 if (!stricmp(n->c_str(),subjectstr.c_str()) || !stricmp(n->c_str(),subjectstr2.c_str())) {
331 log.info("matched full subject DN to a key name (%s)", n->c_str());
340 log.debug("unable to match DN, trying TLS subjectAltName match");
341 STACK_OF(GENERAL_NAME)* altnames=(STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL);
343 int numalts = sk_GENERAL_NAME_num(altnames);
344 for (int an=0; checkName && an<numalts; an++) {
345 const GENERAL_NAME* check = sk_GENERAL_NAME_value(altnames, an);
346 if (check->type==GEN_DNS || check->type==GEN_URI) {
347 const char* altptr = (char*)ASN1_STRING_data(check->d.ia5);
348 const int altlen = ASN1_STRING_length(check->d.ia5);
350 for (vector<string>::const_iterator n=keynames.begin(); n!=keynames.end(); n++) {
351 #ifdef HAVE_STRCASECMP
352 if ((check->type==GEN_DNS && !strncasecmp(altptr,n->c_str(),altlen))
354 if ((check->type==GEN_DNS && !strnicmp(altptr,n->c_str(),altlen))
356 || (check->type==GEN_URI && !strncmp(altptr,n->c_str(),altlen))) {
357 log.info("matched DNS/URI subjectAltName to a key name (%s)", n->c_str());
364 GENERAL_NAMES_free(altnames);
368 log.debug("unable to match subjectAltName, trying TLS CN match");
369 memset(buf,0,sizeof(buf));
370 if (X509_NAME_get_text_by_NID(subject,NID_commonName,buf,255)>0) {
371 for (vector<string>::const_iterator n=keynames.begin(); n!=keynames.end(); n++) {
372 #ifdef HAVE_STRCASECMP
373 if (!strcasecmp(buf,n->c_str())) {
375 if (!stricmp(buf,n->c_str())) {
377 log.info("matched subject CN to a key name (%s)", n->c_str());
384 log.warn("no common name in certificate subject");
389 log.error("certificate has no subject?!");
393 log.error("cannot match certificate subject against acceptable key names based on KeyDescriptors");
397 log.debug("performing certificate path validation...");
399 STACK_OF(X509)* untrusted=sk_X509_new_null();
401 while (certChain.hasNext())
402 sk_X509_push(untrusted,(X509*)certChain.next());
404 // Check for entity-level KeyAuthorities.
405 const IExtendedEntityDescriptor* entity=dynamic_cast<const IExtendedEntityDescriptor*>(role->getEntityDescriptor());
407 Iterator<const IKeyAuthority*> kauths=entity->getKeyAuthorities();
408 while (kauths.hasNext())
409 if (validate((X509*)certEE,untrusted,kauths.next())) {
410 sk_X509_free(untrusted);
415 // Now repeat using any embedded metadata.
416 Iterator<IMetadata*> metas(m_metas);
417 while (metas.hasNext()) {
418 IMetadata* m=metas.next();
420 const IEntityDescriptor* ed=m->lookup(role->getEntityDescriptor()->getId());
424 // Check for entity-level KeyAuthorities.
425 entity=dynamic_cast<const IExtendedEntityDescriptor*>(ed);
427 Iterator<const IKeyAuthority*> kauths=entity->getKeyAuthorities();
428 while (kauths.hasNext())
429 if (validate((X509*)certEE,untrusted,kauths.next())) {
430 sk_X509_free(untrusted);
436 const IEntitiesDescriptor* group=role->getEntityDescriptor()->getEntitiesDescriptor();
438 const IExtendedEntitiesDescriptor* egroup=dynamic_cast<const IExtendedEntitiesDescriptor*>(group);
440 Iterator<const IKeyAuthority*> kauths=egroup->getKeyAuthorities();
441 while (kauths.hasNext())
442 if (validate((X509*)certEE,untrusted,kauths.next())) {
443 sk_X509_free(untrusted);
448 // Now repeat using any embedded metadata.
449 Iterator<IMetadata*> metas(m_metas);
450 while (metas.hasNext()) {
451 IMetadata* m=metas.next();
453 const IEntitiesDescriptor* g=m->lookupGroup(group->getName());
457 // Check for group-level KeyAuthorities.
458 egroup=dynamic_cast<const IExtendedEntitiesDescriptor*>(g);
460 Iterator<const IKeyAuthority*> kauths=egroup->getKeyAuthorities();
461 while (kauths.hasNext())
462 if (validate((X509*)certEE,untrusted,kauths.next())) {
463 sk_X509_free(untrusted);
469 group=group->getEntitiesDescriptor();
472 log.debug("failed to validate certificate chain using KeyAuthority extensions");
476 bool ShibbolethTrust::validate(const saml::SAMLSignedObject& token, const IRoleDescriptor* role, ITrust* certValidator)
478 if (BasicTrust::validate(token,role))
482 saml::NDC ndc("validate");
484 Category& log=Category::getInstance(SHIB_LOGCAT".Trust.Shibboleth");
486 // The extended trust implementation supports metadata extensions to validate
487 // signing certificates found inside the signature.
489 // Get the certificate chain out of the object in portable form.
490 vector<XSECCryptoX509*> certs;
491 for (unsigned int i=0; i<token.getX509CertificateCount(); i++) {
492 auto_ptr_char cert(token.getX509Certificate(i));
493 auto_ptr<XSECCryptoX509> x(XSECPlatformUtils::g_cryptoProvider->X509());
495 x->loadX509Base64Bin(cert.get(),strlen(cert.get()));
496 certs.push_back(x.release());
499 log.error("unable to load certificate from signature, skipping it");
503 log.debug("validating signature using certificate from within the signature");
505 // Native representations.
509 // Find and save off a pointer to the certificate that unlocks the object.
510 // Most of the time, this will be the first one anyway.
511 Iterator<XSECCryptoX509*> iter(certs);
512 while (iter.hasNext()) {
514 XSECCryptoX509* c=iter.next();
515 chain.push_back(static_cast<OpenSSLCryptoX509*>(c)->getOpenSSLX509());
518 log.info("signature verified with key inside signature, attempting certificate validation...");
519 certEE=static_cast<OpenSSLCryptoX509*>(c)->getOpenSSLX509();
529 ret=(certValidator) ? certValidator->validate(certEE,chain,role) : this->validate(certEE,chain,role);
531 log.debug("failed to verify signature with embedded certificates");
533 for (vector<XSECCryptoX509*>::iterator j=certs.begin(); j!=certs.end(); j++)