*** empty log message ***
[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 site metadata
51
52    Scott Cantor
53    5/12/03
54
55    $History:$
56 */
57
58 #include "../shib/shib.h"
59
60 #include <fstream>
61 #include <log4cpp/Category.hh>
62 #include <xercesc/framework/URLInputSource.hpp>
63 #include <xsec/enc/XSECCryptoProvider.hpp>
64 #include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
65 #include <xsec/enc/XSECCryptoException.hpp>
66 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
67 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
68 #include <xsec/framework/XSECProvider.hpp>
69 #include <xsec/framework/XSECException.hpp>
70 #include <xsec/dsig/DSIGTransformC14n.hpp>
71 #include <xsec/dsig/DSIGReference.hpp>
72 #include <xsec/dsig/DSIGTransformList.hpp>
73
74 #ifdef WIN32
75 # define DEFAULT_SCHEMA_DIR "/shibboleth/etc/shibboleth/"
76 #else
77 # define DEFAULT_SCHEMA_DIR "/opt/shibboleth/etc/shibboleth/"
78 #endif
79
80 using namespace std;
81 using namespace saml;
82 using namespace shibboleth;
83 using namespace log4cpp;
84
85 void verifySignature(DOMDocument* doc, DOMElement* sigNode, const char* cert)
86 {
87     Category& log=Category::getInstance("siterefresh");
88
89     // Load the certificate, stripping the first and last lines.
90     string certbuf,line1,line2;
91     auto_ptr<OpenSSLCryptoX509> x509(new OpenSSLCryptoX509());
92     ifstream infile(cert);
93     getline(infile,line1);
94     while (!getline(infile,line1).fail())
95     {
96         certbuf+=line2;
97         line2=line1;
98     }
99     x509->loadX509Base64Bin(certbuf.data(),certbuf.length());
100
101     // Load the signature.
102     XSECProvider prov;
103     DSIGSignature* sig=NULL;
104     try
105     {
106         sig=prov.newSignatureFromDOM(doc,sigNode);
107         sig->load();
108
109         bool valid=false;
110
111         // Verify the signature coverage.
112         DSIGReferenceList* refs=sig->getReferenceList();
113         if (sig->getSignatureMethod()==SIGNATURE_RSA && refs && refs->getSize()==1)
114         {
115             DSIGReference* ref=refs->item(0);
116             if (ref)
117             {
118                 const XMLCh* URI=ref->getURI();
119                 if (URI==NULL || *URI==0)
120                 {
121                     DSIGTransformList* tlist=ref->getTransforms();
122                     for (int i=0; tlist && i<tlist->getSize(); i++)
123                     {
124                         if (tlist->item(i)->getTransformType()==TRANSFORM_ENVELOPED_SIGNATURE)
125                             valid=true;
126                         else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N)
127                         {
128                             valid=false;
129                             break;
130                         }
131                     }
132                 }
133             }
134         }
135     
136         if (!valid)
137         {
138             log.error("detected an invalid signature profile");
139             throw InvalidCryptoException("detected an invalid signature profile");
140         }
141
142         sig->setSigningKey(x509->clonePublicKey());
143         if (!sig->verify())
144         {
145             log.error("detected an invalid signature value");
146             throw InvalidCryptoException("detected an invalid signature value");
147         }
148
149         prov.releaseSignature(sig);
150     }
151     catch(...)
152     {
153         if (sig)
154             prov.releaseSignature(sig);
155         throw;
156     }
157 }
158
159 int main(int argc,char* argv[])
160 {
161     int ret=0;
162     SAMLConfig& conf=SAMLConfig::getConfig();
163     char* url_param=NULL;
164     char* cert_param=NULL;
165     char* out_param=NULL;
166     char* path=DEFAULT_SCHEMA_DIR;
167
168     for (int i=1; i<argc; i++)
169     {
170         if (!strcmp(argv[i],"--schema") && i+1<argc)
171             path=argv[++i];
172         else if (!strcmp(argv[i],"--url") && i+1<argc)
173             url_param=argv[++i];
174         else if (!strcmp(argv[i],"--cert") && i+1<argc)
175             cert_param=argv[++i];
176         else if (!strcmp(argv[i],"--out") && i+1<argc)
177             out_param=argv[++i];
178     }
179
180     if (!url_param || !out_param)
181     {
182         cout << "usage: " << argv[0] << " --url <URL of metadata> --out <pathname to copy data into> [--cert <PEM Certificate> --schema <schema path>]" << endl;
183         exit(0);
184     }
185
186     Category::setRootPriority(ERROR);
187     conf.schema_dir=path;
188     if (!conf.init())
189         return -10;
190
191     Category& log=Category::getInstance("siterefresh");
192     saml::XML::registerSchema(shibboleth::XML::SHIB_NS,shibboleth::XML::SHIB_SCHEMA_ID);
193
194     try
195     {
196         // Parse the specified document.
197         saml::XML::Parser p;
198         static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
199         URLInputSource src(base,url_param);
200         Wrapper4InputSource dsrc(&src,false);
201         DOMDocument* doc=p.parse(dsrc);
202
203         // Examine the root element to be sure we know what we have.
204                 DOMElement* e=doc->getDocumentElement();
205         if (XMLString::compareString(shibboleth::XML::SHIB_NS,e->getNamespaceURI()) ||
206             XMLString::compareString(shibboleth::XML::Literals::Sites,e->getLocalName()))
207         {
208             doc->release();
209                         log.error("requires a valid site file: (shib:Sites as root element)");
210                         throw OriginSiteMapperException("Construction requires a valid site file: (shib:Sites as root element)");
211                 }
212
213         // If we're verifying, grab the embedded signature.
214         if (cert_param)
215         {
216             DOMNode* n=e->getLastChild();
217             while (n && n->getNodeType()!=DOMNode::ELEMENT_NODE)
218                 n=n->getPreviousSibling();
219             if (n && !XMLString::compareString(saml::XML::XMLSIG_NS,n->getNamespaceURI()) &&
220                 !XMLString::compareString(L(Signature),n->getLocalName()))
221             {
222                 verifySignature(doc,static_cast<DOMElement*>(n),cert_param);
223             }
224             else
225             {
226                 doc->release();
227                             log.error("unable to locate a signature to verify in document");
228                             throw OriginSiteMapperException("Verification implies that the document must be signed");
229             }
230         }
231
232         // Output the data to the specified file.
233         ofstream outfile(out_param);
234         outfile << *e;
235         
236         doc->release();
237     }
238     catch (OriginSiteMapperException&)
239     {
240         ret=-1;
241     }
242     catch(SAMLException& e)
243     {
244         log.errorStream() << "caught a SAML exception: " << e << CategoryStream::ENDLINE;
245         ret=-2;
246     }
247     catch(XMLException& e)
248     {
249         auto_ptr<char> temp(XMLString::transcode(e.getMessage()));
250         log.errorStream() << "caught an XML exception: " << temp.get() << CategoryStream::ENDLINE;
251         ret=-3;
252     }
253     catch(XSECException& e)
254     {
255         auto_ptr<char> temp(XMLString::transcode(e.getMsg()));
256         log.errorStream() << "caught an XMLSec exception: " << temp.get() << CategoryStream::ENDLINE;
257     }
258     catch(XSECCryptoException& e)
259     {
260         log.errorStream() << "caught an XMLSecCrypto exception: " << e.getMsg() << CategoryStream::ENDLINE;
261     }
262     catch(...)
263     {
264         log.errorStream() << "caught an unknown exception" << CategoryStream::ENDLINE;
265         ret=-4;
266     }
267
268     conf.term();
269     return ret;
270 }