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