388ceb067be56a152eeb4346051a3b2847cf19aa
[shibboleth/cpp-xmltooling.git] / xmltooling / signature / impl / XMLSecSignatureImpl.cpp
1 /*
2 *  Copyright 2001-2007 Internet2
3  * 
4 * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * XMLSecSignatureImpl.cpp
19  * 
20  * Signature class for XMLSec-based signature-handling
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "impl/UnknownElement.h"
26 #include "security/Credential.h"
27 #include "signature/KeyInfo.h"
28 #include "signature/Signature.h"
29 #include "util/NDC.h"
30 #include "util/XMLConstants.h"
31 #include "util/XMLHelper.h"
32
33 #include <log4cpp/Category.hh>
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;
49 using namespace log4cpp;
50 using namespace std;
51 using xmlconstants::XMLSIG_NS;
52 using xmlconstants::XMLSIG_PREFIX;
53
54 #if defined (_MSC_VER)
55     #pragma warning( push )
56     #pragma warning( disable : 4250 4251 )
57 #endif
58
59 namespace xmlsignature {
60     
61     class XMLTOOL_DLLLOCAL XMLSecSignatureImpl : public UnknownElementImpl, public virtual Signature
62     {
63     public:
64         XMLSecSignatureImpl() : AbstractXMLObject(XMLSIG_NS, Signature::LOCAL_NAME, XMLSIG_PREFIX),
65             UnknownElementImpl(XMLSIG_NS, Signature::LOCAL_NAME, XMLSIG_PREFIX),
66             m_signature(NULL), m_c14n(NULL), m_sm(NULL), m_key(NULL), m_keyInfo(NULL), m_reference(NULL) {}
67         virtual ~XMLSecSignatureImpl();
68         
69         void releaseDOM() const;
70         void releaseChildrenDOM(bool propagateRelease=true) const {
71             if (m_keyInfo) {
72                 m_keyInfo->releaseDOM();
73                 if (propagateRelease)
74                     m_keyInfo->releaseChildrenDOM();
75             }
76         }
77         XMLObject* clone() const;
78         Signature* cloneSignature() const;
79
80         DOMElement* marshall(DOMDocument* document=NULL, const vector<Signature*>* sigs=NULL, const Credential* credential=NULL) const;
81         DOMElement* marshall(DOMElement* parentElement, const vector<Signature*>* sigs=NULL, const Credential* credential=NULL) const;
82         XMLObject* unmarshall(DOMElement* element, bool bindDocument=false);
83         
84         // Getters
85         const XMLCh* getCanonicalizationMethod() const {
86             if (m_signature)
87                 return canonicalizationMethod2UNICODEURI(m_signature->getCanonicalizationMethod());
88             return m_c14n ? m_c14n : DSIGConstants::s_unicodeStrURIEXC_C14N_NOC;
89         }
90         const XMLCh* getSignatureAlgorithm() const {
91             if (!m_sm && m_signature) {
92                 safeBuffer sURI;
93                 if (signatureHashMethod2URI(sURI, m_signature->getSignatureMethod(), m_signature->getHashMethod()) == false)
94                     return NULL;
95                 m_sm = XMLString::replicate(sURI.sbStrToXMLCh());
96             }
97             return m_sm ? m_sm : DSIGConstants::s_unicodeStrURIRSA_SHA1;
98         }
99
100         KeyInfo* getKeyInfo() const { return m_keyInfo; }
101         ContentReference* getContentReference() const { return m_reference; }
102         DSIGSignature* getXMLSignature() const { return m_signature; }
103         
104         // Setters
105         void setCanonicalizationMethod(const XMLCh* c14n) { m_c14n = prepareForAssignment(m_c14n,c14n); }
106         void setSignatureAlgorithm(const XMLCh* sm) { m_sm = prepareForAssignment(m_sm,sm); }
107         void setSigningKey(XSECCryptoKey* signingKey) {
108             delete m_key;
109             m_key=signingKey;
110         }
111         void setKeyInfo(KeyInfo* keyInfo) {
112             prepareForAssignment(m_keyInfo, keyInfo);
113             m_keyInfo=keyInfo;
114         }
115         void setContentReference(ContentReference* reference) {
116             delete m_reference;
117             m_reference=reference;
118         }
119         
120         void sign(const Credential* credential=NULL);
121
122     private:
123         mutable DSIGSignature* m_signature;
124         XMLCh* m_c14n;
125         mutable XMLCh* m_sm;
126         XSECCryptoKey* m_key;
127         mutable KeyInfo* m_keyInfo;
128         ContentReference* m_reference;
129     };
130     
131 };
132
133 #if defined (_MSC_VER)
134     #pragma warning( pop )
135 #endif
136
137 XMLSecSignatureImpl::~XMLSecSignatureImpl()
138 {
139     // Release the associated signature.
140     if (m_signature)
141         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);
142
143     XMLString::release(&m_c14n);
144     XMLString::release(&m_sm);
145     delete m_key;
146     delete m_keyInfo;
147     delete m_reference;
148 }
149
150 void XMLSecSignatureImpl::releaseDOM() const
151 {
152     if (getDOM()) {
153         // This should save off the DOM
154         UnknownElementImpl::releaseDOM();
155         
156         // Release the associated signature.
157         if (m_signature) {
158             XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseSignature(m_signature);
159             m_signature=NULL;
160         }
161     }
162 }
163
164 XMLObject* XMLSecSignatureImpl::clone() const
165 {
166     return cloneSignature();
167 }
168
169 Signature* XMLSecSignatureImpl::cloneSignature() const
170 {
171     XMLSecSignatureImpl* ret=new XMLSecSignatureImpl();
172
173     ret->m_c14n=XMLString::replicate(m_c14n);
174     ret->m_sm=XMLString::replicate(m_sm);
175     if (m_key)
176         ret->m_key=m_key->clone();
177     if (m_keyInfo)
178         ret->m_keyInfo=m_keyInfo->cloneKeyInfo();
179
180     // If there's no XML locally, serialize this object into the new one, otherwise just copy it over.
181     if (m_xml.empty())
182         serialize(ret->m_xml);
183     else
184         ret->m_xml=m_xml;
185
186     return ret;
187 }
188
189 void XMLSecSignatureImpl::sign(const Credential* credential)
190 {
191     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".Signature");
192     log.debug("applying signature");
193
194     if (!m_signature)
195         throw SignatureException("Only a marshalled Signature object can be signed.");
196     else if (!m_reference)
197         throw SignatureException("No ContentReference object set for signature creation.");
198
199     XSECCryptoKey* key = credential ? credential->getPrivateKey() : m_key;
200     if (!key)
201         throw SignatureException("No signing key available for signature creation.");
202
203     try {
204         log.debug("creating signature reference(s)");
205         DSIGReferenceList* refs = m_signature->getReferenceList();
206         while (refs && refs->getSize())
207             delete refs->removeReference(0);
208         m_reference->createReferences(m_signature);
209         
210         log.debug("computing signature");
211         m_signature->setSigningKey(key->clone());
212         m_signature->sign();
213     }
214     catch(XSECException& e) {
215         auto_ptr_char temp(e.getMsg());
216         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + temp.get());
217     }
218     catch(XSECCryptoException& e) {
219         throw SignatureException(string("Caught an XMLSecurity exception while signing: ") + e.getMsg());
220     }
221 }
222
223 DOMElement* XMLSecSignatureImpl::marshall(DOMDocument* document, const vector<Signature*>* sigs, const Credential* credential) const
224 {
225 #ifdef _DEBUG
226     xmltooling::NDC ndc("marshall");
227 #endif
228     
229     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".XMLObject.Signature");
230     log.debug("marshalling ds:Signature");
231
232     DOMElement* cachedDOM=getDOM();
233     if (cachedDOM) {
234         if (!document || document==cachedDOM->getOwnerDocument()) {
235             log.debug("Signature has a usable cached DOM, reusing it");
236             if (document)
237                 setDocumentElement(cachedDOM->getOwnerDocument(),cachedDOM);
238             releaseParentDOM(true);
239             return cachedDOM;
240         }
241         
242         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
243         // Without an adoptNode option to maintain the child pointers, we have to either import the
244         // DOM while somehow reassigning all the nested references (which amounts to a complete
245         // *unmarshall* operation), or we just release the existing DOM and hope that we can get
246         // it back. This depends on all objects being able to preserve their DOM at all costs.
247         releaseChildrenDOM(true);
248         releaseDOM();
249     }
250     
251     // If we get here, we didn't have a usable DOM.
252     bool bindDocument=false;
253     if (m_xml.empty()) {
254         // Fresh signature, so we just create an empty one.
255         log.debug("creating empty Signature element");
256         if (!document) {
257             document=DOMImplementationRegistry::getDOMImplementation(NULL)->createDocument();
258             bindDocument=true;
259         }
260         DSIGSignature* temp=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();
261         temp->setDSIGNSPrefix(XMLSIG_PREFIX);
262         cachedDOM=temp->createBlankSignature(document, getCanonicalizationMethod(), getSignatureAlgorithm());
263         m_signature = temp;
264     }
265     else {
266         // We need to reparse the XML we saved off into a new DOM.
267         MemBufInputSource src(reinterpret_cast<const XMLByte*>(m_xml.c_str()),m_xml.length(),"XMLSecSignatureImpl");
268         Wrapper4InputSource dsrc(&src,false);
269         log.debug("parsing Signature XML back into DOM tree");
270         DOMDocument* internalDoc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
271         if (document) {
272             // The caller insists on using his own document, so we now have to import the thing
273             // into it. Then we're just dumping the one we built.
274             log.debug("reimporting new DOM into caller-supplied document");
275             cachedDOM=static_cast<DOMElement*>(document->importNode(internalDoc->getDocumentElement(), true));
276             internalDoc->release();
277         }
278         else {
279             // We just bind the document we built to the object as the result.
280             cachedDOM=static_cast<DOMElement*>(internalDoc->getDocumentElement());
281             document=internalDoc;
282             bindDocument=true;
283         }
284
285         // Now reload the signature from the DOM.
286         try {
287             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(
288                 document, cachedDOM
289                 );
290             m_signature->load();
291         }
292         catch(XSECException& e) {
293             if (bindDocument)
294                 document->release();
295             auto_ptr_char temp(e.getMsg());
296             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());
297         }
298         catch(XSECCryptoException& e) {
299             if (bindDocument)
300                 document->release();
301             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());
302         }
303     }
304     
305     // Marshall KeyInfo data.
306     if (credential) {
307         delete m_keyInfo;
308         m_keyInfo = NULL;
309         const KeyInfo* fromcred = credential->getKeyInfo();
310         if (fromcred)
311             m_keyInfo = fromcred->cloneKeyInfo();
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         const KeyInfo* fromcred = credential->getKeyInfo();
395         if (fromcred)
396             m_keyInfo = fromcred->cloneKeyInfo();
397     }
398     if (m_keyInfo && (!m_signature->getKeyInfoList() || m_signature->getKeyInfoList()->isEmpty())) {
399         m_keyInfo->marshall(cachedDOM);
400     }
401
402     // Recache the DOM and clear the serialized copy.
403     parentElement->appendChild(cachedDOM);
404     log.debug("caching DOM for Signature");
405     setDOM(cachedDOM, false);
406     releaseParentDOM(true);
407     m_xml.erase();
408     return cachedDOM;
409 }
410
411 XMLObject* XMLSecSignatureImpl::unmarshall(DOMElement* element, bool bindDocument)
412 {
413     Category::getInstance(XMLTOOLING_LOGCAT".XMLObject.Signature").debug("unmarshalling ds:Signature");
414
415     try {
416         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(
417             element->getOwnerDocument(), element
418             );
419         m_signature->load();
420     }
421     catch(XSECException& e) {
422         auto_ptr_char temp(e.getMsg());
423         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());
424     }
425     catch(XSECCryptoException& e) {
426         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());
427     }
428
429     setDOM(element, bindDocument);
430     return this;
431 }
432
433 #ifdef HAVE_COVARIANT_RETURNS
434 Signature*
435 #else
436 XMLObject*
437 #endif
438 SignatureBuilder::buildObject(
439     const XMLCh* nsURI, const XMLCh* localName, const XMLCh* prefix, const QName* schemaType
440     ) const
441 {
442     if (!XMLString::equals(nsURI,XMLSIG_NS) || !XMLString::equals(localName,Signature::LOCAL_NAME))
443         throw XMLObjectException("XMLSecSignatureBuilder requires standard Signature element name.");
444     return buildObject();
445 }
446
447 #ifdef HAVE_COVARIANT_RETURNS
448 Signature*
449 #else
450 XMLObject*
451 #endif
452 SignatureBuilder::buildObject() const
453 {
454     return new XMLSecSignatureImpl();
455 }
456
457 const XMLCh Signature::LOCAL_NAME[] = UNICODE_LITERAL_9(S,i,g,n,a,t,u,r,e);
458
459 // Raw signature methods.
460
461 unsigned int Signature::createRawSignature(
462     XSECCryptoKey* key, const XMLCh* sigAlgorithm, const char* in, unsigned int in_len, char* out, unsigned int out_len
463     )
464 {
465     try {
466         XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(sigAlgorithm);
467         if (!handler) {
468             auto_ptr_char alg(sigAlgorithm);
469             throw SignatureException("Unsupported signature algorithm ($1).", params(1,alg.get()));
470         }
471         
472         // Move input into a safeBuffer to source the transform chain.
473         safeBuffer sb,sbout;
474         sb.sbStrncpyIn(in,in_len);
475         TXFMSB* sbt = new TXFMSB(NULL);
476         sbt->setInput(sb, in_len);
477         TXFMChain tx(sbt);
478         
479         // Sign the chain.
480         unsigned int siglen = handler->signToSafeBuffer(&tx, sigAlgorithm, key, out_len-1, sbout);
481         if (siglen >= out_len)
482             throw SignatureException("Signature size exceeded output buffer size.");
483         
484         // Push all non-whitespace into buffer.
485         unsigned int ret_len = 0;
486         const char* source = sbout.rawCharBuffer();
487         while (siglen--) {
488             if (isspace(*source))
489                 ++source;
490             else {
491                 *out++ = *source++;
492                 ++ret_len;
493             }
494         }
495         *out = 0;
496         return ret_len;
497     }
498     catch(XSECException& e) {
499         auto_ptr_char temp(e.getMsg());
500         throw SignatureException(string("Caught an XMLSecurity exception while creating raw signature: ") + temp.get());
501     }
502     catch(XSECCryptoException& e) {
503         throw SignatureException(string("Caught an XMLSecurity exception while creating raw signature: ") + e.getMsg());
504     }
505 }
506
507 bool Signature::verifyRawSignature(
508     XSECCryptoKey* key, const XMLCh* sigAlgorithm, const char* signature, const char* in, unsigned int in_len
509     )
510 {
511     try {
512         XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(sigAlgorithm);
513         if (!handler) {
514             auto_ptr_char alg(sigAlgorithm);
515             throw SignatureException("Unsupported signature algorithm ($1).", params(1,alg.get()));
516         }
517         
518         // Move input into a safeBuffer to source the transform chain.
519         safeBuffer sb;
520         sb.sbStrncpyIn(in,in_len);
521         TXFMSB* sbt = new TXFMSB(NULL);
522         sbt->setInput(sb, in_len);
523         TXFMChain tx(sbt);
524         
525         // Verify the chain.
526         return handler->verifyBase64Signature(&tx, sigAlgorithm, signature, 0, key);
527     }
528     catch(XSECException& e) {
529         auto_ptr_char temp(e.getMsg());
530         throw SignatureException(string("Caught an XMLSecurity exception while verifying raw signature: ") + temp.get());
531     }
532     catch(XSECCryptoException& e) {
533         throw SignatureException(string("Caught an XMLSecurity exception while verifying raw signature: ") + e.getMsg());
534     }
535 }
536
537 void Signature::extractNames(DSIGKeyInfoList* keyInfo, set<string>& names)
538 {
539     char* kn;
540     const XMLCh* n;
541
542     for (size_t s=0; s<keyInfo->getSize(); s++) {
543         n=keyInfo->item(s)->getKeyName();
544         if (n && *n) {
545             kn=toUTF8(n);
546             names.insert(kn);
547             delete[] kn;
548         }
549     }
550 }