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