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