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