52444f7a85fd598013186a2bcf41866e5d69d0e6
[shibboleth/sp.git] / siterefresh / siterefresh.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
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.
22  *
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
28  *
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.
33  *
34  *
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.
48  */
49
50 /* siterefresh.cpp - command-line tool to refresh and verify metadata
51
52    Scott Cantor
53    5/12/03
54
55    $Id$
56 */
57
58 #include <shib-target/shib-target.h>
59
60 #include <fstream>
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>
75
76 using namespace std;
77 using namespace saml;
78 using namespace shibboleth;
79 using namespace log4cpp;
80
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
85 };
86
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
91 };
92
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
96 };
97
98 void verifySignature(DOMDocument* doc, DOMNode* sigNode, const char* cert=NULL)
99 {
100     Category& log=Category::getInstance("siterefresh");
101     static const XMLCh ID[]={chLatin_I, chLatin_D, chNull};
102
103     // Load the signature.
104     XSECProvider prov;
105     DSIGSignature* sig=NULL;
106     try {
107         sig=prov.newSignatureFromDOM(doc,sigNode);
108         sig->load();
109
110         bool valid=false;
111
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);
116             if (ref) {
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)
123                             valid=true;
124                         else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N) {
125                             valid=false;
126                             break;
127                         }
128                     }
129                 }
130             }
131         }
132     
133         if (!valid) {
134             log.error("detected an invalid signature profile");
135             throw InvalidCryptoException("detected an invalid signature profile");
136         }
137
138         if (cert) {
139             // Load the certificate, stripping the header and trailer.
140             string certbuf,line;
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) {
146                     if (sawheader)
147                         certbuf+=line + '\n';
148                 }
149                 else
150                     sawheader=true;
151             }
152             x509->loadX509Base64Bin(certbuf.data(),certbuf.length());
153             sig->setSigningKey(x509->clonePublicKey());
154         }
155         else {
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());
159         }
160         
161         if (!sig->verify()) {
162             log.error("detected an invalid signature value");
163             throw InvalidCryptoException("detected an invalid signature value");
164         }
165
166         prov.releaseSignature(sig);
167     }
168     catch(...) {
169         if (sig)
170             prov.releaseSignature(sig);
171         throw;
172     }
173 }
174
175 int main(int argc,char* argv[])
176 {
177     int ret=0;
178     SAMLConfig& conf=SAMLConfig::getConfig();
179     bool verify=true;
180     char* url_param=NULL;
181     char* cert_param=NULL;
182     char* out_param=NULL;
183     char* path=getenv("SHIBSCHEMAS");
184     char* ns_param=NULL;
185     char* name_param=NULL;
186
187     for (int i=1; i<argc; i++) {
188         if (!strcmp(argv[i],"--schema") && i+1<argc)
189             path=argv[++i];
190         else if (!strcmp(argv[i],"--url") && i+1<argc)
191             url_param=argv[++i];
192         else if (!strcmp(argv[i],"--noverify"))
193             verify=false;
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)
197             out_param=argv[++i];
198         else if (!strcmp(argv[i],"--rootns") && i+1<argc)
199             ns_param=argv[++i];
200         else if (!strcmp(argv[i],"--rootname") && i+1<argc)
201             name_param=argv[++i];
202     }
203
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;
212         return -100;
213     }
214
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 };
224
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;
229     if (!conf.init())
230         return -10;
231
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);
237
238     try {
239         // Parse the specified document.
240         saml::XML::Parser p;
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);
246             doc=p.parse(dsrc);
247         }
248         else {
249             StdInInputSource src;
250             Wrapper4InputSource dsrc(&src,false);
251             doc=p.parse(dsrc);
252         }
253     
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);
260         }
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");
266
267         // Verify the "root" signature.
268         DOMElement* rootSig=saml::XML::getFirstChildElement(doc->getDocumentElement(),saml::XML::XMLSIG_NS,L(Signature));
269         if (verify) {
270             if (rootSig) {
271                 verifySignature(doc,rootSig,cert_param);
272             }
273             else {
274                 doc->release();
275                 log.error("unable to locate root signature to verify in document");
276                 throw InvalidCryptoException("Verification implies that the document must be signed");
277             }
278         }
279         else if (rootSig) {
280             log.warn("verification of signer disabled, make sure you trust the source of this file!");
281             verifySignature(doc,rootSig,cert_param);
282         }
283         else {
284             log.warn("verification disabled, and file is unsigned!");
285         }
286
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);
291
292         if (out_param) {
293             // Output the data to the specified file.
294             ofstream outfile(out_param);
295             outfile << *(doc->getDocumentElement());
296         }
297         else
298             cout << *(doc->getDocumentElement());
299         doc->release();
300     }
301     catch (InvalidCryptoException&) {
302         ret=-1;
303     }
304     catch(SAMLException& e) {
305         log.errorStream() << "caught a SAML exception: " << e.what() << CategoryStream::ENDLINE;
306         ret=-2;
307     }
308     catch(XMLException& e) {
309         auto_ptr_char temp(e.getMessage());
310         log.errorStream() << "caught an XML exception: " << temp.get() << CategoryStream::ENDLINE;
311         ret=-3;
312     }
313     catch(XSECException& e) {
314         auto_ptr_char temp(e.getMsg());
315         log.errorStream() << "caught an XMLSec exception: " << temp.get() << CategoryStream::ENDLINE;
316         ret=-4;
317     }
318     catch(XSECCryptoException& e) {
319         log.errorStream() << "caught an XMLSecCrypto exception: " << e.getMsg() << CategoryStream::ENDLINE;
320         ret=-5;
321     }
322     catch(...) {
323         log.errorStream() << "caught an unknown exception" << CategoryStream::ENDLINE;
324         ret=-6;
325     }
326
327     conf.term();
328     return ret;
329 }