/* Copyright 2019 IBM Corp.
 *
 * Licensed 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.
 */

#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
#if defined(MBEDTLS_PKCS7_C)

#include "mbedtls/x509.h"
#include "mbedtls/asn1.h"
#include "pkcs7.h"
#include "mbedtls/x509_crt.h"
#include "mbedtls/x509_crl.h"
#include "mbedtls/oid.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if defined(MBEDTLS_FS_IO)
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <unistd.h>

#if defined(MBEDTLS_PLATFORM_C)
#include "mbedtls/platform.h"
#include "mbedtls/platform_util.h"
#else
#include <stdio.h>
#include <stdlib.h>
#define mbedtls_free      free
#define mbedtls_calloc    calloc
#define mbedtls_printf    printf
#define mbedtls_snprintf  snprintf
#endif

#if defined(MBEDTLS_HAVE_TIME)
#include "mbedtls/platform_time.h"
#endif
#if defined(MBEDTLS_HAVE_TIME_DATE)
#include <time.h>
#endif

#if defined(MBEDTLS_FS_IO)
/*
 * Load all data from a file into a given buffer.
 *
 * The file is expected to contain DER encoded data.
 * A terminating null byte is always appended.
 */
int mbedtls_pkcs7_load_file( const char *path, unsigned char **buf, size_t *n )
{
    FILE *file;

    if( ( file = fopen( path, "rb" ) ) == NULL )
        return( MBEDTLS_ERR_PKCS7_FILE_IO_ERROR );

    fseek( file, 0, SEEK_END );
    *n = (size_t) ftell( file );
    fseek( file, 0, SEEK_SET );

    *buf = mbedtls_calloc( 1, *n + 1 );
    if( *buf == NULL )
        return( MBEDTLS_ERR_PKCS7_ALLOC_FAILED );

    if( fread( *buf, 1, *n, file ) != *n )
    {
        fclose( file );

        mbedtls_platform_zeroize( *buf, *n + 1 );
        mbedtls_free( *buf );

        return( MBEDTLS_ERR_PKCS7_FILE_IO_ERROR );
    }

    fclose( file );

    (*buf)[*n] = '\0';

    return( 0 );
}
#endif

/**
 * Initializes the pkcs7 structure.
 */
void mbedtls_pkcs7_init( mbedtls_pkcs7 *pkcs7 )
{
    memset( pkcs7, 0, sizeof( mbedtls_pkcs7 ) );
}

static int pkcs7_get_next_content_len( unsigned char **p, unsigned char *end,
                                       size_t *len )
{
    int ret;

    if( ( ret = mbedtls_asn1_get_tag( p, end, len, MBEDTLS_ASN1_CONSTRUCTED
                    | MBEDTLS_ASN1_CONTEXT_SPECIFIC ) ) != 0 )
    {
        return( MBEDTLS_ERR_PKCS7_INVALID_FORMAT + ret );
    }

    return( 0 );
}

/**
 * version Version
 * Version ::= INTEGER
 **/
static int pkcs7_get_version( unsigned char **p, unsigned char *end, int *ver )
{
    int ret;

    if( ( ret = mbedtls_asn1_get_int( p, end, ver ) ) != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_FORMAT + ret );

    /* If version != 1, return invalid version */
    if( *ver != MBEDTLS_PKCS7_SUPPORTED_VERSION )
        return( MBEDTLS_ERR_PKCS7_INVALID_VERSION );

    return( 0 );
}

/**
 * ContentInfo ::= SEQUENCE {
 *      contentType ContentType,
 *      content
 *              [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
 **/
static int pkcs7_get_content_info_type( unsigned char **p, unsigned char *end,
                                        mbedtls_pkcs7_buf *pkcs7 )
{
    size_t len = 0;
    int ret;
    unsigned char *start = *p;

    ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_CONSTRUCTED
                                            | MBEDTLS_ASN1_SEQUENCE );
    if( ret != 0 ) {
	*p = start;
        return( MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO + ret );
    }

    ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_OID );
    if( ret != 0 ) {
        *p = start;
        return( MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO + ret );
	}

    pkcs7->tag = MBEDTLS_ASN1_OID;
    pkcs7->len = len;
    pkcs7->p = *p;

    return( ret );
}

/**
 * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
 *
 * This is from x509.h
 **/
static int pkcs7_get_digest_algorithm( unsigned char **p, unsigned char *end,
                                       mbedtls_x509_buf *alg )
{
    int ret;

    if( ( ret = mbedtls_asn1_get_alg_null( p, end, alg ) ) != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_ALG + ret );

    return( 0 );
}

/**
 * DigestAlgorithmIdentifiers :: SET of DigestAlgorithmIdentifier
 **/
static int pkcs7_get_digest_algorithm_set( unsigned char **p,
                                           unsigned char *end,
                                           mbedtls_x509_buf *alg )
{
    size_t len = 0;
    int ret;

    ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_CONSTRUCTED
                                            | MBEDTLS_ASN1_SET );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_ALG + ret );

    end = *p + len;

    /** For now, it assumes there is only one digest algorithm specified **/
    ret = mbedtls_asn1_get_alg_null( p, end, alg );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_ALG + ret );

    if (*p != end)
        return ( MBEDTLS_ERR_PKCS7_INVALID_FORMAT );

    return( 0 );
}

/**
 * certificates :: SET OF ExtendedCertificateOrCertificate,
 * ExtendedCertificateOrCertificate ::= CHOICE {
 *      certificate Certificate -- x509,
 *      extendedCertificate[0] IMPLICIT ExtendedCertificate }
 **/
static int pkcs7_get_certificates( unsigned char **p, unsigned char *end,
                                   mbedtls_x509_crt *certs )
{
    int ret;
    size_t len1 = 0;
    size_t len2 = 0;
    unsigned char *end_set, *end_cert;
    unsigned char *start = *p;

    if( ( ret = mbedtls_asn1_get_tag( p, end, &len1, MBEDTLS_ASN1_CONSTRUCTED
                    | MBEDTLS_ASN1_CONTEXT_SPECIFIC ) ) != 0 )
    {
        if( ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG )
            return( 0 );

        return( MBEDTLS_ERR_PKCS7_INVALID_FORMAT + ret );
    }
    start = *p;
    end_set = *p + len1;

    /* This is to verify that there is only signer certificate, it can
       have its chain though. */
    ret = mbedtls_asn1_get_tag( p, end_set, &len2, MBEDTLS_ASN1_CONSTRUCTED
            | MBEDTLS_ASN1_SEQUENCE );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_FORMAT + ret );

    end_cert = *p + len2;

    if (end_cert != end_set)
        return (MBEDTLS_ERR_PKCS7_INVALID_FORMAT);

    /* Since it satisfies the condition of single signer, continue parsing */
    *p = start;
    if( ( ret = mbedtls_x509_crt_parse( certs, *p, len1 ) ) < 0 )
        return( ret );

    *p = *p + len1;

    /**
     * Currently we do not check for certificate chain, so we are not handling
     * "> 0" case. Return if atleast one certificate in the chain is correctly
     * parsed.
     **/

    return( 0 );
}

/**
 * EncryptedDigest ::= OCTET STRING
 **/
static int pkcs7_get_signature( unsigned char **p, unsigned char *end,
                                mbedtls_pkcs7_buf *signature )
{
    int ret;
    size_t len = 0;

    ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_OCTET_STRING );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_SIGNATURE + ret );

    signature->tag = MBEDTLS_ASN1_OCTET_STRING;
    signature->len = len;
    signature->p = *p;

    *p = *p + len;

    return( 0 );
}

/**
 * SignerInfos ::= SET of SignerInfo
 * SignerInfo ::= SEQUENCE {
 *      version Version;
 *      issuerAndSerialNumber   IssuerAndSerialNumber,
 *      digestAlgorithm DigestAlgorithmIdentifier,
 *      authenticatedAttributes
 *              [0] IMPLICIT Attributes OPTIONAL,
 *      digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
 *      encryptedDigest EncryptedDigest,
 *      unauthenticatedAttributes
 *              [1] IMPLICIT Attributes OPTIONAL,
 **/
static int pkcs7_get_signers_info_set( unsigned char **p, unsigned char *end,
                                       mbedtls_pkcs7_signer_info *signers_set )
{
    unsigned char *end_set;
    int ret;
    size_t len = 0;

    ret = mbedtls_asn1_get_tag( p, end, &len, MBEDTLS_ASN1_CONSTRUCTED
            | MBEDTLS_ASN1_SET );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO + ret );

    end_set = *p + len;

    ret = mbedtls_asn1_get_tag( p, end_set, &len, MBEDTLS_ASN1_CONSTRUCTED
            | MBEDTLS_ASN1_SEQUENCE );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO + ret );

    end_set = *p + len;

    ret = mbedtls_asn1_get_int( p, end_set, &signers_set->version );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO + ret );

    ret = mbedtls_asn1_get_tag( p, end_set, &len, MBEDTLS_ASN1_CONSTRUCTED
            | MBEDTLS_ASN1_SEQUENCE );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO + ret );

	/* Parsing IssuerAndSerialNumber */
    signers_set->issuer_raw.p = *p;

    ret = mbedtls_asn1_get_tag( p, end_set, &len, MBEDTLS_ASN1_CONSTRUCTED
            | MBEDTLS_ASN1_SEQUENCE );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO + ret );

    ret  = mbedtls_x509_get_name( p, *p + len, &signers_set->issuer );
    if( ret != 0 )
        return( ret );

    signers_set->issuer_raw.len =  *p - signers_set->issuer_raw.p;

    ret = mbedtls_x509_get_serial( p, end_set, &signers_set->serial );
    if( ret != 0 )
        return( ret );

    ret = pkcs7_get_digest_algorithm( p, end_set,
            &signers_set->alg_identifier );
    if( ret != 0 )
        return( ret );

    ret = pkcs7_get_digest_algorithm( p, end_set,
            &signers_set->sig_alg_identifier );
    if( ret != 0 )
        return( ret );

    ret = pkcs7_get_signature( p, end_set, &signers_set->sig );
    if( ret != 0 )
        return( ret );

    signers_set->next = NULL;

    if (*p != end_set)
        return ( MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO );

    return( 0 );
}

/**
 * SignedData ::= SEQUENCE {
 *      version Version,
 *      digestAlgorithms DigestAlgorithmIdentifiers,
 *      contentInfo ContentInfo,
 *      certificates
 *              [0] IMPLICIT ExtendedCertificatesAndCertificates
 *                  OPTIONAL,
 *      crls
 *              [0] IMPLICIT CertificateRevocationLists OPTIONAL,
 *      signerInfos SignerInfos }
 */
static int pkcs7_get_signed_data( unsigned char *buf, size_t buflen,
        mbedtls_pkcs7_signed_data *signed_data )
{
    unsigned char *p = buf;
    unsigned char *end = buf + buflen;
    unsigned char *end_set;
    size_t len = 0;
    int ret;
    mbedtls_md_type_t md_alg;

    ret = mbedtls_asn1_get_tag( &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED
            | MBEDTLS_ASN1_SEQUENCE );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_FORMAT + ret );

    end_set = p + len;

    /* Get version of signed data */
    ret = pkcs7_get_version( &p, end_set, &signed_data->version );
    if( ret != 0 )
        return( ret );

    /* Get digest algorithm */
    ret = pkcs7_get_digest_algorithm_set( &p, end_set,
            &signed_data->digest_alg_identifiers );
    if( ret != 0 )
        return( ret );

    ret = mbedtls_oid_get_md_alg( &signed_data->digest_alg_identifiers, &md_alg );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_ALG + ret );

    /* Do not expect any content */
    ret = pkcs7_get_content_info_type( &p, end_set, &signed_data->content.oid );
    if( ret != 0 )
        return( ret );

    if( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_DATA, &signed_data->content.oid ) )
	{
        return( MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO ) ;
    }

    p = p + signed_data->content.oid.len;

	/* Look for certificates, there may or may not be any */
	mbedtls_x509_crt_init( &signed_data->certs );
	ret = pkcs7_get_certificates( &p, end_set, &signed_data->certs );
    if( ret != 0 )
        return( ret ) ;

    /* TODO: optional CRLs go here, currently no CRLs are expected */

    /* Get signers info */
    ret = pkcs7_get_signers_info_set( &p, end_set, &signed_data->signers );
    if( ret != 0 )
        return( ret );

    if ( p != end )
        ret = MBEDTLS_ERR_PKCS7_INVALID_FORMAT;

    return( ret );
}

int mbedtls_pkcs7_parse_der( const unsigned char *buf, const int buflen,
        mbedtls_pkcs7 *pkcs7 )
{
    unsigned char *start;
    unsigned char *end;
    size_t len = 0;
    int ret;
    int isoidset = 0;

    /* use internal buffer for parsing */
    start = (unsigned char *)buf;
    end = start + buflen;

    if( !pkcs7 )
        return( MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA );

    ret = pkcs7_get_content_info_type( &start, end, &pkcs7->content_type_oid );
    if( ret != 0 )
    {
        len = buflen;
        goto try_data;
    }

    if( ! MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_DATA, &pkcs7->content_type_oid )
     || ! MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_ENCRYPTED_DATA, &pkcs7->content_type_oid )
     || ! MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_ENVELOPED_DATA, &pkcs7->content_type_oid )
     || ! MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_SIGNED_AND_ENVELOPED_DATA, &pkcs7->content_type_oid )
     || ! MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_DIGESTED_DATA, &pkcs7->content_type_oid )
     || ! MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_ENCRYPTED_DATA, &pkcs7->content_type_oid ) )
	{
		ret =  MBEDTLS_ERR_PKCS7_FEATURE_UNAVAILABLE;
		goto out;
	}

    if( MBEDTLS_OID_CMP( MBEDTLS_OID_PKCS7_SIGNED_DATA, &pkcs7->content_type_oid ) )
    {
        ret = MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA;
        goto out;
    }

    isoidset = 1;
    start = start + pkcs7->content_type_oid.len;

    ret = pkcs7_get_next_content_len( &start, end, &len );
    if( ret != 0 )
        goto out;

try_data:
    ret = pkcs7_get_signed_data( start, len, &pkcs7->signed_data );
    if (ret != 0)
        goto out;

    if (!isoidset)
    {
        pkcs7->content_type_oid.tag = MBEDTLS_ASN1_OID;
        pkcs7->content_type_oid.len = MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS7_SIGNED_DATA);
        pkcs7->content_type_oid.p = (unsigned char *)MBEDTLS_OID_PKCS7_SIGNED_DATA;
    }

    ret = MBEDTLS_PKCS7_SIGNED_DATA;

out:
    if ( ret < 0 )
        mbedtls_pkcs7_free( pkcs7 );

    return( ret );
}

int mbedtls_pkcs7_signed_data_verify( mbedtls_pkcs7 *pkcs7,
                                      mbedtls_x509_crt *cert,
                                      const unsigned char *data,
                                      size_t datalen )
{

    int ret;
    unsigned char *hash;
    mbedtls_pk_context pk_cxt = cert->pk;
    const mbedtls_md_info_t *md_info;
    mbedtls_md_type_t md_alg;

    ret = mbedtls_oid_get_md_alg( &pkcs7->signed_data.digest_alg_identifiers, &md_alg );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_ALG + ret );

    md_info = mbedtls_md_info_from_type( md_alg );

    hash = mbedtls_calloc( mbedtls_md_get_size( md_info ), 1 );
    if( hash == NULL ) {
        return( MBEDTLS_ERR_PKCS7_ALLOC_FAILED );
    }

    mbedtls_md( md_info, data, datalen, hash );

    ret = mbedtls_pk_verify( &pk_cxt, md_alg, hash, sizeof(hash),
                                      pkcs7->signed_data.signers.sig.p,
                                      pkcs7->signed_data.signers.sig.len );

    mbedtls_free( hash );

    return( ret );
}

int mbedtls_pkcs7_signed_hash_verify( mbedtls_pkcs7 *pkcs7,
                                      mbedtls_x509_crt *cert,
                                      const unsigned char *hash, int hashlen)
{
    int ret;
    mbedtls_md_type_t md_alg;
    mbedtls_pk_context pk_cxt;

    ret = mbedtls_oid_get_md_alg( &pkcs7->signed_data.digest_alg_identifiers, &md_alg );
    if( ret != 0 )
        return( MBEDTLS_ERR_PKCS7_INVALID_ALG + ret );

    pk_cxt = cert->pk;
    ret = mbedtls_pk_verify( &pk_cxt, md_alg, hash, hashlen,
                             pkcs7->signed_data.signers.sig.p,
                             pkcs7->signed_data.signers.sig.len );

    return ( ret );
}

/*
 * Unallocate all pkcs7 data
 */
void mbedtls_pkcs7_free( mbedtls_pkcs7 *pkcs7 )
{
    mbedtls_x509_name *name_cur;
    mbedtls_x509_name *name_prv;

    if( pkcs7 == NULL )
        return;

    mbedtls_x509_crt_free( &pkcs7->signed_data.certs );
    mbedtls_x509_crl_free( &pkcs7->signed_data.crl );

    name_cur = pkcs7->signed_data.signers.issuer.next;
    while( name_cur != NULL )
    {
        name_prv = name_cur;
        name_cur = name_cur->next;
        mbedtls_platform_zeroize( name_prv, sizeof( mbedtls_x509_name ) );
        mbedtls_free( name_prv );
    }

    mbedtls_platform_zeroize( pkcs7, sizeof( mbedtls_pkcs7 ) );
}

#endif
