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