/**
 * 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
 *
 * NSSCryptoKeyDSA := DSA Keys
 *
 * Author(s): Milan Tomic
 *
 */

#include <xsec/enc/NSS/NSSCryptoKeyDSA.hpp>
#include <xsec/enc/NSS/NSSCryptoProvider.hpp>
#include <xsec/enc/XSCrypt/XSCryptCryptoBase64.hpp>
#include <xsec/enc/XSECCryptoException.hpp>
#include <xsec/framework/XSECError.hpp>

#include <xercesc/util/Janitor.hpp>

#if defined (XSEC_HAVE_NSS)

XSEC_USING_XERCES(ArrayJanitor);

// --------------------------------------------------------------------------------
//           Constructor
// --------------------------------------------------------------------------------

NSSCryptoKeyDSA::NSSCryptoKeyDSA(SECKEYPublicKey * pubkey, SECKEYPrivateKey	*	privkey) {

  // NOTE - We OWN those handles
	mp_pubkey  = pubkey;
	mp_privkey = privkey;

	mp_P = NULL;
	mp_Q = NULL;
	mp_G = NULL;
	mp_Y = NULL;

};

// --------------------------------------------------------------------------------
//           Destructor
// --------------------------------------------------------------------------------

NSSCryptoKeyDSA::~NSSCryptoKeyDSA() {

	// Clean up

	if (mp_pubkey != 0)
		SECKEY_DestroyPublicKey(mp_pubkey);

  if (mp_privkey != 0)
		SECKEY_DestroyPrivateKey(mp_privkey);

	if (mp_P != NULL)
		 SECITEM_FreeItem(mp_P, PR_TRUE);

	if (mp_Q != NULL)
		SECITEM_FreeItem(mp_Q, PR_TRUE);

	if (mp_G != NULL)
		SECITEM_FreeItem(mp_G, PR_TRUE);

	if (mp_Y != NULL)
		SECITEM_FreeItem(mp_Y, PR_TRUE);

};

const XMLCh * NSSCryptoKeyDSA::getProviderName() const {
	return DSIGConstants::s_unicodeStrPROVNSS;
}

// --------------------------------------------------------------------------------
//           Get key type
// --------------------------------------------------------------------------------

XSECCryptoKey::KeyType NSSCryptoKeyDSA::getKeyType() const {

  // Find out what we have
	if (mp_pubkey == NULL) {

		if (mp_privkey != 0)
			return KEY_DSA_PRIVATE;
		
		// Check if we have parameters loaded
		if (mp_P == NULL ||
			mp_Q == NULL ||
			mp_G == NULL ||
			mp_Y == NULL)
			return KEY_NONE;
		else
			return KEY_DSA_PUBLIC;
	}

	if (mp_privkey != 0)
		return KEY_DSA_PAIR;

	// If we have m_key - it must be public
	return KEY_DSA_PUBLIC;

}

// --------------------------------------------------------------------------------
//           Load P parameter
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::loadPBase64BigNums(const char * b64, unsigned int len) {

	if (mp_P != NULL) {
		SECITEM_FreeItem(mp_P, PR_TRUE);
		mp_P = NULL;		// In case we get an exception
	}

	mp_P = NSSCryptoProvider::b642SI(b64, len);

}

// --------------------------------------------------------------------------------
//           Load Q parameter
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::loadQBase64BigNums(const char * b64, unsigned int len) {

	if (mp_Q != NULL) {
		SECITEM_FreeItem(mp_Q, PR_TRUE);
		mp_Q = NULL;		// In case we get an exception
	}

	mp_Q = NSSCryptoProvider::b642SI(b64, len);
}

// --------------------------------------------------------------------------------
//           Load G parameter
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::loadGBase64BigNums(const char * b64, unsigned int len) {

	if (mp_G != NULL) {
		SECITEM_FreeItem(mp_G, PR_TRUE);
		mp_G = NULL;		// In case we get an exception
	}

	mp_G = NSSCryptoProvider::b642SI(b64, len);
}

// --------------------------------------------------------------------------------
//           Load Y parameter
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::loadYBase64BigNums(const char * b64, unsigned int len) {

	if (mp_Y != NULL) {
		SECITEM_FreeItem(mp_Y, PR_TRUE);
		mp_Y = NULL;		// In case we get an exception
	}

	mp_Y = NSSCryptoProvider::b642SI(b64, len);
}

// --------------------------------------------------------------------------------
//           Load Y parameter
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::loadJBase64BigNums(const char * b64, unsigned int len) {
	//Do nothing
}

// --------------------------------------------------------------------------------
//           Import key
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::importKey(void) const {
	
	if (mp_pubkey != 0 ||
		mp_P == NULL ||
		mp_Q == NULL ||
		mp_G == NULL || 
		mp_Y == NULL)
		return;

  PRArenaPool * arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

  if(arena == NULL) {

      throw XSECCryptoException(XSECCryptoException::GeneralError,
			  "NSS:DSA Error attempting create new arena");

  }

  mp_pubkey = (SECKEYPublicKey*)PORT_ArenaZAlloc(arena, sizeof(SECKEYPublicKey));

  if(mp_pubkey == NULL ) {

      PORT_FreeArena(arena, PR_FALSE);

      throw XSECCryptoException(XSECCryptoException::MemoryError,
			  "NSS:DSA Error attempting create new arena");

  }

  mp_pubkey->arena = arena;
  mp_pubkey->u.dsa.params.arena = arena;
  mp_pubkey->keyType = dsaKey;

  SECStatus s = SECITEM_CopyItem(arena, &(mp_pubkey->u.dsa.params.prime), mp_P);

  if (s != SECSuccess) {
  
    PORT_FreeArena(arena, PR_FALSE);

    throw XSECCryptoException(XSECCryptoException::MemoryError,
		  "NSS:DSA Error attempting to import P key parameter");
  
  }

  s = SECITEM_CopyItem(arena, &(mp_pubkey->u.dsa.params.subPrime), mp_Q);

  if (s != SECSuccess) {

    PORT_FreeArena(arena, PR_FALSE);

    throw XSECCryptoException(XSECCryptoException::MemoryError,
		  "NSS:DSA Error attempting to import Q key parameter");
  
  }

  s = SECITEM_CopyItem(arena, &(mp_pubkey->u.dsa.params.base), mp_G);

  if (s != SECSuccess) {

    PORT_FreeArena(arena, PR_FALSE);

    throw XSECCryptoException(XSECCryptoException::MemoryError,
		  "NSS:DSA Error attempting to import G key parameter");

  }

  s = SECITEM_CopyItem(arena, &(mp_pubkey->u.dsa.publicValue), mp_Y);
  
  if (s != SECSuccess) {

    PORT_FreeArena(arena, PR_FALSE);

    throw XSECCryptoException(XSECCryptoException::MemoryError,
		  "NSS:DSA Error attempting to import Y key parameter");
  
  }

}

// --------------------------------------------------------------------------------
//           Verify a signature encoded as a Base64 string
// --------------------------------------------------------------------------------

bool NSSCryptoKeyDSA::verifyBase64Signature(unsigned char * hashBuf, 
								 unsigned int hashLen,
								 char * base64Signature,
								 unsigned int sigLen) const {

	// Use the currently loaded key to validate the Base64 encoded signature

	if (mp_pubkey == 0) {

      // Try to import from the parameters
		  importKey();

      if (mp_pubkey == 0) {

        throw XSECCryptoException(XSECCryptoException::DSAError,
				      "NSS:DSA - Attempt to validate signature with empty key");

      }

	}

	// Decode the signature
	unsigned char * rawSig;
	unsigned int rawSigLen;
	XSECnew(rawSig, unsigned char[sigLen]);
	ArrayJanitor<unsigned char> j_rawSig(rawSig);

	// Decode the signature
	XSCryptCryptoBase64 b64;

	b64.decodeInit();
	rawSigLen = b64.decode((unsigned char *) base64Signature, sigLen, rawSig, sigLen);
	rawSigLen += b64.decodeFinish(&rawSig[rawSigLen], sigLen - rawSigLen);
	
  SECItem signature;
  signature.type = siBuffer;
  signature.data = rawSig;
  signature.len = rawSigLen;

  SECItem data;
  data.type = siBuffer;
  data.data = (unsigned char *)hashBuf;
  data.len = hashLen;

	// Verify signature
	SECStatus s = PK11_Verify(mp_pubkey, &signature, &data, NULL);

  return s == SECSuccess;

}

// --------------------------------------------------------------------------------
//           Sign and encode result as a Base64 string
// --------------------------------------------------------------------------------

unsigned int NSSCryptoKeyDSA::signBase64Signature(unsigned char * hashBuf,
		unsigned int hashLen,
		char * base64SignatureBuf,
		unsigned int base64SignatureBufLen) const {

	// Sign a pre-calculated hash using this key

	if (mp_privkey == 0) {

		throw XSECCryptoException(XSECCryptoException::DSAError,
			"NSS:DSA - Attempt to sign data using a public or un-loaded key");

  }

  unsigned int signatureLen = PK11_SignatureLen(mp_privkey);

  unsigned char * rawSig;
	XSECnew(rawSig, unsigned char[signatureLen]);
	ArrayJanitor<unsigned char> j_rawSig(rawSig);

  SECItem signature;
  signature.type = siBuffer;
  signature.data = rawSig;
  signature.len = signatureLen;

  SECItem data;
  data.type = siBuffer;
  data.data = hashBuf;
  data.len = hashLen;

  SECStatus s = PK11_Sign(mp_privkey, &signature, &data);

  if (s != SECSuccess) {

    throw XSECCryptoException(XSECCryptoException::DSAError,
			"NSS:DSA - Error during signing operation");

  }

  // Now encode
	XSCryptCryptoBase64 b64;
	b64.encodeInit();
	unsigned int ret = b64.encode(signature.data, signature.len,
                               (unsigned char *) base64SignatureBuf, base64SignatureBufLen);
	ret += b64.encodeFinish((unsigned char *) &base64SignatureBuf[ret],
                          base64SignatureBufLen - ret);

  return ret;

}

// --------------------------------------------------------------------------------
//           Clone key
// --------------------------------------------------------------------------------

XSECCryptoKey * NSSCryptoKeyDSA::clone() const {

	NSSCryptoKeyDSA * ret = NULL;

	XSECnew(ret, NSSCryptoKeyDSA(mp_pubkey, mp_privkey));
	
  // Clone public key
	if (mp_pubkey != 0) {

		ret->mp_pubkey = SECKEY_CopyPublicKey(mp_pubkey);

    if (ret->mp_pubkey == 0) {

      throw XSECCryptoException(XSECCryptoException::MemoryError,
		    "NSS:DSA Error attempting to clone (copy) public key");
  
    }

  }

  // Clone private key
  if (mp_privkey != 0) {

    ret->mp_privkey = SECKEY_CopyPrivateKey(mp_privkey);

    if (ret->mp_privkey == 0) {

      throw XSECCryptoException(XSECCryptoException::MemoryError,
		    "NSS:DSA Error attempting to clone (copy) private key");
  
    }

  }

  // Clone parameter P
  if (mp_P != 0) {
    ret->mp_P = SECITEM_DupItem(mp_P);

    if (ret->mp_P == 0) {

      throw XSECCryptoException(XSECCryptoException::MemoryError,
	  	  "NSS:DSA Error attempting to clone (copy) P key parameter");
  
    }
  }

  // Clone parameter Q
  if (mp_Q != 0) {
    ret->mp_Q = SECITEM_DupItem(mp_Q);

    if (ret->mp_Q == 0) {

      throw XSECCryptoException(XSECCryptoException::MemoryError,
		    "NSS:DSA Error attempting to clone (copy) Q key parameter");
  
    }
  }

  // Clone parameter G
  if (mp_G != 0) {
    ret->mp_G = SECITEM_DupItem(mp_G);

    if (ret->mp_G == 0) {

      throw XSECCryptoException(XSECCryptoException::MemoryError,
		    "NSS:DSA Error attempting to clone (copy) G key parameter");
  
    }
  }

  // Clone parameter Y
  if (mp_Y != 0) {
    ret->mp_Y = SECITEM_DupItem(mp_Y);

    if (ret->mp_Y == 0) {

      throw XSECCryptoException(XSECCryptoException::MemoryError,
		    "NSS:DSA Error attempting to clone (copy) Y key parameter");
  
    }
  }

	return ret;

}

// --------------------------------------------------------------------------------
//           Some utility functions
// --------------------------------------------------------------------------------

void NSSCryptoKeyDSA::loadParamsFromKey(void) {

	if (mp_pubkey == 0)
			return;

  mp_P = SECITEM_DupItem(&(mp_pubkey->u.dsa.params.prime));

  if (mp_P == 0) {

    throw XSECCryptoException(XSECCryptoException::DSAError,
			"NSS:DSA - Error during extracting P from public key");

  }

  mp_Q = SECITEM_DupItem(&(mp_pubkey->u.dsa.params.subPrime));

  if (mp_Q == 0) {

    throw XSECCryptoException(XSECCryptoException::DSAError,
			"NSS:DSA - Error during extracting Q from public key");

  }

  mp_G = SECITEM_DupItem(&(mp_pubkey->u.dsa.params.base));

  if (mp_G == 0) {

    throw XSECCryptoException(XSECCryptoException::DSAError,
			"NSS:DSA - Error during extracting G from public key");

  }

  mp_Y = SECITEM_DupItem(&(mp_pubkey->u.dsa.publicValue));

  if (mp_Y == 0) {

    throw XSECCryptoException(XSECCryptoException::DSAError,
			"NSS:DSA - Error during extracting Y from public key");
  
  }

}

// --------------------------------------------------------------------------------
//           Get P parameter
// --------------------------------------------------------------------------------

unsigned int NSSCryptoKeyDSA::getPBase64BigNums(char * b64, unsigned int len) {

	if (mp_pubkey == 0 && mp_P == NULL) {

		return 0;	// Nothing we can do

	}

	if (mp_P == NULL) {

		loadParamsFromKey();

	}

	unsigned int bLen = 0;
	unsigned char * b = NSSCryptoProvider::SI2b64(mp_P, bLen);
	if (bLen > len)
		bLen = len;
	memcpy(b64, b, bLen);
	delete[] b;

	return bLen;

}

// --------------------------------------------------------------------------------
//           Get Q parameter
// --------------------------------------------------------------------------------

unsigned int NSSCryptoKeyDSA::getQBase64BigNums(char * b64, unsigned int len) {

	if (mp_pubkey == 0 && mp_Q == NULL) {

		return 0;	// Nothing we can do

	}

	if (mp_Q == NULL) {

		loadParamsFromKey();

	}

	unsigned int bLen = 0;
	unsigned char * b = NSSCryptoProvider::SI2b64(mp_Q, bLen);
	if (bLen > len)
		bLen = len;
	memcpy(b64, b, bLen);
	delete[] b;

	return bLen;

}

// --------------------------------------------------------------------------------
//           Get G parameter
// --------------------------------------------------------------------------------

unsigned int NSSCryptoKeyDSA::getGBase64BigNums(char * b64, unsigned int len) {

	if (mp_pubkey == 0 && mp_G == NULL) {

		return 0;	// Nothing we can do

	}

	if (mp_G == NULL) {

		loadParamsFromKey();

	}

	unsigned int bLen = 0;
	unsigned char * b = NSSCryptoProvider::SI2b64(mp_G, bLen);
	if (bLen > len)
		bLen = len;
	memcpy(b64, b, bLen);
	delete[] b;

	return bLen;

}

// --------------------------------------------------------------------------------
//           Get Y parameter
// --------------------------------------------------------------------------------

unsigned int NSSCryptoKeyDSA::getYBase64BigNums(char * b64, unsigned int len) {

	if (mp_pubkey == 0 && mp_Y == NULL) {

		return 0;	// Nothing we can do

	}

	if (mp_Y == NULL) {

		loadParamsFromKey();

	}

	unsigned int bLen = 0;
	unsigned char * b = NSSCryptoProvider::SI2b64(mp_Y, bLen);
	if (bLen > len)
		bLen = len;
	memcpy(b64, b, bLen);
	delete[] b;

	return bLen;

}

#endif /* XSEC_HAVE_NSS */
