/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Archive verify code.
 *
 * The Initial Developer of the Original Code is
 * Mozilla Foundation.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Brian R. Bondy <netzen@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifdef XP_WIN
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include "mar_private.h"
#include "mar.h"
#include "cryptox.h"

int mar_verify_signature_fp(FILE *fp, 
                            CryptoX_ProviderHandle provider, 
                            CryptoX_PublicKey key);
int mar_verify_signature_for_fp(FILE *fp, 
                                CryptoX_ProviderHandle provider, 
                                CryptoX_PublicKey key, 
                                PRUint32 signatureCount,
                                char *extractedSignature);

/**
 * Reads the specified number of bytes from the file pointer and
 * stores them in the passed buffer.
 *
 * @param  fp     The file pointer to read from.
 * @param  buffer The buffer to store the read results.
 * @param  size   The number of bytes to read, buffer must be 
 *                at least of this size.
 * @param  ctx    The verify context.
 * @param  err    The name of what is being written to in case of error.
 * @return  0 on success
 *         -1 on read error
 *         -2 on verify update error
*/
int
ReadAndUpdateVerifyContext(FILE *fp, 
                           void *buffer,
                           PRUint32 size, 
                           CryptoX_SignatureHandle *ctx,
                           const char *err) 
{
  if (!fp || !buffer || !ctx || !err) {
    fprintf(stderr, "ERROR: Invalid parameter specified.\n");
    return CryptoX_Error;
  }

  if (!size) { 
    return CryptoX_Success;
  }

  if (fread(buffer, size, 1, fp) != 1) {
    fprintf(stderr, "ERROR: Could not read %s\n", err);
    return CryptoX_Error;
  }

  if (CryptoX_Failed(CryptoX_VerifyUpdate(ctx, buffer, size))) {
    fprintf(stderr, "ERROR: Could not update verify context for %s\n", err);
    return -2;
  }
  return CryptoX_Success;
}

/**
 * Verifies the embedded signature of the specified file path.
 * This is only used by the signmar program when used with arguments to verify 
 * a MAR. This should not be used to verify a MAR that will be extracted in the 
 * same operation by updater code. This function prints the error message if 
 * verification fails.
 * 
 * @param pathToMAR  The path of the MAR file who's signature should be checked
 * @param certData       The certificate file data.
 * @param sizeOfCertData The size of the cert data.
 * @param certName   Used only if compiled as NSS, specifies the certName
 * @return 0 on success
 *         a negative number if there was an error
 *         a positive number if the signature does not verify
 */
int
mar_verify_signature(const char *pathToMARFile, 
                     const char *certData,
                     PRUint32 sizeOfCertData,
                     const char *certName) {
  int rv;
  CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue;
  CryptoX_Certificate cert;
  CryptoX_PublicKey key;
  FILE *fp;
  
  if (!pathToMARFile || (!certData && !certName)) {
    fprintf(stderr, "ERROR: Invalid parameter specified.\n");
    return CryptoX_Error;
  }

  fp = fopen(pathToMARFile, "rb");
  if (!fp) {
    fprintf(stderr, "ERROR: Could not open MAR file.\n");
    return CryptoX_Error;
  }

  if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) {
    fclose(fp);
    fprintf(stderr, "ERROR: Could not init crytpo library.\n");
    return CryptoX_Error;
  }

  if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData, sizeOfCertData,
                                           &key, certName, &cert))) {
    fclose(fp);
    fprintf(stderr, "ERROR: Could not load public key.\n");
    return CryptoX_Error;
  }

  rv = mar_verify_signature_fp(fp, provider, key);
  fclose(fp);
  if (key) {
    CryptoX_FreePublicKey(&key);
  }

  if (cert) {
    CryptoX_FreeCertificate(&cert);
  }
  return rv;
}

#ifdef XP_WIN
/**
 * Verifies a MAR file's signature by making sure at least one 
 * signature verifies.
 * 
 * @param  pathToMARFile The path of the MAR file who's signature 
 *                       should be calculated
 * @param  certData      The certificate data
 * @param sizeOfCertData The size of the data stored in certData
 * @return 0 on success
*/
int
mar_verify_signatureW(MarFile *mar, 
                      const char *certData,
                      PRUint32 sizeOfCertData) {
  int rv;
  CryptoX_ProviderHandle provider = CryptoX_InvalidHandleValue;
  CryptoX_Certificate cert;
  CryptoX_PublicKey key;
  
  if (!mar || !certData) {
    fprintf(stderr, "ERROR: Invalid parameter specified.\n");
    return CryptoX_Error;
  }

  if (!mar->fp) {
    fprintf(stderr, "ERROR: MAR file is not open.\n");
    return CryptoX_Error;
  }

  if (CryptoX_Failed(CryptoX_InitCryptoProvider(&provider))) { 
    fprintf(stderr, "ERROR: Could not init crytpo library.\n");
    return CryptoX_Error;
  }

  if (CryptoX_Failed(CryptoX_LoadPublicKey(provider, certData, sizeOfCertData,
                                           &key, "", &cert))) {
    fprintf(stderr, "ERROR: Could not load public key.\n");
    return CryptoX_Error;
  }

  rv = mar_verify_signature_fp(mar->fp, provider, key);
  if (key) {
    CryptoX_FreePublicKey(&key);
  }

  if (cert) {
    CryptoX_FreeCertificate(&cert);
  }
  return rv;
}
#endif

/**
 * Verifies a MAR file's signature by making sure at least one 
 * signature verifies.
 * 
 * @param  fp       An opened MAR file handle
 * @param  provider A library provider
 * @param  key      The public key to use to verify the MAR
 * @return 0 on success
*/
int
mar_verify_signature_fp(FILE *fp,
                        CryptoX_ProviderHandle provider, 
                        CryptoX_PublicKey key) {
  char buf[5] = {0};
  PRUint32 signatureAlgorithmID, signatureCount, signatureLen, numVerified = 0;
  int rv = -1;
  PRInt64 curPos;
  char *extractedSignature;
  PRUint32 i;

  if (!fp) {
    fprintf(stderr, "ERROR: Invalid file pointer passed.\n");
    return CryptoX_Error;
  }
  
  /* To protect against invalid MAR files, we assumes that the MAR file 
     size is less than or equal to MAX_SIZE_OF_MAR_FILE. */
  if (fseeko(fp, 0, SEEK_END)) {
    fprintf(stderr, "ERROR: Could not seek to the end of the MAR file.\n");
    return CryptoX_Error;
  }
  if (ftello(fp) > MAX_SIZE_OF_MAR_FILE) {
    fprintf(stderr, "ERROR: MAR file is too large to be verified.\n");
    return CryptoX_Error;
  }

  /* Skip to the start of the signature block */
  if (fseeko(fp, SIGNATURE_BLOCK_OFFSET, SEEK_SET)) {
    fprintf(stderr, "ERROR: Could not seek to the signature block.\n");
    return CryptoX_Error;
  }

  /* Get the number of signatures */
  if (fread(&signatureCount, sizeof(signatureCount), 1, fp) != 1) {
    fprintf(stderr, "ERROR: Could not read number of signatures.\n");
    return CryptoX_Error;
  }
  signatureCount = ntohl(signatureCount);

  /* Check that we have less than the max amount of signatures so we don't
     waste too much of either updater's or signmar's time. */
  if (signatureCount > MAX_SIGNATURES) {
    fprintf(stderr, "ERROR: At most %d signatures can be specified.\n",
            MAX_SIGNATURES);
    return CryptoX_Error;
  }

  for (i = 0; i < signatureCount && numVerified == 0; i++) {
    /* Get the signature algorithm ID */
    if (fread(&signatureAlgorithmID, sizeof(PRUint32), 1, fp) != 1) {
      fprintf(stderr, "ERROR: Could not read signatures algorithm ID.\n");
      return CryptoX_Error;
    }
    signatureAlgorithmID = ntohl(signatureAlgorithmID);
  
    if (fread(&signatureLen, sizeof(PRUint32), 1, fp) != 1) {
      fprintf(stderr, "ERROR: Could not read signatures length.\n");
      return CryptoX_Error;
    }
    signatureLen = ntohl(signatureLen);

    /* To protected against invalid input make sure the signature length
       isn't too big. */
    if (signatureLen > MAX_SIGNATURE_LENGTH) {
      fprintf(stderr, "ERROR: Signature length is too large to verify.\n");
      return CryptoX_Error;
    }

    extractedSignature = malloc(signatureLen);
    if (!extractedSignature) {
      fprintf(stderr, "ERROR: Could allocate buffer for signature.\n");
      return CryptoX_Error;
    }
    if (fread(extractedSignature, signatureLen, 1, fp) != 1) {
      fprintf(stderr, "ERROR: Could not read extracted signature.\n");
      free(extractedSignature);
      return CryptoX_Error;
    }

    /* We don't try to verify signatures we don't know about */
    if (1 == signatureAlgorithmID) {
      curPos = ftello(fp);
      rv = mar_verify_signature_for_fp(fp, 
                                       provider, 
                                       key,
                                       signatureCount,
                                       extractedSignature);
      if (CryptoX_Succeeded(rv)) {
        numVerified++;
      }
      free(extractedSignature);
      if (fseeko(fp, curPos, SEEK_SET)) {
        fprintf(stderr, "ERROR: Could not seek back to last signature.\n");
        return CryptoX_Error;
      }
    } else {
      free(extractedSignature);
    }
  }

  /* If we reached here and we verified at least one 
     signature, return success. */
  if (numVerified > 0) {
    return CryptoX_Success;
  } else {
    fprintf(stderr, "ERROR: No signatures were verified.\n");
    return CryptoX_Error;
  }
}

/**
 * Verifies if a specific signature ID matches the extracted signature.
 * 
 * @param  fp                   An opened MAR file handle
 * @param  provider             A library provider
 * @param  key                  The public key to use to verify the MAR
 * @param  signatureCount        The number of signatures in the MAR file
 * @param  extractedSignature    The signature that should be verified
 * @return 0 on success
*/
int
mar_verify_signature_for_fp(FILE *fp, 
                            CryptoX_ProviderHandle provider, 
                            CryptoX_PublicKey key, 
                            PRUint32 signatureCount,
                            char *extractedSignature) {
  CryptoX_SignatureHandle signatureHandle;
  char buf[BLOCKSIZE];
  PRUint32 signatureLen;
  PRUint32 i;

  if (!extractedSignature) {
    fprintf(stderr, "ERROR: Invalid parameter specified.\n");
    return CryptoX_Error;
  }

  /* This function is only called when we have at least one signature,
     but to protected against future people who call this function we
     make sure a non zero value is passed in. 
   */
  if (!signatureCount) {
    fprintf(stderr, "ERROR: There must be at least one signature.\n");
    return CryptoX_Error;
  }

  CryptoX_VerifyBegin(provider, &signatureHandle, &key);

  /* Skip to the start of the file */
  if (fseeko(fp, 0, SEEK_SET)) {
    fprintf(stderr, "ERROR: Could not seek to start of the file\n");
    return CryptoX_Error;
  }

  /* Bytes 0-3: MAR1
     Bytes 4-7: index offset 
     Bytes 8-15: size of entire MAR
   */
  if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, buf, 
                                                SIGNATURE_BLOCK_OFFSET +
                                                sizeof(PRUint32),
                                                &signatureHandle,
                                                "signature block"))) {
    return CryptoX_Error;
  }

  for (i = 0; i < signatureCount; i++) {
    /* Get the signature algorithm ID */
    if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp,
                                                  &buf, 
                                                  sizeof(PRUint32),
                                                  &signatureHandle, 
                                                  "signature algorithm ID"))) {
        return CryptoX_Error;
    }

    if (CryptoX_Failed(ReadAndUpdateVerifyContext(fp, 
                                                  &signatureLen, 
                                                  sizeof(PRUint32), 
                                                  &signatureHandle, 
                                                  "signature length"))) {
      return CryptoX_Error;
    }
    signatureLen = ntohl(signatureLen);

    /* Skip past the signature itself as those are not included */
    if (fseeko(fp, signatureLen, SEEK_CUR)) {
      fprintf(stderr, "ERROR: Could not seek past signature.\n");
      return CryptoX_Error;
    }
  }

  while (!feof(fp)) {
    int numRead = fread(buf, 1, BLOCKSIZE , fp);
    if (ferror(fp)) {
      fprintf(stderr, "ERROR: Error reading data block.\n");
      return CryptoX_Error;
    }

    if (CryptoX_Failed(CryptoX_VerifyUpdate(&signatureHandle, 
                                            buf, numRead))) {
      fprintf(stderr, "ERROR: Error updating verify context with"
                      " data block.\n");
      return CryptoX_Error;
    }
  }

  if (CryptoX_Failed(CryptoX_VerifySignature(&signatureHandle, 
                                             &key,
                                             extractedSignature, 
                                             signatureLen))) {
    fprintf(stderr, "ERROR: Error verifying signature.\n");
    return CryptoX_Error;
  }

  return CryptoX_Success;
}
