Add option for inclusive c14n.
[shibboleth/cpp-sp.git] / siterefresh / siterefresh.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /* siterefresh.cpp - command-line tool to refresh and verify metadata
18
19    Scott Cantor
20    5/12/03
21
22    $Id$
23 */
24
25 #include <shib-target/shib-target.h>
26
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
29 #else
30 # include "config.h"
31 #endif
32
33 #if defined(HAVE_LOG4SHIB)
34 # include <log4shib/Category.hh>
35 # include <log4shib/OstreamAppender.hh>
36 namespace siterefresh {
37     namespace logging = log4shib;
38 };
39 #elif defined(HAVE_LOG4CPP)
40 # include <log4cpp/Category.hh>
41 # include <log4cpp/OstreamAppender.hh>
42 namespace siterefresh {
43     namespace logging = log4cpp;
44 };
45 #else
46 # error "Supported logging library not available."
47 #endif
48
49 #include <fstream>
50 #include <xercesc/framework/URLInputSource.hpp>
51 #include <xercesc/framework/StdInInputSource.hpp>
52 #include <xsec/enc/XSECCryptoProvider.hpp>
53 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
54 #include <xsec/enc/XSECCryptoException.hpp>
55 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
56 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
57 #include <xsec/framework/XSECProvider.hpp>
58 #include <xsec/framework/XSECException.hpp>
59 #include <xsec/dsig/DSIGTransformC14n.hpp>
60 #include <xsec/dsig/DSIGReference.hpp>
61 #include <xsec/dsig/DSIGTransformList.hpp>
62
63 using namespace std;
64 using namespace saml;
65 using namespace shibboleth;
66 using namespace siterefresh::logging;
67
68 static const XMLCh TRUST_NS[] = // urn:mace:shibboleth:trust:1.0
69 { chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,
70   chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,
71   chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chColon, chDigit_1, chPeriod, chDigit_0, chNull
72 };
73
74 static const XMLCh TRUST_SCHEMA_ID[] = // shibboleth-trust-1.0.xsd
75 { chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chDash,
76   chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chDash, chDigit_1, chPeriod, chDigit_0, chPeriod,
77   chLatin_x, chLatin_s, chLatin_d, chNull
78 };
79
80 static const XMLCh SHIB_SCHEMA_ID[] = // shibboleth.xsd
81 { chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chPeriod,
82   chLatin_x, chLatin_s, chLatin_d, chNull
83 };
84
85 void verifySignature(DOMDocument* doc, DOMNode* sigNode, const char* cert=NULL)
86 {
87     Category& log=Category::getInstance("siterefresh");
88     static const XMLCh ID[]={chLatin_I, chLatin_D, chNull};
89
90     // Load the signature.
91     XSECProvider prov;
92     DSIGSignature* sig=NULL;
93     try {
94         sig=prov.newSignatureFromDOM(doc,sigNode);
95         sig->load();
96
97         bool valid=false;
98
99         // Verify the signature coverage.
100         DSIGReferenceList* refs=sig->getReferenceList();
101         if (sig->getSignatureMethod()==SIGNATURE_RSA && refs && refs->getSize()==1) {
102             DSIGReference* ref=refs->item(0);
103             if (ref) {
104                 const XMLCh* URI=ref->getURI();
105                 if (!URI || !*URI || (*URI==chPound &&
106                         !XMLString::compareString(&URI[1],static_cast<DOMElement*>(sigNode->getParentNode())->getAttributeNS(NULL,ID)))) {
107                     DSIGTransformList* tlist=ref->getTransforms();
108                     for (unsigned int i=0; tlist && i<tlist->getSize(); i++) {
109                         if (tlist->item(i)->getTransformType()==TRANSFORM_ENVELOPED_SIGNATURE)
110                             valid=true;
111                         else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N &&
112                                  tlist->item(i)->getTransformType()!=TRANSFORM_C14N) {
113                             valid=false;
114                             break;
115                         }
116                     }
117                 }
118             }
119         }
120     
121         if (!valid) {
122             log.error("detected an invalid signature profile");
123             throw InvalidCryptoException("detected an invalid signature profile");
124         }
125
126         if (cert) {
127             // Load the certificate, stripping the header and trailer.
128             string certbuf,line;
129             auto_ptr<OpenSSLCryptoX509> x509(new OpenSSLCryptoX509());
130             bool sawheader=false;
131             ifstream infile(cert);
132             while (!getline(infile,line).fail()) {
133                 if (line.find("CERTIFICATE-----")==string::npos) {
134                     if (sawheader)
135                         certbuf+=line + '\n';
136                 }
137                 else
138                     sawheader=true;
139             }
140             x509->loadX509Base64Bin(certbuf.data(),certbuf.length());
141             sig->setSigningKey(x509->clonePublicKey());
142         }
143         else {
144             log.warn("verifying with key inside signature, this is a sanity check but provides no security");
145             XSECKeyInfoResolverDefault resolver;
146             sig->setKeyInfoResolver(resolver.clone());
147         }
148         
149         if (!sig->verify()) {
150             log.error("detected an invalid signature value");
151             throw InvalidCryptoException("detected an invalid signature value");
152         }
153
154         prov.releaseSignature(sig);
155     }
156     catch(...) {
157         if (sig)
158             prov.releaseSignature(sig);
159         throw;
160     }
161 }
162
163 int main(int argc,char* argv[])
164 {
165     int ret=0;
166     SAMLConfig& conf=SAMLConfig::getConfig();
167     bool verify=true,quiet=false;
168     char* url_param=NULL;
169     char* cert_param=NULL;
170     char* out_param=NULL;
171     char* path=getenv("SHIBSCHEMAS");
172     char* ns_param=NULL;
173     char* name_param=NULL;
174
175     for (int i=1; i<argc; i++) {
176         if (!strcmp(argv[i],"--schema") && i+1<argc)
177             path=argv[++i];
178         else if (!strcmp(argv[i],"--url") && i+1<argc)
179             url_param=argv[++i];
180         else if (!strcmp(argv[i],"--noverify"))
181             verify=false;
182         else if (!strcmp(argv[i],"--quiet") || !strcmp(argv[i],"-q"))
183             quiet=true;
184         else if (!strcmp(argv[i],"--cert") && i+1<argc)
185             cert_param=argv[++i];
186         else if (!strcmp(argv[i],"--out") && i+1<argc)
187             out_param=argv[++i];
188         else if (!strcmp(argv[i],"--rootns") && i+1<argc)
189             ns_param=argv[++i];
190         else if (!strcmp(argv[i],"--rootname") && i+1<argc)
191             name_param=argv[++i];
192     }
193
194     if (verify && !cert_param) {
195         cout << "usage: " << argv[0] << endl <<
196             "\t--url <URL of metadata>" << endl <<
197             "\t--noverify OR --cert <PEM Certificate>" << endl <<
198             "\t[--out <pathname to copy data to>]" << endl <<
199             "\t[--schema <schema path>]" << endl <<
200             "\t[--rootns <root element XML namespace>]" << endl <<
201             "\t[--rootname <root element name>]" << endl <<
202             "\t[--quiet]" << endl;
203         return -100;
204     }
205
206     static const XMLCh Trust[] = { chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull };
207     static const XMLCh SiteGroup[] =
208     { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chLatin_G, chLatin_r, chLatin_o, chLatin_u, chLatin_p, chNull };
209     static const XMLCh EntitiesDescriptor[] =
210     { chLatin_E, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_i, chLatin_e, chLatin_s,
211       chLatin_D, chLatin_e, chLatin_s, chLatin_c, chLatin_r, chLatin_i, chLatin_p, chLatin_t, chLatin_o, chLatin_r, chNull };
212     static const XMLCh EntityDescriptor[] =
213     { chLatin_E, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_y,
214       chLatin_D, chLatin_e, chLatin_s, chLatin_c, chLatin_r, chLatin_i, chLatin_p, chLatin_t, chLatin_o, chLatin_r, chNull };
215
216     Category::setRootPriority(Priority::WARN);
217     Category::getRoot().addAppender(new OstreamAppender("default",&cerr));
218     Category& log=Category::getInstance("siterefresh");
219     conf.schema_dir=path ? path : SHIB_SCHEMAS;
220     if (!conf.init())
221         return -10;
222
223     saml::XML::registerSchema(Constants::SHIB_NS,SHIB_SCHEMA_ID);
224     saml::XML::registerSchema(TRUST_NS,TRUST_SCHEMA_ID);
225     saml::XML::registerSchema(shibtarget::XML::SAML2META_NS,shibtarget::XML::SAML2META_SCHEMA_ID);
226     saml::XML::registerSchema(shibtarget::XML::SAML2ASSERT_NS,shibtarget::XML::SAML2ASSERT_SCHEMA_ID);
227     saml::XML::registerSchema(shibtarget::XML::XMLENC_NS,shibtarget::XML::XMLENC_SCHEMA_ID);
228
229     try {
230         // Parse the specified document.
231         saml::XML::Parser p;
232         static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
233         DOMDocument* doc=NULL;
234         if (url_param && *url_param) {
235             URLInputSource src(base,url_param);
236             Wrapper4InputSource dsrc(&src,false);
237             doc=p.parse(dsrc);
238         }
239         else {
240             StdInInputSource src;
241             Wrapper4InputSource dsrc(&src,false);
242             doc=p.parse(dsrc);
243         }
244     
245         // Check root element.
246         if (ns_param && name_param) {
247             auto_ptr_XMLCh ns(ns_param);
248             auto_ptr_XMLCh name(name_param);
249             if (!saml::XML::isElementNamed(doc->getDocumentElement(),ns.get(),name.get()))
250                 throw MalformedException(string("Root element does not match specified QName of {") + ns_param + "}:" + name_param);
251         }
252         else if (!saml::XML::isElementNamed(doc->getDocumentElement(),Constants::SHIB_NS,SiteGroup) &&
253                  !saml::XML::isElementNamed(doc->getDocumentElement(),shibtarget::XML::SAML2META_NS,EntitiesDescriptor) &&
254                  !saml::XML::isElementNamed(doc->getDocumentElement(),shibtarget::XML::SAML2META_NS,EntityDescriptor) &&
255                  !saml::XML::isElementNamed(doc->getDocumentElement(),TRUST_NS,Trust))
256             throw MalformedException("Root element does not signify a known metadata or trust format");
257
258         // Verify the "root" signature.
259         DOMElement* rootSig=saml::XML::getFirstChildElement(doc->getDocumentElement(),saml::XML::XMLSIG_NS,L(Signature));
260         if (verify) {
261             if (rootSig) {
262                 verifySignature(doc,rootSig,cert_param);
263             }
264             else {
265                 doc->release();
266                 log.error("unable to locate root signature to verify in document");
267                 throw InvalidCryptoException("Verification implies that the document must be signed");
268             }
269         }
270         else if (rootSig) {
271             if (!quiet)
272                 log.warn("verification of signer disabled, make sure you trust the source of this file!");
273             verifySignature(doc,rootSig,cert_param);
274         }
275         else {
276             if (!quiet)
277                 log.warn("verification disabled, and file is unsigned!");
278         }
279
280         // Verify all signatures.
281         DOMNodeList* siglist=doc->getElementsByTagNameNS(saml::XML::XMLSIG_NS,L(Signature));
282         for (XMLSize_t i=0; siglist && i<siglist->getLength(); i++)
283             verifySignature(doc,siglist->item(i),cert_param);
284
285         if (out_param) {
286             // Output the data to the specified file.
287             ofstream outfile(out_param);
288             outfile << *(doc->getDocumentElement());
289         }
290         else
291             cout << *(doc->getDocumentElement());
292         doc->release();
293     }
294     catch (InvalidCryptoException&) {
295         ret=-1;
296     }
297     catch(SAMLException& e) {
298         log.errorStream() << "caught a SAML exception: " << e.what() << siterefresh::logging::eol;
299         ret=-2;
300     }
301     catch(XMLException& e) {
302         auto_ptr_char temp(e.getMessage());
303         log.errorStream() << "caught an XML exception: " << temp.get() << siterefresh::logging::eol;
304         ret=-3;
305     }
306     catch(XSECException& e) {
307         auto_ptr_char temp(e.getMsg());
308         log.errorStream() << "caught an XMLSec exception: " << temp.get() << siterefresh::logging::eol;
309         ret=-4;
310     }
311     catch(XSECCryptoException& e) {
312         log.errorStream() << "caught an XMLSecCrypto exception: " << e.getMsg() << siterefresh::logging::eol;
313         ret=-5;
314     }
315     catch(...) {
316         log.errorStream() << "caught an unknown exception" << siterefresh::logging::eol;
317         ret=-6;
318     }
319
320     conf.term();
321     return ret;
322 }