cf3afd29b83855fc4caea9914e4f296e4e19b3ec
[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 \r
31 #include <log4cpp/Category.hh>\r
32 #include <xercesc/framework/MemBufInputSource.hpp>\r
33 #include <xercesc/framework/Wrapper4InputSource.hpp>\r
34 #include <xercesc/util/XMLUniDefs.hpp>\r
35 #include <xsec/dsig/DSIGKeyInfoX509.hpp>\r
36 #include <xsec/enc/XSECCryptoException.hpp>\r
37 #include <xsec/framework/XSECException.hpp>\r
38 \r
39 using namespace xmlsignature;\r
40 using namespace xmltooling;\r
41 using namespace log4cpp;\r
42 using namespace std;\r
43 \r
44 #if defined (_MSC_VER)\r
45     #pragma warning( push )\r
46     #pragma warning( disable : 4250 4251 )\r
47 #endif\r
48 \r
49 namespace xmlsignature {\r
50     \r
51     class XMLTOOL_DLLLOCAL XMLSecSignatureImpl : public UnknownElementImpl, public virtual Signature\r
52     {\r
53     public:\r
54         XMLSecSignatureImpl() : UnknownElementImpl(XMLConstants::XMLSIG_NS, Signature::LOCAL_NAME, XMLConstants::XMLSIG_PREFIX),\r
55             m_signature(NULL), m_c14n(NULL), m_sm(NULL) {}\r
56         virtual ~XMLSecSignatureImpl();\r
57         \r
58         void releaseDOM();\r
59         XMLObject* clone() const;\r
60         Signature* cloneSignature() const;\r
61 \r
62         DOMElement* marshall(DOMDocument* document=NULL, MarshallingContext* ctx=NULL) const;\r
63         DOMElement* marshall(DOMElement* parentElement, MarshallingContext* ctx=NULL) const;\r
64         XMLObject* unmarshall(DOMElement* element, bool bindDocument=false);\r
65         \r
66         // Getters\r
67         const XMLCh* getCanonicalizationMethod() const { return m_c14n ? m_c14n : DSIGConstants::s_unicodeStrURIEXC_C14N_NOC; }\r
68         const XMLCh* getSignatureAlgorithm() const { return m_sm ? m_sm : DSIGConstants::s_unicodeStrURIRSA_SHA1; }\r
69 \r
70         // Setters\r
71         void setCanonicalizationMethod(const XMLCh* c14n) { m_c14n = prepareForAssignment(m_c14n,c14n); }\r
72         void setSignatureAlgorithm(const XMLCh* sm) { m_sm = prepareForAssignment(m_sm,sm); }\r
73 \r
74         void sign(SigningContext& ctx);\r
75         void verify(const VerifyingContext& ctx) const;\r
76 \r
77     private:\r
78         mutable DSIGSignature* m_signature;\r
79         XMLCh* m_c14n;\r
80         XMLCh* m_sm;\r
81     };\r
82     \r
83 };\r
84 \r
85 #if defined (_MSC_VER)\r
86     #pragma warning( pop )\r
87 #endif\r
88 \r
89 XMLSecSignatureImpl::~XMLSecSignatureImpl()\r
90 {\r
91     // Release the associated signature.\r
92     if (m_signature)\r
93         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
94 \r
95     XMLString::release(&m_c14n);\r
96     XMLString::release(&m_sm);\r
97 }\r
98 \r
99 void XMLSecSignatureImpl::releaseDOM()\r
100 {\r
101     // This should save off the DOM\r
102     UnknownElementImpl::releaseDOM();\r
103     \r
104     // Release the associated signature.\r
105     if (m_signature) {\r
106         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
107         m_signature=NULL;\r
108     }\r
109 }\r
110 \r
111 XMLObject* XMLSecSignatureImpl::clone() const\r
112 {\r
113     return cloneSignature();\r
114 }\r
115 \r
116 Signature* XMLSecSignatureImpl::cloneSignature() const\r
117 {\r
118     XMLSecSignatureImpl* ret=new XMLSecSignatureImpl();\r
119 \r
120     ret->m_c14n=XMLString::replicate(m_c14n);\r
121     ret->m_sm=XMLString::replicate(m_sm);\r
122 \r
123     // If there's no XML locally, serialize this object into the new one, otherwise just copy it over.\r
124     if (m_xml.empty())\r
125         serialize(ret->m_xml);\r
126     else\r
127         ret->m_xml=m_xml;\r
128 \r
129     return ret;\r
130 }\r
131 \r
132 class _addcert : public std::binary_function<DSIGKeyInfoX509*,XSECCryptoX509*,void> {\r
133 public:\r
134     void operator()(DSIGKeyInfoX509* bag, XSECCryptoX509* cert) const {\r
135         safeBuffer& buf=cert->getDEREncodingSB();\r
136         bag->appendX509Certificate(buf.sbStrToXMLCh());\r
137     }\r
138 };\r
139 \r
140 void XMLSecSignatureImpl::sign(SigningContext& ctx)\r
141 {\r
142     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
143     log.debug("applying signature");\r
144 \r
145     if (!m_signature)\r
146         throw SignatureException("Only a marshalled Signature object can be signed.");\r
147 \r
148     try {\r
149         log.debug("creating signature content");\r
150         CredentialResolver& cr=ctx.getCredentialResolver();\r
151         if (!ctx.createSignature(m_signature)) {\r
152             auto_ptr<KeyInfo> keyInfo(ctx.getKeyInfo());\r
153             if (keyInfo.get()) {\r
154                 DOMElement* domElement=keyInfo->marshall(m_signature->getParentDocument());\r
155                 getDOM()->appendChild(domElement);\r
156             }\r
157             else {\r
158                 Locker locker1(cr);\r
159                 const std::vector<XSECCryptoX509*>* certs=cr.getX509Certificates();\r
160                 if (certs && !certs->empty()) {\r
161                     DSIGKeyInfoX509* x509Data=m_signature->appendX509Data();\r
162                     for_each(certs->begin(),certs->end(),bind1st(_addcert(),x509Data));\r
163                 }\r
164             }\r
165         }\r
166         \r
167         log.debug("computing signature");\r
168         Locker locker2(cr);\r
169         XSECCryptoKey* key=cr.getPrivateKey();\r
170         if (!key)\r
171             throw SignatureException(string("Unable to obtain signing key from CredentialResolver (") + cr.getId() + ")");\r
172         m_signature->setSigningKey(key->clone());\r
173         m_signature->sign();\r
174     }\r
175     catch(XSECException& e) {\r
176         auto_ptr_char temp(e.getMsg());\r
177         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + temp.get());\r
178     }\r
179     catch(XSECCryptoException& e) {\r
180         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + e.getMsg());\r
181     }\r
182 }\r
183 \r
184 void XMLSecSignatureImpl::verify(const VerifyingContext& ctx) const\r
185 {\r
186     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
187     log.debug("verifying signature");\r
188 \r
189     if (!m_signature)\r
190         throw SignatureException("Only a marshalled Signature object can be verified.");\r
191 \r
192     try {\r
193         ctx.verifySignature(m_signature);\r
194     }\r
195     catch(XSECException& e) {\r
196         auto_ptr_char temp(e.getMsg());\r
197         throw SignatureException(string("Caught an XMLSecurity exception verifying signature: ") + temp.get());\r
198     }\r
199     catch(XSECCryptoException& e) {\r
200         throw SignatureException(string("Caught an XMLSecurity exception verifying signature: ") + e.getMsg());\r
201     }\r
202 }\r
203 \r
204 DOMElement* XMLSecSignatureImpl::marshall(DOMDocument* document, MarshallingContext* ctx) const\r
205 {\r
206 #ifdef _DEBUG\r
207     xmltooling::NDC ndc("marshall");\r
208 #endif\r
209     \r
210     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
211     log.debug("marshalling ds:Signature");\r
212 \r
213     DOMElement* cachedDOM=getDOM();\r
214     if (cachedDOM) {\r
215         if (!document || document==cachedDOM->getOwnerDocument()) {\r
216             log.debug("Signature has a usable cached DOM, reusing it");\r
217             if (document)\r
218                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
219             releaseParentDOM(true);\r
220             return cachedDOM;\r
221         }\r
222         \r
223         // We have a DOM but it doesn't match the document we were given, so we import\r
224         // it into the new document.\r
225         cachedDOM=static_cast<DOMElement*>(document->importNode(cachedDOM, true));\r
226 \r
227         try {\r
228             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
229             m_signature=NULL;\r
230             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
231                 document, cachedDOM\r
232                 );\r
233             m_signature->load();\r
234         }\r
235         catch(XSECException& e) {\r
236             auto_ptr_char temp(e.getMsg());\r
237             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
238         }\r
239         catch(XSECCryptoException& e) {\r
240             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
241         }\r
242 \r
243         // Recache the DOM.\r
244         setDocumentElement(document, cachedDOM);\r
245         log.debug("caching imported DOM for Signature");\r
246         setDOM(cachedDOM, false);\r
247         releaseParentDOM(true);\r
248         return cachedDOM;\r
249     }\r
250     \r
251     // If we get here, we didn't have a usable DOM.\r
252     bool bindDocument=false;\r
253     if (m_xml.empty()) {\r
254         // Fresh signature, so we just create an empty one.\r
255         log.debug("creating empty Signature element");\r
256         if (!document) {\r
257             document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
258             bindDocument=true;\r
259         }\r
260         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();\r
261         m_signature->setDSIGNSPrefix(XMLConstants::XMLSIG_PREFIX);\r
262         cachedDOM=m_signature->createBlankSignature(document, getCanonicalizationMethod(), getSignatureAlgorithm());\r
263     }\r
264     else {\r
265         // We need to reparse the XML we saved off into a new DOM.\r
266         MemBufInputSource src(reinterpret_cast<const XMLByte*>(m_xml.c_str()),m_xml.length(),"XMLSecSignatureImpl");\r
267         Wrapper4InputSource dsrc(&src,false);\r
268         log.debug("parsing Signature XML back into DOM tree");\r
269         DOMDocument* internalDoc=XMLToolingConfig::getConfig().getParser().parse(dsrc);\r
270         if (document) {\r
271             // The caller insists on using his own document, so we now have to import the thing\r
272             // into it. Then we're just dumping the one we built.\r
273             log.debug("reimporting new DOM into caller-supplied document");\r
274             cachedDOM=static_cast<DOMElement*>(document->importNode(internalDoc->getDocumentElement(), true));\r
275             internalDoc->release();\r
276         }\r
277         else {\r
278             // We just bind the document we built to the object as the result.\r
279             cachedDOM=static_cast<DOMElement*>(internalDoc->getDocumentElement());\r
280             document=internalDoc;\r
281             bindDocument=true;\r
282         }\r
283 \r
284         // Now reload the signature from the DOM.\r
285         try {\r
286             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
287                 document, cachedDOM\r
288                 );\r
289             m_signature->load();\r
290         }\r
291         catch(XSECException& e) {\r
292             if (bindDocument)\r
293                 document->release();\r
294             auto_ptr_char temp(e.getMsg());\r
295             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
296         }\r
297         catch(XSECCryptoException& e) {\r
298             if (bindDocument)\r
299                 document->release();\r
300             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
301         }\r
302     }\r
303     \r
304     // Recache the DOM and clear the serialized copy.\r
305     setDocumentElement(document, cachedDOM);\r
306     log.debug("caching DOM for Signature (document is %sbound)", bindDocument ? "" : "not ");\r
307     setDOM(cachedDOM, bindDocument);\r
308     releaseParentDOM(true);\r
309     m_xml.erase();\r
310     return cachedDOM;\r
311 }\r
312 \r
313 DOMElement* XMLSecSignatureImpl::marshall(DOMElement* parentElement, MarshallingContext* ctx) const\r
314 {\r
315 #ifdef _DEBUG\r
316     xmltooling::NDC ndc("marshall");\r
317 #endif\r
318     \r
319     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
320     log.debug("marshalling ds:Signature");\r
321 \r
322     DOMElement* cachedDOM=getDOM();\r
323     if (cachedDOM) {\r
324         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {\r
325             log.debug("Signature has a usable cached DOM, reusing it");\r
326             parentElement->appendChild(cachedDOM);\r
327             releaseParentDOM(true);\r
328             return cachedDOM;\r
329         }\r
330         \r
331         // We have a DOM but it doesn't match the document we were given, so we import\r
332         // it into the new document.\r
333         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(cachedDOM, true));\r
334 \r
335         try {\r
336             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
337             m_signature=NULL;\r
338             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
339                 parentElement->getOwnerDocument(), cachedDOM\r
340                 );\r
341             m_signature->load();\r
342         }\r
343         catch(XSECException& e) {\r
344             auto_ptr_char temp(e.getMsg());\r
345             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
346         }\r
347         catch(XSECCryptoException& e) {\r
348             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
349         }\r
350 \r
351         // Recache the DOM.\r
352         parentElement->appendChild(cachedDOM);\r
353         log.debug("caching imported DOM for Signature");\r
354         setDOM(cachedDOM, false);\r
355         releaseParentDOM(true);\r
356         return cachedDOM;\r
357     }\r
358     \r
359     // If we get here, we didn't have a usable DOM.\r
360     if (m_xml.empty()) {\r
361         // Fresh signature, so we just create an empty one.\r
362         log.debug("creating empty Signature element");\r
363         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();\r
364         m_signature->setDSIGNSPrefix(XMLConstants::XMLSIG_PREFIX);\r
365         cachedDOM=m_signature->createBlankSignature(\r
366             parentElement->getOwnerDocument(), getCanonicalizationMethod(), getSignatureAlgorithm()\r
367             );\r
368     }\r
369     else {\r
370         MemBufInputSource src(reinterpret_cast<const XMLByte*>(m_xml.c_str()),m_xml.length(),"XMLSecSignatureImpl");\r
371         Wrapper4InputSource dsrc(&src,false);\r
372         log.debug("parsing XML back into DOM tree");\r
373         DOMDocument* internalDoc=XMLToolingConfig::getConfig().getParser().parse(dsrc);\r
374         \r
375         log.debug("reimporting new DOM into caller-supplied document");\r
376         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(internalDoc->getDocumentElement(),true));\r
377         internalDoc->release();\r
378 \r
379         // Now reload the signature from the DOM.\r
380         try {\r
381             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
382                 parentElement->getOwnerDocument(), cachedDOM\r
383                 );\r
384             m_signature->load();\r
385         }\r
386         catch(XSECException& e) {\r
387             auto_ptr_char temp(e.getMsg());\r
388             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
389         }\r
390         catch(XSECCryptoException& e) {\r
391             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
392         }\r
393     }\r
394 \r
395     // Recache the DOM and clear the serialized copy.\r
396     parentElement->appendChild(cachedDOM);\r
397     log.debug("caching DOM for Signature");\r
398     setDOM(cachedDOM, false);\r
399     releaseParentDOM(true);\r
400     m_xml.erase();\r
401     return cachedDOM;\r
402 }\r
403 \r
404 XMLObject* XMLSecSignatureImpl::unmarshall(DOMElement* element, bool bindDocument)\r
405 {\r
406     Category::getInstance(XMLTOOLING_LOGCAT".Signature").debug("unmarshalling ds:Signature");\r
407 \r
408     try {\r
409         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
410             element->getOwnerDocument(), element\r
411             );\r
412         m_signature->load();\r
413     }\r
414     catch(XSECException& e) {\r
415         auto_ptr_char temp(e.getMsg());\r
416         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
417     }\r
418     catch(XSECCryptoException& e) {\r
419         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
420     }\r
421 \r
422     setDOM(element, bindDocument);\r
423     return this;\r
424 }\r
425 \r
426 Signature* SignatureBuilder::buildObject(\r
427     const XMLCh* nsURI, const XMLCh* localName, const XMLCh* prefix, const QName* schemaType\r
428     ) const\r
429 {\r
430     if (!XMLString::equals(nsURI,XMLConstants::XMLSIG_NS) || !XMLString::equals(localName,Signature::LOCAL_NAME))\r
431         throw XMLObjectException("XMLSecSignatureBuilder requires standard Signature element name.");\r
432     return buildObject();\r
433 }\r
434 \r
435 Signature* SignatureBuilder::buildObject() const\r
436 {\r
437     return new XMLSecSignatureImpl();\r
438 }\r
439 \r
440 const XMLCh Signature::LOCAL_NAME[] = UNICODE_LITERAL_9(S,i,g,n,a,t,u,r,e);\r