aws / amazon-freertos

DEPRECATED - See README.md
https://aws.amazon.com/freertos/
MIT License
2.54k stars 1.1k forks source link

Unable to import JITP Certificate in Flash #1119

Closed bgintz-rsc closed 4 years ago

bgintz-rsc commented 5 years ago

Describe the bug Calling xProvisionCertificate in was_dev_mode_key_provisioning fails

System information

Expected behavior This function should securely store the JITP certificate in FLASH for use by the TLS connect. It fails with a panic in the nvs blob storage due to a null filename.

Cause and Proposed Fix This is caused by failure to handle the PKCS #11 label for Just-In-Time-Provisioning (pkcs11configLABEL_JITP_CERTIFICATE) in the switch statement in prvLabelToFilenameHandle() in iot_pkcs11_pal.c which returns eInvalidHandle.

Additional context Also, it appears that even if the JITP Certificate is imported properly the TLS connect code does not look for an imported file and still looks to access the certificate in prvInitializeClientCredential() by checking for a null or supplied value for keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM on line 409

Thank you!

Best Bobby

dcgaws commented 5 years ago

@bgintz-rsc thank you, it sounds like this is indeed a problem. We're working on a repro. If you're inclined to submit a PR to our master branch, we'd be happy to take it.

By way of background, we did not carve out space for the JITR certificate when we did our initial ports of iot_pkcs11_pal.c. That was arguably a requirements miss (and in any case only incidental to this bug), but the tradeoff is that each additional object supported by our PKCS #11 PAL complicates the flash layout of each supported port. The question is, which is the more common use case: that the JITR certificate is common across a given deployment, and therefore can be compiled into the firmware image, or that the JITR certificate may be independent of the flash image, and therefore should be handled by the PKCS #11 PAL? Any comments you could share regarding your application will be welcome.

bgintz-rsc commented 5 years ago

Thank you for the quick response.

I understand the tradeoff. Ultimately, I feel that importing the JITR into a PKCS object is more secure than hard coding it. At a minimum, my initial suggestion is to remove the code that is intended to provision the JITR from dev_mode_key_provisioning so as not to cause another developer to attempt to use it.

That said - I believe the changes required to support this it seems pretty straightforward.

AFAIK the following changes are required:

  1. Modify the switch statement in prvLabelToFilenameHandle() (to return a defined file name for the label: pkcs11configLABEL_JITP_CERTIFICATE which is already defined as "JITP Cert"

and

  1. Modify prvInitializeClientCredential() to look for the PKCS11 object stored in that file when keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM is NULL. If the object is not found and keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM is NULL then a JITR Cert is not required (same as today).

I was about to code this up and almost did so, but was limited on time and had other issues to solve (preventing formerly working code from connecting to the broker while migrating to 2019-6.00). As those are behind me now I will make the changes if the above is unclear. Let me know if you want me to do this.

Since you asked.about our application, here is our current thinking. We plan use at least a different JITP cert for each product that we incorporate AFR into. Each will also have a unique broker endpoint. Certainly that scenario does work with by hard coding the certs. However, if for some reason we wanted to rotate all credentials and sign with a different JITR cert this would not be possible.

That said - for our application we securely import the other credentials required to connect to the broker and provision the device with them using PKCS11 and obtain the JITP cert at the sam time. So it would seem appropriate to handle this cert the same way and thus for it not to be accessible by anything other than the TLS connect code once provisioned (as opposed to viewing it as a hard coded Root CA).

So our (my) preference would be to provision the device with the JITR cert when the other credentials are imported and once imported have the connection use them transparently as the tls layer is intended.

One question though - if this were to be the chosen solution would we still be able to establish an https client connection (to an arbitrary endpoint) using the same provisioning as we use to connect to the AWS broker? I believe the answer is yes but would like verification.

Thank you again.

Kindest regards, Bobby

dcgaws commented 5 years ago

Hello @bgintz-rsc yes, a PR for the above would be welcome. Thank you for your feedback on the JITR lifecycle considerations for your solution - that's exactly what I was hoping for. One follow-up question: do you plan on supporting OTA for your main app/firmware image?

Regarding connecting to a line-of-business HTTPS endpoint: if there's a JITR certificate present on the device, and if the LoB server requests a client certificate, then the Amazon FreeRTOS sockets library will send the JITR (i.e. issuer) certificate along with the client certificate. Of course, the safest bet would be to lab test that configuration to ensure that the LoB server doesn't get confused (i.e. especially if it's doing some sort of custom authorization logic). However, having the client send multiple certificates is consistent with the TLS standard, so I don't think that you'll encounter trouble with any of the major web server stacks.

bgintz-rsc commented 5 years ago

Thanks for the update. I will submit a PR for the changes. Re: the LoB HTTPS endpoint. Is there a way to use the TLS connect with a different set of credentials once we have provisioned with the AWS-IoT? I agree that the chain of trust built using the JIT cert along with the client cert should work. What is the option if not?

monkeytronics commented 4 years ago

I'm designing a JITP solution running. Could my panic'ed (LoadProhibited) error be related to @bgintz-rsc issue, or is it something more obvious?

Steps to reproduce: I'm starting with the demo app for MQTT. It works fine with demo credentials (keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM = NULL).

Have created my JITP credentials and they work fine too, having published with mosquitto. The provisioning works: thing created with correct name & type etc. All good so far.

Joining it up: As a first step, I've replaced the contents of aws_client_credentials_keys.h with my JITP certificates. But, I get a panic'ed (LoadProhibited):

0x400d451e: nvs::Page::writeItem(unsigned char, nvs::ItemType, char const*, void const*, unsigned int, unsigned char) at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/vendors/espressif/esp-idf/components/nvs_flash/src/nvs_page.cpp:849

0x400d3745: nvs::Storage::writeMultiPageBlob(unsigned char, char const*, void const*, unsigned int, nvs::VerOffset) at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/vendors/espressif/esp-idf/components/nvs_flash/src/nvs_storage.cpp:624

0x400d3bf9: nvs::Storage::writeItem(unsigned char, nvs::ItemType, char const*, void const*, unsigned int) at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/vendors/espressif/esp-idf/components/nvs_flash/src/nvs_storage.cpp:624

0x400d321d: nvs_set_blob at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/vendors/espressif/esp-idf/components/nvs_flash/src/nvs_api.cpp:521

0x4012ced2: PKCS11_PAL_SaveObject at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/vendors/espressif/boards/esp32/ports/pkcs11/iot_pkcs11_pal.c:187

0x4012c25f: prvCreateCertificate at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/libraries/abstractions/pkcs11/mbedtls/iot_pkcs11_mbedtls.c:1210

0x4012c9ce: C_CreateObject at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/libraries/abstractions/pkcs11/mbedtls/iot_pkcs11_mbedtls.c:2039

0x40120c5d: xProvisionCertificate at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/demos/dev_mode_key_provisioning/src/aws_dev_mode_key_provisioning.c:641

0x40120dae: xProvisionDevice at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/demos/dev_mode_key_provisioning/src/aws_dev_mode_key_provisioning.c:1021

0x40120f73: vAlternateKeyProvisioning at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/demos/dev_mode_key_provisioning/src/aws_dev_mode_key_provisioning.c:1220

0x40120faa: vDevModeKeyProvisioning at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/demos/dev_mode_key_provisioning/src/aws_dev_mode_key_provisioning.c:1280

0x400d0c13: app_main at C:\Users\thebi\FreeRTOS\build/../src/main.c:145

0x400d5cf6: main_task at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/vendors/espressif/esp-idf/components/esp32/cpu_start.c:505

My first thought is "Am I understanding the certificates right?"

  1. I replace keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM from NULL to the rootCA I download from amazon.
  2. I put the device private key, $DEVICE.key into keyCLIENT_PRIVATE_KEY_PEM.
  3. Lastly, I put the deviec certificate, $DEVICE.crt, generated as follows into keyCLIENT_CERTIFICATE_PEM. N.B. This look wrong. It's 2 separate pem certificates stuck together. And yet it works with mosquitto.
$ openssl genrsa -out "$DEVICE.key" 2048
$ openssl req -new -key "$DEVICE.key" -out "$DEVICE.csr"  -subj "//CN=$DEVICE\C=NZ"
$ openssl x509 -req -in $DEVICE.csr -CA "$CA.ca.pem" -CAkey "$CA.ca.key" -CAcreateserial -out "$DEVICE.crt.tmp" -days 500 -sha256
$ cat "$DEVICE.crt.tmp" "$CA.ca.pem" > "$DEVICE.crt"

Am I doing something completely wrong?

chegewara commented 4 years ago

@monkeytronics Check in menuconfig settings for NVS, do you have enabled multi-page blob support? Also, in main.c, do you call prvMiscInitialization() before vDevModeKeyProvisioning()?

monkeytronics commented 4 years ago

Yes I do call those functions in that order.

int app_main( void )
{
    /* Perform any hardware initialization that does not require the RTOS to be
     * running.  */

    prvMiscInitialization();

    // my tasks
  //  xTaskCreate(&blinky, "blinky", 2048, NULL, tskIDLE_PRIORITY + 6, NULL );
    xTaskCreate(&rsensorTask, "sensor", 4096, NULL, tskIDLE_PRIORITY + 10, NULL );
    xTaskCreate(&lcd5110_display, "LCD_5110", 2048, NULL, tskIDLE_PRIORITY + 9, NULL );

    if( SYSTEM_Init() == pdPASS )
    {
        /* A simple example to demonstrate key and certificate provisioning in
         * microcontroller flash using PKCS#11 interface. This should be replaced
         * by production ready key provisioning mechanism. */
        vDevModeKeyProvisioning();

        #if BLE_ENABLED
            /* Initialize BLE. */
            if( prvBLEStackInit() != ESP_OK )
            {
                configPRINTF( ( "Failed to initialize the bluetooth stack\n " ) );

                while( 1 )
                {
                }
            }
        #else
            ESP_ERROR_CHECK( esp_bt_controller_mem_release( ESP_BT_MODE_CLASSIC_BT ) );
            ESP_ERROR_CHECK( esp_bt_controller_mem_release( ESP_BT_MODE_BLE ) );
        #endif /* if BLE_ENABLED */
        /* Run all demos. */
        DEMO_RUNNER_RunDemos();
    }

    /* Start the scheduler.  Initialization that requires the OS to be running,
     * including the WiFi initialization, is performed in the RTOS daemon task
     * startup hook. */
    /* Following is taken care by initialization code in ESP IDF */
    /* vTaskStartScheduler(); */
    return 0;
}

Contents of Kconfig file in nvs_flash folder =

menu NVS

config MP_BLOB_SUPPORT
    bool "Enable multi-page blob support"
    default n
    help
        When enabled, blobs larger than the sector size are split and stored on multiple sectors.
        This removes the earlier limitation of 1984 bytes for storing data-blobs.

config NVS_ENCRYPTION
   bool "Enable NVS encryption"
   default n
   depends on FLASH_ENCRYPTION_ENABLED
   help
      This option enables encryption for NVS. When enabled, AES-XTS is used to encrypt
      the complete NVS data, except the page headers. It requires XTS encryption keys 
      to be stored in an encrypted partition. This means enabling flash encryption is 
      a pre-requisite for this feature. 

endmenu

Guess that's a no on multiblob support. How do we use the menuconfig in amazon freertos. I haven't seen any reference to it outside of the standard ESP-IDF flow...

chegewara commented 4 years ago

Are you using CMake or GNU make?

monkeytronics commented 4 years ago

CMake (at least trying to).

chegewara commented 4 years ago

./vendors/espressif/esp-idf/tools/idf.py menuconfig

monkeytronics commented 4 years ago

From the same directory as my top CMake file, just above amazon-freertos, running into a problem. Update fixed it. With a very messy patch.

$ amazon-freertos/vendors/espressif/esp-idf/tools/idf.py menuconfig
Setting IDF_PATH environment variable: C:\Users\thebi\FreeRTOS\amazon-freertos\vendors\espressif\esp-idf
Running ninja in directory C:\Users\thebi\FreeRTOS\build
Executing "ninja menuconfig"...
[0/2] Re-checking globbed directories...
[0/1] amazon-freertos\esp-idf\CMakeFiles\menuconfig-8e5b1cc.bat 4e9287fd8ef653b7
C:/Users/thebi/FreeRTOS/amazon-freertos/vendors/espressif/esp-idf/components/bt/Kconfig:1147: undefined variable "$IDF_PATH/components/nimble/Kconfig.in"
Batch file failed at line 4 with errorcode 1
FAILED: amazon-freertos/esp-idf/CMakeFiles/menuconfig
amazon-freertos\esp-idf\CMakeFiles\menuconfig-8e5b1cc.bat 4e9287fd8ef653b7
ninja: build stopped: subcommand failed.
ninja failed with exit code 1

The file $IDF_PATH/components/nimble/Kconfig.in is failing, because it smoehow doesn't seem to know where $IDF_PATH is. So, I overwrote the line: source "C:/Users/thebi/FreeRTOS/amazon-freertos/vendors/espressif/esp-idf/components/nimble/Kconfig.in"

Opens now...

chegewara commented 4 years ago

Try to call from within amazon-freertos folder. But like i suspected, its probably that menuconfig settings:

config MP_BLOB_SUPPORT
    bool "Enable multi-page blob support"
    default n
    help
        When enabled, blobs larger than the sector size are split and stored on multiple sectors.
        This removes the earlier limitation of 1984 bytes for storing data-blobs.

Default is n.

monkeytronics commented 4 years ago

You the man @chegewara . Unless you are not in fact a man. Either way, hero's work! Thanks for the shove in the right direction.

monkeytronics commented 4 years ago

I've checked and multiblob support is enabled in menuconfig. Actually, in the backtrace I noticed: nvs::Storage::writeMultiPageBlob( ... So it's something to do with the pucJITPCertificate certificate. The other 2 certs go through fine, but when I add in the JITR cert, it falls over. You can follow the function chain through from the backtrace. I think I'm going to open a new issue for this one. The actual contents of the cert are OK. I've checked them.

chegewara commented 4 years ago

Testing with mosquitto and using with AFR is not the same. Remember what i told you in other issue topic, certificates in NVS should be stored as DER, but with mosquitto you are using PEM. Yes, please open new issue and describe full usage you are trying to achieve.

monkeytronics commented 4 years ago

@chegewara I followed the code through. I see what you mean about DER and I agree I'm not on the right track using aws_clietncredentials_keys.h.

Better approach: As you said, putting keys in nvs makes sense in production. Don't run vDevModeKeyProvisioning( ) any more. Make a new function vProductionModeKeyProvision( ) which pulls creds from nvs up front. And in this case, they can be in DER or PEM format. This is potentially very clean as it's all above the afr repo.

Problem: As mentioned originally, trying to set up the JITP key this way doesn't work. I changed up the code in iot_pkcs11_pal.c so it doesn't fail on nvs_set_blob due to NULL. But I'm almost 100% sure it's right and I don't honestly know enough about other dependencies I might break.

line 46:

#define pkcs11palFILE_NAME_KEY                             "P11_Key"
#define pkcs11palFILE_CODE_SIGN_PUBLIC_KEY       "P11_CSK"
#define pkcs11palFILE_NAME_JITP_CERTIFICATE       "P11_JITP" // this is a problem... 

enum eObjectHandles
{
    eInvalidHandle = 0, /* According to PKCS #11 spec, 0 is never a valid object handle. */
    eAwsDevicePrivateKey = 1,
    eAwsDevicePublicKey,
    eAwsDeviceCertificate,
    eAwsCodeSigningKey,
    eAwsCodeJITPKey
};

line 148:

        else if( 0 == memcmp( pcLabel,
                              pkcs11configLABEL_JITP_CERTIFICATE,
                              strlen( (char*)pkcs11configLABEL_JITP_CERTIFICATE ) ) )
        {
            *pcFileName = pkcs11palFILE_NAME_JITP_CERTIFICATE;
            *pHandle = eAwsCodeJITPKey;                                      
        }

line 312:

else if( xHandle == eAwsCodeJITPKey )
    {
        pcFileName = pkcs11palFILE_NAME_JITP_CERTIFICATE;
        *pIsPrivate = CK_FALSE;
    }

What is the tried and tested recipe for production key provisioning? I'm not comfortable messing with these libraries.

Thanks all.

chegewara commented 4 years ago

You see, i didnt have to write any single line of code when create NVS partition with certificates in DER, just dont call vDevModeKeyProvisioning() never ever and all works fine.

Now, even during development i am using this whole procedure to create NVS with certificates, because it saves my time.

All i have to do is to execute scripts: 1) create-ca, 2) register-ca, 3) provision, 4) create-nvs,

and finally flash NVS.

monkeytronics commented 4 years ago

I think I get you. That's genius! So, all this messing about I'm doing is a massive waste of time!? You are skipping right to the end and just putting the blobs in the nvs.

chegewara commented 4 years ago

Exactly. Since in demo we provide PEM certificates which on runtime are converted to DER and then stored in NVS(only if no DER certificates already stored), my idea was to make it production ready and like you said, i skipped to the end.

monkeytronics commented 4 years ago

But you don't put the JITP / JITR certificate in this way. Is it provided another way? Or maybe you not using this mechanism?

chegewara commented 4 years ago

This is only certificate embedded in code.

monkeytronics commented 4 years ago

And I've just searched the repo and keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM is accessed directly using #include "aws_clientcredential_keys.h" but the others are not. So, it looks safe to use.

Thanks so much for this. You've really saved my bacon man.

monkeytronics commented 4 years ago

Oh dear. My JITP scrits are still not working from flash! I'm commenting out

// vDevModeKeyProvisioning();

And putting the der certs in the NVS inthis format :

key,type,encoding,value
creds,namespace,,
P11_Key,file,binary,../keys/thing_name.key.der
P11_Cert,file,binary,../certs/thing_name.crt.der
info,namespace,,
nvs_key_thing_name,data,string,thing_name

Example Output:

key,type,encoding,value
creds,namespace,,
P11_Key,file,binary,6b73e91c-f967-43e7-a39e-ae47789be8b4.key.der
P11_Cert,file,binary,6b73e91c-f967-43e7-a39e-ae47789be8b4.crt.der
info,namespace,,
nvs_key_thing_name,data,string,6b73e91c-f967-43e7-a39e-ae47789be8b4

More from my provisioning script :

openssl rsa  -inform PEM -outform DER -text -in "$DEVICE.key" -out "$DEVICE.key.der"
openssl x509 -inform PEM -outform DER -text -in "$DEVICE.crt" -out "$DEVICE.crt.der"

#Create manufacturing partition file. First line Overwrites. Subsequent Append
echo "key,type,encoding,value" >> mfg_config.csv
echo "creds,namespace,," >> mfg_config.csv
echo "P11_Key,file,binary,$DEVICE.key.der" >> mfg_config.csv
echo "P11_Cert,file,binary,$DEVICE.crt.der" >> mfg_config.csv
echo "info,namespace,," >> mfg_config.csv
echo "nvs_key_thing_name,data,string,$DEVICE" >> mfg_config.csv

nvs_partition_gen.py  --input mfg_config.csv --output mfg_config.bin --size 0x4000 --version v2
ESPPORT=com7  esptool.py write_flash 0x340000 mfg_config.bin

It seems to all work ok. But my code fails trying to find the private key:

5 303 [IP-task] vDHCPProcess: offer c0a8017fip
6 304 [iot_thread] [INFO ][DEMO][3040] Successfully initialized the demo. Network type for the demo: 1
7 304 [iot_thread] [INFO ][MQTT][3040] MQTT library successfully initialized.
8 304 [iot_thread] [INFO ][DEMO][3040] MQTT demo client identifier is ESP32_2 (length 7).
I (3342) PKCS11: Initializing NVS partition: "storage"
9 317 [iot_thread] ERROR: Private key not found. 10 317 [iot_thread] No device certificate found.11 317 [iot_thread] ERROR: Loading credentials into TLS context failed with error -2005.
I (3392) wifi: state: run -> init (0)
I (3392) wifi: pm stop, total sleep time: 867508 us / 1149602 us

I (3392) wifi: n:7 0, o:7 2, ap:255 255, sta:7 2, prof:1
I (3402) WIFI: SYSTEM_EVENT_STA_DISCONNECTED: 8
12 317 [iot_thread] [ERROR][NET][3170] Failed to establish new connection. Socket status: -1002.
13 318 [iot_thread] [ERROR][MQTT][3180] Failed to establish new MQTT connection, error NETWORK ERROR.
14 318 [iot_thread] [ERROR][DEMO][3180] MQTT CONNECT returned error NETWORK ERROR.
15 318 [iot_thread] [INFO ][MQTT][3180] MQTT library cleanup done.
16 318 [iot_thread] [ERROR][DEMO][3180] Error running demo.
17 322 [iot_thread] [INFO ][INIT][3220] SDK cleanup done.
18 322 [iot_thread] [INFO ][DEMO][3220] -------DEMO FINISHED-------

My understanding is that it will go off and look for P11_Cert in the partition. I've tried to chase it through the iot_pksc11 code and iot_tls code, but it'll take a while to get my head round it. Hopefully, I'm doing something silly, or missing another step?

So close...

chegewara commented 4 years ago

This is my provision certificate:

#!/bin/bash
#
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
# Changes to match requirements to create NVS partition on ESP32
# Created certificates are in DER format, which is read in AWSFreeRTOS from NVS
# Updated by: chegewara

DEVICE=$1
CA=${2:-root}

if [[ -z "$DEVICE" ]]; then
    echo "Usage: $0 deviceName [CA]"
    exit
fi

if [[ ! -f "$CA.ca.pem" ]]; then
    echo "Could not find certificate for $CA"
    exit
fi

openssl genrsa -out $DEVICE.key 2048
openssl req -new -key "$DEVICE.key" -out "$DEVICE.csr"  -subj "/CN=$DEVICE"    #version for UNIX like OS
# openssl req -new -key $DEVICE.key -out $DEVICE.csr  -subj "//CN=$DEVICE"      #change to use with msys2 on windows
openssl x509 -req -in $DEVICE.csr -CA $CA.ca.pem -CAkey $CA.ca.key -CAcreateserial -out $DEVICE.crt.tmp -days 100 -sha256
cat $DEVICE.crt.tmp > $DEVICE.crt
cat $DEVICE.crt.tmp $CA.ca.pem > $DEVICE.2.crt

rm "$DEVICE.crt.tmp"

openssl rsa -outform der -in $DEVICE.key -out ../keys/$DEVICE.key.der  #convert to DER format, which is read from esp32 NVS
openssl x509 -outform der -in $DEVICE.crt -out ../certs/$DEVICE.crt.der  #convert to DER format, which is read from esp32 NVS
# openssl rsa -outform der -in $DEVICE.key -out $DEVICE.key.der  #convert to DER format, which is read from esp32 NVS
# openssl x509 -outform der -in $DEVICE.crt -out $DEVICE.crt.der  #convert to DER format, which is read from esp32 NVS

# rm "$DEVICE.key"
# rm "$DEVICE.crt"
rm "$CA.ca.srl"

I think that -text flag may have something to do with your problems.

monkeytronics commented 4 years ago

So that seems to have solved the private key not found problem. Well spotted on the -text flag! But still something not quite right. My monitor output looks like this. I tried with a shorter thingName too, but not change. Unless it has to be linked to the commonName in the provisioning cert??

$ ESPPORT=com7 ./amazon-freertos/vendors/espressif/esp-idf/tools/idf.py monitor
Setting IDF_PATH environment variable: C:\Users\thebi\FreeRTOS\amazon-freertos\vendors\espressif\esp-idf
Running idf_monitor in directory C:\Users\thebi\FreeRTOS
Executing "winpty C:\Python27\python.exe C:\Users\thebi\FreeRTOS\amazon-freertos\vendors\espressif\esp-idf\tools/idf_monitor.py -p com7 -b 115200 C:\Users\thebi\FreeRTOS\build\afr_demo -m 'C:\P
ython27\python.exe' './amazon-freertos/vendors/espressif/esp-idf/tools/idf.py'"...
--- idf_monitor on com7 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:6264
load:0x40078000,len:11684
load:0x40080000,len:6112
0x40080000: _WindowOverflow4 at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/freertos_kernel/portable/ThirdParty/GCC/Xtensa_ESP32/xtensa_vectors.S:1685

entry 0x40080330
0x40080330: _KernelExceptionVector at ??:?

I (28) boot: ESP-IDF v3.1.5-105-g7313c836a5 2nd stage bootloader
I (28) boot: compile time 15:58:27
I (28) boot: Enabling RNG early entropy source...
I (34) boot: SPI Speed      : 40MHz
I (38) boot: SPI Mode       : DIO
I (42) boot: SPI Flash Size : 4MB
I (46) boot: Partition Table:
I (50) boot: ## Label            Usage          Type ST Offset   Length
I (57) boot:  0 nvs              WiFi data        01 02 00010000 00006000
I (65) boot:  1 otadata          OTA data         01 00 00016000 00002000
I (72) boot:  2 phy_init         RF data          01 01 00018000 00001000
I (80) boot:  3 ota_0            OTA app          00 10 00020000 00177000
I (87) boot:  4 ota_1            OTA app          00 11 001a0000 00177000
I (95) boot:  5 storage          WiFi data        01 02 00317000 00010000
I (102) boot: End of partition table
I (106) esp_image: segment 0: paddr=0x00020020 vaddr=0x3f400020 size=0x1c2a4 (115364) map
I (156) esp_image: segment 1: paddr=0x0003c2cc vaddr=0x3ffc0000 size=0x02f18 ( 12056) load
I (161) esp_image: segment 2: paddr=0x0003f1ec vaddr=0x40080000 size=0x00400 (  1024) load
0x40080000: _WindowOverflow4 at C:\Users\thebi\FreeRTOS\build/../amazon-freertos/freertos_kernel/portable/ThirdParty/GCC/Xtensa_ESP32/xtensa_vectors.S:1685

I (163) esp_image: segment 3: paddr=0x0003f5f4 vaddr=0x40080400 size=0x00a1c (  2588) load
I (173) esp_image: segment 4: paddr=0x00040018 vaddr=0x400d0018 size=0x95df4 (613876) map
0x400d0018: _flash_cache_start at ??:?

I (396) esp_image: segment 5: paddr=0x000d5e14 vaddr=0x40080e1c size=0x16388 ( 91016) load
I (447) boot: Loaded app from partition at offset 0x20000
I (447) boot: ota rollback check done
I (447) boot: Disabling RNG early entropy source...
I (452) cpu_start: Pro cpu up.
I (456) cpu_start: Single core mode
I (460) heap_init: Initializing. RAM available for dynamic allocation:
I (467) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (473) heap_init: At 3FFCBB10 len 000144F0 (81 KiB): DRAM
I (479) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (486) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (492) heap_init: At 400971A4 len 00008E5C (35 KiB): IRAM
I (498) cpu_start: Pro cpu start user code
I (181) cpu_start: Starting scheduler on PRO CPU.
SCD30 in I2C Mode...
0 1 [main] Calling foo: 100
1 1 [main] Variable from SCD30 Library : 300
2 1 [iot_thread] [INFO ][DEMO][10] ---------STARTING DEMO---------

3 2 [iot_thread] [INFO ][INIT][20] SDK successfully initialized.
I (241) wifi: wifi driver task: 3ffb1640, prio:23, stack:3584, core=0
I (241) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (251) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (261) wifi: wifi firmware version: 3c46a62
I (261) wifi: config NVS flash: enabled
I (261) wifi: config nano formating: disabled
I (271) wifi: Init dynamic tx buffer num: 32
I (271) wifi: Init data frame dynamic rx buffer num: 32
I (281) wifi: Init management frame dynamic rx buffer num: 32
I (281) wifi: Init management short buffer num: 32
I (291) wifi: Init static rx buffer size: 1600
I (291) wifi: Init static rx buffer num: 10
I (301) wifi: Init dynamic rx buffer num: 32
I (371) phy: phy_version: 4009, e7844c0, Jul 15 2019, 13:20:06, 0, 0
I (371) wifi: mode : sta (24:0a:c4:31:50:a8)
I (371) WIFI: SYSTEM_EVENT_STA_START
I (1221) wifi: n:7 2, o:1 0, ap:255 255, sta:7 2, prof:1
I (2201) wifi: state: init -> auth (b0)
I (2211) wifi: state: auth -> assoc (0)
I (2211) wifi: state: assoc -> run (10)
I (2311) wifi: connected with Lolly, channel 7
I (2311) wifi: pm start, type: 1

I (2311) WIFI: SYSTEM_EVENT_STA_CONNECTED
4 303 [IP-task] vDHCPProcess: offer c0a8017fip
I (3251) event: sta ip: 192.168.1.127, mask: 255.255.255.0, gw: 192.168.1.1
I (3251) WIFI: SYSTEM_EVENT_STA_GOT_IP
5 304 [IP-task] vDHCPProcess: offer c0a8017fip
6 304 [iot_thread] [INFO ][DEMO][3040] Successfully initialized the demo. Network type for the demo: 1
7 304 [iot_thread] [INFO ][MQTT][3040] MQTT library successfully initialized.
8 304 [iot_thread] [INFO ][DEMO][3040] MQTT demo client identifier is 9dfa13ce-ae32-4d3a-9741-c9e073d9e0dc (length 36).
9 304 [iot_thread] [WARN ][MQTT][3040] A client identifier length of 36 is longer than 23, which is the longest client identifier a server must accept.
I (3351) PKCS11: Initializing NVS partition: "storage"
10 743 [iot_thread] [INFO ][MQTT][7430] Establishing new MQTT connection.
11 743 [iot_thread] [INFO ][MQTT][7430] Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable.
12 743 [iot_thread] [INFO ][MQTT][7430] (MQTT connection 0x3ffb7868, CONNECT operation 0x3ffb7984) Waiting for operation completion.
13 763 [iot_thread] [INFO ][MQTT][7630] (MQTT connection 0x3ffb7868, CONNECT operation 0x3ffb7984) Wait complete with result SUCCESS.
14 763 [iot_thread] [INFO ][MQTT][7630] New MQTT connection 0x3ffbf200 established.
15 765 [iot_thread] [INFO ][MQTT][7630] (MQTT connection 0x3ffb7868) SUBSCRIBE operation scheduled.
16 765 [iot_thread] [INFO ][MQTT][7650] (MQTT connection 0x3ffb7868, SUBSCRIBE operation 0x3ffb7ea4) Waiting for operation completion.
17 1265 [iot_thread] [INFO ][MQTT][12650] (MQTT connection 0x3ffb7868, SUBSCRIBE operation 0x3ffb7ea4) Wait complete with result TIMEOUT.
18 1265 [iot_thread] [INFO ][MQTT][12650] (MQTT connection 0x3ffb7868) Disconnecting connection.
19 1265 [iot_thread] [INFO ][MQTT][12650] (MQTT connection 0x3ffb7868, DISCONNECT operation 0x3ffb7ba0) Waiting for operation completion.
20 1266 [iot_thread] [ERROR][NET][12660] Error -27648 while sending data.
I (12891) wifi: state: run -> init (0)
I (12891) wifi: pm stop, total sleep time: 8800515 us / 10580400 us

I (12901) wifi: n:7 0, o:7 2, ap:255 255, sta:7 2, prof:1
I (12901) WIFI: SYSTEM_EVENT_STA_DISCONNECTED: 8
21 1267 [iot_thread] [INFO ][MQTT][12670] (MQTT connection 0x3ffb7868, DISCONNECT operation 0x3ffb7ba0) Wait complete with result NETWORK ERROR.
22 1267 [iot_thread] [WARN ][MQTT][12670] (MQTT connection 0x3ffb7868) DISCONNECT not sent, error NETWORK ERROR.
23 1267 [iot_thread] [WARN ][NET][12670] Failed to close connection.
24 1267 [iot_thread] [INFO ][MQTT][12670] (MQTT connection 0x3ffb7868) Network connection closed.
25 1268 [iot_thread] [INFO ][MQTT][12680] (MQTT connection 0x3ffb7868) Network connection destroyed.
26 1268 [iot_thread] [INFO ][MQTT][12680] MQTT library cleanup done.
27 1268 [iot_thread] [ERROR][DEMO][12680] Error running demo.
28 1273 [iot_thread] [INFO ][INIT][12730] SDK cleanup done.
29 1273 [iot_thread] [INFO ][DEMO][12730] -------DEMO FINISHED-------

Setup the Sensor...

I noticed my partition location was different to yours, so I changed the command:

esptool.py write_flash 0x317000 mfg_config.bin

to put the creds int he right place.

Now, the weirdest thing is that having done all of this, I seea last will and testament entry in my AWS IoT_Core Test :

{
  "format": "string",
  "payload": "MQTT demo unexpectedly disconnected.",
  "qos": 0,
  "timestamp": 1573370337302,
  "topic": "iotdemo/will"
}

But I don't see any of the demo stuff and my JITP thing does not occur. So no successful messages are published. Am I missing something about the format of the certificates that is different when you use mosquitto and afr. I saw some vague posts but no detail. I'm using the same concatenated certificate as I did with mosquitto. This is very puzzling...

chegewara commented 4 years ago

I tried with a shorter thingName too, but not change. Unless it has to be linked to the commonName in the provisioning cert??

Yes. With proper provisioning-template.json.

monkeytronics commented 4 years ago

I am using a provisioning template. And it all worked fine using mosquitto. First connect invokes the JITP process successfully. Things appears all ready to roll in aws iot. Then following messages publish correctly.

mosquitto cmd in script: mosquitto_pub -d --cafile root.cert --cert $thingName.crt --key $thingName.key -h xxxxxxxxxxxxxxxxxx-southeast-2.amazonaws.com -p 8883 -q 0 -t register -i $thingName --tls-version tlsv1.2 -m "$message"

thebi@WorkLaptop MINGW64 ~/FreeRTOS/prod/bin
$ ./pub CO2_Testy
Connecting CO2_Testy to xxxxxxxxxxxxxxxxxx.iot.ap-southeast-2.amazonaws.com
Error: The connection was lost.
Client CO2_Testy sending CONNECT

thebi@WorkLaptop MINGW64 ~/FreeRTOS/prod/bin
$ ./pub CO2_Testy
Connecting CO2_Testy to xxxxxxxxxxxxxxxxxx.iot.ap-southeast-2.amazonaws.com
Client CO2_Testy sending CONNECT
Client CO2_Testy received CONNACK (0)
Client CO2_Testy sending PUBLISH (d0, q0, r0, m1, 'register', ... (0 bytes))
Client CO2_Testy sending DISCONNECT

thebi@WorkLaptop MINGW64 ~/FreeRTOS/prod/bin
$ ./pub CO2_Testy
Connecting CO2_Testy to xxxxxxxxxxxxxxxxxx.iot.ap-southeast-2.amazonaws.com
Client CO2_Testy sending CONNECT
Client CO2_Testy received CONNACK (0)
Client CO2_Testy sending PUBLISH (d0, q0, r0, m1, 'register', ... (0 bytes))
Client CO2_Testy sending DISCONNECT

In AFR code:

#define keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM \
"-----BEGIN CERTIFICATE-----\n"\
"MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB\n"\
... 
"-----END CERTIFICATE-----\n"

I get :

5 304 [IP-task] vDHCPProcess: offer c0a8017fip
6 304 [iot_thread] [INFO ][DEMO][3040] Successfully initialized the demo. Network type for the demo: 1
7 304 [iot_thread] [INFO ][MQTT][3040] MQTT library successfully initialized.
8 304 [iot_thread] [INFO ][DEMO][3040] MQTT demo client identifier is CO2_Testy4 (length 10).
I (3568) PKCS11: Initializing NVS partition: "storage"
9 762 [iot_thread] [INFO ][MQTT][7620] Establishing new MQTT connection.
10 762 [iot_thread] [INFO ][MQTT][7620] Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable.
11 762 [iot_thread] [INFO ][MQTT][7620] (MQTT connection 0x3ffb3424, CONNECT operation 0x3ffb7e1c) Waiting for operation completion.
12 1262 [iot_thread] [INFO ][MQTT][12620] (MQTT connection 0x3ffb3424, CONNECT operation 0x3ffb7e1c) Wait complete with result TIMEOUT.
13 1262 [iot_thread] [ERROR][MQTT][12620] Failed to establish new MQTT connection, error TIMEOUT.
I (12838) wifi: state: run -> init (0)
I (12838) wifi: pm stop, total sleep time: 8496251 us / 10558672 us

I (12848) wifi: n:7 0, o:7 2, ap:255 255, sta:7 2, prof:1
I (12848) WIFI: SYSTEM_EVENT_STA_DISCONNECTED: 8
14 1262 [iot_thread] [WARN ][NET][12620] Failed to close connection.
15 1262 [iot_thread] [INFO ][MQTT][12620] Network connection closed on error.
16 1263 [iot_thread] [INFO ][MQTT][12630] (MQTT connection 0x3ffb3424) Network connection destroyed.
17 1263 [iot_thread] [ERROR][DEMO][12630] MQTT CONNECT returned error TIMEOUT.
18 1263 [iot_thread] [INFO ][MQTT][12630] MQTT library cleanup done.
19 1263 [iot_thread] [ERROR][DEMO][12630] Error running demo.
20 1267 [iot_thread] [INFO ][INIT][12670] SDK cleanup done.
21 1267 [iot_thread] [INFO ][DEMO][12670] -------DEMO FINISHED-------

Are there any changes needed to the standard amazon freertos code that need to be done to get this to work? Really struggling to get to the end of this issue...

chegewara commented 4 years ago

I see few possibly issues here, not sure if they are real problems. Size of NVS partition with certificates, by default its 0x10000. keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM is root certificate you create in AWS IoT core. Partition address, it should be 4 bytes aligned, yours is not.

I spent couple weeks to get my certificates to work. My advice is to:

Each time you delete thing and certificate i am assuming earlier steps passed test.

monkeytronics commented 4 years ago

Just checking I got this.

For

try the same certificates with demo app embedding certificates and thing name in code

this actually failed for me. But I assumed (stupidly perhaps) that I might be fire fighting something that may wash out when I go to the complete solution. With hindsight, perhaps I should have hung around here a little longer. The keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM point you made is screaming out to me. I had no idea you had to create this is IoT core.

chegewara commented 4 years ago
  • keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM is root certificate you create in AWS IoT core! I haven't done this. I've just downloaded it from https://www.amazontrust.com/repository/. I might be completely missing out this entire step...

This is Amazon authority CA certificate, not the one you need to use. Here is really good info which certificates to use: https://aws.amazon.com/blogs/iot/setting-up-just-in-time-provisioning-with-aws-iot-core/

  • The partition address should be 4 bytes aligned. My address is 0x317000 Should this be moved to 320000 for example. Is it just a matter of setting the offset in the partitioning file?

Just change it back to 0x320000 and try to use default partition size (0x10000). When you have it working you can try to change anything you want.

monkeytronics commented 4 years ago

OK. I think the light bulb is slowly coming on! So, when you do it with mosquitto, you send off the $DEVICE.key, the $DEVICE.2.crt (which is a concatenation of two certs) and the root.cert.

If we refer back to the script you use to generate the certs:

openssl genrsa -out $DEVICE.key 2048
openssl req -new -key "$DEVICE.key" -out "$DEVICE.csr"  -subj "/CN=$DEVICE"    #version for UNIX like OS
# openssl req -new -key $DEVICE.key -out $DEVICE.csr  -subj "//CN=$DEVICE"      #change to use with msys2 on windows
openssl x509 -req -in $DEVICE.csr -CA $CA.ca.pem -CAkey $CA.ca.key -CAcreateserial -out $DEVICE.crt.tmp -days 100 -sha256
cat $DEVICE.crt.tmp > $DEVICE.crt
cat $DEVICE.crt.tmp $CA.ca.pem > $DEVICE.2.crt

rm "$DEVICE.crt.tmp"

openssl rsa -outform der -in $DEVICE.key -out ../keys/$DEVICE.key.der  #convert to DER format, which is read from esp32 NVS
openssl x509 -outform der -in $DEVICE.crt -out ../certs/$DEVICE.crt.der  #convert to DER format, which is read from esp32 NVS
# openssl rsa -outform der -in $DEVICE.key -out $DEVICE.key.der  #convert to DER format, which is read from esp32 NVS
# openssl x509 -outform der -in $DEVICE.crt -out $DEVICE.crt.der  #convert to DER format, which is read from esp32 NVS

# rm "$DEVICE.key"
# rm "$DEVICE.crt"
rm "$CA.ca.srl"

And in the code you send these:

keyCLIENT_PRIVATE_KEY_PEM = $DEVICE.key
keyCLIENT_CERTIFICATE_PEM = $DEVICE.crt
keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM =  $DEVICE.2.crt

No that's not right... What about...


keyCLIENT_PRIVATE_KEY_PEM = $DEVICE.key
keyCLIENT_CERTIFICATE_PEM = $DEVICE.crt
keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM = $CA.ca.pem    ?
chegewara commented 4 years ago

keyCLIENT_PRIVATE_KEY_PEM = $DEVICE.key keyCLIENT_CERTIFICATE_PEM = $DEVICE.crt keyJITR_DEVICE_CERTIFICATE_AUTHORITY_PEM = $CA.ca.pem

monkeytronics commented 4 years ago

Ha. I'll just try that out now. Need to regen from scratch to be on the safe side!

monkeytronics commented 4 years ago

xParams.pucJITPCertificate = NULL;

10 886 [iot_thread] [INFO ][MQTT][8860] Establishing new MQTT connection.
11 887 [iot_thread] [INFO ][MQTT][8870] Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable.
12 887 [iot_thread] [INFO ][MQTT][8870] (MQTT connection 0x3ffb3234, CONNECT operation 0x3ffb3590) Waiting for operation completion.
13 927 [iot_thread] [INFO ][MQTT][9270] (MQTT connection 0x3ffb3234, CONNECT operation 0x3ffb3590) Wait complete with result SUCCESS.
14 927 [iot_thread] [INFO ][MQTT][9270] New MQTT connection 0x3ffbf200 established.
15 927 [iot_thread] [INFO ][MQTT][9270] (MQTT connection 0x3ffb3234) SUBSCRIBE operation scheduled.
16 927 [iot_thread] [INFO ][MQTT][9270] (MQTT connection 0x3ffb3234, SUBSCRIBE operation 0x3ffb3170) Waiting for operation completion.
17 1427 [iot_thread] [INFO ][MQTT][14270] (MQTT connection 0x3ffb3234, SUBSCRIBE operation 0x3ffb3170) Wait complete with result TIMEOUT.
18 1427 [iot_thread] [INFO ][MQTT][14270] (MQTT connection 0x3ffb3234) Disconnecting connection.
19 1427 [iot_thread] [INFO ][MQTT][14270] (MQTT connection 0x3ffb3234, DISCONNECT operation 0x3ffb3170) Waiting for operation completion.
20 1428 [iot_thread] [ERROR][NET][14280] Error -27648 while sending data.
I (14426) wifi: state: run -> init (0)
I (14426) wifi: pm stop, total sleep time: 4135536 us / 12054703 us

I (14436) wifi: n:6 0, o:6 0, ap:255 255, sta:6 0, prof:1
I (14436) WIFI: SYSTEM_EVENT_STA_DISCONNECTED: 8
21 1429 [iot_thread] [INFO ][MQTT][14290] (MQTT connection 0x3ffb3234, DISCONNECT operation 0x3ffb3170) Wait complete with result NETWORK ERROR.
22 1429 [iot_thread] [WARN ][MQTT][14290] (MQTT connection 0x3ffb3234) DISCONNECT not sent, error NETWORK ERROR.
23 1429 [iot_thread] [WARN ][NET][14290] Failed to close connection.
24 1429 [iot_thread] [INFO ][MQTT][14290] (MQTT connection 0x3ffb3234) Network connection closed.
25 1430 [iot_thread] [INFO ][MQTT][14300] (MQTT connection 0x3ffb3234) Network connection destroyed.
26 1430 [iot_thread] [INFO ][MQTT][14300] MQTT library cleanup done.
27 1430 [iot_thread] [ERROR][DEMO][14300] Error running demo.
28 1434 [iot_thread] [INFO ][INIT][14340] SDK cleanup done.
29 1434 [iot_thread] [INFO ][DEMO][14340] -------DEMO FINISHED-------

I'm wondering if the demo isn't really designed to work in this case. Increased timeout to 20000 ms. No change... Is the Wifi is going to sleep when it shouldn't? Just a few more tiny crumbs to clean up and we'll have it running!

monkeytronics commented 4 years ago

Don't know if it's relevant or not, but I just looked at the thing that was created. It's certificate has a policy attached. It looks like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Publish"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Connect and Publish... No mention of subscribe, which is what it's trying to do when it times out... Could that be relevant? I will check...

UPDATE:

Hello world 1!

It worked.

For the record, you can't use the sample provisioning template from https://aws.amazon.com/blogs/iot/setting-up-just-in-time-provisioning-with-aws-iot-core/. You need to add at least Allow: Subscribe to your policy. And Ideally lots more good stuff to limit your device to only putting stuff out on it's own channel! You don't want a certificate that allows devices to publish on another devices channel.

BTW @chegewara I owe you a beer. A real one! Or even a box of them. I really appreciate your expert help with this. And I have no doubt that this exchange will serve someone else like myself, who is less experienced, to figure out how to get this up and running.

dan4thewin commented 4 years ago

At a glance, it looks like there are no outstanding problems here. I'll close this issue now. If there is still a problem, please re-open.

sameerdasarwad commented 4 years ago

i have successfully done provisioning with the help of this issue, thanks to all , but i want to read thing name from the same file as we are giving thing name here , i don't want to declare my thing name static as we do in aws_clientcredential.h like

define clientcredentialIOT_THING_NAME "thing_name"

i want the thing name should be read from the same file i try but failed

chegewara commented 4 years ago

Did you try to replace #define clientcredentialIOT_THING_NAME "thing_name" with extern char* clientcredentialIOT_THING_NAME or something similar?

sameerdasarwad commented 4 years ago

yes i see another issue where you mention the same thing it click me idea then i successfully take thing name from thanks for the help i think i should take classes from you, your genius ;)