├── .editorconfig ├── .gitignore ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── css ├── styles.css └── styles.css.map ├── data └── .gitkeep ├── favicon.ico ├── images └── github.png ├── index.html ├── js ├── constants.js ├── ctaphid.js ├── helpers.js ├── main.js └── vendor │ ├── cbor.js │ ├── intel-hex.js │ ├── platform.js │ ├── sha256.js │ └── u2f-api.js ├── netlify.toml ├── sass └── styles.scss ├── scripts ├── fetch-firmware.sh ├── make-localhost-cert.sh └── reloading-serve.sh └── serve-local.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | tab_width = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [Makefile] 13 | indent_style = tab 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | localhost.key 2 | localhost.crt 3 | venv/ 4 | tools/ 5 | data/* 6 | !data/.gitkeep 7 | 8 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | serve-local: venv 2 | scripts/reloading-serve.sh venv/bin/python3 serve-local.py 3 | 4 | setup-local: venv get-bulma get-sass fetch-firmware 5 | setup-local-full: setup-local get-bulma get-sass styles 6 | 7 | # Only needed to experiment with U2F 8 | # WebAuthn accepts http://localhost as secure origin 9 | localhost-cert: 10 | scripts/make-localhost-cert.sh 11 | 12 | fetch-firmware: 13 | scripts/fetch-firmware.sh 14 | 15 | venv: 16 | python3 -m venv venv 17 | venv/bin/pip3 install -U pip 18 | 19 | styles: 20 | tools/dart-sass/sass -s compressed sass/styles.scss css/styles.css 21 | 22 | BULMA_VERSION = "0.7.4" 23 | get-bulma: 24 | mkdir -p tools 25 | rm -rf tools/bulma 26 | wget https://github.com/jgthms/bulma/releases/download/$(BULMA_VERSION)/bulma-$(BULMA_VERSION).zip 27 | unzip -d tools bulma-$(BULMA_VERSION).zip 28 | rm -rf bulma-$(BULMA_VERSION).zip 29 | rm -rf tools/__MACOSX 30 | mv tools/bulma-$(BULMA_VERSION) tools/bulma 31 | 32 | DART_SASS_VERSION = "1.16.1" 33 | get-sass: 34 | mkdir -p tools 35 | rm -rf tools/dart-sass 36 | wget https://github.com/sass/dart-sass/releases/download/$(DART_SASS_VERSION)/dart-sass-$(DART_SASS_VERSION)-linux-x64.tar.gz 37 | tar -C tools -xf dart-sass-$(DART_SASS_VERSION)-linux-x64.tar.gz 38 | rm -rf dart-sass-$(DART_SASS_VERSION)-linux-x64.tar.gz 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Netlify Status](https://api.netlify.com/api/v1/badges/cf747fa0-e4d2-4405-897c-47cc3affba8e/deploy-status)](https://app.netlify.com/sites/solokeys-webupdate/deploys) 2 | 3 | # solo-webupdate 4 | 5 | ## What is this? 6 | While advanced users capable of using the command line can test if their keys are genuine, and update them via 7 | , this project lets you do the same more easily 8 | using any WebAuthn-capable web browser (all current versions of Chrome, Firefox, Edge, Opera, Vivaldi, ...). 9 | 10 | To use it, visit and follow the instructions. 11 | 12 | ## What does it do? 13 | In a first step, it inspects your key to determine whether you have a Solo Secure or a Solo Hacker. 14 | 15 | During this process, the signature of the key is checked, letting you determine whether your key is genuine. 16 | 17 | Once this is done, it tells you whether the firmware is out of date. 18 | If so, we recommend you update, to receive bug fixes and possibly additional functionality. 19 | 20 | To do so, you need to unplug your key, and plug it in an while keeping its button pressed, until the LED light blinks. 21 | 22 | Once this is done, your key is in "bootloader mode" and can be updated by the website. 23 | 24 | You can simply click on "Update Firmware", and your key will be updated. 25 | 26 | **NOTE:** Due to how this update is implemented, while your firmware is being updated you will see a LOT of popups (that disappear again immediately). This is no reason for concern! 27 | 28 | ## How does it work? 29 | For general information on WebAuthn, the new standard for secure login in the browser, we recommend the following sites: 30 | - https://webauthn.io 31 | - https://webauthn.guide 32 | - https://www.w3.org/TR/webauthn/ 33 | 34 | This standard replaces its predecessor, U2F, and consists of two API methods: 35 | - `navigator.credentials.create()` 36 | - `navigator.credentials.get()` 37 | 38 | We use the first method to verify that your Solo is genuine, and determine whether it is the secure or the hacker version. 39 | 40 | We use the second method to update your key if necessary. The method used is to encode custom commands that your key understands, and [send these encoded commands as "keyhandle"](https://github.com/solokeys/solo-webupdate/blob/master/js/ctaphid.js). 41 | 42 | ## Hosting & Verification 43 | The site is hosted on Netlify, as configured via [netlify.toml](netlify.toml). 44 | 45 | ## Running solo-webupdate locally 46 | 47 | *For advanced users only* 48 | 49 | 1. Download or clone the solo-webupdate repo to a directory on your machine. 50 | 1. Enter the _solo-webupdate_ directory in your terminal and run the following two commands: 51 | ``` 52 | make setup-local 53 | make serve-local 54 | ``` 55 | Note that you may get messages about missing packages and dependencies when running the `make setup-local` command. Install and then run the command again. 56 | 1. If all goes well, you will get a message `serving on http://localhost:8080`. Visit to access webupdate on your machine. 57 | 1. Follow the on screen directions to update your firmware. *Do not* leave the webupdate window or change tabs when running webupdate. Stay on the webupdate page until the process is complete. This does not take long. 58 | 59 | ## Troubleshooting 60 | 61 | * If your Solo is stuck in bootloader mode (yellow flashing LED) and webupdate won't detect it, then you may need to use _Advanced Mode_ to re-flash the Solo Key. 62 | 1. Make sure you know which version of the Solo Key you are working with. It's either _Solo Secure_ or _Solo Hacker_. 63 | 1. Click on the _Advanced Mode (DANGER ZONE)_ button. 64 | 1. Select the appropriate Solo Key version you have and follow the on-screen instructions. 65 | 66 | One technical detail: Our official firmware builds are hosted as releases under , with the file [STABLE_VERSION](https://github.com/solokeys/solo/blob/master/STABLE_VERSION) specifying the latest stable version. Unfortunately, due to [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) restrictions, these files can't be fetched by your browser during the update process. For this reason, the current latest builds are [fetched and hosted](https://github.com/solokeys/solo-webupdate/blob/master/scripts/fetch-firmware.sh) on the update site itself, which always checks whether its firmware is out of date. 67 | -------------------------------------------------------------------------------- /css/styles.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../sass/styles.scss","../tools/bulma/bulma.sass","../tools/bulma/sass/utilities/animations.sass","../tools/bulma/sass/utilities/mixins.sass","../tools/bulma/sass/utilities/initial-variables.sass","../tools/bulma/sass/utilities/controls.sass","../tools/bulma/sass/base/minireset.sass","../tools/bulma/sass/base/generic.sass","../tools/bulma/sass/base/helpers.sass","../tools/bulma/sass/elements/box.sass","../tools/bulma/sass/elements/button.sass","../tools/bulma/sass/elements/container.sass","../tools/bulma/sass/elements/content.sass","../tools/bulma/sass/elements/form.sass","../tools/bulma/sass/elements/icon.sass","../tools/bulma/sass/elements/image.sass","../tools/bulma/sass/elements/notification.sass","../tools/bulma/sass/elements/progress.sass","../tools/bulma/sass/elements/table.sass","../tools/bulma/sass/utilities/derived-variables.sass","../tools/bulma/sass/elements/tag.sass","../tools/bulma/sass/elements/title.sass","../tools/bulma/sass/elements/other.sass","../tools/bulma/sass/components/breadcrumb.sass","../tools/bulma/sass/components/card.sass","../tools/bulma/sass/components/dropdown.sass","../tools/bulma/sass/components/level.sass","../tools/bulma/sass/components/list.sass","../tools/bulma/sass/components/media.sass","../tools/bulma/sass/components/menu.sass","../tools/bulma/sass/components/message.sass","../tools/bulma/sass/components/modal.sass","../tools/bulma/sass/components/navbar.sass","../tools/bulma/sass/components/pagination.sass","../tools/bulma/sass/components/panel.sass","../tools/bulma/sass/components/tabs.sass","../tools/bulma/sass/grid/columns.sass","../tools/bulma/sass/grid/tiles.sass","../tools/bulma/sass/layout/hero.sass","../tools/bulma/sass/layout/section.sass","../tools/bulma/sass/layout/footer.sass"],"names":[],"mappings":"CAEA,KACI,uBCFJ,8DCDA,sBACE,KACE,uBACF,GACE,0BCuIJ,kJANE,2BACA,yBACA,sBACA,qBACA,iBAqBF,yFAfE,6BACA,kBACA,eACA,aACA,YACA,cACA,cACA,qBACA,oBACA,kBACA,QACA,yBACA,wBACA,aAMA,wYACE,qBAuEJ,qBAhEE,qBACA,wBACA,mCACA,YACA,cC5Ge,SD6Gf,eACA,oBACA,qBACA,YACA,cACA,YACA,YACA,gBACA,eACA,gBACA,eACA,aACA,kBACA,mBACA,WACA,wEAEE,iBClLW,KDmLX,WACA,cACA,SACA,kBACA,QACA,0DACA,+BACF,qCACE,WACA,UACF,mCACE,WACA,UACF,kEAEE,mCACF,mCACE,mCAEF,uCACE,YACA,gBACA,eACA,gBACA,eACA,WACF,yCACE,YACA,gBACA,eACA,gBACA,eACA,WACF,uCACE,YACA,gBACA,eACA,gBACA,eACA,WAiBJ,uFAXE,2CACA,yBACA,cC9Ke,SD+Kf,+BACA,6BACA,WACA,cACA,WACA,kBACA,UAYF,ywBANE,OADgB,EAEhB,KAFgB,EAGhB,kBACA,MAJgB,EAKhB,IALgB,EErNlB,yIA3BE,qBACA,wBACA,mBACA,6BACA,cDgDO,IC/CP,gBACA,oBACA,UDiBO,KChBP,OAfe,OAgBf,2BACA,YAhBoB,IAiBpB,eAfyB,oBAgBzB,aAf2B,oBAgB3B,cAhB2B,oBAiB3B,YAlByB,oBAmBzB,kBACA,mBAEA,w3BAIE,aACF,slBAEE,mBCrCJ,2EAEA,yGAuBE,SACA,UAGF,kBAME,eACA,mBAGF,GACE,gBAGF,6BAIE,SAGF,KACE,sBAGA,qBAGE,mBAGJ,8BAKE,YACA,eAEF,MACE,eAGF,OACE,SAGF,MACE,yBACA,iBAEF,MAEE,UACA,gBC/DF,KACE,iBHPa,KGQb,UArBU,KAsBV,kCACA,mCACA,gBACA,kBACA,kBACA,eA1Be,mBA2Bf,sBAEF,kDAOE,cAEF,kCAKE,YPvCkB,WOyCpB,SAEE,6BACA,4BACA,YHvBiB,UGyBnB,KACE,MH/Ca,QGgDb,eACA,YHhBc,IGiBd,YAnDiB,IAuDnB,EACE,MPpDK,QOqDL,eACA,qBACA,SACE,mBACF,QACE,MH9DW,QGgEf,KACE,iBH3Da,QG4Db,MPhEI,QOiEJ,UA9DU,OA+DV,YAhEY,OAiEZ,QAlEa,iBAoEf,GACE,iBHlEa,QGmEb,YACA,cACA,OAnEU,IAoEV,OAnEU,SAqEZ,IACE,YACA,eAEF,uCAEE,wBAEF,MACE,iBAEF,KACE,mBACA,oBAEF,OACE,MH9Fa,QG+Fb,YH1DY,IG8Dd,SACE,YAEF,IJ9CE,iCIgDA,iBHlGa,QGmGb,MHxGa,QGyGb,iBACA,gBACA,uBACA,gBACA,iBACA,SACE,6BACA,mBACA,cACA,UAGF,kBAEE,gBACA,mBACF,SACE,MH3HW,QDHb,oBACE,WACA,YACA,cKDJ,gBACE,sBAEF,iBACE,uBAIF,YACE,2BAYE,WACE,0BADF,WACE,4BADF,WACE,0BADF,WACE,4BADF,WACE,6BADF,WACE,0BADF,WACE,4BLsDJ,qCKvDE,kBACE,0BADF,kBACE,4BADF,kBACE,0BADF,kBACE,4BADF,kBACE,6BADF,kBACE,0BADF,kBACE,6BL0DJ,2CK3DE,kBACE,0BADF,kBACE,4BADF,kBACE,0BADF,kBACE,4BADF,kBACE,6BADF,kBACE,0BADF,kBACE,6BLkEJ,sCKnEE,iBACE,0BADF,iBACE,4BADF,iBACE,0BADF,iBACE,4BADF,iBACE,6BADF,iBACE,0BADF,iBACE,6BLsEJ,sCKvEE,mBACE,0BADF,mBACE,4BADF,mBACE,0BADF,mBACE,4BADF,mBACE,6BADF,mBACE,0BADF,mBACE,6BLqFF,sCKtFA,sBACE,0BADF,sBACE,4BADF,sBACE,0BADF,sBACE,4BADF,sBACE,6BADF,sBACE,0BADF,sBACE,6BLoGF,sCKrGA,kBACE,0BADF,kBACE,4BADF,kBACE,0BADF,kBACE,4BADF,kBACE,6BADF,kBACE,0BADF,kBACE,6BAyBJ,mBACE,6BADF,oBACE,8BADF,eACE,2BADF,gBACE,4BL4BF,qCKxBE,0BACE,8BL2BJ,2CKzBE,0BACE,8BL4BJ,4DK1BE,+BACE,8BL6BJ,sCK3BE,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W,KM0LX,aN9LW,QM+LX,WA/KqB,KAgLrB,QA/KsB,GAgLxB,qBACE,aACA,WACF,mBACE,6BACA,oBACA,0BPxMF,kBAKE,2BACA,0BOqME,6BACJ,kBACE,iBN1MW,QM2MX,aN7MW,QM8MX,MNhNW,QMiNX,gBACA,oBACF,mBACE,cN1Ja,SM2Jb,iBACA,kBAEJ,SACE,mBACA,aACA,eACA,2BACA,iBACE,oBACA,qDACE,mBACJ,oBACE,sBACF,0BACE,mBAGA,0EA5MF,cN4Ba,IM3Bb,UNCO,OM6ML,0EA1MF,UNLO,QMkNL,0EA3MF,UNRO,OMuNH,8CACE,4BACA,yBACF,6CACE,6BACA,0BACA,kBACF,uCACE,eACF,yEAEE,UACF,0LAKE,UACA,wNACE,UACJ,wCACE,YACN,qBACE,uBACF,kBACE,yBClRJ,WACE,cACA,kBR+FA,sCQjGF,WAII,gBACA,YACA,oBACE,YPyCA,KOxCA,aPwCA,KOvCA,eACA,YRiGF,sCQ/FA,yBACE,iBACA,YR4GF,sCQ1GA,qBACE,iBACA,YR8FF,sCQhHJ,WAoBI,iBACA,cR0GA,sCQ/HJ,WAuBI,iBACA,cCFF,eACE,iBASA,sNACE,kBACJ,wEAME,MRlCW,QQmCX,YRCc,gBQxCY,MAyC5B,YACE,cACA,mBACA,8BACE,eACJ,YACE,iBACA,sBACA,8BACE,oBACJ,YACE,gBACA,sBACA,8BACE,oBACJ,YACE,iBACA,mBACF,YACE,kBACA,sBACF,YACE,cACA,kBACF,oBACE,iBRxDW,QQyDX,YAhE6B,kBAiE7B,QAhEyB,aAiE3B,YACE,4BACA,gBACA,eACA,wBACE,wBACA,uCACE,4BACF,uCACE,4BACF,uCACE,4BACF,uCACE,4BACN,YACE,wBACA,gBACA,eACA,eACE,uBACA,gBACA,kBACE,uBACN,YACE,gBACF,gBACE,gBACA,iBACA,kBACA,kCACE,eACF,iCACE,kBACF,oBACE,qBACF,2BACE,kBACJ,aT9CA,iCSgDE,gBACA,QAvGkB,aAwGlB,gBACA,iBACF,0BAEE,cACF,eACE,WACA,oCAEE,OA/GsB,kBAgHtB,aA/G4B,QAgH5B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ACA,aACA,2BACA,kBAMI,yBACE,iBAJI,KAKJ,yBACA,MALW,QAQX,mEACE,yBACA,yBACA,MAXS,QAcX,mEACE,yBACA,0CACA,MAjBS,QAoBX,mEACE,yBACA,yBACA,MAvBS,QAEb,yBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,mEACE,yBACA,yBACA,MAXS,KAcX,mEACE,yBACA,uCACA,MAjBS,KAoBX,mEACE,sBACA,yBACA,MAvBS,KAEb,yBACE,iBAJI,QAKJ,yBACA,MALW,QAQX,mEACE,sBACA,yBACA,MAXS,QAcX,mEACE,yBACA,0CACA,MAjBS,QAoBX,mEACE,yBACA,yBACA,MAvBS,QAEb,wBACE,iBAJI,QAKJ,yBACA,MALW,QAQX,iEACE,yBACA,yBACA,MAXS,QAcX,iEACE,yBACA,uCACA,MAjBS,QAoBX,iEACE,yBACA,yBACA,MAvBS,QAEb,2BACE,iBAJI,QAKJ,yBACA,MALW,KAQX,uEACE,yBACA,yBACA,MAXS,KAcX,uEACE,yBACA,yCACA,MAjBS,KAoBX,uEACE,yBACA,yBACA,MAvBS,KAEb,wBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,iEACE,yBACA,yBACA,MAXS,KAcX,iEACE,yBACA,yCACA,MAjBS,KAoBX,iEACE,yBACA,yBACA,MAvBS,KAEb,wBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,iEACE,yBACA,yBACA,MAXS,KAcX,iEACE,yBACA,yCACA,MAjBS,KAoBX,iEACE,yBACA,yBACA,MAvBS,KAEb,2BACE,iBAJI,QAKJ,yBACA,MALW,KAQX,uEACE,yBACA,yBACA,MAXS,KAcX,uEACE,yBACA,wCACA,MAjBS,KAoBX,uEACE,yBACA,yBACA,MAvBS,KAEb,2BACE,iBAJI,QAKJ,yBACA,MALW,eAQX,uEACE,yBACA,yBACA,MAXS,eAcX,uEACE,yBACA,yCACA,MAjBS,eAoBX,uEACE,yBACA,yBACA,MAvBS,eAEb,0BACE,iBAJI,QAKJ,yBACA,MALW,KAQX,qEACE,yBACA,yBACA,MAXS,KAcX,qEACE,yBACA,wCACA,MAjBS,KAoBX,qEACE,yBACA,yBACA,MAvBS,KAyBjB,eACE,UTzOK,OS0OP,gBACE,UT7OK,QS+OH,+BACE,eACN,eACE,UTnPK,OSqPH,8BACE,eAGJ,yBACE,6BACA,0BACF,0BACE,4BACA,yBAEA,kCACE,cTnOC,ISoOH,mCACE,aAEJ,2BACE,sBACF,yBACE,sBACA,YACA,gBACF,0BACE,uBACF,0BACE,aACA,YACA,8BACE,eAEF,uCACE,eAEF,wCACE,eAEF,uCACE,eAEF,kCACE,0BACF,mCACE,0BACA,uBACN,kBACE,uBAEA,+BACE,WACF,8BACE,YACA,eACJ,eACE,yBACA,yBACE,0BACF,0BACE,0BACA,2BACA,SAEN,YACE,oBACA,aACA,eACA,2BACA,gBACA,kBAEE,4BACE,sBACA,MTxVS,QSyVX,6BACE,qBAEF,6BACE,yBACA,MT9VS,QS+VX,8BACE,qBAEN,YACE,YACA,OACA,UACA,aACA,kBACA,MACA,WAEF,qBAGE,aT1Wa,QS2Wb,cTrTO,ISsTP,cACA,iBACA,kBACA,mBAEF,UACE,iBThXa,QSiXb,MTtXa,QSwXf,WACE,aTtXa,QSuXb,aA7VuB,MA8VvB,aA7VuB,cA8VvB,cACA,UA9VoB,KA+VpB,gBACA,gBACA,uBAEF,WACE,mBACA,aACA,WACA,uBACA,kBACA,UACA,eACE,eAEJ,OACE,MT9Ya,QS+Yb,cACA,UTlXO,KSmXP,YT5WY,IS6WZ,wBACE,mBAEF,gBACE,UTvXK,OSwXP,iBACE,UT3XK,QS4XP,gBACE,UT9XK,OSgYT,MACE,cACA,UT/XO,OSgYP,kBAGE,eACE,MAFM,KACR,eACE,MAFM,QACR,eACE,MAFM,QACR,cACE,MAFM,QACR,iBACE,MAFM,QACR,cACE,MAFM,QACR,cACE,MAFM,QACR,iBACE,MAFM,QACR,iBACE,MAFM,QACR,gBACE,MAFM,QAOV,wBACE,qBAEF,kBACE,aACA,2BAEE,4CACE,kBAEA,wNAGE,gBAEF,sMAGE,6BACA,0BAEF,mMAGE,4BACA,yBAKA,iXAEE,UACF,kuBAIE,UACA,0yBACE,UACR,uCACE,YACJ,sCACE,uBACF,mCACE,yBAEA,gDACE,YACA,cACN,kBACE,aACA,2BACA,2BACE,cACA,4CACE,gBACA,oBACF,uCACE,YACA,cACJ,sCACE,uBACF,mCACE,yBACF,uCACE,eAEE,4HAEE,qBACJ,kDACE,uBACF,wDACE,gBVnaN,2CUoaA,qBAEI,cAGJ,oBACE,kBV9aF,qCU4aF,aAII,qBV5aF,2CUwaF,aAMI,aACA,YACA,cACA,oBACA,iBACA,sBACE,UTpeG,OSqeH,mBACF,uBACE,mBACF,uBACE,UT3eG,QS4eH,mBACF,sBACE,UT/eG,OSgfH,oBAGJ,0BACE,gBVjcF,2CU+bF,YAII,aACA,aACA,YACA,cACA,mBACE,gBACF,mBACE,cACA,mCACE,YACF,oCACE,qBAER,SACE,sBACA,WACA,UTpgBO,KSqgBP,kBACA,gBAOM,gLACE,MT1iBK,QS2iBT,4LACE,UT/gBC,OSghBH,gMACE,UTnhBC,QSohBH,4LACE,UTthBC,OSuhBL,6DACE,MThjBS,QSijBT,ORtjBW,OQujBX,oBACA,kBACA,MACA,MR1jBW,OQ2jBX,UAEF,sEAEE,aR/jBW,OQgkBb,sCACE,OAEF,wEAEE,cRrkBW,OQskBb,wCACE,QAEF,2BAEE,6BACA,aACA,WACA,UACF,mCACE,UThjBG,OSijBL,oCACE,UTpjBG,QSqjBL,mCACE,UTvjBG,OU7BT,MACE,mBACA,oBACA,uBACA,OATgB,OAUhB,MAVgB,OAYhB,eACE,OAZoB,KAapB,MAboB,KActB,gBACE,OAdqB,KAerB,MAfqB,KAgBvB,eACE,OAhBoB,KAiBpB,MAjBoB,KCDxB,OACE,cACA,kBACA,WACE,cACA,YACA,WACA,sBACE,cXwDW,SWtCb,wtBAGE,YACA,WACJ,gCAEE,iBACF,eACE,gBACF,eACE,gBACF,eACE,qBACF,eACE,gBACF,gBACE,mBACF,eACE,gBACF,eACE,qBACF,eACE,iBACF,eACE,sBACF,eACE,iBACF,eACE,sBACF,gBACE,sBACF,eACE,iBACF,eACE,iBAGA,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gB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ACF,mCACE,UACA,WACF,6CACE,UACA,UACF,yCACE,UACA,eACF,mCACE,UACA,UACF,wCACE,UACA,eACF,0CACE,UACA,UACF,wCACE,UACA,UACF,yCACE,UACA,UACF,2CACE,UACA,UACF,0CACE,UACA,UACF,oDACE,gBACF,gDACE,qBACF,0CACE,gBACF,+CACE,qBACF,iDACE,gBACF,+CACE,gBACF,gDACE,gBACF,kDACE,gBACF,iDACE,gBAEA,gCACE,UACA,oBACF,uCACE,0BAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,UACF,uCACE,gBAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,UACF,uCACE,gBAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,UACF,uCACE,gBAJF,iCACE,UACA,qBACF,wCACE,2BAJF,iCACE,UACA,qBACF,wCACE,2BAJF,iCACE,UACA,WACF,wCACE,iBjCkBJ,qCiChBE,yBACE,UACF,uBACE,UACA,WACF,iCACE,UACA,UACF,6BACE,UACA,eACF,uBACE,UACA,UACF,4BACE,UACA,eACF,8BACE,UACA,UACF,4BACE,UACA,UACF,6BACE,UACA,UACF,+BACE,UACA,UACF,8BACE,UACA,UACF,wCACE,gBACF,oCACE,qBACF,8BACE,gBACF,mCACE,qBACF,qCACE,gBACF,mCACE,gBACF,oCACE,gBACF,sCACE,gBACF,qCACE,gBAEA,oBACE,UACA,oBACF,2BACE,0BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,WACF,4BACE,kBjCnCN,2CiCqCE,2CAEE,UACF,uCAEE,UACA,WACF,2DAEE,UACA,UACF,mDAEE,UACA,eACF,uCAEE,UACA,UACF,iDAEE,UACA,eACF,qDAEE,UACA,UACF,iDAEE,UACA,UACF,mDAEE,UACA,UACF,uDAEE,UACA,UACF,qDAEE,UACA,UACF,yEAEE,gBACF,iEAEE,qBACF,qDAEE,gBACF,+DAEE,qBACF,mEAEE,gBACF,+DAEE,gBACF,iEAEE,gBACF,qEAEE,gBACF,mEAEE,gBAEA,iCAEE,UACA,oBACF,+CAEE,0BANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,UACF,+CAEE,gBANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,UACF,+CAEE,gBANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,UACF,+CAEE,gBANF,mCAEE,UACA,qBACF,iDAEE,2BANF,mCAEE,UACA,qBACF,iDAEE,2BANF,mCAEE,UACA,WACF,iDAEE,kBjC1GN,sCiC4GE,wBACE,UACF,sBACE,UACA,WACF,gCACE,UACA,UACF,4BACE,UACA,eACF,sBACE,UACA,UACF,2BACE,UACA,eACF,6BACE,UACA,UACF,2BACE,UACA,UACF,4BACE,UACA,UACF,8BACE,UACA,UACF,6BACE,UACA,UACF,uCACE,gBACF,mCACE,qBACF,6BACE,gBACF,kCACE,qBACF,oCACE,gBACF,kCACE,gBACF,mCACE,gBACF,qCACE,gBACF,oCACE,gBAEA,mBACE,UACA,oBACF,0BACE,0BAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,UACF,0BACE,gBAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,UACF,0BACE,gBAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,UACF,0BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,WACF,2BACE,kBjC/JN,sCiCiKE,0BACE,UACF,wBACE,UACA,WACF,kCACE,UACA,UACF,8BACE,UACA,eACF,wBACE,UACA,UACF,6BACE,UACA,eACF,+BACE,UACA,UACF,6BACE,UACA,UACF,8BACE,UACA,UACF,gCACE,UACA,UACF,+BACE,UACA,UACF,yCACE,gBACF,qCACE,qBACF,+BACE,gBACF,oCACE,qBACF,sCACE,gBACF,oCACE,gBACF,qCACE,gBACF,uCACE,gBACF,sCACE,gBAEA,qBACE,UACA,oBACF,4BACE,0BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,UACF,4BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,UACF,4BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,UACF,4BACE,gBAJF,sBACE,UACA,qBACF,6BACE,2BAJF,sBACE,UACA,qBACF,6BACE,2BAJF,sBACE,UACA,WACF,6BACE,kBjCzMJ,sCiC2MA,6BACE,UACF,2BACE,UACA,WACF,qCACE,UACA,UACF,iCACE,UACA,eACF,2BACE,UACA,UACF,gCACE,UACA,eACF,kCACE,UACA,UACF,gCACE,UACA,UACF,iCACE,UACA,UACF,mCACE,UACA,UACF,kCACE,UACA,UACF,4CACE,gBACF,wCACE,qBACF,kCACE,gBACF,uCACE,qBACF,yCACE,gBACF,uCACE,gBACF,wCACE,gBACF,0CACE,gBACF,yCACE,gBAEA,wBACE,UACA,oBACF,+BACE,0BAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,UACF,+BACE,gBAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,UACF,+BACE,gBAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,UACF,+BACE,gBAJF,yBACE,UACA,qBACF,gCACE,2BAJF,yBACE,UACA,qBACF,gCACE,2BAJF,yBACE,UACA,WACF,gCACE,kBjCnPJ,sCiCqPA,yBACE,UACF,uBACE,UACA,WACF,iCACE,UACA,UACF,6BACE,UACA,eACF,uBACE,UACA,UACF,4BACE,UACA,eACF,8BACE,UACA,UACF,4BACE,UACA,UACF,6BACE,UACA,UACF,+BACE,UACA,UACF,8BACE,UACA,UACF,wCACE,gBACF,oCACE,qBACF,8BACE,gBACF,mCACE,qBACF,qCACE,gBACF,mCACE,gBACF,oCACE,gBACF,sCACE,gBACF,qCACE,gBAEA,oBACE,UACA,oBACF,2BACE,0BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,WACF,4BACE,kBAER,SACE,qBACA,sBACA,oBACA,oBACE,uBACF,0BACE,qCAEF,qBACE,uBACF,oBACE,cACA,eACA,aACA,4BACE,SACA,qBACF,qCACE,qBACF,+BACE,gBACJ,mBACE,aACF,sBACE,eACF,sBACE,mBjCnXF,2CiCsXE,0BACE,cjC3WJ,sCiC8WE,oBACE,cAGJ,qBACE,qBACA,wCACA,yCACA,6BACE,8BACA,+BAEA,0BACE,kBjC3YN,qCiC6YM,iCACE,mBjC1YR,2CiC4YM,iCACE,mBjCzYR,4DiC2YM,sCACE,mBjCxYR,sCiC0YM,gCACE,mBjCvYR,sCiCyYM,kCACE,mBjCrYN,6DiCuYI,uCACE,mBjC9XN,sCiCgYI,qCACE,mBjC5XN,6DiC8XI,0CACE,mBjCrXN,sCiCuXI,iCACE,mBA5BJ,0BACE,qBjC3YN,qCiC6YM,iCACE,sBjC1YR,2CiC4YM,iCACE,sBjCzYR,4DiC2YM,sCACE,sBjCxYR,sCiC0YM,gCACE,sBjCvYR,sCiCyYM,kCACE,sBjCrYN,6DiCuYI,uCACE,sBjC9XN,sCiCgYI,qCACE,sBjC5XN,6DiC8XI,0CACE,sBjCrXN,sCiCuXI,iCACE,sBA5BJ,0BACE,oBjC3YN,qCiC6YM,iCACE,qBjC1YR,2CiC4YM,iCACE,qBjCzYR,4DiC2YM,sCACE,qBjCxYR,sCiC0YM,gCACE,qBjCvYR,sCiCyYM,kCACE,qBjCrYN,6DiCuYI,uCACE,qBjC9XN,sCiCgYI,qCACE,qBjC5XN,6DiC8XI,0CACE,qBjCrXN,sCiCuXI,iCACE,qBA5BJ,0BACE,qBjC3YN,qCiC6YM,iCACE,sBjC1YR,2CiC4YM,iCACE,sBjCzYR,4DiC2YM,sCACE,sBjCxYR,sCiC0YM,gCACE,sBjCvYR,sCiCyYM,kCACE,sBjCrYN,6DiCuYI,uCACE,sBjC9XN,sCiCgYI,qCACE,sBjC5XN,6DiC8XI,0CACE,sBjCrXN,sCiCuXI,iCACE,sBA5BJ,0BACE,kBjC3YN,qCiC6YM,iCACE,mBjC1YR,2CiC4YM,iCACE,mBjCzYR,4DiC2YM,sCACE,mBjCxYR,sCiC0YM,gCACE,mBjCvYR,sCiCyYM,kCACE,mBjCrYN,6DiCuYI,uCACE,mBjC9XN,sCiCgYI,qCACE,mBjC5XN,6DiC8XI,0CACE,mBjCrXN,sCiCuXI,iCACE,mBA5BJ,0BACE,qBjC3YN,qCiC6YM,iCACE,sBjC1YR,2CiC4YM,iCACE,sBjCzYR,4DiC2YM,sCACE,sBjCxYR,sCiC0YM,gCACE,sBjCvYR,sCiCyYM,kCACE,sBjCrYN,6DiCuYI,uCACE,sBjC9XN,sCiCgYI,qCACE,sBjC5XN,6DiC8XI,0CACE,sBjCrXN,sCiCuXI,iCACE,sBA5BJ,0BACE,oBjC3YN,qCiC6YM,iCACE,qBjC1YR,2CiC4YM,iCACE,qBjCzYR,4DiC2YM,sCACE,qBjCxYR,sCiC0YM,gCACE,qBjCvYR,sCiCyYM,kCACE,qBjCrYN,6DiCuYI,uCACE,qBjC9XN,sCiCgYI,qCACE,qBjC5XN,6DiC8XI,0CACE,qBjCrXN,sCiCuXI,iCACE,qBA5BJ,0BACE,qBjC3YN,qCiC6YM,iCACE,sBjC1YR,2CiC4YM,iCACE,sBjCzYR,4DiC2YM,sCACE,sBjCxYR,sCiC0YM,gCACE,sBjCvYR,sCiCyYM,kCACE,sBjCrYN,6DiCuYI,uCACE,sBjC9XN,sCiCgYI,qCACE,sBjC5XN,6DiC8XI,0CACE,sBjCrXN,sCiCuXI,iCACE,sBA5BJ,0BACE,kBjC3YN,qCiC6YM,iCACE,mBjC1YR,2CiC4YM,iCACE,mBjCzYR,4DiC2YM,sCACE,mBjCxYR,sCiC0YM,gCACE,mBjCvYR,sCiCyYM,kCACE,mBjCrYN,6DiCuYI,uCACE,mBjC9XN,sCiCgYI,qCACE,mBjC5XN,6DiC8XI,0CACE,mBjCrXN,sCiCuXI,iCACE,mBCvfV,MACE,oBACA,cACA,aACA,YACA,cACA,uBAEA,kBACE,qBACA,sBACA,oBACA,6BACE,uBACF,mCACE,qBACJ,eACE,oBACF,gBACE,eACF,kBACE,sBACA,kDACE,gClC8DJ,2CkC3DE,qBACE,aAEA,WACE,UACA,oBAFF,WACE,UACA,qBAFF,WACE,UACA,UAFF,WACE,UACA,qBAFF,WACE,UACA,qBAFF,WACE,UACA,UAFF,WACE,UACA,qBAFF,WACE,UACA,qBAFF,WACE,UACA,UAFF,YACE,UACA,qBAFF,YACE,UACA,qBAFF,YACE,UACA,YC7BR,MACE,oBACA,aACA,sBACA,8BACA,cACE,gBAEA,eACE,mBAKF,eACE,iBAHM,KAIN,MAHa,QAIb,kFAEE,cACF,sBACE,MARW,QASb,yBACE,wBACA,wEAEE,MAbS,QnC8EjB,sCmChEI,4BAEI,iBAjBE,MAkBN,wDAEE,wBAGA,kJAEE,yBACA,MAzBS,QA2BX,uBACE,MA5BS,QA6BT,WACA,6BACE,UAEF,oCACE,UAGF,iEACE,MAtCO,QAuCP,6EACE,mCAEF,kMAEE,iBA5CK,QA6CL,aA7CK,QA8CL,MA/CF,KAiDN,uBAGE,4EnCeN,qCmCbQ,oCACE,6EArDR,eACE,iBAHM,QAIN,MAHa,KAIb,kFAEE,cACF,sBACE,MARW,KASb,yBACE,2BACA,wEAEE,MAbS,KnC8EjB,sCmChEI,4BAEI,iBAjBE,SAkBN,wDAEE,2BAGA,kJAEE,sBACA,MAzBS,KA2BX,uBACE,MA5BS,KA6BT,WACA,6BACE,UAEF,oCACE,UAGF,iEACE,MAtCO,KAuCP,6EACE,mCAEF,kMAEE,iBA5CK,KA6CL,aA7CK,KA8CL,MA/CF,QAiDN,uBAGE,8EnCeN,qCmCbQ,oCACE,+EArDR,eACE,iBAHM,QAIN,MAHa,QAIb,kFAEE,cACF,sBACE,MARW,QASb,yBACE,wBACA,wEAEE,MAbS,QnC8EjB,sCmChEI,4BAEI,iBAjBE,SAkBN,wDAEE,wBAGA,kJAEE,yBACA,MAzBS,QA2BX,uBACE,MA5BS,QA6BT,WACA,6BACE,UAEF,oCACE,UAGF,iEACE,MAtCO,QAuCP,6EACE,mCAEF,kMAEE,iBA5CK,QA6CL,aA7CK,QA8CL,MA/CF,QAiDN,uBAGE,iFnCeN,qCmCbQ,oCACE,kFArDR,cACE,iBAHM,QAIN,MAHa,QAIb,gFAEE,cACF,qBACE,MARW,QASb,wBACE,2BACA,sEAEE,MAbS,QnC8EjB,sCmChEI,2BAEI,iBAjBE,SAkBN,sDAEE,2BAGA,8IAEE,yBACA,MAzBS,QA2BX,sBACE,MA5BS,QA6BT,WACA,4BACE,UAEF,mCACE,UAGF,+DACE,MAtCO,QAuCP,2EACE,mCAEF,8LAEE,iBA5CK,QA6CL,aA7CK,QA8CL,MA/CF,QAiDN,sBAGE,gFnCeN,qCmCbQ,mCACE,iFArDR,iBACE,iBAHM,QAIN,MAHa,KAIb,sFAEE,cACF,wBACE,MARW,KASb,2BACE,2BACA,4EAEE,MAbS,KnC8EjB,sCmChEI,8BAEI,iBAjBE,SAkBN,4DAEE,2BAGA,0JAEE,yBACA,MAzBS,KA2BX,yBACE,MA5BS,KA6BT,WACA,+BACE,UAEF,sCACE,UAGF,qEACE,MAtCO,KAuCP,iFACE,mCAEF,0MAEE,iBA5CK,KA6CL,aA7CK,KA8CL,MA/CF,QAiDN,yBAGE,gFnCeN,qCmCbQ,sCACE,iFArDR,cACE,iBAHM,QAIN,MAHa,KAIb,gFAEE,cACF,qBACE,MARW,KASb,wBACE,2BACA,sEAEE,MAbS,KnC8EjB,sCmChEI,2BAEI,iBAjBE,SAkBN,sDAEE,2BAGA,8IAEE,yBACA,MAzBS,KA2BX,sBACE,MA5BS,KA6BT,WACA,4BACE,UAEF,mCACE,UAGF,+DACE,MAtCO,KAuCP,2EACE,mCAEF,8LAEE,iBA5CK,KA6CL,aA7CK,KA8CL,MA/CF,QAiDN,sBAGE,gFnCeN,qCmCbQ,mCACE,iFArDR,cACE,iBAHM,QAIN,MAHa,KAIb,gFAEE,cACF,qBACE,MARW,KASb,wBACE,2BACA,sEAEE,MAbS,KnC8EjB,sCmChEI,2BAEI,iBAjBE,SAkBN,sDAEE,2BAGA,8IAEE,yBACA,MAzBS,KA2BX,sBACE,MA5BS,KA6BT,WACA,4BACE,UAEF,mCACE,UAGF,+DACE,MAtCO,KAuCP,2EACE,mCAEF,8LAEE,iBA5CK,KA6CL,aA7CK,KA8CL,MA/CF,QAiDN,sBAGE,gFnCeN,qCmCbQ,mCACE,iFArDR,iBACE,iBAHM,QAIN,MAHa,KAIb,sFAEE,cACF,wBACE,MARW,KASb,2BACE,2BACA,4EAEE,MAbS,KnC8EjB,sCmChEI,8BAEI,iBAjBE,SAkBN,4DAEE,2BAGA,0JAEE,yBACA,MAzBS,KA2BX,yBACE,MA5BS,KA6BT,WACA,+BACE,UAEF,sCACE,UAGF,qEACE,MAtCO,KAuCP,iFACE,mCAEF,0MAEE,iBA5CK,KA6CL,aA7CK,KA8CL,MA/CF,QAiDN,yBAGE,gFnCeN,qCmCbQ,sCACE,iFArDR,iBACE,iBAHM,QAIN,MAHa,eAIb,sFAEE,cACF,wBACE,MARW,eASb,2BACE,qBACA,4EAEE,MAbS,enC8EjB,sCmChEI,8BAEI,iBAjBE,SAkBN,4DAEE,qBAGA,0JAEE,yBACA,MAzBS,eA2BX,yBACE,MA5BS,eA6BT,WACA,+BACE,UAEF,sCACE,UAGF,qEACE,MAtCO,eAuCP,iFACE,mCAEF,0MAEE,iBA5CK,eA6CL,aA7CK,eA8CL,MA/CF,QAiDN,yBAGE,gFnCeN,qCmCbQ,sCACE,iFArDR,gBACE,iBAHM,QAIN,MAHa,KAIb,oFAEE,cACF,uBACE,MARW,KASb,0BACE,2BACA,0EAEE,MAbS,KnC8EjB,sCmChEI,6BAEI,iBAjBE,SAkBN,0DAEE,2BAGA,sJAEE,yBACA,MAzBS,KA2BX,wBACE,MA5BS,KA6BT,WACA,8BACE,UAEF,qCACE,UAGF,mEACE,MAtCO,KAuCP,+EACE,mCAEF,sMAEE,iBA5CK,KA6CL,aA7CK,KA8CL,MA/CF,QAiDN,wBAGE,gFnCeN,qCmCbQ,qCACE,iFAgBR,0BACE,sBACA,mBnCFJ,2CmCKI,2BACE,oBACA,kBnCPN,2CmCUI,0BACE,qBACA,mBAIJ,yGACE,mBACA,aACA,0IACE,YACA,cACN,oBACE,gBACF,oBACE,iBAIJ,YAEE,gBACA,kBACE,SACA,gBACA,eACA,kBACA,QACA,qCAEF,2BACE,WnC7CF,qCmCiCF,YAeI,cAEJ,cACE,kBnCnDA,qCmCsDE,sBACE,aACA,uCACE,sBnCrDN,2CmC8CF,cASI,aACA,uBACA,uCACE,qBAIN,sBAEE,YACA,cAEF,WACE,YACA,cACA,oBCvJF,SACE,QALgB,YpCiGhB,sCoCxFE,mBACE,QATmB,YAUrB,kBACE,QAVkB,cCCxB,QACE,iBpCSa,QoCRb,QAJe,iBxCgBjB,kCACE,iBACA,WACA,OOnBU,KPoBV","file":"styles.css"} -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solokeys/solo-webupdate/038ff239c47e9f14c667afab589e8de72da03a24/data/.gitkeep -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solokeys/solo-webupdate/038ff239c47e9f14c667afab589e8de72da03a24/favicon.ico -------------------------------------------------------------------------------- /images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solokeys/solo-webupdate/038ff239c47e9f14c667afab589e8de72da03a24/images/github.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Solo Webupdate 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |

16 | Solo Webupdate 17 |

18 |

19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 30 | 31 | Latest Solo firmware version: 32 | 33 | unknown 34 |

35 | 46 | 47 | 48 | 49 | 61 | 62 | 75 |
76 |
77 | 78 | 79 | 80 | 81 |
82 | 83 | 147 | 148 | 159 | 160 |
161 | 162 |
163 |
164 |
165 |
166 |

This updater is deprecated. But you can still try to use it.

167 |
168 |
169 |

170 | Due to FIDO stacks on various browsers and operating systems not being 171 | able to provide a good UX for this process (or being possible at all), 172 | this updater is deprecated. Be on lookout for 173 | Solo Desktop, coming 174 | very soon. 175 |

176 |
177 |

178 | You can also update using our command line tool, which is provided as a python package. 179 |

180 |
181 |

182 | It was fun while it lasted. 183 |

184 |
185 |
186 |
187 |
188 | 189 |
190 | 193 |
194 |
195 |
196 | 201 |
202 |
203 |
204 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /js/constants.js: -------------------------------------------------------------------------------- 1 | const known_certs_hashed = { 2 | "Solo Secure": "8e06b4060fc58677055285ce3ee6a69a0666b59f4c2a0a00a025c7f0f3ce9a50", 3 | "Solo Hacker": "2a0a22ceaedac89b3d02e2b53cbfaa763c6efa8a73f03976ec72fe4c5d9a1ff3", 4 | "U2F Zero": "ded15e86dae60b48f7507bc81d7471b61102c2eb844bd954b653164d8c0cb677", 5 | }; 6 | 7 | // invert known_certs_hashed 8 | const known_certs_lookup = Object.assign( 9 | {}, 10 | ...Object.entries(known_certs_hashed).map( 11 | ([a, b]) => ({[b]: a}) 12 | ) 13 | ); 14 | 15 | const CMD = { 16 | solo_sign: 0x10, 17 | solo_register: 0x11, 18 | solo_pin: 0x12, 19 | solo_reset: 0x13, 20 | solo_version: 0x14, 21 | solo_rng: 0x15, 22 | solo_pubkey: 0x16, 23 | 24 | boot_write: 0x40, // 64 25 | boot_done: 0x41, // 65 26 | boot_check: 0x42, // 66 27 | boot_erase: 0x43, // 67 28 | boot_version: 0x44, // 68 29 | }; 30 | 31 | const command_codes = { 32 | 0x14: "SOLO VERSION", 33 | 0x15: "SOLO RNG", 34 | 35 | 0x40: "BOOT WRITE", 36 | 0x41: "BOOT DONE", 37 | 0x42: "BOOT CHECK", 38 | 0x43: "BOOT ERASE", 39 | 0x44: "BOOT VERSION", 40 | }; 41 | 42 | const ctap_error_codes = { 43 | 0x00: 'CTAP1_SUCCESS', 44 | 0x01: 'CTAP1_ERR_INVALID_COMMAND', 45 | 0x02: 'CTAP1_ERR_INVALID_PARAMETER', 46 | 0x03: 'CTAP1_ERR_INVALID_LENGTH', 47 | 0x04: 'CTAP1_ERR_INVALID_SEQ', 48 | 0x05: 'CTAP1_ERR_TIMEOUT', 49 | 0x06: 'CTAP1_ERR_CHANNEL_BUSY', 50 | 0x0A: 'CTAP1_ERR_LOCK_REQUIRED', 51 | 0x0B: 'CTAP1_ERR_INVALID_CHANNEL', 52 | 53 | 0x10: 'CTAP2_ERR_CBOR_PARSING', 54 | 0x11: 'CTAP2_ERR_CBOR_UNEXPECTED_TYPE', 55 | 0x12: 'CTAP2_ERR_INVALID_CBOR', 56 | 0x13: 'CTAP2_ERR_INVALID_CBOR_TYPE', 57 | 0x14: 'CTAP2_ERR_MISSING_PARAMETER', 58 | 0x15: 'CTAP2_ERR_LIMIT_EXCEEDED', 59 | 0x16: 'CTAP2_ERR_UNSUPPORTED_EXTENSION', 60 | 0x17: 'CTAP2_ERR_TOO_MANY_ELEMENTS', 61 | 0x18: 'CTAP2_ERR_EXTENSION_NOT_SUPPORTED', 62 | 0x19: 'CTAP2_ERR_CREDENTIAL_EXCLUDED', 63 | 0x20: 'CTAP2_ERR_CREDENTIAL_NOT_VALID', 64 | 0x21: 'CTAP2_ERR_PROCESSING', 65 | 0x22: 'CTAP2_ERR_INVALID_CREDENTIAL', 66 | 0x23: 'CTAP2_ERR_USER_ACTION_PENDING', 67 | 0x24: 'CTAP2_ERR_OPERATION_PENDING', 68 | 0x25: 'CTAP2_ERR_NO_OPERATIONS', 69 | 0x26: 'CTAP2_ERR_UNSUPPORTED_ALGORITHM', 70 | 0x27: 'CTAP2_ERR_OPERATION_DENIED', 71 | 0x28: 'CTAP2_ERR_KEY_STORE_FULL', 72 | 0x29: 'CTAP2_ERR_NOT_BUSY', 73 | 0x2A: 'CTAP2_ERR_NO_OPERATION_PENDING', 74 | 0x2B: 'CTAP2_ERR_UNSUPPORTED_OPTION', 75 | 0x2C: 'CTAP2_ERR_INVALID_OPTION', 76 | 0x2D: 'CTAP2_ERR_KEEPALIVE_CANCEL', 77 | 0x2E: 'CTAP2_ERR_NO_CREDENTIALS', 78 | 0x2F: 'CTAP2_ERR_USER_ACTION_TIMEOUT', 79 | 0x30: 'CTAP2_ERR_NOT_ALLOWED', 80 | 0x31: 'CTAP2_ERR_PIN_INVALID', 81 | 0x32: 'CTAP2_ERR_PIN_BLOCKED', 82 | 0x33: 'CTAP2_ERR_PIN_AUTH_INVALID', 83 | 0x34: 'CTAP2_ERR_PIN_AUTH_BLOCKED', 84 | 0x35: 'CTAP2_ERR_PIN_NOT_SET', 85 | 0x36: 'CTAP2_ERR_PIN_REQUIRED', 86 | 0x37: 'CTAP2_ERR_PIN_POLICY_VIOLATION', 87 | 0x38: 'CTAP2_ERR_PIN_TOKEN_EXPIRED', 88 | 0x39: 'CTAP2_ERR_REQUEST_TOO_LARGE', 89 | } 90 | 91 | -------------------------------------------------------------------------------- /js/ctaphid.js: -------------------------------------------------------------------------------- 1 | async function ctaphid_via_webauthn(cmd, addr, data, timeout) { 2 | // if a token does not support CTAP2, WebAuthn re-encodes as CTAP1/U2F: 3 | // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#interoperating-with-ctap1-u2f-authenticators 4 | // 5 | // the bootloader only supports CTAP1, so the idea is to drop 6 | // u2f-api.js and the Firefox about:config fiddling 7 | // 8 | // problem: the popup to press button flashes up briefly :( 9 | // 10 | 11 | var keyhandle = encode_ctaphid_request_as_keyhandle(cmd, addr, data); 12 | var challenge = window.crypto.getRandomValues(new Uint8Array(32)); 13 | 14 | var request_options = { 15 | challenge: challenge, 16 | allowCredentials: [{ 17 | id: keyhandle, 18 | type: 'public-key', 19 | }], 20 | timeout: timeout, 21 | userVerification: 'discouraged', 22 | } 23 | 24 | return navigator.credentials.get({ 25 | publicKey: request_options 26 | }).then(assertion => { 27 | console.log("GOT ASSERTION", assertion); 28 | console.log("RESPONSE", assertion.response); 29 | let response = decode_ctaphid_response_from_signature(assertion.response); 30 | console.log("RESPONSE:", response); 31 | return response.data; 32 | }).catch(error => { 33 | console.log("ERROR CALLING:", cmd, addr, data); 34 | console.log("THE ERROR:", error); 35 | return Promise.resolve(); // error; 36 | }); 37 | } 38 | 39 | 40 | // The idea is to encode CTAPHID_VENDOR commands 41 | // in the keyhandle, that is sent via WebAuthn or U2F 42 | // as signature request to the authenticator. 43 | // 44 | // The authenticator reacts to signature requests with 45 | // the four "magic" bytes set with a special signature, 46 | // which can then be decoded 47 | 48 | function encode_ctaphid_request_as_keyhandle(cmd, addr, data) { 49 | console.log('REQUEST CMD', cmd, '(', command_codes[cmd], ')'); 50 | console.log('REQUEST ADDR', addr); 51 | console.log('REQUEST DATA', data); 52 | 53 | // should we check that `data` is either null or an Uint8Array? 54 | data = data || new Uint8Array(); 55 | 56 | const offset = 10; 57 | 58 | if (offset + data.length > 255) { 59 | throw new Error("Max size exceeded"); 60 | } 61 | 62 | // on Solo side, `is_extension_request` expects at least 16 bytes of data 63 | const data_pad = data.length < 16 ? 16 - data.length : 0; 64 | var array = new Uint8Array(offset + data.length + data_pad); 65 | 66 | array[0] = cmd & 0xff; 67 | 68 | array[1] = (addr >> 0) & 0xff; 69 | array[2] = (addr >> 8) & 0xff; 70 | array[3] = (addr >> 16) & 0xff; 71 | 72 | // magic values, telling bootloader U2F interface 73 | // to interpret `data` as encoded U2F APDU command, 74 | // when passed as keyhandle in u2f.sign. 75 | // yes, there can theoretically be clashes :) 76 | array[4] = 0x8C; // 140 77 | array[5] = 0x27; // 39 78 | array[6] = 0x90; // 144 79 | array[7] = 0xf6; // 246 80 | 81 | array[8] = 0; 82 | array[9] = data.length & 0xff; 83 | 84 | array.set(data, offset); 85 | 86 | console.log('FORMATTED REQUEST:', array); 87 | return array; 88 | } 89 | 90 | function decode_ctaphid_response_from_signature(response) { 91 | // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#using-the-ctap2-authenticatorgetassertion-command-with-ctap1-u2f-authenticators 92 | // 93 | // compared to `parse_device_response`, the data is encoded a little differently here 94 | // 95 | // attestation.response.authenticatorData 96 | // 97 | // first 32 bytes: SHA-256 hash of the rp.id 98 | // 1 byte: zeroth bit = user presence set in U2F response (always 1) 99 | // last 4 bytes: signature counter (32 bit big-endian) 100 | // 101 | // attestation.response.signature 102 | // signature data (bytes 5-end of U2F response 103 | 104 | signature_count = ( 105 | new DataView( 106 | response.authenticatorData.slice(33, 37) 107 | ) 108 | ).getUint32(0, false); // get count as 32 bit BE integer 109 | 110 | signature = new Uint8Array(response.signature); 111 | data = null; 112 | error_code = signature[0]; 113 | if (error_code == 0) { 114 | data = signature.slice(1, signature.length); 115 | 116 | } 117 | return { 118 | count: signature_count, 119 | status: ctap_error_codes[error_code], 120 | data: data, 121 | signature: signature, 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /js/helpers.js: -------------------------------------------------------------------------------- 1 | async function get_url_json(file_url) { 2 | let response = await fetch(file_url); 3 | let data = await response.blob(); 4 | let metadata = { 5 | type: 'application/json' 6 | }; 7 | let file = new File([data], "solo.json", metadata); 8 | return file; 9 | } 10 | 11 | function TEST(bool, test){ 12 | if (bool) { 13 | if (test ) console.log("PASS: " + test); 14 | } 15 | else { 16 | console.log("FAIL: " + test); 17 | throw new Error("FAIL: " + test); 18 | } 19 | } 20 | 21 | function hex2array(string) 22 | { 23 | if (string.slice(0,2) == '0x') 24 | { 25 | string = string.slice(2,string.length); 26 | } 27 | if (string.length & 1) 28 | { 29 | throw new Error('Odd length hex string'); 30 | } 31 | let arr = new Uint8Array(string.length/2); 32 | var i; 33 | for (i = 0; i < string.length; i+=2) 34 | { 35 | arr[i/2] = parseInt(string.slice(i,i+2),16); 36 | } 37 | return arr; 38 | } 39 | 40 | function array2hex(buffer) { 41 | return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); 42 | } 43 | 44 | function websafe64(base64) { 45 | return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); 46 | } 47 | 48 | function array2websafe(array) { 49 | var result = ""; 50 | for(var i = 0; i < array.length; ++i){ 51 | result+= (String.fromCharCode(array[i])); 52 | } 53 | return websafe64(window.btoa(result)); 54 | } 55 | 56 | function normal64(base64) { 57 | return base64.replace(/\-/g, '+').replace(/_/g, '/') + '=='.substring(0, (3*base64.length)%4); 58 | } 59 | 60 | function websafe2array(base64) { 61 | var binary_string = window.atob(normal64(base64)); 62 | var len = binary_string.length; 63 | var bytes = new Uint8Array(len); 64 | for (var i = 0; i < len; i++) { 65 | bytes[i] = binary_string.charCodeAt(i); 66 | } 67 | return bytes; 68 | } 69 | 70 | function error2string(err) 71 | { 72 | return lookup_table[err] 73 | } 74 | 75 | function websafe2string(string) { 76 | return window.atob(normal64(string)); 77 | } 78 | 79 | function wrap_promise(func) { 80 | var self = this; 81 | return function() { 82 | var args = arguments; 83 | return new Promise(function(resolve, reject) { 84 | var i; 85 | var oldfunc = null; 86 | for (i = 0; i < args.length; i++) { 87 | if (typeof args[i] == 'function') { 88 | oldfunc = args[i]; 89 | args[i] = function() { 90 | oldfunc.apply(self, arguments); 91 | resolve.apply(self, arguments); 92 | }; 93 | break; 94 | } 95 | } 96 | if (oldfunc === null) { 97 | args = Array.prototype.slice.call(args); 98 | args.push(function() { 99 | resolve.apply(self, arguments); 100 | }) 101 | func.apply(self, args); 102 | } 103 | }); 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | var app = new Vue({ 2 | el: '#app', 3 | data: { 4 | platform_description: '', 5 | webauthn_support: '', 6 | solo_version_parts: null, 7 | solo_version: null, 8 | stable_version_parts: null, 9 | stable_version: null, 10 | is_solo_secure: null, 11 | is_solo_hacker: null, 12 | needs_update: false, 13 | ask_for_attestation: null, 14 | correct_firmware: true, 15 | signed_firmware: null, 16 | update_status: null, 17 | update_progress: null, 18 | advanced_mode: false, 19 | cannot_inspect: null, 20 | cannot_flash: null, 21 | update_success: null, 22 | about_to_flash: null, 23 | p_progress: null, 24 | is_linux: null, 25 | } 26 | }); 27 | 28 | async function reset_messages() { 29 | app.cannot_inspect = null; 30 | app.cannot_flash = null; 31 | app.update_success = null; 32 | app.update_progress = null; 33 | app.ask_for_attestation = null; 34 | } 35 | 36 | async function toggle_advanced_mode() { 37 | app.advanced_mode = !app.advanced_mode; 38 | } 39 | 40 | async function inspect_browser() { 41 | app.platform_description = platform.description; 42 | if (!window.PublicKeyCredential) { 43 | app.webauthn_support = "Your browser does not support WebAuthn, please use another one"; 44 | } else { 45 | app.webauthn_support = "Your browser supports WebAuthn"; 46 | } 47 | if (platform.os["family"] == "Linux") { 48 | app.is_linux = true; 49 | } 50 | } 51 | 52 | async function check_version(){ 53 | await ctaphid_via_webauthn( 54 | // option a) timeout --> leads to ugly persistent popup in chrome (firefox is better) 55 | CMD.solo_version, null, null, 1000 56 | // option b) no timeout --> user needs to click cancel 57 | // CMD.solo_version, 58 | ).then(response => { 59 | console.log("check-version RESPONSE", response); 60 | if (typeof response !== "undefined") { 61 | app.solo_version_parts = response.slice(0, 3); 62 | let solo_version = response[0] + '.' + response[1] + '.' + response[2]; 63 | app.solo_version = solo_version; 64 | } else { 65 | // we assume this is a pre-1.1.0 solo 66 | app.solo_version_parts = new Uint8Array([0, 0, 0]); 67 | app.solo_version = "unknown"; 68 | } 69 | } 70 | ) 71 | .catch(error => { 72 | console.log(error); 73 | }); 74 | } 75 | 76 | async function fetch_stable_version() { 77 | var response = await fetch( 78 | "https://raw.githubusercontent.com/solokeys/solo/master/STABLE_VERSION", 79 | {cache: "no-store"} 80 | ); 81 | let stable_version_github = (await response.text()).trim(); 82 | console.log("STABLE_VERSION GITHUB", stable_version_github); 83 | 84 | var response = await fetch( 85 | "data/STABLE_VERSION", 86 | {cache: "no-store"} 87 | ); 88 | let stable_version_fetched = (await response.text()).trim(); 89 | console.log("STABLE_VERSION FETCHED", stable_version_fetched); 90 | 91 | if (stable_version_github != stable_version_fetched) { 92 | app.stable_version = "fetched firmware out of date"; 93 | app.correct_firmware = false; 94 | } else { 95 | app.stable_version = stable_version_fetched; 96 | app.stable_version_parts = new Uint8Array(stable_version_fetched.split(".").map(Number)); 97 | app.correct_firmware = true; 98 | } 99 | } 100 | 101 | async function prepare() { 102 | await inspect_browser(); 103 | await fetch_stable_version(); 104 | // await check_version(); 105 | } 106 | 107 | async function create_direct_attestation(timeout) { 108 | // random nonce 109 | var challenge = new Uint8Array(32); 110 | window.crypto.getRandomValues(challenge); 111 | 112 | // our relying party 113 | let rp_id = window.location.hostname; 114 | 115 | // GOAL: register a key signed by key's attestation certificate 116 | let publicKeyCredentialCreationOptions = { 117 | rp: { 118 | name: 'SoloKeys Service Station', 119 | id: rp_id, 120 | }, 121 | authenticatorSelection: { 122 | userVerification: 'discouraged', 123 | }, 124 | attestation: 'direct', 125 | 126 | challenge, 127 | 128 | pubKeyCredParams: [ 129 | { type: 'public-key', alg: -7 }, 130 | ], 131 | 132 | user: { 133 | id: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]), 134 | name: "little-solo-keys@solokeys.com", 135 | displayName: "Lil' Solo Keys", 136 | }, 137 | 138 | timeout: timeout, 139 | excludeCredentials: [], 140 | }; 141 | 142 | return navigator.credentials.create({ 143 | publicKey: publicKeyCredentialCreationOptions 144 | }); 145 | }; 146 | 147 | async function inspect() { 148 | await reset_messages(); 149 | app.is_solo_secure = null; 150 | app.is_solo_hacker = null; 151 | console.log("app.solo_version", app.solo_version); 152 | if (app.solo_version != "unknown" && !(app.solo_version == null)) { 153 | console.log("PRE-CHECKING IF IN BOOTLOADER"); 154 | if (await is_bootloader()) { 155 | app.cannot_inspect = true; 156 | return; 157 | } 158 | } 159 | console.log("ASKING FOR ATTESTATION"); 160 | app.ask_for_attestation = true; 161 | let credential = await create_direct_attestation(); 162 | app.ask_for_attestation = null; 163 | 164 | let utf8_decoder = new TextDecoder('utf-8'); 165 | let client_data_json = utf8_decoder.decode(credential.response.clientDataJSON); 166 | let client_data = JSON.parse(client_data_json); 167 | var attestation = CBOR.decode(credential.response.attestationObject); 168 | var certificate = attestation.attStmt.x5c[0]; 169 | let certificate_fingerprint = sha256(certificate); 170 | let what_is_it = known_certs_lookup[certificate_fingerprint]; 171 | 172 | if (typeof what_is_it === "undefined") { 173 | console.log("UNKNOWN ATTESTATION CERTIFIATE"); 174 | } else { 175 | if (what_is_it == "Solo Secure") { 176 | app.is_solo_secure = true; 177 | app.is_solo_hacker = false; 178 | } 179 | if (what_is_it == "Solo Hacker") { 180 | app.is_solo_secure = false; 181 | app.is_solo_hacker = true; 182 | } 183 | }; 184 | 185 | // now we know a key is plugged in 186 | if (app.solo_version != "1.0.0") { 187 | await check_version(); 188 | } 189 | 190 | let need = app.stable_version_parts; 191 | let have = app.solo_version_parts; 192 | console.log("NEED", need); 193 | console.log("HAVE", have); 194 | 195 | if (have == null) { 196 | app.needs_update = true; 197 | } else { 198 | app.needs_update = (need[0] > have[0]) || (need[1] > have[1]) || (need[2] > have[2]); 199 | } 200 | } 201 | 202 | async function fetch_firmware() { 203 | // TODO: cache downloads 204 | url_base = "data/"; 205 | let file_url = url_base + "firmware-" + app.stable_version + ".json"; 206 | console.log(file_url); 207 | 208 | let fetched = await fetch(file_url); 209 | let content = await fetched.json(); 210 | 211 | let firmware = websafe2string(content.firmware); 212 | var signature = websafe2array(content.signature); 213 | var sigs = [] 214 | 215 | for (var v in content.versions){ 216 | sigs.push(websafe2array(content.versions[v].signature)) 217 | } 218 | 219 | return { 220 | firmware: firmware, 221 | signature: signature, 222 | sigs: sigs 223 | } 224 | 225 | } 226 | 227 | async function is_bootloader() { 228 | let response = await ctaphid_via_webauthn(CMD.boot_check, null, null, 1000); 229 | // console.log(response); 230 | let _is_bootloader = !(response == null); 231 | // console.log(is_bootloader); 232 | return _is_bootloader; 233 | } 234 | 235 | 236 | async function update_firmware() { 237 | app.is_solo_hacker = false; 238 | app.is_solo_secure = true; 239 | await reset_messages(); 240 | await toggle_advanced_mode(); 241 | await update(); 242 | } 243 | 244 | async function update() { 245 | await reset_messages(); 246 | if (!await is_bootloader()) { 247 | app.cannot_flash = true; 248 | return 249 | } 250 | app.update_status = "DOWNLOADING FIRMWARE"; 251 | let signed_firmware = await fetch_firmware(); 252 | app.signed_firmware = signed_firmware; 253 | 254 | let firmware = signed_firmware.firmware; 255 | let signature = signed_firmware.signature; 256 | let sigs = signed_firmware.sigs; 257 | 258 | let num_pages = 64; 259 | 260 | let blocks = MemoryMap.fromHex(firmware); 261 | let addresses = blocks.keys(); 262 | 263 | let addr = addresses.next(); 264 | let chunk_size = 240; 265 | console.log("WRITING..."); 266 | app.update_status = "FLASHING FIRMWARE"; 267 | 268 | while(!addr.done) { 269 | var data = blocks.get(addr.value); 270 | var i; 271 | for (i = 0; i < data.length; i += chunk_size) { 272 | var chunk = data.slice(i,i+chunk_size); 273 | 274 | p = await ctaphid_via_webauthn( 275 | CMD.boot_write, 276 | addr.value + i, 277 | chunk 278 | ); 279 | TEST(p.status != 'CTAP1_SUCCESS', 'Device wrote data'); 280 | 281 | var progress = (((i/data.length) * 100 * 100) | 0)/100; 282 | console.log("PROGRESS:", progress); 283 | app.p_progress = Math.round(progress); 284 | app.update_progress = "Progress: " + progress + "%"; 285 | } 286 | 287 | addr = addresses.next(); 288 | } 289 | app.update_progress = "Progress: 100%"; 290 | app.p_progress = null; 291 | console.log("...DONE"); 292 | 293 | app.update_status = "VERIFYING FIRMWARE SIGNATURE"; 294 | for (s in sigs){ 295 | p = await ctaphid_via_webauthn( 296 | CMD.boot_done, 0x8000, sigs[s] 297 | ); 298 | console.log('Accepted?', p); 299 | } 300 | app.update_status = null; 301 | app.update_success = true; 302 | 303 | app.signed_firmware = null; 304 | // await check_version(); 305 | } 306 | -------------------------------------------------------------------------------- /js/vendor/cbor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2016 Patrick Gansterer 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | (function(global, undefined) { "use strict"; 26 | var POW_2_24 = 5.960464477539063e-8, 27 | POW_2_32 = 4294967296, 28 | POW_2_53 = 9007199254740992; 29 | 30 | function encode(value) { 31 | var data = new ArrayBuffer(256); 32 | var dataView = new DataView(data); 33 | var lastLength; 34 | var offset = 0; 35 | 36 | function prepareWrite(length) { 37 | var newByteLength = data.byteLength; 38 | var requiredLength = offset + length; 39 | while (newByteLength < requiredLength) 40 | newByteLength <<= 1; 41 | if (newByteLength !== data.byteLength) { 42 | var oldDataView = dataView; 43 | data = new ArrayBuffer(newByteLength); 44 | dataView = new DataView(data); 45 | var uint32count = (offset + 3) >> 2; 46 | for (var i = 0; i < uint32count; ++i) 47 | dataView.setUint32(i << 2, oldDataView.getUint32(i << 2)); 48 | } 49 | 50 | lastLength = length; 51 | return dataView; 52 | } 53 | function commitWrite() { 54 | offset += lastLength; 55 | } 56 | function writeFloat64(value) { 57 | commitWrite(prepareWrite(8).setFloat64(offset, value)); 58 | } 59 | function writeUint8(value) { 60 | commitWrite(prepareWrite(1).setUint8(offset, value)); 61 | } 62 | function writeUint8Array(value) { 63 | var dataView = prepareWrite(value.length); 64 | for (var i = 0; i < value.length; ++i) 65 | dataView.setUint8(offset + i, value[i]); 66 | commitWrite(); 67 | } 68 | function writeUint16(value) { 69 | commitWrite(prepareWrite(2).setUint16(offset, value)); 70 | } 71 | function writeUint32(value) { 72 | commitWrite(prepareWrite(4).setUint32(offset, value)); 73 | } 74 | function writeUint64(value) { 75 | var low = value % POW_2_32; 76 | var high = (value - low) / POW_2_32; 77 | var dataView = prepareWrite(8); 78 | dataView.setUint32(offset, high); 79 | dataView.setUint32(offset + 4, low); 80 | commitWrite(); 81 | } 82 | function writeTypeAndLength(type, length) { 83 | if (length < 24) { 84 | writeUint8(type << 5 | length); 85 | } else if (length < 0x100) { 86 | writeUint8(type << 5 | 24); 87 | writeUint8(length); 88 | } else if (length < 0x10000) { 89 | writeUint8(type << 5 | 25); 90 | writeUint16(length); 91 | } else if (length < 0x100000000) { 92 | writeUint8(type << 5 | 26); 93 | writeUint32(length); 94 | } else { 95 | writeUint8(type << 5 | 27); 96 | writeUint64(length); 97 | } 98 | } 99 | 100 | function encodeItem(value) { 101 | var i; 102 | 103 | if (value === false) 104 | return writeUint8(0xf4); 105 | if (value === true) 106 | return writeUint8(0xf5); 107 | if (value === null) 108 | return writeUint8(0xf6); 109 | if (value === undefined) 110 | return writeUint8(0xf7); 111 | 112 | switch (typeof value) { 113 | case "number": 114 | if (Math.floor(value) === value) { 115 | if (0 <= value && value <= POW_2_53) 116 | return writeTypeAndLength(0, value); 117 | if (-POW_2_53 <= value && value < 0) 118 | return writeTypeAndLength(1, -(value + 1)); 119 | } 120 | writeUint8(0xfb); 121 | return writeFloat64(value); 122 | 123 | case "string": 124 | var utf8data = []; 125 | for (i = 0; i < value.length; ++i) { 126 | var charCode = value.charCodeAt(i); 127 | if (charCode < 0x80) { 128 | utf8data.push(charCode); 129 | } else if (charCode < 0x800) { 130 | utf8data.push(0xc0 | charCode >> 6); 131 | utf8data.push(0x80 | charCode & 0x3f); 132 | } else if (charCode < 0xd800) { 133 | utf8data.push(0xe0 | charCode >> 12); 134 | utf8data.push(0x80 | (charCode >> 6) & 0x3f); 135 | utf8data.push(0x80 | charCode & 0x3f); 136 | } else { 137 | charCode = (charCode & 0x3ff) << 10; 138 | charCode |= value.charCodeAt(++i) & 0x3ff; 139 | charCode += 0x10000; 140 | 141 | utf8data.push(0xf0 | charCode >> 18); 142 | utf8data.push(0x80 | (charCode >> 12) & 0x3f); 143 | utf8data.push(0x80 | (charCode >> 6) & 0x3f); 144 | utf8data.push(0x80 | charCode & 0x3f); 145 | } 146 | } 147 | 148 | writeTypeAndLength(3, utf8data.length); 149 | return writeUint8Array(utf8data); 150 | 151 | default: 152 | var length; 153 | if (Array.isArray(value)) { 154 | length = value.length; 155 | writeTypeAndLength(4, length); 156 | for (i = 0; i < length; ++i) 157 | encodeItem(value[i]); 158 | } else if (value instanceof Uint8Array) { 159 | writeTypeAndLength(2, value.length); 160 | writeUint8Array(value); 161 | } else { 162 | var keys = Object.keys(value); 163 | length = keys.length; 164 | writeTypeAndLength(5, length); 165 | for (i = 0; i < length; ++i) { 166 | var key = keys[i]; 167 | encodeItem(key); 168 | encodeItem(value[key]); 169 | } 170 | } 171 | } 172 | } 173 | 174 | encodeItem(value); 175 | 176 | if ("slice" in data) 177 | return data.slice(0, offset); 178 | 179 | var ret = new ArrayBuffer(offset); 180 | var retView = new DataView(ret); 181 | for (var i = 0; i < offset; ++i) 182 | retView.setUint8(i, dataView.getUint8(i)); 183 | return ret; 184 | } 185 | 186 | function decode(data, tagger, simpleValue) { 187 | var dataView = new DataView(data); 188 | var offset = 0; 189 | 190 | if (typeof tagger !== "function") 191 | tagger = function(value) { return value; }; 192 | if (typeof simpleValue !== "function") 193 | simpleValue = function() { return undefined; }; 194 | 195 | function commitRead(length, value) { 196 | offset += length; 197 | return value; 198 | } 199 | function readArrayBuffer(length) { 200 | return commitRead(length, new Uint8Array(data, offset, length)); 201 | } 202 | function readFloat16() { 203 | var tempArrayBuffer = new ArrayBuffer(4); 204 | var tempDataView = new DataView(tempArrayBuffer); 205 | var value = readUint16(); 206 | 207 | var sign = value & 0x8000; 208 | var exponent = value & 0x7c00; 209 | var fraction = value & 0x03ff; 210 | 211 | if (exponent === 0x7c00) 212 | exponent = 0xff << 10; 213 | else if (exponent !== 0) 214 | exponent += (127 - 15) << 10; 215 | else if (fraction !== 0) 216 | return (sign ? -1 : 1) * fraction * POW_2_24; 217 | 218 | tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13); 219 | return tempDataView.getFloat32(0); 220 | } 221 | function readFloat32() { 222 | return commitRead(4, dataView.getFloat32(offset)); 223 | } 224 | function readFloat64() { 225 | return commitRead(8, dataView.getFloat64(offset)); 226 | } 227 | function readUint8() { 228 | return commitRead(1, dataView.getUint8(offset)); 229 | } 230 | function readUint16() { 231 | return commitRead(2, dataView.getUint16(offset)); 232 | } 233 | function readUint32() { 234 | return commitRead(4, dataView.getUint32(offset)); 235 | } 236 | function readUint64() { 237 | return readUint32() * POW_2_32 + readUint32(); 238 | } 239 | function readBreak() { 240 | if (dataView.getUint8(offset) !== 0xff) 241 | return false; 242 | offset += 1; 243 | return true; 244 | } 245 | function readLength(additionalInformation) { 246 | if (additionalInformation < 24) 247 | return additionalInformation; 248 | if (additionalInformation === 24) 249 | return readUint8(); 250 | if (additionalInformation === 25) 251 | return readUint16(); 252 | if (additionalInformation === 26) 253 | return readUint32(); 254 | if (additionalInformation === 27) 255 | return readUint64(); 256 | if (additionalInformation === 31) 257 | return -1; 258 | throw "Invalid length encoding"; 259 | } 260 | function readIndefiniteStringLength(majorType) { 261 | var initialByte = readUint8(); 262 | if (initialByte === 0xff) 263 | return -1; 264 | var length = readLength(initialByte & 0x1f); 265 | if (length < 0 || (initialByte >> 5) !== majorType) 266 | throw "Invalid indefinite length element"; 267 | return length; 268 | } 269 | 270 | function appendUtf16Data(utf16data, length) { 271 | for (var i = 0; i < length; ++i) { 272 | var value = readUint8(); 273 | if (value & 0x80) { 274 | if (value < 0xe0) { 275 | value = (value & 0x1f) << 6 276 | | (readUint8() & 0x3f); 277 | length -= 1; 278 | } else if (value < 0xf0) { 279 | value = (value & 0x0f) << 12 280 | | (readUint8() & 0x3f) << 6 281 | | (readUint8() & 0x3f); 282 | length -= 2; 283 | } else { 284 | value = (value & 0x0f) << 18 285 | | (readUint8() & 0x3f) << 12 286 | | (readUint8() & 0x3f) << 6 287 | | (readUint8() & 0x3f); 288 | length -= 3; 289 | } 290 | } 291 | 292 | if (value < 0x10000) { 293 | utf16data.push(value); 294 | } else { 295 | value -= 0x10000; 296 | utf16data.push(0xd800 | (value >> 10)); 297 | utf16data.push(0xdc00 | (value & 0x3ff)); 298 | } 299 | } 300 | } 301 | 302 | function decodeItem() { 303 | var initialByte = readUint8(); 304 | var majorType = initialByte >> 5; 305 | var additionalInformation = initialByte & 0x1f; 306 | var i; 307 | var length; 308 | 309 | if (majorType === 7) { 310 | switch (additionalInformation) { 311 | case 25: 312 | return readFloat16(); 313 | case 26: 314 | return readFloat32(); 315 | case 27: 316 | return readFloat64(); 317 | } 318 | } 319 | 320 | length = readLength(additionalInformation); 321 | if (length < 0 && (majorType < 2 || 6 < majorType)) 322 | throw "Invalid length"; 323 | 324 | switch (majorType) { 325 | case 0: 326 | return length; 327 | case 1: 328 | return -1 - length; 329 | case 2: 330 | if (length < 0) { 331 | var elements = []; 332 | var fullArrayLength = 0; 333 | while ((length = readIndefiniteStringLength(majorType)) >= 0) { 334 | fullArrayLength += length; 335 | elements.push(readArrayBuffer(length)); 336 | } 337 | var fullArray = new Uint8Array(fullArrayLength); 338 | var fullArrayOffset = 0; 339 | for (i = 0; i < elements.length; ++i) { 340 | fullArray.set(elements[i], fullArrayOffset); 341 | fullArrayOffset += elements[i].length; 342 | } 343 | return fullArray; 344 | } 345 | return readArrayBuffer(length); 346 | case 3: 347 | var utf16data = []; 348 | if (length < 0) { 349 | while ((length = readIndefiniteStringLength(majorType)) >= 0) 350 | appendUtf16Data(utf16data, length); 351 | } else 352 | appendUtf16Data(utf16data, length); 353 | return String.fromCharCode.apply(null, utf16data); 354 | case 4: 355 | var retArray; 356 | if (length < 0) { 357 | retArray = []; 358 | while (!readBreak()) 359 | retArray.push(decodeItem()); 360 | } else { 361 | retArray = new Array(length); 362 | for (i = 0; i < length; ++i) 363 | retArray[i] = decodeItem(); 364 | } 365 | return retArray; 366 | case 5: 367 | var retObject = {}; 368 | for (i = 0; i < length || length < 0 && !readBreak(); ++i) { 369 | var key = decodeItem(); 370 | retObject[key] = decodeItem(); 371 | } 372 | return retObject; 373 | case 6: 374 | return tagger(decodeItem(), length); 375 | case 7: 376 | switch (length) { 377 | case 20: 378 | return false; 379 | case 21: 380 | return true; 381 | case 22: 382 | return null; 383 | case 23: 384 | return undefined; 385 | default: 386 | return simpleValue(length); 387 | } 388 | } 389 | } 390 | 391 | var ret = decodeItem(); 392 | if (offset !== data.byteLength) 393 | throw "Remaining bytes"; 394 | return ret; 395 | } 396 | 397 | var obj = { encode: encode, decode: decode }; 398 | 399 | if (typeof define === "function" && define.amd) 400 | define("cbor/cbor", obj); 401 | else if (typeof module !== "undefined" && module.exports) 402 | module.exports = obj; 403 | else if (!global.CBOR) 404 | global.CBOR = obj; 405 | 406 | })(this); 407 | -------------------------------------------------------------------------------- /js/vendor/intel-hex.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.MemoryMap = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | /** 8 | * Parser/writer for the "Intel hex" format. 9 | */ 10 | 11 | /* 12 | * A regexp that matches lines in a .hex file. 13 | * 14 | * One hexadecimal character is matched by "[0-9A-Fa-f]". 15 | * Two hex characters are matched by "[0-9A-Fa-f]{2}" 16 | * Eight or more hex characters are matched by "[0-9A-Fa-f]{8,}" 17 | * A capture group of two hex characters is "([0-9A-Fa-f]{2})" 18 | * 19 | * Record mark : 20 | * 8 or more hex chars ([0-9A-Fa-f]{8,}) 21 | * Checksum ([0-9A-Fa-f]{2}) 22 | * Optional newline (?:\r\n|\r|\n|) 23 | */ 24 | var hexLineRegexp = /:([0-9A-Fa-f]{8,})([0-9A-Fa-f]{2})(?:\r\n|\r|\n|)/g; 25 | 26 | 27 | // Takes a Uint8Array as input, 28 | // Returns an integer in the 0-255 range. 29 | function checksum(bytes) { 30 | return (-bytes.reduce(function (sum, v){ return sum + v; }, 0)) & 0xFF; 31 | } 32 | 33 | // Takes two Uint8Arrays as input, 34 | // Returns an integer in the 0-255 range. 35 | function checksumTwo(array1, array2) { 36 | var partial1 = array1.reduce(function (sum, v){ return sum + v; }, 0); 37 | var partial2 = array2.reduce(function (sum, v){ return sum + v; }, 0); 38 | return -( partial1 + partial2 ) & 0xFF; 39 | } 40 | 41 | 42 | // Trivial utility. Converts a number to hex and pads with zeroes up to 2 characters. 43 | function hexpad(number) { 44 | return number.toString(16).toUpperCase().padStart(2, '0'); 45 | } 46 | 47 | 48 | // Polyfill as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger 49 | Number.isInteger = Number.isInteger || function(value) { 50 | return typeof value === 'number' && 51 | isFinite(value) && 52 | Math.floor(value) === value; 53 | }; 54 | 55 | 56 | /** 57 | * @class MemoryMap 58 | * 59 | * Represents the contents of a memory layout, with main focus into (possibly sparse) blocks of data. 60 | *
61 | * A {@linkcode MemoryMap} acts as a subclass of 62 | * {@linkcode https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map|Map}. 63 | * In every entry of it, the key is the starting address of a data block (an integer number), 64 | * and the value is the Uint8Array with the data for that block. 65 | *
66 | * The main rationale for this is that a .hex file can contain a single block of contiguous 67 | * data starting at memory address 0 (and it's the common case for simple .hex files), 68 | * but complex files with several non-contiguous data blocks are also possible, thus 69 | * the need for a data structure on top of the Uint8Arrays. 70 | *
71 | * In order to parse .hex files, use the {@linkcode MemoryMap.fromHex} static factory 72 | * method. In order to write .hex files, create a new {@linkcode MemoryMap} and call 73 | * its {@linkcode MemoryMap.asHexString} method. 74 | * 75 | * @extends Map 76 | * @example 77 | * import MemoryMap from 'nrf-intel-hex'; 78 | * 79 | * let memMap1 = new MemoryMap(); 80 | * let memMap2 = new MemoryMap([[0, new Uint8Array(1,2,3,4)]]); 81 | * let memMap3 = new MemoryMap({0: new Uint8Array(1,2,3,4)}); 82 | * let memMap4 = new MemoryMap({0xCF0: new Uint8Array(1,2,3,4)}); 83 | */ 84 | var MemoryMap = function MemoryMap(blocks) { 85 | var this$1 = this; 86 | 87 | this._blocks = new Map(); 88 | 89 | if (blocks && typeof blocks[Symbol.iterator] === 'function') { 90 | for (var tuple of blocks) { 91 | if (!(tuple instanceof Array) || tuple.length !== 2) { 92 | throw new Error('First parameter to MemoryMap constructor must be an iterable of [addr, bytes] or undefined'); 93 | } 94 | this$1.set(tuple[0], tuple[1]); 95 | } 96 | } else if (typeof blocks === 'object') { 97 | // Try iterating through the object's keys 98 | var addrs = Object.keys(blocks); 99 | for (var addr of addrs) { 100 | this$1.set(parseInt(addr), blocks[addr]); 101 | } 102 | 103 | } else if (blocks !== undefined && blocks !== null) { 104 | throw new Error('First parameter to MemoryMap constructor must be an iterable of [addr, bytes] or undefined'); 105 | } 106 | }; 107 | 108 | var prototypeAccessors = { size: { configurable: true } }; 109 | 110 | MemoryMap.prototype.set = function set (addr, value) { 111 | if (!Number.isInteger(addr)) { 112 | throw new Error('Address passed to MemoryMap is not an integer'); 113 | } 114 | if (addr < 0) { 115 | throw new Error('Address passed to MemoryMap is negative'); 116 | } 117 | if (!(value instanceof Uint8Array)) { 118 | throw new Error('Bytes passed to MemoryMap are not an Uint8Array'); 119 | } 120 | return this._blocks.set(addr, value); 121 | }; 122 | // Delegate the following to the 'this._blocks' Map: 123 | MemoryMap.prototype.get = function get (addr){ return this._blocks.get(addr);}; 124 | MemoryMap.prototype.clear = function clear () { return this._blocks.clear(); }; 125 | MemoryMap.prototype.delete = function delete$1 (addr) { return this._blocks.delete(addr); }; 126 | MemoryMap.prototype.entries = function entries (){ return this._blocks.entries();}; 127 | MemoryMap.prototype.forEach = function forEach (callback, that) { return this._blocks.forEach(callback, that); }; 128 | MemoryMap.prototype.has = function has (addr){ return this._blocks.has(addr);}; 129 | MemoryMap.prototype.keys = function keys () { return this._blocks.keys(); }; 130 | MemoryMap.prototype.values = function values () { return this._blocks.values(); }; 131 | prototypeAccessors.size.get = function () { return this._blocks.size; }; 132 | MemoryMap.prototype[Symbol.iterator] = function () { return this._blocks[Symbol.iterator](); }; 133 | 134 | 135 | /** 136 | * Parses a string containing data formatted in "Intel HEX" format, and 137 | * returns an instance of {@linkcode MemoryMap}. 138 | *
139 | * The insertion order of keys in the {@linkcode MemoryMap} is guaranteed to be strictly 140 | * ascending. In other words, when iterating through the {@linkcode MemoryMap}, the addresses 141 | * will be ordered in ascending order. 142 | *
143 | * The parser has an opinionated behaviour, and will throw a descriptive error if it 144 | * encounters some malformed input. Check the project's 145 | * {@link https://github.com/NordicSemiconductor/nrf-intel-hex#Features|README file} for details. 146 | *
147 | * If maxBlockSize is given, any contiguous data block larger than that will 148 | * be split in several blocks. 149 | * 150 | * @param {String} hexText The contents of a .hex file. 151 | * @param {Number} [maxBlockSize=Infinity] Maximum size of the returned Uint8Arrays. 152 | * 153 | * @return {MemoryMap} 154 | * 155 | * @example 156 | * import MemoryMap from 'nrf-intel-hex'; 157 | * 158 | * let intelHexString = 159 | * ":100000000102030405060708090A0B0C0D0E0F1068\n" + 160 | * ":00000001FF"; 161 | * 162 | * let memMap = MemoryMap.fromHex(intelHexString); 163 | * 164 | * for (let [address, dataBlock] of memMap) { 165 | * console.log('Data block at ', address, ', bytes: ', dataBlock); 166 | * } 167 | */ 168 | MemoryMap.fromHex = function fromHex (hexText, maxBlockSize) { 169 | if ( maxBlockSize === void 0 ) maxBlockSize = Infinity; 170 | 171 | var blocks = new MemoryMap(); 172 | 173 | var lastCharacterParsed = 0; 174 | var matchResult; 175 | var recordCount = 0; 176 | 177 | // Upper Linear Base Address, the 16 most significant bits (2 bytes) of 178 | // the current 32-bit (4-byte) address 179 | // In practice this is a offset that is summed to the "load offset" of the 180 | // data records 181 | var ulba = 0; 182 | 183 | hexLineRegexp.lastIndex = 0; // Reset the regexp, if not it would skip content when called twice 184 | 185 | while ((matchResult = hexLineRegexp.exec(hexText)) !== null) { 186 | recordCount++; 187 | 188 | // By default, a regexp loop ignores gaps between matches, but 189 | // we want to be aware of them. 190 | if (lastCharacterParsed !== matchResult.index) { 191 | throw new Error( 192 | 'Malformed hex file: Could not parse between characters ' + 193 | lastCharacterParsed + 194 | ' and ' + 195 | matchResult.index + 196 | ' ("' + 197 | hexText.substring(lastCharacterParsed, Math.min(matchResult.index, lastCharacterParsed + 16)).trim() + 198 | '")'); 199 | } 200 | lastCharacterParsed = hexLineRegexp.lastIndex; 201 | 202 | // Give pretty names to the match's capture groups 203 | var recordStr = matchResult[1]; 204 | var recordChecksum = matchResult[2]; 205 | 206 | // String to Uint8Array - https://stackoverflow.com/questions/43131242/how-to-convert-a-hexademical-string-of-data-to-an-arraybuffer-in-javascript 207 | var recordBytes = new Uint8Array(recordStr.match(/[\da-f]{2}/gi).map(function (h){ return parseInt(h, 16); })); 208 | 209 | var recordLength = recordBytes[0]; 210 | if (recordLength + 4 !== recordBytes.length) { 211 | throw new Error('Mismatched record length at record ' + recordCount + ' (' + matchResult[0].trim() + '), expected ' + (recordLength) + ' data bytes but actual length is ' + (recordBytes.length - 4)); 212 | } 213 | 214 | var cs = checksum(recordBytes); 215 | if (parseInt(recordChecksum, 16) !== cs) { 216 | throw new Error('Checksum failed at record ' + recordCount + ' (' + matchResult[0].trim() + '), should be ' + cs.toString(16) ); 217 | } 218 | 219 | var offset = (recordBytes[1] << 8) + recordBytes[2]; 220 | var recordType = recordBytes[3]; 221 | var data = recordBytes.subarray(4); 222 | 223 | if (recordType === 0) { 224 | // Data record, contains data 225 | // Create a new block, at (upper linear base address + offset) 226 | if (blocks.has(ulba + offset)) { 227 | throw new Error('Duplicated data at record ' + recordCount + ' (' + matchResult[0].trim() + ')'); 228 | } 229 | if (offset + data.length > 0x10000) { 230 | throw new Error( 231 | 'Data at record ' + 232 | recordCount + 233 | ' (' + 234 | matchResult[0].trim() + 235 | ') wraps over 0xFFFF. This would trigger ambiguous behaviour. Please restructure your data so that for every record the data offset plus the data length do not exceed 0xFFFF.'); 236 | } 237 | 238 | blocks.set( ulba + offset, data ); 239 | 240 | } else { 241 | 242 | // All non-data records must have a data offset of zero 243 | if (offset !== 0) { 244 | throw new Error('Record ' + recordCount + ' (' + matchResult[0].trim() + ') must have 0000 as data offset.'); 245 | } 246 | 247 | switch (recordType) { 248 | case 1: // EOF 249 | if (lastCharacterParsed !== hexText.length) { 250 | // This record should be at the very end of the string 251 | throw new Error('There is data after an EOF record at record ' + recordCount); 252 | } 253 | 254 | return blocks.join(maxBlockSize); 255 | 256 | case 2: // Extended Segment Address Record 257 | // Sets the 16 most significant bits of the 20-bit Segment Base 258 | // Address for the subsequent data. 259 | ulba = ((data[0] << 8) + data[1]) << 4; 260 | break; 261 | 262 | case 3: // Start Segment Address Record 263 | // Do nothing. Record type 3 only applies to 16-bit Intel CPUs, 264 | // where it should reset the program counter (CS+IP CPU registers) 265 | break; 266 | 267 | case 4: // Extended Linear Address Record 268 | // Sets the 16 most significant (upper) bits of the 32-bit Linear Address 269 | // for the subsequent data 270 | ulba = ((data[0] << 8) + data[1]) << 16; 271 | break; 272 | 273 | case 5: // Start Linear Address Record 274 | // Do nothing. Record type 5 only applies to 32-bit Intel CPUs, 275 | // where it should reset the program counter (EIP CPU register) 276 | // It might have meaning for other CPU architectures 277 | // (see http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka9903.html ) 278 | // but will be ignored nonetheless. 279 | break; 280 | default: 281 | throw new Error('Invalid record type 0x' + hexpad(recordType) + ' at record ' + recordCount + ' (should be between 0x00 and 0x05)'); 282 | } 283 | } 284 | } 285 | 286 | if (recordCount) { 287 | throw new Error('No EOF record at end of file'); 288 | } else { 289 | throw new Error('Malformed .hex file, could not parse any registers'); 290 | } 291 | }; 292 | 293 | 294 | /** 295 | * Returns a new instance of {@linkcode MemoryMap}, containing 296 | * the same data, but concatenating together those memory blocks that are adjacent. 297 | *
298 | * The insertion order of keys in the {@linkcode MemoryMap} is guaranteed to be strictly 299 | * ascending. In other words, when iterating through the {@linkcode MemoryMap}, the addresses 300 | * will be ordered in ascending order. 301 | *
302 | * If maxBlockSize is given, blocks will be concatenated together only 303 | * until the joined block reaches this size in bytes. This means that the output 304 | * {@linkcode MemoryMap} might have more entries than the input one. 305 | *
306 | * If there is any overlap between blocks, an error will be thrown. 307 | *
308 | * The returned {@linkcode MemoryMap} will use newly allocated memory. 309 | * 310 | * @param {Number} [maxBlockSize=Infinity] Maximum size of the Uint8Arrays in the 311 | * returned {@linkcode MemoryMap}. 312 | * 313 | * @return {MemoryMap} 314 | */ 315 | MemoryMap.prototype.join = function join (maxBlockSize) { 316 | var this$1 = this; 317 | if ( maxBlockSize === void 0 ) maxBlockSize = Infinity; 318 | 319 | 320 | // First pass, create a Map of address→length of contiguous blocks 321 | var sortedKeys = Array.from(this.keys()).sort(function (a,b){ return a-b; }); 322 | var blockSizes = new Map(); 323 | var lastBlockAddr = -1; 324 | var lastBlockEndAddr = -1; 325 | 326 | for (var i=0,l=sortedKeys.length; iMap} 364 | * of {@linkcode MemoryMap}s, indexed by a alphanumeric ID, 365 | * returns a Map of address to tuples (Arrayss of length 2) of the form 366 | * (id, Uint8Array)s. 367 | *
368 | * The scenario for using this is having several {@linkcode MemoryMap}s, from several calls to 369 | * {@link module:nrf-intel-hex~hexToArrays|hexToArrays}, each having a different identifier. 370 | * This function locates where those memory block sets overlap, and returns a Map 371 | * containing addresses as keys, and arrays as values. Each array will contain 1 or more 372 | * (id, Uint8Array) tuples: the identifier of the memory block set that has 373 | * data in that region, and the data itself. When memory block sets overlap, there will 374 | * be more than one tuple. 375 | *
376 | * The Uint8Arrays in the output are 377 | * {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray|subarrays} 378 | * of the input data; new memory is not allocated for them. 379 | *
380 | * The insertion order of keys in the output Map is guaranteed to be strictly 381 | * ascending. In other words, when iterating through the Map, the addresses 382 | * will be ordered in ascending order. 383 | *
384 | * When two blocks overlap, the corresponding array of tuples will have the tuples ordered 385 | * in the insertion order of the input Map of block sets. 386 | *
387 | * 388 | * @param {Map.MemoryMap} memoryMaps The input memory block sets 389 | * 390 | * @example 391 | * import MemoryMap from 'nrf-intel-hex'; 392 | * 393 | * let memMap1 = MemoryMap.fromHex( hexdata1 ); 394 | * let memMap2 = MemoryMap.fromHex( hexdata2 ); 395 | * let memMap3 = MemoryMap.fromHex( hexdata3 ); 396 | * 397 | * let maps = new Map([ 398 | * ['file A', blocks1], 399 | * ['file B', blocks2], 400 | * ['file C', blocks3] 401 | * ]); 402 | * 403 | * let overlappings = MemoryMap.overlapMemoryMaps(maps); 404 | * 405 | * for (let [address, tuples] of overlappings) { 406 | * // if 'tuples' has length > 1, there is an overlap starting at 'address' 407 | * 408 | * for (let [address, tuples] of overlappings) { 409 | * let [id, bytes] = tuple; 410 | * // 'id' in this example is either 'file A', 'file B' or 'file C' 411 | * } 412 | * } 413 | * @return {Map.Array} The map of possibly overlapping memory blocks 414 | */ 415 | MemoryMap.overlapMemoryMaps = function overlapMemoryMaps (memoryMaps) { 416 | // First pass: create a list of addresses where any block starts or ends. 417 | var cuts = new Set(); 418 | for (var [, blocks] of memoryMaps) { 419 | for (var [address, block] of blocks) { 420 | cuts.add(address); 421 | cuts.add(address + block.length); 422 | } 423 | } 424 | 425 | var orderedCuts = Array.from(cuts.values()).sort(function (a,b){ return a-b; }); 426 | var overlaps = new Map(); 427 | 428 | // Second pass: iterate through the cuts, get slices of every intersecting blockset 429 | var loop = function ( i, l ) { 430 | var cut = orderedCuts[i]; 431 | var nextCut = orderedCuts[i+1]; 432 | var tuples = []; 433 | 434 | for (var [setId, blocks$1] of memoryMaps) { 435 | // Find the block with the highest address that is equal or lower to 436 | // the current cut (if any) 437 | var blockAddr = Array.from(blocks$1.keys()).reduce(function (acc, val){ 438 | if (val > cut) { 439 | return acc; 440 | } 441 | return Math.max( acc, val ); 442 | }, -1); 443 | 444 | if (blockAddr !== -1) { 445 | var block$1 = blocks$1.get(blockAddr); 446 | var subBlockStart = cut - blockAddr; 447 | var subBlockEnd = nextCut - blockAddr; 448 | 449 | if (subBlockStart < block$1.length) { 450 | tuples.push([ setId, block$1.subarray(subBlockStart, subBlockEnd) ]); 451 | } 452 | } 453 | } 454 | 455 | if (tuples.length) { 456 | overlaps.set(cut, tuples); 457 | } 458 | }; 459 | 460 | for (var i=0, l=orderedCuts.length-1; iMap of address to an Array of (id, Uint8Array) tuples), 469 | * returns a {@linkcode MemoryMap}. This discards the IDs in the process. 470 | *
471 | * The output Map contains as many entries as the input one (using the same addresses 472 | * as keys), but the value for each entry will be the Uint8Array of the last 473 | * tuple for each address in the input data. 474 | *
475 | * The scenario is wanting to join together several parsed .hex files, not worrying about 476 | * their overlaps. 477 | *
478 | * 479 | * @param {Map.Array} overlaps The (possibly overlapping) input memory blocks 480 | * @return {MemoryMap} The flattened memory blocks 481 | */ 482 | MemoryMap.flattenOverlaps = function flattenOverlaps (overlaps) { 483 | return new MemoryMap( 484 | Array.from(overlaps.entries()).map(function (ref) { 485 | var address = ref[0]; 486 | var tuples = ref[1]; 487 | 488 | return [address, tuples[tuples.length - 1][1] ]; 489 | }) 490 | ); 491 | }; 492 | 493 | 494 | /** 495 | * Returns a new instance of {@linkcode MemoryMap}, where: 496 | * 497 | *
    498 | *
  • Each key (the start address of each Uint8Array) is a multiple of 499 | *pageSize
  • 500 | *
  • The size of each Uint8Array is exactly pageSize
  • 501 | *
  • Bytes from the input map to bytes in the output
  • 502 | *
  • Bytes not in the input are replaced by a padding value
  • 503 | *
504 | *
505 | * The scenario is wanting to prepare pages of bytes for a write operation, where the write 506 | * operation affects a whole page/sector at once. 507 | *
508 | * The insertion order of keys in the output {@linkcode MemoryMap} is guaranteed 509 | * to be strictly ascending. In other words, when iterating through the 510 | * {@linkcode MemoryMap}, the addresses will be ordered in ascending order. 511 | *
512 | * The Uint8Arrays in the output will be newly allocated. 513 | *
514 | * 515 | * @param {Number} [pageSize=1024] The size of the output pages, in bytes 516 | * @param {Number} [pad=0xFF] The byte value to use for padding 517 | * @return {MemoryMap} 518 | */ 519 | MemoryMap.prototype.paginate = function paginate ( pageSize, pad) { 520 | var this$1 = this; 521 | if ( pageSize === void 0 ) pageSize=1024; 522 | if ( pad === void 0 ) pad=0xFF; 523 | 524 | if (pageSize <= 0) { 525 | throw new Error('Page size must be greater than zero'); 526 | } 527 | var outPages = new MemoryMap(); 528 | var page; 529 | 530 | var sortedKeys = Array.from(this.keys()).sort(function (a,b){ return a-b; }); 531 | 532 | for (var i=0,l=sortedKeys.length; iUint8Array which contains the given offset, 566 | * and returns the four bytes held at that offset, as a 32-bit unsigned integer. 567 | * 568 | *
569 | * Behaviour is similar to {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32|DataView.prototype.getUint32}, 570 | * except that this operates over a {@linkcode MemoryMap} instead of 571 | * over an ArrayBuffer, and that this may return undefined if 572 | * the address is not entirely contained within one of the Uint8Arrays. 573 | *
574 | * 575 | * @param {Number} offset The memory offset to read the data 576 | * @param {Boolean} [littleEndian=false] Whether to fetch the 4 bytes as a little- or big-endian integer 577 | * @return {Number|undefined} An unsigned 32-bit integer number 578 | */ 579 | MemoryMap.prototype.getUint32 = function getUint32 (offset, littleEndian) { 580 | var this$1 = this; 581 | 582 | var keys = Array.from(this.keys()); 583 | 584 | for (var i=0,l=keys.length; iString of text representing a .hex file. 600 | *
601 | * The writer has an opinionated behaviour. Check the project's 602 | * {@link https://github.com/NordicSemiconductor/nrf-intel-hex#Features|README file} for details. 603 | * 604 | * @param {Number} [lineSize=16] Maximum number of bytes to be encoded in each data record. 605 | * Must have a value between 1 and 255, as per the specification. 606 | * 607 | * @return {String} String of text with the .hex representation of the input binary data 608 | * 609 | * @example 610 | * import MemoryMap from 'nrf-intel-hex'; 611 | * 612 | * let memMap = new MemoryMap(); 613 | * let bytes = new Uint8Array(....); 614 | * memMap.set(0x0FF80000, bytes); // The block with 'bytes' will start at offset 0x0FF80000 615 | * 616 | * let string = memMap.asHexString(); 617 | */ 618 | MemoryMap.prototype.asHexString = function asHexString (lineSize) { 619 | var this$1 = this; 620 | if ( lineSize === void 0 ) lineSize = 16; 621 | 622 | var lowAddress = 0;// 16 least significant bits of the current addr 623 | var highAddress = -1 << 16; // 16 most significant bits of the current addr 624 | var records = []; 625 | if (lineSize <=0) { 626 | throw new Error('Size of record must be greater than zero'); 627 | } else if (lineSize > 255) { 628 | throw new Error('Size of record must be less than 256'); 629 | } 630 | 631 | // Placeholders 632 | var offsetRecord = new Uint8Array(6); 633 | var recordHeader = new Uint8Array(4); 634 | 635 | var sortedKeys = Array.from(this.keys()).sort(function (a,b){ return a-b; }); 636 | for (var i=0,l=sortedKeys.length; i (highAddress + 0xFFFF)) { 652 | // Insert a new 0x04 record to jump to a new 64KiB block 653 | 654 | // Round up the least significant 16 bits - no bitmasks because they trigger 655 | // base-2 negative numbers, whereas subtracting the modulo maintains precision 656 | highAddress = blockAddr - blockAddr % 0x10000; 657 | lowAddress = 0; 658 | 659 | offsetRecord[0] = 2;// Length 660 | offsetRecord[1] = 0;// Load offset, high byte 661 | offsetRecord[2] = 0;// Load offset, low byte 662 | offsetRecord[3] = 4;// Record type 663 | offsetRecord[4] = highAddress >> 24;// new address offset, high byte 664 | offsetRecord[5] = highAddress >> 16;// new address offset, low byte 665 | 666 | records.push( 667 | ':' + 668 | Array.prototype.map.call(offsetRecord, hexpad).join('') + 669 | hexpad(checksum(offsetRecord)) 670 | ); 671 | } 672 | 673 | if (blockAddr < (highAddress + lowAddress)) { 674 | throw new Error( 675 | 'Block starting at 0x' + 676 | blockAddr.toString(16) + 677 | ' overlaps with a previous block.'); 678 | } 679 | 680 | lowAddress = blockAddr % 0x10000; 681 | var blockOffset = 0; 682 | var blockEnd = blockAddr + blockSize; 683 | if (blockEnd > 0xFFFFFFFF) { 684 | throw new Error('Data cannot be over 0xFFFFFFFF'); 685 | } 686 | 687 | // Loop for every 64KiB memory segment that spans this block 688 | while (highAddress + lowAddress < blockEnd) { 689 | 690 | if (lowAddress > 0xFFFF) { 691 | // Insert a new 0x04 record to jump to a new 64KiB block 692 | highAddress += 1 << 16; // Increase by one 693 | lowAddress = 0; 694 | 695 | offsetRecord[0] = 2;// Length 696 | offsetRecord[1] = 0;// Load offset, high byte 697 | offsetRecord[2] = 0;// Load offset, low byte 698 | offsetRecord[3] = 4;// Record type 699 | offsetRecord[4] = highAddress >> 24;// new address offset, high byte 700 | offsetRecord[5] = highAddress >> 16;// new address offset, low byte 701 | 702 | records.push( 703 | ':' + 704 | Array.prototype.map.call(offsetRecord, hexpad).join('') + 705 | hexpad(checksum(offsetRecord)) 706 | ); 707 | } 708 | 709 | var recordSize = -1; 710 | // Loop for every record for that spans the current 64KiB memory segment 711 | while (lowAddress < 0x10000 && recordSize) { 712 | recordSize = Math.min( 713 | lineSize, // Normal case 714 | blockEnd - highAddress - lowAddress, // End of block 715 | 0x10000 - lowAddress // End of low addresses 716 | ); 717 | 718 | if (recordSize) { 719 | 720 | recordHeader[0] = recordSize; // Length 721 | recordHeader[1] = lowAddress >> 8;// Load offset, high byte 722 | recordHeader[2] = lowAddress;// Load offset, low byte 723 | recordHeader[3] = 0;// Record type 724 | 725 | var subBlock = block.subarray(blockOffset, blockOffset + recordSize); // Data bytes for this record 726 | 727 | records.push( 728 | ':' + 729 | Array.prototype.map.call(recordHeader, hexpad).join('') + 730 | Array.prototype.map.call(subBlock, hexpad).join('') + 731 | hexpad(checksumTwo(recordHeader, subBlock)) 732 | ); 733 | 734 | blockOffset += recordSize; 735 | lowAddress += recordSize; 736 | } 737 | } 738 | } 739 | } 740 | 741 | records.push(':00000001FF');// EOF record 742 | 743 | return records.join('\n'); 744 | }; 745 | 746 | 747 | /** 748 | * Performs a deep copy of the current {@linkcode MemoryMap}, returning a new one 749 | * with exactly the same contents, but allocating new memory for each of its 750 | * Uint8Arrays. 751 | * 752 | * @return {MemoryMap} 753 | */ 754 | MemoryMap.prototype.clone = function clone () { 755 | var this$1 = this; 756 | 757 | var cloned = new MemoryMap(); 758 | 759 | for (var [addr, value] of this$1) { 760 | cloned.set(addr, new Uint8Array(value)); 761 | } 762 | 763 | return cloned; 764 | }; 765 | 766 | 767 | /** 768 | * Given one Uint8Array, looks through its contents and returns a new 769 | * {@linkcode MemoryMap}, stripping away those regions where there are only 770 | * padding bytes. 771 | *
772 | * The start of the input Uint8Array is assumed to be offset zero for the output. 773 | *
774 | * The use case here is dumping memory from a working device and try to see the 775 | * "interesting" memory regions it has. This assumes that there is a constant, 776 | * predefined padding byte value being used in the "non-interesting" regions. 777 | * In other words: this will work as long as the dump comes from a flash memory 778 | * which has been previously erased (thus 0xFFs for padding), or from a 779 | * previously blanked HDD (thus 0x00s for padding). 780 | *
781 | * This method uses subarray on the input data, and thus does not allocate memory 782 | * for the Uint8Arrays. 783 | * 784 | * @param {Uint8Array} bytes The input data 785 | * @param {Number} [padByte=0xFF] The value of the byte assumed to be used as padding 786 | * @param {Number} [minPadLength=64] The minimum number of consecutive pad bytes to 787 | * be considered actual padding 788 | * 789 | * @return {MemoryMap} 790 | */ 791 | MemoryMap.fromPaddedUint8Array = function fromPaddedUint8Array (bytes, padByte, minPadLength) { 792 | if ( padByte === void 0 ) padByte=0xFF; 793 | if ( minPadLength === void 0 ) minPadLength=64; 794 | 795 | 796 | if (!(bytes instanceof Uint8Array)) { 797 | throw new Error('Bytes passed to fromPaddedUint8Array are not an Uint8Array'); 798 | } 799 | 800 | // The algorithm used is naïve and checks every byte. 801 | // An obvious optimization would be to implement Boyer-Moore 802 | // (see https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm ) 803 | // or otherwise start skipping up to minPadLength bytes when going through a non-pad 804 | // byte. 805 | // Anyway, we could expect a lot of cases where there is a majority of pad bytes, 806 | // and the algorithm should check most of them anyway, so the perf gain is questionable. 807 | 808 | var memMap = new MemoryMap(); 809 | var consecutivePads = 0; 810 | var lastNonPad = -1; 811 | var firstNonPad = 0; 812 | var skippingBytes = false; 813 | var l = bytes.length; 814 | 815 | for (var addr = 0; addr < l; addr++) { 816 | var byte = bytes[addr]; 817 | 818 | if (byte === padByte) { 819 | consecutivePads++; 820 | if (consecutivePads >= minPadLength) { 821 | // Edge case: ignore writing a zero-length block when skipping 822 | // bytes at the beginning of the input 823 | if (lastNonPad !== -1) { 824 | /// Add the previous block to the result memMap 825 | memMap.set(firstNonPad, bytes.subarray(firstNonPad, lastNonPad+1)); 826 | } 827 | 828 | skippingBytes = true; 829 | } 830 | } else { 831 | if (skippingBytes) { 832 | skippingBytes = false; 833 | firstNonPad = addr; 834 | } 835 | lastNonPad = addr; 836 | consecutivePads = 0; 837 | } 838 | } 839 | 840 | // At EOF, add the last block if not skipping bytes already (and input not empty) 841 | if (!skippingBytes && lastNonPad !== -1) { 842 | memMap.set(firstNonPad, bytes.subarray(firstNonPad, l)); 843 | } 844 | 845 | return memMap; 846 | }; 847 | 848 | 849 | /** 850 | * Returns a new instance of {@linkcode MemoryMap}, containing only data between 851 | * the addresses address and address + length. 852 | * Behaviour is similar to {@linkcode https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/slice|Array.prototype.slice}, 853 | * in that the return value is a portion of the current {@linkcode MemoryMap}. 854 | * 855 | *
856 | * The returned {@linkcode MemoryMap} might be empty. 857 | * 858 | *
859 | * Internally, this uses subarray, so new memory is not allocated. 860 | * 861 | * @param {Number} address The start address of the slice 862 | * @param {Number} length The length of memory map to slice out 863 | * @return {MemoryMap} 864 | */ 865 | MemoryMap.prototype.slice = function slice (address, length){ 866 | var this$1 = this; 867 | if ( length === void 0 ) length = Infinity; 868 | 869 | if (length < 0) { 870 | throw new Error('Length of the slice cannot be negative'); 871 | } 872 | 873 | var sliced = new MemoryMap(); 874 | 875 | for (var [blockAddr, block] of this$1) { 876 | var blockLength = block.length; 877 | 878 | if ((blockAddr + blockLength) >= address && blockAddr < (address + length)) { 879 | var sliceStart = Math.max(address, blockAddr); 880 | var sliceEnd = Math.min(address + length, blockAddr + blockLength); 881 | var sliceLength = sliceEnd - sliceStart; 882 | var relativeSliceStart = sliceStart - blockAddr; 883 | 884 | if (sliceLength > 0) { 885 | sliced.set(sliceStart, block.subarray(relativeSliceStart, relativeSliceStart + sliceLength)); 886 | } 887 | } 888 | } 889 | return sliced; 890 | }; 891 | 892 | /** 893 | * Returns a new instance of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32|Uint8Array}, containing only data between 894 | * the addresses address and address + length. Any byte without a value 895 | * in the input {@linkcode MemoryMap} will have a value of padByte. 896 | * 897 | *
898 | * This method allocates new memory. 899 | * 900 | * @param {Number} address The start address of the slice 901 | * @param {Number} length The length of memory map to slice out 902 | * @param {Number} [padByte=0xFF] The value of the byte assumed to be used as padding 903 | * @return {MemoryMap} 904 | */ 905 | MemoryMap.prototype.slicePad = function slicePad (address, length, padByte){ 906 | var this$1 = this; 907 | if ( padByte === void 0 ) padByte=0xFF; 908 | 909 | if (length < 0) { 910 | throw new Error('Length of the slice cannot be negative'); 911 | } 912 | 913 | var out = (new Uint8Array(length)).fill(padByte); 914 | 915 | for (var [blockAddr, block] of this$1) { 916 | var blockLength = block.length; 917 | 918 | if ((blockAddr + blockLength) >= address && blockAddr < (address + length)) { 919 | var sliceStart = Math.max(address, blockAddr); 920 | var sliceEnd = Math.min(address + length, blockAddr + blockLength); 921 | var sliceLength = sliceEnd - sliceStart; 922 | var relativeSliceStart = sliceStart - blockAddr; 923 | 924 | if (sliceLength > 0) { 925 | out.set(block.subarray(relativeSliceStart, relativeSliceStart + sliceLength), sliceStart - address); 926 | } 927 | } 928 | } 929 | return out; 930 | }; 931 | 932 | /** 933 | * Checks whether the current memory map contains the one given as a parameter. 934 | * 935 | *
936 | * "Contains" means that all the offsets that have a byte value in the given 937 | * memory map have a value in the current memory map, and that the byte values 938 | * are the same. 939 | * 940 | *
941 | * An empty memory map is always contained in any other memory map. 942 | * 943 | *
944 | * Returns boolean true if the memory map is contained, false 945 | * otherwise. 946 | * 947 | * @param {MemoryMap} memMap The memory map to check 948 | * @return {Boolean} 949 | */ 950 | MemoryMap.prototype.contains = function contains (memMap) { 951 | var this$1 = this; 952 | 953 | for (var [blockAddr, block] of memMap) { 954 | 955 | var blockLength = block.length; 956 | 957 | var slice = this$1.slice(blockAddr, blockLength).join().get(blockAddr); 958 | 959 | if ((!slice) || slice.length !== blockLength ) { 960 | return false; 961 | } 962 | 963 | for (var i in block) { 964 | if (block[i] !== slice[i]) { 965 | return false; 966 | } 967 | } 968 | } 969 | return true; 970 | }; 971 | 972 | Object.defineProperties( MemoryMap.prototype, prototypeAccessors ); 973 | 974 | return MemoryMap; 975 | 976 | }))); 977 | //# sourceMappingURL=intel-hex.browser.js.map 978 | -------------------------------------------------------------------------------- /js/vendor/platform.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Platform.js 3 | * Copyright 2014-2018 Benjamin Tan 4 | * Copyright 2011-2013 John-David Dalton 5 | * Available under MIT license 6 | */ 7 | ;(function() { 8 | 'use strict'; 9 | 10 | /** Used to determine if values are of the language type `Object`. */ 11 | var objectTypes = { 12 | 'function': true, 13 | 'object': true 14 | }; 15 | 16 | /** Used as a reference to the global object. */ 17 | var root = (objectTypes[typeof window] && window) || this; 18 | 19 | /** Backup possible global object. */ 20 | var oldRoot = root; 21 | 22 | /** Detect free variable `exports`. */ 23 | var freeExports = objectTypes[typeof exports] && exports; 24 | 25 | /** Detect free variable `module`. */ 26 | var freeModule = objectTypes[typeof module] && module && !module.nodeType && module; 27 | 28 | /** Detect free variable `global` from Node.js or Browserified code and use it as `root`. */ 29 | var freeGlobal = freeExports && freeModule && typeof global == 'object' && global; 30 | if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) { 31 | root = freeGlobal; 32 | } 33 | 34 | /** 35 | * Used as the maximum length of an array-like object. 36 | * See the [ES6 spec](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength) 37 | * for more details. 38 | */ 39 | var maxSafeInteger = Math.pow(2, 53) - 1; 40 | 41 | /** Regular expression to detect Opera. */ 42 | var reOpera = /\bOpera/; 43 | 44 | /** Possible global object. */ 45 | var thisBinding = this; 46 | 47 | /** Used for native method references. */ 48 | var objectProto = Object.prototype; 49 | 50 | /** Used to check for own properties of an object. */ 51 | var hasOwnProperty = objectProto.hasOwnProperty; 52 | 53 | /** Used to resolve the internal `[[Class]]` of values. */ 54 | var toString = objectProto.toString; 55 | 56 | /*--------------------------------------------------------------------------*/ 57 | 58 | /** 59 | * Capitalizes a string value. 60 | * 61 | * @private 62 | * @param {string} string The string to capitalize. 63 | * @returns {string} The capitalized string. 64 | */ 65 | function capitalize(string) { 66 | string = String(string); 67 | return string.charAt(0).toUpperCase() + string.slice(1); 68 | } 69 | 70 | /** 71 | * A utility function to clean up the OS name. 72 | * 73 | * @private 74 | * @param {string} os The OS name to clean up. 75 | * @param {string} [pattern] A `RegExp` pattern matching the OS name. 76 | * @param {string} [label] A label for the OS. 77 | */ 78 | function cleanupOS(os, pattern, label) { 79 | // Platform tokens are defined at: 80 | // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx 81 | // http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx 82 | var data = { 83 | '10.0': '10', 84 | '6.4': '10 Technical Preview', 85 | '6.3': '8.1', 86 | '6.2': '8', 87 | '6.1': 'Server 2008 R2 / 7', 88 | '6.0': 'Server 2008 / Vista', 89 | '5.2': 'Server 2003 / XP 64-bit', 90 | '5.1': 'XP', 91 | '5.01': '2000 SP1', 92 | '5.0': '2000', 93 | '4.0': 'NT', 94 | '4.90': 'ME' 95 | }; 96 | // Detect Windows version from platform tokens. 97 | if (pattern && label && /^Win/i.test(os) && !/^Windows Phone /i.test(os) && 98 | (data = data[/[\d.]+$/.exec(os)])) { 99 | os = 'Windows ' + data; 100 | } 101 | // Correct character case and cleanup string. 102 | os = String(os); 103 | 104 | if (pattern && label) { 105 | os = os.replace(RegExp(pattern, 'i'), label); 106 | } 107 | 108 | os = format( 109 | os.replace(/ ce$/i, ' CE') 110 | .replace(/\bhpw/i, 'web') 111 | .replace(/\bMacintosh\b/, 'Mac OS') 112 | .replace(/_PowerPC\b/i, ' OS') 113 | .replace(/\b(OS X) [^ \d]+/i, '$1') 114 | .replace(/\bMac (OS X)\b/, '$1') 115 | .replace(/\/(\d)/, ' $1') 116 | .replace(/_/g, '.') 117 | .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '') 118 | .replace(/\bx86\.64\b/gi, 'x86_64') 119 | .replace(/\b(Windows Phone) OS\b/, '$1') 120 | .replace(/\b(Chrome OS \w+) [\d.]+\b/, '$1') 121 | .split(' on ')[0] 122 | ); 123 | 124 | return os; 125 | } 126 | 127 | /** 128 | * An iteration utility for arrays and objects. 129 | * 130 | * @private 131 | * @param {Array|Object} object The object to iterate over. 132 | * @param {Function} callback The function called per iteration. 133 | */ 134 | function each(object, callback) { 135 | var index = -1, 136 | length = object ? object.length : 0; 137 | 138 | if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) { 139 | while (++index < length) { 140 | callback(object[index], index, object); 141 | } 142 | } else { 143 | forOwn(object, callback); 144 | } 145 | } 146 | 147 | /** 148 | * Trim and conditionally capitalize string values. 149 | * 150 | * @private 151 | * @param {string} string The string to format. 152 | * @returns {string} The formatted string. 153 | */ 154 | function format(string) { 155 | string = trim(string); 156 | return /^(?:webOS|i(?:OS|P))/.test(string) 157 | ? string 158 | : capitalize(string); 159 | } 160 | 161 | /** 162 | * Iterates over an object's own properties, executing the `callback` for each. 163 | * 164 | * @private 165 | * @param {Object} object The object to iterate over. 166 | * @param {Function} callback The function executed per own property. 167 | */ 168 | function forOwn(object, callback) { 169 | for (var key in object) { 170 | if (hasOwnProperty.call(object, key)) { 171 | callback(object[key], key, object); 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * Gets the internal `[[Class]]` of a value. 178 | * 179 | * @private 180 | * @param {*} value The value. 181 | * @returns {string} The `[[Class]]`. 182 | */ 183 | function getClassOf(value) { 184 | return value == null 185 | ? capitalize(value) 186 | : toString.call(value).slice(8, -1); 187 | } 188 | 189 | /** 190 | * Host objects can return type values that are different from their actual 191 | * data type. The objects we are concerned with usually return non-primitive 192 | * types of "object", "function", or "unknown". 193 | * 194 | * @private 195 | * @param {*} object The owner of the property. 196 | * @param {string} property The property to check. 197 | * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`. 198 | */ 199 | function isHostType(object, property) { 200 | var type = object != null ? typeof object[property] : 'number'; 201 | return !/^(?:boolean|number|string|undefined)$/.test(type) && 202 | (type == 'object' ? !!object[property] : true); 203 | } 204 | 205 | /** 206 | * Prepares a string for use in a `RegExp` by making hyphens and spaces optional. 207 | * 208 | * @private 209 | * @param {string} string The string to qualify. 210 | * @returns {string} The qualified string. 211 | */ 212 | function qualify(string) { 213 | return String(string).replace(/([ -])(?!$)/g, '$1?'); 214 | } 215 | 216 | /** 217 | * A bare-bones `Array#reduce` like utility function. 218 | * 219 | * @private 220 | * @param {Array} array The array to iterate over. 221 | * @param {Function} callback The function called per iteration. 222 | * @returns {*} The accumulated result. 223 | */ 224 | function reduce(array, callback) { 225 | var accumulator = null; 226 | each(array, function(value, index) { 227 | accumulator = callback(accumulator, value, index, array); 228 | }); 229 | return accumulator; 230 | } 231 | 232 | /** 233 | * Removes leading and trailing whitespace from a string. 234 | * 235 | * @private 236 | * @param {string} string The string to trim. 237 | * @returns {string} The trimmed string. 238 | */ 239 | function trim(string) { 240 | return String(string).replace(/^ +| +$/g, ''); 241 | } 242 | 243 | /*--------------------------------------------------------------------------*/ 244 | 245 | /** 246 | * Creates a new platform object. 247 | * 248 | * @memberOf platform 249 | * @param {Object|string} [ua=navigator.userAgent] The user agent string or 250 | * context object. 251 | * @returns {Object} A platform object. 252 | */ 253 | function parse(ua) { 254 | 255 | /** The environment context object. */ 256 | var context = root; 257 | 258 | /** Used to flag when a custom context is provided. */ 259 | var isCustomContext = ua && typeof ua == 'object' && getClassOf(ua) != 'String'; 260 | 261 | // Juggle arguments. 262 | if (isCustomContext) { 263 | context = ua; 264 | ua = null; 265 | } 266 | 267 | /** Browser navigator object. */ 268 | var nav = context.navigator || {}; 269 | 270 | /** Browser user agent string. */ 271 | var userAgent = nav.userAgent || ''; 272 | 273 | ua || (ua = userAgent); 274 | 275 | /** Used to flag when `thisBinding` is the [ModuleScope]. */ 276 | var isModuleScope = isCustomContext || thisBinding == oldRoot; 277 | 278 | /** Used to detect if browser is like Chrome. */ 279 | var likeChrome = isCustomContext 280 | ? !!nav.likeChrome 281 | : /\bChrome\b/.test(ua) && !/internal|\n/i.test(toString.toString()); 282 | 283 | /** Internal `[[Class]]` value shortcuts. */ 284 | var objectClass = 'Object', 285 | airRuntimeClass = isCustomContext ? objectClass : 'ScriptBridgingProxyObject', 286 | enviroClass = isCustomContext ? objectClass : 'Environment', 287 | javaClass = (isCustomContext && context.java) ? 'JavaPackage' : getClassOf(context.java), 288 | phantomClass = isCustomContext ? objectClass : 'RuntimeObject'; 289 | 290 | /** Detect Java environments. */ 291 | var java = /\bJava/.test(javaClass) && context.java; 292 | 293 | /** Detect Rhino. */ 294 | var rhino = java && getClassOf(context.environment) == enviroClass; 295 | 296 | /** A character to represent alpha. */ 297 | var alpha = java ? 'a' : '\u03b1'; 298 | 299 | /** A character to represent beta. */ 300 | var beta = java ? 'b' : '\u03b2'; 301 | 302 | /** Browser document object. */ 303 | var doc = context.document || {}; 304 | 305 | /** 306 | * Detect Opera browser (Presto-based). 307 | * http://www.howtocreate.co.uk/operaStuff/operaObject.html 308 | * http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini 309 | */ 310 | var opera = context.operamini || context.opera; 311 | 312 | /** Opera `[[Class]]`. */ 313 | var operaClass = reOpera.test(operaClass = (isCustomContext && opera) ? opera['[[Class]]'] : getClassOf(opera)) 314 | ? operaClass 315 | : (opera = null); 316 | 317 | /*------------------------------------------------------------------------*/ 318 | 319 | /** Temporary variable used over the script's lifetime. */ 320 | var data; 321 | 322 | /** The CPU architecture. */ 323 | var arch = ua; 324 | 325 | /** Platform description array. */ 326 | var description = []; 327 | 328 | /** Platform alpha/beta indicator. */ 329 | var prerelease = null; 330 | 331 | /** A flag to indicate that environment features should be used to resolve the platform. */ 332 | var useFeatures = ua == userAgent; 333 | 334 | /** The browser/environment version. */ 335 | var version = useFeatures && opera && typeof opera.version == 'function' && opera.version(); 336 | 337 | /** A flag to indicate if the OS ends with "/ Version" */ 338 | var isSpecialCasedOS; 339 | 340 | /* Detectable layout engines (order is important). */ 341 | var layout = getLayout([ 342 | { 'label': 'EdgeHTML', 'pattern': '(?:Edge|EdgA|EdgiOS)' }, 343 | 'Trident', 344 | { 'label': 'WebKit', 'pattern': 'AppleWebKit' }, 345 | 'iCab', 346 | 'Presto', 347 | 'NetFront', 348 | 'Tasman', 349 | 'KHTML', 350 | 'Gecko' 351 | ]); 352 | 353 | /* Detectable browser names (order is important). */ 354 | var name = getName([ 355 | 'Adobe AIR', 356 | 'Arora', 357 | 'Avant Browser', 358 | 'Breach', 359 | 'Camino', 360 | 'Electron', 361 | 'Epiphany', 362 | 'Fennec', 363 | 'Flock', 364 | 'Galeon', 365 | 'GreenBrowser', 366 | 'iCab', 367 | 'Iceweasel', 368 | 'K-Meleon', 369 | 'Konqueror', 370 | 'Lunascape', 371 | 'Maxthon', 372 | { 'label': 'Microsoft Edge', 'pattern': '(?:Edge|EdgA|EdgiOS)' }, 373 | 'Midori', 374 | 'Nook Browser', 375 | 'PaleMoon', 376 | 'PhantomJS', 377 | 'Raven', 378 | 'Rekonq', 379 | 'RockMelt', 380 | { 'label': 'Samsung Internet', 'pattern': 'SamsungBrowser' }, 381 | 'SeaMonkey', 382 | { 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' }, 383 | 'Sleipnir', 384 | 'SlimBrowser', 385 | { 'label': 'SRWare Iron', 'pattern': 'Iron' }, 386 | 'Sunrise', 387 | 'Swiftfox', 388 | 'Waterfox', 389 | 'WebPositive', 390 | 'Opera Mini', 391 | { 'label': 'Opera Mini', 'pattern': 'OPiOS' }, 392 | 'Opera', 393 | { 'label': 'Opera', 'pattern': 'OPR' }, 394 | 'Chrome', 395 | { 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' }, 396 | { 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' }, 397 | { 'label': 'Firefox for iOS', 'pattern': 'FxiOS' }, 398 | { 'label': 'IE', 'pattern': 'IEMobile' }, 399 | { 'label': 'IE', 'pattern': 'MSIE' }, 400 | 'Safari' 401 | ]); 402 | 403 | /* Detectable products (order is important). */ 404 | var product = getProduct([ 405 | { 'label': 'BlackBerry', 'pattern': 'BB10' }, 406 | 'BlackBerry', 407 | { 'label': 'Galaxy S', 'pattern': 'GT-I9000' }, 408 | { 'label': 'Galaxy S2', 'pattern': 'GT-I9100' }, 409 | { 'label': 'Galaxy S3', 'pattern': 'GT-I9300' }, 410 | { 'label': 'Galaxy S4', 'pattern': 'GT-I9500' }, 411 | { 'label': 'Galaxy S5', 'pattern': 'SM-G900' }, 412 | { 'label': 'Galaxy S6', 'pattern': 'SM-G920' }, 413 | { 'label': 'Galaxy S6 Edge', 'pattern': 'SM-G925' }, 414 | { 'label': 'Galaxy S7', 'pattern': 'SM-G930' }, 415 | { 'label': 'Galaxy S7 Edge', 'pattern': 'SM-G935' }, 416 | 'Google TV', 417 | 'Lumia', 418 | 'iPad', 419 | 'iPod', 420 | 'iPhone', 421 | 'Kindle', 422 | { 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' }, 423 | 'Nexus', 424 | 'Nook', 425 | 'PlayBook', 426 | 'PlayStation Vita', 427 | 'PlayStation', 428 | 'TouchPad', 429 | 'Transformer', 430 | { 'label': 'Wii U', 'pattern': 'WiiU' }, 431 | 'Wii', 432 | 'Xbox One', 433 | { 'label': 'Xbox 360', 'pattern': 'Xbox' }, 434 | 'Xoom' 435 | ]); 436 | 437 | /* Detectable manufacturers. */ 438 | var manufacturer = getManufacturer({ 439 | 'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 }, 440 | 'Archos': {}, 441 | 'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 }, 442 | 'Asus': { 'Transformer': 1 }, 443 | 'Barnes & Noble': { 'Nook': 1 }, 444 | 'BlackBerry': { 'PlayBook': 1 }, 445 | 'Google': { 'Google TV': 1, 'Nexus': 1 }, 446 | 'HP': { 'TouchPad': 1 }, 447 | 'HTC': {}, 448 | 'LG': {}, 449 | 'Microsoft': { 'Xbox': 1, 'Xbox One': 1 }, 450 | 'Motorola': { 'Xoom': 1 }, 451 | 'Nintendo': { 'Wii U': 1, 'Wii': 1 }, 452 | 'Nokia': { 'Lumia': 1 }, 453 | 'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1, 'Galaxy S3': 1, 'Galaxy S4': 1 }, 454 | 'Sony': { 'PlayStation': 1, 'PlayStation Vita': 1 } 455 | }); 456 | 457 | /* Detectable operating systems (order is important). */ 458 | var os = getOS([ 459 | 'Windows Phone', 460 | 'Android', 461 | 'CentOS', 462 | { 'label': 'Chrome OS', 'pattern': 'CrOS' }, 463 | 'Debian', 464 | 'Fedora', 465 | 'FreeBSD', 466 | 'Gentoo', 467 | 'Haiku', 468 | 'Kubuntu', 469 | 'Linux Mint', 470 | 'OpenBSD', 471 | 'Red Hat', 472 | 'SuSE', 473 | 'Ubuntu', 474 | 'Xubuntu', 475 | 'Cygwin', 476 | 'Symbian OS', 477 | 'hpwOS', 478 | 'webOS ', 479 | 'webOS', 480 | 'Tablet OS', 481 | 'Tizen', 482 | 'Linux', 483 | 'Mac OS X', 484 | 'Macintosh', 485 | 'Mac', 486 | 'Windows 98;', 487 | 'Windows ' 488 | ]); 489 | 490 | /*------------------------------------------------------------------------*/ 491 | 492 | /** 493 | * Picks the layout engine from an array of guesses. 494 | * 495 | * @private 496 | * @param {Array} guesses An array of guesses. 497 | * @returns {null|string} The detected layout engine. 498 | */ 499 | function getLayout(guesses) { 500 | return reduce(guesses, function(result, guess) { 501 | return result || RegExp('\\b' + ( 502 | guess.pattern || qualify(guess) 503 | ) + '\\b', 'i').exec(ua) && (guess.label || guess); 504 | }); 505 | } 506 | 507 | /** 508 | * Picks the manufacturer from an array of guesses. 509 | * 510 | * @private 511 | * @param {Array} guesses An object of guesses. 512 | * @returns {null|string} The detected manufacturer. 513 | */ 514 | function getManufacturer(guesses) { 515 | return reduce(guesses, function(result, value, key) { 516 | // Lookup the manufacturer by product or scan the UA for the manufacturer. 517 | return result || ( 518 | value[product] || 519 | value[/^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] || 520 | RegExp('\\b' + qualify(key) + '(?:\\b|\\w*\\d)', 'i').exec(ua) 521 | ) && key; 522 | }); 523 | } 524 | 525 | /** 526 | * Picks the browser name from an array of guesses. 527 | * 528 | * @private 529 | * @param {Array} guesses An array of guesses. 530 | * @returns {null|string} The detected browser name. 531 | */ 532 | function getName(guesses) { 533 | return reduce(guesses, function(result, guess) { 534 | return result || RegExp('\\b' + ( 535 | guess.pattern || qualify(guess) 536 | ) + '\\b', 'i').exec(ua) && (guess.label || guess); 537 | }); 538 | } 539 | 540 | /** 541 | * Picks the OS name from an array of guesses. 542 | * 543 | * @private 544 | * @param {Array} guesses An array of guesses. 545 | * @returns {null|string} The detected OS name. 546 | */ 547 | function getOS(guesses) { 548 | return reduce(guesses, function(result, guess) { 549 | var pattern = guess.pattern || qualify(guess); 550 | if (!result && (result = 551 | RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua) 552 | )) { 553 | result = cleanupOS(result, pattern, guess.label || guess); 554 | } 555 | return result; 556 | }); 557 | } 558 | 559 | /** 560 | * Picks the product name from an array of guesses. 561 | * 562 | * @private 563 | * @param {Array} guesses An array of guesses. 564 | * @returns {null|string} The detected product name. 565 | */ 566 | function getProduct(guesses) { 567 | return reduce(guesses, function(result, guess) { 568 | var pattern = guess.pattern || qualify(guess); 569 | if (!result && (result = 570 | RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) || 571 | RegExp('\\b' + pattern + ' *\\w+-[\\w]*', 'i').exec(ua) || 572 | RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua) 573 | )) { 574 | // Split by forward slash and append product version if needed. 575 | if ((result = String((guess.label && !RegExp(pattern, 'i').test(guess.label)) ? guess.label : result).split('/'))[1] && !/[\d.]+/.test(result[0])) { 576 | result[0] += ' ' + result[1]; 577 | } 578 | // Correct character case and cleanup string. 579 | guess = guess.label || guess; 580 | result = format(result[0] 581 | .replace(RegExp(pattern, 'i'), guess) 582 | .replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ') 583 | .replace(RegExp('(' + guess + ')[-_.]?(\\w)', 'i'), '$1 $2')); 584 | } 585 | return result; 586 | }); 587 | } 588 | 589 | /** 590 | * Resolves the version using an array of UA patterns. 591 | * 592 | * @private 593 | * @param {Array} patterns An array of UA patterns. 594 | * @returns {null|string} The detected version. 595 | */ 596 | function getVersion(patterns) { 597 | return reduce(patterns, function(result, pattern) { 598 | return result || (RegExp(pattern + 599 | '(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null; 600 | }); 601 | } 602 | 603 | /** 604 | * Returns `platform.description` when the platform object is coerced to a string. 605 | * 606 | * @name toString 607 | * @memberOf platform 608 | * @returns {string} Returns `platform.description` if available, else an empty string. 609 | */ 610 | function toStringPlatform() { 611 | return this.description || ''; 612 | } 613 | 614 | /*------------------------------------------------------------------------*/ 615 | 616 | // Convert layout to an array so we can add extra details. 617 | layout && (layout = [layout]); 618 | 619 | // Detect product names that contain their manufacturer's name. 620 | if (manufacturer && !product) { 621 | product = getProduct([manufacturer]); 622 | } 623 | // Clean up Google TV. 624 | if ((data = /\bGoogle TV\b/.exec(product))) { 625 | product = data[0]; 626 | } 627 | // Detect simulators. 628 | if (/\bSimulator\b/i.test(ua)) { 629 | product = (product ? product + ' ' : '') + 'Simulator'; 630 | } 631 | // Detect Opera Mini 8+ running in Turbo/Uncompressed mode on iOS. 632 | if (name == 'Opera Mini' && /\bOPiOS\b/.test(ua)) { 633 | description.push('running in Turbo/Uncompressed mode'); 634 | } 635 | // Detect IE Mobile 11. 636 | if (name == 'IE' && /\blike iPhone OS\b/.test(ua)) { 637 | data = parse(ua.replace(/like iPhone OS/, '')); 638 | manufacturer = data.manufacturer; 639 | product = data.product; 640 | } 641 | // Detect iOS. 642 | else if (/^iP/.test(product)) { 643 | name || (name = 'Safari'); 644 | os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua)) 645 | ? ' ' + data[1].replace(/_/g, '.') 646 | : ''); 647 | } 648 | // Detect Kubuntu. 649 | else if (name == 'Konqueror' && !/buntu/i.test(os)) { 650 | os = 'Kubuntu'; 651 | } 652 | // Detect Android browsers. 653 | else if ((manufacturer && manufacturer != 'Google' && 654 | ((/Chrome/.test(name) && !/\bMobile Safari\b/i.test(ua)) || /\bVita\b/.test(product))) || 655 | (/\bAndroid\b/.test(os) && /^Chrome/.test(name) && /\bVersion\//i.test(ua))) { 656 | name = 'Android Browser'; 657 | os = /\bAndroid\b/.test(os) ? os : 'Android'; 658 | } 659 | // Detect Silk desktop/accelerated modes. 660 | else if (name == 'Silk') { 661 | if (!/\bMobi/i.test(ua)) { 662 | os = 'Android'; 663 | description.unshift('desktop mode'); 664 | } 665 | if (/Accelerated *= *true/i.test(ua)) { 666 | description.unshift('accelerated'); 667 | } 668 | } 669 | // Detect PaleMoon identifying as Firefox. 670 | else if (name == 'PaleMoon' && (data = /\bFirefox\/([\d.]+)\b/.exec(ua))) { 671 | description.push('identifying as Firefox ' + data[1]); 672 | } 673 | // Detect Firefox OS and products running Firefox. 674 | else if (name == 'Firefox' && (data = /\b(Mobile|Tablet|TV)\b/i.exec(ua))) { 675 | os || (os = 'Firefox OS'); 676 | product || (product = data[1]); 677 | } 678 | // Detect false positives for Firefox/Safari. 679 | else if (!name || (data = !/\bMinefield\b/i.test(ua) && /\b(?:Firefox|Safari)\b/.exec(name))) { 680 | // Escape the `/` for Firefox 1. 681 | if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) { 682 | // Clear name of false positives. 683 | name = null; 684 | } 685 | // Reassign a generic name. 686 | if ((data = product || manufacturer || os) && 687 | (product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) { 688 | name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser'; 689 | } 690 | } 691 | // Add Chrome version to description for Electron. 692 | else if (name == 'Electron' && (data = (/\bChrome\/([\d.]+)\b/.exec(ua) || 0)[1])) { 693 | description.push('Chromium ' + data); 694 | } 695 | // Detect non-Opera (Presto-based) versions (order is important). 696 | if (!version) { 697 | version = getVersion([ 698 | '(?:Cloud9|CriOS|CrMo|Edge|EdgA|EdgiOS|FxiOS|IEMobile|Iron|Opera ?Mini|OPiOS|OPR|Raven|SamsungBrowser|Silk(?!/[\\d.]+$))', 699 | 'Version', 700 | qualify(name), 701 | '(?:Firefox|Minefield|NetFront)' 702 | ]); 703 | } 704 | // Detect stubborn layout engines. 705 | if ((data = 706 | layout == 'iCab' && parseFloat(version) > 3 && 'WebKit' || 707 | /\bOpera\b/.test(name) && (/\bOPR\b/.test(ua) ? 'Blink' : 'Presto') || 708 | /\b(?:Midori|Nook|Safari)\b/i.test(ua) && !/^(?:Trident|EdgeHTML)$/.test(layout) && 'WebKit' || 709 | !layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident') || 710 | layout == 'WebKit' && /\bPlayStation\b(?! Vita\b)/i.test(name) && 'NetFront' 711 | )) { 712 | layout = [data]; 713 | } 714 | // Detect Windows Phone 7 desktop mode. 715 | if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) { 716 | name += ' Mobile'; 717 | os = 'Windows Phone ' + (/\+$/.test(data) ? data : data + '.x'); 718 | description.unshift('desktop mode'); 719 | } 720 | // Detect Windows Phone 8.x desktop mode. 721 | else if (/\bWPDesktop\b/i.test(ua)) { 722 | name = 'IE Mobile'; 723 | os = 'Windows Phone 8.x'; 724 | description.unshift('desktop mode'); 725 | version || (version = (/\brv:([\d.]+)/.exec(ua) || 0)[1]); 726 | } 727 | // Detect IE 11 identifying as other browsers. 728 | else if (name != 'IE' && layout == 'Trident' && (data = /\brv:([\d.]+)/.exec(ua))) { 729 | if (name) { 730 | description.push('identifying as ' + name + (version ? ' ' + version : '')); 731 | } 732 | name = 'IE'; 733 | version = data[1]; 734 | } 735 | // Leverage environment features. 736 | if (useFeatures) { 737 | // Detect server-side environments. 738 | // Rhino has a global function while others have a global object. 739 | if (isHostType(context, 'global')) { 740 | if (java) { 741 | data = java.lang.System; 742 | arch = data.getProperty('os.arch'); 743 | os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version'); 744 | } 745 | if (rhino) { 746 | try { 747 | version = context.require('ringo/engine').version.join('.'); 748 | name = 'RingoJS'; 749 | } catch(e) { 750 | if ((data = context.system) && data.global.system == context.system) { 751 | name = 'Narwhal'; 752 | os || (os = data[0].os || null); 753 | } 754 | } 755 | if (!name) { 756 | name = 'Rhino'; 757 | } 758 | } 759 | else if ( 760 | typeof context.process == 'object' && !context.process.browser && 761 | (data = context.process) 762 | ) { 763 | if (typeof data.versions == 'object') { 764 | if (typeof data.versions.electron == 'string') { 765 | description.push('Node ' + data.versions.node); 766 | name = 'Electron'; 767 | version = data.versions.electron; 768 | } else if (typeof data.versions.nw == 'string') { 769 | description.push('Chromium ' + version, 'Node ' + data.versions.node); 770 | name = 'NW.js'; 771 | version = data.versions.nw; 772 | } 773 | } 774 | if (!name) { 775 | name = 'Node.js'; 776 | arch = data.arch; 777 | os = data.platform; 778 | version = /[\d.]+/.exec(data.version); 779 | version = version ? version[0] : null; 780 | } 781 | } 782 | } 783 | // Detect Adobe AIR. 784 | else if (getClassOf((data = context.runtime)) == airRuntimeClass) { 785 | name = 'Adobe AIR'; 786 | os = data.flash.system.Capabilities.os; 787 | } 788 | // Detect PhantomJS. 789 | else if (getClassOf((data = context.phantom)) == phantomClass) { 790 | name = 'PhantomJS'; 791 | version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch); 792 | } 793 | // Detect IE compatibility modes. 794 | else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) { 795 | // We're in compatibility mode when the Trident version + 4 doesn't 796 | // equal the document mode. 797 | version = [version, doc.documentMode]; 798 | if ((data = +data[1] + 4) != version[1]) { 799 | description.push('IE ' + version[1] + ' mode'); 800 | layout && (layout[1] = ''); 801 | version[1] = data; 802 | } 803 | version = name == 'IE' ? String(version[1].toFixed(1)) : version[0]; 804 | } 805 | // Detect IE 11 masking as other browsers. 806 | else if (typeof doc.documentMode == 'number' && /^(?:Chrome|Firefox)\b/.test(name)) { 807 | description.push('masking as ' + name + ' ' + version); 808 | name = 'IE'; 809 | version = '11.0'; 810 | layout = ['Trident']; 811 | os = 'Windows'; 812 | } 813 | os = os && format(os); 814 | } 815 | // Detect prerelease phases. 816 | if (version && (data = 817 | /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) || 818 | /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) || 819 | /\bMinefield\b/i.test(ua) && 'a' 820 | )) { 821 | prerelease = /b/i.test(data) ? 'beta' : 'alpha'; 822 | version = version.replace(RegExp(data + '\\+?$'), '') + 823 | (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || ''); 824 | } 825 | // Detect Firefox Mobile. 826 | if (name == 'Fennec' || name == 'Firefox' && /\b(?:Android|Firefox OS)\b/.test(os)) { 827 | name = 'Firefox Mobile'; 828 | } 829 | // Obscure Maxthon's unreliable version. 830 | else if (name == 'Maxthon' && version) { 831 | version = version.replace(/\.[\d.]+/, '.x'); 832 | } 833 | // Detect Xbox 360 and Xbox One. 834 | else if (/\bXbox\b/i.test(product)) { 835 | if (product == 'Xbox 360') { 836 | os = null; 837 | } 838 | if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) { 839 | description.unshift('mobile mode'); 840 | } 841 | } 842 | // Add mobile postfix. 843 | else if ((/^(?:Chrome|IE|Opera)$/.test(name) || name && !product && !/Browser|Mobi/.test(name)) && 844 | (os == 'Windows CE' || /Mobi/i.test(ua))) { 845 | name += ' Mobile'; 846 | } 847 | // Detect IE platform preview. 848 | else if (name == 'IE' && useFeatures) { 849 | try { 850 | if (context.external === null) { 851 | description.unshift('platform preview'); 852 | } 853 | } catch(e) { 854 | description.unshift('embedded'); 855 | } 856 | } 857 | // Detect BlackBerry OS version. 858 | // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp 859 | else if ((/\bBlackBerry\b/.test(product) || /\bBB10\b/.test(ua)) && (data = 860 | (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] || 861 | version 862 | )) { 863 | data = [data, /BB10/.test(ua)]; 864 | os = (data[1] ? (product = null, manufacturer = 'BlackBerry') : 'Device Software') + ' ' + data[0]; 865 | version = null; 866 | } 867 | // Detect Opera identifying/masking itself as another browser. 868 | // http://www.opera.com/support/kb/view/843/ 869 | else if (this != forOwn && product != 'Wii' && ( 870 | (useFeatures && opera) || 871 | (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) || 872 | (name == 'Firefox' && /\bOS X (?:\d+\.){2,}/.test(os)) || 873 | (name == 'IE' && ( 874 | (os && !/^Win/.test(os) && version > 5.5) || 875 | /\bWindows XP\b/.test(os) && version > 8 || 876 | version == 8 && !/\bTrident\b/.test(ua) 877 | )) 878 | ) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) { 879 | // When "identifying", the UA contains both Opera and the other browser's name. 880 | data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : ''); 881 | if (reOpera.test(name)) { 882 | if (/\bIE\b/.test(data) && os == 'Mac OS') { 883 | os = null; 884 | } 885 | data = 'identify' + data; 886 | } 887 | // When "masking", the UA contains only the other browser's name. 888 | else { 889 | data = 'mask' + data; 890 | if (operaClass) { 891 | name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2')); 892 | } else { 893 | name = 'Opera'; 894 | } 895 | if (/\bIE\b/.test(data)) { 896 | os = null; 897 | } 898 | if (!useFeatures) { 899 | version = null; 900 | } 901 | } 902 | layout = ['Presto']; 903 | description.push(data); 904 | } 905 | // Detect WebKit Nightly and approximate Chrome/Safari versions. 906 | if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) { 907 | // Correct build number for numeric comparison. 908 | // (e.g. "532.5" becomes "532.05") 909 | data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data]; 910 | // Nightly builds are postfixed with a "+". 911 | if (name == 'Safari' && data[1].slice(-1) == '+') { 912 | name = 'WebKit Nightly'; 913 | prerelease = 'alpha'; 914 | version = data[1].slice(0, -1); 915 | } 916 | // Clear incorrect browser versions. 917 | else if (version == data[1] || 918 | version == (data[2] = (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1])) { 919 | version = null; 920 | } 921 | // Use the full Chrome version when available. 922 | data[1] = (/\bChrome\/([\d.]+)/i.exec(ua) || 0)[1]; 923 | // Detect Blink layout engine. 924 | if (data[0] == 537.36 && data[2] == 537.36 && parseFloat(data[1]) >= 28 && layout == 'WebKit') { 925 | layout = ['Blink']; 926 | } 927 | // Detect JavaScriptCore. 928 | // http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi 929 | if (!useFeatures || (!likeChrome && !data[1])) { 930 | layout && (layout[1] = 'like Safari'); 931 | data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : data < 537 ? 6 : data < 538 ? 7 : data < 601 ? 8 : '8'); 932 | } else { 933 | layout && (layout[1] = 'like Chrome'); 934 | data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 536.05 ? 18 : data < 536.10 ? 19 : data < 537.01 ? 20 : data < 537.11 ? '21+' : data < 537.13 ? 23 : data < 537.18 ? 24 : data < 537.24 ? 25 : data < 537.36 ? 26 : layout != 'Blink' ? '27' : '28'); 935 | } 936 | // Add the postfix of ".x" or "+" for approximate versions. 937 | layout && (layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+')); 938 | // Obscure version for some Safari 1-2 releases. 939 | if (name == 'Safari' && (!version || parseInt(version) > 45)) { 940 | version = data; 941 | } 942 | } 943 | // Detect Opera desktop modes. 944 | if (name == 'Opera' && (data = /\bzbov|zvav$/.exec(os))) { 945 | name += ' '; 946 | description.unshift('desktop mode'); 947 | if (data == 'zvav') { 948 | name += 'Mini'; 949 | version = null; 950 | } else { 951 | name += 'Mobile'; 952 | } 953 | os = os.replace(RegExp(' *' + data + '$'), ''); 954 | } 955 | // Detect Chrome desktop mode. 956 | else if (name == 'Safari' && /\bChrome\b/.exec(layout && layout[1])) { 957 | description.unshift('desktop mode'); 958 | name = 'Chrome Mobile'; 959 | version = null; 960 | 961 | if (/\bOS X\b/.test(os)) { 962 | manufacturer = 'Apple'; 963 | os = 'iOS 4.3+'; 964 | } else { 965 | os = null; 966 | } 967 | } 968 | // Strip incorrect OS versions. 969 | if (version && version.indexOf((data = /[\d.]+$/.exec(os))) == 0 && 970 | ua.indexOf('/' + data + '-') > -1) { 971 | os = trim(os.replace(data, '')); 972 | } 973 | // Add layout engine. 974 | if (layout && !/\b(?:Avant|Nook)\b/.test(name) && ( 975 | /Browser|Lunascape|Maxthon/.test(name) || 976 | name != 'Safari' && /^iOS/.test(os) && /\bSafari\b/.test(layout[1]) || 977 | /^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Samsung Internet|Sleipnir|Web)/.test(name) && layout[1])) { 978 | // Don't add layout details to description if they are falsey. 979 | (data = layout[layout.length - 1]) && description.push(data); 980 | } 981 | // Combine contextual information. 982 | if (description.length) { 983 | description = ['(' + description.join('; ') + ')']; 984 | } 985 | // Append manufacturer to description. 986 | if (manufacturer && product && product.indexOf(manufacturer) < 0) { 987 | description.push('on ' + manufacturer); 988 | } 989 | // Append product to description. 990 | if (product) { 991 | description.push((/^on /.test(description[description.length - 1]) ? '' : 'on ') + product); 992 | } 993 | // Parse the OS into an object. 994 | if (os) { 995 | data = / ([\d.+]+)$/.exec(os); 996 | isSpecialCasedOS = data && os.charAt(os.length - data[0].length - 1) == '/'; 997 | os = { 998 | 'architecture': 32, 999 | 'family': (data && !isSpecialCasedOS) ? os.replace(data[0], '') : os, 1000 | 'version': data ? data[1] : null, 1001 | 'toString': function() { 1002 | var version = this.version; 1003 | return this.family + ((version && !isSpecialCasedOS) ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : ''); 1004 | } 1005 | }; 1006 | } 1007 | // Add browser/OS architecture. 1008 | if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) { 1009 | if (os) { 1010 | os.architecture = 64; 1011 | os.family = os.family.replace(RegExp(' *' + data), ''); 1012 | } 1013 | if ( 1014 | name && (/\bWOW64\b/i.test(ua) || 1015 | (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/\bWin64; x64\b/i.test(ua))) 1016 | ) { 1017 | description.unshift('32-bit'); 1018 | } 1019 | } 1020 | // Chrome 39 and above on OS X is always 64-bit. 1021 | else if ( 1022 | os && /^OS X/.test(os.family) && 1023 | name == 'Chrome' && parseFloat(version) >= 39 1024 | ) { 1025 | os.architecture = 64; 1026 | } 1027 | 1028 | ua || (ua = null); 1029 | 1030 | /*------------------------------------------------------------------------*/ 1031 | 1032 | /** 1033 | * The platform object. 1034 | * 1035 | * @name platform 1036 | * @type Object 1037 | */ 1038 | var platform = {}; 1039 | 1040 | /** 1041 | * The platform description. 1042 | * 1043 | * @memberOf platform 1044 | * @type string|null 1045 | */ 1046 | platform.description = ua; 1047 | 1048 | /** 1049 | * The name of the browser's layout engine. 1050 | * 1051 | * The list of common layout engines include: 1052 | * "Blink", "EdgeHTML", "Gecko", "Trident" and "WebKit" 1053 | * 1054 | * @memberOf platform 1055 | * @type string|null 1056 | */ 1057 | platform.layout = layout && layout[0]; 1058 | 1059 | /** 1060 | * The name of the product's manufacturer. 1061 | * 1062 | * The list of manufacturers include: 1063 | * "Apple", "Archos", "Amazon", "Asus", "Barnes & Noble", "BlackBerry", 1064 | * "Google", "HP", "HTC", "LG", "Microsoft", "Motorola", "Nintendo", 1065 | * "Nokia", "Samsung" and "Sony" 1066 | * 1067 | * @memberOf platform 1068 | * @type string|null 1069 | */ 1070 | platform.manufacturer = manufacturer; 1071 | 1072 | /** 1073 | * The name of the browser/environment. 1074 | * 1075 | * The list of common browser names include: 1076 | * "Chrome", "Electron", "Firefox", "Firefox for iOS", "IE", 1077 | * "Microsoft Edge", "PhantomJS", "Safari", "SeaMonkey", "Silk", 1078 | * "Opera Mini" and "Opera" 1079 | * 1080 | * Mobile versions of some browsers have "Mobile" appended to their name: 1081 | * eg. "Chrome Mobile", "Firefox Mobile", "IE Mobile" and "Opera Mobile" 1082 | * 1083 | * @memberOf platform 1084 | * @type string|null 1085 | */ 1086 | platform.name = name; 1087 | 1088 | /** 1089 | * The alpha/beta release indicator. 1090 | * 1091 | * @memberOf platform 1092 | * @type string|null 1093 | */ 1094 | platform.prerelease = prerelease; 1095 | 1096 | /** 1097 | * The name of the product hosting the browser. 1098 | * 1099 | * The list of common products include: 1100 | * 1101 | * "BlackBerry", "Galaxy S4", "Lumia", "iPad", "iPod", "iPhone", "Kindle", 1102 | * "Kindle Fire", "Nexus", "Nook", "PlayBook", "TouchPad" and "Transformer" 1103 | * 1104 | * @memberOf platform 1105 | * @type string|null 1106 | */ 1107 | platform.product = product; 1108 | 1109 | /** 1110 | * The browser's user agent string. 1111 | * 1112 | * @memberOf platform 1113 | * @type string|null 1114 | */ 1115 | platform.ua = ua; 1116 | 1117 | /** 1118 | * The browser/environment version. 1119 | * 1120 | * @memberOf platform 1121 | * @type string|null 1122 | */ 1123 | platform.version = name && version; 1124 | 1125 | /** 1126 | * The name of the operating system. 1127 | * 1128 | * @memberOf platform 1129 | * @type Object 1130 | */ 1131 | platform.os = os || { 1132 | 1133 | /** 1134 | * The CPU architecture the OS is built for. 1135 | * 1136 | * @memberOf platform.os 1137 | * @type number|null 1138 | */ 1139 | 'architecture': null, 1140 | 1141 | /** 1142 | * The family of the OS. 1143 | * 1144 | * Common values include: 1145 | * "Windows", "Windows Server 2008 R2 / 7", "Windows Server 2008 / Vista", 1146 | * "Windows XP", "OS X", "Ubuntu", "Debian", "Fedora", "Red Hat", "SuSE", 1147 | * "Android", "iOS" and "Windows Phone" 1148 | * 1149 | * @memberOf platform.os 1150 | * @type string|null 1151 | */ 1152 | 'family': null, 1153 | 1154 | /** 1155 | * The version of the OS. 1156 | * 1157 | * @memberOf platform.os 1158 | * @type string|null 1159 | */ 1160 | 'version': null, 1161 | 1162 | /** 1163 | * Returns the OS string. 1164 | * 1165 | * @memberOf platform.os 1166 | * @returns {string} The OS string. 1167 | */ 1168 | 'toString': function() { return 'null'; } 1169 | }; 1170 | 1171 | platform.parse = parse; 1172 | platform.toString = toStringPlatform; 1173 | 1174 | if (platform.version) { 1175 | description.unshift(version); 1176 | } 1177 | if (platform.name) { 1178 | description.unshift(name); 1179 | } 1180 | if (os && name && !(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product))) { 1181 | description.push(product ? '(' + os + ')' : 'on ' + os); 1182 | } 1183 | if (description.length) { 1184 | platform.description = description.join(' '); 1185 | } 1186 | return platform; 1187 | } 1188 | 1189 | /*--------------------------------------------------------------------------*/ 1190 | 1191 | // Export platform. 1192 | var platform = parse(); 1193 | 1194 | // Some AMD build optimizers, like r.js, check for condition patterns like the following: 1195 | if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { 1196 | // Expose platform on the global object to prevent errors when platform is 1197 | // loaded by a script tag in the presence of an AMD loader. 1198 | // See http://requirejs.org/docs/errors.html#mismatch for more details. 1199 | root.platform = platform; 1200 | 1201 | // Define as an anonymous module so platform can be aliased through path mapping. 1202 | define(function() { 1203 | return platform; 1204 | }); 1205 | } 1206 | // Check for `exports` after `define` in case a build optimizer adds an `exports` object. 1207 | else if (freeExports && freeModule) { 1208 | // Export for CommonJS support. 1209 | forOwn(platform, function(value, key) { 1210 | freeExports[key] = value; 1211 | }); 1212 | } 1213 | else { 1214 | // Export to the global object. 1215 | root.platform = platform; 1216 | } 1217 | }.call(this)); 1218 | -------------------------------------------------------------------------------- /js/vendor/sha256.js: -------------------------------------------------------------------------------- 1 | // public domain: https://github.com/geraintluff/sha256 2 | 3 | var sha256 = function sha256(ascii) { 4 | function rightRotate(value, amount) { 5 | return (value>>>amount) | (value<<(32 - amount)); 6 | }; 7 | 8 | var mathPow = Math.pow; 9 | var maxWord = mathPow(2, 32); 10 | var lengthProperty = 'length'; 11 | var i, j; // Used as a counter across the whole file 12 | var result = ''; 13 | 14 | var words = []; 15 | var asciiBitLength = ascii[lengthProperty]*8; 16 | 17 | //* caching results is optional - remove/add slash from front of this line to toggle 18 | // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes 19 | // (we actually calculate the first 64, but extra values are just ignored) 20 | var hash = sha256.h = sha256.h || []; 21 | // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes 22 | var k = sha256.k = sha256.k || []; 23 | var primeCounter = k[lengthProperty]; 24 | /*/ 25 | var hash = [], k = []; 26 | var primeCounter = 0; 27 | //*/ 28 | 29 | var isComposite = {}; 30 | for (var candidate = 2; primeCounter < 64; candidate++) { 31 | if (!isComposite[candidate]) { 32 | for (i = 0; i < 313; i += candidate) { 33 | isComposite[i] = candidate; 34 | } 35 | hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0; 36 | k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0; 37 | } 38 | } 39 | 40 | ascii += '\x80'; // Append '1' bit (plus zero padding) 41 | while (ascii[lengthProperty]%64 - 56) ascii += '\x00'; // More zero padding 42 | for (i = 0; i < ascii[lengthProperty]; i++) { 43 | j = ascii.charCodeAt(i); 44 | if (j>>8) return; // ASCII check: only accept characters in range 0-255 45 | words[i>>2] |= j << ((3 - i)%4)*8; 46 | } 47 | words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0); 48 | words[words[lengthProperty]] = (asciiBitLength) 49 | 50 | // process each chunk 51 | for (j = 0; j < words[lengthProperty];) { 52 | var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration 53 | var oldHash = hash; 54 | // This is now the "working hash", often labelled as variables a...g 55 | // (we have to truncate as well, otherwise extra entries at the end accumulate 56 | hash = hash.slice(0, 8); 57 | 58 | for (i = 0; i < 64; i++) { 59 | var i2 = i + j; 60 | // Expand the message into 64 words 61 | // Used below if 62 | var w15 = w[i - 15], w2 = w[i - 2]; 63 | 64 | // Iterate 65 | var a = hash[0], e = hash[4]; 66 | var temp1 = hash[7] 67 | + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1 68 | + ((e&hash[5])^((~e)&hash[6])) // ch 69 | + k[i] 70 | // Expand the message schedule if needed 71 | + (w[i] = (i < 16) ? w[i] : ( 72 | w[i - 16] 73 | + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) // s0 74 | + w[i - 7] 75 | + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)) // s1 76 | )|0 77 | ); 78 | // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble 79 | var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0 80 | + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2])); // maj 81 | 82 | hash = [(temp1 + temp2)|0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice() 83 | hash[4] = (hash[4] + temp1)|0; 84 | } 85 | 86 | for (i = 0; i < 8; i++) { 87 | hash[i] = (hash[i] + oldHash[i])|0; 88 | } 89 | } 90 | 91 | for (i = 0; i < 8; i++) { 92 | for (j = 3; j + 1; j--) { 93 | var b = (hash[i]>>(j*8))&255; 94 | result += ((b < 16) ? 0 : '') + b.toString(16); 95 | } 96 | } 97 | return result; 98 | }; 99 | -------------------------------------------------------------------------------- /js/vendor/u2f-api.js: -------------------------------------------------------------------------------- 1 | //Copyright 2014-2015 Google Inc. All rights reserved. 2 | 3 | //Use of this source code is governed by a BSD-style 4 | //license that can be found in the LICENSE file or at 5 | //https://developers.google.com/open-source/licenses/bsd 6 | 7 | /** 8 | * @fileoverview The U2F api. 9 | */ 10 | 'use strict'; 11 | 12 | 13 | /** 14 | * Namespace for the U2F api. 15 | * @type {Object} 16 | */ 17 | var u2f = u2f || {}; 18 | 19 | /** 20 | * FIDO U2F Javascript API Version 21 | * @number 22 | */ 23 | var js_api_version; 24 | 25 | /** 26 | * The U2F extension id 27 | * @const {string} 28 | */ 29 | // The Chrome packaged app extension ID. 30 | // Uncomment this if you want to deploy a server instance that uses 31 | // the package Chrome app and does not require installing the U2F Chrome extension. 32 | u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; 33 | // The U2F Chrome extension ID. 34 | // Uncomment this if you want to deploy a server instance that uses 35 | // the U2F Chrome extension to authenticate. 36 | // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; 37 | 38 | 39 | /** 40 | * Message types for messsages to/from the extension 41 | * @const 42 | * @enum {string} 43 | */ 44 | u2f.MessageTypes = { 45 | 'U2F_REGISTER_REQUEST': 'u2f_register_request', 46 | 'U2F_REGISTER_RESPONSE': 'u2f_register_response', 47 | 'U2F_SIGN_REQUEST': 'u2f_sign_request', 48 | 'U2F_SIGN_RESPONSE': 'u2f_sign_response', 49 | 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', 50 | 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' 51 | }; 52 | 53 | 54 | /** 55 | * Response status codes 56 | * @const 57 | * @enum {number} 58 | */ 59 | u2f.ErrorCodes = { 60 | 'OK': 0, 61 | 'OTHER_ERROR': 1, 62 | 'BAD_REQUEST': 2, 63 | 'CONFIGURATION_UNSUPPORTED': 3, 64 | 'DEVICE_INELIGIBLE': 4, 65 | 'TIMEOUT': 5 66 | }; 67 | 68 | 69 | /** 70 | * A message for registration requests 71 | * @typedef {{ 72 | * type: u2f.MessageTypes, 73 | * appId: ?string, 74 | * timeoutSeconds: ?number, 75 | * requestId: ?number 76 | * }} 77 | */ 78 | u2f.U2fRequest; 79 | 80 | 81 | /** 82 | * A message for registration responses 83 | * @typedef {{ 84 | * type: u2f.MessageTypes, 85 | * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), 86 | * requestId: ?number 87 | * }} 88 | */ 89 | u2f.U2fResponse; 90 | 91 | 92 | /** 93 | * An error object for responses 94 | * @typedef {{ 95 | * errorCode: u2f.ErrorCodes, 96 | * errorMessage: ?string 97 | * }} 98 | */ 99 | u2f.Error; 100 | 101 | /** 102 | * Data object for a single sign request. 103 | * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC, USB_INTERNAL}} 104 | */ 105 | u2f.Transport; 106 | 107 | 108 | /** 109 | * Data object for a single sign request. 110 | * @typedef {Array} 111 | */ 112 | u2f.Transports; 113 | 114 | /** 115 | * Data object for a single sign request. 116 | * @typedef {{ 117 | * version: string, 118 | * challenge: string, 119 | * keyHandle: string, 120 | * appId: string 121 | * }} 122 | */ 123 | u2f.SignRequest; 124 | 125 | 126 | /** 127 | * Data object for a sign response. 128 | * @typedef {{ 129 | * keyHandle: string, 130 | * signatureData: string, 131 | * clientData: string 132 | * }} 133 | */ 134 | u2f.SignResponse; 135 | 136 | 137 | /** 138 | * Data object for a registration request. 139 | * @typedef {{ 140 | * version: string, 141 | * challenge: string 142 | * }} 143 | */ 144 | u2f.RegisterRequest; 145 | 146 | 147 | /** 148 | * Data object for a registration response. 149 | * @typedef {{ 150 | * version: string, 151 | * keyHandle: string, 152 | * transports: Transports, 153 | * appId: string 154 | * }} 155 | */ 156 | u2f.RegisterResponse; 157 | 158 | 159 | /** 160 | * Data object for a registered key. 161 | * @typedef {{ 162 | * version: string, 163 | * keyHandle: string, 164 | * transports: ?Transports, 165 | * appId: ?string 166 | * }} 167 | */ 168 | u2f.RegisteredKey; 169 | 170 | 171 | /** 172 | * Data object for a get API register response. 173 | * @typedef {{ 174 | * js_api_version: number 175 | * }} 176 | */ 177 | u2f.GetJsApiVersionResponse; 178 | 179 | 180 | //Low level MessagePort API support 181 | 182 | /** 183 | * Sets up a MessagePort to the U2F extension using the 184 | * available mechanisms. 185 | * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback 186 | */ 187 | u2f.getMessagePort = function(callback) { 188 | if (typeof chrome != 'undefined' && chrome.runtime) { 189 | // The actual message here does not matter, but we need to get a reply 190 | // for the callback to run. Thus, send an empty signature request 191 | // in order to get a failure response. 192 | var msg = { 193 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, 194 | signRequests: [] 195 | }; 196 | chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { 197 | if (!chrome.runtime.lastError) { 198 | // We are on a whitelisted origin and can talk directly 199 | // with the extension. 200 | u2f.getChromeRuntimePort_(callback); 201 | } else { 202 | // chrome.runtime was available, but we couldn't message 203 | // the extension directly, use iframe 204 | u2f.getIframePort_(callback); 205 | } 206 | }); 207 | } else if (u2f.isAndroidChrome_()) { 208 | u2f.getAuthenticatorPort_(callback); 209 | } else if (u2f.isIosChrome_()) { 210 | u2f.getIosPort_(callback); 211 | } else { 212 | // chrome.runtime was not available at all, which is normal 213 | // when this origin doesn't have access to any extensions. 214 | u2f.getIframePort_(callback); 215 | } 216 | }; 217 | 218 | /** 219 | * Detect chrome running on android based on the browser's useragent. 220 | * @private 221 | */ 222 | u2f.isAndroidChrome_ = function() { 223 | var userAgent = navigator.userAgent; 224 | return userAgent.indexOf('Chrome') != -1 && 225 | userAgent.indexOf('Android') != -1; 226 | }; 227 | 228 | /** 229 | * Detect chrome running on iOS based on the browser's platform. 230 | * @private 231 | */ 232 | u2f.isIosChrome_ = function() { 233 | return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; 234 | }; 235 | 236 | /** 237 | * Connects directly to the extension via chrome.runtime.connect. 238 | * @param {function(u2f.WrappedChromeRuntimePort_)} callback 239 | * @private 240 | */ 241 | u2f.getChromeRuntimePort_ = function(callback) { 242 | var port = chrome.runtime.connect(u2f.EXTENSION_ID, 243 | {'includeTlsChannelId': true}); 244 | setTimeout(function() { 245 | callback(new u2f.WrappedChromeRuntimePort_(port)); 246 | }, 0); 247 | }; 248 | 249 | /** 250 | * Return a 'port' abstraction to the Authenticator app. 251 | * @param {function(u2f.WrappedAuthenticatorPort_)} callback 252 | * @private 253 | */ 254 | u2f.getAuthenticatorPort_ = function(callback) { 255 | setTimeout(function() { 256 | callback(new u2f.WrappedAuthenticatorPort_()); 257 | }, 0); 258 | }; 259 | 260 | /** 261 | * Return a 'port' abstraction to the iOS client app. 262 | * @param {function(u2f.WrappedIosPort_)} callback 263 | * @private 264 | */ 265 | u2f.getIosPort_ = function(callback) { 266 | setTimeout(function() { 267 | callback(new u2f.WrappedIosPort_()); 268 | }, 0); 269 | }; 270 | 271 | /** 272 | * A wrapper for chrome.runtime.Port that is compatible with MessagePort. 273 | * @param {Port} port 274 | * @constructor 275 | * @private 276 | */ 277 | u2f.WrappedChromeRuntimePort_ = function(port) { 278 | this.port_ = port; 279 | }; 280 | 281 | /** 282 | * Format and return a sign request compliant with the JS API version supported by the extension. 283 | * @param {Array} signRequests 284 | * @param {number} timeoutSeconds 285 | * @param {number} reqId 286 | * @return {Object} 287 | */ 288 | u2f.formatSignRequest_ = 289 | function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { 290 | if (js_api_version === undefined || js_api_version < 1.1) { 291 | // Adapt request to the 1.0 JS API 292 | var signRequests = []; 293 | for (var i = 0; i < registeredKeys.length; i++) { 294 | signRequests[i] = { 295 | version: registeredKeys[i].version, 296 | challenge: challenge, 297 | keyHandle: registeredKeys[i].keyHandle, 298 | appId: appId 299 | }; 300 | } 301 | return { 302 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, 303 | signRequests: signRequests, 304 | timeoutSeconds: timeoutSeconds, 305 | requestId: reqId 306 | }; 307 | } 308 | // JS 1.1 API 309 | return { 310 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, 311 | appId: appId, 312 | challenge: challenge, 313 | registeredKeys: registeredKeys, 314 | timeoutSeconds: timeoutSeconds, 315 | requestId: reqId 316 | }; 317 | }; 318 | 319 | /** 320 | * Format and return a register request compliant with the JS API version supported by the extension.. 321 | * @param {Array} signRequests 322 | * @param {Array} signRequests 323 | * @param {number} timeoutSeconds 324 | * @param {number} reqId 325 | * @return {Object} 326 | */ 327 | u2f.formatRegisterRequest_ = 328 | function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { 329 | if (js_api_version === undefined || js_api_version < 1.1) { 330 | // Adapt request to the 1.0 JS API 331 | for (var i = 0; i < registerRequests.length; i++) { 332 | registerRequests[i].appId = appId; 333 | } 334 | var signRequests = []; 335 | for (var i = 0; i < registeredKeys.length; i++) { 336 | signRequests[i] = { 337 | version: registeredKeys[i].version, 338 | challenge: registerRequests[0], 339 | keyHandle: registeredKeys[i].keyHandle, 340 | appId: appId 341 | }; 342 | } 343 | return { 344 | type: u2f.MessageTypes.U2F_REGISTER_REQUEST, 345 | signRequests: signRequests, 346 | registerRequests: registerRequests, 347 | timeoutSeconds: timeoutSeconds, 348 | requestId: reqId 349 | }; 350 | } 351 | // JS 1.1 API 352 | return { 353 | type: u2f.MessageTypes.U2F_REGISTER_REQUEST, 354 | appId: appId, 355 | registerRequests: registerRequests, 356 | registeredKeys: registeredKeys, 357 | timeoutSeconds: timeoutSeconds, 358 | requestId: reqId 359 | }; 360 | }; 361 | 362 | 363 | /** 364 | * Posts a message on the underlying channel. 365 | * @param {Object} message 366 | */ 367 | u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { 368 | this.port_.postMessage(message); 369 | }; 370 | 371 | 372 | /** 373 | * Emulates the HTML 5 addEventListener interface. Works only for the 374 | * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. 375 | * @param {string} eventName 376 | * @param {function({data: Object})} handler 377 | */ 378 | u2f.WrappedChromeRuntimePort_.prototype.addEventListener = 379 | function(eventName, handler) { 380 | var name = eventName.toLowerCase(); 381 | if (name == 'message' || name == 'onmessage') { 382 | this.port_.onMessage.addListener(function(message) { 383 | // Emulate a minimal MessageEvent object 384 | handler({'data': message}); 385 | }); 386 | } else { 387 | console.error('WrappedChromeRuntimePort only supports onMessage'); 388 | } 389 | }; 390 | 391 | /** 392 | * Wrap the Authenticator app with a MessagePort interface. 393 | * @constructor 394 | * @private 395 | */ 396 | u2f.WrappedAuthenticatorPort_ = function() { 397 | this.requestId_ = -1; 398 | this.requestObject_ = null; 399 | } 400 | 401 | /** 402 | * Launch the Authenticator intent. 403 | * @param {Object} message 404 | */ 405 | u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { 406 | var intentUrl = 407 | u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + 408 | ';S.request=' + encodeURIComponent(JSON.stringify(message)) + 409 | ';end'; 410 | document.location = intentUrl; 411 | }; 412 | 413 | /** 414 | * Tells what type of port this is. 415 | * @return {String} port type 416 | */ 417 | u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { 418 | return "WrappedAuthenticatorPort_"; 419 | }; 420 | 421 | 422 | /** 423 | * Emulates the HTML 5 addEventListener interface. 424 | * @param {string} eventName 425 | * @param {function({data: Object})} handler 426 | */ 427 | u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { 428 | var name = eventName.toLowerCase(); 429 | if (name == 'message') { 430 | var self = this; 431 | /* Register a callback to that executes when 432 | * chrome injects the response. */ 433 | window.addEventListener( 434 | 'message', self.onRequestUpdate_.bind(self, handler), false); 435 | } else { 436 | console.error('WrappedAuthenticatorPort only supports message'); 437 | } 438 | }; 439 | 440 | /** 441 | * Callback invoked when a response is received from the Authenticator. 442 | * @param function({data: Object}) callback 443 | * @param {Object} message message Object 444 | */ 445 | u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = 446 | function(callback, message) { 447 | var messageObject = JSON.parse(message.data); 448 | var intentUrl = messageObject['intentURL']; 449 | 450 | var errorCode = messageObject['errorCode']; 451 | var responseObject = null; 452 | if (messageObject.hasOwnProperty('data')) { 453 | responseObject = /** @type {Object} */ ( 454 | JSON.parse(messageObject['data'])); 455 | } 456 | 457 | callback({'data': responseObject}); 458 | }; 459 | 460 | /** 461 | * Base URL for intents to Authenticator. 462 | * @const 463 | * @private 464 | */ 465 | u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = 466 | 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; 467 | 468 | /** 469 | * Wrap the iOS client app with a MessagePort interface. 470 | * @constructor 471 | * @private 472 | */ 473 | u2f.WrappedIosPort_ = function() {}; 474 | 475 | /** 476 | * Launch the iOS client app request 477 | * @param {Object} message 478 | */ 479 | u2f.WrappedIosPort_.prototype.postMessage = function(message) { 480 | var str = JSON.stringify(message); 481 | var url = "u2f://auth?" + encodeURI(str); 482 | location.replace(url); 483 | }; 484 | 485 | /** 486 | * Tells what type of port this is. 487 | * @return {String} port type 488 | */ 489 | u2f.WrappedIosPort_.prototype.getPortType = function() { 490 | return "WrappedIosPort_"; 491 | }; 492 | 493 | /** 494 | * Emulates the HTML 5 addEventListener interface. 495 | * @param {string} eventName 496 | * @param {function({data: Object})} handler 497 | */ 498 | u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { 499 | var name = eventName.toLowerCase(); 500 | if (name !== 'message') { 501 | console.error('WrappedIosPort only supports message'); 502 | } 503 | }; 504 | 505 | /** 506 | * Sets up an embedded trampoline iframe, sourced from the extension. 507 | * @param {function(MessagePort)} callback 508 | * @private 509 | */ 510 | u2f.getIframePort_ = function(callback) { 511 | // Create the iframe 512 | var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; 513 | var iframe = document.createElement('iframe'); 514 | iframe.src = iframeOrigin + '/u2f-comms.html'; 515 | iframe.setAttribute('style', 'display:none'); 516 | document.body.appendChild(iframe); 517 | 518 | var channel = new MessageChannel(); 519 | var ready = function(message) { 520 | if (message.data == 'ready') { 521 | channel.port1.removeEventListener('message', ready); 522 | callback(channel.port1); 523 | } else { 524 | console.error('First event on iframe port was not "ready"'); 525 | } 526 | }; 527 | channel.port1.addEventListener('message', ready); 528 | channel.port1.start(); 529 | 530 | iframe.addEventListener('load', function() { 531 | // Deliver the port to the iframe and initialize 532 | iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); 533 | }); 534 | }; 535 | 536 | 537 | //High-level JS API 538 | 539 | /** 540 | * Default extension response timeout in seconds. 541 | * @const 542 | */ 543 | u2f.EXTENSION_TIMEOUT_SEC = 30; 544 | 545 | /** 546 | * A singleton instance for a MessagePort to the extension. 547 | * @type {MessagePort|u2f.WrappedChromeRuntimePort_} 548 | * @private 549 | */ 550 | u2f.port_ = null; 551 | 552 | /** 553 | * Callbacks waiting for a port 554 | * @type {Array} 555 | * @private 556 | */ 557 | u2f.waitingForPort_ = []; 558 | 559 | /** 560 | * A counter for requestIds. 561 | * @type {number} 562 | * @private 563 | */ 564 | u2f.reqCounter_ = 0; 565 | 566 | /** 567 | * A map from requestIds to client callbacks 568 | * @type {Object.} 570 | * @private 571 | */ 572 | u2f.callbackMap_ = {}; 573 | 574 | /** 575 | * Creates or retrieves the MessagePort singleton to use. 576 | * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback 577 | * @private 578 | */ 579 | u2f.getPortSingleton_ = function(callback) { 580 | if (u2f.port_) { 581 | callback(u2f.port_); 582 | } else { 583 | if (u2f.waitingForPort_.length == 0) { 584 | u2f.getMessagePort(function(port) { 585 | u2f.port_ = port; 586 | u2f.port_.addEventListener('message', 587 | /** @type {function(Event)} */ (u2f.responseHandler_)); 588 | 589 | // Careful, here be async callbacks. Maybe. 590 | while (u2f.waitingForPort_.length) 591 | u2f.waitingForPort_.shift()(u2f.port_); 592 | }); 593 | } 594 | u2f.waitingForPort_.push(callback); 595 | } 596 | }; 597 | 598 | /** 599 | * Handles response messages from the extension. 600 | * @param {MessageEvent.} message 601 | * @private 602 | */ 603 | u2f.responseHandler_ = function(message) { 604 | var response = message.data; 605 | var reqId = response['requestId']; 606 | if (!reqId || !u2f.callbackMap_[reqId]) { 607 | console.error('Unknown or missing requestId in response.'); 608 | return; 609 | } 610 | var cb = u2f.callbackMap_[reqId]; 611 | delete u2f.callbackMap_[reqId]; 612 | cb(response['responseData']); 613 | }; 614 | 615 | /** 616 | * Dispatches an array of sign requests to available U2F tokens. 617 | * If the JS API version supported by the extension is unknown, it first sends a 618 | * message to the extension to find out the supported API version and then it sends 619 | * the sign request. 620 | * @param {string=} appId 621 | * @param {string=} challenge 622 | * @param {Array} registeredKeys 623 | * @param {function((u2f.Error|u2f.SignResponse))} callback 624 | * @param {number=} opt_timeoutSeconds 625 | */ 626 | u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { 627 | if (js_api_version === undefined) { 628 | // Send a message to get the extension to JS API version, then send the actual sign request. 629 | u2f.getApiVersion( 630 | function (response) { 631 | js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; 632 | console.log("Extension JS API Version: ", js_api_version); 633 | u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); 634 | }); 635 | } else { 636 | // We know the JS API version. Send the actual sign request in the supported API version. 637 | u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); 638 | } 639 | }; 640 | 641 | /** 642 | * Dispatches an array of sign requests to available U2F tokens. 643 | * @param {string=} appId 644 | * @param {string=} challenge 645 | * @param {Array} registeredKeys 646 | * @param {function((u2f.Error|u2f.SignResponse))} callback 647 | * @param {number=} opt_timeoutSeconds 648 | */ 649 | u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { 650 | u2f.getPortSingleton_(function(port) { 651 | var reqId = ++u2f.reqCounter_; 652 | u2f.callbackMap_[reqId] = callback; 653 | var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? 654 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); 655 | var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); 656 | port.postMessage(req); 657 | }); 658 | }; 659 | 660 | /** 661 | * Dispatches register requests to available U2F tokens. An array of sign 662 | * requests identifies already registered tokens. 663 | * If the JS API version supported by the extension is unknown, it first sends a 664 | * message to the extension to find out the supported API version and then it sends 665 | * the register request. 666 | * @param {string=} appId 667 | * @param {Array} registerRequests 668 | * @param {Array} registeredKeys 669 | * @param {function((u2f.Error|u2f.RegisterResponse))} callback 670 | * @param {number=} opt_timeoutSeconds 671 | */ 672 | u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { 673 | if (js_api_version === undefined) { 674 | // Send a message to get the extension to JS API version, then send the actual register request. 675 | u2f.getApiVersion( 676 | function (response) { 677 | js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; 678 | console.log("Extension JS API Version: ", js_api_version); 679 | u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, 680 | callback, opt_timeoutSeconds); 681 | }); 682 | } else { 683 | // We know the JS API version. Send the actual register request in the supported API version. 684 | u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, 685 | callback, opt_timeoutSeconds); 686 | } 687 | }; 688 | 689 | /** 690 | * Dispatches register requests to available U2F tokens. An array of sign 691 | * requests identifies already registered tokens. 692 | * @param {string=} appId 693 | * @param {Array} registerRequests 694 | * @param {Array} registeredKeys 695 | * @param {function((u2f.Error|u2f.RegisterResponse))} callback 696 | * @param {number=} opt_timeoutSeconds 697 | */ 698 | u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { 699 | u2f.getPortSingleton_(function(port) { 700 | var reqId = ++u2f.reqCounter_; 701 | u2f.callbackMap_[reqId] = callback; 702 | var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? 703 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); 704 | var req = u2f.formatRegisterRequest_( 705 | appId, registeredKeys, registerRequests, timeoutSeconds, reqId); 706 | port.postMessage(req); 707 | }); 708 | }; 709 | 710 | 711 | /** 712 | * Dispatches a message to the extension to find out the supported 713 | * JS API version. 714 | * If the user is on a mobile phone and is thus using Google Authenticator instead 715 | * of the Chrome extension, don't send the request and simply return 0. 716 | * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback 717 | * @param {number=} opt_timeoutSeconds 718 | */ 719 | u2f.getApiVersion = function(callback, opt_timeoutSeconds) { 720 | u2f.getPortSingleton_(function(port) { 721 | // If we are using Android Google Authenticator or iOS client app, 722 | // do not fire an intent to ask which JS API version to use. 723 | if (port.getPortType) { 724 | var apiVersion; 725 | switch (port.getPortType()) { 726 | case 'WrappedIosPort_': 727 | case 'WrappedAuthenticatorPort_': 728 | apiVersion = 1.1; 729 | break; 730 | 731 | default: 732 | apiVersion = 0; 733 | break; 734 | } 735 | callback({ 'js_api_version': apiVersion }); 736 | return; 737 | } 738 | var reqId = ++u2f.reqCounter_; 739 | u2f.callbackMap_[reqId] = callback; 740 | var req = { 741 | type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, 742 | timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? 743 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), 744 | requestId: reqId 745 | }; 746 | port.postMessage(req); 747 | }); 748 | }; 749 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "make fetch-firmware" 3 | 4 | [[redirects]] 5 | from = "https://genuine.solokeys.com/*" 6 | to = "https://update.solokeys.com/:splat" 7 | status = 302 8 | force = true 9 | -------------------------------------------------------------------------------- /sass/styles.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | font-family: sans-serif; 5 | } 6 | 7 | $family-sans-serif: sans-serif; 8 | 9 | $red: #d83a40; 10 | $mint: #34c39e; 11 | $blue: #3392e5; 12 | 13 | $primary: $blue; 14 | 15 | // cut this down later 16 | @import "../tools/bulma/bulma.sass"; 17 | 18 | a[href^="https://github.com"] img { 19 | margin-right: 4px; 20 | width: auto; 21 | height: $body-size; 22 | vertical-align: middle; 23 | } 24 | -------------------------------------------------------------------------------- /scripts/fetch-firmware.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | rm -f data/* 4 | 5 | wget -P data/ https://raw.githubusercontent.com/solokeys/solo/master/STABLE_VERSION 6 | STABLE_VERSION="$(curl -s https://raw.githubusercontent.com/solokeys/solo/master/STABLE_VERSION)" 7 | echo ${STABLE_VERSION} 8 | 9 | wget -P data/ https://github.com/solokeys/solo/releases/download/${STABLE_VERSION}/firmware-${STABLE_VERSION}.hex 10 | wget -P data/ https://github.com/solokeys/solo/releases/download/${STABLE_VERSION}/firmware-${STABLE_VERSION}.json 11 | wget -P data/ https://github.com/solokeys/solo/releases/download/${STABLE_VERSION}/firmware-${STABLE_VERSION}.sha2 12 | cd data/ 13 | # TODO (possibly in main.js): Check sha2 on firmware embedded in the signed .json 14 | # sha256sum -c firmware-secure-${STABLE_VERSION}.sha2 15 | sha256sum -c firmware-${STABLE_VERSION}.sha2 16 | cd .. 17 | -------------------------------------------------------------------------------- /scripts/make-localhost-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout localhost.key -out localhost.crt \ 4 | -subj /CN=localhost \ 5 | -addext subjectAltName=DNS:localhost,IP:127.0.0.1 6 | -------------------------------------------------------------------------------- /scripts/reloading-serve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # sudo apt install inotify-tools 4 | 5 | sigint_handler() 6 | { 7 | kill $PID 8 | exit 9 | } 10 | 11 | trap sigint_handler SIGINT 12 | 13 | while true; do 14 | $@ & 15 | PID=$! 16 | inotifywait -e modify -e move -e create -e delete -e attrib -r `pwd` 17 | kill $PID 18 | done 19 | 20 | -------------------------------------------------------------------------------- /serve-local.py: -------------------------------------------------------------------------------- 1 | from http.server import HTTPServer, SimpleHTTPRequestHandler 2 | import ssl 3 | 4 | # U2F requires serving over https 5 | # WebAuthn does not 6 | https = False 7 | 8 | host = "localhost" 9 | port = 8443 if https else 8080 10 | protocol = f"http{'s' if https else ''}" 11 | url = f"{protocol}://{host}:{port}" 12 | 13 | httpd = HTTPServer((host, port), SimpleHTTPRequestHandler) 14 | 15 | if https: 16 | httpd.socket = ssl.wrap_socket( 17 | httpd.socket, 18 | keyfile="localhost.key", 19 | certfile="localhost.crt", 20 | server_side=True, 21 | ) 22 | 23 | print(f"serving on {url}") 24 | httpd.serve_forever() 25 | --------------------------------------------------------------------------------