/*
 * The OpenSAML License, Version 1.
 * Copyright (c) 2002
 * University Corporation for Advanced Internet Development, Inc.
 * All rights reserved
 *
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution, if any, must include
 * the following acknowledgment: "This product includes software developed by
 * the University Corporation for Advanced Internet Development
 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
 * may appear in the software itself, if and wherever such third-party
 * acknowledgments normally appear.
 *
 * Neither the name of OpenSAML nor the names of its contributors, nor
 * Internet2, nor the University Corporation for Advanced Internet Development,
 * Inc., nor UCAID may be used to endorse or promote products derived from this
 * software without specific prior written permission. For written permission,
 * please contact opensaml@opensaml.org
 *
 * Products derived from this software may not be called OpenSAML, Internet2,
 * UCAID, or the University Corporation for Advanced Internet Development, nor
 * may OpenSAML appear in their name, without prior written permission of the
 * University Corporation for Advanced Internet Development.
 *
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/* SAMLSignedObject.cpp - base class for all signed SAML constructs

   Scott Cantor
   8/12/02

   $History:$
*/

#include "internal.h"

#include <xsec/enc/XSECCryptoProvider.hpp>
#include <xsec/enc/XSECKeyInfoResolverDefault.hpp>
#include <xsec/enc/XSECCryptoException.hpp>
#include <xsec/framework/XSECException.hpp>
#include <xsec/dsig/DSIGKeyInfo.hpp>
#include <xsec/dsig/DSIGKeyInfoX509.hpp>
#include <xsec/dsig/DSIGTransformC14n.hpp>
#include <xsec/dsig/DSIGReference.hpp>
#include <xsec/dsig/DSIGTransformList.hpp>

using namespace saml;
using namespace std;

SAMLSignedObject::SAMLSignedObject(istream& in) : SAMLObject(in), m_signature(NULL), m_sigElement(NULL)
{
    RTTI(SAMLSignedObject);
}

SAMLSignedObject::~SAMLSignedObject()
{
    if (m_signature)
        dynamic_cast<SAMLInternalConfig&>(SAMLConfig::getConfig()).m_xsec->releaseSignature(m_signature);
}

bool SAMLSignedObject::isSigned() const
{
    return (m_signature) ? true : false;
}

void SAMLSignedObject::sign(signatureMethod alg, XSECCryptoKey* k, const Iterator<XSECCryptoX509*>& certs)
{
    if (isSigned())
        throw InvalidCryptoException("SAMLSignedObject::sign() can't sign object a second time");

    // Generate the DOM if not already built, and anchor the DOM in the document.
    toDOM();
    plantRoot();

    // Build the empty signature.
    static const XMLCh ds[]={chLatin_d, chLatin_s, chNull};
    m_signature=dynamic_cast<SAMLInternalConfig&>(SAMLConfig::getConfig()).m_xsec->newSignature();
    m_signature->setDSIGNSPrefix(ds);
    m_sigElement=m_signature->createBlankSignature(m_root->getOwnerDocument(),CANON_C14NE_NOC,alg);
    
    // Have the object place it in the proper place.
    insertSignature();    
    
    try
    {
        DSIGReference* ref=NULL;
        if (SAMLConfig::getConfig().compatibility_mode)
        {
            static const XMLCh empty[]={chNull};
            ref=m_signature->createReference(empty);    
        }
        else
        {
            xstring buf;
            buf+=chPound;
            buf+=getId();
            ref=m_signature->createReference(buf.c_str());
        }

        ref->appendEnvelopedSignatureTransform();
        DSIGTransformC14n* c14n=ref->appendCanonicalizationTransform(CANON_C14NE_NOC);
        c14n->setInclusiveNamespaces(
            const_cast<XMLCh*>(dynamic_cast<SAMLInternalConfig&>(SAMLConfig::getConfig()).wide_inclusive_namespace_prefixes.c_str())
            );
        
        // Append any certs.
        if (certs.size())
        {
            DSIGKeyInfoX509* x509Data=m_signature->appendX509Data();
            while (certs.hasNext())
            {
                safeBuffer& buf=certs.next()->getDEREncodingSB();
                x509Data->appendX509Certificate(buf.sbStrToXMLCh());
            }
        }
        
        // Finally, sign the thing.
        m_signature->setSigningKey(k);
        m_signature->sign();
    }
    catch(XSECException& e)
    {
        if (m_signature)
        {
            dynamic_cast<SAMLInternalConfig&>(SAMLConfig::getConfig()).m_xsec->releaseSignature(m_signature);
            m_signature=NULL;
        }
        if (m_sigElement)
        {
            m_sigElement->getParentNode()->removeChild(m_sigElement);
            m_sigElement->release();
            m_sigElement=NULL;
        }
        auto_ptr<char> temp(XMLString::transcode(e.getMsg()));
        NDC ndc("sign");
        SAML_log.error("caught an XMLSec exception: %s",temp.get());
        throw InvalidCryptoException("SAMLSignedObject::sign() caught an XMLSec exception");
    }
    catch(XSECCryptoException& e)
    {
        if (m_signature)
        {
            dynamic_cast<SAMLInternalConfig&>(SAMLConfig::getConfig()).m_xsec->releaseSignature(m_signature);
            m_signature=NULL;
        }
        if (m_sigElement)
        {
            m_sigElement->getParentNode()->removeChild(m_sigElement);
            m_sigElement->release();
            m_sigElement=NULL;
        }
        NDC ndc("sign");
        SAML_log.error("caught an XMLSec crypto exception: %s",e.getMsg());
        throw InvalidCryptoException("SAMLSignedObject::sign() caught an XMLSec crypto exception");
    }
}

void SAMLSignedObject::verify(XSECCryptoKey* k) const
{
    if (!isSigned())
        throw InvalidCryptoException("SAMLSignedObject::verify() can't examine unsigned object");
    
    bool valid=false;

    DSIGReferenceList* refs=m_signature->getReferenceList();
    if (refs && refs->getSize()==1)
    {
        DSIGReference* ref=refs->item(0);
        if (ref)
        {
            const XMLCh* URI=ref->getURI();
            if (URI==NULL || *URI==0 || (*URI==chPound && !XMLString::compareString(URI+1,getId())))
            {
                DSIGTransformList* tlist=ref->getTransforms();
                for (int i=0; tlist && i<tlist->getSize(); i++)
                {
                    if (tlist->item(i)->getTransformType()==TRANSFORM_ENVELOPED_SIGNATURE)
                        valid=true;
                    else if (tlist->item(i)->getTransformType()!=TRANSFORM_EXC_C14N)
                    {
                        valid=false;
                        break;
                    }
                }
            }
        }
    }
    
    if (!valid)
        throw InvalidCryptoException("SAMLSignedObject::verify() detected an invalid signature profile");

    try
    {
        if (k)
            m_signature->setSigningKey(k);
        else
        {
            XSECKeyInfoResolverDefault resolver;
            m_signature->setKeyInfoResolver(resolver.clone());
        }
            
        if (!m_signature->verify())
        {
            auto_ptr<char> temp(XMLString::transcode(m_signature->getErrMsgs()));
            NDC ndc("verify");
            SAML_log.error("signature failed to verify, error messages follow:\n%s",temp.get());
            throw InvalidCryptoException("SAMLSignedObject::verify() failed to validate signature value");
        }
    }
    catch(XSECException& e)
    {
        auto_ptr<char> temp(XMLString::transcode(e.getMsg()));
        NDC ndc("verify");
        SAML_log.error("caught an XMLSec exception: %s",temp.get());
        throw InvalidCryptoException("SAMLSignedObject::verify() caught an XMLSec exception");
    }
    catch(XSECCryptoException& e)
    {
        NDC ndc("verify");
        SAML_log.error("caught an XMLSec crypto exception: %s",e.getMsg());
        throw InvalidCryptoException("SAMLSignedObject::verify() caught an XMLSec crypto exception");
    }
}

void SAMLSignedObject::verify(XSECCryptoX509& cert) const
{
    verify(cert.clonePublicKey());
}

signatureMethod SAMLSignedObject::getSignatureAlgorithm() const
{
    if (isSigned())
        return m_signature->getSignatureMethod();
    throw InvalidCryptoException("SAMLSignedObject::getSignatureAlgorithm() can't examine unsigned object");
}

unsigned int SAMLSignedObject::getX509CertificateCount() const
{
    if (isSigned())
    {
        DSIGKeyInfoList* klist=m_signature->getKeyInfoList();
        if (klist && klist->getSize()>0)
        {
            DSIGKeyInfo* ki=klist->item(0);
            if (ki && ki->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509)
                return static_cast<DSIGKeyInfoX509*>(ki)->getCertificateListSize();
            return 0;
        }
        return 0;
    }
    throw InvalidCryptoException("SAMLSignedObject::getX509CertificateCount() can't examine unsigned object");
}

const XMLCh* SAMLSignedObject::getX509Certificate(unsigned int index) const
{
    if (isSigned())
    {
        DSIGKeyInfoList* klist=m_signature->getKeyInfoList();
        if (klist && klist->getSize()>0)
        {
            DSIGKeyInfo* ki=klist->item(0);
            if (ki && ki->getKeyInfoType()==DSIGKeyInfo::KEYINFO_X509)
            {
                DSIGKeyInfoX509* kix509=static_cast<DSIGKeyInfoX509*>(ki);
                if (index<kix509->getCertificateListSize())
                    return kix509->getCertificateItem(index);
                throw InvalidCryptoException("SAMLSignedObject::getX509Certificate() detected invalid index");
            }
            throw InvalidCryptoException("SAMLSignedObject::getX509Certificate() can't locate ds:X509Data");
        }
        throw InvalidCryptoException("SAMLSignedObject::getX509Certificate() can't locate ds:KeyInfo");
    }
    throw InvalidCryptoException("SAMLSignedObject::getX509Certificate() can't examine unsigned object");
}
