/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * XSEC
 *
 * DSIGSignedInfo := Class for checking and setting up signed Info nodes in a DSIG signature
 *
 * $Id: DSIGSignedInfo.cpp 1493959 2013-06-17 22:26:41Z scantor $
 *
 */

// XSEC Includes
#include <xsec/dsig/DSIGSignedInfo.hpp>
#include <xsec/dsig/DSIGReference.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/framework/XSECEnv.hpp>
#include <xsec/utils/XSECDOMUtils.hpp>
#include <xsec/dsig/DSIGSignature.hpp>

#include <xercesc/util/Janitor.hpp>

XERCES_CPP_NAMESPACE_USE

// Constructors and Destructors

DSIGSignedInfo::DSIGSignedInfo(DOMDocument *doc, 
		XSECSafeBufferFormatter * pFormatter, 
		DOMNode *signedInfoNode, const XSECEnv * env) {

	mp_doc = doc;
	m_HMACOutputLength = 0;
	mp_formatter = pFormatter;
	mp_signedInfoNode = signedInfoNode;
	m_signatureMethod = SIGNATURE_NONE;
	mp_env = env;
	mp_referenceList = NULL;
	m_loaded = false;

}

DSIGSignedInfo::DSIGSignedInfo(DOMDocument *doc, 
		XSECSafeBufferFormatter * pFormatter, 
		const XSECEnv * env) {

	mp_doc = doc;
	m_HMACOutputLength = 0;
	mp_formatter = pFormatter;
	mp_signedInfoNode = NULL;
	m_signatureMethod = SIGNATURE_NONE;
	mp_env = env;
	mp_referenceList = NULL;
	m_loaded = false;

}

DSIGSignedInfo::~DSIGSignedInfo() {

	mp_formatter = NULL;
	mp_env = NULL;
	if (mp_referenceList != NULL) {

		delete mp_referenceList;
		mp_referenceList = NULL;

	}

}

signatureMethod DSIGSignedInfo::getSignatureMethod(void) {

	return m_signatureMethod;

}

DOMNode * DSIGSignedInfo::getDOMNode() {

	return mp_signedInfoNode;

}

canonicalizationMethod DSIGSignedInfo::getCanonicalizationMethod(void) {

	return m_canonicalizationMethod;

}

hashMethod DSIGSignedInfo::getHashMethod() {

	return m_hashMethod;

}

int DSIGSignedInfo::getHMACOutputLength() {

	return m_HMACOutputLength;

}




// --------------------------------------------------------------------------------
//           Verify each reference element
// --------------------------------------------------------------------------------


bool DSIGSignedInfo::verify(safeBuffer &errStr) {

	return DSIGReference::verifyReferenceList(mp_referenceList, errStr);

}

// --------------------------------------------------------------------------------
//           Calculate and set hash values for each reference element
// --------------------------------------------------------------------------------

void DSIGSignedInfo::hash(bool interlockingReferences) {

	DSIGReference::hashReferenceList(mp_referenceList, interlockingReferences);

}

// --------------------------------------------------------------------------------
//           Create an empty reference in the signed info
// --------------------------------------------------------------------------------


DSIGReference * DSIGSignedInfo::createReference(const XMLCh * URI, 
								hashMethod hm, 
								char * type) {

	safeBuffer hURI;

	if (hashMethod2URI(hURI, hm) == false) {
		throw XSECException(XSECException::UnknownSignatureAlgorithm,
			"DSIGSignedInfo::createReference - Hash method unknown");
	}
	
	return createReference(URI, hURI.sbStrToXMLCh(), MAKE_UNICODE_STRING(type));;
}

DSIGReference * DSIGSignedInfo::createReference(
		const XMLCh * URI,
		const XMLCh * hashAlgorithmURI, 
		const XMLCh * type) {

	DSIGReference * ref;
	XSECnew(ref, DSIGReference(mp_env));
	Janitor<DSIGReference> j_ref(ref);

	DOMNode *refNode = ref->createBlankReference(URI, hashAlgorithmURI, type);

	// Add the node to the end of the childeren
	mp_signedInfoNode->appendChild(refNode);
	mp_env->doPrettyPrint(mp_signedInfoNode);

	// Add to the reference List
	j_ref.release();
	mp_referenceList->addReference(ref);

	return ref;
}

DSIGReference * DSIGSignedInfo::removeReference(DSIGReferenceList::size_type index) {

    DSIGReference* ret = mp_referenceList ? mp_referenceList->removeReference(index): NULL;
    if (ret && mp_signedInfoNode) {
        mp_signedInfoNode->removeChild(ret->mp_referenceNode);
        mp_env->doPrettyPrint(mp_signedInfoNode);
    }

    return ret;

}


// --------------------------------------------------------------------------------
//           Create an empty SignedInfo
// --------------------------------------------------------------------------------

// deprecated

DOMElement *DSIGSignedInfo::createBlankSignedInfo(canonicalizationMethod cm,
			signatureMethod	sm,
			hashMethod hm) {

	// This is now deprecated.  Because this is so, we go the long way - translate
	// to URI and then call the "standard" method, which will translate back to 
	// internal enums if possible

	const XMLCh * cURI;
	safeBuffer sURI;

	if ((cURI = canonicalizationMethod2UNICODEURI(cm)) == NULL) {
		throw XSECException(XSECException::UnknownCanonicalization,
			"DSIGSignature::createBlankSignature - Canonicalisation method unknown");
	}

	if (signatureHashMethod2URI(sURI, sm, hm) == false) {
		throw XSECException(XSECException::UnknownSignatureAlgorithm,
			"DSIGSignature::createBlankSignature - Signature/Hash method unknown");
	}

	return createBlankSignedInfo(cURI, sURI.sbStrToXMLCh());

}

DOMElement * DSIGSignedInfo::createBlankSignedInfo(
			const XMLCh * canonicalizationAlgorithmURI,
			const XMLCh * signatureAlgorithmURI) {

	safeBuffer str;
	const XMLCh * prefixNS = mp_env->getDSIGNSPrefix();

	makeQName(str, prefixNS, "SignedInfo");

	DOMElement *ret = mp_doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG, 
								str.rawXMLChBuffer());
	
	mp_signedInfoNode = ret;

	// Now create the algorithm parts
	XSECmapURIToCanonicalizationMethod(canonicalizationAlgorithmURI, m_canonicalizationMethod);
	XSECmapURIToSignatureMethods(signatureAlgorithmURI, m_signatureMethod, m_hashMethod);

	// Canonicalisation

	DOMElement *canMeth = mp_doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG, 
			makeQName(str, prefixNS, "CanonicalizationMethod").rawXMLChBuffer());

	mp_env->doPrettyPrint(mp_signedInfoNode);
	mp_signedInfoNode->appendChild(canMeth);
	mp_env->doPrettyPrint(mp_signedInfoNode);

	canMeth->setAttributeNS(NULL,DSIGConstants::s_unicodeStrAlgorithm,
		canonicalizationAlgorithmURI);

	// Now the SignatureMethod

	DOMElement *sigMeth = mp_doc->createElementNS(DSIGConstants::s_unicodeStrURIDSIG, 
			makeQName(str, prefixNS, "SignatureMethod").rawXMLChBuffer());

	mp_signedInfoNode->appendChild(sigMeth);
	mp_env->doPrettyPrint(mp_signedInfoNode);

	sigMeth->setAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm, 
		signatureAlgorithmURI);

	// Store the algorithm URI internally
	mp_algorithmURI = sigMeth->getAttributeNS(NULL, DSIGConstants::s_unicodeStrAlgorithm);

	// Create an empty reference list

	XSECnew(mp_referenceList, DSIGReferenceList());

	return ret;

}


// --------------------------------------------------------------------------------
//           Load the SignedInfo
// --------------------------------------------------------------------------------

void DSIGSignedInfo::load(void) {


	if (mp_signedInfoNode == 0) {

		// Attempt to load an empty signature element
		throw XSECException(XSECException::LoadEmptySignedInfo);

	}

	if (!strEquals(getDSIGLocalName(mp_signedInfoNode), "SignedInfo")) {

		throw XSECException(XSECException::LoadNonSignedInfo);

	}

	DOMNode * tmpSI = mp_signedInfoNode->getFirstChild();

	// Check for CanonicalizationMethod

	while (tmpSI != 0 && (tmpSI->getNodeType() != DOMNode::ELEMENT_NODE)) {
		if (tmpSI->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		// Skip text and comments
		tmpSI = tmpSI->getNextSibling();
	}

	if (tmpSI == 0 || !strEquals(getDSIGLocalName(tmpSI), "CanonicalizationMethod")) {

		throw XSECException(XSECException::ExpectedDSIGChildNotFound, 
				"Expected <CanonicalizationMethod> as first child of <SignedInfo>");

	}

	// Determine what the canonicalization method is
	DOMNamedNodeMap *tmpAtts = tmpSI->getAttributes();

	DOMNode *algorithm = tmpAtts->getNamedItem(DSIGConstants::s_unicodeStrAlgorithm);

	if (algorithm == 0) {

		throw XSECException(XSECException::ExpectedDSIGChildNotFound,
					"Expected Algorithm attribute in <CanonicalizationMethod>");

	}

	safeBuffer tmpSB;

	tmpSB << (*mp_formatter << algorithm->getNodeValue());

	if (tmpSB.sbStrcmp(URI_ID_C14N_NOC) == 0) {

		m_canonicalizationMethod = CANON_C14N_NOC;

	}

	else if (tmpSB.sbStrcmp(URI_ID_C14N_COM) == 0) {

		m_canonicalizationMethod = CANON_C14N_COM;

	}

	else if (tmpSB.sbStrcmp(URI_ID_C14N11_NOC) == 0) {

        m_canonicalizationMethod = CANON_C14N11_NOC;

    }

    else if (tmpSB.sbStrcmp(URI_ID_C14N11_COM) == 0) {

        m_canonicalizationMethod = CANON_C14N11_COM;

    }

    else if (tmpSB.sbStrcmp(URI_ID_EXC_C14N_COM) == 0) {

		m_canonicalizationMethod = CANON_C14NE_COM;

	}

	else if (tmpSB.sbStrcmp(URI_ID_EXC_C14N_NOC) == 0) {

		m_canonicalizationMethod = CANON_C14NE_NOC;

	}

	else {

		throw XSECException(XSECException::UnknownCanonicalization);
	}

	// Now load the SignatureMethod

	tmpSI = tmpSI->getNextSibling();

	while (tmpSI != 0 && (tmpSI->getNodeType() != DOMNode::ELEMENT_NODE)) {
		if (tmpSI->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		// Skip text and comments
		tmpSI = tmpSI->getNextSibling();
	}

	if (tmpSI == 0 || !strEquals(getDSIGLocalName(tmpSI), "SignatureMethod")) {

		throw XSECException(XSECException::ExpectedDSIGChildNotFound, 
				"Expected <SignatureMethod> as child of <SignedInfo>");
	}


	// Determine the algorithms used to sign this document

	tmpAtts = tmpSI->getAttributes();

	algorithm = tmpAtts->getNamedItem(DSIGConstants::s_unicodeStrAlgorithm);
	
	if (algorithm == 0) {

		throw XSECException(XSECException::ExpectedDSIGChildNotFound,
					"Expected Algorithm attribute in <SignatureMethod>");

	}
	
	mp_algorithmURI = algorithm->getNodeValue();

	/* NOTE - as of version 1.3.1 all code relating to parsing the algorithm 
	 * has been removed.  This should all be handled inside the algorithm mappers.
	 * Having code here restricts available algorithms, as this code is not extended for
	 * new algorthms.
	 */

	/* Look for maximum output value. Really only applies to HMACs, but as we no
	 * longer know at this point if this is an HMAC, we need to check. */

	DOMNode *tmpSOV = tmpSI->getFirstChild();
	while (tmpSOV != NULL &&
		(tmpSOV->getNodeType() != DOMNode::ELEMENT_NODE || !strEquals(getDSIGLocalName(tmpSOV), "HMACOutputLength"))) {
		if (tmpSOV->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		tmpSOV = tmpSOV->getNextSibling();
	}

	if (tmpSOV != NULL) {

		// Have a max output value!
		tmpSOV = tmpSOV->getFirstChild();
		while (tmpSOV != NULL && tmpSOV->getNodeType() != DOMNode::TEXT_NODE)
			tmpSOV = tmpSOV->getNextSibling();

		if (tmpSOV != NULL) {

			safeBuffer val;
			val << (*mp_formatter << tmpSOV->getNodeValue());
			m_HMACOutputLength = atoi((char *) val.rawBuffer());

		}
	}

	// Now look at references....

	tmpSI = tmpSI->getNextSibling();

	// Run through the rest of the elements until done

	while (tmpSI != 0 && (tmpSI->getNodeType() != DOMNode::ELEMENT_NODE)) {
		if (tmpSI->getNodeType() == DOMNode::ENTITY_REFERENCE_NODE) {
			throw XSECException(XSECException::ExpectedDSIGChildNotFound,
				"EntityReference nodes in <SignedInfo> are unsupported.");
		}
		// Skip text and comments
		tmpSI = tmpSI->getNextSibling();
	}

	if (tmpSI != NULL) {

		// Have an element node - should be a reference, so let's load the list

		mp_referenceList = DSIGReference::loadReferenceListFromXML(mp_env, tmpSI);

	}

}

