First cut at signing support.
[shibboleth/cpp-xmltooling.git] / xmltooling / signature / impl / XMLSecSignature.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  * XMLSecSignature.cpp\r
19  * \r
20  * Signature classes for XMLSec-based signature-handling\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "exceptions.h"\r
25 #include "signature/impl/XMLSecSignature.h"\r
26 #include "util/NDC.h"\r
27 #include "util/XMLHelper.h"\r
28 \r
29 #include <log4cpp/Category.hh>\r
30 #include <xercesc/framework/MemBufInputSource.hpp>\r
31 #include <xercesc/framework/Wrapper4InputSource.hpp>\r
32 #include <xercesc/util/XMLUniDefs.hpp>\r
33 #include <xsec/dsig/DSIGKeyInfoX509.hpp>\r
34 #include <xsec/enc/XSECCryptoException.hpp>\r
35 #include <xsec/framework/XSECException.hpp>\r
36 \r
37 using namespace xmltooling;\r
38 using namespace log4cpp;\r
39 using namespace std;\r
40 \r
41 const XMLCh xmltooling::Signature::LOCAL_NAME[] = {\r
42     chLatin_S, chLatin_i, chLatin_g, chLatin_n, chLatin_a, chLatin_t, chLatin_u, chLatin_r, chLatin_e, chNull\r
43 }; \r
44 \r
45 const XMLCh xmltooling::Signature::PREFIX[] = {\r
46     chLatin_d, chLatin_s, chNull\r
47 }; \r
48 \r
49 XMLSecSignatureImpl::~XMLSecSignatureImpl()\r
50 {\r
51     // Release the associated signature.\r
52     if (m_signature)\r
53         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
54 \r
55     XMLString::release(&m_c14n);\r
56     XMLString::release(&m_sm);\r
57 }\r
58 \r
59 void XMLSecSignatureImpl::releaseDOM()\r
60 {\r
61     // This should save off the DOM\r
62     UnknownElementImpl::releaseDOM();\r
63     \r
64     // Release the associated signature.\r
65     if (m_signature) {\r
66         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);\r
67         m_signature=NULL;\r
68     }\r
69 }\r
70 \r
71 XMLObject* XMLSecSignatureImpl::clone() const\r
72 {\r
73     XMLSecSignatureImpl* ret=new XMLSecSignatureImpl();\r
74 \r
75     ret->m_c14n=XMLString::replicate(m_c14n);\r
76     ret->m_sm=XMLString::replicate(m_sm);\r
77 \r
78     // If there's no XML locally, serialize this object into the new one, otherwise just copy it over.\r
79     if (m_xml.empty())\r
80         serialize(ret->m_xml);\r
81     else\r
82         ret->m_xml=m_xml;\r
83 \r
84     return ret;\r
85 }\r
86 \r
87 const DSIGKeyInfoList* XMLSecSignatureImpl::getKeyInfo() const\r
88 {\r
89     return m_signature ? m_signature->getKeyInfoList() : NULL;\r
90 }\r
91 \r
92 class _addcert : public std::binary_function<DSIGKeyInfoX509*,XSECCryptoX509*,void> {\r
93 public:\r
94     void operator()(DSIGKeyInfoX509* bag, XSECCryptoX509* cert) const {\r
95         safeBuffer& buf=cert->getDEREncodingSB();\r
96         bag->appendX509Certificate(buf.sbStrToXMLCh());\r
97     }\r
98 };\r
99 \r
100 void XMLSecSignatureImpl::sign(const SigningContext* ctx)\r
101 {\r
102     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");\r
103     log.debug("applying signature");\r
104 \r
105     if (!m_signature)\r
106         throw SignatureException("Only a marshalled Signature object can be signed.");\r
107 \r
108     try {\r
109         log.debug("creating signature content");\r
110         ctx->createSignature(m_signature);\r
111         const std::vector<XSECCryptoX509*>& certs=ctx->getX509Certificates();\r
112         if (!certs.empty()) {\r
113             DSIGKeyInfoX509* x509Data=m_signature->appendX509Data();\r
114             for_each(certs.begin(),certs.end(),bind1st(_addcert(),x509Data));\r
115         }\r
116         \r
117         log.debug("computing signature");\r
118         m_signature->setSigningKey(ctx->getSigningKey());\r
119         m_signature->sign();\r
120     }\r
121     catch(XSECException& e) {\r
122         auto_ptr_char temp(e.getMsg());\r
123         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + temp.get());\r
124     }\r
125     catch(XSECCryptoException& e) {\r
126         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + e.getMsg());\r
127     }\r
128 }\r
129 \r
130 DOMElement* XMLSecSignatureMarshaller::marshall(XMLObject* xmlObject, DOMDocument* document, MarshallingContext* ctx) const\r
131 {\r
132 #ifdef _DEBUG\r
133     xmltooling::NDC ndc("marshall");\r
134 #endif\r
135     \r
136     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Marshaller");\r
137     log.debug("marshalling ds:Signature");\r
138 \r
139     XMLSecSignatureImpl* sig=dynamic_cast<XMLSecSignatureImpl*>(xmlObject);\r
140     if (!sig)\r
141         throw MarshallingException("Only objects of class XMLSecSignatureImpl can be marshalled.");\r
142     \r
143     DOMElement* cachedDOM=sig->getDOM();\r
144     if (cachedDOM) {\r
145         if (!document || document==cachedDOM->getOwnerDocument()) {\r
146             log.debug("Signature has a usable cached DOM, reusing it");\r
147             if (document)\r
148                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);\r
149             sig->releaseParentDOM(true);\r
150             return cachedDOM;\r
151         }\r
152         \r
153         // We have a DOM but it doesn't match the document we were given, so we import\r
154         // it into the new document.\r
155         cachedDOM=static_cast<DOMElement*>(document->importNode(cachedDOM, true));\r
156 \r
157         try {\r
158             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(sig->m_signature);\r
159             sig->m_signature=NULL;\r
160             sig->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
161                 document, cachedDOM\r
162                 );\r
163         }\r
164         catch(XSECException& e) {\r
165             auto_ptr_char temp(e.getMsg());\r
166             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
167         }\r
168         catch(XSECCryptoException& e) {\r
169             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
170         }\r
171 \r
172         // Recache the DOM.\r
173         setDocumentElement(document, cachedDOM);\r
174         log.debug("caching imported DOM for Signature");\r
175         sig->setDOM(cachedDOM, false);\r
176         sig->releaseParentDOM(true);\r
177         return cachedDOM;\r
178     }\r
179     \r
180     // If we get here, we didn't have a usable DOM.\r
181     bool bindDocument=false;\r
182     if (sig->m_xml.empty()) {\r
183         // Fresh signature, so we just create an empty one.\r
184         log.debug("creating empty Signature element");\r
185         if (!document) {\r
186             document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();\r
187             bindDocument=true;\r
188         }\r
189         sig->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();\r
190         sig->m_signature->setDSIGNSPrefix(Signature::PREFIX);\r
191         cachedDOM=sig->m_signature->createBlankSignature(\r
192             document, sig->getCanonicalizationMethod(), sig->getSignatureAlgorithm()\r
193             );\r
194     }\r
195     else {\r
196         // We need to reparse the XML we saved off into a new DOM.\r
197         MemBufInputSource src(reinterpret_cast<const XMLByte*>(sig->m_xml.c_str()),sig->m_xml.length(),"XMLSecSignatureImpl");\r
198         Wrapper4InputSource dsrc(&src,false);\r
199         log.debug("parsing Signature XML back into DOM tree");\r
200         DOMDocument* internalDoc=XMLToolingInternalConfig::getInternalConfig().m_parserPool->parse(dsrc);\r
201         if (document) {\r
202             // The caller insists on using his own document, so we now have to import the thing\r
203             // into it. Then we're just dumping the one we built.\r
204             log.debug("reimporting new DOM into caller-supplied document");\r
205             cachedDOM=static_cast<DOMElement*>(document->importNode(internalDoc->getDocumentElement(), true));\r
206             internalDoc->release();\r
207         }\r
208         else {\r
209             // We just bind the document we built to the object as the result.\r
210             cachedDOM=static_cast<DOMElement*>(internalDoc->getDocumentElement());\r
211             document=internalDoc;\r
212             bindDocument=true;\r
213         }\r
214 \r
215         // Now reload the signature from the DOM.\r
216         try {\r
217             sig->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
218                 document, cachedDOM\r
219                 );\r
220         }\r
221         catch(XSECException& e) {\r
222             if (bindDocument)\r
223                 document->release();\r
224             auto_ptr_char temp(e.getMsg());\r
225             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
226         }\r
227         catch(XSECCryptoException& e) {\r
228             if (bindDocument)\r
229                 document->release();\r
230             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
231         }\r
232     }\r
233     \r
234     // Recache the DOM and clear the serialized copy.\r
235     setDocumentElement(document, cachedDOM);\r
236     log.debug("caching DOM for Signature (document is %sbound)", bindDocument ? "" : "not ");\r
237     sig->setDOM(cachedDOM, bindDocument);\r
238     sig->releaseParentDOM(true);\r
239     sig->m_xml.erase();\r
240     return cachedDOM;\r
241 }\r
242 \r
243 DOMElement* XMLSecSignatureMarshaller::marshall(XMLObject* xmlObject, DOMElement* parentElement, MarshallingContext* ctx) const\r
244 {\r
245 #ifdef _DEBUG\r
246     xmltooling::NDC ndc("marshall");\r
247 #endif\r
248     \r
249     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Marshaller");\r
250     log.debug("marshalling ds:Signature");\r
251 \r
252     XMLSecSignatureImpl* sig=dynamic_cast<XMLSecSignatureImpl*>(xmlObject);\r
253     if (!sig)\r
254         throw MarshallingException("Only objects of class XMLSecSignatureImpl can be marshalled.");\r
255     \r
256     DOMElement* cachedDOM=sig->getDOM();\r
257     if (cachedDOM) {\r
258         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {\r
259             log.debug("Signature has a usable cached DOM, reusing it");\r
260             parentElement->appendChild(cachedDOM);\r
261             sig->releaseParentDOM(true);\r
262             return cachedDOM;\r
263         }\r
264         \r
265         // We have a DOM but it doesn't match the document we were given, so we import\r
266         // it into the new document.\r
267         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(cachedDOM, true));\r
268 \r
269         try {\r
270             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(sig->m_signature);\r
271             sig->m_signature=NULL;\r
272             sig->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
273                 parentElement->getOwnerDocument(), cachedDOM\r
274                 );\r
275         }\r
276         catch(XSECException& e) {\r
277             auto_ptr_char temp(e.getMsg());\r
278             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
279         }\r
280         catch(XSECCryptoException& e) {\r
281             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
282         }\r
283 \r
284         // Recache the DOM.\r
285         parentElement->appendChild(cachedDOM);\r
286         log.debug("caching imported DOM for Signature");\r
287         sig->setDOM(cachedDOM, false);\r
288         sig->releaseParentDOM(true);\r
289         return cachedDOM;\r
290     }\r
291     \r
292     // If we get here, we didn't have a usable DOM.\r
293     if (sig->m_xml.empty()) {\r
294         // Fresh signature, so we just create an empty one.\r
295         log.debug("creating empty Signature element");\r
296         sig->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();\r
297         sig->m_signature->setDSIGNSPrefix(Signature::PREFIX);\r
298         cachedDOM=sig->m_signature->createBlankSignature(\r
299             parentElement->getOwnerDocument(), sig->getCanonicalizationMethod(), sig->getSignatureAlgorithm()\r
300             );\r
301     }\r
302     else {\r
303         MemBufInputSource src(reinterpret_cast<const XMLByte*>(sig->m_xml.c_str()),sig->m_xml.length(),"XMLSecSignatureImpl");\r
304         Wrapper4InputSource dsrc(&src,false);\r
305         log.debug("parsing XML back into DOM tree");\r
306         DOMDocument* internalDoc=XMLToolingInternalConfig::getInternalConfig().m_parserPool->parse(dsrc);\r
307         \r
308         log.debug("reimporting new DOM into caller-supplied document");\r
309         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(internalDoc->getDocumentElement(), true));\r
310         internalDoc->release();\r
311 \r
312         // Now reload the signature from the DOM.\r
313         try {\r
314             sig->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
315                 parentElement->getOwnerDocument(), cachedDOM\r
316                 );\r
317         }\r
318         catch(XSECException& e) {\r
319             auto_ptr_char temp(e.getMsg());\r
320             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
321         }\r
322         catch(XSECCryptoException& e) {\r
323             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
324         }\r
325     }\r
326 \r
327     // Recache the DOM and clear the serialized copy.\r
328     parentElement->appendChild(cachedDOM);\r
329     log.debug("caching DOM for Signature");\r
330     sig->setDOM(cachedDOM, false);\r
331     sig->releaseParentDOM(true);\r
332     sig->m_xml.erase();\r
333     return cachedDOM;\r
334 }\r
335 \r
336 XMLObject* XMLSecSignatureUnmarshaller::unmarshall(DOMElement* element, bool bindDocument) const\r
337 {\r
338     Category::getInstance(XMLTOOLING_LOGCAT".Unmarshaller").debug("unmarshalling ds:Signature");\r
339 \r
340     auto_ptr<XMLSecSignatureImpl> ret(new XMLSecSignatureImpl());\r
341     try {\r
342         ret->m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(\r
343             element->getOwnerDocument(), element\r
344             );\r
345     }\r
346     catch(XSECException& e) {\r
347         auto_ptr_char temp(e.getMsg());\r
348         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());\r
349     }\r
350     catch(XSECCryptoException& e) {\r
351         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());\r
352     }\r
353 \r
354     ret->setDOM(element, bindDocument);\r
355     return ret.release();\r
356 }\r