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