2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 /* siterefresh.cpp - command-line tool to refresh and verify metadata
58 #include <shib-target/shib-target.h>
61 #include <log4cpp/Category.hh>
62 #include <log4cpp/OstreamAppender.hh>
63 #include <xercesc/framework/URLInputSource.hpp>
64 #include <xercesc/framework/StdInInputSource.hpp>
65 #include <xsec/enc/XSECCryptoProvider.hpp>
66 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
67 #include <xsec/enc/XSECCryptoException.hpp>
68 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
69 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
70 #include <xsec/framework/XSECProvider.hpp>
71 #include <xsec/framework/XSECException.hpp>
72 #include <xsec/dsig/DSIGTransformC14n.hpp>
73 #include <xsec/dsig/DSIGReference.hpp>
74 #include <xsec/dsig/DSIGTransformList.hpp>
78 using namespace shibboleth;
79 using namespace log4cpp;
81 static const XMLCh TRUST_NS[] = // urn:mace:shibboleth:trust:1.0
82 { chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,
83 chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,
84 chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chColon, chDigit_1, chPeriod, chDigit_0, chNull
87 static const XMLCh TRUST_SCHEMA_ID[] = // shibboleth-trust-1.0.xsd
88 { chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chDash,
89 chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chDash, chDigit_1, chPeriod, chDigit_0, chPeriod,
90 chLatin_x, chLatin_s, chLatin_d, chNull
93 static const XMLCh SHIB_SCHEMA_ID[] = // shibboleth.xsd
94 { chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chPeriod,
95 chLatin_x, chLatin_s, chLatin_d, chNull
98 void verifySignature(DOMDocument* doc, DOMNode* sigNode, const char* cert=NULL)
100 Category& log=Category::getInstance("siterefresh");
101 static const XMLCh ID[]={chLatin_I, chLatin_D, chNull};
103 // Load the signature.
105 DSIGSignature* sig=NULL;
107 sig=prov.newSignatureFromDOM(doc,sigNode);
112 // Verify the signature coverage.
113 DSIGReferenceList* refs=sig->getReferenceList();
114 if (sig->getSignatureMethod()==SIGNATURE_RSA && refs && refs->getSize()==1) {
115 DSIGReference* ref=refs->item(0);
117 const XMLCh* URI=ref->getURI();
118 if (!URI || !*URI || (*URI==chPound &&
119 !XMLString::compareString(&URI[1],static_cast<DOMElement*>(sigNode->getParentNode())->getAttributeNS(NULL,ID)))) {
120 DSIGTransformList* tlist=ref->getTransforms();
121 for (int i=0; tlist && i<tlist->getSize(); i++) {
122 if (tlist->item(i)->getTransformType()==TRANSFORM_ENVELOPED_SIGNATURE)
124 else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N) {
134 log.error("detected an invalid signature profile");
135 throw InvalidCryptoException("detected an invalid signature profile");
139 // Load the certificate, stripping the header and trailer.
141 auto_ptr<OpenSSLCryptoX509> x509(new OpenSSLCryptoX509());
142 bool sawheader=false;
143 ifstream infile(cert);
144 while (!getline(infile,line).fail()) {
145 if (line.find("CERTIFICATE-----")==string::npos) {
147 certbuf+=line + '\n';
152 x509->loadX509Base64Bin(certbuf.data(),certbuf.length());
153 sig->setSigningKey(x509->clonePublicKey());
156 log.warn("verifying with key inside signature, this is a sanity check but provides no security");
157 XSECKeyInfoResolverDefault resolver;
158 sig->setKeyInfoResolver(resolver.clone());
161 if (!sig->verify()) {
162 log.error("detected an invalid signature value");
163 throw InvalidCryptoException("detected an invalid signature value");
166 prov.releaseSignature(sig);
170 prov.releaseSignature(sig);
175 int main(int argc,char* argv[])
178 SAMLConfig& conf=SAMLConfig::getConfig();
180 char* url_param=NULL;
181 char* cert_param=NULL;
182 char* out_param=NULL;
183 char* path=getenv("SHIBSCHEMAS");
185 char* name_param=NULL;
187 for (int i=1; i<argc; i++) {
188 if (!strcmp(argv[i],"--schema") && i+1<argc)
190 else if (!strcmp(argv[i],"--url") && i+1<argc)
192 else if (!strcmp(argv[i],"--noverify"))
194 else if (!strcmp(argv[i],"--cert") && i+1<argc)
195 cert_param=argv[++i];
196 else if (!strcmp(argv[i],"--out") && i+1<argc)
198 else if (!strcmp(argv[i],"--rootns") && i+1<argc)
200 else if (!strcmp(argv[i],"--rootname") && i+1<argc)
201 name_param=argv[++i];
204 if (verify && !cert_param) {
205 cout << "usage: " << argv[0] << endl <<
206 "\t--url <URL of metadata>" << endl <<
207 "\t--noverify OR --cert <PEM Certificate>" << endl <<
208 "\t[--out <pathname to copy data to>]" << endl <<
209 "\t[--schema <schema path>]" << endl <<
210 "\t[--rootns <root element XML namespace>]" << endl <<
211 "\t[--rootname <root element name>]" << endl;
215 static const XMLCh Trust[] = { chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull };
216 static const XMLCh SiteGroup[] =
217 { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chLatin_G, chLatin_r, chLatin_o, chLatin_u, chLatin_p, chNull };
218 static const XMLCh EntitiesDescriptor[] =
219 { chLatin_E, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_i, chLatin_e, chLatin_s,
220 chLatin_D, chLatin_e, chLatin_s, chLatin_c, chLatin_r, chLatin_i, chLatin_p, chLatin_t, chLatin_o, chLatin_r, chNull };
221 static const XMLCh EntityDescriptor[] =
222 { chLatin_E, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_y,
223 chLatin_D, chLatin_e, chLatin_s, chLatin_c, chLatin_r, chLatin_i, chLatin_p, chLatin_t, chLatin_o, chLatin_r, chNull };
225 Category::setRootPriority(Priority::WARN);
226 Category::getRoot().addAppender(new OstreamAppender("default",&cerr));
227 Category& log=Category::getInstance("siterefresh");
228 conf.schema_dir=path ? path : SHIB_SCHEMAS;
232 saml::XML::registerSchema(Constants::SHIB_NS,SHIB_SCHEMA_ID);
233 saml::XML::registerSchema(TRUST_NS,TRUST_SCHEMA_ID);
234 saml::XML::registerSchema(shibtarget::XML::SAML2META_NS,shibtarget::XML::SAML2META_SCHEMA_ID);
235 saml::XML::registerSchema(shibtarget::XML::SAML2ASSERT_NS,shibtarget::XML::SAML2ASSERT_SCHEMA_ID);
236 saml::XML::registerSchema(shibtarget::XML::XMLENC_NS,shibtarget::XML::XMLENC_SCHEMA_ID);
239 // Parse the specified document.
241 static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
242 DOMDocument* doc=NULL;
243 if (url_param && *url_param) {
244 URLInputSource src(base,url_param);
245 Wrapper4InputSource dsrc(&src,false);
249 StdInInputSource src;
250 Wrapper4InputSource dsrc(&src,false);
254 // Check root element.
255 if (ns_param && name_param) {
256 auto_ptr_XMLCh ns(ns_param);
257 auto_ptr_XMLCh name(name_param);
258 if (!saml::XML::isElementNamed(doc->getDocumentElement(),ns.get(),name.get()))
259 throw MalformedException(string("Root element does not match specified QName of {") + ns_param + "}:" + name_param);
261 else if (!saml::XML::isElementNamed(doc->getDocumentElement(),Constants::SHIB_NS,SiteGroup) &&
262 !saml::XML::isElementNamed(doc->getDocumentElement(),shibtarget::XML::SAML2META_NS,EntitiesDescriptor) &&
263 !saml::XML::isElementNamed(doc->getDocumentElement(),shibtarget::XML::SAML2META_NS,EntityDescriptor) &&
264 !saml::XML::isElementNamed(doc->getDocumentElement(),TRUST_NS,Trust))
265 throw MalformedException("Root element does not signify a known metadata or trust format");
267 // Verify the "root" signature.
268 DOMElement* rootSig=saml::XML::getFirstChildElement(doc->getDocumentElement(),saml::XML::XMLSIG_NS,L(Signature));
271 verifySignature(doc,rootSig,cert_param);
275 log.error("unable to locate root signature to verify in document");
276 throw InvalidCryptoException("Verification implies that the document must be signed");
280 log.warn("verification of signer disabled, make sure you trust the source of this file!");
281 verifySignature(doc,rootSig,cert_param);
284 log.warn("verification disabled, and file is unsigned!");
287 // Verify all signatures.
288 DOMNodeList* siglist=doc->getElementsByTagNameNS(saml::XML::XMLSIG_NS,L(Signature));
289 for (int i=0; siglist && i<siglist->getLength(); i++)
290 verifySignature(doc,siglist->item(i),cert_param);
293 // Output the data to the specified file.
294 ofstream outfile(out_param);
295 outfile << *(doc->getDocumentElement());
298 cout << *(doc->getDocumentElement());
301 catch (InvalidCryptoException&) {
304 catch(SAMLException& e) {
305 log.errorStream() << "caught a SAML exception: " << e.what() << CategoryStream::ENDLINE;
308 catch(XMLException& e) {
309 auto_ptr_char temp(e.getMessage());
310 log.errorStream() << "caught an XML exception: " << temp.get() << CategoryStream::ENDLINE;
313 catch(XSECException& e) {
314 auto_ptr_char temp(e.getMsg());
315 log.errorStream() << "caught an XMLSec exception: " << temp.get() << CategoryStream::ENDLINE;
318 catch(XSECCryptoException& e) {
319 log.errorStream() << "caught an XMLSecCrypto exception: " << e.getMsg() << CategoryStream::ENDLINE;
323 log.errorStream() << "caught an unknown exception" << CategoryStream::ENDLINE;