diff --git a/app/pkcs11/example_pkcs11_config.c b/app/pkcs11/example_pkcs11_config.c index b6b744ed2..91b26b762 100644 --- a/app/pkcs11/example_pkcs11_config.c +++ b/app/pkcs11/example_pkcs11_config.c @@ -135,7 +135,7 @@ CK_RV pkcs11_config_load_objects(pkcs11_slot_ctx_ptr pSlot) xLabel.pValue = pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS; xLabel.ulValueLen = strlen(xLabel.pValue); xLabel.type = CKA_LABEL; - pkcs11_config_key(NULL, pSlot, pObject, &xLabel); + rv = pkcs11_config_key(NULL, pSlot, pObject, &xLabel); } } @@ -148,7 +148,7 @@ CK_RV pkcs11_config_load_objects(pkcs11_slot_ctx_ptr pSlot) xLabel.pValue = pkcs11configLABEL_DEVICE_PUBLIC_KEY_FOR_TLS; xLabel.ulValueLen = strlen(xLabel.pValue); xLabel.type = CKA_LABEL; - pkcs11_config_key(NULL, pSlot, pObject, &xLabel); + rv = pkcs11_config_key(NULL, pSlot, pObject, &xLabel); } } diff --git a/app/pkcs11/slot.conf.tmpl b/app/pkcs11/slot.conf.tmpl index b637abd65..9549d015a 100644 --- a/app/pkcs11/slot.conf.tmpl +++ b/app/pkcs11/slot.conf.tmpl @@ -3,17 +3,26 @@ # These are processed in order. Configuration parameters must be comma # delimited and may not contain spaces -interface = i2c,0xB0 -freeslots = 1,2,3 +# Set a label for this slot (optional) - will default to ABC so +# 0.conf will have a default label 00ABC +#label = MCHP -# Slot 0 is the primary private key -object = private,device,0 +# Configure the device interface for an enabled HAL +# hid,i2c,
+# i2c,
, +# spi,, +interface = hid,i2c,0x6c + +# Configure the device type - base part number (optional) +device = ATECC608A-TFLXTLS -# Slot 10 is the certificate data for the device's public key -#object = certificate,device,10 +#Configure open slots for additional pkcs11 objects (optional) +#freeslots = 1,2,3 -# Slot 12 is the intermedate/signer certificate data -#object = certificate,signer,12 +# Manually configure keys into device locations (slots/handles) + +# Slot 0 is the primary private key +#object = private,device,0 # Slot 15 is a public key -object = public,root,15 +#object = public,root,15 diff --git a/harmony/config/pkcs11.py b/harmony/config/pkcs11.py index d8c33456e..c30d55b7a 100644 --- a/harmony/config/pkcs11.py +++ b/harmony/config/pkcs11.py @@ -91,6 +91,10 @@ def instantiateComponent(calPkcs11Component): calPkcs11DebugPrint.setLabel("Enable Debug Print?") calPkcs11DebugPrint.setDefaultValue(False) + calPkcs11AwsFreeRTOS = calPkcs11Component.createBooleanSymbol("CAL_PKCS11_AWS_FREERTOS", None) + calPkcs11AwsFreeRTOS.setLabel("Enable AWS FreeRTOS Modifications?") + calPkcs11AwsFreeRTOS.setDefaultValue(False) + calPkcs11MaxSlots = calPkcs11Component.createIntegerSymbol('CAL_PKCS11_MAX_SLOTS', None) calPkcs11MaxSlots.setLabel('Maximum number of PKCS11 slots') calPkcs11MaxSlots.setDefaultValue(1) diff --git a/harmony/templates/pkcs11_config.h.ftl b/harmony/templates/pkcs11_config.h.ftl index 132789542..86c08c63c 100644 --- a/harmony/templates/pkcs11_config.h.ftl +++ b/harmony/templates/pkcs11_config.h.ftl @@ -43,6 +43,10 @@ #define PKCS11_DEBUG_ENABLE ${CAL_PKCS11_ENABLE_DEBUG_PRINT?then(1,0)} #endif +<#if CAL_PKCS11_AWS_FREERTOS> +#define PKCS11_LABEL_IS_SERNUM 1 + + /** Use a compiled configuration rather than loading from a filestore */ #ifndef PKCS11_USE_STATIC_CONFIG #define PKCS11_USE_STATIC_CONFIG 1 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 8139a8128..7299aac2f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -65,6 +65,7 @@ if(ATCA_PKCS11 AND (ATCA_TNGTLS_SUPPORT OR ATCA_TNGLORA_SUPPORT OR ATCA_TFLEX_SU SET(TNG_SRC ${TNG_SRC} ../app/pkcs11/trust_pkcs11_config.c) endif() +if(${CMAKE_VERSION} VERSION_GREATER "3.8.0") source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${ATCACERT_SRC}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CALIB_SRC}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${TALIB_SRC}) @@ -73,6 +74,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${HOST_SRC}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${JWT_SRC}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${PKCS11_SRC}) source_group("App/Tng" FILES ${TNG_SRC}) +endif() if (ATCA_MBEDTLS) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../third_party/CMakeLists-mbedtls.txt.in ${CMAKE_BINARY_DIR}/${DEPENDENCY_DIR}/mbedtls_downloader/CMakeLists.txt) @@ -89,7 +91,9 @@ include_directories(mbedtls PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} file(GLOB MBEDTLS_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "mbedtls/*.c") +if(${CMAKE_VERSION} VERSION_GREATER "3.8.0") source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${MBEDTLS_SRC}) +endif() endif(ATCA_MBEDTLS) if (ATCA_WOLFSSL) @@ -116,12 +120,16 @@ include_directories(wolfssl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} file(GLOB WOLFSSL_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "wolfssl/*.c") +if(${CMAKE_VERSION} VERSION_GREATER "3.8.0") source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${WOLFSSL_SRC}) +endif() endif(ATCA_WOLFSSL) if (ATCA_OPENSSL) file(GLOB OPENSSL_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "openssl/*.c") +if(${CMAKE_VERSION} VERSION_GREATER "3.8.0") source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OPENSSL_SRC}) +endif() find_package(OpenSSL REQUIRED) endif(ATCA_OPENSSL) @@ -239,7 +247,6 @@ if(ATCA_BUILD_SHARED_LIBS) add_definitions(-DATCA_BUILD_SHARED_LIBS) endif(ATCA_BUILD_SHARED_LIBS) -#source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CRYPTOAUTH_SRC}) add_library(cryptoauth ${CRYPTOAUTH_SRC} ${ATCACERT_DEF_SRC}) set_property(TARGET cryptoauth PROPERTY C_STANDARD 99) diff --git a/lib/atca_basic.c b/lib/atca_basic.c index 5bbfc1a99..d8120ab89 100644 --- a/lib/atca_basic.c +++ b/lib/atca_basic.c @@ -78,7 +78,7 @@ ATCA_STATUS atcab_init_ext(ATCADevice* device, ATCAIfaceCfg *cfg) // If a device has already been initialized, release it if (*device) { - atcab_release(); + atcab_release_ext(device); } #ifdef ATCA_NO_HEAP @@ -165,12 +165,12 @@ ATCA_STATUS atcab_init_device(ATCADevice ca_device) ATCA_STATUS atcab_release_ext(ATCADevice* device) { #ifdef ATCA_NO_HEAP - ATCA_STATUS status = releaseATCADevice(_gDevice); + ATCA_STATUS status = releaseATCADevice(*device); if (status != ATCA_SUCCESS) { return status; } - _gDevice = NULL; + *device = NULL; #else deleteATCADevice(device); #endif diff --git a/lib/atca_compiler.h b/lib/atca_compiler.h index c1890fbc5..2062b09a7 100644 --- a/lib/atca_compiler.h +++ b/lib/atca_compiler.h @@ -51,7 +51,7 @@ #define ATCA_UINT64_BE_TO_HOST(x) __builtin_bswap64(x) #endif -#ifdef (WIN32) +#ifdef WIN32 #define SHARED_LIB_EXPORT __declspec(dllexport) #define SHARED_LIB_IMPORT __declspec(dllimport) #else diff --git a/lib/calib/calib_aes.c b/lib/calib/calib_aes.c index fbd283565..f973f5029 100644 --- a/lib/calib/calib_aes.c +++ b/lib/calib/calib_aes.c @@ -49,7 +49,7 @@ ATCA_STATUS calib_aes(ATCADevice device, uint8_t mode, uint16_t key_id, const uint8_t* aes_in, uint8_t* aes_out) { ATCAPacket packet; - ATCACommand ca_cmd = _gDevice->mCommands; + ATCACommand ca_cmd = device->mCommands; ATCA_STATUS status = ATCA_GEN_FAIL; do diff --git a/lib/calib/calib_basic.c b/lib/calib/calib_basic.c index 3999bd520..ebdb17ab1 100644 --- a/lib/calib/calib_basic.c +++ b/lib/calib/calib_basic.c @@ -254,7 +254,7 @@ ATCA_STATUS calib_get_zone_size(ATCADevice device, uint8_t zone, uint16_t slot, default: status = ATCA_BAD_PARAM; break; } } - else if (_gDevice->mIface->mIfaceCFG->devtype == ATSHA206A) + else if (device->mIface->mIfaceCFG->devtype == ATSHA206A) { switch (zone) { diff --git a/lib/calib/calib_basic.h b/lib/calib/calib_basic.h index bf50cf56b..1cf67bd11 100644 --- a/lib/calib/calib_basic.h +++ b/lib/calib/calib_basic.h @@ -11,6 +11,10 @@ * @{ */ +#ifdef __cplusplus +extern "C" { +#endif + ATCA_STATUS calib_wakeup(ATCADevice device); ATCA_STATUS calib_idle(ATCADevice device); ATCA_STATUS calib_sleep(ATCADevice device); @@ -183,6 +187,10 @@ ATCA_STATUS calib_write_enc(ATCADevice device, uint16_t key_id, uint8_t block, c ATCA_STATUS calib_write_config_counter(ATCADevice device, uint16_t counter_id, uint32_t counter_value); +#ifdef __cplusplus +} +#endif + /** @} */ #endif \ No newline at end of file diff --git a/lib/calib/calib_read.c b/lib/calib/calib_read.c index ee698cbef..6131edf1e 100644 --- a/lib/calib/calib_read.c +++ b/lib/calib/calib_read.c @@ -361,7 +361,7 @@ ATCA_STATUS calib_read_config_zone(ATCADevice device, uint8_t* config_data) break; } - if ((_gDevice->mIface->mIfaceCFG->devtype == ATSHA204A) || (_gDevice->mIface->mIfaceCFG->devtype == ATSHA206A)) + if ((device->mIface->mIfaceCFG->devtype == ATSHA204A) || (device->mIface->mIfaceCFG->devtype == ATSHA206A)) { status = calib_read_bytes_zone(device, ATCA_ZONE_CONFIG, 0, 0x00, config_data, ATCA_SHA_CONFIG_SIZE); } @@ -432,7 +432,7 @@ ATCA_STATUS calib_cmp_config_zone(ATCADevice device, uint8_t* config_data, bool* break; } - if (_gDevice->mIface->mIfaceCFG->devtype == ATECC608A) + if (device->mIface->mIfaceCFG->devtype == ATECC608A) { /* Skip Counter[0], Counter[1], which can change during operation */ diff --git a/lib/calib/calib_sha.c b/lib/calib/calib_sha.c index 906523f0c..021e131f4 100644 --- a/lib/calib/calib_sha.c +++ b/lib/calib/calib_sha.c @@ -286,7 +286,7 @@ ATCA_STATUS calib_hw_sha2_256_finish(ATCADevice device, atca_sha256_ctx_t* ctx, uint32_t pad_zero_count; uint16_t digest_size; - if (_gDevice->mIface->mIfaceCFG->devtype == ATSHA204A) + if (device->mIface->mIfaceCFG->devtype == ATSHA204A) { // ATSHA204A only implements the raw 64-byte block operation, but // doesn't add in the final footer information. So we do that manually @@ -456,7 +456,7 @@ ATCA_STATUS calib_sha_hmac_finish(ATCADevice device, atca_hmac_sha256_ctx_t *ctx uint8_t mode = SHA_MODE_HMAC_END; uint16_t digest_size = 32; - if (ATECC608A == _gDevice->mIface->mIfaceCFG->devtype) + if (ATECC608A == device->mIface->mIfaceCFG->devtype) { mode = SHA_MODE_608_HMAC_END; } diff --git a/lib/calib/calib_sign.c b/lib/calib/calib_sign.c index 17b6284b6..db7f25aa4 100644 --- a/lib/calib/calib_sign.c +++ b/lib/calib/calib_sign.c @@ -122,7 +122,7 @@ ATCA_STATUS calib_sign(ATCADevice device, uint16_t key_id, const uint8_t *msg, u } // Load message into device - if (_gDevice->mCommands->dt == ATECC608A) + if (device->mCommands->dt == ATECC608A) { // Use the Message Digest Buffer for the ATECC608A nonce_target = NONCE_MODE_TARGET_MSGDIGBUF; diff --git a/lib/calib/calib_verify.c b/lib/calib/calib_verify.c index bc275ebc5..b5b4e70b3 100644 --- a/lib/calib/calib_verify.c +++ b/lib/calib/calib_verify.c @@ -258,7 +258,7 @@ ATCA_STATUS calib_verify_extern(ATCADevice device, const uint8_t *message, const do { // Load message into device - if (_gDevice->mCommands->dt == ATECC608A) + if (device->mCommands->dt == ATECC608A) { // Use the Message Digest Buffer for the ATECC608A nonce_target = NONCE_MODE_TARGET_MSGDIGBUF; @@ -348,7 +348,7 @@ ATCA_STATUS calib_verify_stored(ATCADevice device, const uint8_t *message, const do { // Load message into device - if (_gDevice->mCommands->dt == ATECC608A) + if (device->mCommands->dt == ATECC608A) { // Use the Message Digest Buffer for the ATECC608A nonce_target = NONCE_MODE_TARGET_MSGDIGBUF; diff --git a/lib/mbedtls/atca_mbedtls_wrap.c b/lib/mbedtls/atca_mbedtls_wrap.c index 6e234c3c3..4576c6056 100644 --- a/lib/mbedtls/atca_mbedtls_wrap.c +++ b/lib/mbedtls/atca_mbedtls_wrap.c @@ -638,7 +638,8 @@ int atca_mbedtls_cert_add(mbedtls_x509_crt * cert, const atcacert_def_t * cert_d } } - if (NULL == (cert_buf = mbedtls_calloc(1, cert_def->cert_template_size + 8))) + cert_len = cert_def->cert_template_size + 8; + if (NULL == (cert_buf = mbedtls_calloc(1, cert_len))) { ret = -1; } diff --git a/lib/pkcs11/pkcs11_config.c b/lib/pkcs11/pkcs11_config.c index 3eadcaeda..84495af35 100644 --- a/lib/pkcs11/pkcs11_config.c +++ b/lib/pkcs11/pkcs11_config.c @@ -351,6 +351,26 @@ static CK_RV pkcs11_config_parse_interface(pkcs11_slot_ctx_ptr slot_ctx, char* c return rv; } +#ifndef PKCS11_LABEL_IS_SERNUM +static CK_RV pkcs11_config_parse_label(pkcs11_slot_ctx_ptr slot_ctx, char* cfgstr) +{ + CK_RV rv = CKR_OK; + size_t len = strlen(cfgstr); + + if (len && (len < PKCS11_MAX_LABEL_SIZE)) + { + memcpy(slot_ctx->label, cfgstr, len); + slot_ctx->label[PKCS11_MAX_LABEL_SIZE] = 0; + } + else + { + rv = CKR_ARGUMENTS_BAD; + } + + return rv; +} +#endif + static CK_RV pkcs11_config_parse_freeslots(pkcs11_slot_ctx_ptr slot_ctx, char* cfgstr) { int argc = 16; @@ -486,6 +506,12 @@ static CK_RV pkcs11_config_parse_slot_file(pkcs11_slot_ctx_ptr slot_ctx, int arg { rv = pkcs11_config_parse_interface(slot_ctx, argv[i + 1]); } +#ifndef PKCS11_LABEL_IS_SERNUM + else if (!strcmp(argv[i], "label")) + { + rv = pkcs11_config_parse_label(slot_ctx, argv[i + 1]); + } +#endif else if (!strcmp(argv[i], "freeslots")) { rv = pkcs11_config_parse_freeslots(slot_ctx, argv[i + 1]); @@ -578,6 +604,7 @@ CK_RV pkcs11_config_key(pkcs11_lib_ctx_ptr pLibCtx, pkcs11_slot_ctx_ptr pSlot, p char *objtype = ""; char filename[200]; int i; + CK_RV rv = CKR_FUNCTION_FAILED; /* Find a free slot that matches the object type */ @@ -626,10 +653,12 @@ CK_RV pkcs11_config_key(pkcs11_lib_ctx_ptr pLibCtx, pkcs11_slot_ctx_ptr pSlot, p fprintf(fp, "type = %s\n", objtype); fprintf(fp, "label = %s\n", pObject->name); fclose(fp); + rv = CKR_OK; } } } - return CKR_OK; + + return rv; } CK_RV pkcs11_config_remove_object(pkcs11_lib_ctx_ptr pLibCtx, pkcs11_slot_ctx_ptr pSlot, pkcs11_object_ptr pObject) @@ -642,6 +671,7 @@ CK_RV pkcs11_config_remove_object(pkcs11_lib_ctx_ptr pLibCtx, pkcs11_slot_ctx_pt if (ret > 0 && ret < sizeof(filename)) { remove(filename); + pSlot->flags |= (1 << pObject->slot); } return CKR_OK; @@ -723,6 +753,16 @@ CK_RV pkcs11_config_load_objects(pkcs11_slot_ctx_ptr slot_ctx) { PKCS11_DEBUG("Failed to parse the slot configuration file"); } +#ifndef PKCS11_LABEL_IS_SERNUM + if (CKR_OK == rv) + { + /* If a label wasn't set - configure a default */ + if (!slot_ctx->label[0]) + { + snprintf((char*)slot_ctx->label, sizeof(slot_ctx->label)-1, "%02XABC", (uint8_t)i); + } + } +#endif pkcs11_os_free(buffer); } } diff --git a/lib/pkcs11/pkcs11_object.c b/lib/pkcs11/pkcs11_object.c index a3b3ddc16..12cc361ea 100644 --- a/lib/pkcs11/pkcs11_object.c +++ b/lib/pkcs11/pkcs11_object.c @@ -413,7 +413,7 @@ CK_RV pkcs11_object_create ) { CK_RV rv; - pkcs11_object_ptr pObject; + pkcs11_object_ptr pObject = NULL; CK_ATTRIBUTE_PTR pLabel = NULL; CK_OBJECT_CLASS_PTR pClass = NULL; CK_ATTRIBUTE_PTR pData = NULL; @@ -456,7 +456,12 @@ CK_RV pkcs11_object_create rv = pkcs11_object_find(&pObject, pTemplate, ulCount); - if (rv) + if (rv) + { + return rv; + } + + if (!pObject) { /* Allocate a new object */ rv = pkcs11_object_alloc(&pObject); diff --git a/lib/pkcs11/pkcs11_slot.h b/lib/pkcs11/pkcs11_slot.h index 1a859ddbf..e3b111fa2 100644 --- a/lib/pkcs11/pkcs11_slot.h +++ b/lib/pkcs11/pkcs11_slot.h @@ -39,17 +39,20 @@ extern "C" { /** Slot Context */ typedef struct _pkcs11_slot_ctx { - CK_BBOOL initialized; - CK_SLOT_ID slot_id; - ATCADevice device_ctx; - ATCAIfaceCfg interface_config; - CK_SESSION_HANDLE session; + CK_BBOOL initialized; + CK_SLOT_ID slot_id; + ATCADevice device_ctx; + ATCAIfaceCfg interface_config; + CK_SESSION_HANDLE session; #if ATCA_CA_SUPPORT - atecc608a_config_t cfg_zone; + atecc608a_config_t cfg_zone; +#endif + CK_FLAGS flags; + uint16_t user_pin_handle; + uint16_t so_pin_handle; +#ifndef PKCS11_LABEL_IS_SERNUM + CK_UTF8CHAR label[PKCS11_MAX_LABEL_SIZE + 1]; #endif - CK_FLAGS flags; - uint16_t user_pin_handle; - uint16_t so_pin_handle; } pkcs11_slot_ctx, *pkcs11_slot_ctx_ptr; #ifdef __cplusplus diff --git a/lib/pkcs11/pkcs11_token.c b/lib/pkcs11/pkcs11_token.c index 243894eff..7eb3eaad3 100644 --- a/lib/pkcs11/pkcs11_token.c +++ b/lib/pkcs11/pkcs11_token.c @@ -432,15 +432,20 @@ CK_RV pkcs11_token_get_info(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) /* Read the serial number */ if (!atcab_read_serial_number(buf)) { +#ifdef PKCS11_LABEL_IS_SERNUM + size_t len = sizeof(pInfo->label); + (void)atcab_bin2hex_(buf, 9, (char*)pInfo->label, &len, FALSE, FALSE, TRUE); + memcpy(pInfo->serialNumber, &pInfo->label[2], sizeof(pInfo->serialNumber)); +#else size_t len = sizeof(pInfo->serialNumber); - - /* Bytes 2-7 are the unique serial number */ - atcab_bin2hex_(&buf[2], 6, (char*)pInfo->serialNumber, &len, FALSE, FALSE, TRUE); - - /* Bytes 0, 1 & 8 is the customer code */ - snprintf((char*)pInfo->label, sizeof(pInfo->label), "%02XABC", (uint8_t)slotID); + (void)atcab_bin2hex_(&buf[1], 8, (char*)pInfo->serialNumber, &len, FALSE, FALSE, TRUE); +#endif } +#ifndef PKCS11_LABEL_IS_SERNUM + memcpy(pInfo->label, slot_ctx->label, strlen(slot_ctx->label)); +#endif + /* Read the hardware revision data */ if (!atcab_info(buf)) { diff --git a/module.xml b/module.xml index 3ce666e53..956f48065 100644 --- a/module.xml +++ b/module.xml @@ -1,4 +1,4 @@ - + diff --git a/package.xml b/package.xml index ce34421b4..ecb8222c3 100644 --- a/package.xml +++ b/package.xml @@ -1,6 +1,6 @@ - + diff --git a/release_notes.md b/release_notes.md index 20b43f348..62dcf057e 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,12 +1,21 @@ # Microchip Cryptoauthlib Release Notes +## Release v3.2.1 (06/29/2020) + +### Fixes + - PKCS11 configuration option to set token label to the device serial number + - Fix OSX CLANG macro error + - Add missing c++ wrapper macros to calib_basic.h + - Ensure atcab_init_ext calls atcab_release_ext rather than atcab_release + + ## Release v3.2.0 (06/10/2020) ### New features - TA100 device support (requires NDA, consult with your FAE or submit a request through your myMicrochip account) - - Extension of the existin API to support device context retention to allow + - Extension of the existing API to support device context retention to allow multiple independent contexts to be maintained. The application still needs to ensure concurrency protections are used in the application to guard bus communication. @@ -203,4 +212,4 @@ - Certificate I/O and reconstruction - New SHA2 implementation - Major update to API docs, Doxygen files found in cryptoauthlib/docs - - load cryptoauthlib/docs/index.html with your browser \ No newline at end of file + - load cryptoauthlib/docs/index.html with your browser