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