2 * Copyright 2001-2005 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 /* siterefresh.cpp - command-line tool to refresh and verify metadata
25 #include <shib-target/shib-target.h>
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
33 #if defined(HAVE_LOG4SHIB)
34 # include <log4shib/Category.hh>
35 # include <log4shib/OstreamAppender.hh>
36 namespace siterefresh {
37 namespace logging = log4shib;
39 #elif defined(HAVE_LOG4CPP)
40 # include <log4cpp/Category.hh>
41 # include <log4cpp/OstreamAppender.hh>
42 namespace siterefresh {
43 namespace logging = log4cpp;
46 # error "Supported logging library not available."
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>
65 using namespace shibboleth;
66 using namespace siterefresh::logging;
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
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
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
85 void verifySignature(DOMDocument* doc, DOMNode* sigNode, const char* cert=NULL)
87 Category& log=Category::getInstance("siterefresh");
88 static const XMLCh ID[]={chLatin_I, chLatin_D, chNull};
90 // Load the signature.
92 DSIGSignature* sig=NULL;
94 sig=prov.newSignatureFromDOM(doc,sigNode);
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);
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)
111 else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N) {
121 log.error("detected an invalid signature profile");
122 throw InvalidCryptoException("detected an invalid signature profile");
126 // Load the certificate, stripping the header and trailer.
128 auto_ptr<OpenSSLCryptoX509> x509(new OpenSSLCryptoX509());
129 bool sawheader=false;
130 ifstream infile(cert);
131 while (!getline(infile,line).fail()) {
132 if (line.find("CERTIFICATE-----")==string::npos) {
134 certbuf+=line + '\n';
139 x509->loadX509Base64Bin(certbuf.data(),certbuf.length());
140 sig->setSigningKey(x509->clonePublicKey());
143 log.warn("verifying with key inside signature, this is a sanity check but provides no security");
144 XSECKeyInfoResolverDefault resolver;
145 sig->setKeyInfoResolver(resolver.clone());
148 if (!sig->verify()) {
149 log.error("detected an invalid signature value");
150 throw InvalidCryptoException("detected an invalid signature value");
153 prov.releaseSignature(sig);
157 prov.releaseSignature(sig);
162 int main(int argc,char* argv[])
165 SAMLConfig& conf=SAMLConfig::getConfig();
166 bool verify=true,quiet=false;
167 char* url_param=NULL;
168 char* cert_param=NULL;
169 char* out_param=NULL;
170 char* path=getenv("SHIBSCHEMAS");
172 char* name_param=NULL;
174 for (int i=1; i<argc; i++) {
175 if (!strcmp(argv[i],"--schema") && i+1<argc)
177 else if (!strcmp(argv[i],"--url") && i+1<argc)
179 else if (!strcmp(argv[i],"--noverify"))
181 else if (!strcmp(argv[i],"--quiet") || !strcmp(argv[i],"-q"))
183 else if (!strcmp(argv[i],"--cert") && i+1<argc)
184 cert_param=argv[++i];
185 else if (!strcmp(argv[i],"--out") && i+1<argc)
187 else if (!strcmp(argv[i],"--rootns") && i+1<argc)
189 else if (!strcmp(argv[i],"--rootname") && i+1<argc)
190 name_param=argv[++i];
193 if (verify && !cert_param) {
194 cout << "usage: " << argv[0] << endl <<
195 "\t--url <URL of metadata>" << endl <<
196 "\t--noverify OR --cert <PEM Certificate>" << endl <<
197 "\t[--out <pathname to copy data to>]" << endl <<
198 "\t[--schema <schema path>]" << endl <<
199 "\t[--rootns <root element XML namespace>]" << endl <<
200 "\t[--rootname <root element name>]" << endl <<
201 "\t[--quiet]" << endl;
205 static const XMLCh Trust[] = { chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull };
206 static const XMLCh SiteGroup[] =
207 { chLatin_S, chLatin_i, chLatin_t, chLatin_e, chLatin_G, chLatin_r, chLatin_o, chLatin_u, chLatin_p, chNull };
208 static const XMLCh EntitiesDescriptor[] =
209 { chLatin_E, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_i, chLatin_e, chLatin_s,
210 chLatin_D, chLatin_e, chLatin_s, chLatin_c, chLatin_r, chLatin_i, chLatin_p, chLatin_t, chLatin_o, chLatin_r, chNull };
211 static const XMLCh EntityDescriptor[] =
212 { chLatin_E, chLatin_n, chLatin_t, chLatin_i, chLatin_t, chLatin_y,
213 chLatin_D, chLatin_e, chLatin_s, chLatin_c, chLatin_r, chLatin_i, chLatin_p, chLatin_t, chLatin_o, chLatin_r, chNull };
215 Category::setRootPriority(Priority::WARN);
216 Category::getRoot().addAppender(new OstreamAppender("default",&cerr));
217 Category& log=Category::getInstance("siterefresh");
218 conf.schema_dir=path ? path : SHIB_SCHEMAS;
222 saml::XML::registerSchema(Constants::SHIB_NS,SHIB_SCHEMA_ID);
223 saml::XML::registerSchema(TRUST_NS,TRUST_SCHEMA_ID);
224 saml::XML::registerSchema(shibtarget::XML::SAML2META_NS,shibtarget::XML::SAML2META_SCHEMA_ID);
225 saml::XML::registerSchema(shibtarget::XML::SAML2ASSERT_NS,shibtarget::XML::SAML2ASSERT_SCHEMA_ID);
226 saml::XML::registerSchema(shibtarget::XML::XMLENC_NS,shibtarget::XML::XMLENC_SCHEMA_ID);
229 // Parse the specified document.
231 static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
232 DOMDocument* doc=NULL;
233 if (url_param && *url_param) {
234 URLInputSource src(base,url_param);
235 Wrapper4InputSource dsrc(&src,false);
239 StdInInputSource src;
240 Wrapper4InputSource dsrc(&src,false);
244 // Check root element.
245 if (ns_param && name_param) {
246 auto_ptr_XMLCh ns(ns_param);
247 auto_ptr_XMLCh name(name_param);
248 if (!saml::XML::isElementNamed(doc->getDocumentElement(),ns.get(),name.get()))
249 throw MalformedException(string("Root element does not match specified QName of {") + ns_param + "}:" + name_param);
251 else if (!saml::XML::isElementNamed(doc->getDocumentElement(),Constants::SHIB_NS,SiteGroup) &&
252 !saml::XML::isElementNamed(doc->getDocumentElement(),shibtarget::XML::SAML2META_NS,EntitiesDescriptor) &&
253 !saml::XML::isElementNamed(doc->getDocumentElement(),shibtarget::XML::SAML2META_NS,EntityDescriptor) &&
254 !saml::XML::isElementNamed(doc->getDocumentElement(),TRUST_NS,Trust))
255 throw MalformedException("Root element does not signify a known metadata or trust format");
257 // Verify the "root" signature.
258 DOMElement* rootSig=saml::XML::getFirstChildElement(doc->getDocumentElement(),saml::XML::XMLSIG_NS,L(Signature));
261 verifySignature(doc,rootSig,cert_param);
265 log.error("unable to locate root signature to verify in document");
266 throw InvalidCryptoException("Verification implies that the document must be signed");
271 log.warn("verification of signer disabled, make sure you trust the source of this file!");
272 verifySignature(doc,rootSig,cert_param);
276 log.warn("verification disabled, and file is unsigned!");
279 // Verify all signatures.
280 DOMNodeList* siglist=doc->getElementsByTagNameNS(saml::XML::XMLSIG_NS,L(Signature));
281 for (XMLSize_t i=0; siglist && i<siglist->getLength(); i++)
282 verifySignature(doc,siglist->item(i),cert_param);
285 // Output the data to the specified file.
286 ofstream outfile(out_param);
287 outfile << *(doc->getDocumentElement());
290 cout << *(doc->getDocumentElement());
293 catch (InvalidCryptoException&) {
296 catch(SAMLException& e) {
297 log.errorStream() << "caught a SAML exception: " << e.what() << siterefresh::logging::eol;
300 catch(XMLException& e) {
301 auto_ptr_char temp(e.getMessage());
302 log.errorStream() << "caught an XML exception: " << temp.get() << siterefresh::logging::eol;
305 catch(XSECException& e) {
306 auto_ptr_char temp(e.getMsg());
307 log.errorStream() << "caught an XMLSec exception: " << temp.get() << siterefresh::logging::eol;
310 catch(XSECCryptoException& e) {
311 log.errorStream() << "caught an XMLSecCrypto exception: " << e.getMsg() << siterefresh::logging::eol;
315 log.errorStream() << "caught an unknown exception" << siterefresh::logging::eol;