blob: 2afa315a2b2052598628f38f1b2affc2931b553d [file] [log] [blame]
/* 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, 0,
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