/*
 * hash algorithms used in OAuth 
 *
 * Copyright 2007-2010 Robin Gareus <robin@gareus.org>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if USE_BUILTIN_HASH // built-in / AVR -- TODO: check license of sha1.c
#include <stdio.h>
#include "liboauth_oauth.h" // oauth_encode_base64
#include "xmalloc.h"

#include "sha1.c" // TODO: sha1.h ; Makefile.am: add sha1.c

/* API */
char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) {
	sha1nfo s;
	sha1_initHmac(&s, (const uint8_t*) k, kl);
	sha1_write(&s, m, ml);
	unsigned char *digest = sha1_resultHmac(&s);
  return oauth_encode_base64(HASH_LENGTH, digest);
}

char *oauth_sign_hmac_sha1 (const char *m, const char *k) {
  return(oauth_sign_hmac_sha1_raw (m, (int)strlen(m), k, (int)strlen(k)));
}

char *oauth_body_hash_file(char *filename) {
  FILE *F= fopen(filename, "r");
  if (!F) return NULL;

  size_t len=0;
  char fb[BUFSIZ];
	sha1nfo s;
	sha1_init(&s);

  while (!feof(F) && (len=fread(fb,sizeof(char),BUFSIZ, F))>0) {
		sha1_write(&s, fb, len);
	}
  fclose(F);

  unsigned char *dgst = xmalloc(HASH_LENGTH*sizeof(char)); // oauth_body_hash_encode frees the digest..
  memcpy(dgst, sha1_result(&s), HASH_LENGTH);
  return oauth_body_hash_encode(HASH_LENGTH, dgst);
}

char *oauth_body_hash_data(size_t length, const char *data) {
	sha1nfo s;
	sha1_init(&s);
	for (;length--;) sha1_writebyte(&s, *data++);

  unsigned char *dgst = xmalloc(HASH_LENGTH*sizeof(char)); // oauth_body_hash_encode frees the digest..
  memcpy(dgst, sha1_result(&s), HASH_LENGTH);
  return oauth_body_hash_encode(HASH_LENGTH, dgst);
}

char *oauth_sign_rsa_sha1 (const char *m, const char *k) {
	/* NOT RSA/PK11 support */
	return xstrdup("---RSA/PK11-is-not-supported-by-this-version-of-liboauth---");
}

int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *sig) {
	/* NOT RSA/PK11 support */
	return -1; // mismatch , error
}

#elif defined (USE_NSS)
/* use http://www.mozilla.org/projects/security/pki/nss/ for hash/sign */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DMID 9
#include "dmalloc.h"
#include "liboath_oauth.h" // oauth base64 encode fn's.

// NSS includes
#include "pk11pub.h"
#include "nss.h"
#include "base64.h"
#include "keyhi.h"
#include "cryptohi.h"
#include "cert.h"

#if 1 // work-around compiler-warning
      // see http://bugzilla.mozilla.org/show_bug.cgi?id=243245#c3
  extern CERTCertificate *
  __CERT_DecodeDERCertificate (SECItem *derSignedCert, PRBool copyDER, char *nickname);
#endif

static const char NS_CERT_HEADER[]  = "-----BEGIN CERTIFICATE-----";
static const char NS_CERT_TRAILER[] = "-----END CERTIFICATE-----";
static const char NS_PRIV_HEADER[]  = "-----BEGIN PRIVATE KEY-----";
static const char NS_PRIV_TRAILER[] = "-----END PRIVATE KEY-----";

void oauth_init_nss() {
  static short nss_initialized = 0;
  if (!nss_initialized) { NSS_NoDB_Init("."); nss_initialized=1;}
}

/**
 * Removes heading & trailing strings; used only internally.
 * similar to NSS-source/nss/lib/pkcs7/certread.c
 *
 * the returned string (if not NULL) needs to be freed by the caller
 */
char *oauth_strip_pkcs(const char *txt, const char *h, const char *t) {
  char *start, *end, *rv;
  size_t len;
  if ((start=strstr(txt, h))==NULL) return NULL;
  start+=(int)strlen(h);
  while (*start=='\r' || *start=='\n') start++;
  if ((end=strstr(start, t))==NULL) return NULL;
  end--;
  while (*end=='\r' || *end=='\n') end--;
  len = end-start+2;
  rv = xmalloc(len*sizeof(char));
  memcpy(rv,start,len);
  rv[len-1]='\0';
  return rv;
}

char *oauth_sign_hmac_sha1 (const char *m, const char *k) {
  return(oauth_sign_hmac_sha1_raw (m, (int)strlen(m), k, (int)strlen(k)));
}

char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) {
  PK11SlotInfo  *slot = NULL;
  PK11SymKey    *pkey = NULL;
  PK11Context   *context = NULL;
  unsigned char  digest[20]; // Is there a way to tell how large the output is?
  unsigned int   len;
  SECStatus      s;
  SECItem        keyItem, noParams;
  char          *rv=NULL;

  keyItem.type = siBuffer;
  keyItem.data = (unsigned char*) k;
  keyItem.len = kl;

  noParams.type = siBuffer;
  noParams.data = NULL;
  noParams.len = 0;

  oauth_init_nss();

  slot = PK11_GetInternalKeySlot();
  if (!slot) goto looser;
  pkey = PK11_ImportSymKey(slot, CKM_SHA_1_HMAC, PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL);
  if (!pkey)  goto looser;
  context = PK11_CreateContextBySymKey(CKM_SHA_1_HMAC, CKA_SIGN, pkey, &noParams);
  if (!context) goto looser;

  s = PK11_DigestBegin(context);
  if (s != SECSuccess) goto looser;
  s = PK11_DigestOp(context, (unsigned char*) m, ml);
  if (s != SECSuccess) goto looser;
  s = PK11_DigestFinal(context, digest, &len, sizeof digest);
  if (s != SECSuccess) goto looser;

  rv=oauth_encode_base64(len, digest);

looser:
  if (context) PK11_DestroyContext(context, PR_TRUE);
  if (pkey) PK11_FreeSymKey(pkey);
  if (slot) PK11_FreeSlot(slot);
  return rv;
}

char *oauth_sign_rsa_sha1 (const char *m, const char *k) {
  PK11SlotInfo      *slot = NULL;
  SECKEYPrivateKey  *pkey = NULL;
  SECItem            signature;
  SECStatus          s;
  SECItem            der;
  char              *rv=NULL;

  char *key = oauth_strip_pkcs(k, NS_PRIV_HEADER, NS_PRIV_TRAILER); 
  if (!key) return NULL;

  oauth_init_nss();

  slot = PK11_GetInternalKeySlot();
  if (!slot) goto looser;
  s = ATOB_ConvertAsciiToItem(&der, key);
  if (s != SECSuccess) goto looser;
  s = PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, &der, NULL, NULL, PR_FALSE, PR_TRUE, KU_ALL, &pkey, NULL);
  SECITEM_FreeItem(&der, PR_FALSE);
  if (s != SECSuccess) goto looser;
  if (!pkey) goto looser;
  if (pkey->keyType != rsaKey) goto looser;
  s = SEC_SignData(&signature, (unsigned char*) m, (int)strlen(m), pkey, SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE);
  if (s != SECSuccess) goto looser;

  rv=oauth_encode_base64(signature.len, signature.data);
  SECITEM_FreeItem(&signature, PR_FALSE);

looser:
  if (pkey) SECKEY_DestroyPrivateKey(pkey);
  if (slot) PK11_FreeSlot(slot);
  free(key);
  return rv;
}

int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *sig) {
  PK11SlotInfo      *slot = NULL;
  SECKEYPublicKey   *pkey = NULL;
  CERTCertificate   *cert = NULL;
  SECItem            signature;
  SECStatus          s;
  SECItem            der;
  int                rv=0;

  char *key = oauth_strip_pkcs(c, NS_CERT_HEADER, NS_CERT_TRAILER); 
  if (!key) return 0;

  oauth_init_nss();

  s = ATOB_ConvertAsciiToItem(&signature, (char*) sig); // XXX cast (const char*) -> (char*)
  if (s != SECSuccess) goto looser;
  slot = PK11_GetInternalKeySlot();
  if (!slot) goto looser;
  s = ATOB_ConvertAsciiToItem(&der, key);
  if (s != SECSuccess) goto looser;
  cert = __CERT_DecodeDERCertificate(&der, PR_TRUE, NULL);
  SECITEM_FreeItem(&der, PR_FALSE); 
  if (!cert) goto looser;
  pkey = CERT_ExtractPublicKey(cert);
  if (!pkey) goto looser;
  if (pkey->keyType != rsaKey) goto looser;

  s = VFY_VerifyData((unsigned char*) m, (int)strlen(m), pkey, &signature, SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, NULL);
  if (s == SECSuccess) rv=1;
#if 0
  else if (PR_GetError()!= SEC_ERROR_BAD_SIGNATURE) rv=-1;
#endif

looser:
  if (pkey) SECKEY_DestroyPublicKey(pkey);
  if (slot) PK11_FreeSlot(slot);
  free(key);
  return rv;
}

char *oauth_body_hash_file(char *filename) {
  PK11SlotInfo  *slot = NULL;
  PK11Context   *context = NULL;
  unsigned char  digest[20]; // Is there a way to tell how large the output is?
  unsigned int   len;
  SECStatus      s;
  char          *rv=NULL;
  size_t         bl;
  unsigned char  fb[BUFSIZ];

  FILE *F= fopen(filename, "r");
  if (!F) return NULL;

  oauth_init_nss();

  slot = PK11_GetInternalKeySlot();
  if (!slot) goto looser;
  context = PK11_CreateDigestContext(SEC_OID_SHA1);
  if (!context) goto looser;

  s = PK11_DigestBegin(context);
  if (s != SECSuccess) goto looser;
  while (!feof(F) && (bl=fread(fb,sizeof(char),BUFSIZ, F))>0) {
    s = PK11_DigestOp(context, (unsigned char*) fb, bl);
    if (s != SECSuccess) goto looser;
  }
  s = PK11_DigestFinal(context, digest, &len, sizeof digest);
  if (s != SECSuccess) goto looser;

  unsigned char *dgst = xmalloc(len*sizeof(char)); // oauth_body_hash_encode frees the digest..
  memcpy(dgst, digest, len);
  rv=oauth_body_hash_encode(len, dgst);

looser:
  fclose(F);
  if (context) PK11_DestroyContext(context, PR_TRUE);
  if (slot) PK11_FreeSlot(slot);
  return rv;
}

char *oauth_body_hash_data(size_t length, const char *data) {
  PK11SlotInfo  *slot = NULL;
  PK11Context   *context = NULL;
  unsigned char  digest[20]; // Is there a way to tell how large the output is?
  unsigned int   len;
  SECStatus      s;
  char          *rv=NULL;

  oauth_init_nss();

  slot = PK11_GetInternalKeySlot();
  if (!slot) goto looser;
  context = PK11_CreateDigestContext(SEC_OID_SHA1);
  if (!context) goto looser;

  s = PK11_DigestBegin(context);
  if (s != SECSuccess) goto looser;
  s = PK11_DigestOp(context, (unsigned char*) data, length);
  if (s != SECSuccess) goto looser;
  s = PK11_DigestFinal(context, digest, &len, sizeof digest);
  if (s != SECSuccess) goto looser;

  unsigned char *dgst = xmalloc(len*sizeof(char)); // oauth_body_hash_encode frees the digest..
  memcpy(dgst, digest, len);
  rv=oauth_body_hash_encode(len, dgst);

looser:
  if (context) PK11_DestroyContext(context, PR_TRUE);
  if (slot) PK11_FreeSlot(slot);
  return rv;
}

#else 
/* use http://www.openssl.org/ for hash/sign */

#ifdef _GNU_SOURCE
/*
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 */
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DMID 10
#include "dmalloc.h"
#include "liboauth_oauth.h" // base64 encode fn's.
#include <openssl/hmac.h>

char *oauth_sign_hmac_sha1 (const char *m, const char *k) {
  return(oauth_sign_hmac_sha1_raw (m, (int)strlen(m), k, (int)strlen(k)));
}

char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) {
  unsigned char result[EVP_MAX_MD_SIZE];
  unsigned int resultlen = 0;
  
  HMAC(EVP_sha1(), k, (int)kl, 
      (unsigned char*) m, ml,
      result, &resultlen);

  return(oauth_encode_base64(resultlen, result));
}

#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/ssl.h>

#include "ssltrack.h" 
int myssl_cleanup(void);


char *oauth_sign_rsa_sha1 (const char *m, const char *k) {
  unsigned char *sig = NULL;
  unsigned char *passphrase = NULL;
  unsigned int len=0;
  EVP_MD_CTX *md_ctx=NULL;

  EVP_PKEY *pkey;
  BIO *in;
  md_ctx = EVP_MD_CTX_create();
  in = BIO_new_mem_buf((unsigned char*) k, (int)strlen(k));
  pkey = PEM_read_bio_PrivateKey(in, NULL, 0, passphrase); // generate sign
  BIO_free(in);
	myssl_cleanup();

  if (pkey == NULL) {
  //fprintf(stderr, "liboauth/OpenSSL: can not read private key\n");
	EVP_MD_CTX_destroy(md_ctx); md_ctx = NULL;
    return xstrdup("liboauth/OpenSSL: can not read private key");
  }

  md_ctx = EVP_MD_CTX_create();
  len = EVP_PKEY_size(pkey);
  sig = (unsigned char*)xmalloc((len+1)*sizeof(char));

  EVP_SignInit(md_ctx, EVP_sha1());
  EVP_SignUpdate(md_ctx, m, (int)strlen(m));
  if (EVP_SignFinal (md_ctx, sig, &len, pkey)) {
    char *tmp;
    sig[len] = '\0';
    tmp = oauth_encode_base64(len,sig);
    OPENSSL_free(sig);
    EVP_PKEY_free(pkey);
	EVP_MD_CTX_destroy(md_ctx); md_ctx = NULL;
	return tmp;
  }
  EVP_MD_CTX_destroy(md_ctx); md_ctx = NULL;
  return xstrdup("liboauth/OpenSSL: rsa-sha1 signing failed");
}

int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *s) {
  EVP_MD_CTX *md_ctx=NULL;
  EVP_PKEY *pkey;
  BIO *in;
  X509 *cert = NULL;
  unsigned char *b64d;
  int slen, err;

  md_ctx = EVP_MD_CTX_create();
  in = BIO_new_mem_buf((unsigned char*)c, (int)strlen(c));
  cert = PEM_read_bio_X509(in, NULL, 0, NULL);
  if (cert)  {
    pkey = (EVP_PKEY *) X509_get_pubkey(cert); 
    X509_free(cert);
  } else {
    pkey = PEM_read_bio_PUBKEY(in, NULL, 0, NULL);
  }
  BIO_free(in);
	myssl_cleanup();
  if (pkey == NULL) {
  //fprintf(stderr, "could not read cert/pubkey.\n");
    return -2;
  }
  
  b64d= (unsigned char*) xmalloc(sizeof(char)*(int)strlen(s));
  slen = oauth_decode_base64(b64d, s);

  EVP_VerifyInit(md_ctx, EVP_sha1());
  EVP_VerifyUpdate(md_ctx, m, (int)strlen(m));
  err = EVP_VerifyFinal(md_ctx, b64d, slen, pkey);
#ifdef openssl101
  EVP_MD_CTX_cleanup(md_ctx);
#endif
  EVP_MD_CTX_destroy(md_ctx); md_ctx = NULL;
  EVP_PKEY_free(pkey);
  free(b64d);
  return (err);
}


/** 
 * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
 */

// Marijn: surgemail linkage error on EVP_MD_size, lets see if these functions are actually needed!!
/*
char *oauth_body_hash_file(char *filename) {
  unsigned char fb[BUFSIZ];
  EVP_MD_CTX ctx;
  size_t len=0;
  unsigned char *md;
  FILE *F= fopen(filename, "r");
  if (!F) return NULL;

  EVP_MD_CTX_init(&ctx);
  EVP_DigestInit(&ctx,EVP_sha1());
  while (!feof(F) && (len=fread(fb,sizeof(char),BUFSIZ, F))>0) {
    EVP_DigestUpdate(&ctx, fb, len);
  }
  fclose(F);
  len=0;
  md=(unsigned char*) xcalloc(EVP_MD_size(EVP_sha1()),sizeof(unsigned char));
  EVP_DigestFinal(&ctx, md,(unsigned int*) &len);
  EVP_MD_CTX_cleanup(&ctx);
  return oauth_body_hash_encode(len, md);
}

char *oauth_body_hash_data(size_t length, const char *data) {
  EVP_MD_CTX ctx;
  size_t len=0;
  unsigned char *md;
  md=(unsigned char*) xcalloc(EVP_MD_size(EVP_sha1()),sizeof(unsigned char));
  EVP_MD_CTX_init(&ctx);
  EVP_DigestInit(&ctx,EVP_sha1());
  EVP_DigestUpdate(&ctx, data, length);
  EVP_DigestFinal(&ctx, md,(unsigned int*) &len);
  EVP_MD_CTX_cleanup(&ctx);
  return oauth_body_hash_encode(len, md);
}
*/
#endif

// vi: sts=2 sw=2 ts=2
