blob: 374140a106741b23a25762556f9965a3eeadb40e [file] [log] [blame]
/**
* @file ex_hlse_scp.c
* @author NXP Semiconductors
* @version 1.0
* @par License
*
* Copyright 2016 NXP
* SPDX-License-Identifier: Apache-2.0
*
* @par Description
* Set up an SCP03 channel between Host and A71CH,
* under the assumption the HostOS may set - and also has access to - the SCP03 Base Keys.
* In principle the binding between Host and Secure channel is only done once in the lifetime of
* the system.
*
* @note In a potentially more secure setup only the bootloader has access to the base key set,
* the bootloader hands over the session keys to the host OS.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "a71ch_ex_hlse.h"
#include "a71_debug.h"
#include "sm_types.h"
#include "sm_apdu.h"
#include "tst_sm_util.h"
#include "sm_printf.h"
#include "axHostCrypto.h"
#include "tstHostCrypto.h"
#include "scp.h"
#include "ax_util.h"
#include "tst_a71ch_util.h"
#include "tst_hlse_a71ch_util.h"
#include "HLSEAPI.h"
#include "HostCryptoAPI.h"
/*******************************************************************
* global variables and struct definitions
*******************************************************************/
static U8 exAuthenticate(U8 *keyEnc, U8 *keyMac, U8 *keyDek);
static U8 exProvision(void);
static U8 exUseCrypto(void);
static EC_KEY *eccKeyTls_0 = NULL; //!< OpenSSL specific datastructure to contain keypair to be stored at index 0
static EC_KEY *eccKeyTls_1 = NULL; //!< OpenSSL specific datastructure to contain keypair to be stored at index 1
static EC_KEY *eccKeyRootCA_0 = NULL; //!< OpenSSL specific datastructure to contain keypair, whose public key is stored at index 0
static EC_KEY *eccKeyRootCA_1 = NULL; //!< OpenSSL specific datastructure to contain keypair, whose public key is stored at index 1
static eccKeyComponents_t eccKcTls_0; //!< Crypto library independent datastructure to contain keypair to be stored at index 0
static eccKeyComponents_t eccKcTls_1; //!< Crypto library independent datastructure to contain keypair to be stored at index 1
static eccKeyComponents_t eccKcRootCA_0; //!< Crypto library independent datastructure to contain keypair, whose public key is stored at index 0
static eccKeyComponents_t eccKcRootCA_1; //!< Crypto library independent datastructure to contain keypair, whose public key is stored at index 1
/// @cond
static U8 aesBasePattern[] = { 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
static U16 aesBasePatternLen = sizeof(aesBasePattern);
static U8 aesRef[A71CH_SYM_KEY_MAX][16];
/// @endcond
static void signalFunctionCallback(HLSE_SCP_EVENT /*ScpEvent_t*/ event, void *context)
{
AX_UNUSED_ARG(context);
PRINTF("scpCallback: ");
switch (event)
{
case SCP_WRONG_PADDING:
PRINTF("Wrong padding\r\n");
break;
case SCP_WRONG_RESPMAC:
PRINTF("Wrong response mac\r\n");
break;
case SCP_GENERIC_FAILURE:
PRINTF("Non specified failure\r\n");
break;
default:
PRINTF("Unknown event type\r\n");
break;
}
return;
}
/**
* This example illustrates setting up an SCP03 channel between Host and A71CH.
* It is assumed the HostOS may select and set the SCP03 Base Keys.
*
* - Set an initial SCP03 base key set (based on random data retrieved from the SM (Secure Module))
* - Establish an SCP03 session
* - Provision the A71CH with some key material
* - Demonstrate crypto operations using the provisioned key material
*/
U8 exHlseScp()
{
U8 result = 1;
U8 initMode = INIT_MODE_RESET;
U8 scpKeyEncBase[SCP_KEY_SIZE];
U8 scpKeyMacBase[SCP_KEY_SIZE];
U8 scpKeyDekBase[SCP_KEY_SIZE];
PRINTF("\r\n-----------\r\nStart exScp(%s)\r\n------------\r\n", getInitModeAsString(initMode));
// Installing Callback (this step is optional)
#if 0
SCP_Subscribe(signalFunctionCallback, NULL);
#else
HLSE_SCP_Subscribe(signalFunctionCallback, NULL);
#endif
DEV_ClearChannelState();
sm_printf(CONSOLE, "Reset Secure Module\r\n");
result &= hlse_a71chInitModule(initMode);
// STAGE-0:
// - The A71CH has been forced into the initial state through the DBG Interface
// NOTE: The function DBG_RESET is not available in production samples
result &= axExHlseCreateAndSetInitialHostScpKeys(scpKeyEncBase, scpKeyMacBase, scpKeyDekBase);
result &= exAuthenticate(scpKeyEncBase, scpKeyMacBase, scpKeyDekBase);
// STAGE-1:
// - A set of KNOWN (but randon) HOST Static keys has been injected into the SM
// - The SCP03 session has been established
result &= exProvision();
result &= exUseCrypto();
assert(result);
HOSTCRYPTO_FreeEccKey(&eccKeyTls_0);
HOSTCRYPTO_FreeEccKey(&eccKeyTls_1);
HOSTCRYPTO_FreeEccKey(&eccKeyRootCA_0);
HOSTCRYPTO_FreeEccKey(&eccKeyRootCA_1);
// overall result
PRINTF("\r\n-----------\r\nEnd exScp(%s), result = %s\r\n------------\r\n",
getInitModeAsString(initMode), ((result == 1)? "OK": "FAILED"));
return result;
}
/**
* The host locally stores the SCP03 base keys passed as parameters and establishes
* the authenticated and encrypted SCP03 channel with the A71CH.
*
* @param[in] keyEnc IN: SCP03 base key
* @param[in] keyMac IN: SCP03 base key
* @param[in] keyDek IN: SCP03 base key
*/
static U8 exAuthenticate(U8 *keyEnc, U8 *keyMac, U8 *keyDek)
{
U8 sCounter[3];
U16 sCounterLen = sizeof(sCounter);
U8 result = 1;
U16 err = 0;
PRINTF( "\r\n-----------\r\nStart exAuthenticate()\r\n------------\r\n");
// Authenticate Channel
sm_printf(CONSOLE, "\r\nSCP_Authenticate()\r\n");
#if 0
err = SCP_Authenticate(keyEnc, keyMac, keyDek, SCP_KEY_SIZE, sCounter, &sCounterLen);
#else
err = hlse_SCP_Authenticate(keyEnc, keyMac, keyDek, SCP_KEY_SIZE, sCounter, &sCounterLen);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
result &= AX_CHECK_U16(sCounterLen, 0, "Only expected when SCP03 is configured for pseudo-random challenge");
PRINTF( "\r\n-----------\r\nEnd exAuthenticate(), result = %s\r\n------------\r\n",
((result == 1)? "OK": "FAILED"));
return result;
}
/**
* Demonstrate crypto operations using the provisioned key material
*/
static U8 exUseCrypto()
{
U8 result = 1;
U16 err;
SST_Index_t index;
HLSE_RET_CODE retcode;
int nRet = 0;
U8 isOk = 0;
HLSE_MECHANISM_INFO mechInfo;
U8 preCookedSha256[] = {
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0,
0x01, 0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, 0xa1, 0xb1, 0xc1, 0xd1, 0xe1, 0xf1
};
U8 dataToSign[] = {
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x22, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x33, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x44, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x55, 0x11, 0x11, 0x11
};
U8 dataToSignLen = 68;
U8 hashSha256[32];
U16 hashSha256Len = sizeof(hashSha256);
U8 signature[256];
U16 signatureLen = sizeof(signature);
U8 signatureOnHost[256];
U16 signatureOnHostLen = sizeof(signatureOnHost);
U32 nSigLen = 0;
U8 sharedSecretOnA71CH[32] = { 0 };
U16 sharedSecretOnA71CHLen = 0;
U8 sharedSecretOnHost[32] = { 0 };
U16 sharedSecretOnHostLen = 0;
U16 expectedSharedSecretLen = sizeof(sharedSecretOnHost);
PRINTF( "\r\n-----------\r\nStart exUseCrypto()\r\n------------\r\n");
// Sign a precooked hash on the A71CH with the first key pair, do the subsequent verification on the Host
index = A71CH_KEY_PAIR_0;
PRINTF("\r\nA71_EccSign(0x%02x) on A71CH.\r\n", index);
signatureLen = sizeof(signature);
#if 1
err = A71_EccSign(index, preCookedSha256, sizeof(preCookedSha256), signature, &signatureLen);
#else
// TODO HLSE - when available
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
axPrintByteArray("signature", signature, signatureLen, AX_COLON_32);
// .. Verify on host
PRINTF("\r\n(Host)ECDSA_verify API with eccKeyTls_0 (verify signature created on A71CH).\r\n");
memset(&mechInfo, 0, sizeof(mechInfo));
mechInfo.mechanism = HLSE_ECDSA_VERIFY;
retcode = HLCRYPT_Verify(&mechInfo,(U8 *)eccKeyTls_0,0,preCookedSha256,sizeof(preCookedSha256),signature,signatureLen);
if (retcode == HLSE_SW_OK)
{
PRINTF("Verification OK for eccKeyTls_0.\r\n");
}
else
{
PRINTF("Return value: %d, Verification Not OK for eccKeyTls_0. Test Failed!\r\n", retcode);
result &= 0;
}
// On the Host calculate SHA256 on data, sign the hash using eccKcRootCA_0.
hashSha256Len = sizeof(hashSha256);
nRet = HOST_SHA256_Get(dataToSign, dataToSignLen, hashSha256);
assert(nRet == HOST_CRYPTO_OK);
if ( nRet != HOST_CRYPTO_OK)
result &= 0;
PRINTF("\r\n(Host)ECDSA_sign API with eccKeyRootCA_0.\r\n");
memset(&mechInfo, 0, sizeof(mechInfo));
mechInfo.mechanism = HLSE_ECDSA_SIGN;
retcode = HLCRYPT_Sign(&mechInfo, (U8 *)eccKeyRootCA_0, 0,hashSha256, hashSha256Len, signatureOnHost, &nSigLen);
PRINTF("ECDSA_sign returned: 0x%02X, Siglen is %ld\r\n", retcode, nSigLen);
if (retcode != HLSE_SW_OK)
{
PRINTF("ECDSA_sign operation failed.\r\n");
result &= 0;
}
else
{
axPrintByteArray("signatureOnHost", signatureOnHost, (U16)nSigLen, AX_COLON_32);
}
// ... do the subsequent verification on the A71CH with the matching public key object.
index = A71CH_PUBLIC_KEY_0;
PRINTF("\r\nA71_EccVerify(0x%02x) on A71CH.\r\n", index);
signatureOnHostLen = (U16)nSigLen;
isOk = 0x00;
#if 0
err = A71_EccVerify(index, hashSha256, sizeof(hashSha256), signatureOnHost, signatureOnHostLen, &isOk);
#else
err = hlse_EccVerify(index, hashSha256, sizeof(hashSha256), signatureOnHost, signatureOnHostLen, &isOk);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
result &= AX_CHECK_U8(isOk, 1, "Signature verification failed");
if (isOk == 1)
{
PRINTF("Verification on A71CH is OK, Test Passed\r\n");
}
// NEGATIVE TEST: verifying the signature with the wrong public key must fail
index = A71CH_PUBLIC_KEY_1;
PRINTF("\r\nA71_EccVerify(0x%02x) on A71CH.\r\n", index);
signatureOnHostLen = (U16)nSigLen;
isOk = 0x00;
#if 0
err = A71_EccVerify(index, hashSha256, sizeof(hashSha256), signatureOnHost, signatureOnHostLen, &isOk);
result &= AX_CHECK_SW(err, SW_OK, "err");
#else
err = hlse_EccVerify(index, hashSha256, sizeof(hashSha256), signatureOnHost, signatureOnHostLen, &isOk);
result &= AX_CHECK_SW(err, HLSE_ERR_GENERAL_ERROR, "err");
#endif
result &= AX_CHECK_U8(isOk, 0, "Negative test: Signature verification must fail");
if (isOk == 0)
{
PRINTF("Negative Test Passed\r\n");
}
// Create and compare a shared secret on A71CH and Host
// The Second ECC key pair was already set, now use it in combination with
// eccKeyTls_0 to create a shared secret
index = A71CH_KEY_PAIR_1;
PRINTF("\r\nA71_EcdhGetSharedSecret(0x%02x) on A71CH\r\n", index);
sharedSecretOnA71CHLen = sizeof(sharedSecretOnA71CH);
#if 0
err = A71_EcdhGetSharedSecret(index, eccKcTls_0.pub, eccKcTls_0.pubLen, sharedSecretOnA71CH, &sharedSecretOnA71CHLen);
#else
{
HLSE_OBJECT_HANDLE handles[A71CH_KEY_PAIR_MAX] = {0};
U16 handleNum = A71CH_KEY_PAIR_MAX;
err = HLSE_EnumerateObjects(HLSE_KEY_PAIR, handles, &handleNum);
result &= AX_CHECK_SW(err, HLSE_SW_OK, "err");
err = hlse_EcdhGetSharedSecret(handles[index], eccKcTls_0.pub, eccKcTls_0.pubLen, sharedSecretOnA71CH, &sharedSecretOnA71CHLen);
}
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
axPrintByteArray("sharedSecretOnA71CH", sharedSecretOnA71CH, sharedSecretOnA71CHLen, AX_COLON_32);
PRINTF("\r\nA71_EcdhGetSharedSecret() on Host\r\n");
sharedSecretOnHostLen = sizeof(sharedSecretOnHost);
err = HOSTCRYPTO_ECC_ComputeSharedSecret(eccKeyTls_0, eccKcTls_1.pub, eccKcTls_1.pubLen, sharedSecretOnHost, &sharedSecretOnHostLen);
result &= AX_CHECK_SW(err, SW_OK, "err");
axPrintByteArray("sharedSecretOnHost", sharedSecretOnHost, sharedSecretOnHostLen, AX_COLON_32);
if (memcmp(sharedSecretOnA71CH, sharedSecretOnHost, expectedSharedSecretLen) != 0)
{
PRINTF("Shared secret calculated on A71CH does not match with shared secret calculated on Host.\r\n");
result &= 0;
}
PRINTF( "\r\n-----------\r\nEnd exUseCrypto(), result = %s\r\n------------\r\n",
((result == 1)? "OK": "FAILED"));
return result;
}
/**
* Provision the A71CH with some key material
*/
static U8 exProvision()
{
U8 result = 1;
U16 err = 0;
ECCCurve_t eccCurve = ECCCurve_NIST_P256;
U8 pubTlsKey[256] = { 0 };
U16 pubTlsKeyLen = 0;
int indexAesKey = 0;
const U16 expectedPubKeyLen = 65;
const U16 expectedPrivKeyLen = 32;
SST_Index_t index;
HLSE_OBJECT_HANDLE keyPairHandles[2];
HLSE_OBJECT_HANDLE pubkeyHandles[2];
HLSE_OBJECT_HANDLE aesKeyHandles[A71CH_SYM_KEY_MAX] = {0};
PRINTF( "\r\n-----------\r\nStart exProvision()\r\n------------\r\n");
err = HOSTCRYPTO_GenerateEccKey(eccCurve, &eccKeyTls_0);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GenerateEccKey(eccCurve, &eccKeyTls_1);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GenerateEccKey(eccCurve, &eccKeyRootCA_0);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GenerateEccKey(eccCurve, &eccKeyRootCA_1);
result &= AX_CHECK_SW(err, SW_OK, "err");
// Break down the ECC keys generated in OpenSSL into eccKeyComponents
err = HOSTCRYPTO_GetPublicKey(eccKeyTls_0, eccKcTls_0.pub, &(eccKcTls_0.pubLen), (64 << 1)+1);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GetPrivateKey(eccKeyTls_0, eccKcTls_0.priv, &(eccKcTls_0.privLen), 64);
result &= AX_CHECK_SW(err, SW_OK, "err");
eccKcTls_0.bits = expectedPrivKeyLen << 3;
eccKcTls_0.curve = eccCurve;
err = HOSTCRYPTO_GetPublicKey(eccKeyTls_1, eccKcTls_1.pub, &(eccKcTls_1.pubLen), (64 << 1)+1);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GetPrivateKey(eccKeyTls_1, eccKcTls_1.priv, &(eccKcTls_1.privLen), 64);
result &= AX_CHECK_SW(err, SW_OK, "err");
eccKcTls_1.bits = expectedPrivKeyLen << 3;
eccKcTls_1.curve = eccCurve;
err = HOSTCRYPTO_GetPublicKey(eccKeyRootCA_0, eccKcRootCA_0.pub, &(eccKcRootCA_0.pubLen), (64 << 1)+1);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GetPrivateKey(eccKeyRootCA_0, eccKcRootCA_0.priv, &(eccKcRootCA_0.privLen), 64);
result &= AX_CHECK_SW(err, SW_OK, "err");
eccKcRootCA_0.bits = expectedPrivKeyLen << 3;
eccKcRootCA_0.curve = eccCurve;
err = HOSTCRYPTO_GetPublicKey(eccKeyRootCA_1, eccKcRootCA_1.pub, &(eccKcRootCA_1.pubLen), (64 << 1)+1);
result &= AX_CHECK_SW(err, SW_OK, "err");
err = HOSTCRYPTO_GetPrivateKey(eccKeyRootCA_1, eccKcRootCA_1.priv, &(eccKcRootCA_1.privLen), 64);
result &= AX_CHECK_SW(err, SW_OK, "err");
eccKcRootCA_1.bits = expectedPrivKeyLen << 3;
eccKcRootCA_1.curve = eccCurve;
// Set first ECC keyPair with eccKcTls_0 (Key pair created on Host)
index = A71CH_KEY_PAIR_0;
PRINTF("\r\nA71_SetEccKeyPair(0x%02x)\r\n", index);
#if 0
err = A71_SetEccKeyPair(index, eccKcTls_0.pub, eccKcTls_0.pubLen, eccKcTls_0.priv, eccKcTls_0.privLen);
#else
err = hlse_SetEccKeyPair(index, eccKcTls_0.pub, eccKcTls_0.pubLen, eccKcTls_0.priv, eccKcTls_0.privLen,
&keyPairHandles[index]);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
// Set second ECC keyPair with eccKcTls_1 (Key pair created on Host)
index = A71CH_KEY_PAIR_1;
PRINTF("\r\nA71_SetEccKeyPair(0x%02x)\r\n", index);
#if 0
err = A71_SetEccKeyPair(index, eccKcTls_1.pub, eccKcTls_1.pubLen, eccKcTls_1.priv, eccKcTls_1.privLen);
#else
err = hlse_SetEccKeyPair(index, eccKcTls_1.pub, eccKcTls_1.pubLen, eccKcTls_1.priv, eccKcTls_1.privLen,
&keyPairHandles[index]);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
// Both ECC Key pairs have been set, compare the public key of the first key pair with reference value
index = A71CH_KEY_PAIR_0;
PRINTF( "\r\nSST_GetPublicKeyECCKeyPair(0x%02x)\r\n", index);
pubTlsKeyLen = sizeof(pubTlsKey);
#if 0
err = A71_GetPublicKeyEccKeyPair(index, pubTlsKey, &pubTlsKeyLen);
#else
err = hlse_GetEccPublicKey(pubTlsKey, &pubTlsKeyLen, &keyPairHandles[index]);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
result &= AX_CHECK_U16(pubTlsKeyLen, expectedPubKeyLen, "pubTlsKeyLength");
// Compare retrieved value with reference value
if (memcmp(pubTlsKey, eccKcTls_0.pub, expectedPubKeyLen) != 0)
{
PRINTF("Retrieved Public key does not match reference value.\r\n");
result &= 0;
}
// First set the matching public key on the A71CH (index=0)
index = A71CH_PUBLIC_KEY_0;
PRINTF("\r\nA71_SetEccPublicKey(0x%02x)\r\n", index);
#if 0
err = A71_SetEccPublicKey(index, eccKcRootCA_0.pub, eccKcRootCA_0.pubLen);
#else
err = hlse_SetEccPublicKey(index, eccKcRootCA_0.pub, eccKcRootCA_0.pubLen, &pubkeyHandles[index]);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
// Set the public key part of eccKcRootCA_1 on the A71CH (index=1)
index = A71CH_PUBLIC_KEY_1;
PRINTF("\r\nA71_SetEccPublicKey(0x%02x)\r\n", index);
#if 0
err = A71_SetEccPublicKey(index, eccKcRootCA_1.pub, eccKcRootCA_1.pubLen);
#else
err = hlse_SetEccPublicKey(index, eccKcRootCA_1.pub, eccKcRootCA_1.pubLen, &pubkeyHandles[index]);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
// Fill the SYM key store with A71CH_SYM_KEY_MAX keys (128 bits long)
// The keys being written are based upon a Base pattern, with the first key written having a 0x00 byte
// in position 0, the second key written a 0x00 byte in position 1 and so on
// Base Pattern: 0xF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
// First Key : 0x00F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
// Second Key : 0xF000F2F3F4F5F6F7F8F9FAFBFCFDFEFF
// ..
for (indexAesKey=0; indexAesKey<A71CH_SYM_KEY_MAX; indexAesKey++)
{
memcpy(aesRef[indexAesKey], aesBasePattern, aesBasePatternLen);
// Put the 0x00 marker
aesRef[indexAesKey][indexAesKey] = (U8)0x00;
// Write the key (unwrapped)
PRINTF( "\r\nA71_SetSymKey(0x%02x)\r\n", indexAesKey);
#if 0
err = A71_SetSymKey((SST_Index_t)indexAesKey, aesRef[indexAesKey], sizeof(aesRef[indexAesKey]));
#else
err = hlse_SetSymKey((SST_Index_t)indexAesKey, aesRef[indexAesKey], sizeof(aesRef[indexAesKey]), &aesKeyHandles[indexAesKey]);
#endif
result &= AX_CHECK_SW(err, SW_OK, "err");
axPrintByteArray("aesRef[indexAesKey]", aesRef[indexAesKey], sizeof(aesRef[indexAesKey]), AX_COLON_32);
}
// NOTE: Reading out symmetric keys is not supported
PRINTF( "\r\n-----------\r\nEnd exProvision(), result = %s\r\n------------\r\n",
((result == 1)? "OK": "FAILED"));
return result;
}