5218d57c0ae964ac98272903e33ff155d7bcd612
[shibboleth/cpp-xmltooling.git] / xmltooling / signature / impl / XMLSecSignatureImpl.cpp
1 /*\r
2 *  Copyright 2001-2006 Internet2\r
3  * \r
4 * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * XMLSecSignatureImpl.cpp\r
19  * \r
20  * Signature class for XMLSec-based signature-handling\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "exceptions.h"\r
25 #include "impl/UnknownElement.h"\r
26 #include "signature/Signature.h"\r
27 #include "util/NDC.h"\r
28 #include "util/XMLConstants.h"\r
29 #include "util/XMLHelper.h"\r
30 #include "validation/ValidatingXMLObject.h"\r
31 \r
32 #include <log4cpp/Category.hh>\r
33 #include <xercesc/framework/MemBufInputSource.hpp>\r
34 #include <xercesc/framework/Wrapper4InputSource.hpp>\r
35 #include <xercesc/util/XMLUniDefs.hpp>\r
36 #include <xsec/dsig/DSIGKeyInfoX509.hpp>\r
37 #include <xsec/enc/XSECCryptoException.hpp>\r
38 #include <xsec/framework/XSECException.hpp>\r
39 \r
40 using namespace xmlsignature;\r
41 using namespace xmltooling;\r
42 using namespace log4cpp;\r
43 using namespace std;\r
44 \r
45 #if defined (_MSC_VER)\r
46     #pragma warning( push )\r
47     #pragma warning( disable : 4250 4251 )\r
48 #endif\r
49 \r
50 namespace xmlsignature {\r
51     \r
52     class XMLTOOL_DLLLOCAL XMLSecSignatureImpl\r
53         : public UnknownElementImpl, public virtual Signature, public virtual ValidatingXMLObject\r
54     {\r
55     public:\r
56         XMLSecSignatureImpl() : UnknownElementImpl(XMLConstants::XMLSIG_NS, Signature::LOCAL_NAME, XMLConstants::XMLSIG_PREFIX),\r
57             m_signature(NULL), m_c14n(NULL), m_sm(NULL), m_key(NULL), m_keyInfo(NULL), m_reference(NULL) {}\r
58         virtual ~XMLSecSignatureImpl();\r
59         \r
60         void releaseDOM();\r
61         void releaseChildrenDOM(bool propagateRelease=true) {\r
62             if (m_keyInfo) {\r
63                 m_keyInfo->releaseDOM();\r
64                 if (propagateRelease)\r
65                     m_keyInfo->releaseChildrenDOM();\r
66             }\r
67         }\r
68         XMLObject* clone() const;\r
69         Signature* cloneSignature() const;\r
70 \r
71         DOMElement* marshall(DOMDocument* document=NULL, const vector<Signature*>* sigs=NULL) const;\r
72         DOMElement* marshall(DOMElement* parentElement, const vector<Signature*>* sigs=NULL) const;\r
73         XMLObject* unmarshall(DOMElement* element, bool bindDocument=false);\r
74         \r
75         // Getters\r
76         const XMLCh* getCanonicalizationMethod() const { return m_c14n ? m_c14n : DSIGConstants::s_unicodeStrURIEXC_C14N_NOC; }\r
77         const XMLCh* getSignatureAlgorithm() const { return m_sm ? m_sm : DSIGConstants::s_unicodeStrURIRSA_SHA1; }\r
78         KeyInfo* getKeyInfo() const { return m_keyInfo; }\r
79         ContentReference* getContentReference() const { return m_reference; }\r
80         DSIGSignature* getXMLSignature() const { return m_signature; }\r
81         \r
82         // Setters\r
83         void setCanonicalizationMethod(const XMLCh* c14n) { m_c14n = prepareForAssignment(m_c14n,c14n); }\r
84         void setSignatureAlgorithm(const XMLCh* sm) { m_sm = prepareForAssignment(m_sm,sm); }\r
85         void setSigningKey(XSECCryptoKey* signingKey) {\r
86             delete m_key;\r
87             m_key=signingKey;\r
88             if (m_key)\r
89                 releaseThisandParentDOM();\r
90         }\r
91         void setKeyInfo(KeyInfo* keyInfo) {\r
92             prepareForAssignment(m_keyInfo, keyInfo);\r
93             m_keyInfo=keyInfo;\r
94         }\r
95         void setContentReference(ContentReference* reference) {\r
96             delete m_reference;\r
97             m_reference=reference;\r
98             releaseThisandParentDOM();\r
99         }\r
100         \r
101         void sign();\r
102 \r
103         void registerValidator(Validator* validator);\r
104         void deregisterValidator(Validator* validator);\r
105         void deregisterAll();\r
106         void validate(bool validateDescendants) const;\r
107 \r
108     private:\r
109         mutable DSIGSignature* m_signature;\r
110         XMLCh* m_c14n;\r
111         XMLCh* m_sm;\r
112         XSECCryptoKey* m_key;\r
113         KeyInfo* m_keyInfo;\r
114         ContentReference* m_reference;\r
115         vector<Validator*> m_validators;\r
116     };\r
117     \r
118 };\r
119 \r
120 #if defined (_MSC_VER)\r
121     #pragma warning( pop )\r
122 #endif\r
123 \r
124 XMLSecSignatureImpl::~XMLSecSignatureImpl()\r
125 {\r
126     // Release the associated signature.\r
127     if (m_signature)\r
128         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
129 \r
130     XMLString::release(&m_c14n);\r
131     XMLString::release(&m_sm);\r
132     delete m_key;\r
133     delete m_keyInfo;\r
134     delete m_reference;\r
135     for_each(m_validators.begin(),m_validators.end(),cleanup<Validator>());\r
136 }\r
137 \r
138 void XMLSecSignatureImpl::releaseDOM()\r
139 {\r
140     // This should save off the DOM\r
141     UnknownElementImpl::releaseDOM();\r
142     \r
143     // Release the associated signature.\r
144     if (m_signature) {\r
145         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
146         m_signature=NULL;\r
147     }\r
148 }\r
149 \r
150 XMLObject* XMLSecSignatureImpl::clone() const\r
151 {\r
152     return cloneSignature();\r
153 }\r
154 \r
155 Signature* XMLSecSignatureImpl::cloneSignature() const\r
156 {\r
157     XMLSecSignatureImpl* ret=new XMLSecSignatureImpl();\r
158 \r
159     ret->m_c14n=XMLString::replicate(m_c14n);\r
160     ret->m_sm=XMLString::replicate(m_sm);\r
161     if (m_key)\r
162         ret->m_key=m_key->clone();\r
163     if (m_keyInfo)\r
164         ret->m_keyInfo=m_keyInfo->cloneKeyInfo();\r
165     if (m_reference)\r
166         ret->m_reference=m_reference->clone();\r
167 \r
168     xmltooling::clone(m_validators,ret->m_validators);\r
169 \r
170     // If there's no XML locally, serialize this object into the new one, otherwise just copy it over.\r
171     if (m_xml.empty())\r
172         serialize(ret->m_xml);\r
173     else\r
174         ret->m_xml=m_xml;\r
175 \r
176     return ret;\r
177 }\r
178 \r
179 void XMLSecSignatureImpl::sign()\r
180 {\r
181     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
182     log.debug("applying signature");\r
183 \r
184     if (!m_signature)\r
185         throw SignatureException("Only a marshalled Signature object can be signed.");\r
186     else if (!m_key)\r
187         throw SignatureException("No signing key available for signature creation.");\r
188     else if (!m_reference)\r
189         throw SignatureException("No ContentReference object set for signature creation.");\r
190 \r
191     try {\r
192         log.debug("creating signature reference(s)");\r
193         m_reference->createReferences(m_signature);\r
194         if (m_keyInfo) {\r
195             m_keyInfo->marshall(getDOM());\r
196         }\r
197         \r
198         log.debug("computing signature");\r
199         m_signature->setSigningKey(m_key->clone());\r
200         m_signature->sign();\r
201     }\r
202     catch(XSECException& e) {\r
203         auto_ptr_char temp(e.getMsg());\r
204         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + temp.get());\r
205     }\r
206     catch(XSECCryptoException& e) {\r
207         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + e.getMsg());\r
208     }\r
209 }\r
210 \r
211 DOMElement* XMLSecSignatureImpl::marshall(DOMDocument* document, const vector<Signature*>* sigs) const\r
212 {\r
213 #ifdef _DEBUG\r
214     xmltooling::NDC ndc("marshall");\r
215 #endif\r
216     \r
217     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
218     log.debug("marshalling ds:Signature");\r
219 \r
220     DOMElement* cachedDOM=getDOM();\r
221     if (cachedDOM) {\r
222         if (!document || document==cachedDOM->getOwnerDocument()) {\r
223             log.debug("Signature has a usable cached DOM, reusing it");\r
224             if (document)\r
225                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
226             releaseParentDOM(true);\r
227             return cachedDOM;\r
228         }\r
229         \r
230         // We have a DOM but it doesn't match the document we were given, so we import\r
231         // it into the new document.\r
232         cachedDOM=static_cast<DOMElement*>(document->importNode(cachedDOM, true));\r
233 \r
234         try {\r
235             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
236             m_signature=NULL;\r
237             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
238                 document, cachedDOM\r
239                 );\r
240             m_signature->load();\r
241         }\r
242         catch(XSECException& e) {\r
243             auto_ptr_char temp(e.getMsg());\r
244             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
245         }\r
246         catch(XSECCryptoException& e) {\r
247             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
248         }\r
249 \r
250         // Recache the DOM.\r
251         setDocumentElement(document, cachedDOM);\r
252         log.debug("caching imported DOM for Signature");\r
253         setDOM(cachedDOM, false);\r
254         releaseParentDOM(true);\r
255         return cachedDOM;\r
256     }\r
257     \r
258     // If we get here, we didn't have a usable DOM.\r
259     bool bindDocument=false;\r
260     if (m_xml.empty()) {\r
261         // Fresh signature, so we just create an empty one.\r
262         log.debug("creating empty Signature element");\r
263         if (!document) {\r
264             document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
265             bindDocument=true;\r
266         }\r
267         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();\r
268         m_signature->setDSIGNSPrefix(XMLConstants::XMLSIG_PREFIX);\r
269         cachedDOM=m_signature->createBlankSignature(document, getCanonicalizationMethod(), getSignatureAlgorithm());\r
270     }\r
271     else {\r
272         // We need to reparse the XML we saved off into a new DOM.\r
273         MemBufInputSource src(reinterpret_cast<const XMLByte*>(m_xml.c_str()),m_xml.length(),"XMLSecSignatureImpl");\r
274         Wrapper4InputSource dsrc(&src,false);\r
275         log.debug("parsing Signature XML back into DOM tree");\r
276         DOMDocument* internalDoc=XMLToolingConfig::getConfig().getParser().parse(dsrc);\r
277         if (document) {\r
278             // The caller insists on using his own document, so we now have to import the thing\r
279             // into it. Then we're just dumping the one we built.\r
280             log.debug("reimporting new DOM into caller-supplied document");\r
281             cachedDOM=static_cast<DOMElement*>(document->importNode(internalDoc->getDocumentElement(), true));\r
282             internalDoc->release();\r
283         }\r
284         else {\r
285             // We just bind the document we built to the object as the result.\r
286             cachedDOM=static_cast<DOMElement*>(internalDoc->getDocumentElement());\r
287             document=internalDoc;\r
288             bindDocument=true;\r
289         }\r
290 \r
291         // Now reload the signature from the DOM.\r
292         try {\r
293             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
294                 document, cachedDOM\r
295                 );\r
296             m_signature->load();\r
297         }\r
298         catch(XSECException& e) {\r
299             if (bindDocument)\r
300                 document->release();\r
301             auto_ptr_char temp(e.getMsg());\r
302             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
303         }\r
304         catch(XSECCryptoException& e) {\r
305             if (bindDocument)\r
306                 document->release();\r
307             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
308         }\r
309     }\r
310     \r
311     // Recache the DOM and clear the serialized copy.\r
312     setDocumentElement(document, cachedDOM);\r
313     log.debug("caching DOM for Signature (document is %sbound)", bindDocument ? "" : "not ");\r
314     setDOM(cachedDOM, bindDocument);\r
315     releaseParentDOM(true);\r
316     m_xml.erase();\r
317     return cachedDOM;\r
318 }\r
319 \r
320 DOMElement* XMLSecSignatureImpl::marshall(DOMElement* parentElement, const vector<Signature*>* sigs) const\r
321 {\r
322 #ifdef _DEBUG\r
323     xmltooling::NDC ndc("marshall");\r
324 #endif\r
325     \r
326     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
327     log.debug("marshalling ds:Signature");\r
328 \r
329     DOMElement* cachedDOM=getDOM();\r
330     if (cachedDOM) {\r
331         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {\r
332             log.debug("Signature has a usable cached DOM, reusing it");\r
333             parentElement->appendChild(cachedDOM);\r
334             releaseParentDOM(true);\r
335             return cachedDOM;\r
336         }\r
337         \r
338         // We have a DOM but it doesn't match the document we were given, so we import\r
339         // it into the new document.\r
340         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(cachedDOM, true));\r
341 \r
342         try {\r
343             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
344             m_signature=NULL;\r
345             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
346                 parentElement->getOwnerDocument(), cachedDOM\r
347                 );\r
348             m_signature->load();\r
349         }\r
350         catch(XSECException& e) {\r
351             auto_ptr_char temp(e.getMsg());\r
352             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
353         }\r
354         catch(XSECCryptoException& e) {\r
355             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
356         }\r
357 \r
358         // Recache the DOM.\r
359         parentElement->appendChild(cachedDOM);\r
360         log.debug("caching imported DOM for Signature");\r
361         setDOM(cachedDOM, false);\r
362         releaseParentDOM(true);\r
363         return cachedDOM;\r
364     }\r
365     \r
366     // If we get here, we didn't have a usable DOM.\r
367     if (m_xml.empty()) {\r
368         // Fresh signature, so we just create an empty one.\r
369         log.debug("creating empty Signature element");\r
370         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();\r
371         m_signature->setDSIGNSPrefix(XMLConstants::XMLSIG_PREFIX);\r
372         cachedDOM=m_signature->createBlankSignature(\r
373             parentElement->getOwnerDocument(), getCanonicalizationMethod(), getSignatureAlgorithm()\r
374             );\r
375     }\r
376     else {\r
377         MemBufInputSource src(reinterpret_cast<const XMLByte*>(m_xml.c_str()),m_xml.length(),"XMLSecSignatureImpl");\r
378         Wrapper4InputSource dsrc(&src,false);\r
379         log.debug("parsing XML back into DOM tree");\r
380         DOMDocument* internalDoc=XMLToolingConfig::getConfig().getParser().parse(dsrc);\r
381         \r
382         log.debug("reimporting new DOM into caller-supplied document");\r
383         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(internalDoc->getDocumentElement(),true));\r
384         internalDoc->release();\r
385 \r
386         // Now reload the signature from the DOM.\r
387         try {\r
388             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
389                 parentElement->getOwnerDocument(), cachedDOM\r
390                 );\r
391             m_signature->load();\r
392         }\r
393         catch(XSECException& e) {\r
394             auto_ptr_char temp(e.getMsg());\r
395             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
396         }\r
397         catch(XSECCryptoException& e) {\r
398             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
399         }\r
400     }\r
401 \r
402     // Recache the DOM and clear the serialized copy.\r
403     parentElement->appendChild(cachedDOM);\r
404     log.debug("caching DOM for Signature");\r
405     setDOM(cachedDOM, false);\r
406     releaseParentDOM(true);\r
407     m_xml.erase();\r
408     return cachedDOM;\r
409 }\r
410 \r
411 XMLObject* XMLSecSignatureImpl::unmarshall(DOMElement* element, bool bindDocument)\r
412 {\r
413     Category::getInstance(XMLTOOLING_LOGCAT".Signature").debug("unmarshalling ds:Signature");\r
414 \r
415     try {\r
416         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
417             element->getOwnerDocument(), element\r
418             );\r
419         m_signature->load();\r
420     }\r
421     catch(XSECException& e) {\r
422         auto_ptr_char temp(e.getMsg());\r
423         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
424     }\r
425     catch(XSECCryptoException& e) {\r
426         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
427     }\r
428 \r
429     setDOM(element, bindDocument);\r
430     return this;\r
431 }\r
432 \r
433 void XMLSecSignatureImpl::registerValidator(Validator* validator)\r
434 {\r
435     m_validators.push_back(validator);\r
436 }\r
437 \r
438 void XMLSecSignatureImpl::deregisterValidator(Validator* validator)\r
439 {\r
440     for (vector<Validator*>::iterator i=m_validators.begin(); i!=m_validators.end(); i++) {\r
441         if ((*i)==validator) {\r
442             m_validators.erase(i);\r
443             return;\r
444         }\r
445     }\r
446 }\r
447 \r
448 void XMLSecSignatureImpl::deregisterAll()\r
449 {\r
450     for_each(m_validators.begin(),m_validators.end(),cleanup<Validator>());\r
451     m_validators.clear();\r
452 }\r
453 \r
454 class _validate : public binary_function<const XMLObject*,bool,void> {\r
455 public:\r
456     void operator()(const XMLObject* obj, bool propagate) const {\r
457         const ValidatingXMLObject* val = dynamic_cast<const ValidatingXMLObject*>(obj);\r
458         if (val) {\r
459             val->validate(propagate);\r
460         }\r
461     }\r
462 };\r
463 \r
464 void XMLSecSignatureImpl::validate(bool validateDescendants) const\r
465 {\r
466     for_each(\r
467         m_validators.begin(),m_validators.end(),\r
468         bind2nd(mem_fun<void,Validator,const XMLObject*>(&Validator::validate),this)\r
469         );\r
470     \r
471     if (validateDescendants && m_keyInfo) {\r
472         m_keyInfo->validate(validateDescendants);\r
473     }\r
474 }\r
475 \r
476 Signature* SignatureBuilder::buildObject(\r
477     const XMLCh* nsURI, const XMLCh* localName, const XMLCh* prefix, const QName* schemaType\r
478     ) const\r
479 {\r
480     if (!XMLString::equals(nsURI,XMLConstants::XMLSIG_NS) || !XMLString::equals(localName,Signature::LOCAL_NAME))\r
481         throw XMLObjectException("XMLSecSignatureBuilder requires standard Signature element name.");\r
482     return buildObject();\r
483 }\r
484 \r
485 Signature* SignatureBuilder::buildObject() const\r
486 {\r
487     return new XMLSecSignatureImpl();\r
488 }\r
489 \r
490 const XMLCh Signature::LOCAL_NAME[] = UNICODE_LITERAL_9(S,i,g,n,a,t,u,r,e);\r