├── .gitignore ├── .gitmodules ├── Changelog.md ├── LICENSE ├── Makefile ├── README.md ├── certs.h ├── deploy.md ├── design-v5.png ├── homekit integration ├── esp-homekit-instructions.md ├── ota-api.c └── ota-api.h ├── local.mk ├── main.c ├── ota.c ├── ota.h ├── udplogger.c ├── udplogger.h └── versions1 ├── .gitignore ├── 2.2.6v ├── certs.sector ├── latest-pre-release ├── otaboot.bin ├── otabootbeta.bin └── otamain.bin ├── AAACertificateServices.pem ├── DigiCertGlobalRootCA.pem ├── DigiCertGlobalRootG2.pem ├── DigiCertHighAssuranceEVRootCA.pem ├── blank.bin ├── certs.sector └── secp384r1pub.der /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | firmware/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "esp-wifi-config"] 2 | path = esp-wifi-config 3 | url = https://github.com/HomeACcessoryKid/esp-wifi-config 4 | branch = ota-parameters 5 | [submodule "esp-wolfssl"] 6 | path = esp-wolfssl 7 | url = https://github.com/HomeACcessoryKid/esp-wolfssl 8 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.2.6+ Certificates updated 4 | - GitHub updated their server certificates so need for new Root CA certs 5 | - and install clarification in README 6 | 7 | ## 2.2.6 overclocking and fix in version string processing 8 | - overclock in ota_set_verify=ON fixes tighter timeout applied by GitHub 9 | - version string malloc was 1 byte short 10 | - keyid now uint16_t 11 | 12 | ## 2.2.5 updated certificates to be identical across all versions 13 | - contains DigiCertGlobalRootCA and DigiCertHighAssuranceEVRootCA 14 | - EC support put back in place 15 | - flow diagram still not updated 16 | 17 | ## 2.2.4 TEST: new scheme without initial certificate checking 18 | - to test, made it broken without EC support (like 2.2.0) 19 | - added a sysparam ota_count to set LCM outcomes also from user space 20 | - now requires a full signature on rboot4lcm binary 21 | - flow diagram not yet updated 22 | 23 | ## 2.2.3 updated GitHub root CA certificate 24 | - since the new certificates use EllipticCurve, versions prior to 2.2.1 crash when checking cert 25 | - note that the usage of EC makes the TLS process a lot slower 26 | 27 | ## 2.2.2 updated README and fixed wifi-config repo commit 28 | - removed now outdated info from README 29 | - the commit to wifi-config was not synced to github and got rolled back 30 | which meant that the hash code changed, even though the code did not... 31 | now added one cosmetic change after all 32 | 33 | ## 2.2.1 fixed support for ECDHE in TLS protocol 34 | - even though these protocols were offered in the ClientHello, they were broken 35 | - the supporting extensions were missing and the server never selected them 36 | 37 | ## 2.2.0 more robust parsing of Location header and added ota_string 38 | - even long headers existing before the Location header will be parsable 39 | - ota_string sysparam added to pass configuration to user app 40 | 41 | ## 2.1.2 creating smaller otamain.bin with -Os 42 | - because 2.1.1 otamain was too big and clobbered sysparam area 43 | 44 | ## 2.1.1 introduced SNI extension to fix issue created by GitHub new CDN 45 | - without SNI the server presents the wrong certificate 46 | - no need to add the intermediate certificate since it is offered by the server 47 | - this means the certificate file remains the same as in version 2.0.2 48 | 49 | ## 2.1.0 updated to the new certificate used by GitHub for the content distribution server 50 | - GitHub switched to their own domain and now use a DigiCert CA instead of Baltimore CA 51 | - make a final 0x0a and or 0x0d optional for the prerelease file 52 | - fixed an exception if user did not provide proper .sig file 53 | 54 | ## 2.0.2 overwrite RavenSystem/haa filename with lcm special version 55 | - this special version uses sysparam definition compatible with lcm 56 | 57 | ## 2.0.1 fix serial config menu after confirm stalling 58 | 59 | ## 2.0.0 stable new version with new functions 60 | - rboot4lcm gives control regardless of the user code 61 | - emergency mode allows to replace code OTA without dependency of GitHub 62 | - ability to define a led pin for visual feedback 63 | - load initial certificate from inside otaboot.bin 64 | - see the change log below for all the details 65 | 66 | ## 2.0.0 when using serial config, set ota_version=0.0.0 67 | 68 | ## 1.9.12 added led option to the serial config menu 69 | 70 | ## 1.9.11 led back to input mode after restart 71 | - if not, led continues output in last state 72 | 73 | ## 1.9.10 introduced blinking led feedback 74 | - uses the sysparam led_pin and starts blinking only from ota_init onwards 75 | - cosmetic fixes to design diagram 76 | 77 | ## 1.9.9 increased stack size for logsend and pre-wifi tasks 78 | - these were showing high watermark errors in testing 1.9.8 79 | - cosmetic changes to count-down messages 80 | 81 | ## 1.9.8 starting udplogger earlier better 82 | - wifi won't start until user_init is over so created a pre-wifi-task 83 | 84 | ## 1.9.7 start udplogger earlier and fix new safari issue 85 | - a tiny fix in wifi_config solves an incompatability with new Safari versions (macOS 86 | 10.15.4 and iOS 13.4) 87 | - a non-working attempt to make sure the count down messages show up in `nc -kulnw0 45678` 88 | 89 | ## 1.9.6 process led_pin info and grace period for power cycles 90 | - the wifi page allows setting info about the led pin. 91 | - choices are for GPIO 2, 4, 5, 13, 14, 15 since these are the least likely to create hardware issues 92 | - default is that the led is connected to +Vcc and is active on '0' 93 | - if your led connects to GND then you can check the box to indicate it is active on '1' 94 | - the led info is stored in sysparam_int8 led_pin and parsing is done for all values between -15 and 15 95 | - the values below zero indicate the led is active on '1' 96 | - Also, if count>4 from rboot4lcm then a message counting down from 10 allows abort in case of mistake 97 | - It is recommended to ALWAYS monitor LCM via `nc -kulnw0 45678` to check for these messages 98 | 99 | ## 1.9.5 Fix bootflags when uploading rboot 100 | - The uploading of a bootloader to sector 0 should replicate the flags1 and flags2 values of the previous bootloader. 101 | Else it will break the access to the flash in case it is not compatible. 102 | - esp-open-rtos used has been updated from [esp-open-rtos#a721fb0](https://github.com/SuperHouse/esp-open-rtos/commit/a721fb0bc7867ef421cd81fb89d486ed2a67ee9e) 103 | to [esp-open-rtos#bc97988](https://github.com/SuperHouse/esp-open-rtos/commit/bc979883c27ea57e948daa813e2bca752ebd39e1) 104 | - change the verification of the signature of otamain.bin prior to downloading this file instead of afterwards 105 | 106 | ## 1.9.4 load initial cert from inside OTABOOT plus details 107 | - clear lcm_beta instead of setting it to 0 108 | - allow for missing trailing / in emergency base URL 109 | - updated README.md and design diagram 110 | - changed order for checking otaboot.sig 111 | 112 | ## 1.9.3 clear LCM_beta after emergency 113 | - this didn't actually work 114 | 115 | ## 1.9.2 first test of LANmode fallback 116 | - after GitHub changed their http header this allows to recover 117 | - also called emergency mode 118 | 119 | ## proof of concept emergency mode 120 | 121 | ## 1.9.1 fix to make http header parsing more robust 122 | - after GitHub changed the syntax of Location: to location: 123 | 124 | ## Fixes bug caused by GitHub header changes (#22)
 125 | - Fixes case sensitive headers. 126 | - Makes blank space optional for some headers. 127 | - Rename strstr_lc() function to ota_strstr(). 128 | - Added '\n' to the beginning of some headers. 129 | 130 | ## 1.9.0 transfer from LCMdev 1.2.5 131 | - LCM has arrived to a new stage with its own adaptation of rboot - 132 | rboot4lcm - which counts powercycles. These can be used to check 133 | updates, reset wifi or factory reset. 134 | The versions 1.9.x will test at beta level what was started in the repo 135 | LCMdev v1.2.5 and lead up to version 2.0.0 136 | 137 | ## updates done in LCMdev 138 | - 1.2.5 really erase wifi settings and fix ota_beta readout 139 | - 1.2.4 changed ota_count_step to sysparam string 140 | - 1.2.3 ota_count_step defines power cycle behaviour 141 | - 1.2.2 documentation update 142 | - 1.2.2 improved wifi reset code 143 | - 1.2.1 fixed wifi erase and ota_new_layout 144 | - 1.2.0 read rtc power cycle count and reduce ota-main binary 145 | - 1.1.2 fix boot bits init and serial input 146 | - 1.1.1 fixed the position of boot update code to ota-main-only 147 | - 1.1.0 added update of boot loader and minor fixes 148 | - 1.0.1 initial adjustments after cloning
 149 | - 1.0.0 clone of Life-Cycle-Manager 1.0.0 150 | 151 | ## completed instructions how to integrate with esp-homekit 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #set this in local.mk 2 | #PROGRAM = main 3 | 4 | EXTRA_COMPONENTS = \ 5 | extras/sntp \ 6 | extras/http-parser \ 7 | extras/dhcpserver \ 8 | extras/rboot-ota \ 9 | $(abspath esp-wifi-config) \ 10 | $(abspath esp-wolfssl) 11 | 12 | FLASH_SIZE ?= 8 13 | 14 | EXTRA_WOLFSSL_CFLAGS = \ 15 | -DWOLFSSL_USER_SETTINGS \ 16 | -DWOLFSSL_STATIC_RSA \ 17 | -DUSE_SLOW_SHA \ 18 | -DUSE_SLOW_SHA2 \ 19 | -DHAVE_AESGCM \ 20 | -DHAVE_TLS_EXTENSIONS \ 21 | -DHAVE_SNI \ 22 | -DNO_MD5 \ 23 | -DNO_FILESYSTEM \ 24 | -DNO_WRITEV \ 25 | -DNO_WOLFSSL_SERVER \ 26 | -DNO_RABBIT \ 27 | -DNO_DH \ 28 | -DNO_PWDBASED \ 29 | -DNO_DES3 \ 30 | -DNO_ERROR_STRINGS \ 31 | -DNO_OLD_TLS \ 32 | -DNO_RC4 \ 33 | -DNO_PSK \ 34 | -DNO_MD4 \ 35 | -DNO_HC128 \ 36 | -DNO_DEV_RANDOM \ 37 | -DNO_SESSION_CACHE \ 38 | -DNO_DSA \ 39 | -DWOLFSSL_SHA512 \ 40 | -DWOLFSSL_SHA384 \ 41 | -DHAVE_ECC \ 42 | -DHAVE_ECC384 \ 43 | -DHAVE_ECC_SIGN \ 44 | -DHAVE_ECC_VERIFY \ 45 | -DHAVE_ECC_KEY_IMPORT \ 46 | -DHAVE_ECC_DHE \ 47 | -DHAVE_SUPPORTED_CURVES \ 48 | 49 | # -DDEBUG_WOLFSSL \ 50 | 51 | # -DNO_SHA \ 52 | # -DLARGE_STATIC_BUFFERS \ 53 | # -DSTATIC_CHUNKS_ONLY \ 54 | # -DRECORD_SIZE=1024 \ 55 | # -DHAVE_ONE_TIME_AUTH \ 56 | # -DNO_CODING \ 57 | # -DNO_INLINE \ 58 | # -DBUILD_TLS_RSA_WITH_AES_128_GCM_SHA256 \ 59 | 60 | esp-wolfssl_CFLAGS += $(EXTRA_WOLFSSL_CFLAGS) 61 | 62 | EXTRA_CFLAGS += $(EXTRA_WOLFSSL_CFLAGS) 63 | EXTRA_CFLAGS += -DTCP_MSS=1460 -DTCP_WND=2920 64 | 65 | ifdef OTAVERSION 66 | EXTRA_CFLAGS += -DOTAVERSION=\"$(OTAVERSION)\" 67 | endif 68 | 69 | ifdef OTABETA 70 | EXTRA_CFLAGS += -DOTABETA 71 | endif 72 | 73 | EXTRA_CFLAGS += -DDEFAULT_SYSPARAM_SECTORS=0 74 | 75 | #EXTRA_CFLAGS += -DLWIP_DEBUG 76 | EXTRA_CFLAGS += -DDNS_TABLE_SIZE=2 77 | 78 | include $(SDK_PATH)/common.mk 79 | 80 | monitor: 81 | $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Life-Cycle-Manager (LCM) 2 | Initial install, WiFi settings and over the air firmware upgrades for any esp-open-rtos repository on GitHub 3 | (c) 2018-2024 HomeAccessoryKid 4 | 5 | ## Update December 2023 6 | It looks like GitHub has put a 10s timeout on their TLS stack. 7 | When verifying the server certificate, we take >15s and the server finishes the connection. 8 | Version 2.2.6 tries to fix this by using overclock during this phase. 9 | 10 | ## Update season 16 April 2022 11 | After 14 months, version 2.1.2 will get upgraded to version 2.2.5. So be aware your own app update will take extra long. 12 | It would be recommendable to also update devices that do not have an user app update. 13 | Not that 2.1.2 is broken or in danger, but 2.2.5 is more future proof. 14 | 15 | ## Version 16 | [Changelog](https://github.com/HomeACcessoryKid/life-cycle-manager/blob/master/Changelog.md) 17 | With version 2.0.0 LCM has arrived to a new stage with its own adaptation of rboot - rboot4lcm - which counts powercycles. 18 | These are used to check updates, reset wifi, clear or set LCM_beta or factory reset. It also gives access to the emergency mode. 19 | Setting a value for a led_pin visual feedback is possible. 20 | By having introduced the latest-pre-release concept in version 1.0.0, users (and LCM itself) can test new software before exposing it to production devices. 21 | See the 'How to use it' section. 22 | 23 | https://github.com/HomeACcessoryKid/ota-demo has been upgraded to offer system-parameter editing features which allows for flexible testing of the LCM code. 24 | 25 | ## Scope 26 | This is a program that allows any simple repository based on esp-open-rtos on esp8266 to solve its life cycle tasks. 27 | - assign a WiFi AccessPoint for internet connectivity 28 | - specify and install the user app without flashing over cable (once the LCM image is in) 29 | - assign app specific and device specific parameters 30 | - update the user app over the air by using releases and versions on GitHub 31 | 32 | ## New features version 2 33 | 34 | This new LCM code is able to load/update the bootloader from github. 35 | The new bootloader is able to count the amount of short power cycles (<1.5s) 36 | From the second cycle the cycles must be shorter than 4 seconds. Also a LED is lit if defined. 37 | The boot loader conveys the count to the loaded code using the rtc.temp_rom value 38 | User code is allowed the values from 1-4 39 | - 1 : this is a normal boot 40 | - 2-4: users choice in user code (communicate 3 to user or risk a bit and use 2, 3 and 4 separatly) 41 | 42 | If count > 4 the bootloader launches LCM otamain.bin in rom[1] 43 | 44 | For these values the behaviour is controlled through the sysparam string `ota_count_step`. 45 | The default value of 3 reduces the chance of user misscounting and triggering something else than intended or playfull children. 46 | 47 | Note that with LCM_beta mode and wifi erased you can set any emergency fallback server to collect a new signed version of otaboot.bin. 48 | This is to prevent a lockout as witnessed when Github changed their webserver in 2020. 49 | Tested with macOS [builtin apache server](https://discussions.apple.com/docs/DOC-13841). 50 | By monitoring the output with the terminal command `nc -kulnw0 45678` you have 10 seconds to see which action was chosen before it executes. 51 | 52 | If `ota_count_step=="3"` (default) 53 | - 5-7: check for new code (communicate 6 to user) 54 | - 8-10: erase wifi info and clear LCM_beta mode (communicate 9 to user) 55 | - 11-13: erase wifi info and set LCM_beta mode and gain access to emergency mode (communicate 12 to user) 56 | - 14-16: factory reset (communicate 15 to user) 57 | 58 | If `ota_count_step=="2"` 59 | - 5-6: check for new code (communicate 5 to user) 60 | - 7-8: erase wifi info and clear LCM_beta mode (communicate 7 to user) 61 | - 9-10: erase wifi info and set LCM_beta mode and gain access to emergency mode (communicate 9 to user) 62 | - 11-12: factory reset (communicate 11 to user) 63 | 64 | If `ota_count_step=="1"` 65 | - 5: check for new code (communicate 5 to user) 66 | - 6: erase wifi info and clear LCM_beta mode (communicate 6 to user) 67 | - 7: erase wifi info and set LCM_beta mode and gain access to emergency mode (communicate 7 to user) 68 | - 8: factory reset (communicate 8 to user) 69 | 70 | Missing or other `ota_count_step` values will be interpreted as 3 71 | 72 | In version 2.2.5 there are two new features: 73 | There is a new possibility for those user apps that need some configuration data to work that is specific to each instantiation. 74 | One can set the `ota_string` parameter which can be parsed by the user app to set e.g. MQTT server, user and password or whatever else you fancy. 75 | Since it is up to the user app to parse it, you test whatever works for you within the cgi transfer of parameters. 76 | Also, using the 'erase wifi' mode, new settings can be set again when needed. 77 | 78 | There also exists the possibility to set the sysparam `ota_count` to activate the 'erase wifi' etc from the user app as well. 79 | 80 | 81 | ## Non-typical solution 82 | The solution is dedicated to a particular set of repositories and devices, which I consider is worth solving. 83 | - Many ESP8266 devices have only 1Mbyte of flash 84 | - Many people have no interest in setting up a software (web)server to solve their upgrade needs 85 | - Many repositories have no ram or flash available to combine the upgrade routines and the user routines 86 | - For repositories that will be applied by MANY people, a scalable software server is needed 87 | - be able to setup wifi securly while not depending on an electrical connection whenever wifi needs setup *) 88 | 89 | If all of the above would not be an issue, the typical solution would be to 90 | - combine the usercode and the upgrade code 91 | - load a full new code image side by side with the old proven image 92 | - have a checkpoint in the code that 'proofs' that the upgrade worked or else it will fall back to the old code 93 | - run a server from a home computer at dedicated moments 94 | - setup the wifi password when electrically connected or send it unencrypted and cross fingers no-one is snooping *) 95 | 96 | In my opinion, for the target group, the typical solution doesn't work and so LCM will handle it. 97 | Also it turns out that there are no out-of-the-box solutions of the typical case out there so if you are fine with the limitations of LCM, just enjoy it... or roll your own. 98 | (PS. the balance is much less black and white but you get the gist) 99 | *) This feature is not yet implemented (it is quite hard), so 'cross your fingers'. 100 | 101 | ## Benefits 102 | - Having over the air firmware updates is very important to be able to close security holes and prevent others to introduce malicious code 103 | - The user app only requires a few simple lines of code so no increase in RAM usage or complexity and an overall smaller flash footprint 104 | - Through the use of cryptography throughout the life cycle manager, it is not possible for any outside party to squeeze in any malicious code nor to snoop the password of the WiFi AccessPoint *) 105 | - The fact that it is hosted on GitHub means your code is protected by the https certificates from GitHub and that no matter how many devices are out there, it will scale 106 | - The code is publicly visible and can be audited at all times so that security is at its best 107 | - The user could add their own DigitalSignature (ecDSA) although it is not essential. (feature on todolist) 108 | - The producer of hardware could preinstall the LCM code on hardware thereby allowing the final user to select any relevant repository. 109 | - Many off-the-shelf hardware devices have OTA code that can be highjacked and replaced with LCM so no solder iron or mechanical hacks needed (feature on todolist) 110 | 111 | ## Can I trust you? 112 | If you feel you need 100% control, you can fork this repository, create your own private key and do the life cycle of the LCM yourself. 113 | But since the code of LCM is public, by audit it is unlikely that malicious events will happen. It is up to you. And if you have ideas how to improve on this subject, please share your ideas in the issue #1 that is open for this reason. 114 | 115 | ## How to use it 116 | User code preparation part 117 | - in an appropriate part of your code, add these two function calls which will trigger an update if you want to: 118 | - ```rboot_set_temp_rom(1); //select the OTA main routine``` 119 | - ```sdk_system_restart(); //#include ``` 120 | - there is a bug in the esp SDK such that if you do not power cycle the chip after flashing, restart is unreliable 121 | - compile your own code and create a signature (see below) 122 | - in the shell, `echo -n x.y.z > latest-pre-release` 123 | - commit this to Git and sync it with GitHub 124 | - Start a release from this commit and take care the version is in x.y.z format 125 | - Attach/Upload the binary and the signature and create the release _as a pre-release_ **) 126 | - Now go to the current 'latest release', ie the non-pre-release one you’re about to improve upon, edit its list of assets and either add or remove and replace the `latest-pre-release` file so that we now have a pointer to the pre-release we created above 127 | **) except the very first time, you must set it as latest release 128 | 129 | Now test your new code by using a device that you enroll to the pre-release versions (a checkbox in the wifi-setup page). 130 | 131 | - If fatal errors are found, just start a new version and leave this one as a pre-release. 132 | - Once a version is approved you can mark it as 'latest release'. 133 | - If a 'latest release' is also the latest release overall, a latest-pre-release is not needed, it points to itself. 134 | 135 | User device setup part 136 | - clone or fork the LCM repository (or download just the otaboot.bin file) 137 | - wipe out the entire flash (not essential, but cleaner) 138 | - upload these three files making sure to specify a 1MByte flash size: 139 | ``` 140 | esptool.py write_flash -fs 1MB 141 | 0x0 /esp-open-rtos/bootloader/firmware_prebuilt/rboot.bin 142 | 0x1000 /esp-open-rtos/bootloader/firmware_prebuilt/blank_config.bin 143 | 0x2000 /otaboot.bin 144 | ``` 145 | - (or otabootbeta.bin if enrolling in the LCM pre-release testing) 146 | - start the code and either use serial input menu or wait till the Wifi AP starts. 147 | - set the repository you want to use in your device: `yourname/repository` and name of binary 148 | - then select your Wifi AP and insert your password 149 | - once selected, it will take up to 5 minutes for the system to download the ota-main software in the second bootsector and the user code in the 1st boot sector 150 | - you can follow progress on the serial port or use the UDPlogger using the terminal command `nc -kulnw0 45678` 151 | 152 | ## Creating a user app DigitalSignature 153 | from the directory where `make` is run execute: 154 | ``` 155 | openssl sha384 -binary -out firmware/main.bin.sig firmware/main.bin 156 | printf "%08x" `cat firmware/main.bin | wc -c`| xxd -r -p >>firmware/main.bin.sig 157 | ``` 158 | 159 | ## How it works 160 | This design serves to read through the code base. 161 | The actual entry point of the process is the self-updater which is called ota-boot and which is flashed by serial cable. 162 | 163 | ![](https://github.com/HomeACcessoryKid/life-cycle-manager/blob/master/design-v5.png) 164 | 165 | ### Concepts 166 | ``` 167 | User app(0) 168 | v.X triggers 169 | ``` 170 | The usercode Main app is running in bootslot 0 at version x. It can trigger a switch to bootslot 1. 171 | Also the tuned bootloader [rBoot4LCM](https://github.com/HomeACcessoryKid/rboot4lcm) can switch to bootslot 1. 172 | 173 | ``` 174 | powercycles select: 175 | ``` 176 | Based on the number of cycles, we will check for new versions, reset the wifi parameters or with lcmbeta allow the setting of an emergency server. 177 | Choosing factory reset will erase all the usercode and parameters so no sensitive data stays behind. 178 | After this the normal update cycle starts, except if an emergency server is defined 179 | 180 | ``` 181 | use http://not.github.com/somewhere/ 182 | ``` 183 | After resetting wifi and selecting lcmbeta mode (12 power cycles) the user can specify another base location where the files otaboot.bin.sig and otaboot.bin will be collected. 184 | This enters emergency mode. If the signature is valid against the public key of LCM then it will replace the bootslot 0 and continue to update otamain etc. 185 | ``` 186 | (t) 187 | ``` 188 | This represents an exponential hold-off to prevent excesive hammering on the github servers. It resets at a power-cycle. 189 | 190 | ``` 191 | download certificate signature 192 | certificate update? 193 | Download Certificates 194 | ``` 195 | This is a file that contains the checksum of the sector containing three certificates/keys 196 | - public key of HomeACessoryKid that signs the certificate/key sector 197 | - root CA used by GitHub 198 | - root CA used by the DistributedContentProvider (now GitHub's own, Amazon before release 2.1.0) 199 | 200 | Once downloaded, the signature is checked against the known public key and the sha384 checksum of the active sector is compared to the checksum in the signature file. If equal, we move on. If not, we download the updated sector file to the standby sector. 201 | 202 | ``` 203 | signature match? 204 | ``` 205 | From the sector containing up to date certificates the sha384 hash has been signed by the private key of LCM. 206 | Using the available public key, the validity is verified. 207 | From here, the files are intended to be downloaded with server certificate verification activated. If this fails, the server is marked as invalid. 208 | 209 | ``` 210 | new boot version? 211 | ``` 212 | This will download the latest version of [rboot4lcm](https://github.com/HomeACcessoryKid/rboot4lcm) 213 | 214 | ``` 215 | new OTA version? 216 | download OTA-boot➔0 217 | update OTA-main➔1 218 | sig & checksum OK? 219 | ``` 220 | 221 | We verify if there is an update of this OTA repo itself? If so, we use ota-boot to self update. After this we have the latest OTA code. 222 | 223 | ``` 224 | server valid? 225 | ``` 226 | If by checking the certificates the server is marked invalid, we return to the main app in boot slot 0 and we report by syslog to a server (to be determinded) so we learn that github has changed its certificate CA provider and HomeACessoryKid can issue a new certificate sector. 227 | Now that the downloading from GitHub has been secured, we can trust whatever we download based on a checksum. 228 | 229 | ``` 230 | OTA-main(1) updates User app➔0 231 | sig & checksum OK? 232 | ``` 233 | Using the baseURL info and the version as stored in sysparam area, the latest binary is found and downloaded if needed. If the checksum does not work out, we return to the OTA app start point considering we cannot run the old code anymore. 234 | But normally we boot the new code and the mission is done. 235 | 236 | Note that switching from boot=slot1 to boot=slot0 does not require a reflash 237 | 238 | 239 | 240 | ## AS-IS disclaimer and License 241 | While I pride myself to make this software error free and backward compatible and otherwise perfect, this is the 242 | result of a hobby etc. etc. etc. So don't expect me to be responsible for anything... 243 | 244 | See the LICENSE file for license information 245 | -------------------------------------------------------------------------------- /certs.h: -------------------------------------------------------------------------------- 1 | unsigned char certs_sector[] = { 2 | 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 3 | 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 4 | 0x94, 0x30, 0xcc, 0x5c, 0x4e, 0x50, 0x29, 0x15, 0xff, 0x3d, 0x41, 0xda, 5 | 0xe9, 0xe5, 0xe3, 0x64, 0x27, 0xb3, 0x96, 0x07, 0x61, 0x36, 0xa4, 0x83, 6 | 0x28, 0x4d, 0xfd, 0xf9, 0x05, 0x4f, 0x0d, 0x82, 0xfa, 0x57, 0x26, 0x06, 7 | 0xb7, 0xe8, 0x1f, 0x22, 0x2d, 0x2b, 0x8a, 0xd2, 0xff, 0x6e, 0xac, 0x59, 8 | 0x73, 0x00, 0xb5, 0xe4, 0x9c, 0x5f, 0x68, 0xab, 0x31, 0xd3, 0x9f, 0xad, 9 | 0xbb, 0x3d, 0xc8, 0x4f, 0x6d, 0x38, 0xe6, 0x6f, 0xb8, 0xb3, 0xf7, 0x22, 10 | 0x21, 0xb7, 0xf2, 0xe4, 0xb7, 0x75, 0xed, 0x09, 0x2c, 0x87, 0x2b, 0x00, 11 | 0x56, 0x5b, 0x81, 0xe8, 0x64, 0x5c, 0x2c, 0x84, 0x85, 0xc0, 0x3c, 0x34, 12 | 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 13 | 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 14 | 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x44, 0x72, 0x7a, 0x43, 0x43, 15 | 0x41, 0x70, 0x65, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 16 | 0x43, 0x44, 0x76, 0x67, 0x56, 0x70, 0x42, 0x43, 0x52, 0x72, 0x47, 0x68, 17 | 0x64, 0x57, 0x72, 0x4a, 0x57, 0x5a, 0x48, 0x48, 0x53, 0x6a, 0x41, 0x4e, 18 | 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 19 | 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x42, 0x68, 0x0a, 0x4d, 0x51, 0x73, 20 | 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 21 | 0x56, 0x55, 0x7a, 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 22 | 0x45, 0x43, 0x68, 0x4d, 0x4d, 0x52, 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, 23 | 0x6c, 0x63, 0x6e, 0x51, 0x67, 0x53, 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x6b, 24 | 0x77, 0x46, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4c, 0x45, 0x78, 0x42, 25 | 0x33, 0x0a, 0x64, 0x33, 0x63, 0x75, 0x5a, 0x47, 0x6c, 0x6e, 0x61, 0x57, 26 | 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, 0x59, 0x32, 0x39, 0x74, 0x4d, 0x53, 27 | 0x41, 0x77, 0x48, 0x67, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x78, 28 | 0x64, 0x45, 0x61, 0x57, 0x64, 0x70, 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, 29 | 0x42, 0x48, 0x62, 0x47, 0x39, 0x69, 0x59, 0x57, 0x77, 0x67, 0x55, 0x6d, 30 | 0x39, 0x76, 0x64, 0x43, 0x42, 0x44, 0x0a, 0x51, 0x54, 0x41, 0x65, 0x46, 31 | 0x77, 0x30, 0x77, 0x4e, 0x6a, 0x45, 0x78, 0x4d, 0x54, 0x41, 0x77, 0x4d, 32 | 0x44, 0x41, 0x77, 0x4d, 0x44, 0x42, 0x61, 0x46, 0x77, 0x30, 0x7a, 0x4d, 33 | 0x54, 0x45, 0x78, 0x4d, 0x54, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 34 | 0x44, 0x42, 0x61, 0x4d, 0x47, 0x45, 0x78, 0x43, 0x7a, 0x41, 0x4a, 0x42, 35 | 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, 0x0a, 36 | 0x4d, 0x52, 0x55, 0x77, 0x45, 0x77, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, 37 | 0x45, 0x77, 0x78, 0x45, 0x61, 0x57, 0x64, 0x70, 0x51, 0x32, 0x56, 0x79, 38 | 0x64, 0x43, 0x42, 0x4a, 0x62, 0x6d, 0x4d, 0x78, 0x47, 0x54, 0x41, 0x58, 39 | 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x73, 0x54, 0x45, 0x48, 0x64, 0x33, 40 | 0x64, 0x79, 0x35, 0x6b, 0x61, 0x57, 0x64, 0x70, 0x59, 0x32, 0x56, 0x79, 41 | 0x64, 0x43, 0x35, 0x6a, 0x0a, 0x62, 0x32, 0x30, 0x78, 0x49, 0x44, 0x41, 42 | 0x65, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x4d, 0x54, 0x46, 0x30, 0x52, 43 | 0x70, 0x5a, 0x32, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x49, 0x45, 0x64, 44 | 0x73, 0x62, 0x32, 0x4a, 0x68, 0x62, 0x43, 0x42, 0x53, 0x62, 0x32, 0x39, 45 | 0x30, 0x49, 0x45, 0x4e, 0x42, 0x4d, 0x49, 0x49, 0x42, 0x49, 0x6a, 0x41, 46 | 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x0a, 0x39, 0x77, 47 | 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 48 | 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 49 | 0x45, 0x41, 0x34, 0x6a, 0x76, 0x68, 0x45, 0x58, 0x4c, 0x65, 0x71, 0x4b, 50 | 0x54, 0x54, 0x6f, 0x31, 0x65, 0x71, 0x55, 0x4b, 0x4b, 0x50, 0x43, 0x33, 51 | 0x65, 0x51, 0x79, 0x61, 0x4b, 0x6c, 0x37, 0x68, 0x4c, 0x4f, 0x6c, 0x6c, 52 | 0x73, 0x42, 0x0a, 0x43, 0x53, 0x44, 0x4d, 0x41, 0x5a, 0x4f, 0x6e, 0x54, 53 | 0x6a, 0x43, 0x33, 0x55, 0x2f, 0x64, 0x44, 0x78, 0x47, 0x6b, 0x41, 0x56, 54 | 0x35, 0x33, 0x69, 0x6a, 0x53, 0x4c, 0x64, 0x68, 0x77, 0x5a, 0x41, 0x41, 55 | 0x49, 0x45, 0x4a, 0x7a, 0x73, 0x34, 0x62, 0x67, 0x37, 0x2f, 0x66, 0x7a, 56 | 0x54, 0x74, 0x78, 0x52, 0x75, 0x4c, 0x57, 0x5a, 0x73, 0x63, 0x46, 0x73, 57 | 0x33, 0x59, 0x6e, 0x46, 0x6f, 0x39, 0x37, 0x0a, 0x6e, 0x68, 0x36, 0x56, 58 | 0x66, 0x65, 0x36, 0x33, 0x53, 0x4b, 0x4d, 0x49, 0x32, 0x74, 0x61, 0x76, 59 | 0x65, 0x67, 0x77, 0x35, 0x42, 0x6d, 0x56, 0x2f, 0x53, 0x6c, 0x30, 0x66, 60 | 0x76, 0x42, 0x66, 0x34, 0x71, 0x37, 0x37, 0x75, 0x4b, 0x4e, 0x64, 0x30, 61 | 0x66, 0x33, 0x70, 0x34, 0x6d, 0x56, 0x6d, 0x46, 0x61, 0x47, 0x35, 0x63, 62 | 0x49, 0x7a, 0x4a, 0x4c, 0x76, 0x30, 0x37, 0x41, 0x36, 0x46, 0x70, 0x74, 63 | 0x0a, 0x34, 0x33, 0x43, 0x2f, 0x64, 0x78, 0x43, 0x2f, 0x2f, 0x41, 0x48, 64 | 0x32, 0x68, 0x64, 0x6d, 0x6f, 0x52, 0x42, 0x42, 0x59, 0x4d, 0x71, 0x6c, 65 | 0x31, 0x47, 0x4e, 0x58, 0x52, 0x6f, 0x72, 0x35, 0x48, 0x34, 0x69, 0x64, 66 | 0x71, 0x39, 0x4a, 0x6f, 0x7a, 0x2b, 0x45, 0x6b, 0x49, 0x59, 0x49, 0x76, 67 | 0x55, 0x58, 0x37, 0x51, 0x36, 0x68, 0x4c, 0x2b, 0x68, 0x71, 0x6b, 0x70, 68 | 0x4d, 0x66, 0x54, 0x37, 0x50, 0x0a, 0x54, 0x31, 0x39, 0x73, 0x64, 0x6c, 69 | 0x36, 0x67, 0x53, 0x7a, 0x65, 0x52, 0x6e, 0x74, 0x77, 0x69, 0x35, 0x6d, 70 | 0x33, 0x4f, 0x46, 0x42, 0x71, 0x4f, 0x61, 0x73, 0x76, 0x2b, 0x7a, 0x62, 71 | 0x4d, 0x55, 0x5a, 0x42, 0x66, 0x48, 0x57, 0x79, 0x6d, 0x65, 0x4d, 0x72, 72 | 0x2f, 0x79, 0x37, 0x76, 0x72, 0x54, 0x43, 0x30, 0x4c, 0x55, 0x71, 0x37, 73 | 0x64, 0x42, 0x4d, 0x74, 0x6f, 0x4d, 0x31, 0x4f, 0x2f, 0x34, 0x0a, 0x67, 74 | 0x64, 0x57, 0x37, 0x6a, 0x56, 0x67, 0x2f, 0x74, 0x52, 0x76, 0x6f, 0x53, 75 | 0x53, 0x69, 0x69, 0x63, 0x4e, 0x6f, 0x78, 0x42, 0x4e, 0x33, 0x33, 0x73, 76 | 0x68, 0x62, 0x79, 0x54, 0x41, 0x70, 0x4f, 0x42, 0x36, 0x6a, 0x74, 0x53, 77 | 0x6a, 0x31, 0x65, 0x74, 0x58, 0x2b, 0x6a, 0x6b, 0x4d, 0x4f, 0x76, 0x4a, 78 | 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x6f, 0x32, 0x4d, 0x77, 0x59, 79 | 0x54, 0x41, 0x4f, 0x0a, 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x38, 0x42, 80 | 0x41, 0x66, 0x38, 0x45, 0x42, 0x41, 0x4d, 0x43, 0x41, 0x59, 0x59, 0x77, 81 | 0x44, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 82 | 0x42, 0x41, 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x64, 83 | 0x42, 0x67, 0x4e, 0x56, 0x48, 0x51, 0x34, 0x45, 0x46, 0x67, 0x51, 0x55, 84 | 0x41, 0x39, 0x35, 0x51, 0x4e, 0x56, 0x62, 0x52, 0x0a, 0x54, 0x4c, 0x74, 85 | 0x6d, 0x38, 0x4b, 0x50, 0x69, 0x47, 0x78, 0x76, 0x44, 0x6c, 0x37, 0x49, 86 | 0x39, 0x30, 0x56, 0x55, 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 87 | 0x6a, 0x42, 0x42, 0x67, 0x77, 0x46, 0x6f, 0x41, 0x55, 0x41, 0x39, 0x35, 88 | 0x51, 0x4e, 0x56, 0x62, 0x52, 0x54, 0x4c, 0x74, 0x6d, 0x38, 0x4b, 0x50, 89 | 0x69, 0x47, 0x78, 0x76, 0x44, 0x6c, 0x37, 0x49, 0x39, 0x30, 0x56, 0x55, 90 | 0x77, 0x0a, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 91 | 0x63, 0x4e, 0x41, 0x51, 0x45, 0x46, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 92 | 0x45, 0x42, 0x41, 0x4d, 0x75, 0x63, 0x4e, 0x36, 0x70, 0x49, 0x45, 0x78, 93 | 0x49, 0x4b, 0x2b, 0x74, 0x31, 0x45, 0x6e, 0x45, 0x39, 0x53, 0x73, 0x50, 94 | 0x54, 0x66, 0x72, 0x67, 0x54, 0x31, 0x65, 0x58, 0x6b, 0x49, 0x6f, 0x79, 95 | 0x51, 0x59, 0x2f, 0x45, 0x73, 0x72, 0x0a, 0x68, 0x4d, 0x41, 0x74, 0x75, 96 | 0x64, 0x58, 0x48, 0x2f, 0x76, 0x54, 0x42, 0x48, 0x31, 0x6a, 0x4c, 0x75, 97 | 0x47, 0x32, 0x63, 0x65, 0x6e, 0x54, 0x6e, 0x6d, 0x43, 0x6d, 0x72, 0x45, 98 | 0x62, 0x58, 0x6a, 0x63, 0x4b, 0x43, 0x68, 0x7a, 0x55, 0x79, 0x49, 0x6d, 99 | 0x5a, 0x4f, 0x4d, 0x6b, 0x58, 0x44, 0x69, 0x71, 0x77, 0x38, 0x63, 0x76, 100 | 0x70, 0x4f, 0x70, 0x2f, 0x32, 0x50, 0x56, 0x35, 0x41, 0x64, 0x67, 0x0a, 101 | 0x30, 0x36, 0x4f, 0x2f, 0x6e, 0x56, 0x73, 0x4a, 0x38, 0x64, 0x57, 0x4f, 102 | 0x34, 0x31, 0x50, 0x30, 0x6a, 0x6d, 0x50, 0x36, 0x50, 0x36, 0x66, 0x62, 103 | 0x74, 0x47, 0x62, 0x66, 0x59, 0x6d, 0x62, 0x57, 0x30, 0x57, 0x35, 0x42, 104 | 0x6a, 0x66, 0x49, 0x74, 0x74, 0x65, 0x70, 0x33, 0x53, 0x70, 0x2b, 0x64, 105 | 0x57, 0x4f, 0x49, 0x72, 0x57, 0x63, 0x42, 0x41, 0x49, 0x2b, 0x30, 0x74, 106 | 0x4b, 0x49, 0x4a, 0x46, 0x0a, 0x50, 0x6e, 0x6c, 0x55, 0x6b, 0x69, 0x61, 107 | 0x59, 0x34, 0x49, 0x42, 0x49, 0x71, 0x44, 0x66, 0x76, 0x38, 0x4e, 0x5a, 108 | 0x35, 0x59, 0x42, 0x62, 0x65, 0x72, 0x4f, 0x67, 0x4f, 0x7a, 0x57, 0x36, 109 | 0x73, 0x52, 0x42, 0x63, 0x34, 0x4c, 0x30, 0x6e, 0x61, 0x34, 0x55, 0x55, 110 | 0x2b, 0x4b, 0x72, 0x6b, 0x32, 0x55, 0x38, 0x38, 0x36, 0x55, 0x41, 0x62, 111 | 0x33, 0x4c, 0x75, 0x6a, 0x45, 0x56, 0x30, 0x6c, 0x73, 0x0a, 0x59, 0x53, 112 | 0x45, 0x59, 0x31, 0x51, 0x53, 0x74, 0x65, 0x44, 0x77, 0x73, 0x4f, 0x6f, 113 | 0x42, 0x72, 0x70, 0x2b, 0x75, 0x76, 0x46, 0x52, 0x54, 0x70, 0x32, 0x49, 114 | 0x6e, 0x42, 0x75, 0x54, 0x68, 0x73, 0x34, 0x70, 0x46, 0x73, 0x69, 0x76, 115 | 0x39, 0x6b, 0x75, 0x58, 0x63, 0x6c, 0x56, 0x7a, 0x44, 0x41, 0x47, 0x79, 116 | 0x53, 0x6a, 0x34, 0x64, 0x7a, 0x70, 0x33, 0x30, 0x64, 0x38, 0x74, 0x62, 117 | 0x51, 0x6b, 0x0a, 0x43, 0x41, 0x55, 0x77, 0x37, 0x43, 0x32, 0x39, 0x43, 118 | 0x37, 0x39, 0x46, 0x76, 0x31, 0x43, 0x35, 0x71, 0x66, 0x50, 0x72, 0x6d, 119 | 0x41, 0x45, 0x53, 0x72, 0x63, 0x69, 0x49, 0x78, 0x70, 0x67, 0x30, 0x58, 120 | 0x34, 0x30, 0x4b, 0x50, 0x4d, 0x62, 0x70, 0x31, 0x5a, 0x57, 0x56, 0x62, 121 | 0x64, 0x34, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 122 | 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 123 | 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 124 | 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 125 | 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 126 | 0x49, 0x44, 0x78, 0x54, 0x43, 0x43, 0x41, 0x71, 0x32, 0x67, 0x41, 0x77, 127 | 0x49, 0x42, 0x41, 0x67, 0x49, 0x51, 0x41, 0x71, 0x78, 0x63, 0x4a, 0x6d, 128 | 0x6f, 0x4c, 0x51, 0x4a, 0x75, 0x50, 0x43, 0x33, 0x6e, 0x79, 0x72, 0x6b, 129 | 0x59, 0x6c, 0x64, 0x7a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 130 | 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 131 | 0x42, 0x73, 0x0a, 0x4d, 0x51, 0x73, 0x77, 0x43, 0x51, 0x59, 0x44, 0x56, 132 | 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x55, 0x7a, 0x45, 0x56, 0x4d, 133 | 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, 0x43, 0x68, 0x4d, 0x4d, 0x52, 134 | 0x47, 0x6c, 0x6e, 0x61, 0x55, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x67, 0x53, 135 | 0x57, 0x35, 0x6a, 0x4d, 0x52, 0x6b, 0x77, 0x46, 0x77, 0x59, 0x44, 0x56, 136 | 0x51, 0x51, 0x4c, 0x45, 0x78, 0x42, 0x33, 0x0a, 0x64, 0x33, 0x63, 0x75, 137 | 0x5a, 0x47, 0x6c, 0x6e, 0x61, 0x57, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x75, 138 | 0x59, 0x32, 0x39, 0x74, 0x4d, 0x53, 0x73, 0x77, 0x4b, 0x51, 0x59, 0x44, 139 | 0x56, 0x51, 0x51, 0x44, 0x45, 0x79, 0x4a, 0x45, 0x61, 0x57, 0x64, 0x70, 140 | 0x51, 0x32, 0x56, 0x79, 0x64, 0x43, 0x42, 0x49, 0x61, 0x57, 0x64, 0x6f, 141 | 0x49, 0x45, 0x46, 0x7a, 0x63, 0x33, 0x56, 0x79, 0x59, 0x57, 0x35, 0x6a, 142 | 0x0a, 0x5a, 0x53, 0x42, 0x46, 0x56, 0x69, 0x42, 0x53, 0x62, 0x32, 0x39, 143 | 0x30, 0x49, 0x45, 0x4e, 0x42, 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x41, 144 | 0x32, 0x4d, 0x54, 0x45, 0x78, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, 145 | 0x77, 0x4d, 0x46, 0x6f, 0x58, 0x44, 0x54, 0x4d, 0x78, 0x4d, 0x54, 0x45, 146 | 0x78, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x44, 0x41, 0x77, 0x4d, 0x46, 0x6f, 147 | 0x77, 0x62, 0x44, 0x45, 0x4c, 0x0a, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 148 | 0x55, 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x56, 0x4d, 0x78, 0x46, 0x54, 149 | 0x41, 0x54, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x54, 0x44, 0x45, 150 | 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x44, 0x5a, 0x58, 0x4a, 0x30, 0x49, 0x45, 151 | 0x6c, 0x75, 0x59, 0x7a, 0x45, 0x5a, 0x4d, 0x42, 0x63, 0x47, 0x41, 0x31, 152 | 0x55, 0x45, 0x43, 0x78, 0x4d, 0x51, 0x64, 0x33, 0x64, 0x33, 0x0a, 0x4c, 153 | 0x6d, 0x52, 0x70, 0x5a, 0x32, 0x6c, 0x6a, 0x5a, 0x58, 0x4a, 0x30, 0x4c, 154 | 0x6d, 0x4e, 0x76, 0x62, 0x54, 0x45, 0x72, 0x4d, 0x43, 0x6b, 0x47, 0x41, 155 | 0x31, 0x55, 0x45, 0x41, 0x78, 0x4d, 0x69, 0x52, 0x47, 0x6c, 0x6e, 0x61, 156 | 0x55, 0x4e, 0x6c, 0x63, 0x6e, 0x51, 0x67, 0x53, 0x47, 0x6c, 0x6e, 0x61, 157 | 0x43, 0x42, 0x42, 0x63, 0x33, 0x4e, 0x31, 0x63, 0x6d, 0x46, 0x75, 0x59, 158 | 0x32, 0x55, 0x67, 0x0a, 0x52, 0x56, 0x59, 0x67, 0x55, 0x6d, 0x39, 0x76, 159 | 0x64, 0x43, 0x42, 0x44, 0x51, 0x54, 0x43, 0x43, 0x41, 0x53, 0x49, 0x77, 160 | 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, 161 | 0x41, 0x51, 0x45, 0x42, 0x42, 0x51, 0x41, 0x44, 0x67, 0x67, 0x45, 0x50, 162 | 0x41, 0x44, 0x43, 0x43, 0x41, 0x51, 0x6f, 0x43, 0x67, 0x67, 0x45, 0x42, 163 | 0x41, 0x4d, 0x62, 0x4d, 0x35, 0x58, 0x50, 0x6d, 0x0a, 0x2b, 0x39, 0x53, 164 | 0x37, 0x35, 0x53, 0x30, 0x74, 0x4d, 0x71, 0x62, 0x66, 0x35, 0x59, 0x45, 165 | 0x2f, 0x79, 0x63, 0x30, 0x6c, 0x53, 0x62, 0x5a, 0x78, 0x4b, 0x73, 0x50, 166 | 0x56, 0x6c, 0x44, 0x52, 0x6e, 0x6f, 0x67, 0x6f, 0x63, 0x73, 0x46, 0x39, 167 | 0x70, 0x70, 0x6b, 0x43, 0x78, 0x78, 0x4c, 0x65, 0x79, 0x6a, 0x39, 0x43, 168 | 0x59, 0x70, 0x4b, 0x6c, 0x42, 0x57, 0x54, 0x72, 0x54, 0x33, 0x4a, 0x54, 169 | 0x57, 0x0a, 0x50, 0x4e, 0x74, 0x30, 0x4f, 0x4b, 0x52, 0x4b, 0x7a, 0x45, 170 | 0x30, 0x6c, 0x67, 0x76, 0x64, 0x4b, 0x70, 0x56, 0x4d, 0x53, 0x4f, 0x4f, 171 | 0x37, 0x7a, 0x53, 0x57, 0x31, 0x78, 0x6b, 0x58, 0x35, 0x6a, 0x74, 0x71, 172 | 0x75, 0x6d, 0x58, 0x38, 0x4f, 0x6b, 0x68, 0x50, 0x68, 0x50, 0x59, 0x6c, 173 | 0x47, 0x2b, 0x2b, 0x4d, 0x58, 0x73, 0x32, 0x7a, 0x69, 0x53, 0x34, 0x77, 174 | 0x62, 0x6c, 0x43, 0x4a, 0x45, 0x4d, 0x0a, 0x78, 0x43, 0x68, 0x42, 0x56, 175 | 0x66, 0x76, 0x4c, 0x57, 0x6f, 0x6b, 0x56, 0x66, 0x6e, 0x48, 0x6f, 0x4e, 176 | 0x62, 0x39, 0x4e, 0x63, 0x67, 0x6b, 0x39, 0x76, 0x6a, 0x6f, 0x34, 0x55, 177 | 0x46, 0x74, 0x33, 0x4d, 0x52, 0x75, 0x4e, 0x73, 0x38, 0x63, 0x6b, 0x52, 178 | 0x5a, 0x71, 0x6e, 0x72, 0x47, 0x30, 0x41, 0x46, 0x46, 0x6f, 0x45, 0x74, 179 | 0x37, 0x6f, 0x54, 0x36, 0x31, 0x45, 0x4b, 0x6d, 0x45, 0x46, 0x42, 0x0a, 180 | 0x49, 0x6b, 0x35, 0x6c, 0x59, 0x59, 0x65, 0x42, 0x51, 0x56, 0x43, 0x6d, 181 | 0x65, 0x56, 0x79, 0x4a, 0x33, 0x68, 0x6c, 0x4b, 0x56, 0x39, 0x55, 0x75, 182 | 0x35, 0x6c, 0x30, 0x63, 0x55, 0x79, 0x78, 0x2b, 0x6d, 0x4d, 0x30, 0x61, 183 | 0x42, 0x68, 0x61, 0x6b, 0x61, 0x48, 0x50, 0x51, 0x4e, 0x41, 0x51, 0x54, 184 | 0x58, 0x4b, 0x46, 0x78, 0x30, 0x31, 0x70, 0x38, 0x56, 0x64, 0x74, 0x65, 185 | 0x5a, 0x4f, 0x45, 0x33, 0x0a, 0x68, 0x7a, 0x42, 0x57, 0x42, 0x4f, 0x55, 186 | 0x52, 0x74, 0x43, 0x6d, 0x41, 0x45, 0x76, 0x46, 0x35, 0x4f, 0x59, 0x69, 187 | 0x69, 0x41, 0x68, 0x46, 0x38, 0x4a, 0x32, 0x61, 0x33, 0x69, 0x4c, 0x64, 188 | 0x34, 0x38, 0x73, 0x6f, 0x4b, 0x71, 0x44, 0x69, 0x72, 0x43, 0x6d, 0x54, 189 | 0x43, 0x76, 0x32, 0x5a, 0x64, 0x6c, 0x59, 0x54, 0x42, 0x6f, 0x53, 0x55, 190 | 0x65, 0x68, 0x31, 0x30, 0x61, 0x55, 0x41, 0x73, 0x67, 0x0a, 0x45, 0x73, 191 | 0x78, 0x42, 0x75, 0x32, 0x34, 0x4c, 0x55, 0x54, 0x69, 0x34, 0x53, 0x38, 192 | 0x73, 0x43, 0x41, 0x77, 0x45, 0x41, 0x41, 0x61, 0x4e, 0x6a, 0x4d, 0x47, 193 | 0x45, 0x77, 0x44, 0x67, 0x59, 0x44, 0x56, 0x52, 0x30, 0x50, 0x41, 0x51, 194 | 0x48, 0x2f, 0x42, 0x41, 0x51, 0x44, 0x41, 0x67, 0x47, 0x47, 0x4d, 0x41, 195 | 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x45, 0x77, 0x45, 0x42, 0x2f, 0x77, 196 | 0x51, 0x46, 0x0a, 0x4d, 0x41, 0x4d, 0x42, 0x41, 0x66, 0x38, 0x77, 0x48, 197 | 0x51, 0x59, 0x44, 0x56, 0x52, 0x30, 0x4f, 0x42, 0x42, 0x59, 0x45, 0x46, 198 | 0x4c, 0x45, 0x2b, 0x77, 0x32, 0x6b, 0x44, 0x2b, 0x4c, 0x39, 0x48, 0x41, 199 | 0x64, 0x53, 0x59, 0x4a, 0x68, 0x6f, 0x49, 0x41, 0x75, 0x39, 0x6a, 0x5a, 200 | 0x43, 0x76, 0x44, 0x4d, 0x42, 0x38, 0x47, 0x41, 0x31, 0x55, 0x64, 0x49, 201 | 0x77, 0x51, 0x59, 0x4d, 0x42, 0x61, 0x41, 0x0a, 0x46, 0x4c, 0x45, 0x2b, 202 | 0x77, 0x32, 0x6b, 0x44, 0x2b, 0x4c, 0x39, 0x48, 0x41, 0x64, 0x53, 0x59, 203 | 0x4a, 0x68, 0x6f, 0x49, 0x41, 0x75, 0x39, 0x6a, 0x5a, 0x43, 0x76, 0x44, 204 | 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, 0x62, 0x33, 205 | 0x44, 0x51, 0x45, 0x42, 0x42, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 206 | 0x41, 0x51, 0x41, 0x63, 0x47, 0x67, 0x61, 0x58, 0x33, 0x4e, 0x65, 0x63, 207 | 0x0a, 0x6e, 0x7a, 0x79, 0x49, 0x5a, 0x67, 0x59, 0x49, 0x56, 0x79, 0x48, 208 | 0x62, 0x49, 0x55, 0x66, 0x34, 0x4b, 0x6d, 0x65, 0x71, 0x76, 0x78, 0x67, 209 | 0x79, 0x64, 0x6b, 0x41, 0x51, 0x56, 0x38, 0x47, 0x4b, 0x38, 0x33, 0x72, 210 | 0x5a, 0x45, 0x57, 0x57, 0x4f, 0x4e, 0x66, 0x71, 0x65, 0x2f, 0x45, 0x57, 211 | 0x31, 0x6e, 0x74, 0x6c, 0x4d, 0x4d, 0x55, 0x75, 0x34, 0x6b, 0x65, 0x68, 212 | 0x44, 0x4c, 0x49, 0x36, 0x7a, 0x0a, 0x65, 0x4d, 0x37, 0x62, 0x34, 0x31, 213 | 0x4e, 0x35, 0x63, 0x64, 0x62, 0x6c, 0x49, 0x5a, 0x51, 0x42, 0x32, 0x6c, 214 | 0x57, 0x48, 0x6d, 0x69, 0x52, 0x6b, 0x39, 0x6f, 0x70, 0x6d, 0x7a, 0x4e, 215 | 0x36, 0x63, 0x4e, 0x38, 0x32, 0x6f, 0x4e, 0x4c, 0x46, 0x70, 0x6d, 0x79, 216 | 0x50, 0x49, 0x6e, 0x6e, 0x67, 0x69, 0x4b, 0x33, 0x42, 0x44, 0x34, 0x31, 217 | 0x56, 0x48, 0x4d, 0x57, 0x45, 0x5a, 0x37, 0x31, 0x6a, 0x46, 0x0a, 0x68, 218 | 0x53, 0x39, 0x4f, 0x4d, 0x50, 0x61, 0x67, 0x4d, 0x52, 0x59, 0x6a, 0x79, 219 | 0x4f, 0x66, 0x69, 0x5a, 0x52, 0x59, 0x7a, 0x79, 0x37, 0x38, 0x61, 0x47, 220 | 0x36, 0x41, 0x39, 0x2b, 0x4d, 0x70, 0x65, 0x69, 0x7a, 0x47, 0x4c, 0x59, 221 | 0x41, 0x69, 0x4a, 0x4c, 0x51, 0x77, 0x47, 0x58, 0x46, 0x4b, 0x33, 0x78, 222 | 0x50, 0x6b, 0x4b, 0x6d, 0x4e, 0x45, 0x56, 0x58, 0x35, 0x38, 0x53, 0x76, 223 | 0x6e, 0x77, 0x32, 0x0a, 0x59, 0x7a, 0x69, 0x39, 0x52, 0x4b, 0x52, 0x2f, 224 | 0x35, 0x43, 0x59, 0x72, 0x43, 0x73, 0x53, 0x58, 0x61, 0x51, 0x33, 0x70, 225 | 0x6a, 0x4f, 0x4c, 0x41, 0x45, 0x46, 0x65, 0x34, 0x79, 0x48, 0x59, 0x53, 226 | 0x6b, 0x56, 0x58, 0x79, 0x53, 0x47, 0x6e, 0x59, 0x76, 0x43, 0x6f, 0x43, 227 | 0x57, 0x77, 0x39, 0x45, 0x31, 0x43, 0x41, 0x78, 0x32, 0x2f, 0x53, 0x36, 228 | 0x63, 0x43, 0x5a, 0x64, 0x6b, 0x47, 0x43, 0x65, 0x0a, 0x76, 0x45, 0x73, 229 | 0x58, 0x43, 0x53, 0x2b, 0x30, 0x79, 0x78, 0x35, 0x44, 0x61, 0x4d, 0x6b, 230 | 0x48, 0x4a, 0x38, 0x48, 0x53, 0x58, 0x50, 0x66, 0x71, 0x49, 0x62, 0x6c, 231 | 0x6f, 0x45, 0x70, 0x77, 0x38, 0x6e, 0x4c, 0x2b, 0x65, 0x2f, 0x49, 0x42, 232 | 0x63, 0x6d, 0x32, 0x50, 0x4e, 0x37, 0x45, 0x65, 0x71, 0x4a, 0x53, 0x64, 233 | 0x6e, 0x6f, 0x44, 0x66, 0x7a, 0x41, 0x49, 0x4a, 0x39, 0x56, 0x4e, 0x65, 234 | 0x70, 0x0a, 0x2b, 0x4f, 0x6b, 0x75, 0x45, 0x36, 0x4e, 0x33, 0x36, 0x42, 235 | 0x39, 0x4b, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 236 | 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 237 | 0x2d, 0x2d, 0x2d, 0x2d, 0x0a 238 | }; 239 | unsigned int certs_sector_len = 2825; 240 | -------------------------------------------------------------------------------- /deploy.md: -------------------------------------------------------------------------------- 1 | (c) 2018-2024 HomeAccessoryKid 2 | 3 | ### Instructions for end users: 4 | TBD 5 | 6 | ### Instructions if you own the private key: 7 | ``` 8 | cd life-cycle-manager 9 | ``` 10 | - initial steps to be expanded 11 | 12 | #### These are the steps if not introducing a new key pair 13 | - create/update the file versions1/latest-pre-release without new-line and setup 2.2.6 version folder 14 | ``` 15 | mkdir versions1/2.2.6v 16 | echo -n 2.2.6 > versions1/2.2.6v/latest-pre-release 17 | cp versions1/certs.sector versions1/certs.sector.sig versions1/2.2.6v 18 | cp versions1/public*key* versions1/2.2.6v 19 | ``` 20 | - set local.mk to the ota-main program 21 | ``` 22 | make -j6 rebuild OTAVERSION=2.2.6 23 | mv firmware/otamain.bin versions1/2.2.6v 24 | ``` 25 | - set local.mk back to ota-boot program 26 | ``` 27 | make -j6 rebuild OTAVERSION=2.2.6 28 | mv firmware/otaboot.bin versions1/2.2.6v 29 | make -j6 rebuild OTAVERSION=2.2.6 OTABETA=1 30 | cp firmware/otaboot.bin versions1/2.2.6v/otabootbeta.bin 31 | ``` 32 | - remove the older version files 33 | # 34 | - update Changelog 35 | - if you can sign the binaries locally, do so, else follow later steps 36 | ``` 37 | ~/bin/ecc_signer otaboot.bin ../secp384r1prv.der ../secp384r1pub.der 38 | printf "%08x" `cat otaboot.bin | wc -c`| xxd -r -p > len 39 | cat hash len sign > otaboot.bin.sig 40 | ~/bin/ecc_signer otamain.bin ../secp384r1prv.der ../secp384r1pub.der 41 | printf "%08x" `cat otamain.bin | wc -c`| xxd -r -p > len 42 | cat hash len sign > otamain.bin.sig 43 | rm hash len sign 44 | ``` 45 | - test otaboot for basic behaviour 46 | - commit and sync submodules 47 | - commit and sync this as version 2.2.6 48 | - set up a new github release 2.2.6 as a pre-release using the just commited master... 49 | - upload the certs and binaries to the pre-release assets on github 50 | # 51 | - erase the flash and upload the privatekey 52 | ``` 53 | esptool.py -p /dev/cu.usbserial-* --baud 230400 erase_flash 54 | esptool.py -p /dev/cu.usbserial-* --baud 230400 write_flash 0xf9000 versions1-privatekey.der 55 | ``` 56 | - upload the ota-boot BETA program to the device that contains the private key 57 | ``` 58 | make flash OTAVERSION=2.2.6 OTABETA=1 59 | ``` 60 | - power cycle to prevent the bug for software reset after flash 61 | - setup wifi and select the ota-demo repo without pre-release checkbox 62 | - create the 2 signature files next to the bin file and upload to github one by one 63 | - verify the hashes on the computer 64 | ``` 65 | openssl sha384 versions1/2.2.6v/otamain.bin 66 | xxd versions1/2.2.6v/otamain.bin.sig 67 | ``` 68 | 69 | - upload the file versions1/2.2.6v/latest-pre-release to the 'latest release' assets on github 70 | 71 | #### Testing 72 | 73 | - test the release with several devices that have the beta flag set 74 | - if bugs are found, leave this release at pre-release and start a new version 75 | # 76 | - if the results are 100% stable 77 | - make the release a production release on github 78 | - remove the private key 79 | ``` 80 | esptool.py -p /dev/cu.usbserial-* --baud 230400 write_flash 0xf9000 versions1/blank.bin 81 | ``` 82 | 83 | 84 | ### How to make a new signing key pair 85 | 86 | - use the finder to duplicate the content from the previous versions folder => call it versions1 87 | - push all existing public-N.key.sig and public-N.key files one number up 88 | - e.g. if a public-2.key.sig already exists, this would then be renamed to public-3.key.sig etc... 89 | - rename the duplicated cert.sector to public-2.key 90 | - note the new certs.sector is public-1.key but we never need to call it with that name 91 | - remove all the duplicates that will not change from the previous versions folder like blank.bin ... 92 | - note a public-N.key.sig is a signature on a certs.sector file, but using an older key 93 | - and certs.sector.sig is also a signature on a certs.sector file, but using its own key 94 | - there is no need to upload or even keep public-N.key for versionsN since it is never needed 95 | # 96 | - make a new key pair 97 | ``` 98 | mkdir /tmp/ecdsa 99 | cd /tmp/ecdsa 100 | openssl ecparam -genkey -name secp384r1 -out secp384r1prv.pem 101 | openssl ec -in secp384r1prv.pem -outform DER -out secp384r1prv.der 102 | openssl ec -in secp384r1prv.pem -outform DER -out secp384r1pub.der -pubout 103 | cat secp384r1prv.pem 104 | xxd -p secp384r1pub.der 105 | ``` 106 | - capture the private key pem in a secret place and destroy .pem and .der from /tmp 107 | 108 | - open certs.hex and replace the first 4 rows with the public key xxd output, then make the new certs.sector. 109 | ``` 110 | vi versions1/certs.hex; xxd -p -r versions1/certs.hex > versions1/certs.sector 111 | ``` 112 | - edit certs.h to not contain the trailing 0xff entries. make the length correct. 113 | ``` 114 | cd versions1; xxd -i certs.sector > ../certs.h; cd ..; vi certs.h 115 | ``` 116 | - start a new release as described above, but in the first run, use the previous private key in 0xf9000 117 | ``` 118 | esptool.py -p /dev/cu.usbserial-* --baud 230400 write_flash 0xf9000 versionsN-1-privatekey.der 119 | ``` 120 | - collect public-1.key.sig and store it in the new version folder and copy it to versions1 121 | ``` 122 | cp versions1/2.2.6v/public-1.key.sig versions1 123 | ``` 124 | - then flash the new private key 125 | ``` 126 | esptool.py -p /dev/cu.usbserial-* --baud 230400 write_flash 0xf9000 versions1-privatekey.der 127 | ``` 128 | - collect cert.sector.sig and store it in the new version folder and copy it to versions1 129 | ``` 130 | cp versions1/2.2.6v/certs.sector.sig versions1 131 | ``` 132 | - continue with a normal deployment to create the 2 signature files next to the bin files 133 | -------------------------------------------------------------------------------- /design-v5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/design-v5.png -------------------------------------------------------------------------------- /homekit integration/esp-homekit-instructions.md: -------------------------------------------------------------------------------- 1 | ### This assumes you are using Maxim Kulkin's esp-homekit library for HomeKit support 2 | 3 | - First, add the files ota-api.c and ota-api.h next to your main.c file 4 | - next add extras/rboot-ota to the Makefile 5 | ``` 6 | EXTRA_COMPONENTS = \ 7 | extras/http-parser \ 8 | extras/dhcpserver \ 9 | extras/rboot-ota \ 10 | $(abspath esp-wolfssl) \ 11 | $(abspath esp-cjson) \ 12 | $(abspath esp-homekit) \ 13 | $(abspath esp-wifi-config) 14 | ``` 15 | - inside main.c you should start with adding this section soon after #include section 16 | ``` 17 | // add this section to make your device OTA capable 18 | // create the extra characteristic &ota_trigger, at the end of the primary service (before the NULL) 19 | // it can be used in Eve, which will show it, where Home does not 20 | // and apply the four other parameters in the accessories_information section 21 | 22 | #include "ota-api.h" 23 | homekit_characteristic_t ota_trigger = API_OTA_TRIGGER; 24 | homekit_characteristic_t manufacturer = HOMEKIT_CHARACTERISTIC_(MANUFACTURER, "X"); 25 | homekit_characteristic_t serial = HOMEKIT_CHARACTERISTIC_(SERIAL_NUMBER, "1"); 26 | homekit_characteristic_t model = HOMEKIT_CHARACTERISTIC_(MODEL, "Z"); 27 | homekit_characteristic_t revision = HOMEKIT_CHARACTERISTIC_(FIRMWARE_REVISION, "0.0.0"); 28 | 29 | // next use these two lines before calling homekit_server_init(&config); 30 | // int c_hash=ota_read_sysparam(&manufacturer.value.string_value,&serial.value.string_value, 31 | // &model.value.string_value,&revision.value.string_value); 32 | // config.accessories[0]->config_number=c_hash; 33 | // end of OTA add-in instructions 34 | ``` 35 | ### for example it could end up like this: 36 | 37 | ##### !! Be aware that if in the future you change the id's of the characteristics (not only ota_trigger) your device might be rejected by the iPhone and it has proven impossible safe re-flash and re-pair to get the device back into action!! So, if after this you decide to add even more, add it behind what you already had. 38 | 39 | ``` 40 | homekit_accessory_t *accessories[] = { 41 | HOMEKIT_ACCESSORY( 42 | .id=1, 43 | .category=homekit_accessory_category_window_covering, 44 | .services=(homekit_service_t*[]){ 45 | HOMEKIT_SERVICE(ACCESSORY_INFORMATION, 46 | .characteristics=(homekit_characteristic_t*[]){ 47 | HOMEKIT_CHARACTERISTIC(NAME, "basic-curtain"), 48 | &manufacturer, 49 | &serial, 50 | &model, 51 | &revision, 52 | HOMEKIT_CHARACTERISTIC(IDENTIFY, identify), 53 | NULL 54 | }), 55 | HOMEKIT_SERVICE(WINDOW_COVERING, .primary=true, 56 | .characteristics=(homekit_characteristic_t*[]){ 57 | HOMEKIT_CHARACTERISTIC(NAME, "Curtain"), 58 | &target, 59 | ¤t, 60 | &state, 61 | &ota_trigger, 62 | NULL 63 | }), 64 | NULL 65 | }), 66 | NULL 67 | }; 68 | 69 | homekit_server_config_t config = {.accessories = accessories, .password = "111-11-111"}; 70 | 71 | void on_wifi_ready() { 72 | device_init(); 73 | 74 | int c_hash=ota_read_sysparam(&manufacturer.value.string_value,&serial.value.string_value, 75 | &model.value.string_value,&revision.value.string_value); 76 | //c_hash=1; revision.value.string_value="0.0.1"; //cheat line 77 | config.accessories[0]->config_number=c_hash; 78 | 79 | homekit_server_init(&config); 80 | } 81 | ``` 82 | The cheat line can be used in the initial stage before LCM is used at all since without it homekit will not work -------------------------------------------------------------------------------- /homekit integration/ota-api.c: -------------------------------------------------------------------------------- 1 | #include //for printf 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // the first function is the ONLY thing needed for a repo to support ota after having started with ota-boot 11 | // in ota-boot the user gets to set the wifi and the repository details and it then installs the ota-main binary 12 | 13 | void ota_update(void *arg) { //arg not used 14 | rboot_set_temp_rom(1); //select the OTA main routine 15 | sdk_system_restart(); //#include 16 | // there is a bug in the esp SDK such that if you do not power cycle the chip after serial flashing, restart is unreliable 17 | } 18 | 19 | // this function is optional to couple Homekit parameters to the sysparam variables and github parameters 20 | unsigned int ota_read_sysparam(char **manufacturer,char **serial,char **model,char **revision) { 21 | sysparam_status_t status; 22 | char *value; 23 | 24 | status = sysparam_get_string("ota_repo", &value); 25 | if (status == SYSPARAM_OK) { 26 | strchr(value,'/')[0]=0; 27 | *manufacturer=value; 28 | *model=value+strlen(value)+1; 29 | } else { 30 | *manufacturer="manuf_unknown"; 31 | *model="model_unknown"; 32 | } 33 | status = sysparam_get_string("ota_version", &value); 34 | if (status == SYSPARAM_OK) { 35 | *revision=value; 36 | } else *revision="0.0.0"; 37 | 38 | uint8_t macaddr[6]; 39 | sdk_wifi_get_macaddr(STATION_IF, macaddr); 40 | *serial=malloc(18); 41 | sprintf(*serial,"%02X:%02X:%02X:%02X:%02X:%02X",macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]); 42 | 43 | unsigned int c_hash=0; 44 | char version[16]; 45 | char* rev=version; 46 | char* dot; 47 | strncpy(rev,*revision,16); 48 | if ((dot=strchr(rev,'.'))) {dot[0]=0; c_hash= atoi(rev); rev=dot+1;} 49 | if ((dot=strchr(rev,'.'))) {dot[0]=0; c_hash=c_hash*1000+atoi(rev); rev=dot+1;} 50 | c_hash=c_hash*1000+atoi(rev); 51 | //c_hash=c_hash*10 +configuration_variant; //possible future extension 52 | printf("manuf=\'%s\' serial=\'%s\' model=\'%s\' revision=\'%s\' c#=%d\n",*manufacturer,*serial,*model,*revision,c_hash); 53 | return c_hash; 54 | } 55 | 56 | 57 | 58 | 59 | #include 60 | #include 61 | #include 62 | 63 | static ETSTimer update_timer; 64 | 65 | void ota_set(homekit_value_t value) { 66 | if (value.format != homekit_format_bool) { 67 | printf("Invalid ota-value format: %d\n", value.format); 68 | return; 69 | } 70 | if (value.bool_value) { 71 | //make a distinct light pattern or other feedback to the user = call identify routine 72 | sdk_os_timer_setfn(&update_timer, ota_update, NULL); 73 | sdk_os_timer_arm(&update_timer, 500, 0); //wait 0.5 seconds to trigger the reboot so gui can update and events sent 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /homekit integration/ota-api.h: -------------------------------------------------------------------------------- 1 | #ifndef __HOMEKIT_CUSTOM_CHARACTERISTICS__ 2 | #define __HOMEKIT_CUSTOM_CHARACTERISTICS__ 3 | 4 | #define HOMEKIT_CUSTOM_UUID(value) (value "-0e36-4a42-ad11-745a73b84f2b") 5 | 6 | #define HOMEKIT_SERVICE_CUSTOM_SETUP HOMEKIT_CUSTOM_UUID("000000FF") 7 | 8 | #define HOMEKIT_CHARACTERISTIC_CUSTOM_OTA_TRIGGER HOMEKIT_CUSTOM_UUID("F0000001") 9 | #define HOMEKIT_DECLARE_CHARACTERISTIC_CUSTOM_OTA_TRIGGER(_value, ...) \ 10 | .type = HOMEKIT_CHARACTERISTIC_CUSTOM_OTA_TRIGGER, \ 11 | .description = "}FirmwareUpdate", \ 12 | .format = homekit_format_bool, \ 13 | .permissions = homekit_permissions_paired_read \ 14 | | homekit_permissions_paired_write \ 15 | | homekit_permissions_notify, \ 16 | .value = HOMEKIT_BOOL_(_value), \ 17 | ##__VA_ARGS__ 18 | 19 | unsigned int ota_read_sysparam(char **manufacturer,char **serial,char **model,char **revision); 20 | 21 | void ota_update(void *arg); 22 | 23 | void ota_set(homekit_value_t value); 24 | 25 | #define API_OTA_TRIGGER HOMEKIT_CHARACTERISTIC_(CUSTOM_OTA_TRIGGER, false, .setter=ota_set) 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /local.mk: -------------------------------------------------------------------------------- 1 | PROGRAM = otaboot 2 | EXTRA_CFLAGS += -DOTABOOT 3 | 4 | ### select EITHER the top block OR the bottom block 5 | 6 | #PROGRAM = otamain 7 | #LINKER_SCRIPTS = $(ROOT)ld/program1.ld 8 | #EXTRA_CFLAGS += -Os 9 | 10 | #================================================== 11 | # for this to work, we need to copy $(ROOT)ld/program.ld 12 | # to $(ROOT)ld/program1.ld and in the copy change this: 13 | # irom0_0_seg : org = 0x40202010, len = (1M - 0x2010) 14 | # to this: 15 | # irom0_0_seg : org = 0x4028D010, len = (0xf5000 - 0x2010) 16 | # 17 | # note that the previous len is forgetting about the system settings area which 18 | # is 9 sectors for esp-open-rtos, and then it is ignored as well... 19 | # 20 | # also we need to change paramters.mk like this: 21 | # LINKER_SCRIPTS += $(ROOT)ld/program.ld $(ROOT)ld/rom.ld 22 | # becomes: 23 | # LINKER_SCRIPTS ?= $(ROOT)ld/program.ld 24 | # LINKER_SCRIPTS += $(ROOT)ld/rom.ld 25 | #== 26 | # if you know a more elegant way, I am all open for it ;-) 27 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* (c) 2018-2022 HomeAccessoryKid 2 | * LifeCycleManager dual app 3 | * use local.mk to turn it into the LCM otamain.bin app or the otaboot.bin app 4 | */ 5 | 6 | #include //for UDPLGP and free 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include //for stdcmp 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | void ota_task(void *arg) { 21 | int holdoff_time=1; //32bit, in seconds 22 | char* user_repo=NULL; 23 | char* user_version=NULL; 24 | char* user_file=NULL; 25 | char* new_version=NULL; 26 | char* ota_version=NULL; 27 | // char* lcm_version=NULL; 28 | #ifndef OTABOOT 29 | char* btl_version=NULL; 30 | #endif 31 | signature_t signature; 32 | extern int active_cert_sector; 33 | extern int backup_cert_sector; 34 | int file_size; //32bit 35 | #ifdef OTABOOT 36 | int have_private_key=0; 37 | #endif 38 | uint16_t keyid; 39 | int foundkey=0; 40 | char keyname[KEYNAMELEN]; 41 | 42 | ota_init(); 43 | 44 | file_size=ota_get_pubkey(active_cert_sector); 45 | 46 | #ifdef OTABOOT 47 | if (!ota_get_privkey()) { //have private key 48 | have_private_key=1; 49 | UDPLGP("have private key\n"); 50 | if (ota_verify_pubkey()) { 51 | ota_sign(active_cert_sector,file_size, &signature, "public-1.key");//use this (old) privkey to sign the (new) pubkey 52 | vTaskDelete(NULL); //upload the signature out of band to github and flash the new private key to backupsector 53 | } 54 | } 55 | #else 56 | btl_version=ota_get_btl_version(); 57 | #endif 58 | if (ota_boot()) ota_write_status("0.0.0"); //we will have to get user code from scratch if running ota_boot 59 | if ( !ota_load_user_app(&user_repo, &user_version, &user_file)) { //repo/file must be configured 60 | if (!strcmp(user_repo,HAAREPO)) user_file=HAAFILE; 61 | #ifdef OTABOOT 62 | if (ota_boot()) { 63 | new_version=ota_get_version(user_repo); //check if this repository exists at all 64 | if (!strcmp(new_version,"404")) { 65 | UDPLGP("%s does not exist! HALTED TILL NEXT POWERCYCLE!\n",user_repo); 66 | vTaskDelete(NULL); 67 | } 68 | } 69 | #endif 70 | 71 | for (;;) { //escape from this loop by continue (try again) or break (boots into slot 0) 72 | UDPLGP("--- entering the loop\n"); 73 | //UDPLGP("%d\n",sdk_system_get_time()/1000); 74 | //need for a protection against an electricity outage recovery storm 75 | vTaskDelay(holdoff_time*(1000/portTICK_PERIOD_MS)); 76 | holdoff_time*=HOLDOFF_MULTIPLIER; holdoff_time=(holdoff_time0) { //can only upgrade 168 | UDPLGP("BTLREPO=\'%s\' new_version=\'%s\' BTLFILE=\'%s\'\n",BTLREPO,new_version,BTLFILE); 169 | if (!ota_get_hash(BTLREPO, new_version, BTLFILE, &signature)) { 170 | if (!ota_verify_signature(&signature)) { 171 | file_size=ota_get_file(BTLREPO,new_version,BTLFILE,backup_cert_sector); 172 | if (file_size>0 && !ota_verify_hash(backup_cert_sector,&signature)) { 173 | ota_finalize_file(backup_cert_sector); 174 | ota_copy_bootloader(backup_cert_sector, file_size, new_version); //transfer it to sector zero 175 | } 176 | } 177 | } //else maybe next time more luck for the bootloader 178 | } //no bootloader update 179 | } 180 | //if there is a newer version of ota-main... 181 | if (ota_compare(ota_version,OTAVERSION)>0) { //set OTAVERSION when running make and match with github 182 | ota_get_hash(OTAREPO, ota_version, BOOTFILE, &signature); 183 | if (ota_verify_signature(&signature)) break; //signature file is not signed by our key, ABORT 184 | file_size=ota_get_file(OTAREPO,ota_version,BOOTFILE,BOOT0SECTOR); 185 | if (file_size<=0) continue; //something went wrong, but now boot0 is broken so start over 186 | if (ota_verify_hash(BOOT0SECTOR,&signature)) continue; //download failed 187 | ota_finalize_file(BOOT0SECTOR); 188 | break; //leads to boot=0 and starts self-updating/otaboot-app 189 | } //ota code is up to date 190 | ota_set_verify(1); //reject faked server only for user_repo 191 | if (new_version) free(new_version); 192 | new_version=ota_get_version(user_repo); 193 | if (ota_compare(new_version,user_version)>0) { //can only upgrade 194 | UDPLGP("user_repo=\'%s\' new_version=\'%s\' user_file=\'%s\'\n",user_repo,new_version,user_file); 195 | if (!ota_get_hash(user_repo, new_version, user_file, &signature)) { 196 | file_size=ota_get_file(user_repo,new_version,user_file,BOOT0SECTOR); 197 | if (file_size<=0 || ota_verify_hash(BOOT0SECTOR,&signature)) continue; //something went wrong, but now boot0 is broken so start over 198 | ota_finalize_file(BOOT0SECTOR); //TODO return status and if wrong, continue 199 | ota_write_status(new_version); //we have been successful, hurray! 200 | } else break; //user did not supply a proper sig file or fake server -> return to boot0 201 | } //nothing to update 202 | break; //leads to boot=0 and starts updated user app 203 | #endif 204 | } 205 | } 206 | } 207 | ota_reboot(); //boot0, either the user program or the otaboot app 208 | vTaskDelete(NULL); //just for completeness sake, would never get till here 209 | } 210 | 211 | void emergency_task(void *ota_srvr) { 212 | UDPLGP("--- emergency_task\n"); 213 | signature_t signature; 214 | extern int active_cert_sector; 215 | 216 | ota_active_sector(); 217 | ota_get_pubkey(active_cert_sector); 218 | if (ota_get_hash(ota_srvr,EMERGENCY,BOOTFILE,&signature)) vTaskDelete(NULL); 219 | if (ota_verify_signature(&signature)) vTaskDelete(NULL); 220 | if (ota_get_file(ota_srvr,EMERGENCY,BOOTFILE,BOOT0SECTOR)<=0) vTaskDelete(NULL); 221 | if (ota_verify_hash(BOOT0SECTOR,&signature)) vTaskDelete(NULL); 222 | ota_finalize_file(BOOT0SECTOR); 223 | ota_reboot(); //boot0, the new otaboot app 224 | vTaskDelete(NULL); //just for completeness sake, would never get till here 225 | } 226 | 227 | void on_wifi_ready() { 228 | UDPLGP("--- on_wifi_ready\n"); 229 | char* ota_srvr=NULL; 230 | 231 | if (ota_emergency(&ota_srvr)){ 232 | xTaskCreate(emergency_task,EMERGENCY,4096,ota_srvr,1,NULL); 233 | } else { 234 | xTaskCreate(ota_task,"ota",4096,NULL,1,NULL); 235 | } 236 | } 237 | 238 | void pre_wifi_config(void *pvParameters) { 239 | UDPLGP("--- pre_wifi_config\n"); 240 | ota_read_rtc(); //read RTC outcome from rboot4lcm and act accordingly 241 | wifi_config_init("LCM", NULL, on_wifi_ready); //expanded it with setting repo-details 242 | vTaskDelete(NULL); 243 | } 244 | 245 | void user_init(void) { 246 | uart_set_baud(0, 115200); 247 | UDPLGP("\n\n\n\n\n\n\n--- user_init\n"); 248 | #ifdef OTABOOT 249 | UDPLGP("--- OTABOOT "); 250 | #else 251 | UDPLGP("--- OTAMAIN "); 252 | #endif 253 | UDPLGP("VERSION: %s\n",OTAVERSION); 254 | 255 | xTaskCreate(udplog_send, "logsend", 1024, NULL, 2, NULL); 256 | xTaskCreate(pre_wifi_config, "pre_wifi", 1024, NULL, 1, NULL); 257 | } 258 | -------------------------------------------------------------------------------- /ota.c: -------------------------------------------------------------------------------- 1 | /* (c) 2018-2022 HomeAccessoryKid */ 2 | #include //for UDPLGP 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include // needed by wolfSSL_check_domain_name() 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | //#include //included in sntp.h 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | #include //for overclocking functions 27 | 28 | static int verify = 1; 29 | static byte file_first_byte[]={0xff}; 30 | ecc_key prvecckey; 31 | ecc_key pubecckey; 32 | 33 | WOLFSSL_CTX* ctx; 34 | 35 | #ifdef DEBUG_WOLFSSL 36 | void MyLoggingCallback(const int logLevel, const char* const logMessage) { 37 | /*custom logging function*/ 38 | UDPLGP("loglevel: %d - %s\n",logLevel, logMessage); 39 | } 40 | #endif 41 | 42 | bool userbeta=0; 43 | bool otabeta=0; 44 | 45 | char *ota_strstr(const char *full_string, const char *search) { //lowercase version of strstr() 46 | char *lc_string = strdup(full_string); 47 | unsigned char *ch = (unsigned char *) lc_string; 48 | while(*ch) { 49 | *ch = tolower(*ch); 50 | ch++; 51 | } 52 | const char *found = strstr(lc_string, search); 53 | free(lc_string); 54 | if(found == NULL) return NULL; 55 | const int offset = (int) found - (int) lc_string; 56 | 57 | return (char *) ((int) full_string + offset); 58 | } 59 | 60 | void ota_read_rtc() { 61 | UDPLGP("--- ota_read_rtc\n"); 62 | int sector,count=0,user_count=0; 63 | int count_step=3; 64 | sysparam_status_t status; 65 | char *value=NULL; 66 | bool reset_wifi=0; 67 | bool reset_otabeta=0; 68 | bool factory_reset=0; 69 | rboot_rtc_data rtc; 70 | 71 | status = sysparam_init(SYSPARAMSECTOR, 0); 72 | if (status == SYSPARAM_OK) { 73 | status = sysparam_get_string("ota_count_step", &value); 74 | if (status == SYSPARAM_OK) { 75 | if (*value<0x34 && *value>0x30 && strlen(value)==1) count_step=*value-0x30; 76 | free(value); 77 | } 78 | status = sysparam_get_string("ota_count", &value); 79 | if (status == SYSPARAM_OK) { 80 | user_count=atoi(value); 81 | sysparam_set_string("ota_count",""); 82 | free(value); 83 | } 84 | } 85 | UDPLGP("--- count_step=%d\n",count_step); 86 | 87 | if (rboot_get_rtc_data(&rtc)) count=rtc.temp_rom; 88 | if (count<2) count=user_count; 89 | 90 | UDPLGP("--- count=%d\n",count); 91 | if (count<5+count_step*1) { //standard ota-main or ota-boot behavior 92 | value="--- standard ota"; 93 | } 94 | else if (count<5+count_step*2) { //reset wifi parameters and clear LCM_beta 95 | value="--- reset wifi and clear LCM_beta"; 96 | reset_wifi=1; 97 | reset_otabeta=1; 98 | } 99 | else if (count<5+count_step*3) { //reset wifi parameters and set LCM_beta 100 | value="--- reset wifi and set LCM_beta"; 101 | reset_wifi=1; 102 | otabeta=1; 103 | } 104 | else {//factory reset 105 | value="--- factory reset"; 106 | factory_reset=1; 107 | } 108 | UDPLGP("%s\n",value); 109 | if (count>4) { 110 | UDPLGP("IF this is NOT what you wanted, reset/power-down NOW!\n"); 111 | for (int i=9;i>-1;i--) { 112 | vTaskDelay(1000/portTICK_PERIOD_MS); 113 | UDPLGP("%s in %d s\n",value,i); 114 | } 115 | } 116 | if (factory_reset) { 117 | spiflash_erase_sector(SYSPARAMSECTOR); spiflash_erase_sector(SYSPARAMSECTOR+SECTORSIZE);//sysparam reset 118 | for (sector=0xfb000; sector< 0x100000; sector+=SECTORSIZE) spiflash_erase_sector(sector);//Espressif area 119 | #ifndef OTABOOT 120 | for(sector= 0x2000; sector11) { //do not allow pins 6-11 203 | UDPLGP(" blinking led pin %d\n",led); 204 | gpio_enable(led, GPIO_OUTPUT); 205 | while(1) { 206 | gpio_write(led, 1); vTaskDelay(BLINKDELAY/portTICK_PERIOD_MS); 207 | gpio_write(led, 0); vTaskDelay(BLINKDELAY/portTICK_PERIOD_MS); 208 | } 209 | } else { 210 | UDPLGP(": invalid pin %d\n",led); 211 | } 212 | ledblinkHandle = NULL; 213 | vTaskDelete(NULL); 214 | } 215 | 216 | void ota_init() { 217 | UDPLGP("--- ota_init\n"); 218 | 219 | sysparam_get_bool("lcm_beta", &otabeta); 220 | sysparam_get_bool("ota_beta", &userbeta); 221 | UDPLGP("userbeta=\'%d\' otabeta=\'%d\'\n",userbeta,otabeta); 222 | 223 | ip_addr_t target_ip; 224 | int ret; 225 | 226 | sysparam_status_t status; 227 | uint8_t led_info=0; 228 | 229 | status = sysparam_get_int8("led_pin", &led); 230 | if (status == SYSPARAM_OK) { 231 | if (led<0) {led_info=0x10; led=-led;} 232 | led_info+=(led<16)?(0x40+(led&0x0f)):0; 233 | if (led<16) xTaskCreate(led_blink_task, "ledblink", 256, NULL, 1, &ledblinkHandle); 234 | } 235 | 236 | //rboot setup 237 | rboot_config conf; 238 | conf=rboot_get_config(); 239 | UDPLGP("rboot_config.unused[1]=LEDinfo from 0x%02x to 0x%02x\n",conf.unused[1],led_info); 240 | if (conf.count!=2 || conf.roms[0]!=BOOT0SECTOR || conf.roms[1]!=BOOT1SECTOR || conf.current_rom!=0 || conf.unused[1]!=led_info) { 241 | conf.count =2; conf.roms[0] =BOOT0SECTOR; conf.roms[1] =BOOT1SECTOR; conf.current_rom =0; conf.unused[1] =led_info; 242 | rboot_set_config(&conf); 243 | } 244 | 245 | //time support 246 | const char *servers[] = {SNTP_SERVERS}; 247 | sntp_set_update_delay(24*60*60000); //SNTP will request an update every 24 hour 248 | //const struct timezone tz = {1*60, 0}; //Set GMT+1 zone, daylight savings off 249 | //sntp_initialize(&tz); 250 | sntp_initialize(NULL); 251 | sntp_set_servers(servers, sizeof(servers) / sizeof(char*)); //Servers must be configured right after initialization 252 | 253 | #ifdef DEBUG_WOLFSSL 254 | if (wolfSSL_SetLoggingCb(MyLoggingCallback)) UDPLGP("error setting debug callback\n"); 255 | #endif 256 | 257 | wolfSSL_Init(); 258 | 259 | ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); 260 | if (!ctx) { 261 | //error 262 | } 263 | ota_active_sector(); 264 | ota_set_verify(0); 265 | UDPLGP("--- DNS: "); 266 | ret = netconn_gethostbyname(HOST, &target_ip); 267 | while(ret) { 268 | UDPLGP("%d",ret); 269 | vTaskDelay(200); 270 | ret = netconn_gethostbyname(HOST, &target_ip); 271 | } 272 | UDPLGP("done!\n"); 273 | } 274 | 275 | #ifdef OTABOOT 276 | int ota_get_privkey() { 277 | UDPLGP("--- ota_get_privkey\n"); 278 | 279 | byte buffer[PKEYSIZE]; //maybe 49 bytes would be enough 280 | int ret; 281 | unsigned int idx; 282 | int length; 283 | 284 | //load private key as produced by openssl 285 | if (!spiflash_read(backup_cert_sector, (byte *)buffer, 24)) { 286 | UDPLGP("error reading flash\n"); return -1; 287 | } 288 | if (buffer[0]!=0x30 || buffer[1]!=0x81) return -2; //not a valid keyformat 289 | if (buffer[3]!=0x02 || buffer[4]!=0x01 || buffer[5]!=0x01) return -2; //not a valid keyformat 290 | if (buffer[6]!=0x04) return -2; //not a valid keyformat 291 | idx=7; 292 | length=buffer[idx++]; //bitstring start 293 | 294 | if (!spiflash_read(backup_cert_sector+idx, (byte *)buffer, length)) { 295 | UDPLGP("error reading flash\n"); return -1; 296 | } 297 | for (idx=0;idxhash, 0xff); // 0xff=no special first byte action 389 | wc_ecc_sign_hash(signature->hash, HASHSIZE, signature->sign, &siglen, &rng, &prvecckey); 390 | printf("echo "); for (i=0;ihash[i]); printf("> x.hex\n"); 391 | printf("echo %08x >>x.hex\n",filesize); 392 | printf("echo "); for (i=0;isign[i]); printf(">>x.hex\n"); 393 | printf("xxd -r -p x.hex > %s.sig\n",file); printf("rm x.hex\n"); 394 | } 395 | #endif 396 | 397 | int ota_compare(char* newv, char* oldv) { //(if equal,0) (if newer,1) (if pre-release or older,-1) 398 | UDPLGP("--- ota_compare "); 399 | printf("\n"); 400 | char* dot; 401 | int valuen=0,valueo=0; 402 | char news[MAXVERSIONLEN],olds[MAXVERSIONLEN]; 403 | char * new=news; 404 | char * old=olds; 405 | int result=0; 406 | 407 | if (strcmp(newv,oldv)) { //https://semver.org/#spec-item-11 408 | do { 409 | if (strchr(newv,'-')) {result=-1;break;} //we cannot handle versions with pre-release suffix notation (yet) 410 | //pre-release marker in github serves to identify those 411 | strncpy(new,newv,MAXVERSIONLEN-1); 412 | strncpy(old,oldv,MAXVERSIONLEN-1); 413 | if ((dot=strchr(new,'.'))) {dot[0]=0; valuen=atoi(new); new=dot+1;} 414 | if ((dot=strchr(old,'.'))) {dot[0]=0; valueo=atoi(old); old=dot+1;} 415 | printf("%d-%d,%s-%s\n",valuen,valueo,new,old); 416 | if (valuen>valueo) {result= 1;break;} 417 | if (valuenvalueo) {result= 1;break;} 423 | if (valuenvalueo) {result= 1;break;} 428 | if (valuen> 0), 456 | (unsigned char)((target_ip.addr & 0x0000ff00) >> 8), 457 | (unsigned char)((target_ip.addr & 0x00ff0000) >> 16), 458 | (unsigned char)((target_ip.addr & 0xff000000) >> 24)); 459 | //printf("create socket ......"); 460 | *socket = socket(AF_INET, SOCK_STREAM, 0); 461 | if (*socket < 0) { 462 | UDPLGP(FAILED); 463 | return -3; 464 | } 465 | 466 | UDPLGP("local.."); 467 | memset(&sock_addr, 0, sizeof(sock_addr)); 468 | sock_addr.sin_family = AF_INET; 469 | sock_addr.sin_addr.s_addr = 0; 470 | sock_addr.sin_port = htons(local_port++); 471 | if (local_port==0x10000) local_port=0xc000; 472 | ret = bind(*socket, (struct sockaddr*)&sock_addr, sizeof(sock_addr)); 473 | if (ret) { 474 | UDPLGP(FAILED); 475 | return -2; 476 | } 477 | UDPLGP("OK "); 478 | 479 | UDPLGP("remote.."); 480 | memset(&sock_addr, 0, sizeof(sock_addr)); 481 | sock_addr.sin_family = AF_INET; 482 | sock_addr.sin_addr.s_addr = target_ip.addr; 483 | sock_addr.sin_port = htons(port); 484 | ret = connect(*socket, (struct sockaddr*)&sock_addr, sizeof(sock_addr)); 485 | if (ret) { 486 | UDPLGP(FAILED); 487 | return -2; 488 | } 489 | UDPLGP("OK "); 490 | 491 | if (port==HTTPS_PORT) { //SSL mode, in emergency mode this is skipped 492 | UDPLGP("SSL.."); 493 | *ssl = wolfSSL_new(ctx); 494 | if (!*ssl) { 495 | UDPLGP(FAILED); 496 | return -2; 497 | } 498 | UDPLGP("OK "); 499 | 500 | //wolfSSL_Debugging_ON(); 501 | wolfSSL_set_fd(*ssl, *socket); 502 | UDPLGP("set_fd "); 503 | 504 | ret = wolfSSL_UseSNI(*ssl, WOLFSSL_SNI_HOST_NAME, host, strlen(host)); 505 | if (ret != SSL_SUCCESS) { 506 | UDPLGP("failed, return [-0x%x]\n", -ret); 507 | ret=wolfSSL_get_error(*ssl,ret); 508 | UDPLGP("wolfSSL_UseSNI error = %d\n", ret); 509 | return -1; 510 | } 511 | 512 | if (verify) ret=wolfSSL_check_domain_name(*ssl, host); 513 | //wolfSSL_Debugging_OFF(); 514 | 515 | UDPLGP("to %s port %d..", host, port); 516 | ret = wolfSSL_connect(*ssl); 517 | if (ret != SSL_SUCCESS) { 518 | UDPLGP("failed, return [-0x%x]\n", -ret); 519 | ret=wolfSSL_get_error(*ssl,ret); 520 | UDPLGP("wolfSSL_send error = %d\n", ret); 521 | return -1; 522 | } 523 | UDPLGP("OK\n"); 524 | } //end SSL mode 525 | return 0; 526 | 527 | } 528 | 529 | int ota_load_user_app(char * *repo, char * *version, char * *file) { 530 | UDPLGP("--- ota_load_user_app\n"); 531 | sysparam_status_t status; 532 | char *value; 533 | 534 | status = sysparam_get_string("ota_repo", &value); 535 | if (status == SYSPARAM_OK) { 536 | *repo=value; 537 | } else return -1; 538 | status = sysparam_get_string("ota_version", &value); 539 | if (status == SYSPARAM_OK) { 540 | *version=value; 541 | } else { 542 | *version=malloc(6); 543 | strcpy(*version,"0.0.0"); 544 | } 545 | status = sysparam_get_string("ota_file", &value); 546 | if (status == SYSPARAM_OK) { 547 | *file=value; 548 | } else return -1; 549 | 550 | UDPLGP("user_repo=\'%s\' user_version=\'%s\' user_file=\'%s\'\n",*repo,*version,*file); 551 | return 0; 552 | } 553 | 554 | void ota_set_verify(int onoff) { 555 | UDPLGP("--- ota_set_verify..."); 556 | int ret=0; 557 | byte abyte[1]; 558 | 559 | if (onoff) { 560 | UDPLGP("ON\n"); 561 | if (verify==0) { 562 | verify= 1; 563 | do { 564 | if (!spiflash_read(active_cert_sector+PKEYSIZE+(ret++), (byte *)abyte, 1)) { 565 | UDPLGP("error reading flash\n"); 566 | break; 567 | } 568 | } while (abyte[0]!=0xff); ret--; 569 | UDPLGP("certs size: %d\n",ret); 570 | byte *certs=malloc(ret); 571 | spiflash_read(active_cert_sector+PKEYSIZE, (byte *)certs, ret); 572 | 573 | ret=wolfSSL_CTX_load_verify_buffer(ctx, certs, ret, SSL_FILETYPE_PEM); 574 | if ( ret != SSL_SUCCESS) { 575 | UDPLGP("fail cert loading, return %d\n", ret); 576 | } 577 | free(certs); 578 | 579 | time_t ts; 580 | do { 581 | ts = time(NULL); 582 | if (ts == ((time_t)-1)) printf("ts=-1, "); 583 | vTaskDelay(1); 584 | } while (!(ts>1073741823)); //2^30-1 which is supposed to be like 2004 585 | UDPLGP("TIME: %s", ctime(&ts)); //we need to have the clock right to check certificates 586 | 587 | wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); 588 | sdk_system_overclock(); //With verification, we are to slow for the 10s timeout of GitHub (end 2023) 589 | } 590 | } else { 591 | UDPLGP("OFF\n"); 592 | if (verify==1) { 593 | verify= 0; 594 | wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); 595 | sdk_system_restoreclock(); 596 | } 597 | } 598 | } 599 | 600 | void ota_copy_bootloader(int sector, int size, char * version) { 601 | UDPLGP("--- ota_copy_bootloader\n"); 602 | byte buffer[SECTORSIZE]; 603 | byte fourbyte[4]; 604 | char versionbuff[MAXVERSIONLEN]; 605 | 606 | memset(versionbuff,0xff,MAXVERSIONLEN); 607 | strcpy(versionbuff,version); 608 | spiflash_read(sector, buffer, size); 609 | spiflash_read(0, fourbyte, 4); //transfer the flash setting flags from previous boot sector... 610 | buffer[2]=fourbyte[2]; buffer[3]=fourbyte[3]; 611 | spiflash_erase_sector(0); 612 | spiflash_write(0, buffer, size); 613 | //version is stored as a string in last MAXVERSIONLEN bytes of sector 614 | spiflash_write(SECTORSIZE-MAXVERSIONLEN, (byte *)versionbuff, MAXVERSIONLEN); 615 | //set last uint32 to zero of the config sector so rboot will reflash it 616 | memset(versionbuff,0,4); 617 | spiflash_write(2*SECTORSIZE-4, (byte *)versionbuff, 4); 618 | } 619 | 620 | char* ota_get_btl_version() { 621 | UDPLGP("--- ota_get_btl_version\n"); 622 | char versionbuff[MAXVERSIONLEN]; 623 | char* version=NULL; 624 | 625 | spiflash_read(SECTORSIZE-MAXVERSIONLEN, (byte *)versionbuff, MAXVERSIONLEN); 626 | if (versionbuff[0]!=0xff) { //TODO: make this more error resistant 627 | version=malloc(strlen(versionbuff)+1); 628 | strcpy(version,versionbuff); 629 | } else { 630 | version=malloc(6); 631 | strcpy(version,"0.0.0"); 632 | } 633 | UDPLGP("bootloader version:\"%s\"\n",version); 634 | return version; 635 | } 636 | 637 | int ota_get_file_ex(char * repo, char * version, char * file, int sector, byte * buffer, int bufsz); //prototype needed 638 | char* ota_get_version(char * repo) { 639 | UDPLGP("--- ota_get_version\n"); 640 | 641 | char* version=NULL; 642 | char prerelease[64]; 643 | int retc, ret=0; 644 | int httpcode; 645 | WOLFSSL* ssl; 646 | int socket; 647 | //host=begin(repo); 648 | //mid =end(repo)+blabla+version 649 | char* found_ptr; 650 | char recv_buf[RECV_BUF_LEN]; 651 | int send_bytes; //= sizeof(send_data); 652 | 653 | strcat(strcat(strcat(strcat(strcat(strcpy(recv_buf, \ 654 | REQUESTHEAD),repo),"/releases/latest"),REQUESTTAIL),HOST),CRLFCRLF); 655 | send_bytes=strlen(recv_buf); 656 | //printf("%s\n",recv_buf); 657 | 658 | retc = ota_connect(HOST, HTTPS_PORT, &socket, &ssl); //release socket and ssl when ready 659 | 660 | if (!retc) { 661 | UDPLGP("%s",recv_buf); 662 | ret = wolfSSL_write(ssl, recv_buf, send_bytes); 663 | if (ret > 0) { 664 | printf("sent OK\n"); 665 | 666 | ret = wolfSSL_peek(ssl, recv_buf, RECV_BUF_LEN - 1); 667 | if (ret > 0) { 668 | recv_buf[ret]=0; //prevent falling of the end of the buffer when doing string operations 669 | found_ptr=ota_strstr(recv_buf,"http/1.1 "); 670 | found_ptr+=9; //flush "HTTP/1.1 " 671 | httpcode=atoi(found_ptr); 672 | UDPLGP("HTTP returns %d for ",httpcode); 673 | if (httpcode!=302) { 674 | wolfSSL_free(ssl); 675 | lwip_close(socket); 676 | return "404"; 677 | } 678 | } else { 679 | UDPLGP("failed, return [-0x%x]\n", -ret); 680 | ret=wolfSSL_get_error(ssl,ret); 681 | UDPLGP("wolfSSL_send error = %d\n", ret); 682 | return "404"; 683 | } 684 | 685 | while (1) { 686 | recv_buf[ret]=0; //prevent falling of the end of the buffer when doing string operations 687 | found_ptr=ota_strstr(recv_buf,"\nlocation:"); 688 | if (found_ptr) break; 689 | wolfSSL_read(ssl, recv_buf, RECV_BUF_LEN - 12); 690 | ret = wolfSSL_peek(ssl, recv_buf, RECV_BUF_LEN - 1); 691 | if (ret <= 0) { 692 | UDPLGP("failed, return [-0x%x]\n", -ret); 693 | ret=wolfSSL_get_error(ssl,ret); 694 | UDPLGP("wolfSSL_send error = %d\n", ret); 695 | return "404"; 696 | } 697 | } 698 | ret=wolfSSL_read(ssl, recv_buf, found_ptr-recv_buf + 11); //flush all previous material 699 | ret=wolfSSL_read(ssl, recv_buf, RECV_BUF_LEN - 1); //this starts for sure with the content of "Location: " 700 | recv_buf[ret]=0; //prevent falling of the end of the buffer when doing string operations 701 | strchr(recv_buf,'\r')[0]=0; 702 | found_ptr=ota_strstr(recv_buf,"releases/tag/"); 703 | if (found_ptr[13]=='v' || found_ptr[13]=='V') found_ptr++; 704 | version=malloc(strlen(found_ptr+13)+1); 705 | strcpy(version,found_ptr+13); 706 | printf("%s@version:\"%s\" according to latest release\n",repo,version); 707 | } else { 708 | UDPLGP("failed, return [-0x%x]\n", -ret); 709 | ret=wolfSSL_get_error(ssl,ret); 710 | UDPLGP("wolfSSL_send error = %d\n", ret); 711 | } 712 | } 713 | switch (retc) { 714 | case 0: 715 | case -1: 716 | wolfSSL_free(ssl); 717 | case -2: 718 | lwip_close(socket); 719 | case -3: 720 | default: 721 | ; 722 | } 723 | 724 | // if (retc) return retc; 725 | // if (ret <= 0) return ret; 726 | 727 | //TODO: maybe add more error return messages... like version "99999.99.99" 728 | //find latest-pre-release if joined beta program 729 | bool OTAorBTL=!(strcmp(OTAREPO,repo)&&strcmp(BTLREPO,repo)); 730 | if ( (userbeta && !OTAorBTL) || (otabeta && OTAorBTL)) { 731 | prerelease[63]=0; 732 | ret=ota_get_file_ex(repo,version,"latest-pre-release",0,(byte *)prerelease,63); 733 | if (ret>0) { 734 | prerelease[ret]=0; //TODO: UNTESTED make a final 0x0a and or 0x0d optional 735 | if (prerelease[ret-1]=='\n') { 736 | prerelease[ret-1]=0; 737 | if (prerelease[ret-2]=='\r') prerelease[ret-2]=0; 738 | } 739 | free(version); 740 | version=malloc(strlen(prerelease)+1); 741 | strcpy(version,prerelease); 742 | } 743 | } 744 | 745 | if (ota_boot() && ota_compare(version,OTAVERSION)<0) { //this acts when setting up a new version 746 | free(version); 747 | version=malloc(strlen(OTAVERSION)+1); 748 | strcpy(version,OTAVERSION); 749 | } 750 | 751 | UDPLGP("%s@version:\"%s\"\n",repo,version); 752 | return version; 753 | } 754 | 755 | int ota_get_file_ex(char * repo, char * version, char * file, int sector, byte * buffer, int bufsz) { //number of bytes 756 | UDPLGP("--- ota_get_file_ex\n"); 757 | 758 | int retc, ret=0, slash; 759 | WOLFSSL* ssl; 760 | int socket; 761 | //host=begin(repo); 762 | //mid =end(repo)+blabla+version 763 | char* found_ptr=NULL; 764 | char recv_buf[RECV_BUF_LEN]; 765 | int recv_bytes = 0; 766 | int send_bytes; //= sizeof(send_data); 767 | int length=1; 768 | int clength=0; 769 | int left; 770 | int collected=0; 771 | int writespace=0; 772 | int header; 773 | bool emergency=(strcmp(version,EMERGENCY))?0:1; 774 | int port=(emergency)?HTTP_PORT:HTTPS_PORT; 775 | 776 | if (sector==0 && buffer==NULL) return -5; //needs to be either a sector or a signature 777 | 778 | if (!emergency) { //if not emergency, find the redirection done by GitHub 779 | strcat(strcat(strcat(strcat(strcat(strcat(strcat(strcat(strcpy(recv_buf, \ 780 | REQUESTHEAD),repo),"/releases/download/"),version),"/"),file),REQUESTTAIL),HOST),CRLFCRLF); 781 | send_bytes=strlen(recv_buf); 782 | UDPLGP("%s",recv_buf); 783 | 784 | retc = ota_connect(HOST, HTTPS_PORT, &socket, &ssl); //release socket and ssl when ready 785 | 786 | if (!retc) { 787 | ret = wolfSSL_write(ssl, recv_buf, send_bytes); 788 | if (ret > 0) { 789 | UDPLGP("sent OK\n"); 790 | 791 | ret = wolfSSL_peek(ssl, recv_buf, RECV_BUF_LEN - 1); 792 | if (ret > 0) { 793 | recv_buf[ret]=0; //prevent falling of the end of the buffer when doing string operations 794 | found_ptr=ota_strstr(recv_buf,"http/1.1 "); 795 | found_ptr+=9; //flush "HTTP/1.1 " 796 | slash=atoi(found_ptr); 797 | UDPLGP("HTTP returns %d\n",slash); 798 | if (slash!=302) { 799 | wolfSSL_free(ssl); 800 | lwip_close(socket); 801 | return -1; 802 | } 803 | } else { 804 | UDPLGP("failed, return [-0x%x]\n", -ret); 805 | ret=wolfSSL_get_error(ssl,ret); 806 | UDPLGP("wolfSSL_send error = %d\n", ret); 807 | return -1; 808 | } 809 | while (1) { 810 | recv_buf[ret]=0; //prevent falling of the end of the buffer when doing string operations 811 | found_ptr=ota_strstr(recv_buf,"\nlocation:"); 812 | if (found_ptr) break; 813 | wolfSSL_read(ssl, recv_buf, RECV_BUF_LEN - 12); 814 | ret = wolfSSL_peek(ssl, recv_buf, RECV_BUF_LEN - 1); 815 | if (ret <= 0) { 816 | UDPLGP("failed, return [-0x%x]\n", -ret); 817 | ret=wolfSSL_get_error(ssl,ret); 818 | UDPLGP("wolfSSL_send error = %d\n", ret); 819 | return -1; 820 | } 821 | } 822 | ret=wolfSSL_read(ssl, recv_buf, found_ptr-recv_buf + 11); //flush all previous material 823 | ret=wolfSSL_read(ssl, recv_buf, RECV_BUF_LEN - 1); //this starts for sure with the content of "Location: " 824 | recv_buf[ret]=0; //prevent falling of the end of the buffer when doing string operations 825 | strchr(recv_buf,'\r')[0]=0; 826 | found_ptr=recv_buf; 827 | //if (found_ptr[0] == ' ') found_ptr++; 828 | found_ptr+=8; //flush https:// 829 | //printf("location=%s\n",found_ptr); 830 | } else { 831 | UDPLGP("failed, return [-0x%x]\n", -ret); 832 | ret=wolfSSL_get_error(ssl,ret); 833 | UDPLGP("wolfSSL_send error = %d\n", ret); 834 | } 835 | } 836 | switch (retc) { 837 | case 0: 838 | case -1: 839 | wolfSSL_free(ssl); 840 | case -2: 841 | lwip_close(socket); 842 | case -3: 843 | default: 844 | ; 845 | } 846 | 847 | if (retc) return retc; 848 | if (ret <= 0) return ret; 849 | 850 | } else { //emergency mode, repo is expected to have the format "not.github.com/somewhere/" 851 | strcpy(recv_buf,repo); 852 | found_ptr=recv_buf; 853 | if (found_ptr[strlen(found_ptr)-1]!='/') strcat(found_ptr, "/"); 854 | strcat(found_ptr, file); 855 | UDPLGP("emergency GET http://%s\n",found_ptr); 856 | } //found_ptr now contains the url without https:// or http:// 857 | //process the Location 858 | strcat(found_ptr, REQUESTTAIL); 859 | slash=strchr(found_ptr,'/')-found_ptr; 860 | found_ptr[slash]=0; //cut behind the hostname 861 | char * host2=malloc(strlen(found_ptr)+1); 862 | strcpy(host2,found_ptr); 863 | //printf("next host: %s\n",host2); 864 | 865 | retc = ota_connect(host2, port, &socket, &ssl); //release socket and ssl when ready 866 | 867 | strcat(strcat(found_ptr+slash+1,host2),RANGE); //append hostname and range to URI 868 | found_ptr+=slash-4; 869 | memcpy(found_ptr,REQUESTHEAD,5); 870 | char * getlinestart=malloc(strlen(found_ptr)+1); 871 | strcpy(getlinestart,found_ptr); 872 | //printf("request:\n%s\n",getlinestart); 873 | //if (!retc) { 874 | while (collected 0) { 882 | printf("OK\n"); 883 | 884 | header=1; 885 | memset(recv_buf,0,RECV_BUF_LEN); 886 | //wolfSSL_Debugging_ON(); 887 | do { 888 | if (emergency) ret = lwip_read(socket, recv_buf, RECV_BUF_LEN - 1); else ret = wolfSSL_read(ssl, recv_buf, RECV_BUF_LEN - 1); 889 | if (ret > 0) { 890 | if (header) { 891 | //printf("%s\n-------- %d\n", recv_buf, ret); 892 | //parse Content-Length: xxxx 893 | found_ptr=ota_strstr(recv_buf,"\ncontent-length:"); 894 | strchr(found_ptr,'\r')[0]=0; 895 | found_ptr+=16; //flush Content-Length:// 896 | //if (found_ptr[0] == ' ') found_ptr++; //flush a space, atoi would also do that 897 | clength=atoi(found_ptr); 898 | found_ptr[strlen(found_ptr)]='\r'; //in case the order changes 899 | //parse Content-Range: bytes xxxx-yyyy/zzzz 900 | found_ptr=ota_strstr(recv_buf,"\ncontent-range:"); 901 | strchr(found_ptr,'\r')[0]=0; 902 | found_ptr+=15; //flush Content-Range:// 903 | found_ptr=ota_strstr(recv_buf,"bytes "); 904 | found_ptr+=6; //flush Content-Range: bytes // 905 | found_ptr=strstr(found_ptr,"/"); found_ptr++; //flush / 906 | length=atoi(found_ptr); 907 | found_ptr[strlen(found_ptr)]='\r'; //search the entire buffer again 908 | found_ptr=strstr(recv_buf,CRLFCRLF)+4; //go to end of header 909 | if ((left=ret-(found_ptr-recv_buf))) { 910 | header=0; //we have body in the same IP packet as the header so we need to process it already 911 | ret=left; 912 | memmove(recv_buf,found_ptr,left); //move this payload to the head of the recv_buf 913 | } 914 | } 915 | if (!header) { 916 | recv_bytes += ret; 917 | if (sector) { //write to flash 918 | if (writespace", sector+collected); 920 | if (!spiflash_erase_sector(sector+collected)) return -6; //erase error 921 | writespace+=SECTORSIZE; 922 | } 923 | if (collected) { 924 | if (!spiflash_write(sector+collected, (byte *)recv_buf, ret )) return -7; //write error 925 | } else { //at the very beginning, do not write the first byte yet but store it for later 926 | file_first_byte[0]=(byte)recv_buf[0]; 927 | if (!spiflash_write(sector+1 , (byte *)recv_buf+1, ret-1)) return -7; //write error 928 | } 929 | writespace-=ret; 930 | } else { //buffer 931 | if (ret>bufsz) return -8; //too big 932 | memcpy(buffer,recv_buf,ret); 933 | } 934 | collected+=ret; 935 | int i; 936 | for (i=0;i<3;i++) printf("%02x", recv_buf[i]); 937 | printf("..."); 938 | for (i=3;i>0;i--) printf("%02x", recv_buf[ret-i]); 939 | printf(" "); 940 | } 941 | } else { 942 | if (ret && !emergency) {ret=wolfSSL_get_error(ssl,ret); UDPLGP("error %d\n",ret);} 943 | if (!ret && collectedhash,0,HASHSIZE); 1003 | memset(signature->sign,0,SIGNSIZE); 1004 | ret=ota_get_file_ex(repo,version,signame,0,buffer,HASHSIZE+4+SIGNSIZE); 1005 | free(signame); 1006 | if (ret<0) return ret; 1007 | memcpy(signature->hash,buffer,HASHSIZE); 1008 | signature->size=((buffer[HASHSIZE]*256 + buffer[HASHSIZE+1])*256 + buffer[HASHSIZE+2])*256 + buffer[HASHSIZE+3]; 1009 | if (ret>HASHSIZE+4) memcpy(signature->sign,buffer+HASHSIZE+4,SIGNSIZE); 1010 | 1011 | return 0; 1012 | } 1013 | 1014 | int ota_verify_hash(int address, signature_t* signature) { 1015 | UDPLGP("--- ota_verify_hash\n"); 1016 | 1017 | byte hash[HASHSIZE]; 1018 | ota_hash(address, signature->size, hash, file_first_byte[0]); 1019 | // int i; 1020 | // printf("signhash:"); for (i=0;ihash[i]); printf("\n"); 1021 | // printf("calchash:"); for (i=0;ihash,HASHSIZE)) ota_hash(address, signature->size, hash, 0xff); 1024 | 1025 | return memcmp(hash,signature->hash,HASHSIZE); 1026 | } 1027 | 1028 | int ota_verify_signature(signature_t* signature) { 1029 | UDPLGP("--- ota_verify_signature\n"); 1030 | 1031 | int answer=0; 1032 | 1033 | wc_ecc_verify_hash(signature->sign, SIGNSIZE, signature->hash, HASHSIZE, &answer, &pubecckey); 1034 | UDPLGP("signature valid: %d\n",answer); 1035 | 1036 | return answer-1; 1037 | } 1038 | 1039 | void ota_kill_file(int sector) { 1040 | UDPLGP("--- ota_kill_file\n"); 1041 | 1042 | byte zero[]={0x00}; 1043 | if (!spiflash_write(sector, zero, 1)) UDPLGP("error writing flash\n"); 1044 | } 1045 | 1046 | void ota_swap_cert_sector() { 1047 | UDPLGP("--- ota_swap_cert_sector\n"); 1048 | 1049 | ota_kill_file(active_cert_sector); 1050 | ota_finalize_file(backup_cert_sector); 1051 | if (active_cert_sector==HIGHERCERTSECTOR) { 1052 | active_cert_sector=LOWERCERTSECTOR; 1053 | backup_cert_sector=HIGHERCERTSECTOR; 1054 | } else { 1055 | active_cert_sector=HIGHERCERTSECTOR; 1056 | backup_cert_sector=LOWERCERTSECTOR; 1057 | } 1058 | } 1059 | 1060 | void ota_write_status(char * version) { 1061 | UDPLGP("--- ota_write_status\n"); 1062 | 1063 | sysparam_set_string("ota_version", version); 1064 | } 1065 | 1066 | int ota_boot(void) { 1067 | UDPLGP("--- ota_boot..."); 1068 | byte bootrom; 1069 | rboot_get_last_boot_rom(&bootrom); 1070 | UDPLGP("%d\n",bootrom); 1071 | return 1-bootrom; 1072 | } 1073 | 1074 | void ota_temp_boot(void) { 1075 | UDPLGP("--- ota_temp_boot\n"); 1076 | 1077 | rboot_set_temp_rom(1); 1078 | vTaskDelay(20); //allows UDPLOG to flush 1079 | sdk_system_restart(); 1080 | } 1081 | 1082 | void ota_reboot(void) { 1083 | UDPLGP("--- ota_reboot\n"); 1084 | 1085 | if (ledblinkHandle) { 1086 | vTaskDelete(ledblinkHandle); 1087 | gpio_enable(led, GPIO_INPUT); 1088 | gpio_set_pullup(led, 0, 0); 1089 | } 1090 | vTaskDelay(20); //allows UDPLOG to flush 1091 | sdk_system_restart(); 1092 | } 1093 | 1094 | int ota_emergency(char * *ota_srvr) { 1095 | UDPLGP("--- ota_emergency?\n"); 1096 | 1097 | if (otabeta) { 1098 | char *value; 1099 | if (sysparam_get_string("ota_srvr", &value)== SYSPARAM_OK) *ota_srvr=value; else return 0; 1100 | sysparam_set_string("ota_srvr",""); 1101 | sysparam_set_data("lcm_beta", NULL,0,0); 1102 | UDPLGP("YES: backing up from http://%s\n",*ota_srvr); 1103 | return 1; 1104 | } else return 0; 1105 | } 1106 | -------------------------------------------------------------------------------- /ota.h: -------------------------------------------------------------------------------- 1 | /* (c) 2018-2022 HomeAccessoryKid */ 2 | #ifndef __OTA_H__ 3 | #define __OTA_H__ 4 | 5 | #ifndef OTAVERSION 6 | #error You must set OTAVERSION=x.y.z of the ota code to match github version tag x.y.z 7 | #endif 8 | #define OTAREPO "HomeACcessoryKid/life-cycle-manager" 9 | //#define LCMREPO "HomeACcessoryKid/life-cycle-manager" 10 | #define BTLREPO "HomeACcessoryKid/rboot4lcm" 11 | #define BTLFILE "rboot.bin" 12 | #define MAINFILE "otamain.bin" 13 | #define BOOTFILE "otaboot.bin" 14 | #define CERTFILE "certs.sector" 15 | #define HAAREPO "RavenSystem/haa" 16 | #define HAAFILE "haa_lcm.bin" 17 | #define HOLDOFF_MULTIPLIER 20 //more like 20 -> 20s,400 (~6min),8000 (~2h),160000 (~2days) 18 | #define HOLDOFF_MAX 604800 //more like 604800 (1 week) 19 | #define BLINKDELAY 250 20 | #define EMERGENCY "emergency" 21 | 22 | #define SECTORSIZE 4096 23 | #define HIGHERCERTSECTOR 0xFA000 24 | #define LOWERCERTSECTOR 0xF9000 25 | #define SYSPARAMSECTOR 0xF7000 26 | #define BOOT0SECTOR 0x02000 27 | #define BOOT1SECTOR 0x8D000 //must match the program1.ld value!! 28 | #define HOST "github.com" 29 | #define HTTPS_PORT 443 30 | #define HTTP_PORT 80 31 | #define FAILED "failed\n" 32 | #define REQUESTHEAD "GET /" 33 | #define REQUESTTAIL " HTTP/1.1\r\nHost: " 34 | #define CRLFCRLF "\r\n\r\n" 35 | #define RECV_BUF_LEN 1025 // current length of amazon URL 724 36 | #define RANGE "\r\nRange: bytes=" 37 | #define MAXVERSIONLEN 16 38 | #define SNTP_SERVERS "0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org" 39 | 40 | #define ECDSAKEYLENGTHMAX 128 //to be verified better, example is 120 bytes secP384r1 41 | #define HASHSIZE 48 //SHA-384 42 | #define SIGNSIZE 104 //ECDSA r+s in ASN1 format secP384r1 43 | #define PKEYSIZE 120 //size of a pub key 44 | #define KEYNAME "public-%d.key" 45 | #define KEYNAMELEN 17 //allows for 65535 keys with uint16_t 46 | 47 | typedef unsigned char byte; 48 | 49 | typedef struct { 50 | byte hash[HASHSIZE]; 51 | unsigned int size; //32 bit 52 | byte sign[SIGNSIZE]; 53 | } signature_t; 54 | 55 | int active_cert_sector; 56 | int backup_cert_sector; 57 | 58 | void ota_read_rtc(); 59 | 60 | void ota_active_sector(); 61 | 62 | void ota_init(); 63 | 64 | int ota_get_privkey(); 65 | 66 | int ota_get_pubkey(int sector); //get the ecdsa key from the active_cert_sector 67 | 68 | int ota_verify_pubkey(void); //check if public and private key are a pair 69 | 70 | void ota_sign(int start_sector, int num_sectors, signature_t* signature, char* file); 71 | 72 | int ota_compare(char* newv, char* oldv); 73 | 74 | int ota_load_user_app(char * *repo, char * *version, char * *file); 75 | 76 | void ota_set_verify(int onoff); 77 | 78 | void ota_copy_bootloader(int sector, int size, char * version); 79 | 80 | char* ota_get_btl_version(); 81 | 82 | char* ota_get_version(char * repo); 83 | 84 | int ota_get_file(char * repo, char * version, char * file, int sector); //number of bytes 85 | 86 | void ota_finalize_file(int sector); 87 | 88 | int ota_get_newkey(char * repo, char * version, char * file, signature_t* signature); 89 | 90 | int ota_get_hash(char * repo, char * version, char * file, signature_t* signature); 91 | 92 | int ota_verify_hash(int address, signature_t* signature); 93 | 94 | int ota_verify_signature(signature_t* signature); 95 | 96 | void ota_swap_cert_sector(); 97 | 98 | void ota_write_status(char * version); 99 | 100 | int ota_boot(void); 101 | 102 | void ota_temp_boot(void); 103 | 104 | void ota_reboot(void); 105 | 106 | int ota_emergency(char * *ota_srvr); 107 | 108 | #endif // __OTA_H__ 109 | -------------------------------------------------------------------------------- /udplogger.c: -------------------------------------------------------------------------------- 1 | // (c) 2018-2021 HomeAccessoryKid 2 | 3 | #include 4 | #include 5 | #include 6 | // //#include //for timestamp report only 7 | // #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | char udplogstring[2900]={0}; //in the end I do not know to prevent overflow, so I use the max size of 2 UDP packets ?? 17 | int udplogstring_len=0; 18 | 19 | void udplog_send(void *pvParameters){ 20 | int lSocket,i=0; 21 | struct sockaddr_in sLocalAddr, sDestAddr; 22 | 23 | while (sdk_wifi_station_get_connect_status() != STATION_GOT_IP) vTaskDelay(20); //Check if we have an IP every 200ms 24 | 25 | lSocket = lwip_socket(AF_INET, SOCK_DGRAM, 0); 26 | memset((char *)&sLocalAddr, 0, sizeof(sLocalAddr)); 27 | memset((char *)&sDestAddr, 0, sizeof(sDestAddr)); 28 | /*Destination*/ 29 | sDestAddr.sin_family = AF_INET; 30 | sDestAddr.sin_len = sizeof(sDestAddr); 31 | sDestAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); 32 | sDestAddr.sin_port =28338; //= 45678; //reversed bytes 33 | /*Source*/ 34 | sLocalAddr.sin_family = AF_INET; 35 | sLocalAddr.sin_len = sizeof(sLocalAddr); 36 | sLocalAddr.sin_addr.s_addr = htonl(INADDR_ANY); 37 | sLocalAddr.sin_port =40109; //= 44444; //reversed bytes 38 | lwip_bind(lSocket, (struct sockaddr *)&sLocalAddr, sizeof(sLocalAddr)); 39 | 40 | while (1) { 41 | if ((!i && udplogstring_len) || udplogstring_len>700) { 42 | lwip_sendto(lSocket, udplogstring, udplogstring_len, 0, (struct sockaddr *)&sDestAddr, sizeof(sDestAddr)); 43 | udplogstring_len=0; 44 | i=10; 45 | } 46 | if (!i) i=10; //sends output every 100ms if not more than 700 bytes 47 | i--; 48 | vTaskDelay(1); //with len>1000 and delay=10ms, we might handle 800kbps throughput 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /udplogger.h: -------------------------------------------------------------------------------- 1 | // (c) 2018-2021 HomeAccessoryKid 2 | #ifndef __UDPLOGGER_H__ 3 | #define __UDPLOGGER_H__ 4 | 5 | //use nc -kulnw0 45678 to collect this output 6 | //and use xTaskCreate(udplog_send, "logsend", 256, NULL, 4, NULL); //is prio4 a good idea?? 7 | 8 | #define UDPLOG(format, ...) udplogstring_len+=sprintf(udplogstring+udplogstring_len,format,##__VA_ARGS__) 9 | #define UDPLGP(format, ...) do {printf(format,##__VA_ARGS__); \ 10 | udplogstring_len+=sprintf(udplogstring+udplogstring_len,format,##__VA_ARGS__); \ 11 | } while(0) 12 | void udplog_send(void *pvParameters); 13 | extern char udplogstring[]; 14 | extern int udplogstring_len; 15 | 16 | #endif //__UDPLOGGER_H__ 17 | -------------------------------------------------------------------------------- /versions1/.gitignore: -------------------------------------------------------------------------------- 1 | *prv* 2 | *.hex 3 | *.sig 4 | -------------------------------------------------------------------------------- /versions1/2.2.6v/certs.sector: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/2.2.6v/certs.sector -------------------------------------------------------------------------------- /versions1/2.2.6v/latest-pre-release: -------------------------------------------------------------------------------- 1 | 2.2.6 -------------------------------------------------------------------------------- /versions1/2.2.6v/otaboot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/2.2.6v/otaboot.bin -------------------------------------------------------------------------------- /versions1/2.2.6v/otabootbeta.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/2.2.6v/otabootbeta.bin -------------------------------------------------------------------------------- /versions1/2.2.6v/otamain.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/2.2.6v/otamain.bin -------------------------------------------------------------------------------- /versions1/AAACertificateServices.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb 3 | MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow 4 | GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj 5 | YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL 6 | MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE 7 | BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM 8 | GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP 9 | ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua 10 | BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe 11 | 3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 12 | YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR 13 | rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm 14 | ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU 15 | oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF 16 | MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v 17 | QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t 18 | b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF 19 | AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q 20 | GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz 21 | Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 22 | G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi 23 | l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 24 | smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /versions1/DigiCertGlobalRootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P 12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO 14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR 15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw 16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr 17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF 19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls 20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk 21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /versions1/DigiCertGlobalRootG2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH 5 | MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI 9 | 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx 10 | 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ 11 | q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz 12 | tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ 13 | vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP 14 | BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV 15 | 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY 16 | 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 17 | NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG 18 | Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 19 | 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe 20 | pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl 21 | MrY= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /versions1/DigiCertHighAssuranceEVRootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 5 | ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL 6 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 7 | LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug 8 | RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm 9 | +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW 10 | PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM 11 | xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB 12 | Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 13 | hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg 14 | EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF 15 | MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA 16 | FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec 17 | nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z 18 | eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF 19 | hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 20 | Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe 21 | vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep 22 | +OkuE6N36B9K 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /versions1/blank.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/blank.bin -------------------------------------------------------------------------------- /versions1/certs.sector: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/certs.sector -------------------------------------------------------------------------------- /versions1/secp384r1pub.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomeACcessoryKid/life-cycle-manager/24720c73e050ba2ed1be7f050c79115d76418c1d/versions1/secp384r1pub.der --------------------------------------------------------------------------------