Add code paths for new xmlsec APIs, and allow for undetermined signature algorithm.
[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 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(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 #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=NULL);
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=NULL;
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(NULL)->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             cachedDOM=static_cast<DOMElement*>(document->importNode(internalDoc->getDocumentElement(), true));
299             internalDoc->release();
300         }
301         else {
302             // We just bind the document we built to the object as the result.
303             cachedDOM=static_cast<DOMElement*>(internalDoc->getDocumentElement());
304             document=internalDoc;
305             bindDocument=true;
306         }
307
308         // Now reload the signature from the DOM.
309         try {
310             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(
311                 document, cachedDOM
312                 );
313             m_signature->load();
314         }
315         catch(XSECException& e) {
316             if (bindDocument)
317                 document->release();
318             auto_ptr_char temp(e.getMsg());
319             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());
320         }
321         catch(XSECCryptoException& e) {
322             if (bindDocument)
323                 document->release();
324             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());
325         }
326     }
327     
328     // Marshall KeyInfo data.
329     if (credential) {
330         delete m_keyInfo;
331         m_keyInfo = NULL;
332         m_keyInfo = credential->getKeyInfo();
333     }
334     if (m_keyInfo && (!m_signature->getKeyInfoList() || m_signature->getKeyInfoList()->isEmpty())) {
335         m_keyInfo->marshall(cachedDOM);
336     }
337
338     // Recache the DOM and clear the serialized copy.
339     setDocumentElement(document, cachedDOM);
340     log.debug("caching DOM for Signature (document is %sbound)", bindDocument ? "" : "not ");
341     setDOM(cachedDOM, bindDocument);
342     releaseParentDOM(true);
343     m_xml.erase();
344     return cachedDOM;
345 }
346
347 DOMElement* XMLSecSignatureImpl::marshall(DOMElement* parentElement, const vector<Signature*>* sigs, const Credential* credential) const
348 {
349 #ifdef _DEBUG
350     xmltooling::NDC ndc("marshall");
351 #endif
352     
353     Category& log=Category::getInstance(XMLTOOLING_LOGCAT".XMLObject.Signature");
354     log.debug("marshalling ds:Signature");
355
356     DOMElement* cachedDOM=getDOM();
357     if (cachedDOM) {
358         if (parentElement->getOwnerDocument()==cachedDOM->getOwnerDocument()) {
359             log.debug("Signature has a usable cached DOM, reusing it");
360             if (parentElement!=cachedDOM->getParentNode()) {
361                 parentElement->appendChild(cachedDOM);
362                 releaseParentDOM(true);
363             }
364             return cachedDOM;
365         }
366         
367         // We have a DOM but it doesn't match the document we were given. This both sucks and blows.
368         // Without an adoptNode option to maintain the child pointers, we have to either import the
369         // DOM while somehow reassigning all the nested references (which amounts to a complete
370         // *unmarshall* operation), or we just release the existing DOM and hope that we can get
371         // it back. This depends on all objects being able to preserve their DOM at all costs.
372         releaseChildrenDOM(true);
373         releaseDOM();
374     }
375     
376     // If we get here, we didn't have a usable DOM.
377     if (m_xml.empty()) {
378         // Fresh signature, so we just create an empty one.
379         log.debug("creating empty Signature element");
380         DSIGSignature* temp=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignature();
381         temp->setDSIGNSPrefix(XMLSIG_PREFIX);
382         const XMLCh* alg = getSignatureAlgorithm();
383         if (!alg)
384             alg = DSIGConstants::s_unicodeStrURIRSA_SHA1;
385         cachedDOM=temp->createBlankSignature(parentElement->getOwnerDocument(), getCanonicalizationMethod(), alg);
386         m_signature = temp;
387     }
388     else {
389         MemBufInputSource src(reinterpret_cast<const XMLByte*>(m_xml.c_str()),m_xml.length(),"XMLSecSignatureImpl");
390         Wrapper4InputSource dsrc(&src,false);
391         log.debug("parsing XML back into DOM tree");
392         DOMDocument* internalDoc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
393         
394         log.debug("reimporting new DOM into caller-supplied document");
395         cachedDOM=static_cast<DOMElement*>(parentElement->getOwnerDocument()->importNode(internalDoc->getDocumentElement(),true));
396         internalDoc->release();
397
398         // Now reload the signature from the DOM.
399         try {
400             m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(
401                 parentElement->getOwnerDocument(), cachedDOM
402                 );
403             m_signature->load();
404         }
405         catch(XSECException& e) {
406             auto_ptr_char temp(e.getMsg());
407             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());
408         }
409         catch(XSECCryptoException& e) {
410             throw MarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());
411         }
412     }
413
414     // Marshall KeyInfo data.
415     if (credential) {
416         delete m_keyInfo;
417         m_keyInfo = NULL;
418         m_keyInfo = credential->getKeyInfo();
419     }
420     if (m_keyInfo && (!m_signature->getKeyInfoList() || m_signature->getKeyInfoList()->isEmpty())) {
421         m_keyInfo->marshall(cachedDOM);
422     }
423
424     // Recache the DOM and clear the serialized copy.
425     parentElement->appendChild(cachedDOM);
426     log.debug("caching DOM for Signature");
427     setDOM(cachedDOM, false);
428     releaseParentDOM(true);
429     m_xml.erase();
430     return cachedDOM;
431 }
432
433 XMLObject* XMLSecSignatureImpl::unmarshall(DOMElement* element, bool bindDocument)
434 {
435     Category::getInstance(XMLTOOLING_LOGCAT".XMLObject.Signature").debug("unmarshalling ds:Signature");
436
437     try {
438         m_signature=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newSignatureFromDOM(
439             element->getOwnerDocument(), element
440             );
441         m_signature->load();
442     }
443     catch(XSECException& e) {
444         auto_ptr_char temp(e.getMsg());
445         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + temp.get());
446     }
447     catch(XSECCryptoException& e) {
448         throw UnmarshallingException(string("Caught an XMLSecurity exception while loading signature: ") + e.getMsg());
449     }
450
451     setDOM(element, bindDocument);
452     return this;
453 }
454
455 #ifdef HAVE_COVARIANT_RETURNS
456 Signature*
457 #else
458 XMLObject*
459 #endif
460 SignatureBuilder::buildObject(
461     const XMLCh* nsURI, const XMLCh* localName, const XMLCh* prefix, const xmltooling::QName* schemaType
462     ) const
463 {
464     if (!XMLString::equals(nsURI,XMLSIG_NS) || !XMLString::equals(localName,Signature::LOCAL_NAME))
465         throw XMLObjectException("XMLSecSignatureBuilder requires standard Signature element name.");
466     return buildObject();
467 }
468
469 #ifdef HAVE_COVARIANT_RETURNS
470 Signature*
471 #else
472 XMLObject*
473 #endif
474 SignatureBuilder::buildObject() const
475 {
476     return new XMLSecSignatureImpl();
477 }
478
479 Signature* SignatureBuilder::buildSignature() {
480     const SignatureBuilder* b = dynamic_cast<const SignatureBuilder*>(
481         XMLObjectBuilder::getBuilder(xmltooling::QName(xmlconstants::XMLSIG_NS,Signature::LOCAL_NAME))
482         );
483     if (b) {
484 #ifdef HAVE_COVARIANT_RETURNS
485         return b->buildObject();
486 #else
487         return dynamic_cast<Signature*>(b->buildObject());
488 #endif
489     }
490     throw XMLObjectException("Unable to obtain typed builder for Signature.");
491 }
492
493 const XMLCh Signature::LOCAL_NAME[] = UNICODE_LITERAL_9(S,i,g,n,a,t,u,r,e);
494
495 // Raw signature methods.
496
497 unsigned int Signature::createRawSignature(
498     XSECCryptoKey* key, const XMLCh* sigAlgorithm, const char* in, unsigned int in_len, char* out, unsigned int out_len
499     )
500 {
501     try {
502         XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(sigAlgorithm);
503         if (!handler) {
504             auto_ptr_char alg(sigAlgorithm);
505             throw SignatureException("Unsupported signature algorithm ($1).", params(1,alg.get()));
506         }
507         
508         // Move input into a safeBuffer to source the transform chain.
509         safeBuffer sb,sbout;
510         sb.sbStrncpyIn(in,in_len);
511         TXFMSB* sbt = new TXFMSB(NULL);
512         sbt->setInput(sb, in_len);
513         TXFMChain tx(sbt);
514         
515         // Sign the chain.
516         unsigned int siglen = handler->signToSafeBuffer(&tx, sigAlgorithm, key, out_len-1, sbout);
517         if (siglen >= out_len)
518             throw SignatureException("Signature size exceeded output buffer size.");
519         
520         // Push all non-whitespace into buffer.
521         unsigned int ret_len = 0;
522         const char* source = sbout.rawCharBuffer();
523         while (siglen--) {
524             if (isspace(*source))
525                 ++source;
526             else {
527                 *out++ = *source++;
528                 ++ret_len;
529             }
530         }
531         *out = 0;
532         return ret_len;
533     }
534     catch(XSECException& e) {
535         auto_ptr_char temp(e.getMsg());
536         throw SignatureException(string("Caught an XMLSecurity exception while creating raw signature: ") + temp.get());
537     }
538     catch(XSECCryptoException& e) {
539         throw SignatureException(string("Caught an XMLSecurity exception while creating raw signature: ") + e.getMsg());
540     }
541 }
542
543 bool Signature::verifyRawSignature(
544     XSECCryptoKey* key, const XMLCh* sigAlgorithm, const char* signature, const char* in, unsigned int in_len
545     )
546 {
547     try {
548         XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(sigAlgorithm);
549         if (!handler) {
550             auto_ptr_char alg(sigAlgorithm);
551             throw SignatureException("Unsupported signature algorithm ($1).", params(1,alg.get()));
552         }
553         
554         // Move input into a safeBuffer to source the transform chain.
555         safeBuffer sb;
556         sb.sbStrncpyIn(in,in_len);
557         TXFMSB* sbt = new TXFMSB(NULL);
558         sbt->setInput(sb, in_len);
559         TXFMChain tx(sbt);
560         
561         // Verify the chain.
562         return handler->verifyBase64Signature(&tx, sigAlgorithm, signature, 0, key);
563     }
564     catch(XSECException& e) {
565         auto_ptr_char temp(e.getMsg());
566         throw SignatureException(string("Caught an XMLSecurity exception while verifying raw signature: ") + temp.get());
567     }
568     catch(XSECCryptoException& e) {
569         throw SignatureException(string("Caught an XMLSecurity exception while verifying raw signature: ") + e.getMsg());
570     }
571 }