ports/mbedTLS/azure_iot_jws_mbedtls.c (663 lines of code) (raw):
/* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License. */
#include "azure_iot_jws.h"
#include "azure/az_core.h"
#include "azure/az_iot.h"
#include "azure_iot_config.h"
#include "azure_iot_result.h"
#include "azure_iot_json_reader.h"
#include "azure_iot_adu_client.h"
#include "mbedtls/base64.h"
#include "mbedtls/rsa.h"
#include "mbedtls/pk.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include "mbedtls/cipher.h"
/**
* @brief Convenience macro to return if an operation failed.
*/
#define azureiotresultRETURN_IF_FAILED( exp ) \
do \
{ \
AzureIoTResult_t const _xAzResult = ( exp ); \
if( _xAzResult != eAzureIoTSuccess ) \
{ \
return _xAzResult; \
} \
} while( 0 )
#define azureiotjwsPKCS7_PAYLOAD_OFFSET 19
static const uint8_t jws_sha256_json_value[] = "sha256";
static const uint8_t jws_sjwk_json_value[] = "sjwk";
static const uint8_t jws_kid_json_value[] = "kid";
static const uint8_t jws_n_json_value[] = "n";
static const uint8_t jws_e_json_value[] = "e";
static const uint8_t jws_alg_json_value[] = "alg";
static const uint8_t jws_alg_rs256[] = "RS256";
typedef struct prvJWSValidationContext
{
int32_t outParsedManifestShaSize;
uint8_t * ucJWSHeader;
uint8_t * ucJWKHeader;
uint8_t * ucJWKPayload;
uint8_t * ucJWKSignature;
uint8_t * ucScratchCalculationBuffer;
uint8_t * ucJWSPayload;
uint8_t * ucJWSSignature;
uint8_t * ucSigningKeyN;
uint8_t * ucSigningKeyE;
uint8_t * ucManifestSHACalculation;
uint8_t * ucParsedManifestSha;
uint8_t * pucBase64EncodedHeader;
uint8_t * pucBase64EncodedPayload;
uint8_t * pucBase64EncodedSignature;
uint8_t * pucJWKBase64EncodedHeader;
uint8_t * pucJWKBase64EncodedPayload;
uint8_t * pucJWKBase64EncodedSignature;
uint32_t ulBase64EncodedHeaderLength;
uint32_t ulBase64EncodedPayloadLength;
uint32_t ulBase64SignatureLength;
uint32_t ulJWKBase64EncodedHeaderLength;
uint32_t ulJWKBase64EncodedPayloadLength;
uint32_t ulJWKBase64EncodedSignatureLength;
int32_t outSigningKeyELength;
int32_t outSigningKeyNLength;
int32_t outJWSHeaderLength;
int32_t outJWSPayloadLength;
int32_t outJWSSignatureLength;
int32_t outJWKHeaderLength;
int32_t outJWKPayloadLength;
int32_t outJWKSignatureLength;
az_span kidSpan;
az_span sha256Span;
az_span xBase64EncodedNSpan;
az_span xBase64EncodedESpan;
az_span xAlgSpan;
az_span xJWKManifestSpan;
} prvJWSValidationContext_t;
/* prvSplitJWS takes a JWS payload and returns pointers to its constituent header, payload, and signature parts. */
static AzureIoTResult_t prvSplitJWS( uint8_t * pucJWS,
uint32_t ulJWSLength,
uint8_t ** ppucHeader,
uint32_t * pulHeaderLength,
uint8_t ** ppucPayload,
uint32_t * pulPayloadLength,
uint8_t ** ppucSignature,
uint32_t * pulSignatureLength )
{
uint8_t * pucFirstDot;
uint8_t * pucSecondDot;
uint32_t ulDotCount = 0;
uint32_t ulIndex = 0;
*ppucHeader = pucJWS;
while( ulIndex < ulJWSLength )
{
if( *pucJWS == '.' )
{
ulDotCount++;
if( ulDotCount == 1 )
{
pucFirstDot = pucJWS;
}
else if( ulDotCount == 2 )
{
pucSecondDot = pucJWS;
}
else if( ulDotCount > 2 )
{
AZLogError( ( "JWS had more '.' than required (2)" ) );
return eAzureIoTErrorFailed;
}
}
pucJWS++;
ulIndex++;
}
if( ( ulDotCount != 2 ) || ( pucSecondDot >= ( *ppucHeader + ulJWSLength - 1 ) ) )
{
return eAzureIoTErrorFailed;
}
*pulHeaderLength = pucFirstDot - *ppucHeader;
*ppucPayload = pucFirstDot + 1;
*pulPayloadLength = pucSecondDot - *ppucPayload;
*ppucSignature = pucSecondDot + 1;
*pulSignatureLength = *ppucHeader + ulJWSLength - *ppucSignature;
return eAzureIoTSuccess;
}
/**
* @brief Calculate the SHA256 over a buffer of bytes
*
* @param pucInput The input buffer over which to calculate the SHA256.
* @param ulInputLength The length of \p pucInput.
* @param pucOutput The output buffer into which the SHA256. It must be 32 bytes in length.
* @return uint32_t The result of the operation.
* @retval 0 if successful.
* @retval Non-0 if not successful.
*/
static AzureIoTResult_t prvJWS_SHA256Calculate( const uint8_t * pucInput,
uint32_t ulInputLength,
uint8_t * pucOutput )
{
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init( &ctx );
mbedtls_md_setup( &ctx, mbedtls_md_info_from_type( md_type ), 0 );
mbedtls_md_starts( &ctx );
mbedtls_md_update( &ctx, pucInput, ulInputLength );
mbedtls_md_finish( &ctx, pucOutput );
mbedtls_md_free( &ctx );
return eAzureIoTSuccess;
}
/**
* @brief Verify the manifest via RS256 for the JWS.
*
* @param pucInput The input over which the RS256 will be verified.
* @param ulInputLength The length of \p pucInput.
* @param pucSignature The encrypted signature which will be decrypted by \p pucN and \p pucE.
* @param ulSignatureLength The length of \p pucSignature.
* @param pucN The key's modulus which is used to decrypt \p signature.
* @param ulNLength The length of \p pucN.
* @param pucE The exponent used for the key.
* @param ulELength The length of \p pucE.
* @param pucBuffer The buffer used as scratch space to make the calculations. It should be at least
* `azureiotjwsSHA256_SIZE` in size.
* @param ulBufferLength The length of \p pucBuffer.
* @return uint32_t The result of the operation.
* @retval 0 if successful.
* @retval Non-0 if not successful.
*/
static AzureIoTResult_t prvJWS_RS256Verify( uint8_t * pucInput,
uint32_t ulInputLength,
uint8_t * pucSignature,
uint32_t ulSignatureLength,
uint8_t * pucN,
uint32_t ulNLength,
uint8_t * pucE,
uint32_t ulELength,
uint8_t * pucBuffer,
uint32_t ulBufferLength )
{
AzureIoTResult_t xResult;
int32_t lMbedTLSResult;
mbedtls_rsa_context ctx;
if( ulBufferLength < azureiotjwsSHA_CALCULATION_SCRATCH_SIZE )
{
AZLogError( ( "[JWS] Buffer Not Large Enough" ) );
return eAzureIoTErrorOutOfMemory;
}
/* The signature is encrypted using the input key. We need to decrypt the */
/* signature which gives us the SHA256 inside a PKCS7 structure. We then compare */
/* that to the SHA256 of the input. */
#if MBEDTLS_VERSION_NUMBER >= 0x03000000
mbedtls_rsa_init( &ctx );
#else
mbedtls_rsa_init( &ctx, MBEDTLS_RSA_PKCS_V15, 0 );
#endif
lMbedTLSResult = mbedtls_rsa_import_raw( &ctx,
pucN, ulNLength,
NULL, 0,
NULL, 0,
NULL, 0,
pucE, ulELength );
if( lMbedTLSResult != 0 )
{
AZLogError( ( "[JWS] mbedtls_rsa_import_raw res: %08x", ( uint16_t ) lMbedTLSResult ) );
mbedtls_rsa_free( &ctx );
return eAzureIoTErrorFailed;
}
lMbedTLSResult = mbedtls_rsa_complete( &ctx );
if( lMbedTLSResult != 0 )
{
AZLogError( ( "[JWS] mbedtls_rsa_complete res: %08x", ( uint16_t ) lMbedTLSResult ) );
mbedtls_rsa_free( &ctx );
return eAzureIoTErrorFailed;
}
lMbedTLSResult = mbedtls_rsa_check_pubkey( &ctx );
if( lMbedTLSResult != 0 )
{
AZLogError( ( "[JWS] mbedtls_rsa_check_pubkey res: %08x", ( uint16_t ) lMbedTLSResult ) );
mbedtls_rsa_free( &ctx );
return eAzureIoTErrorFailed;
}
/* RSA */
xResult = prvJWS_SHA256Calculate( pucInput, ulInputLength,
pucBuffer );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvJWS_SHA256Calculate failed" ) );
return xResult;
}
#if MBEDTLS_VERSION_NUMBER >= 0x03000000
lMbedTLSResult = mbedtls_rsa_pkcs1_verify( &ctx, MBEDTLS_MD_SHA256, azureiotjwsSHA256_SIZE, pucBuffer, pucSignature );
#else
lMbedTLSResult = mbedtls_rsa_pkcs1_verify( &ctx, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, azureiotjwsSHA256_SIZE, pucBuffer, pucSignature );
#endif
if( lMbedTLSResult != 0 )
{
AZLogError( ( "[JWS] SHA of JWK does NOT match (%08x)", ( uint16_t ) lMbedTLSResult ) );
xResult = eAzureIoTErrorFailed;
}
else
{
xResult = eAzureIoTSuccess;
}
mbedtls_rsa_free( &ctx );
return xResult;
}
static AzureIoTResult_t prvFindSJWKValue( AzureIoTJSONReader_t * pxPayload,
az_span * pxJWSValue )
{
AzureIoTResult_t xResult = eAzureIoTSuccess;
AzureIoTJSONTokenType_t xJSONTokenType;
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
while( xResult == eAzureIoTSuccess )
{
if( AzureIoTJSONReader_TokenIsTextEqual( pxPayload, ( const uint8_t * ) jws_sjwk_json_value, sizeof( jws_sjwk_json_value ) - 1 ) )
{
/* Found name, move to value */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
break;
}
else
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_SkipChildren( pxPayload ) );
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
}
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] Parse JSK JSON Payload Error: 0x%08x", xResult ) );
return xResult;
}
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_TokenType( pxPayload, &xJSONTokenType ) );
if( xJSONTokenType != eAzureIoTJSONTokenSTRING )
{
AZLogError( ( "[JWS] JSON token type wrong | type: 0x%08x", xJSONTokenType ) );
return eAzureIoTErrorFailed;
}
*pxJWSValue = pxPayload->_internal.xCoreReader.token.slice;
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvFindRootKeyValue( AzureIoTJSONReader_t * pxPayload,
az_span * pxKIDSpan )
{
AzureIoTResult_t xResult = eAzureIoTSuccess;
AzureIoTJSONTokenType_t xJSONTokenType;
/*Begin object */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
/*Property Name */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
while( xResult == eAzureIoTSuccess )
{
if( AzureIoTJSONReader_TokenIsTextEqual( pxPayload, ( const uint8_t * ) jws_kid_json_value, sizeof( jws_kid_json_value ) - 1 ) )
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
*pxKIDSpan = pxPayload->_internal.xCoreReader.token.slice;
break;
}
else
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_SkipChildren( pxPayload ) );
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
}
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] Parse Root Key Error: 0x%08x", xResult ) );
return xResult;
}
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_TokenType( pxPayload, &xJSONTokenType ) );
if( xJSONTokenType != eAzureIoTJSONTokenSTRING )
{
AZLogError( ( "[JWS] JSON token type wrong | type: %08x", xJSONTokenType ) );
return eAzureIoTErrorFailed;
}
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvFindKeyParts( AzureIoTJSONReader_t * pxPayload,
az_span * pxBase64EncodedNSpan,
az_span * pxBase64EncodedESpan,
az_span * pxAlgSpan )
{
AzureIoTResult_t xResult = eAzureIoTSuccess;
/*Begin object */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
/*Property Name */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
while( xResult == eAzureIoTSuccess &&
( az_span_size( *pxBase64EncodedNSpan ) == 0 ||
az_span_size( *pxBase64EncodedESpan ) == 0 ||
az_span_size( *pxAlgSpan ) == 0 ) )
{
if( AzureIoTJSONReader_TokenIsTextEqual( pxPayload, jws_n_json_value, sizeof( jws_n_json_value ) - 1 ) )
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
*pxBase64EncodedNSpan = pxPayload->_internal.xCoreReader.token.slice;
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
else if( AzureIoTJSONReader_TokenIsTextEqual( pxPayload, jws_e_json_value, sizeof( jws_e_json_value ) - 1 ) )
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
*pxBase64EncodedESpan = pxPayload->_internal.xCoreReader.token.slice;
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
else if( AzureIoTJSONReader_TokenIsTextEqual( pxPayload, jws_alg_json_value, sizeof( jws_alg_json_value ) - 1 ) )
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
*pxAlgSpan = pxPayload->_internal.xCoreReader.token.slice;
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
else
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_SkipChildren( pxPayload ) );
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
}
if( ( xResult != eAzureIoTSuccess ) ||
( az_span_size( *pxBase64EncodedNSpan ) == 0 ) ||
( az_span_size( *pxBase64EncodedESpan ) == 0 ) ||
( az_span_size( *pxAlgSpan ) == 0 ) )
{
AZLogError( ( "[JWS] Parse Signing Key Payload Error: %i",
xResult != eAzureIoTSuccess ? xResult : eAzureIoTErrorFailed ) );
return xResult != eAzureIoTSuccess ? xResult : eAzureIoTErrorFailed;
}
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvFindManifestSHA( AzureIoTJSONReader_t * pxPayload,
az_span * pxSHA )
{
AzureIoTResult_t xResult = eAzureIoTSuccess;
AzureIoTJSONTokenType_t xJSONTokenType;
/*Begin object */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
/*Property Name */
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
while( xResult == eAzureIoTSuccess )
{
if( AzureIoTJSONReader_TokenIsTextEqual( pxPayload, jws_sha256_json_value, sizeof( jws_sha256_json_value ) - 1 ) )
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
break;
}
else
{
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_NextToken( pxPayload ) );
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_SkipChildren( pxPayload ) );
xResult = AzureIoTJSONReader_NextToken( pxPayload );
}
}
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] Parse manifest SHA error: 0x%08x", xResult ) );
return xResult;
}
azureiotresultRETURN_IF_FAILED( AzureIoTJSONReader_TokenType( pxPayload, &xJSONTokenType ) );
if( xJSONTokenType != eAzureIoTJSONTokenSTRING )
{
AZLogError( ( "[JWS] JSON token type wrong | type: %08x", xJSONTokenType ) );
return eAzureIoTErrorFailed;
}
*pxSHA = pxPayload->_internal.xCoreReader.token.slice;
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvBase64DecodeJWK( prvJWSValidationContext_t * pxManifestContext )
{
az_result xCoreResult;
xCoreResult = az_base64_url_decode( az_span_create( pxManifestContext->ucJWKHeader, azureiotjwsJWK_HEADER_SIZE ),
az_span_create( pxManifestContext->pucJWKBase64EncodedHeader, pxManifestContext->ulJWKBase64EncodedHeaderLength ),
&pxManifestContext->outJWKHeaderLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_url_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsJWK_HEADER_SIZE ) );
}
return eAzureIoTErrorFailed;
}
xCoreResult = az_base64_url_decode( az_span_create( pxManifestContext->ucJWKPayload, azureiotjwsJWK_PAYLOAD_SIZE ),
az_span_create( pxManifestContext->pucJWKBase64EncodedPayload, pxManifestContext->ulJWKBase64EncodedPayloadLength ),
&pxManifestContext->outJWKPayloadLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_url_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsJWK_PAYLOAD_SIZE ) );
}
return eAzureIoTErrorFailed;
}
xCoreResult = az_base64_url_decode( az_span_create( pxManifestContext->ucJWKSignature, azureiotjwsSIGNATURE_SIZE ),
az_span_create( pxManifestContext->pucJWKBase64EncodedSignature, pxManifestContext->ulJWKBase64EncodedSignatureLength ),
&pxManifestContext->outJWKSignatureLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_url_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsSIGNATURE_SIZE ) );
}
return eAzureIoTErrorFailed;
}
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvBase64DecodeSigningKey( prvJWSValidationContext_t * pxManifestContext )
{
az_result xCoreResult;
xCoreResult = az_base64_decode( az_span_create( pxManifestContext->ucSigningKeyN, azureiotjwsRSA3072_SIZE ),
pxManifestContext->xBase64EncodedNSpan,
&pxManifestContext->outSigningKeyNLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsRSA3072_SIZE ) );
}
return eAzureIoTErrorFailed;
}
xCoreResult = az_base64_decode( az_span_create( pxManifestContext->ucSigningKeyE, azureiotjwsSIGNING_KEY_E_SIZE ),
pxManifestContext->xBase64EncodedESpan,
&pxManifestContext->outSigningKeyELength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsSIGNING_KEY_E_SIZE ) );
}
return eAzureIoTErrorFailed;
}
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvBase64DecodeJWSHeaderAndPayload( prvJWSValidationContext_t * pxManifestContext )
{
az_result xCoreResult;
xCoreResult = az_base64_url_decode( az_span_create( pxManifestContext->ucJWSPayload, azureiotjwsJWS_PAYLOAD_SIZE ),
az_span_create( pxManifestContext->pucBase64EncodedPayload, pxManifestContext->ulBase64EncodedPayloadLength ),
&pxManifestContext->outJWSPayloadLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_url_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsJWS_PAYLOAD_SIZE ) );
}
return eAzureIoTErrorFailed;
}
xCoreResult = az_base64_url_decode( az_span_create( pxManifestContext->ucJWSSignature, azureiotjwsSIGNATURE_SIZE ),
az_span_create( pxManifestContext->pucBase64EncodedSignature, pxManifestContext->ulBase64SignatureLength ),
&pxManifestContext->outJWSSignatureLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_url_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsSIGNATURE_SIZE ) );
}
return eAzureIoTErrorFailed;
}
return eAzureIoTSuccess;
}
static AzureIoTResult_t prvValidateRootKey( prvJWSValidationContext_t * pxManifestContext,
AzureIoTJWS_RootKey_t * xADURootKeys,
uint32_t ulADURootKeysLength,
int32_t * pulADURootKeyIndex )
{
AzureIoTJSONReader_t xJSONReader;
AzureIoTJSONReader_Init( &xJSONReader, pxManifestContext->ucJWKHeader, pxManifestContext->outJWKHeaderLength );
if( prvFindRootKeyValue( &xJSONReader, &pxManifestContext->kidSpan ) != eAzureIoTSuccess )
{
AZLogError( ( "Could not find kid in JSON" ) );
return eAzureIoTErrorFailed;
}
for( int i = 0; i < ulADURootKeysLength; i++ )
{
if( az_span_is_content_equal( az_span_create( ( uint8_t * ) xADURootKeys[ i ].pucRootKeyId, xADURootKeys[ i ].ulRootKeyIdLength ),
pxManifestContext->kidSpan ) )
{
*pulADURootKeyIndex = i;
return eAzureIoTSuccess;
}
}
*pulADURootKeyIndex = -1;
AZLogError( ( "[JWS] Using the wrong root key" ) );
return eAzureIoTErrorFailed;
}
static AzureIoTResult_t prvVerifySHAMatch( prvJWSValidationContext_t * pxManifestContext,
const uint8_t * pucManifest,
uint32_t ulManifestLength )
{
AzureIoTJSONReader_t xJSONReader;
AzureIoTResult_t ulVerificationResult;
az_result xCoreResult;
ulVerificationResult = prvJWS_SHA256Calculate( pucManifest,
ulManifestLength,
pxManifestContext->ucManifestSHACalculation );
if( ulVerificationResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] SHA256 Calculation failed" ) );
return ulVerificationResult;
}
AzureIoTJSONReader_Init( &xJSONReader, pxManifestContext->ucJWSPayload, pxManifestContext->outJWSPayloadLength );
if( prvFindManifestSHA( &xJSONReader, &pxManifestContext->sha256Span ) != eAzureIoTSuccess )
{
AZLogError( ( "Error finding manifest signature SHA" ) );
return eAzureIoTErrorFailed;
}
xCoreResult = az_base64_decode( az_span_create( pxManifestContext->ucParsedManifestSha, azureiotjwsSHA256_SIZE ),
pxManifestContext->sha256Span,
&pxManifestContext->outParsedManifestShaSize );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsSHA256_SIZE ) );
}
return eAzureIoTErrorFailed;
}
if( pxManifestContext->outParsedManifestShaSize != azureiotjwsSHA256_SIZE )
{
AZLogError( ( "[JWS] Base64 decoded SHA256 is not the correct length | expected: %i | actual: %i", azureiotjwsSHA256_SIZE, ( int16_t ) pxManifestContext->outParsedManifestShaSize ) );
return eAzureIoTErrorFailed;
}
int32_t lComparisonResult = memcmp( pxManifestContext->ucManifestSHACalculation, pxManifestContext->ucParsedManifestSha, azureiotjwsSHA256_SIZE );
if( lComparisonResult != 0 )
{
AZLogError( ( "[JWS] Calculated manifest SHA does not match SHA in payload" ) );
return eAzureIoTErrorFailed;
}
else
{
AZLogInfo( ( "[JWS] Calculated manifest SHA matches parsed SHA" ) );
}
return eAzureIoTSuccess;
}
AzureIoTResult_t AzureIoTJWS_ManifestAuthenticate( const uint8_t * pucManifest,
uint32_t ulManifestLength,
uint8_t * pucJWS,
uint32_t ulJWSLength,
AzureIoTJWS_RootKey_t * xADURootKeys,
uint32_t ulADURootKeysLength,
uint8_t * pucScratchBuffer,
uint32_t ulScratchBufferLength )
{
az_result xCoreResult;
AzureIoTResult_t xResult;
AzureIoTJSONReader_t xJSONReader;
prvJWSValidationContext_t xManifestContext = { 0 };
int32_t lRootKeyIndex;
/* Break up scratch buffer for reusable and persistant sections */
uint8_t * ucPersistentScratchSpaceHead = pucScratchBuffer;
uint8_t * ucReusableScratchSpaceRoot = ucPersistentScratchSpaceHead + azureiotjwsJWS_HEADER_SIZE + azureiotjwsJWK_PAYLOAD_SIZE;
uint8_t * ucReusableScratchSpaceHead = ucReusableScratchSpaceRoot;
/*------------------- Parse and Decode the JWS Header ------------------------*/
xResult = prvSplitJWS( pucJWS, ulJWSLength,
&xManifestContext.pucBase64EncodedHeader, &xManifestContext.ulBase64EncodedHeaderLength,
&xManifestContext.pucBase64EncodedPayload, &xManifestContext.ulBase64EncodedPayloadLength,
&xManifestContext.pucBase64EncodedSignature, &xManifestContext.ulBase64SignatureLength );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvSplitJWS failed" ) );
return xResult;
}
xManifestContext.ucJWSHeader = ucPersistentScratchSpaceHead;
ucPersistentScratchSpaceHead += azureiotjwsJWS_HEADER_SIZE;
xCoreResult = az_base64_url_decode( az_span_create( xManifestContext.ucJWSHeader, azureiotjwsJWS_HEADER_SIZE ),
az_span_create( xManifestContext.pucBase64EncodedHeader, xManifestContext.ulBase64EncodedHeaderLength ),
&xManifestContext.outJWSHeaderLength );
if( az_result_failed( xCoreResult ) )
{
AZLogError( ( "[JWS] az_base64_url_decode failed: result 0x%08x", ( uint16_t ) xCoreResult ) );
if( xCoreResult == AZ_ERROR_NOT_ENOUGH_SPACE )
{
AZLogError( ( "[JWS] Decode buffer was too small: %i bytes", azureiotjwsJWS_HEADER_SIZE ) );
}
return eAzureIoTErrorFailed;
}
/*------------------- Parse SJWK JSON Payload ------------------------*/
/* The "sjwk" is the signed signing public key */
AzureIoTJSONReader_Init( &xJSONReader, xManifestContext.ucJWSHeader, xManifestContext.outJWSHeaderLength );
if( prvFindSJWKValue( &xJSONReader, &xManifestContext.xJWKManifestSpan ) != eAzureIoTSuccess )
{
AZLogError( ( "Error finding sjwk value in payload" ) );
return eAzureIoTErrorFailed;
}
/*------------------- Split JWK and Base64 Decode the JWK Payload ------------------------*/
xResult = prvSplitJWS( az_span_ptr( xManifestContext.xJWKManifestSpan ), az_span_size( xManifestContext.xJWKManifestSpan ),
&xManifestContext.pucJWKBase64EncodedHeader, &xManifestContext.ulJWKBase64EncodedHeaderLength,
&xManifestContext.pucJWKBase64EncodedPayload, &xManifestContext.ulJWKBase64EncodedPayloadLength,
&xManifestContext.pucJWKBase64EncodedSignature, &xManifestContext.ulJWKBase64EncodedSignatureLength );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvSplitJWS failed" ) );
return xResult;
}
xManifestContext.ucJWKHeader = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsJWK_HEADER_SIZE;
/* Needs to be persisted so we can parse the signing key N and E later */
xManifestContext.ucJWKPayload = ucPersistentScratchSpaceHead;
ucPersistentScratchSpaceHead += azureiotjwsJWK_PAYLOAD_SIZE;
xManifestContext.ucJWKSignature = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsSIGNATURE_SIZE;
prvBase64DecodeJWK( &xManifestContext );
/*------------------- Parse root key id ------------------------*/
xResult = prvValidateRootKey( &xManifestContext, xADURootKeys, ulADURootKeysLength, &lRootKeyIndex );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvValidateRootKey failed" ) );
return xResult;
}
/*------------------- Parse necessary pieces for signing key ------------------------*/
AzureIoTJSONReader_Init( &xJSONReader, xManifestContext.ucJWKPayload, xManifestContext.outJWKPayloadLength );
if( prvFindKeyParts( &xJSONReader, &xManifestContext.xBase64EncodedNSpan, &xManifestContext.xBase64EncodedESpan, &xManifestContext.xAlgSpan ) != eAzureIoTSuccess )
{
AZLogError( ( "Could not find parts for the signing key" ) );
return eAzureIoTErrorFailed;
}
/*------------------- Verify the signature ------------------------*/
xManifestContext.ucScratchCalculationBuffer = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsSHA_CALCULATION_SCRATCH_SIZE;
xResult = prvJWS_RS256Verify( xManifestContext.pucJWKBase64EncodedHeader, xManifestContext.ulJWKBase64EncodedHeaderLength + xManifestContext.ulJWKBase64EncodedPayloadLength + 1,
xManifestContext.ucJWKSignature, xManifestContext.outJWKSignatureLength,
( uint8_t * ) xADURootKeys[ lRootKeyIndex ].pucRootKeyN, xADURootKeys[ lRootKeyIndex ].ulRootKeyNLength,
( uint8_t * ) xADURootKeys[ lRootKeyIndex ].pucRootKeyExponent, xADURootKeys[ lRootKeyIndex ].ulRootKeyExponentLength,
xManifestContext.ucScratchCalculationBuffer, azureiotjwsSHA_CALCULATION_SCRATCH_SIZE );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvJWS_RS256Verify failed" ) );
return xResult;
}
/*------------------- Reuse Buffer Space ------------------------*/
/* The JWK verification is now done, so we can reuse the buffers which it used. */
ucReusableScratchSpaceHead = ucReusableScratchSpaceRoot;
/*------------------- Decode remaining values from JWS ------------------------*/
xManifestContext.ucJWSPayload = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsJWS_PAYLOAD_SIZE;
xManifestContext.ucJWSSignature = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsSIGNATURE_SIZE;
xResult = prvBase64DecodeJWSHeaderAndPayload( &xManifestContext );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvBase64DecodeJWSHeaderAndPayload failed" ) );
return xResult;
}
/*------------------- Base64 decode the signing key ------------------------*/
xManifestContext.ucSigningKeyN = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsRSA3072_SIZE;
xManifestContext.ucSigningKeyE = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsSIGNING_KEY_E_SIZE;
xResult = prvBase64DecodeSigningKey( &xManifestContext );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] prvBase64DecodeSigningKey failed" ) );
return xResult;
}
/*------------------- Verify that the signature was signed by signing key ------------------------*/
if( !az_span_is_content_equal( xManifestContext.xAlgSpan, az_span_create( ( uint8_t * ) jws_alg_rs256, sizeof( jws_alg_rs256 ) - 1 ) ) )
{
AZLogError( ( "[JWS] Algorithm not supported | expected %.*s | actual %.*s",
sizeof( jws_alg_rs256 ) - 1, jws_alg_rs256,
( int16_t ) az_span_size( xManifestContext.xAlgSpan ), az_span_ptr( xManifestContext.xAlgSpan ) ) );
return eAzureIoTErrorFailed;
}
xResult = prvJWS_RS256Verify( xManifestContext.pucBase64EncodedHeader, xManifestContext.ulBase64EncodedHeaderLength + xManifestContext.ulBase64EncodedPayloadLength + 1,
xManifestContext.ucJWSSignature, xManifestContext.outJWSSignatureLength,
xManifestContext.ucSigningKeyN, xManifestContext.outSigningKeyNLength,
xManifestContext.ucSigningKeyE, xManifestContext.outSigningKeyELength,
xManifestContext.ucScratchCalculationBuffer, azureiotjwsSHA_CALCULATION_SCRATCH_SIZE );
if( xResult != eAzureIoTSuccess )
{
AZLogError( ( "[JWS] Verification of signed manifest SHA failed" ) );
return xResult;
}
/*------------------- Verify that the SHAs match ------------------------*/
xManifestContext.ucManifestSHACalculation = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsSHA256_SIZE;
xManifestContext.ucParsedManifestSha = ucReusableScratchSpaceHead;
ucReusableScratchSpaceHead += azureiotjwsSHA256_SIZE;
return prvVerifySHAMatch( &xManifestContext, pucManifest, ulManifestLength );
}