Forgot to add appender header
[shibboleth/cpp-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 <xsec/enc/XSECCryptoProvider.hpp>
65 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
66 #include <xsec/enc/XSECCryptoException.hpp>
67 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
68 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
69 #include <xsec/framework/XSECProvider.hpp>
70 #include <xsec/framework/XSECException.hpp>
71 #include <xsec/dsig/DSIGTransformC14n.hpp>
72 #include <xsec/dsig/DSIGReference.hpp>
73 #include <xsec/dsig/DSIGTransformList.hpp>
74
75 using namespace std;
76 using namespace saml;
77 using namespace shibboleth;
78 using namespace log4cpp;
79
80 void verifySignature(DOMDocument* doc, DOMElement* sigNode, const char* cert=NULL)
81 {
82     Category& log=Category::getInstance("siterefresh");
83
84     // Load the signature.
85     XSECProvider prov;
86     DSIGSignature* sig=NULL;
87     try {
88         sig=prov.newSignatureFromDOM(doc,sigNode);
89         sig->load();
90
91         bool valid=false;
92
93         // Verify the signature coverage.
94         DSIGReferenceList* refs=sig->getReferenceList();
95         if (sig->getSignatureMethod()==SIGNATURE_RSA && refs && refs->getSize()==1) {
96             DSIGReference* ref=refs->item(0);
97             if (ref) {
98                 const XMLCh* URI=ref->getURI();
99                 if (URI==NULL || *URI==0) {
100                     DSIGTransformList* tlist=ref->getTransforms();
101                     for (int i=0; tlist && i<tlist->getSize(); i++) {
102                         if (tlist->item(i)->getTransformType()==TRANSFORM_ENVELOPED_SIGNATURE)
103                             valid=true;
104                         else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N &&
105                                  tlist->item(i)->getTransformType()!=TRANSFORM_C14N) {
106                             valid=false;
107                             break;
108                         }
109                     }
110                 }
111             }
112         }
113     
114         if (!valid) {
115             log.error("detected an invalid signature profile");
116             throw InvalidCryptoException("detected an invalid signature profile");
117         }
118
119         if (cert) {
120             // Load the certificate, stripping the first and last lines.
121             string certbuf,line;
122             auto_ptr<OpenSSLCryptoX509> x509(new OpenSSLCryptoX509());
123             ifstream infile(cert);
124             while (!getline(infile,line).fail())
125                 if (line.find("CERTIFICATE")==string::npos)
126                     certbuf+=line + '\n';
127             x509->loadX509Base64Bin(certbuf.data(),certbuf.length());
128             sig->setSigningKey(x509->clonePublicKey());
129         }
130         else {
131             XSECKeyInfoResolverDefault resolver;
132             sig->setKeyInfoResolver(resolver.clone());
133         }
134         
135         if (!sig->verify()) {
136             log.error("detected an invalid signature value");
137             throw InvalidCryptoException("detected an invalid signature value");
138         }
139
140         prov.releaseSignature(sig);
141     }
142     catch(...) {
143         if (sig)
144             prov.releaseSignature(sig);
145         throw;
146     }
147 }
148
149 int main(int argc,char* argv[])
150 {
151     int ret=0;
152     SAMLConfig& conf=SAMLConfig::getConfig();
153     bool verify=true;
154     char* url_param=NULL;
155     char* cert_param=NULL;
156     char* out_param=NULL;
157     char* path=getenv("SHIBSCHEMAS");
158     char* ns_param=NULL;
159     char* name_param=NULL;
160
161     for (int i=1; i<argc; i++) {
162         if (!strcmp(argv[i],"--schema") && i+1<argc)
163             path=argv[++i];
164         else if (!strcmp(argv[i],"--url") && i+1<argc)
165             url_param=argv[++i];
166         else if (!strcmp(argv[i],"--noverify"))
167             verify=false;
168         else if (!strcmp(argv[i],"--cert") && i+1<argc)
169             cert_param=argv[++i];
170         else if (!strcmp(argv[i],"--out") && i+1<argc)
171             out_param=argv[++i];
172         else if (!strcmp(argv[i],"--rootns") && i+1<argc)
173             ns_param=argv[++i];
174         else if (!strcmp(argv[i],"--rootname") && i+1<argc)
175             name_param=argv[++i];
176     }
177
178     if (!url_param || !out_param || (verify && !cert_param)) {
179         cout << "usage: " << argv[0] << endl <<
180             "\t--url <URL of metadata>" << endl <<
181             "\t--out <pathname to copy data to>" << endl <<
182             "\t--noverify OR --cert <PEM Certificate>" << endl <<
183             "\t[--schema <schema path>]" << endl <<
184             "\t[--rootns <root element XML namespace>]" << endl <<
185             "\t[--rootname <root element name>]" << endl;
186         return -100;
187     }
188
189     Category::setRootPriority(Priority::WARN);
190     Category::getRoot().addAppender(new OstreamAppender("default",&cerr));
191     Category& log=Category::getInstance("siterefresh");
192     conf.schema_dir=path ? path : SHIB_SCHEMAS;
193     if (!conf.init())
194         return -10;
195
196     static const XMLCh Trust[] = { chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull };
197     static const XMLCh SiteGroup[] =
198     { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chLatin_G, chLatin_r, chLatin_o, chLatin_u, chLatin_p, chNull };
199
200     static const XMLCh TRUST_NS[] = // urn:mace:shibboleth:trust:1.0
201     { chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,
202       chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,
203       chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chColon, chDigit_1, chPeriod, chDigit_0, chNull
204     };
205
206     static const XMLCh TRUST_SCHEMA_ID[] = // shibboleth-trust-1.0.xsd
207     { chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chDash,
208       chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chDash, chDigit_1, chPeriod, chDigit_0, chPeriod,
209       chLatin_x, chLatin_s, chLatin_d, chNull
210     };
211
212     static const XMLCh SHIB_SCHEMA_ID[] = // shibboleth.xsd
213     { chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chPeriod,
214       chLatin_x, chLatin_s, chLatin_d, chNull
215     };
216
217     saml::XML::registerSchema(shibboleth::Constants::SHIB_NS,SHIB_SCHEMA_ID);
218     saml::XML::registerSchema(TRUST_NS,TRUST_SCHEMA_ID);
219
220     try {
221         // Parse the specified document.
222         saml::XML::Parser p;
223         static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
224         URLInputSource src(base,url_param);
225         Wrapper4InputSource dsrc(&src,false);
226         DOMDocument* doc=p.parse(dsrc);
227
228         // Check root element.
229         if (ns_param && name_param) {
230             auto_ptr_XMLCh ns(ns_param);
231             auto_ptr_XMLCh name(name_param);
232             if (!saml::XML::isElementNamed(doc->getDocumentElement(),ns.get(),name.get()))
233                 throw MalformedException(string("Root element does not match specified QName of {") + ns_param + "}:" + name_param);
234         }
235         else if (!saml::XML::isElementNamed(doc->getDocumentElement(),shibboleth::Constants::SHIB_NS,SiteGroup) &&
236                     !saml::XML::isElementNamed(doc->getDocumentElement(),TRUST_NS,Trust))
237             throw MalformedException("Root element does not match SiteGroup or Trust");
238
239         // If we're verifying, grab the embedded signature.
240         DOMElement* n=saml::XML::getLastChildElement(doc->getDocumentElement(),saml::XML::XMLSIG_NS,L(Signature));
241         if (verify) {
242             if (n)
243                 verifySignature(doc,n,cert_param);
244             else {
245                 doc->release();
246                             log.error("unable to locate a signature to verify in document");
247                             throw InvalidCryptoException("Verification implies that the document must be signed");
248             }
249         }
250         else if (n) {
251             log.warn("verification of signer disabled, make sure you trust the source of this file!");
252             verifySignature(doc,n);
253         }
254         else {
255             log.warn("verification disabled, and file is unsigned!");
256         }
257
258         // Output the data to the specified file.
259         ofstream outfile(out_param);
260         outfile << *(doc->getDocumentElement());
261
262         doc->release();
263     }
264     catch (InvalidCryptoException&) {
265         ret=-1;
266     }
267     catch(SAMLException& e) {
268         log.errorStream() << "caught a SAML exception: " << e.what() << CategoryStream::ENDLINE;
269         ret=-2;
270     }
271     catch(XMLException& e) {
272         auto_ptr_char temp(e.getMessage());
273         log.errorStream() << "caught an XML exception: " << temp.get() << CategoryStream::ENDLINE;
274         ret=-3;
275     }
276     catch(XSECException& e) {
277         auto_ptr_char temp(e.getMsg());
278         log.errorStream() << "caught an XMLSec exception: " << temp.get() << CategoryStream::ENDLINE;
279         ret=-4;
280     }
281     catch(XSECCryptoException& e) {
282         log.errorStream() << "caught an XMLSecCrypto exception: " << e.getMsg() << CategoryStream::ENDLINE;
283         ret=-5;
284     }
285     catch(...) {
286         log.errorStream() << "caught an unknown exception" << CategoryStream::ENDLINE;
287         ret=-6;
288     }
289
290     conf.term();
291     return ret;
292 }