├── LICENSE ├── Makefile ├── README.md ├── bundle_keys.go ├── console.go ├── go.mod ├── go.sum ├── gokey_imx6.go ├── gokey_vpcd.go ├── internal ├── age │ └── plugin.go ├── ccid │ ├── buf.go │ ├── data_block.go │ ├── get_parameters.go │ ├── icc_power.go │ ├── interface.go │ ├── slot_status.go │ └── xfr_block.go ├── icc │ ├── apdu.go │ ├── crypto.go │ ├── crypto_imx6.go │ ├── crypto_stubs.go │ ├── data.go │ ├── interface.go │ ├── led.go │ ├── led_stubs.go │ ├── openpgp.go │ ├── rpc.go │ ├── tlv.go │ ├── verify.go │ └── wake.go ├── snvs │ ├── crypto.go │ ├── snvs.go │ └── stubs.go ├── u2f │ ├── const.go │ ├── counter.go │ └── token.go └── usb │ ├── const.go │ ├── device.go │ ├── irq.go │ ├── smartcard.go │ └── ssh.go ├── keys.go └── mem.go /LICENSE: -------------------------------------------------------------------------------- 1 | https://github.com/usbarmory/GoKey 2 | Copyright (c) WithSecure Corporation 3 | 4 | This program is free software: you can redistribute it and/or modify it under 5 | the terms of the GNU General Public License as published by the Free Software 6 | Foundation under version 3 of the License. 7 | 8 | This program is distributed in the hope that it will be useful, but WITHOUT ANY 9 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 10 | PARTICULAR PURPOSE. See the GNU General Public License for more details. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # https://github.com/usbarmory/GoKey 2 | # 3 | # Copyright (c) WithSecure Corporation 4 | # 5 | # Use of this source code is governed by the license 6 | # that can be found in the LICENSE file. 7 | 8 | BUILD_TAGS = "linkramsize,linkprintk" 9 | REV = $(shell git rev-parse --short HEAD 2> /dev/null) 10 | 11 | APP := gokey 12 | GOENV := GO_EXTLINK_ENABLED=0 CGO_ENABLED=0 GOOS=tamago GOARM=7 GOARCH=arm 13 | TEXT_START := 0x80010000 # ramStart (defined in imx6/imx6ul/memory.go) + 0x10000 14 | GOFLAGS := -tags ${BUILD_TAGS} -trimpath -ldflags "-s -w -T $(TEXT_START) -E _rt0_arm_tamago -R 0x1000" 15 | SHELL = /bin/bash 16 | 17 | .PHONY: clean 18 | 19 | #### primary targets #### 20 | 21 | all: $(APP) 22 | 23 | imx: $(APP).imx 24 | 25 | imx_signed: $(APP)-signed.imx 26 | 27 | elf: $(APP) 28 | 29 | #### utilities #### 30 | 31 | check_tamago: 32 | @if [ "${TAMAGO}" == "" ] || [ ! -f "${TAMAGO}" ]; then \ 33 | echo 'You need to set the TAMAGO variable to a compiled version of https://github.com/usbarmory/tamago-go'; \ 34 | exit 1; \ 35 | fi 36 | 37 | check_hab_keys: 38 | @if [ "${HAB_KEYS}" == "" ]; then \ 39 | echo 'You need to set the HAB_KEYS variable to the path of secure boot keys'; \ 40 | echo 'See https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)'; \ 41 | exit 1; \ 42 | fi 43 | 44 | dcd: 45 | echo $(GOMODCACHE) 46 | echo $(TAMAGO_PKG) 47 | cp -f $(GOMODCACHE)/$(TAMAGO_PKG)/board/usbarmory/mk2/imximage.cfg $(APP).dcd 48 | 49 | check_bundled_keys: 50 | @if { [ "${PGP_SECRET_KEY}" == "" ] || [ ! -f "${PGP_SECRET_KEY}" ]; } && { [ "${U2F_PRIVATE_KEY}" == "" ] || [ ! -f "${U2F_PRIVATE_KEY}" ]; } then \ 51 | echo 'You need to set either PGP_SECRET_KEY or U2F_PRIVATE_KEY variables to a valid path'; \ 52 | exit 1; \ 53 | fi 54 | @if { [ -f "${U2F_PRIVATE_KEY}" ]; } && { [ "${U2F_PUBLIC_KEY}" == "" ] || [ ! -f "${U2F_PUBLIC_KEY}" ]; } then \ 55 | echo 'You need to set the U2F_PUBLIC_KEY variable to a valid path'; \ 56 | exit 1; \ 57 | fi 58 | 59 | clean: 60 | rm -f $(APP) gokey_vpcd 61 | @rm -fr $(APP).bin $(APP).imx $(APP)-signed.imx $(APP).csf $(APP).dcd 62 | 63 | gokey_vpcd: check_bundled_keys 64 | cd $(CURDIR) && ${TAMAGO} generate && \ 65 | go build -tags vpcd -o $(CURDIR)/gokey_vpcd || (rm -f $(CURDIR)/tmp.go && exit 1) 66 | rm -f $(CURDIR)/tmp.go 67 | 68 | #### dependencies #### 69 | 70 | $(APP): check_tamago check_bundled_keys 71 | cd $(CURDIR) && ${TAMAGO} generate && \ 72 | $(GOENV) $(TAMAGO) build $(GOFLAGS) -o $(CURDIR)/$(APP) || (rm -f $(CURDIR)/tmp.go && exit 1) 73 | rm -f $(CURDIR)/tmp.go 74 | 75 | $(APP).dcd: check_tamago 76 | $(APP).dcd: GOMODCACHE=$(shell ${TAMAGO} env GOMODCACHE) 77 | $(APP).dcd: TAMAGO_PKG=$(shell grep "github.com/usbarmory/tamago v" go.mod | awk '{print $$1"@"$$2}') 78 | $(APP).dcd: dcd 79 | 80 | $(APP).bin: CROSS_COMPILE=arm-none-eabi- 81 | $(APP).bin: $(APP) 82 | $(CROSS_COMPILE)objcopy -j .text -j .rodata -j .shstrtab -j .typelink \ 83 | -j .itablink -j .gopclntab -j .go.buildinfo -j .noptrdata -j .data \ 84 | -j .bss --set-section-flags .bss=alloc,load,contents \ 85 | -j .noptrbss --set-section-flags .noptrbss=alloc,load,contents\ 86 | $(APP) -O binary $(APP).bin 87 | 88 | $(APP).imx: $(APP).bin $(APP).dcd 89 | mkimage -n $(APP).dcd -T imximage -e $(TEXT_START) -d $(APP).bin $(APP).imx 90 | # Copy entry point from ELF file 91 | dd if=$(APP) of=$(APP).imx bs=1 count=4 skip=24 seek=4 conv=notrunc 92 | 93 | #### secure boot #### 94 | 95 | $(APP)-signed.imx: check_hab_keys $(APP).imx 96 | ${TAMAGO} install github.com/usbarmory/crucible/cmd/habtool@latest 97 | $(shell ${TAMAGO} env GOPATH)/bin/habtool \ 98 | -A ${HAB_KEYS}/CSF_1_key.pem \ 99 | -a ${HAB_KEYS}/CSF_1_crt.pem \ 100 | -B ${HAB_KEYS}/IMG_1_key.pem \ 101 | -b ${HAB_KEYS}/IMG_1_crt.pem \ 102 | -t ${HAB_KEYS}/SRK_1_2_3_4_table.bin \ 103 | -x 1 \ 104 | -i $(APP).imx \ 105 | -o $(APP).csf && \ 106 | cat $(APP).imx $(APP).csf > $(APP)-signed.imx 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | Andrea Barisani 5 | andrea@inversepath.com 6 | 7 | Introduction 8 | ============ 9 | 10 | The GoKey application implements a USB smartcard in pure Go with support for: 11 | 12 | * [OpenPGP 3.4](https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.pdf) 13 | * [FIDO U2F](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.pdf) 14 | * [age plugin](https://github.com/FiloSottile/age) 15 | * [PKCS#11 over RPC](https://github.com/google/go-p11-kit) 16 | 17 | In combination with the [TamaGo framework](https://github.com/usbarmory/tamago) 18 | GoKey is meant to be executed on ARM bare metal on hardware such as the 19 | [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki). 20 | 21 | > :warning: the SSH management console only works on Linux or macOS hosts. 22 | 23 | ![GoKey demo](https://github.com/usbarmory/GoKey/wiki/media/gokey-usage.gif) 24 | 25 | Security model 26 | -------------- 27 | 28 | When running on [secure booted](https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)) 29 | NXP i.MX6ULL devices, the built-in Data Co-Processor (DCP) is used to provide 30 | device specific hardware encryption. 31 | 32 | A device specific random 256-bit OTPMK key is fused in each NXP i.MX6ULL SoC at 33 | manufacturing time, this key is unreadable and can only be used by the DCP for 34 | AES encryption/decryption of user data, through the Secure Non-Volatile Storage 35 | (SNVS) companion block. 36 | 37 | The OTPMK is used to derive device specific keys, which can be used for the 38 | following operations: 39 | 40 | * Bundling of OpenPGP/SSH/U2F private keys, within the GoKey firmware, in 41 | encrypted form. This ensures that bundled keys are authenticated, confidential 42 | and only decrypted on a specific unit. 43 | 44 | The SSH private key can be uniquely and deterministically generated for each 45 | hardware unit, rather than being bundled in the firmware, if not passed at 46 | compilation time. 47 | 48 | * Creation of the AES256 Data Object used by PSO:DEC (in AES mode) and PSO:ENC, 49 | this entails that AES encryption/decryption operations can only be executed 50 | on a specific unit. 51 | 52 | * Generation and unwrapping of [age](https://github.com/FiloSottile/age) 53 | identities, AES encypted/decrypted with an OTPMK derived key. 54 | 55 | On units which are *not* secure booted (not recommended): 56 | 57 | * The OpenPGP private key is bundled without hardware encryption, its sole 58 | protection can therefore be encryption with the user passphrase (if present 59 | in the key). 60 | 61 | * The SSH and U2F private keys are bundled without hardware encryption, and 62 | therefore readable from the firmware image. 63 | 64 | The SSH private key is randomly generated at each boot if not passed at 65 | compilation time. 66 | 67 | * The U2F master key is derived from unique hardware serial numbers and 68 | the SoC unique ID, both are readable from a stolen device without secure boot 69 | in place. 70 | 71 | * PSO:DEC (in AES mode) and PSO:ENC are not available. 72 | 73 | For certain users and uses, a non secure booted device might lead to an 74 | acceptable level of risk in case of a stolen device, nonetheless it is highly 75 | recommended to always use a secure booted device for all configurations and to 76 | leverage on SNVS features (see _Compiling_). 77 | 78 | On a secure booted unit the GoKey firmware, bundled with private keys encrypted 79 | with the device unique key, can be built by compiling on the device itself with 80 | the [mxs-dcp kernel module](https://github.com/usbarmory/mxs-dcp) 81 | loaded. 82 | 83 | The module is included in all [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki) 84 | standard [Debian base image releases](https://github.com/usbarmory/usbarmory-debian-base_image). 85 | 86 | Deviations from OpenPGP standard support 87 | ---------------------------------------- 88 | 89 | These are security features, not bugs: 90 | 91 | * PW3 is not implemented and card personalization is managed outside OpenPGP 92 | specifications to reduce the attack surface (see _Management_). 93 | 94 | * The VERIFY command user PIN (PW1) is the passphrase of the relevant imported 95 | key for the requested operation (the PSO:ENC operation does not use any 96 | OpenPGP key, however the decryption subkey passphrase is still used for 97 | cardholder authentication). 98 | 99 | * The optional key derived format (KDF) is not supported to avoid the 100 | transmission and internal storage of passwords in plain format, rather the 101 | user can issue the key passphrase over SSH for improved security 102 | (see _Management_). 103 | 104 | * To prevent plaintext transmission of the PIN/passphrase, the VERIFY command 105 | will take any PIN (>=6 characters) if the relevant OpenPGP key has been 106 | already unlocked over SSH (see _Management_). 107 | 108 | * On bare metal secure booted i.MX6ULL, the AES256 Data Object for PSO:DEC (in 109 | AES mode) and PSO:ENC is internally created with a device specific random 110 | 256-bit key (OTPMK). This means that PSO:DEC and PSO:ENC can only 111 | decrypt/encrypt, using AES, data on the same device. 112 | 113 | These are current limitations: 114 | 115 | * Only signature (Sig) and decryption (Dec) keys are supported, authentication 116 | keys (Aut) are pending [this pull request](https://github.com/keybase/go-crypto/pull/86). 117 | 118 | * PW1 and DSO counters are volatile (e.g. not permanent across reboots), other 119 | such as RC and PW3 are unused due to lack of functionality. 120 | 121 | * Data Object 0x7f21 (Cardholder certificate) is not implemented. 122 | 123 | Comparison with conventional smartcards 124 | --------------------------------------- 125 | 126 | A conventional smartcard authenticates the user typically with a numeric PIN, 127 | this cardholder authentication unlocks the code paths that allow use of key 128 | material and/or management functions. The secret key material is stored in an 129 | internal flash unencrypted, relaying on the physical barrier and simple 130 | read protection mechanisms for its security. 131 | 132 | In other words a conventional smartcard does not employ encryption of data at 133 | rest and its code has internal access to key material unconditionally. 134 | 135 | Futher, traditional smartcards require physical security measures to prevent 136 | tampering attacks such as glitching or memory extraction, as there is 137 | opportunity for an attacker in possession of a stolen card to try and extract 138 | key material. 139 | 140 | The GoKey firmware, when running on the 141 | [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki), 142 | employs a different security model as all data at rest is either authenticated, 143 | by secure boot, or encrypted. The private OpenPGP keys are actually encrypted 144 | twice, the first layer for the benefit of the hardware so that only 145 | authenticated code can unwrap the key and the second layer for the benefit of 146 | user authentication. 147 | 148 | Therefore the GoKey firmware does not need to be stored on an internal flash 149 | with read protection but is meant to be accessible by anyone, as it is 150 | authenticated by the hardware and only holds encrypted content which can be 151 | unlocked by a specific device *and* a specific user. 152 | 153 | Additionally, to help mitigating attacks against the first layer of hardware 154 | key wrapping, hardware decryption can be configured to take place only when a 155 | user is successfully authenticated through the management interface. 156 | 157 | The security model of GoKey, opposed to conventional smartcards, entails that a 158 | stolen device gives no opportunity for an attacker to extract private key 159 | material unless the user private SSH key (or secure boot process) as well as 160 | OpenPGP key passphrases are compromised, when all security features are used. 161 | 162 | Last but not least, thanks to the [TamaGo framework](https://github.com/usbarmory/tamago), 163 | GoKey on the [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki) 164 | employs a runtime environment written in pure high-level, memory safe, Go code 165 | and without the dependency of an OS, or any other C based dependency. This 166 | dramatically reduces the attack surface while increasing implementation 167 | trustworthiness. 168 | 169 | The following table summarizes the fundamental technological differences with a 170 | traditional smartcard: 171 | 172 | | Hardware type | Trust anchor | Data protection | Runtime environment | Application environment | Requires tamper proofing | Encryption at rest | 173 | |:--------------------------|------------------|----------------------|---------------------|-------------------------|--------------------------|--------------------| 174 | | Traditional smartcards | Flash protection | Flash protection | JCOP | JCOP applets | Yes | No | 175 | | GoKey on USB armory Mk II | Secure boot | SoC security element | Bare metal Go | Bare metal Go | No | Yes | 176 | 177 | The following table summarizes the multiple authentication options available, 178 | depending on OpenPGP and GoKey configuration, which enhance the traditional 179 | smartcard authentication model: 180 | 181 | | OpenPGP passphrase | GoKey authentication (see `SNVS=ssh`) | Comment | 182 | |:-------------------|---------------------------------------|-------------------------------------------------------------------------------------------------| 183 | | None | None | No security, device can be used without any authentication | 184 | | Yes over VERIFY | None | Low security, passphrase transmitted in plaintext over USB | 185 | | Yes over SSH | None | Better security, passphrase transmitted securely | 186 | | Yes over VERIFY | Yes | Good security, plaintext passphrase but standard SSH authentication required to enable key use | 187 | | None | Yes | Good security and convenience, standard SSH authentication required for hardware key decryption | 188 | | Yes over SSH | Yes | High security, standard SSH authentication enables key use, passphrase transmitted securely | 189 | 190 | Tutorial 191 | ======== 192 | 193 | The next sections detail the compilation, configuration, execution and 194 | operation for bare metal, virtualized and simulated environments. 195 | 196 | A simplified tutorial for secure booted USB armory Mk II boards is available in 197 | the [project wiki](https://github.com/usbarmory/GoKey/wiki). 198 | 199 | Compiling 200 | ========= 201 | 202 | Unless otherwise stated, all commands shown are relative to this repository 203 | directory: 204 | 205 | ``` 206 | git clone https://github.com/usbarmory/GoKey && cd GoKey 207 | ``` 208 | 209 | As a pre-requisite for all compilation targets, the following environment 210 | variables must be set or passed to the make command: 211 | 212 | * `SNVS`: when set to a non empty value, use hardware encryption for OpenPGP, 213 | SSH, U2F and age private keys wrapping, see the _Security Model_ section for 214 | more information. 215 | 216 | If set to "ssh", OpenPGP and U2F key decryption, rather than executed at 217 | boot, must be initialized by the user over SSH (see _Management_). This improve 218 | resilience against physical hardware attacks as the SNVS decryption process 219 | cannot happen automatically on a stolen devices. 220 | 221 | This option can only be used when compiling on a [secure booted](https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)) 222 | [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki). 223 | 224 | > :warning: the SSH management console only works on Linux or macOS hosts, it 225 | > must be disabled for Windows hosts. 226 | 227 | * `SSH_PUBLIC_KEY`: public key for SSH client authentication by the network 228 | management interface (see _Management_). If empty the SSH interface is 229 | disabled. 230 | 231 | * `SSH_PRIVATE_KEY`: private key for SSH client authentication of the 232 | management interface SSH server (see _Management_). The key must not have a 233 | passphrase. When SNVS is set the key is encrypted, before being bundled, for a 234 | specific hardware unit. 235 | 236 | On secure booted units, if left empty, the SSH server key is uniquely and 237 | deterministically generated for each hardware unit. 238 | 239 | On units which are not secure booted, if left empty, the SSH server key is 240 | randomly generated at each boot. 241 | 242 | OpenPGP 243 | ------- 244 | 245 | * `PGP_SECRET_KEY`: OpenPGP secret keys in ASCII armor format, bundled 246 | in the output firmware. If empty OpenPGP smartcard support is disabled. 247 | 248 | When SNVS is set the key is encrypted, before being bundled, for a specific 249 | hardware unit. 250 | 251 | * `URL`: optional public key URL. 252 | 253 | * `NAME`, `LANGUAGE`, `SEX`: optional cardholder related data elements. 254 | 255 | OpenPGP smartcard secret keys are typically made of 3 subkeys: signature, 256 | decryption, authentication. 257 | 258 | The GoKey card cannot import keys while running as any runtime change is not 259 | allowed (see _Deviations from OpenPGP standard support_), only key bundling at 260 | compile time is currently supported. 261 | 262 | There are several resources on-line on OpenPGP key creation and should all be 263 | applicable to GoKey as long as the smartcard specific `keytocard` command is 264 | not used, but rather keys are exported armored and passed via `PGP_SECRET_KEY` 265 | at compile time. 266 | 267 | Some good references to start: 268 | * [Subkeys](https://wiki.debian.org/Subkeys) 269 | * [Creating newer ECC keys for GnuPG](https://www.gniibe.org/memo/software/gpg/keygen-25519.html) 270 | 271 | Finally always ensure that existing keys are imported with minimal content, an 272 | example preparation is the following: 273 | 274 | ``` 275 | gpg --armor --export-options export-minimal,export-clean --export-secret-key ID 276 | ``` 277 | 278 | > :warning: Please note that only RSA, ECDSA, ECDH keys are supported. Any 279 | > other key (such as ElGamal, Ed25519) will not work. 280 | 281 | U2F keys 282 | -------- 283 | 284 | To enable U2F support using the [fidati](https://github.com/gsora/fidati) 285 | library, the following variables can be set: 286 | 287 | * `U2F_PUBLIC_KEY`: U2F device attestation certificate, if empty the U2F 288 | interface is disabled. 289 | 290 | * `U2F_PRIVATE_KEY`: U2F device attestation private key, if empty the U2F 291 | interface is disabled. 292 | 293 | When SNVS is set the key is encrypted, before being bundled, for a specific 294 | hardware unit. 295 | 296 | The attestation key material can be created using the 297 | [gen-cert](https://github.com/gsora/fidati/tree/master/cmd/gen-cert) tool from 298 | the [fidati](https://github.com/gsora/fidati) library. 299 | 300 | On USB armory Mk II rev. β models the ATECC608B security element is used as 301 | hardware backed monotonic counter for U2F purposes. The counter runs out at 302 | 2097151, which is considered a range sufficient for its intended purpose. 303 | 304 | On USB armory Mk II rev. γ models a 32-bit monotonic counter is saved on the 305 | internal eMMC in an unused reserved sector. 306 | 307 | The U2F library performs peer-specific key derivation using a master secret 308 | ([U2F Key Wrapping](https://www.yubico.com/blog/yubicos-u2f-key-wrapping)), 309 | GoKey derives such master secret using the SNVS to obtain an authenticated 310 | device specific value. 311 | 312 | When the management interface is disabled, FIDO U2F user presence is 313 | automatically acknowledged, otherwise it can be configured at initialization 314 | through the management interface (see _Management_). 315 | 316 | Building the bare metal executable 317 | ---------------------------------- 318 | 319 | Build the [TamaGo compiler](https://github.com/usbarmory/tamago-go) 320 | (or use the [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)): 321 | 322 | ``` 323 | wget https://github.com/usbarmory/tamago-go/archive/refs/tags/latest.zip 324 | unzip latest.zip 325 | cd tamago-go-latest/src && ./all.bash 326 | cd ../bin && export TAMAGO=`pwd`/go 327 | ``` 328 | 329 | Please note that if performed on the USB armory Mk II, due to this 330 | [issue](https://github.com/golang/go/issues/37122), this requires adding some 331 | temporary [swap space](http://raspberrypimaker.com/adding-swap-to-the-raspberrypi/) 332 | to be disabled and removed after this step is completed (to prevent eMMC wear), 333 | alternatively you can cross compile from another host or use the 334 | [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)). 335 | 336 | Build the `gokey.imx` application executable with the desired variables: 337 | 338 | ``` 339 | make imx CROSS_COMPILE=arm-none-eabi- NAME="Alice" PGP_SECRET_KEY= SSH_PUBLIC_KEY= SSH_PRIVATE_KEY= 340 | ``` 341 | 342 | For signed images to be executed on [secure booted](https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)) 343 | USB armory Mk II devices, which enable use of Secure Non-Volatile Storage 344 | (SNVS), the `imx_signed` target should be used with the relevant `HAB_KEYS` 345 | set: 346 | 347 | ``` 348 | make imx_signed CROSS_COMPILE=arm-none-eabi- NAME="Alice" PGP_SECRET_KEY= SSH_PUBLIC_KEY= SSH_PRIVATE_KEY= HAB_KEYS= SNVS=ssh 349 | ``` 350 | 351 | OpenPGP host configuration 352 | ========================== 353 | 354 | macOS 355 | ----- 356 | 357 | The GoKey USB smartcard works out of the box on modern macOS installations with 358 | [GPG Suite](https://gpgtools.org/). 359 | 360 | Linux 361 | ----- 362 | 363 | The GoKey USB smartcard has been tested with [libccid](https://ccid.apdu.fr/), 364 | used by [OpenSC](https://github.com/OpenSC/OpenSC/wiki) on most Linux 365 | distributions. 366 | 367 | The libccid library [now supports](https://ccid.apdu.fr/ccid/shouldwork.html#0x12090x2702) GoKey vendor 368 | and product IDs, if your installed version is older than this change apply the 369 | following instructions. 370 | 371 | To enable detection an entry must be added in `libccid_Info.plist` (typically 372 | located in `/etc`): 373 | 374 | * Locate the `ifdVendorID` array and add the following at its bottom: 375 | 376 | ``` 377 | 0x1209 378 | ``` 379 | 380 | * Locate the `ifdProductID` array and add the following at its bottom: 381 | 382 | ``` 383 | 0x2702 384 | ``` 385 | 386 | * Locate the `ifdFriendlyName` array and add the following at its bottom: 387 | 388 | ``` 389 | USB armory Mk II 390 | ``` 391 | 392 | The GoKey USB smartcard, once the CCID driver entries are added as in the 393 | previous section, can then be used as any other smartcard via OpenSC on Linux. 394 | 395 | You can refer to [Arch Linux smartcards documentation](https://wiki.archlinux.org/index.php/Smartcards) 396 | for configuration documentation. 397 | 398 | Windows 399 | ------- 400 | 401 | Windows does not support Ethernet over USB devices implemented with CDC-ECM, 402 | therefore the SSH management console must be disabled (e.g. `SNVS` can be non 403 | empty but not to `ssh`, see _Compiling_) 404 | 405 | Smartcard operation has not been tested but should be possible with software 406 | that uses up-to-date smartcard drivers. 407 | 408 | Executing 409 | ========= 410 | 411 | USB armory Mk II: imx image 412 | --------------------------- 413 | 414 | Follow [these instructions](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#flashing-bootable-images-on-externalinternal-media) 415 | using the built `gokey.imx` or `gokey_signed.imx` image. 416 | 417 | USB armory Mk II: existing bootloader 418 | ------------------------------------- 419 | 420 | Copy the built `gokey` binary on an external microSD card (replace `$dev` with 421 | `0`) or the internal eMMC (replace `$dev` with `1`), then launch it from the 422 | U-Boot console as follows: 423 | 424 | ``` 425 | ext2load mmc $dev:1 0x90000000 gokey 426 | bootelf -p 0x90000000 427 | ``` 428 | 429 | For non-interactive execution modify U-Boot configuration accordingly. 430 | 431 | Operation 432 | ========= 433 | 434 | GoKey can be conveniently accessed from either the USB armory Type-C plug (e.g. 435 | USB armory directly connected to a Type-C host port) or receptacle (e.g. USB 436 | armory connected with a Type-C to Type-A cable). 437 | 438 | Management 439 | ---------- 440 | 441 | When running on bare metal the GoKey firmware exposes, on top of the USB CCID 442 | smartcard and/or U2F token interfaces, an SSH server started on 443 | [Ethernet over USB](https://github.com/usbarmory/usbarmory/wiki/Host-communication). 444 | 445 | The SSH server authenticates the user using the public key passed at 446 | compilation time with the `SSH_PUBLIC_KEY` environment variable. Any username 447 | can be passed when connecting. If empty the SSH interface is disabled. 448 | 449 | A private key for the SSH server can be optionally passed at compilation time 450 | with the `SSH_PRIVATE_KEY` environment variable, on secure booted units it can 451 | also be deterministically generated at each boot (see _Compiling_). 452 | 453 | The server responds on address 10.0.0.10, with standard port 22, and can be 454 | used to securely message passphrase verification, in alternative to smartcard 455 | clients which issue unencrypted VERIFY commands with PIN/passphrases, signal 456 | U2F user presence and perform additional management functions. 457 | 458 | ``` 459 | help # this help 460 | exit, quit # close session 461 | rand # gather 32 bytes from TRNG via crypto/rand 462 | reboot # restart 463 | status # display smartcard/token status 464 | build # display build information 465 | 466 | init # initialize OpenPGP smartcard 467 | lock (all|sig|dec) # OpenPGP key(s) lock 468 | unlock (all|sig|dec) # OpenPGP key(s) unlock, prompts passphrase 469 | 470 | rpc # PKCS#11 RPC socket 471 | # use with 'ssh -L p11kit.sock:127.0.0.1:22' 472 | 473 | age-plugin (gen|identity-v1) # handle age plugin state machine 474 | 475 | u2f # initialize U2F token w/ user presence test 476 | u2f !test # initialize U2F token w/o user presence test 477 | p # confirm user presence 478 | ``` 479 | 480 | Note that to prevent plaintext transmission of the PIN/passphrase, the VERIFY 481 | command requested by any OpenPGP host client will take any PIN (>= 6 482 | characters) if the relevant OpenPGP key has been already unlocked over SSH. 483 | 484 | OpenPGP smartcard 485 | ----------------- 486 | 487 | You should be able to use the GoKey smartcard like any other OpenPGP card, you 488 | can test its operation with the following commands: 489 | 490 | * OpenSC detection: `pcsc_scan` 491 | 492 | * OpenSC explorer: `opensc-explorer` 493 | 494 | * OpenPGP tool key information: `openpgp-tool -K` 495 | 496 | * GnuPG card status: `gpg --card-status` (`>` shows keys stored on a smartcard) 497 | 498 | U2F token 499 | --------- 500 | 501 | The U2F functionality can be used with any website or application that supports 502 | FIDO U2F. 503 | 504 | When the SSH interface is enabled (see _Management_) the U2F functionality must 505 | be initialized with the `u2f` command, user presence can be demonstrated with 506 | the `p` command (not required if `u2f !test` is used for initialization). 507 | 508 | When the SSH interface is disabled user presence is automatically acknowledged 509 | at each request. 510 | 511 | age plugin 512 | ---------- 513 | 514 | The [age plugin](https://github.com/FiloSottile/age) functionality is available 515 | only on secure booted units (e.g. `SNVS` set to non empty value when 516 | _Compiling_). 517 | 518 | An age key pair can be generated through the _Management_ interface with the 519 | `age gen` command: 520 | 521 | ``` 522 | ssh 10.0.0.1 age-plugin gen 523 | 524 | # public key: age1u7q3elfae7wawlneek660ayqd8270u6c35mdt246rmq79z8k5p2qqwxry6 525 | AGE-PLUGIN-GOKEY-1EPZFX6TNDUCF55FQLU83VW820GZGKMU2QSZ577SCH8CLKXGHXS7JZLPRY8... 526 | ``` 527 | 528 | The generated recipient and identity file can now be used with an age plugin 529 | that relays the `identity-v1` command, over SSH, to the GoKey _Management_ 530 | interface. 531 | 532 | The following example shell wrapper can be used by an age client if present in 533 | the `PATH`: 534 | 535 | ```shell 536 | #!/bin/sh 537 | 538 | OPTS=$(getopt -n age-plugin-gokey --options p: --longoptions 'age-plugin:' -- $@) 539 | 540 | eval set -- "$OPTS" 541 | 542 | while (($#)); do 543 | case $1 in 544 | -p|--age-plugin) sm=$2; shift;; 545 | --) shift; break;; 546 | *) echo "invalid argument";; 547 | esac 548 | shift 549 | done 550 | 551 | ssh 10.0.0.1 age-plugin $sm 552 | ``` 553 | 554 | Once the plugin wrapper is present in the `PATH`, the GoKey generated identity 555 | can be used as follows while the GoKey device is reachable via its _Management_ 556 | interface: 557 | 558 | ``` 559 | age -a -R gokey.recipient -o secret.txt.age -e secret.txt 560 | age -i gokey.identity -d secret.txt.age 561 | ``` 562 | 563 | PKCS#11 token 564 | ------------- 565 | 566 | When the SSH interface is enabled (see _Management_) a GoKey protected 567 | ECDSA/RSA key can be exposed through 568 | [PKCS#11 over RPC](https://github.com/google/go-p11-kit) through an 569 | [SSH forwarded Unix socket](https://p11-glue.github.io/p11-glue/p11-kit/manual/remoting.html). 570 | 571 | A direct SSH TCP forward to a Unix socket exposes GoKey PKCS#11 over RPC 572 | interface: 573 | 574 | ``` 575 | $ ssh -N -L p11kit.sock:127.0.0.1:22 10.0.0.10 576 | ``` 577 | 578 | This allows use of such key through PKCS#11 API clients, the following example 579 | illustrates integration with OpenSSL. 580 | 581 | ``` 582 | $ export P11_KIT_SERVER_ADDRESS=unix:path=p11kit.sock 583 | $ pkcs11-tool --module /usr/lib/pkcs11/p11-kit-client.so --list-slots 584 | 585 | Available slots: 586 | Slot 0 (0x1): example-slot 587 | token label : GoKey 588 | token manufacturer : WithSecure Foundry 589 | token model : USB armory Mk II 590 | token flags : token initialized, readonly 591 | hardware version : 2.0 592 | firmware version : 0.1 593 | serial num : C148261A 594 | pin min/max : 0/0 595 | 596 | $ pkcs11-tool --module /usr/lib/pkcs11/p11-kit-client.so --list-objects 597 | 598 | Using slot 0 with a present token (0x1) 599 | Certificate Object; type = X.509 cert 600 | subject: DN: 601 | serial: 75A3F93B15D62D15 602 | ID: 010275a3f93b15d62d15 603 | Private Key Object; RSA 604 | Usage: decrypt, sign 605 | Access: sensitive, always sensitive, never extractable 606 | Public Key Object; RSA 2048 bits 607 | ID: 010275a3f93b15d62d15 608 | Usage: encrypt, verify 609 | Access: none 610 | ``` 611 | 612 | LED status 613 | ---------- 614 | 615 | On the [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki) 616 | the LEDs are used as follows: 617 | 618 | | LED | On | Off | 619 | |:-------------:|--------------------------------------------------|----------------------------------------| 620 | | blue + white | at startup: card is initializing¹ | card has been initialized | 621 | | blue | one or more OpenPGP private subkeys are unlocked | all OpenPGP private subkeys are locked | 622 | | white | OpenPGP security operation in progress | no security operation in progress | 623 | | white | blinking: U2F user presence is requested | no presence requested | 624 | 625 | ¹ With `SNVS=ssh` both LEDs remain on until the `init` command has been issued over SSH management interface. 626 | 627 | Debugging 628 | ========= 629 | 630 | Virtual Smart Card 631 | ------------------ 632 | 633 | The [virtual smart card project](http://frankmorgner.github.io/vsmartcard/virtualsmartcard/README.html) 634 | allows testing of GoKey OpenPGP functionality in userspace. 635 | 636 | Build the `gokey_vpcd` application executable: 637 | 638 | ``` 639 | make gokey_vpcd PGP_SECRET_KEY= 640 | ``` 641 | 642 | On the host install [vsmartcard](http://frankmorgner.github.io/vsmartcard/index.html), 643 | Arch Linux users can use the [virtualsmartcard AUR package](https://aur.archlinux.org/packages/virtualsmartcard/). 644 | 645 | Ensure that a configuration for `vpcd` is added in your `pcsc` configuration. 646 | On Arch Linux the following is automatically created in `/etc/reader.conf.d`: 647 | 648 | ``` 649 | FRIENDLYNAME "Virtual PCD" 650 | DEVICENAME /dev/null:0x8C7B 651 | LIBPATH //usr/lib/pcsc/drivers/serial/libifdvpcd.so 652 | CHANNELID 0x8C7B 653 | ``` 654 | 655 | Launch the PC/SC daemon: 656 | 657 | ``` 658 | sudo systemctl start pcscd 659 | ``` 660 | 661 | Launch the built `gokey_vpcd` executable: 662 | 663 | ``` 664 | ./gokey_vpcd -c 127.0.0.1:35963 665 | ``` 666 | 667 | The same executable can also be used to test the _PKCS#11 token_ interface, a 668 | relevant `P11_KIT_SERVER_ADDRESS` variable is returned upon execution of 669 | `gokey_vpcd`. 670 | 671 | Send manual commands to GnuPG smart-card daemon (SCD) 672 | ----------------------------------------------------- 673 | 674 | * Example for issuing a GET CHALLENGE request to get 256 random bytes (due to 675 | SCD protocol formatting some post processing required to extract the actual 676 | random output): 677 | 678 | ``` 679 | gpg-connect-agent "SCD RANDOM 256" /bye | perl -pe 'chomp;s/^D\s//;s/%(0[AD]|25)/chr(hex($1))/eg;if(eof&&/^OK$/){exit}' 680 | ``` 681 | 682 | License 683 | ======= 684 | 685 | GoKey | https://github.com/usbarmory/GoKey 686 | Copyright (c) WithSecure Corporation 687 | 688 | This program is free software: you can redistribute it and/or modify it under 689 | the terms of the GNU General Public License as published by the Free Software 690 | Foundation under version 3 of the License. 691 | 692 | This program is distributed in the hope that it will be useful, but WITHOUT ANY 693 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 694 | PARTICULAR PURPOSE. See the GNU General Public License for more details. 695 | 696 | See accompanying LICENSE file for full details. 697 | -------------------------------------------------------------------------------- /bundle_keys.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | // 8 | // This program bundles OpenPGP secret and SSH public keys in the resulting 9 | // GoKey firmware executable. If available the NXP Data Co-Processor (DCP) can 10 | // be used to encrypt OpenPGP secret keys uniquely to the target USB armory Mk 11 | // II board. 12 | // 13 | // To use such feature the GoKey firmware must be compiled on the same hardware 14 | // it will then be executed on, setting the `SNVS` environment variable. The 15 | // `mxs-dcp` module (https://github.com/usbarmory/mxs-dcp) must be 16 | // loaded. 17 | // 18 | // IMPORTANT: the unique OTPMK internal key is available only when Secure Boot 19 | // (HAB) is enabled, otherwise a Non-volatile Test Key (NVTK), identical for 20 | // each SoC, is used. The secure operation of the DCP and SNVS, in production 21 | // deployments, should always be paired with Secure Boot activation. 22 | 23 | //go:build linux && ignore 24 | 25 | package main 26 | 27 | import ( 28 | "crypto/aes" 29 | "crypto/rand" 30 | "errors" 31 | "fmt" 32 | "io" 33 | "log" 34 | "os" 35 | "strconv" 36 | "syscall" 37 | "unsafe" 38 | 39 | "github.com/usbarmory/GoKey/internal/icc" 40 | "github.com/usbarmory/GoKey/internal/snvs" 41 | "github.com/usbarmory/GoKey/internal/u2f" 42 | "github.com/usbarmory/GoKey/internal/usb" 43 | 44 | "golang.org/x/sys/unix" 45 | ) 46 | 47 | type af_alg_iv struct { 48 | ivlen uint32 49 | iv [aes.BlockSize]byte 50 | } 51 | 52 | func init() { 53 | log.SetFlags(0) 54 | log.SetOutput(os.Stdout) 55 | } 56 | 57 | func main() { 58 | var err error 59 | var SNVS bool 60 | 61 | var sshPublicKey []byte 62 | var sshPrivateKey []byte 63 | var pgpSecretKey []byte 64 | var u2fPublicKey []byte 65 | var u2fPrivateKey []byte 66 | 67 | if os.Getenv("SNVS") != "" { 68 | SNVS = true 69 | 70 | if _, err = deriveKey("test", make([]byte, aes.BlockSize)); err != nil { 71 | log.Fatalf("SNVS requested but cbc-aes-dcp missing (%v)", err) 72 | } 73 | } 74 | 75 | if SNVS { 76 | log.Printf("████████████████████████████████████████████████████████████████████████████████") 77 | log.Printf(" ** WARNING ** ") 78 | log.Printf(" SNVS *enabled*, private keys will be encrypted for this specific hardware unit ") 79 | log.Printf("████████████████████████████████████████████████████████████████████████████████") 80 | } else { 81 | log.Printf("████████████████████████████████████████████████████████████████████████████████") 82 | log.Printf(" ** WARNING ** ") 83 | log.Printf(" SNVS *disabled*, private keys will be bundled *without* hardware encryption ") 84 | log.Printf("████████████████████████████████████████████████████████████████████████████████") 85 | } 86 | 87 | if sshPublicKeyPath := os.Getenv("SSH_PUBLIC_KEY"); sshPublicKeyPath != "" { 88 | sshPublicKey, err = os.ReadFile(sshPublicKeyPath) 89 | 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | } 94 | 95 | if os.Getenv("SNVS") == "ssh" && len(sshPublicKey) == 0 { 96 | log.Fatal("SSH_PUBLIC_KEY is required with SNVS=ssh") 97 | } 98 | 99 | if sshPrivateKeyPath := os.Getenv("SSH_PRIVATE_KEY"); sshPrivateKeyPath != "" { 100 | if SNVS { 101 | sshPrivateKey, err = encrypt(sshPrivateKeyPath, usb.DiversifierSSH) 102 | } else { 103 | sshPrivateKey, err = os.ReadFile(sshPrivateKeyPath) 104 | } 105 | 106 | if err != nil { 107 | log.Fatal(err) 108 | } 109 | } 110 | 111 | if pgpSecretKeyPath := os.Getenv("PGP_SECRET_KEY"); pgpSecretKeyPath != "" { 112 | if SNVS { 113 | pgpSecretKey, err = encrypt(pgpSecretKeyPath, icc.DiversifierPGP) 114 | } else { 115 | pgpSecretKey, err = os.ReadFile(pgpSecretKeyPath) 116 | } 117 | 118 | if err != nil { 119 | log.Fatal(err) 120 | } 121 | } 122 | 123 | if u2fPublicKeyPath := os.Getenv("U2F_PUBLIC_KEY"); u2fPublicKeyPath != "" { 124 | u2fPublicKey, err = os.ReadFile(u2fPublicKeyPath) 125 | 126 | if err != nil { 127 | log.Fatal(err) 128 | } 129 | } 130 | 131 | if u2fPrivateKeyPath := os.Getenv("U2F_PRIVATE_KEY"); u2fPrivateKeyPath != "" { 132 | if SNVS { 133 | u2fPrivateKey, err = encrypt(u2fPrivateKeyPath, u2f.DiversifierU2F) 134 | } else { 135 | u2fPrivateKey, err = os.ReadFile(u2fPrivateKeyPath) 136 | } 137 | 138 | if err != nil { 139 | log.Fatal(err) 140 | } 141 | } 142 | 143 | out, err := os.Create("tmp.go") 144 | 145 | if err != nil { 146 | log.Fatal(err) 147 | } 148 | 149 | out.WriteString(` 150 | package main 151 | 152 | func init() { 153 | `) 154 | 155 | if SNVS { 156 | fmt.Fprint(out, "\tSNVS = true\n") 157 | } 158 | 159 | if os.Getenv("SNVS") == "ssh" { 160 | fmt.Fprint(out, "\tinitAtBoot = false\n") 161 | } else { 162 | fmt.Fprint(out, "\tinitAtBoot = true\n") 163 | } 164 | 165 | if len(sshPublicKey) > 0 { 166 | fmt.Fprintf(out, "\tsshPublicKey = []byte(%s)\n", strconv.Quote(string(sshPublicKey))) 167 | } 168 | 169 | if len(sshPrivateKey) > 0 { 170 | fmt.Fprintf(out, "\tsshPrivateKey = []byte(%s)\n", strconv.Quote(string(sshPrivateKey))) 171 | } 172 | 173 | if len(pgpSecretKey) > 0 { 174 | fmt.Fprintf(out, "\tpgpSecretKey = []byte(%s)\n", strconv.Quote(string(pgpSecretKey))) 175 | fmt.Fprintf(out, "\tURL = %s\n", strconv.Quote(os.Getenv("URL"))) 176 | fmt.Fprintf(out, "\tNAME = %s\n", strconv.Quote(os.Getenv("NAME"))) 177 | fmt.Fprintf(out, "\tLANGUAGE = %s\n", strconv.Quote(os.Getenv("LANGUAGE"))) 178 | fmt.Fprintf(out, "\tSEX = %s\n", strconv.Quote(os.Getenv("SEX"))) 179 | } 180 | 181 | if len(u2fPublicKey) > 0 { 182 | fmt.Fprintf(out, "\tu2fPublicKey = []byte(%s)\n", strconv.Quote(string(u2fPublicKey))) 183 | } 184 | 185 | if len(u2fPrivateKey) > 0 { 186 | fmt.Fprintf(out, "\tu2fPrivateKey = []byte(%s)\n", strconv.Quote(string(u2fPrivateKey))) 187 | } 188 | 189 | out.WriteString(` 190 | } 191 | `) 192 | } 193 | 194 | func encrypt(path string, diversifier string) (output []byte, err error) { 195 | input, err := os.ReadFile(path) 196 | 197 | if err != nil { 198 | return 199 | } 200 | 201 | // It is advised to use only deterministic input data for key 202 | // derivation, therefore we use the empty allocated IV before it being 203 | // filled. 204 | iv := make([]byte, aes.BlockSize) 205 | key, err := deriveKey(diversifier, iv) 206 | 207 | if err != nil { 208 | return 209 | } 210 | _, err = io.ReadFull(rand.Reader, iv) 211 | 212 | if err != nil { 213 | return 214 | } 215 | 216 | output, err = snvs.Encrypt(input, key, iv) 217 | 218 | return 219 | } 220 | 221 | // equivalent to PKCS#11 C_DeriveKey with CKM_AES_CBC_ENCRYPT_DATA 222 | func deriveKey(diversifier string, iv []byte) (key []byte, err error) { 223 | if len(iv) != aes.BlockSize { 224 | return nil, errors.New("invalid IV size") 225 | } 226 | 227 | if len(diversifier) > aes.BlockSize { 228 | return nil, errors.New("invalid diversifier size") 229 | } 230 | 231 | fd, err := unix.Socket(unix.AF_ALG, unix.SOCK_SEQPACKET, 0) 232 | 233 | if err != nil { 234 | return 235 | } 236 | defer unix.Close(fd) 237 | 238 | addr := &unix.SockaddrALG{ 239 | Type: "skcipher", 240 | Name: "cbc-aes-dcp", 241 | } 242 | 243 | err = unix.Bind(fd, addr) 244 | 245 | if err != nil { 246 | return 247 | } 248 | 249 | // https://github.com/golang/go/issues/31277 250 | // SetsockoptString does not allow empty strings 251 | _, _, e1 := syscall.Syscall6(syscall.SYS_SETSOCKOPT, uintptr(fd), uintptr(unix.SOL_ALG), uintptr(unix.ALG_SET_KEY), uintptr(0), uintptr(0), 0) 252 | 253 | if e1 != 0 { 254 | err = errors.New("setsockopt failed") 255 | return 256 | } 257 | 258 | apifd, _, _ := unix.Syscall(unix.SYS_ACCEPT, uintptr(fd), 0, 0) 259 | 260 | return cryptoAPI(apifd, unix.ALG_OP_ENCRYPT, iv, icc.Pad([]byte(diversifier), false)) 261 | } 262 | 263 | func cryptoAPI(fd uintptr, mode uint32, iv []byte, input []byte) (output []byte, err error) { 264 | api := os.NewFile(fd, "cryptoAPI") 265 | 266 | cmsg := buildCmsg(mode, iv) 267 | 268 | output = make([]byte, len(input)) 269 | err = syscall.Sendmsg(int(fd), input, cmsg, nil, 0) 270 | 271 | if err != nil { 272 | return 273 | } 274 | 275 | _, err = api.Read(output) 276 | 277 | return 278 | } 279 | 280 | func buildCmsg(mode uint32, iv []byte) []byte { 281 | cbuf := make([]byte, syscall.CmsgSpace(4)+syscall.CmsgSpace(20)) 282 | 283 | cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&cbuf[0])) 284 | cmsg.Level = unix.SOL_ALG 285 | cmsg.Type = unix.ALG_SET_OP 286 | cmsg.SetLen(syscall.CmsgLen(4)) 287 | 288 | op := (*uint32)(unsafe.Pointer(CMSG_DATA(cmsg))) 289 | *op = mode 290 | 291 | cmsg = (*syscall.Cmsghdr)(unsafe.Pointer(&cbuf[syscall.CmsgSpace(4)])) 292 | cmsg.Level = unix.SOL_ALG 293 | cmsg.Type = unix.ALG_SET_IV 294 | cmsg.SetLen(syscall.CmsgLen(20)) 295 | 296 | alg_iv := (*af_alg_iv)(unsafe.Pointer(CMSG_DATA(cmsg))) 297 | alg_iv.ivlen = uint32(len(iv)) 298 | copy(alg_iv.iv[:], iv) 299 | 300 | return cbuf 301 | } 302 | 303 | func CMSG_DATA(cmsg *syscall.Cmsghdr) unsafe.Pointer { 304 | return unsafe.Pointer(uintptr(unsafe.Pointer(cmsg)) + uintptr(syscall.SizeofCmsghdr)) 305 | } 306 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package main 11 | 12 | import ( 13 | _ "unsafe" 14 | 15 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 16 | ) 17 | 18 | // GoKey does not log any sensitive information to the serial console, however 19 | // it is desirable to silence any potential stack trace or runtime errors to 20 | // avoid unwanted information leaks. 21 | // 22 | // The TamaGo board support for the USB armory Mk II enables the serial console 23 | // (UART2) at runtime initialization, which therefore invokes imx6.UART2.Init() 24 | // before init(). 25 | // 26 | // To this end the runtime printk function, responsible for all console logging 27 | // operations (i.e. stdout/stderr), is overridden with a NOP. Secondarily UART2 28 | // is disabled at the first opportunity (init()). 29 | 30 | func init() { 31 | // disable console 32 | imx6ul.UART2.Disable() 33 | } 34 | 35 | //go:linkname printk runtime.printk 36 | func printk(c byte) { 37 | // ensure that any serial output is supressed before UART2 disabling 38 | } 39 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/usbarmory/GoKey 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | filippo.io/age v1.2.1-0.20240926110859-2214a556f604 7 | filippo.io/keygen v0.0.0-20240718133620-7f162efbbd87 8 | github.com/ProtonMail/go-crypto v1.1.5 9 | github.com/google/go-p11-kit v0.4.0 10 | github.com/gsora/fidati v0.0.0-20230806170658-ab651720d7c3 11 | github.com/hsanjuan/go-nfctype4 v0.0.2 12 | github.com/usbarmory/armoryctl v0.0.0-20241003152241-189a1edd4402 13 | github.com/usbarmory/imx-usbnet v0.0.0-20250123113617-d39929cd7171 14 | github.com/usbarmory/tamago v0.0.0-20250212123402-5facf762488d 15 | golang.org/x/crypto v0.33.0 16 | ) 17 | 18 | require ( 19 | filippo.io/bigmod v0.0.3 // indirect 20 | github.com/albenik/go-serial/v2 v2.6.1 // indirect 21 | github.com/cloudflare/circl v1.3.7 // indirect 22 | github.com/creack/goselect v0.1.2 // indirect 23 | github.com/google/btree v1.1.2 // indirect 24 | go.uber.org/atomic v1.10.0 // indirect 25 | go.uber.org/multierr v1.9.0 // indirect 26 | golang.org/x/sys v0.30.0 // indirect 27 | golang.org/x/term v0.29.0 // indirect 28 | golang.org/x/time v0.5.0 // indirect 29 | gvisor.dev/gvisor v0.0.0-20240909175600-91fb8ad18db5 // indirect 30 | periph.io/x/conn/v3 v3.7.1 // indirect 31 | periph.io/x/host/v3 v3.8.2 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0= 2 | c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w= 3 | filippo.io/age v1.2.1-0.20240926110859-2214a556f604 h1:LeljYZXJZFcoXQh8p+C5GGzI2A0M2mxaDileBHw3ch4= 4 | filippo.io/age v1.2.1-0.20240926110859-2214a556f604/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004= 5 | filippo.io/bigmod v0.0.3 h1:qmdCFHmEMS+PRwzrW6eUrgA4Q3T8D6bRcjsypDMtWHM= 6 | filippo.io/bigmod v0.0.3/go.mod h1:WxGvOYE0OUaBC2N112Dflb3CjOnMBuNRA2UWZc2UbPE= 7 | filippo.io/keygen v0.0.0-20240718133620-7f162efbbd87 h1:HlcHAMbI9Xvw3aWnhPngghMl5AKE2GOvjmvSGOKzCcI= 8 | filippo.io/keygen v0.0.0-20240718133620-7f162efbbd87/go.mod h1:nAs0+DyACEQGudhkTwlPC9atyqDYC7ZotgZR7D8OwXM= 9 | github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= 10 | github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 11 | github.com/albenik/go-serial/v2 v2.6.1 h1:AhVjPVegSa/loFUmaIPNdhbeL/+6b+pCNgeCJ9CT7W8= 12 | github.com/albenik/go-serial/v2 v2.6.1/go.mod h1:sqQA6eeZHKUB6rAgrBsP/8d3Go5Md5cjCof1WcyaK0o= 13 | github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= 14 | github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= 15 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 16 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 17 | github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= 18 | github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 23 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 24 | github.com/google/go-p11-kit v0.4.0 h1:2HCRptPun8gkfOJH6u8goMjCcGESuJpMx2ugozLtiL8= 25 | github.com/google/go-p11-kit v0.4.0/go.mod h1:tg3TK33e/pkQUoXAun7q1zD5VwmYcN20sPaMl4l3hJo= 26 | github.com/gsora/fidati v0.0.0-20230806170658-ab651720d7c3 h1:zugXhdIprbuLMfR3ATkt5+YRx9VMBJgjPn1IDwluvJs= 27 | github.com/gsora/fidati v0.0.0-20230806170658-ab651720d7c3/go.mod h1:pqELFmXT+lU57T8pIGwPSOODIvRv/r/lwxlJX0UupvY= 28 | github.com/hsanjuan/go-nfctype4 v0.0.2 h1:rp4gUWhjouLEYb14QDiA+9Z6AX55UOm6WTEenfhzZ+0= 29 | github.com/hsanjuan/go-nfctype4 v0.0.2/go.mod h1:x4ZfW+fg20SjD3ewqUYnv9F1uKPB+d+vYmZsACWBmSs= 30 | github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= 31 | github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 37 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 38 | github.com/usbarmory/armoryctl v0.0.0-20241003152241-189a1edd4402 h1:puoJCVBatBxCBY3pFg2C2ZiNhEdKVPYvWheTCSAW/ro= 39 | github.com/usbarmory/armoryctl v0.0.0-20241003152241-189a1edd4402/go.mod h1:l0cQgyvVZpVios8cJHGGlvZxGHfy0UH5wqTBxlT1nmc= 40 | github.com/usbarmory/imx-usbnet v0.0.0-20250123113617-d39929cd7171 h1:0xXzXU689aEIa/UQ1wByXPbk+PogCKMMFoW8rFodQlI= 41 | github.com/usbarmory/imx-usbnet v0.0.0-20250123113617-d39929cd7171/go.mod h1:zvzUu4SfzoCEDVnxzga81SoK3ezsW8gjxck2v1Ry1zw= 42 | github.com/usbarmory/tamago v0.0.0-20220823080407-04f05cf2a5a3/go.mod h1:Lok79mjbJnhoBGqhX5cCUsZtSemsQF5FNZW+2R1dRr8= 43 | github.com/usbarmory/tamago v0.0.0-20250212123402-5facf762488d h1:V3R6r4ofDFTZAlz2iGpeSXJ97OMZcBhdS0bskfPbwxw= 44 | github.com/usbarmory/tamago v0.0.0-20250212123402-5facf762488d/go.mod h1:NL88q9ZsIPYFzXaosAeKgu1Kr5i1k4Rau3wnbNBL5bY= 45 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 46 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 47 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 48 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 49 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 50 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 51 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 52 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 53 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 54 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 55 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 56 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 57 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= 58 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 61 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 62 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 63 | gvisor.dev/gvisor v0.0.0-20240909175600-91fb8ad18db5 h1:hpXKYYLBqtz3Le2H17xbQsWhb254+QOGuuVS2orihjo= 64 | gvisor.dev/gvisor v0.0.0-20240909175600-91fb8ad18db5/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= 65 | periph.io/x/conn/v3 v3.7.1 h1:tMjNv3WO8jEz/ePuXl7y++2zYi8LsQ5otbmqGKy3Myg= 66 | periph.io/x/conn/v3 v3.7.1/go.mod h1:c+HCVjkzbf09XzcqZu/t+U8Ss/2QuJj0jgRF6Nye838= 67 | periph.io/x/host/v3 v3.8.2 h1:ayKUDzgUCN0g8+/xM9GTkWaOBhSLVcVHGTfjAOi8OsQ= 68 | periph.io/x/host/v3 v3.8.2/go.mod h1:yFL76AesNHR68PboofSWYaQTKmvPXsQH2Apvp/ls/K4= 69 | -------------------------------------------------------------------------------- /gokey_imx6.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "log" 15 | "os" 16 | "runtime" 17 | 18 | "github.com/usbarmory/GoKey/internal/age" 19 | "github.com/usbarmory/GoKey/internal/ccid" 20 | "github.com/usbarmory/GoKey/internal/icc" 21 | "github.com/usbarmory/GoKey/internal/u2f" 22 | "github.com/usbarmory/GoKey/internal/usb" 23 | 24 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 25 | imxusb "github.com/usbarmory/tamago/soc/nxp/usb" 26 | 27 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 28 | 29 | "github.com/usbarmory/imx-usbnet" 30 | ) 31 | 32 | const ( 33 | deviceIP = "10.0.0.10" 34 | deviceMAC = "1a:55:89:a2:69:41" 35 | hostMAC = "1a:55:89:a2:69:42" 36 | ) 37 | 38 | func init() { 39 | imx6ul.SetARMFreq(imx6ul.FreqMax) 40 | } 41 | 42 | func initCard(device *imxusb.Device, card *icc.Interface) { 43 | // Initialize an OpenPGP card with the bundled key information (defined 44 | // in `keys.go` and generated at compilation time). 45 | card.SNVS = SNVS 46 | card.ArmoredKey = pgpSecretKey 47 | card.Name = NAME 48 | card.Language = LANGUAGE 49 | card.Sex = SEX 50 | card.URL = URL 51 | card.Debug = false 52 | 53 | if initAtBoot { 54 | if err := card.Init(); err != nil { 55 | log.Printf("OpenPGP ICC initialization error: %v", err) 56 | } 57 | } 58 | 59 | // initialize CCID interface 60 | reader := &ccid.Interface{ 61 | ICC: card, 62 | } 63 | 64 | // configure Smart Card over USB endpoints (CCID protocol) 65 | usb.ConfigureCCID(device, reader) 66 | } 67 | 68 | func initToken(device *imxusb.Device, token *u2f.Token) { 69 | token.SNVS = SNVS 70 | token.PublicKey = u2fPublicKey 71 | token.PrivateKey = u2fPrivateKey 72 | 73 | if err := u2f.Configure(device, token); err != nil { 74 | log.Printf("U2F configuration error: %v", err) 75 | } 76 | 77 | if initAtBoot { 78 | if err := token.Init(); err != nil { 79 | log.Printf("U2F initialization error: %v", err) 80 | } 81 | } 82 | } 83 | 84 | func main() { 85 | device := &imxusb.Device{} 86 | card := &icc.Interface{} 87 | token := &u2f.Token{} 88 | 89 | log.SetFlags(0) 90 | log.SetOutput(os.Stdout) 91 | 92 | // set card serial number to 2nd half of NXP Unique ID 93 | uid := imx6ul.UniqueID() 94 | copy(card.Serial[0:4], uid[4:8]) 95 | 96 | usb.ConfigureDevice(device, fmt.Sprintf("%X", card.Serial)) 97 | 98 | if SNVS && !imx6ul.SNVS.Available() { 99 | log.Fatalf("SNVS not available") 100 | } 101 | 102 | if len(pgpSecretKey) != 0 { 103 | initCard(device, card) 104 | } 105 | 106 | if len(u2fPublicKey) != 0 && len(u2fPrivateKey) != 0 { 107 | initToken(device, token) 108 | } 109 | 110 | if len(sshPublicKey) != 0 { 111 | configureNetworking(device, card, token) 112 | } 113 | 114 | // The plug is checked, rather than the receptacle, as a workaround for: 115 | // https://github.com/usbarmory/usbarmory/wiki/Errata-(Mk-II)#errata-type-c-plugreceptacle-reset-plug-resolved-receptacle-workaround 116 | mode, _ := usbarmory.FrontPortMode() 117 | port := usbarmory.USB1 118 | 119 | if mode == usbarmory.STATE_NOT_ATTACHED { 120 | port = usbarmory.USB2 121 | } 122 | 123 | port.Init() 124 | port.Device = device 125 | port.DeviceMode() 126 | 127 | usb.StartInterruptHandler(port) 128 | } 129 | 130 | func configureNetworking(device *imxusb.Device, card *icc.Interface, token *u2f.Token) { 131 | gonet := usbnet.Interface{} 132 | 133 | if err := gonet.Add(device, deviceIP, deviceMAC, hostMAC); err != nil { 134 | log.Fatalf("could not initialize USB networking, %v", err) 135 | } 136 | 137 | gonet.EnableICMP() 138 | 139 | listener, err := gonet.ListenerTCP4(22) 140 | 141 | if err != nil { 142 | log.Fatalf("could not initialize SSH listener, %v", err) 143 | } 144 | 145 | banner := fmt.Sprintf("GoKey • %s/%s (%s)", 146 | runtime.GOOS, runtime.GOARCH, runtime.Version()) 147 | 148 | console := &usb.Console{ 149 | AuthorizedKey: sshPublicKey, 150 | PrivateKey: sshPrivateKey, 151 | Card: card, 152 | Token: token, 153 | Started: make(chan bool), 154 | Listener: listener, 155 | Banner: banner, 156 | } 157 | 158 | console.Plugin = &age.Plugin{ 159 | SNVS: SNVS, 160 | } 161 | 162 | if err = console.Plugin.Init(); err != nil { 163 | log.Printf("age plugin initialization error: %v", err) 164 | } 165 | 166 | // start SSH server for management console 167 | if err = console.Start(); err != nil { 168 | log.Printf("SSH server initialization error: %v", err) 169 | } 170 | 171 | // wait for ssh server to start before responding to USB requests 172 | <-console.Started 173 | } 174 | -------------------------------------------------------------------------------- /gokey_vpcd.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build vpcd 9 | 10 | package main 11 | 12 | import ( 13 | "encoding/binary" 14 | "flag" 15 | "fmt" 16 | "io" 17 | "log" 18 | "net" 19 | "os" 20 | "path/filepath" 21 | "time" 22 | 23 | "github.com/usbarmory/GoKey/internal/icc" 24 | ) 25 | 26 | var server string 27 | 28 | // http://frankmorgner.github.io/vsmartcard/virtualsmartcard/api.html 29 | const ( 30 | POWER_OFF = 0x00 31 | POWER_ON = 0x01 32 | RESET = 0x02 33 | GET_ATR = 0x04 34 | ) 35 | 36 | // dummyUID for virtual smart card operation 37 | var dummyUID = [4]byte{0xaa, 0xbb, 0xcc, 0xdd} 38 | 39 | func init() { 40 | log.SetFlags(0) 41 | log.SetOutput(os.Stdout) 42 | 43 | flag.StringVar(&server, "c", "127.0.0.1:35963", "vpcd address:port pair") 44 | } 45 | 46 | func main() { 47 | flag.Parse() 48 | 49 | // Initialize an OpenPGP card with the bundled key information (defined 50 | // in `keys.go` and generated at compilation time). 51 | card := &icc.Interface{ 52 | Serial: dummyUID, 53 | SNVS: SNVS, 54 | ArmoredKey: pgpSecretKey, 55 | Name: NAME, 56 | Language: LANGUAGE, 57 | Sex: SEX, 58 | URL: URL, 59 | Debug: true, 60 | } 61 | 62 | if err := card.Init(); err != nil { 63 | log.Printf("initialization error: %v", err) 64 | } 65 | 66 | go serveRPC(card) 67 | 68 | // never returns 69 | dialVPCD(card) 70 | } 71 | 72 | func serveRPC(card *icc.Interface) { 73 | tmp, err := os.MkdirTemp("", "") 74 | 75 | if err != nil { 76 | log.Fatalf("error creating temporary directory %v", err) 77 | } 78 | defer os.RemoveAll(tmp) 79 | 80 | path := filepath.Join(tmp, "p11kit.sock") 81 | 82 | l, err := net.Listen("unix", path) 83 | 84 | if err != nil { 85 | log.Fatalf("listening on %s: %v", path, err) 86 | } 87 | defer l.Close() 88 | 89 | log.Printf("export P11_KIT_SERVER_ADDRESS=unix:path=%s", path) 90 | 91 | for { 92 | conn, err := l.Accept() 93 | 94 | if err != nil { 95 | log.Printf("cannot accept, %v", err) 96 | continue 97 | } 98 | 99 | go func() { 100 | if err := card.ServeRPC(conn); err != nil { 101 | log.Printf("cannot handle request, %v", err) 102 | } 103 | conn.Close() 104 | }() 105 | } 106 | } 107 | 108 | func dialVPCD(card *icc.Interface) { 109 | for { 110 | conn, err := net.Dial("tcp", server) 111 | 112 | if err != nil { 113 | log.Printf("retrying (%v)", err) 114 | conn.Close() 115 | 116 | time.Sleep(1 * time.Second) 117 | continue 118 | } 119 | 120 | handleVPCDConnection(conn, card) 121 | } 122 | } 123 | 124 | func handleVPCDConnection(conn net.Conn, card *icc.Interface) { 125 | defer conn.Close() 126 | 127 | for { 128 | length := make([]byte, 2) 129 | 130 | if _, err := conn.Read(length); err != nil { 131 | log.Printf("cannot read, %v", err) 132 | return 133 | } 134 | 135 | res, err := handleVPCDRequest(conn, length, card) 136 | 137 | if err != nil { 138 | log.Fatalf("cannot handle request, %v", err) 139 | } 140 | 141 | if _, err = conn.Write(res); err != nil { 142 | log.Printf("cannot send response, %v", err) 143 | return 144 | } 145 | } 146 | } 147 | 148 | func handleVPCDRequest(conn net.Conn, length []byte, card *icc.Interface) (res []byte, err error) { 149 | if len(length) < 2 { 150 | err = fmt.Errorf("request too short (%d)", len(length)) 151 | return 152 | } 153 | 154 | n := binary.BigEndian.Uint16(length[0:2]) 155 | 156 | if n < 1 { 157 | err = fmt.Errorf("length too short (%d)", n) 158 | return 159 | } 160 | 161 | req := make([]byte, n) 162 | io.ReadAtLeast(conn, req, int(n)) 163 | 164 | if card.Debug { 165 | log.Printf("vpcd << %x", req) 166 | } 167 | 168 | if n == 1 { 169 | switch req[0] { 170 | case POWER_OFF, POWER_ON, RESET: 171 | // no response 172 | return 173 | case GET_ATR: 174 | res = card.ATR() 175 | } 176 | } else { 177 | if res, err = card.RawCommand(req[0:]); err != nil { 178 | return 179 | } 180 | } 181 | 182 | length = make([]byte, 2) 183 | binary.BigEndian.PutUint16(length, uint16(len(res))) 184 | res = append(length, res...) 185 | 186 | if card.Debug { 187 | log.Printf("vpcd >> %x", res) 188 | } 189 | 190 | return 191 | } 192 | -------------------------------------------------------------------------------- /internal/age/plugin.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package age 9 | 10 | import ( 11 | "crypto/aes" 12 | "crypto/rand" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "log" 17 | 18 | "filippo.io/age" 19 | "filippo.io/age/plugin" 20 | 21 | "github.com/usbarmory/GoKey/internal/snvs" 22 | ) 23 | 24 | // Diversifier for hardware key derivation (age private key wrapping). 25 | const DiversifierAGE = "GoKeySNVSAGE " 26 | 27 | type Plugin struct { 28 | // enable device unique hardware encryption for bundled private keys 29 | SNVS bool 30 | 31 | // age plugin instance 32 | plugin *plugin.Plugin 33 | 34 | // internal state flags 35 | initialized bool 36 | } 37 | 38 | func (p *Plugin) generate() (res string, err error) { 39 | identity, err := age.GenerateX25519Identity() 40 | 41 | if err != nil { 42 | return 43 | } 44 | 45 | iv := make([]byte, aes.BlockSize) 46 | 47 | if _, err = rand.Read(iv); err != nil { 48 | return 49 | } 50 | 51 | output, err := snvs.Encrypt([]byte(identity.String()), []byte(DiversifierAGE), iv) 52 | 53 | if err != nil { 54 | return 55 | } 56 | 57 | res = fmt.Sprintf("# public key: %s\n%s", 58 | identity.Recipient().String(), 59 | plugin.EncodeIdentity("GOKEY", output), 60 | ) 61 | 62 | return 63 | } 64 | 65 | func (p *Plugin) identity(rw io.ReadWriter) (err error) { 66 | p.plugin.SetIO(rw, rw, log.Default().Writer()) 67 | 68 | if c := p.plugin.IdentityV1(); c != 0 { 69 | return fmt.Errorf("exit code: %v", c) 70 | } 71 | 72 | return 73 | } 74 | 75 | func (p *Plugin) Init() (err error) { 76 | ap, err := plugin.New("gokey") 77 | 78 | if err != nil { 79 | return 80 | } 81 | 82 | ap.HandleRecipient(func(data []byte) (age.Recipient, error) { 83 | return nil, errors.New("unsupported") 84 | }) 85 | 86 | ap.HandleIdentity(func(data []byte) (age.Identity, error) { 87 | log.Printf("age/plugin identity-v1 data:%x", data) 88 | 89 | s, err := snvs.Decrypt(data, []byte(DiversifierAGE)) 90 | 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return age.ParseX25519Identity(string(s)) 96 | }) 97 | 98 | p.plugin = ap 99 | p.initialized = true 100 | 101 | return 102 | } 103 | 104 | // Initialized returns the age plugin initialization state. 105 | func (p *Plugin) Initialized() bool { 106 | return p.initialized 107 | } 108 | 109 | func (p *Plugin) Handle(rw io.ReadWriter, sm string) (res string) { 110 | var err error 111 | 112 | if !p.SNVS { 113 | return "SNVS not available" 114 | } 115 | 116 | switch sm { 117 | case "gen": 118 | res, err = p.generate() 119 | case "identity-v1": 120 | err = p.identity(rw) 121 | default: 122 | return "unsupported" 123 | } 124 | 125 | if err != nil { 126 | return err.Error() 127 | } 128 | 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /internal/ccid/buf.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | ) 14 | 15 | // Serialize a structure to a byte array. 16 | func Serialize(data interface{}) ([]byte, error) { 17 | buf := new(bytes.Buffer) 18 | err := binary.Write(buf, binary.LittleEndian, data) 19 | 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return buf.Bytes(), nil 25 | } 26 | 27 | // Data extracts the abData field contents. 28 | func Data(buf []byte, length uint32) (data []byte) { 29 | if length == 0 { 30 | return 31 | } 32 | 33 | if off := len(buf) - int(length); off > 0 { 34 | data = buf[off:] 35 | } 36 | 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /internal/ccid/data_block.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | const MAX_DATA_BLOCK = 65538 11 | 12 | // DataBlock p49, 6.2.1 RDR_to_PC_DataBlock, CCID Rev1.1. 13 | type DataBlock struct { 14 | MessageType uint8 15 | Length uint32 16 | Slot uint8 17 | Seq uint8 18 | Status uint8 19 | Error uint8 20 | ChainParameter uint8 21 | } 22 | -------------------------------------------------------------------------------- /internal/ccid/get_parameters.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | import ( 11 | "github.com/usbarmory/GoKey/internal/icc" 12 | ) 13 | 14 | // GetParameters implements p31, 6.1.5 PC_to_RDR_GetParameters, CCID Rev1.1. 15 | type GetParameters struct { 16 | MessageType uint8 17 | Length uint32 18 | Slot uint8 19 | Seq uint8 20 | RFU [3]byte 21 | } 22 | 23 | // Parameters implements p51, 6.2.3 RDR_to_PC_Parameters, CCID Rev1.1. 24 | type Parameters struct { 25 | MessageType uint8 26 | Length uint32 27 | Slot uint8 28 | Seq uint8 29 | Status uint8 30 | Error uint8 31 | ProtocolNum uint8 32 | } 33 | 34 | // Handle get/reset/set parameters requests. 35 | func (cmd *GetParameters) Handle(_ []byte, _ *icc.Interface) ([]byte, error) { 36 | res := &Parameters{ 37 | MessageType: PARAMETERS, 38 | Slot: cmd.Slot, 39 | Seq: cmd.Seq, 40 | Status: ICC_PRESENT_AND_ACTIVE, 41 | ProtocolNum: 0x01, // indicate use of T=1 42 | } 43 | 44 | return Serialize(res) 45 | } 46 | -------------------------------------------------------------------------------- /internal/ccid/icc_power.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | import ( 11 | "github.com/usbarmory/GoKey/internal/icc" 12 | ) 13 | 14 | // IccPowerOn implements p26, 6.1.1 PC_to_RDR_IccPowerOn, CCID Rev1.1. 15 | type IccPowerOn struct { 16 | MessageType uint8 17 | Length uint32 18 | Slot uint8 19 | Seq uint8 20 | PowerSelect uint8 21 | RFU [2]byte 22 | } 23 | 24 | // IccPowerOff implements p28, 6.1.2 PC_to_RDR_IccPowerOff, CCID Rev1.1. 25 | type IccPowerOff struct { 26 | MessageType uint8 27 | Length uint32 28 | Slot uint8 29 | Seq uint8 30 | RFU [3]byte 31 | } 32 | 33 | // Handle ICC power on requests by returning the ATR. 34 | func (cmd *IccPowerOn) Handle(_ []byte, card *icc.Interface) (buf []byte, err error) { 35 | res := &DataBlock{ 36 | MessageType: DATA_BLOCK, 37 | Slot: cmd.Slot, 38 | Seq: cmd.Seq, 39 | } 40 | 41 | atr := card.ATR() 42 | res.Length = uint32(len(atr)) 43 | 44 | if buf, err = Serialize(res); err != nil { 45 | return 46 | } 47 | 48 | buf = append(buf, atr...) 49 | 50 | return 51 | } 52 | 53 | // Handle ICC power off requests (NOP, card always active). 54 | func (cmd *IccPowerOff) Handle(_ []byte, _ *icc.Interface) ([]byte, error) { 55 | res := &SlotStatus{ 56 | MessageType: SLOT_STATUS, 57 | Slot: cmd.Slot, 58 | Seq: cmd.Seq, 59 | } 60 | 61 | return Serialize(res) 62 | } 63 | -------------------------------------------------------------------------------- /internal/ccid/interface.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/usbarmory/GoKey/internal/icc" 17 | ) 18 | 19 | // p26, Table 6.1-1, CCID Rev1.1 20 | const ( 21 | ICC_POWER_ON = 0x62 22 | ICC_POWER_OFF = 0x63 23 | GET_SLOT_STATUS = 0x65 24 | XFR_BLOCK = 0x6f 25 | GET_PARAMETERS = 0x6c 26 | RESET_PARAMETERS = 0x6d 27 | SET_PARAMETERS = 0x61 28 | ) 29 | 30 | // p48, Table 6.2-1, CCID Rev1.1 31 | const ( 32 | DATA_BLOCK = 0x80 33 | SLOT_STATUS = 0x81 34 | PARAMETERS = 0x82 35 | ) 36 | 37 | // Interface implements a CCID compliant USB smartcard reader. 38 | type Interface struct { 39 | ICC *icc.Interface 40 | } 41 | 42 | // CCIDCommand is the interface of individual CCID command handlers. 43 | type CCIDCommand interface { 44 | Handle(buf []byte, card *icc.Interface) (res []byte, err error) 45 | } 46 | 47 | // Rx handles incoming CCID commands and invokes the relevant command handler. 48 | func (ccid *Interface) Rx(buf []byte) (res []byte, err error) { 49 | var cmd CCIDCommand 50 | 51 | if len(buf) == 0 { 52 | return nil, errors.New("invalid CCID command, too short") 53 | } 54 | 55 | if buf[0] != GET_SLOT_STATUS { 56 | ccid.ICC.Wake() 57 | } 58 | 59 | switch buf[0] { 60 | case ICC_POWER_ON: 61 | cmd = &IccPowerOn{} 62 | case ICC_POWER_OFF: 63 | cmd = &IccPowerOff{} 64 | case GET_SLOT_STATUS: 65 | cmd = &GetSlotStatus{} 66 | case XFR_BLOCK: 67 | cmd = &XfrBlock{} 68 | case GET_PARAMETERS, RESET_PARAMETERS, SET_PARAMETERS: 69 | cmd = &GetParameters{} 70 | default: 71 | return nil, fmt.Errorf("invalid CCID command, unsupported: %x", buf) 72 | } 73 | 74 | if err = binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, cmd); err != nil { 75 | return 76 | } 77 | 78 | return cmd.Handle(buf, ccid.ICC) 79 | } 80 | -------------------------------------------------------------------------------- /internal/ccid/slot_status.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | import ( 11 | "github.com/usbarmory/GoKey/internal/icc" 12 | ) 13 | 14 | const ( 15 | // p55, Table 6.2-3 Slot Status register, CCID Rev1.1 16 | ICC_PRESENT_AND_ACTIVE = 0 17 | FAILED = 1 << 6 18 | ) 19 | 20 | // GetSlotStatus implements p29, 6.1.3 PC_to_RDR_GetSlotStatus, CCID Rev1.1. 21 | type GetSlotStatus struct { 22 | MessageType uint8 23 | Length uint32 24 | Slot uint8 25 | Seq uint8 26 | RFU [3]byte 27 | } 28 | 29 | // SlotStatus implements p50, 6.2.2 RDR_to_PC_SlotStatus, CCID Rev1.1. 30 | type SlotStatus struct { 31 | MessageType uint8 32 | Length uint32 33 | Slot uint8 34 | Seq uint8 35 | Status uint8 36 | Error uint8 37 | ClockStatus uint8 38 | } 39 | 40 | // Handle slot status requests (NOP, card always active). 41 | func (cmd *GetSlotStatus) Handle(_ []byte, _ *icc.Interface) ([]byte, error) { 42 | res := &SlotStatus{ 43 | MessageType: SLOT_STATUS, 44 | Slot: cmd.Slot, 45 | Seq: cmd.Seq, 46 | Status: ICC_PRESENT_AND_ACTIVE, 47 | } 48 | 49 | return Serialize(res) 50 | } 51 | -------------------------------------------------------------------------------- /internal/ccid/xfr_block.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package ccid 9 | 10 | import ( 11 | "github.com/usbarmory/GoKey/internal/icc" 12 | ) 13 | 14 | const ( 15 | BAD_LEVEL_PARAMETER = 8 16 | ) 17 | 18 | // XfrBlock implements p30, 6.1.4 PC_to_RDR_XfrBlock, CCID Rev1.1. 19 | type XfrBlock struct { 20 | MessageType uint8 21 | Length uint32 22 | Slot uint8 23 | Seq uint8 24 | BWI uint8 25 | LevelParameter uint16 26 | } 27 | 28 | // Handle APDU transfer requests. 29 | func (cmd *XfrBlock) Handle(buf []byte, card *icc.Interface) (resBuf []byte, err error) { 30 | res := &DataBlock{ 31 | MessageType: DATA_BLOCK, 32 | Slot: cmd.Slot, 33 | Seq: cmd.Seq, 34 | } 35 | 36 | if cmd.LevelParameter != 0 { 37 | res.Status = FAILED 38 | res.Error = BAD_LEVEL_PARAMETER 39 | return Serialize(res) 40 | } 41 | 42 | resData, err := card.RawCommand(Data(buf, cmd.Length)) 43 | 44 | if err != nil { 45 | return 46 | } 47 | 48 | res.Length = uint32(len(resData)) 49 | 50 | resBuf, err = Serialize(res) 51 | resBuf = append(resBuf, resData...) 52 | 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /internal/icc/apdu.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "github.com/hsanjuan/go-nfctype4/apdu" 12 | ) 13 | 14 | func CommandNotAllowed() *apdu.RAPDU { 15 | return apdu.NewRAPDU(apdu.RAPDUCommandNotAllowed) 16 | } 17 | 18 | func FileNotFound() *apdu.RAPDU { 19 | return apdu.NewRAPDU(apdu.RAPDUFileNotFound) 20 | } 21 | 22 | func CardKeyNotSupported() *apdu.RAPDU { 23 | return &apdu.RAPDU{ 24 | SW1: 0x63, 25 | SW2: 0x82, 26 | } 27 | } 28 | 29 | func WrongData() *apdu.RAPDU { 30 | return &apdu.RAPDU{ 31 | SW1: 0x6a, 32 | SW2: 0x80, 33 | } 34 | } 35 | 36 | func ReferencedDataNotFound() *apdu.RAPDU { 37 | return &apdu.RAPDU{ 38 | SW1: 0x6a, 39 | SW2: 0x88, 40 | } 41 | } 42 | 43 | func SecurityConditionNotSatisfied() *apdu.RAPDU { 44 | return &apdu.RAPDU{ 45 | SW1: 0x69, 46 | SW2: 0x82, 47 | } 48 | } 49 | 50 | func UnrecoverableError() *apdu.RAPDU { 51 | return &apdu.RAPDU{ 52 | SW1: 0x91, 53 | SW2: 0xa1, 54 | } 55 | } 56 | 57 | func CommandCompleted(data []byte) *apdu.RAPDU { 58 | return &apdu.RAPDU{ 59 | SW1: 0x90, 60 | SW2: 0x00, 61 | ResponseBody: data, 62 | } 63 | } 64 | 65 | func VerifyFail(retries byte) *apdu.RAPDU { 66 | return &apdu.RAPDU{ 67 | SW1: 0x63, 68 | SW2: retries, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /internal/icc/crypto.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "bytes" 12 | "crypto" 13 | "crypto/aes" 14 | "crypto/rand" 15 | "crypto/rsa" 16 | "log" 17 | 18 | "github.com/ProtonMail/go-crypto/openpgp/ecdh" 19 | "github.com/ProtonMail/go-crypto/openpgp/ecdsa" 20 | "github.com/hsanjuan/go-nfctype4/apdu" 21 | ) 22 | 23 | const ( 24 | // p65, 7.2.11 PSO: DECIPHER, OpenPGP application Version 3.4 25 | RSA_PADDING = 0x00 26 | AES_PADDING = 0x02 27 | ) 28 | 29 | func padToKeySize(pub ecdsa.PublicKey, b []byte) []byte { 30 | // RFC 4880 - OpenPGP Message Format: 31 | // The size of an MPI is ((MPI.length + 7) / 8) + 2 octets. 32 | k := ((pub.X.BitLen() + 7) / 8) + 2 33 | if len(b) >= k { 34 | return b 35 | } 36 | bb := make([]byte, k) 37 | copy(bb[len(bb)-len(b):], b) 38 | return bb 39 | } 40 | 41 | // ComputeDigitalSignature implements 42 | // p62, 7.2.10 PSO: COMPUTE DIGITAL SIGNATURE, OpenPGP application Version 3.4. 43 | func (card *Interface) ComputeDigitalSignature(data []byte) (rapdu *apdu.RAPDU, err error) { 44 | var sig []byte 45 | 46 | if len(data) == 0 { 47 | return WrongData(), nil 48 | } 49 | 50 | subkey := card.Sig 51 | 52 | if subkey == nil || subkey.PrivateKey == nil { 53 | log.Printf("missing private key for PSO:COMPUTE DIGITAL SIGNATURE") 54 | return CardKeyNotSupported(), nil 55 | } 56 | 57 | if subkey.PrivateKey.Encrypted { 58 | return SecurityConditionNotSatisfied(), nil 59 | } 60 | 61 | if PW1_CDS_MULTI == 0 { 62 | defer card.Verify(PW_LOCK, PW1_CDS, nil) 63 | } 64 | 65 | switch privKey := subkey.PrivateKey.PrivateKey.(type) { 66 | case *rsa.PrivateKey: 67 | var hash crypto.Hash 68 | 69 | // p64, 7.2.10.2 DigestInfo for RSA, OpenPGP application Version 3.4 70 | 71 | // 19 bytes of DigestInfo + at least 32 bytes for smallest hash 72 | if len(data) < 19+32 { 73 | return WrongData(), nil 74 | } 75 | 76 | digest := data[19:] 77 | 78 | switch len(digest) { 79 | case 32: 80 | hash = crypto.SHA256 81 | case 48: 82 | hash = crypto.SHA384 83 | case 64: 84 | hash = crypto.SHA512 85 | default: 86 | return WrongData(), nil 87 | } 88 | 89 | sig, err = rsa.SignPKCS1v15(rand.Reader, privKey, hash, digest) 90 | case *ecdsa.PrivateKey: 91 | // OpenPGP uses ECDSA signatures in raw format 92 | r, s, e := ecdsa.Sign(rand.Reader, privKey, data) 93 | 94 | if e != nil { 95 | err = e 96 | } else { 97 | // https://tools.ietf.org/html/rfc7518#section-3.4 98 | // 99 | // "...adds zero-valued high-order padding bits when 100 | // needed to round the size up to a multiple of 8 bits; 101 | // thus, each 521-bit integer is represented using 528 102 | // bits in 66 octets." 103 | sig = append(sig, padToKeySize(privKey.PublicKey, r.Bytes())...) 104 | sig = append(sig, padToKeySize(privKey.PublicKey, s.Bytes())...) 105 | } 106 | default: 107 | log.Printf("invalid private key for PSO:COMPUTE DIGITAL SIGNATURE") 108 | return CardKeyNotSupported(), nil 109 | } 110 | 111 | if err != nil { 112 | log.Printf("PSO:COMPUTE DIGITAL SIGNATURE error, %v", err) 113 | return UnrecoverableError(), nil 114 | } 115 | 116 | log.Printf("PSO:CDS successful") 117 | card.digitalSignatureCounter += 1 118 | 119 | return CommandCompleted(sig), nil 120 | } 121 | 122 | // Decipher implements 123 | // p65, 7.2.11 PSO: DECIPHER, OpenPGP application Version 3.4. 124 | func (card *Interface) Decipher(data []byte) (rapdu *apdu.RAPDU, err error) { 125 | var plaintext []byte 126 | 127 | if len(data) < 1 { 128 | return WrongData(), nil 129 | } 130 | 131 | if data[0] == AES_PADDING { 132 | return decipher(data[1:]) 133 | } 134 | 135 | subkey := card.Dec 136 | 137 | if subkey == nil || subkey.PrivateKey == nil { 138 | log.Printf("missing private key for PSO:DEC") 139 | return CardKeyNotSupported(), nil 140 | } 141 | 142 | if subkey.PrivateKey.Encrypted { 143 | return SecurityConditionNotSatisfied(), nil 144 | } 145 | 146 | switch privKey := subkey.PrivateKey.PrivateKey.(type) { 147 | case *rsa.PrivateKey: 148 | if data[0] != RSA_PADDING { 149 | log.Printf("invalid private key for PSO:DEC") 150 | return CardKeyNotSupported(), nil 151 | } 152 | 153 | plaintext, err = privKey.Decrypt(rand.Reader, data[1:], nil) 154 | case *ecdh.PrivateKey: 155 | if data[0] != DO_CIPHER { 156 | log.Printf("invalid private key for PSO:DEC") 157 | return CardKeyNotSupported(), nil 158 | } 159 | 160 | // p66, 7.2.11 PSO: DECIPHER, OpenPGP application Version 3.4 161 | pubKey := v(v(v(data, DO_CIPHER), DO_PUB_KEY), DO_EXT_PUB_KEY) 162 | expectedSize := (len(pubKey) - 1) / 2 163 | 164 | if len(pubKey) < 1 || pubKey[0] != 0x04 || expectedSize*2 != len(pubKey)-1 { 165 | return WrongData(), nil 166 | } 167 | 168 | plaintext, err = privKey.GetCurve().Decaps(pubKey, privKey.D) 169 | plaintext = append(make([]byte, expectedSize-len(plaintext)), plaintext...) 170 | default: 171 | log.Printf("invalid private key for PSO:DEC") 172 | return CardKeyNotSupported(), nil 173 | } 174 | 175 | if err != nil { 176 | log.Printf("PSO:DEC error, %v", err) 177 | return UnrecoverableError(), nil 178 | } 179 | 180 | log.Printf("PSO:DEC successful") 181 | 182 | return CommandCompleted(plaintext), nil 183 | } 184 | 185 | // Encipher implements 186 | // p68, 7.2.12 PSO: ENCIPHER, OpenPGP application Version 3.4. 187 | func (card *Interface) Encipher(data []byte) (rapdu *apdu.RAPDU, err error) { 188 | // PSO:ENC does not use any OpenPGP key but we still use the decryption 189 | // subkey for cardholder authentication. 190 | subkey := card.Dec 191 | 192 | if subkey.PrivateKey.Encrypted { 193 | return SecurityConditionNotSatisfied(), nil 194 | } 195 | 196 | if rapdu, err = encipher(data); err == nil { 197 | log.Printf("PSO:ENC successful") 198 | } 199 | 200 | return 201 | } 202 | 203 | // GetChallenge implements 204 | // p74, 7.2.15 GET CHALLENGE, OpenPGP application Version 3.4. 205 | func (card *Interface) GetChallenge(n int) (rapdu *apdu.RAPDU, err error) { 206 | buf := make([]byte, n) 207 | _, err = rand.Read(buf) 208 | 209 | if err != nil { 210 | return UnrecoverableError(), err 211 | } 212 | 213 | return CommandCompleted(buf), nil 214 | } 215 | 216 | // Pad implements PKCS7 compliant padding for symmetric AES operation. 217 | func Pad(buf []byte, extraBlock bool) []byte { 218 | padLen := 0 219 | r := len(buf) % aes.BlockSize 220 | 221 | if r != 0 { 222 | padLen = aes.BlockSize - r 223 | } else if extraBlock { 224 | padLen = aes.BlockSize 225 | } 226 | 227 | padding := []byte{(byte)(padLen)} 228 | padding = bytes.Repeat(padding, padLen) 229 | buf = append(buf, padding...) 230 | 231 | return buf 232 | } 233 | -------------------------------------------------------------------------------- /internal/icc/crypto_imx6.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package icc 11 | 12 | import ( 13 | "crypto/aes" 14 | "crypto/cipher" 15 | 16 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 17 | 18 | "github.com/hsanjuan/go-nfctype4/apdu" 19 | ) 20 | 21 | func encipher(data []byte) (rapdu *apdu.RAPDU, err error) { 22 | return aesCBC(data, false) 23 | } 24 | 25 | func decipher(data []byte) (rapdu *apdu.RAPDU, err error) { 26 | return aesCBC(data, true) 27 | } 28 | 29 | func aesCBC(data []byte, decrypt bool) (rapdu *apdu.RAPDU, err error) { 30 | if !imx6ul.SNVS.Available() { 31 | return CommandNotAllowed(), nil 32 | } 33 | 34 | iv := make([]byte, aes.BlockSize) 35 | key, err := imx6ul.DCP.DeriveKey(RID, iv, -1) 36 | 37 | if err != nil { 38 | return CommandNotAllowed(), nil 39 | } 40 | 41 | if len(data)%aes.BlockSize != 0 { 42 | return WrongData(), nil 43 | } 44 | 45 | block, err := aes.NewCipher(key) 46 | 47 | if err != nil { 48 | return 49 | } 50 | 51 | var mode cipher.BlockMode 52 | 53 | if decrypt { 54 | mode = cipher.NewCBCDecrypter(block, iv) 55 | } else { 56 | mode = cipher.NewCBCEncrypter(block, iv) 57 | } 58 | 59 | mode.CryptBlocks(data, data) 60 | 61 | return CommandCompleted(data), nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/icc/crypto_stubs.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build !tamago 9 | 10 | package icc 11 | 12 | import ( 13 | "errors" 14 | 15 | "github.com/hsanjuan/go-nfctype4/apdu" 16 | ) 17 | 18 | func encipher(data []byte) (rapdu *apdu.RAPDU, err error) { 19 | return nil, errors.New("not implemented") 20 | } 21 | 22 | func decipher(data []byte) (rapdu *apdu.RAPDU, err error) { 23 | return nil, errors.New("not implemented") 24 | } 25 | 26 | func Decrypt(input []byte, diversifier []byte) (output []byte, err error) { 27 | return nil, errors.New("not implemented") 28 | } 29 | -------------------------------------------------------------------------------- /internal/icc/data.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "bytes" 12 | "crypto/rsa" 13 | "encoding/binary" 14 | "fmt" 15 | "log" 16 | 17 | "github.com/ProtonMail/go-crypto/openpgp" 18 | "github.com/ProtonMail/go-crypto/openpgp/ecdh" 19 | "github.com/ProtonMail/go-crypto/openpgp/ecdsa" 20 | "github.com/hsanjuan/go-nfctype4/apdu" 21 | ) 22 | 23 | const ( 24 | // p22, 4.4.1 DOs for GET DATA, OpenPGP application Version 3.4 25 | DO_APPLICATION_IDENTIFIER = 0x4f 26 | DO_LOGIN_DATA = 0x5e 27 | DO_URL = 0x5f50 28 | DO_HISTORICAL_BYTES = 0x5f52 29 | DO_CARDHOLDER_RELATED_DATA = 0x65 30 | DO_APPLICATION_RELATED_DATA = 0x6e 31 | DO_SECURITY_SUPPORT_TEMPLATE = 0x7a 32 | DO_CARDHOLDER_CERTIFICATE = 0x7f21 // TODO 33 | DO_EXTENDED_LENGTH_INFORMATION = 0x7f66 34 | DO_PW_STATUS_BYTES = 0xc4 35 | DO_KEY_INFORMATION = 0xde 36 | DO_ALGORITHM_INFORMATION = 0xfa 37 | 38 | // DOs not directly accessible 39 | DO_NAME = 0x5b 40 | DO_LANGUAGE = 0x5f2d 41 | DO_SEX = 0x5f35 42 | DO_DISCRETIONARY_DATA_OBJECTS = 0x73 43 | DO_EXTENDED_CAPABILITIES = 0xc0 44 | DO_ALGORITHM_ATTRIBUTES_SIG = 0xc1 45 | DO_ALGORITHM_ATTRIBUTES_DEC = 0xc2 46 | DO_ALGORITHM_ATTRIBUTES_AUT = 0xc3 47 | DO_FINGERPRINTS = 0xc5 48 | DO_CA_FINGERPRINTS = 0xc6 49 | DO_GENERATION_EPOCHS = 0xcd 50 | DO_DIGITAL_SIGNATURE_COUNTER = 0x93 51 | 52 | // p65, 7.2.11 PSO: DECIPHER, OpenPGP application Version 3.4 53 | DO_CIPHER = 0xa6 54 | DO_AES256 = 0xd5 55 | // p72, 7.2.14 GENERATE ASYMMETRIC KEY PAIR, OpenPGP application Version 3.4 56 | DO_PUB_KEY = 0x7f49 57 | DO_RSA_MOD = 0x81 58 | DO_RSA_EXP = 0x82 59 | DO_EXT_PUB_KEY = 0x86 60 | 61 | // p33, 4.4.3.9 Algorithm Attributes, OpenPGP application Version 3.4 62 | RSA = 0x01 63 | RSA_KEY_SIZE = 4096 64 | RSA_EXPONENT_SIZE = 32 65 | IMPORT_FORMAT_STANDARD = 0x00 66 | 67 | // p33, 4.4.3.8 Key Information, OpenPGP application Version 3.4 68 | KEY_SIG = 0x01 69 | KEY_DEC = 0x02 70 | KEY_AUT = 0x03 71 | KEY_NOT_PRESENT = 0x00 72 | KEY_GENERATED = 0x01 73 | KEY_IMPORTED = 0x02 74 | 75 | PW1_MAX_LENGTH = 127 76 | RC_MAX_LENGTH = 127 77 | PW3_MAX_LENGTH = 127 78 | ) 79 | 80 | var ( 81 | ATR []byte 82 | HISTORICAL_BYTES []byte 83 | EXTENDED_CAPABILITIES []byte 84 | EXTENDED_LENGTH []byte 85 | 86 | // p15, 4.2.1 Application Identifier (AID), OpenPGP application Version 3.4 87 | RID = []byte{0xd2, 0x76, 0x00, 0x01, 0x24, 0x01} 88 | ) 89 | 90 | func init() { 91 | var tck byte 92 | 93 | // Initialize ATR according to ISO/IEC 7816-4, 94 | // https://en.wikipedia.org/wiki/Answer_to_reset. 95 | ATR = []byte{ 96 | // TS - Direct Convention 97 | 0x3b, 98 | // T0 - Y(1): b1101, K: 10 (historical bytes) 99 | 0xda, 100 | // TA(1) - Fi=372, Di=1, 372 cycles/ETU (10752 bits/s at 4.00 MHz, 13440 bits/s for fMax=5 MHz) 101 | 0x11, 102 | // TC(1) - Extra guard time: 255 103 | 0xff, 104 | // TD(1) - Y(i+1) = b1000, Protocol T=1 105 | 0x81, 106 | // TD(2) - Y(i+1) = b1011, Protocol T=1 107 | 0xb1, 108 | // TA(3) - IFSC: 254 109 | 0xfe, 110 | // TB(3) - Block Waiting Integer: 5 - Character Waiting Integer: 5 111 | 0x55, 112 | // TD(3) Y(i+1) = b0001, Protocol T=15 113 | 0x1f, 114 | // TA(4) - Clock stop: not supported - Class accepted by the card: (3G) A 5V B 3V 115 | 0x03, 116 | // Historical bytes 117 | } 118 | 119 | // p44, 6 Historical Byte, OpenPGP application Version 3.4 120 | HISTORICAL_BYTES = []byte{ 121 | // Category indicator 122 | 0x00, 123 | // Tag: 3, Len: 1 (card service data byte) 124 | 0x31, 125 | // Card service data byte: 132 126 | // - Application selection: by full DF name 127 | // - EF.DIR and EF.ATR access services: by GET DATA command 128 | // - Card with MF 129 | 0x84, 130 | // Tag: 7, Len: 3 (card capabilities) 131 | 0x73, 132 | // Selection methods: 128 133 | // - DF selection by full DF name 134 | 0x80, 135 | // Data coding byte: 1 136 | // - Behaviour of write functions: one-time write 137 | // - Value 'FF' for the first byte of BER-TLV tag fields: valid 138 | // - Data unit in quartets: 1 139 | 0x01, 140 | // Command chaining, length fields and logical channels: 128 141 | // - Command chaining 142 | // - Logical channel number assignment: No logical channel 143 | // - Maximum number of logical channels: 1 144 | 0x40, 145 | // Mandatory status indicator (3 last bytes) 146 | // LCS (life card cycle): 0 (No information given) 147 | // SW: 90 00 () 148 | 0x00, 149 | 0x90, 150 | 0x00, 151 | } 152 | 153 | ATR = append(ATR, HISTORICAL_BYTES...) 154 | 155 | // compute ATR checksum 156 | for _, b := range ATR[1:] { 157 | tck = tck ^ b 158 | } 159 | 160 | // TCK - Checksum 161 | ATR = append(ATR, tck) 162 | 163 | // p32, 4.4.3.7 Extended Capabilities, OpenPGP application Version 3.4 164 | EXTENDED_CAPABILITIES = []byte{ 165 | // support GET CHALLENGE, PSO:DEC/ENC with AES 166 | 0x42, 167 | // no support for Secure Messaging 168 | 0x00, 169 | // maximum length of GET CHALLENGE 170 | 0xff, 0xff, 171 | // maximum length of Cardholder Certificate 172 | 0xff, 0xff, 173 | // maximum length of special DOs 174 | 0xff, 0xff, 175 | // PIN block 2 format not supported 176 | 0x00, 177 | // MSE command for key numbers 2 (DEC) and 3 (AUT) not supported 178 | 0x00, 179 | } 180 | 181 | // p14, 4.1.3.1 Extended length information, OpenPGP application Version 3.4 182 | EXTENDED_LENGTH = []byte{ 183 | // Maximum number of bytes in a command APDU 184 | 0x02, 0x02, 0x0b, 0xfe, 185 | // Maximum number of bytes in a response APDU 186 | 0x02, 0x02, 0x0b, 0xfe, 187 | } 188 | } 189 | 190 | // AID implements 191 | // p15, 4.2.1 Application Identifier (AID), OpenPGP application Version 3.4. 192 | func (card *Interface) AID() (aid []byte) { 193 | aid = RID 194 | // version (3.4) 195 | aid = append(aid, []byte{0x03, 0x04}...) 196 | // manufacturer - 0xF5EC, assigned by GnuPG e.V. on 2020-02-21 to F-Secure 197 | aid = append(aid, []byte{0xF5, 0xEC}...) 198 | // serial number - place holder 199 | aid = append(aid, card.Serial[:]...) 200 | // RFU 201 | aid = append(aid, []byte{0x00, 0x00}...) 202 | 203 | return 204 | } 205 | 206 | // ATR returns the Answer to reset (ATR) according to ISO/IEC 7816-4. 207 | func (card *Interface) ATR() (atr []byte) { 208 | return ATR 209 | } 210 | 211 | // CardholderRelatedData builds and returns Data Object 0x65. 212 | func (card *Interface) CardholderRelatedData() []byte { 213 | data := new(bytes.Buffer) 214 | 215 | data.Write(tlv(DO_NAME, []byte(card.Name))) 216 | data.Write(tlv(DO_LANGUAGE, []byte(card.Language))) 217 | data.Write(tlv(DO_SEX, []byte(card.Sex))) 218 | 219 | return tlv(DO_CARDHOLDER_RELATED_DATA, data.Bytes()) 220 | } 221 | 222 | // DiscretionaryData builds and returns Data Object 0x73. 223 | func (card *Interface) DiscretionaryData() []byte { 224 | data := new(bytes.Buffer) 225 | 226 | data.Write(tlv(DO_EXTENDED_CAPABILITIES, EXTENDED_CAPABILITIES)) 227 | data.Write(tlv(DO_ALGORITHM_ATTRIBUTES_SIG, card.AlgorithmAttributes(card.Sig))) 228 | data.Write(tlv(DO_ALGORITHM_ATTRIBUTES_DEC, card.AlgorithmAttributes(card.Dec))) 229 | data.Write(tlv(DO_ALGORITHM_ATTRIBUTES_AUT, card.AlgorithmAttributes(card.Aut))) 230 | data.Write(tlv(DO_PW_STATUS_BYTES, card.PWStatusBytes())) 231 | data.Write(tlv(DO_FINGERPRINTS, card.Fingerprints())) 232 | data.Write(tlv(DO_CA_FINGERPRINTS, card.CAFingerprints())) 233 | data.Write(tlv(DO_GENERATION_EPOCHS, card.GenerationEpochs())) 234 | 235 | return data.Bytes() 236 | } 237 | 238 | // AlgorithmAttributes builds and returns the Data Objects specified at 239 | // p34, 4.4.3.9 Algorithm Attributes, OpenPGP application Version 3.4. 240 | func (card *Interface) AlgorithmAttributes(subkey *openpgp.Subkey) (data []byte) { 241 | data = make([]byte, 6) 242 | data[0] = RSA 243 | data[5] = IMPORT_FORMAT_STANDARD 244 | 245 | if subkey == nil || subkey.PublicKey == nil { 246 | binary.BigEndian.PutUint16(data[1:], uint16(RSA_KEY_SIZE)) 247 | binary.BigEndian.PutUint16(data[3:], uint16(RSA_EXPONENT_SIZE)) 248 | return 249 | } 250 | 251 | switch pubKey := subkey.PublicKey.PublicKey.(type) { 252 | case *rsa.PublicKey: 253 | binary.BigEndian.PutUint16(data[1:], uint16(pubKey.N.BitLen())) 254 | binary.BigEndian.PutUint16(data[3:], uint16(RSA_EXPONENT_SIZE)) 255 | case *ecdsa.PublicKey: 256 | data = []byte{byte(subkey.PublicKey.PubKeyAlgo)} 257 | data = append(data, getOID(pubKey.GetCurve().GetCurveName())...) 258 | data = append(data, IMPORT_FORMAT_STANDARD) 259 | case *ecdh.PublicKey: 260 | data = []byte{byte(subkey.PublicKey.PubKeyAlgo)} 261 | data = append(data, getOID(pubKey.GetCurve().GetCurveName())...) 262 | data = append(data, IMPORT_FORMAT_STANDARD) 263 | default: 264 | log.Printf("unexpected public key type in DO_ALGORITHM_ATTRIBUTES %T", pubKey) 265 | } 266 | 267 | return data 268 | } 269 | 270 | // PWStatusBytes builds and returns Data Object 0xC4. 271 | func (card *Interface) PWStatusBytes() []byte { 272 | status := new(bytes.Buffer) 273 | 274 | // PW1 only valid for one PSO:CDS command 275 | status.WriteByte(PW1_CDS_MULTI) 276 | // max. length of PW1 (user), UTF-8 or derived password 277 | status.WriteByte((PW1_MAX_LENGTH << 1) & 0b11111110) 278 | // max. length of Resetting Code (RC) for PW1 279 | status.WriteByte(RC_MAX_LENGTH) 280 | // max. length of PW3 (admin) 281 | status.WriteByte(PW3_MAX_LENGTH) 282 | 283 | // error counter for PW1 284 | status.WriteByte(card.errorCounterPW1) 285 | // error counter for RC 286 | status.WriteByte(card.errorCounterRC) 287 | // error counter for PW3 288 | status.WriteByte(card.errorCounterPW3) 289 | 290 | return status.Bytes() 291 | } 292 | 293 | // Fingerprints collects card OpenPGP subkey fingerprints and returns them in 294 | // Data Object 0xC5. 295 | func (card *Interface) Fingerprints() (fingerprints []byte) { 296 | fingerprints = make([]byte, 60) 297 | subkeys := []*openpgp.Subkey{card.Sig, card.Dec, card.Aut} 298 | 299 | for i, subkey := range subkeys { 300 | if subkey == nil { 301 | continue 302 | } 303 | 304 | copy(fingerprints[i*20:], subkey.PublicKey.Fingerprint[:]) 305 | } 306 | 307 | return 308 | } 309 | 310 | // CAFingerprints collects card OpenPGP CA fingerprints and returns them in 311 | // Data Object 0xC6. Currently unused (always empty). 312 | func (card *Interface) CAFingerprints() (fingerprints []byte) { 313 | fingerprints = make([]byte, 60) 314 | 315 | for i, ca := range card.CA { 316 | if ca == nil { 317 | continue 318 | } 319 | 320 | copy(fingerprints[i*20:], ca.PrimaryKey.Fingerprint[:]) 321 | } 322 | 323 | return 324 | } 325 | 326 | // GenerationEpochs collects card OpenPGP creation times and returns them in 327 | // Data Object 0xCD. 328 | func (card *Interface) GenerationEpochs() (epochs []byte) { 329 | epochs = make([]byte, 12) 330 | 331 | if card.Sig != nil { 332 | binary.BigEndian.PutUint32(epochs, uint32(card.Sig.PublicKey.CreationTime.Unix())) 333 | } 334 | 335 | if card.Dec != nil { 336 | binary.BigEndian.PutUint32(epochs[4:], uint32(card.Dec.PublicKey.CreationTime.Unix())) 337 | } 338 | 339 | if card.Aut != nil { 340 | binary.BigEndian.PutUint32(epochs[8:], uint32(card.Aut.PublicKey.CreationTime.Unix())) 341 | } 342 | 343 | return 344 | } 345 | 346 | // KeyInformation implements 347 | // p33, 4.4.3.8 Key Information, OpenPGP application Version 3.4. 348 | // 349 | // This information is required for Yubico OpenPGP attestation, this 350 | // implementation doesn't (yet) support this feature as its usefulness is 351 | // questionable. Therefore we just flag all keys as imported (which also 352 | // happens to be the only allowed mechanism for now). 353 | func (card *Interface) KeyInformation() []byte { 354 | return []byte{ 355 | KEY_SIG, KEY_IMPORTED, 356 | KEY_DEC, KEY_IMPORTED, 357 | KEY_AUT, KEY_IMPORTED, 358 | } 359 | } 360 | 361 | // AlgorithmInformation implements 362 | // p37, 4.4.3.11 Algorithm Information, OpenPGP application Version 3.4. 363 | // 364 | // The standard is ambiguous on whether this DO needs to be present if 365 | // algorithm attributes cannot be changed, the DO table suggests this 366 | // is mandatory but the DO description suggests otherwise. 367 | // 368 | // Given that this implementation does not allow changes, the imported key 369 | // attributes are returned. 370 | func (card *Interface) AlgorithmInformation() []byte { 371 | data := new(bytes.Buffer) 372 | 373 | data.Write(tlv(DO_ALGORITHM_ATTRIBUTES_SIG, card.AlgorithmAttributes(card.Sig))) 374 | data.Write(tlv(DO_ALGORITHM_ATTRIBUTES_DEC, card.AlgorithmAttributes(card.Dec))) 375 | data.Write(tlv(DO_ALGORITHM_ATTRIBUTES_AUT, card.AlgorithmAttributes(card.Aut))) 376 | 377 | return data.Bytes() 378 | } 379 | 380 | func (card *Interface) DigitalSignatureCounter() []byte { 381 | if card.digitalSignatureCounter > 0xffffff { 382 | card.digitalSignatureCounter = 0 383 | } 384 | 385 | counter := make([]byte, 4) 386 | binary.BigEndian.PutUint32(counter, card.digitalSignatureCounter) 387 | 388 | return counter[1:4] 389 | } 390 | 391 | // SecuritySupportTemplate builds and returns Data Object 0x7A. 392 | func (card *Interface) SecuritySupportTemplate() []byte { 393 | data := tlv(DO_DIGITAL_SIGNATURE_COUNTER, card.DigitalSignatureCounter()) 394 | return tlv(DO_SECURITY_SUPPORT_TEMPLATE, data) 395 | } 396 | 397 | // ApplicationRelatedData implements 398 | // p30, 4.4.3.1 Application Related Data, OpenPGP application Version 3.4. 399 | func (card *Interface) ApplicationRelatedData() []byte { 400 | data := new(bytes.Buffer) 401 | 402 | data.Write(tlv(DO_APPLICATION_IDENTIFIER, card.AID())) 403 | data.Write(tlv(DO_HISTORICAL_BYTES, HISTORICAL_BYTES)) 404 | data.Write(tlv(DO_EXTENDED_LENGTH_INFORMATION, EXTENDED_LENGTH)) 405 | data.Write(tlv(DO_DISCRETIONARY_DATA_OBJECTS, card.DiscretionaryData())) 406 | 407 | return tlv(DO_APPLICATION_RELATED_DATA, data.Bytes()) 408 | } 409 | 410 | // GetData implements 411 | // p57, 7.2.6 GET DATA, OpenPGP application Version 3.4. 412 | func (card *Interface) GetData(tag uint16) (rapdu *apdu.RAPDU, err error) { 413 | rapdu = CommandCompleted(nil) 414 | 415 | // p22, 4.4.1 DOs for GET DATA, OpenPGP application Version 3.4 416 | switch tag { 417 | case DO_APPLICATION_IDENTIFIER: 418 | rapdu.ResponseBody = card.AID() 419 | case DO_LOGIN_DATA: 420 | rapdu.ResponseBody = card.LoginData 421 | case DO_URL: 422 | rapdu.ResponseBody = []byte(card.URL) 423 | case DO_HISTORICAL_BYTES: 424 | rapdu.ResponseBody = HISTORICAL_BYTES 425 | case DO_CARDHOLDER_RELATED_DATA: 426 | rapdu.ResponseBody = card.CardholderRelatedData() 427 | case DO_APPLICATION_RELATED_DATA: 428 | rapdu.ResponseBody = card.ApplicationRelatedData() 429 | case DO_SECURITY_SUPPORT_TEMPLATE: 430 | rapdu.ResponseBody = card.SecuritySupportTemplate() 431 | case DO_EXTENDED_LENGTH_INFORMATION: 432 | rapdu.ResponseBody = EXTENDED_LENGTH 433 | case DO_PW_STATUS_BYTES: 434 | rapdu.ResponseBody = card.PWStatusBytes() 435 | case DO_KEY_INFORMATION: 436 | rapdu.ResponseBody = card.KeyInformation() 437 | case DO_ALGORITHM_INFORMATION: 438 | rapdu.ResponseBody = card.AlgorithmInformation() 439 | default: 440 | rapdu.SW1 = 0x6a 441 | rapdu.SW1 = 0x88 442 | log.Printf("unsupported DO tag %x", tag) 443 | } 444 | 445 | return 446 | } 447 | 448 | // PutData implements 449 | // p60, 7.2.8 PUT DATA, OpenPGP application Version 3.4. 450 | // 451 | // This is not implemented (always returns command not allowed) as card 452 | // personalization is managed outside OpenPGP specifications and the PW3 PIN 453 | // (required for this command) is not supported. 454 | func (card *Interface) PutData(tag uint16) (rapdu *apdu.RAPDU, err error) { 455 | return CommandNotAllowed(), nil 456 | } 457 | 458 | // GenerateAsymmetricKeyPair implements 459 | // p72, 7.2.14 GENERATE ASYMMETRIC KEY PAIR, OpenPGP application Version 3.4. 460 | // 461 | // Generation of key pair is not implemented as card personalization is managed 462 | // outside OpenPGP specifications and the PW3 PIN (required for this mode) is 463 | // not supported. 464 | // 465 | // Therefore this command can only be used to read public key templates. 466 | func (card *Interface) GenerateAsymmetricKeyPair(params uint16, crt []byte) (rapdu *apdu.RAPDU, err error) { 467 | var subkey *openpgp.Subkey 468 | 469 | rapdu = CommandNotAllowed() 470 | 471 | switch params { 472 | case 0x8000: 473 | // Generation of key pair. 474 | // 475 | // Not implemented as card personalization is managed outside 476 | // OpenPGP specifications and therefore PW3 PIN is not 477 | // supported. 478 | return 479 | case 0x8100: 480 | // Reading of actual public key template. 481 | default: 482 | log.Printf("unsupported GENERATE parameters %x", params) 483 | return 484 | } 485 | 486 | switch { 487 | case bytes.Equal(crt, []byte{0xb6, 0x00}), bytes.Equal(crt, []byte{0xb6, 0x03, 0x84, 0x01, 0x01}): 488 | // Digital signature 489 | subkey = card.Sig 490 | case bytes.Equal(crt, []byte{0xb8, 0x00}), bytes.Equal(crt, []byte{0xb8, 0x03, 0x84, 0x01, 0x02}): 491 | // Confidentiality 492 | subkey = card.Dec 493 | case bytes.Equal(crt, []byte{0xa4, 0x00}), bytes.Equal(crt, []byte{0xa4, 0x03, 0x84, 0x01, 0x03}): 494 | // Authentication 495 | subkey = card.Aut 496 | default: 497 | log.Printf("unsupported GENERATE CRT %x", crt) 498 | return 499 | } 500 | 501 | if subkey == nil { 502 | return ReferencedDataNotFound(), nil 503 | } 504 | 505 | data := new(bytes.Buffer) 506 | 507 | switch pubKey := subkey.PublicKey.PublicKey.(type) { 508 | case *rsa.PublicKey: 509 | mod := pubKey.N.Bytes() 510 | exp := make([]byte, 4) 511 | binary.BigEndian.PutUint32(exp, uint32(pubKey.E)) 512 | 513 | data.Write(tlv(DO_RSA_MOD, mod)) 514 | data.Write(tlv(DO_RSA_EXP, exp)) 515 | case *ecdsa.PublicKey: 516 | pp := pubKey.MarshalPoint() 517 | data.Write(tlv(DO_EXT_PUB_KEY, pp)) 518 | case *ecdh.PublicKey: 519 | pp := pubKey.MarshalPoint() 520 | data.Write(tlv(DO_EXT_PUB_KEY, pp)) 521 | default: 522 | err = fmt.Errorf("unexpected public key type in GENERATE %T", pubKey) 523 | return 524 | } 525 | 526 | return CommandCompleted(tlv(DO_PUB_KEY, data.Bytes())), nil 527 | } 528 | -------------------------------------------------------------------------------- /internal/icc/interface.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | "errors" 14 | "fmt" 15 | "log" 16 | "regexp" 17 | "sync" 18 | 19 | "github.com/usbarmory/GoKey/internal/snvs" 20 | 21 | "github.com/ProtonMail/go-crypto/openpgp" 22 | "github.com/ProtonMail/go-crypto/openpgp/packet" 23 | "github.com/google/go-p11-kit/p11kit" 24 | "github.com/hsanjuan/go-nfctype4/apdu" 25 | ) 26 | 27 | // Diversifier for hardware key derivation (OpenPGP secret key wrapping). 28 | const DiversifierPGP = "GoKeySNVSOpenPGP" 29 | 30 | const ( 31 | // p48, 7.1 Usage of ISO Standard Commands, OpenPGP application Version 3.4. 32 | SELECT = 0xa4 33 | GET_DATA = 0xca 34 | VERIFY = 0x20 35 | PUT_DATA_1 = 0xda 36 | PUT_DATA_2 = 0xdb 37 | GENERATE_ASYMMETRIC_KEY_PAIR = 0x47 38 | GET_CHALLENGE = 0x84 39 | PERFORM_SECURITY_OPERATION = 0x2a 40 | 41 | // Not implemented: 42 | // SELECT DATA 43 | // GET NEXT DATA 44 | // CHANGE REFERENCE DATA 45 | // RESET RETRY COUNTER 46 | // INTERNAL AUTHENTICATE 47 | // GET RESPONSE 48 | // TERMINATE DF 49 | // ACTIVATE FILE 50 | // MANAGE SECURITY ENVIRONMENT 51 | 52 | // Security Operations 53 | COMPUTE_DIGITAL_SIGNATURE = 0x9e9a 54 | DECIPHER = 0x8086 55 | ENCIPHER = 0x8680 56 | 57 | DEFAULT_PW1_ERROR_COUNTER = 3 58 | ) 59 | 60 | // Interface implements an OpenPGP card instance. 61 | type Interface struct { 62 | sync.Mutex 63 | 64 | // Unique serial number 65 | Serial [4]byte 66 | // p30, 4.4.3.3 Name, OpenPGP application Version 3.4 67 | Name string 68 | // p30, 4.4.3.4 Name, OpenPGP application Version 3.4 69 | Language string 70 | // p31, 4.4.3.5 Name, OpenPGP application Version 3.4 71 | Sex string 72 | 73 | URL string 74 | LoginData []byte 75 | 76 | // enable APDU debugging 77 | Debug bool 78 | // enable device unique hardware encryption for bundled private keys 79 | SNVS bool 80 | 81 | // Armored secret key 82 | ArmoredKey []byte 83 | // Secret key 84 | Key *openpgp.Entity 85 | // Signature subkey 86 | Sig *openpgp.Subkey 87 | // Decryption subkey 88 | Dec *openpgp.Subkey 89 | // Authentication subkey 90 | Aut *openpgp.Subkey 91 | 92 | // encrypted private key caches for PW_LOCK 93 | sig packet.PrivateKey 94 | dec packet.PrivateKey 95 | aut packet.PrivateKey 96 | 97 | // currently unused 98 | CA []*openpgp.Entity 99 | 100 | // volatile (TODO: make it permanent) 101 | errorCounterPW1 uint8 102 | // no reset functionality, unused and fixed to 0x00 103 | errorCounterRC uint8 104 | // PW3 PIN is not supported, unused and fixed to 0x00 105 | errorCounterPW3 uint8 106 | // volatile (TODO: make it permanent) 107 | digitalSignatureCounter uint32 108 | 109 | // PKCS#11 RPC handler 110 | rpc *p11kit.Handler 111 | 112 | // internal state flags 113 | selected bool 114 | initialized bool 115 | awake bool 116 | } 117 | 118 | // Init initializes the OpenPGP card instance, using passed amored secret key 119 | // material. 120 | // 121 | // The SNVS argument indicates whether private keys (which are already 122 | // encrypted with the passphrase unless the user created them without one) are 123 | // to be stored encrypted at rest with a device specific hardware derived key. 124 | func (card *Interface) Init() (err error) { 125 | if card.initialized { 126 | return errors.New("card already initialized") 127 | } 128 | 129 | if card.SNVS { 130 | card.ArmoredKey, err = snvs.Decrypt(card.ArmoredKey, []byte(DiversifierPGP)) 131 | } 132 | 133 | if err != nil { 134 | return fmt.Errorf("OpenPGP key decryption failed, %v", err) 135 | } 136 | 137 | card.Key, err = decodeArmoredKey([]byte(card.ArmoredKey)) 138 | 139 | if err != nil { 140 | return fmt.Errorf("OpenPGP key decoding failed, %v", err) 141 | } 142 | 143 | card.Sig, card.Dec, card.Aut = decodeSubkeys(card.Key) 144 | 145 | // cache encrypted private keys for PW_LOCK 146 | if card.Sig != nil && card.Sig.PrivateKey != nil { 147 | card.sig = *card.Sig.PrivateKey 148 | } 149 | if card.Dec != nil && card.Dec.PrivateKey != nil { 150 | card.dec = *card.Dec.PrivateKey 151 | } 152 | if card.Aut != nil && card.Aut.PrivateKey != nil { 153 | card.aut = *card.Aut.PrivateKey 154 | } 155 | 156 | card.errorCounterPW1 = DEFAULT_PW1_ERROR_COUNTER 157 | card.initialized = true 158 | 159 | log.Printf("OpenPGP card initialized") 160 | log.Print(card.Status()) 161 | 162 | LED("white", false) 163 | LED("blue", false) 164 | 165 | card.signalVerificationStatus() 166 | 167 | return 168 | } 169 | 170 | // Initialized returns the OpenPGP card initialization state. 171 | func (card *Interface) Initialized() bool { 172 | return card.initialized 173 | } 174 | 175 | // Restore overwrites decrypted subkeys with their encrypted version, imported 176 | // at card initialization. 177 | func (card *Interface) Restore(subkey *openpgp.Subkey) *packet.PrivateKey { 178 | if subkey == nil || subkey.PrivateKey == nil { 179 | return nil 180 | } 181 | 182 | for _, privateKey := range []packet.PrivateKey{card.sig, card.dec, card.aut} { 183 | if bytes.Equal(privateKey.Fingerprint, subkey.PrivateKey.Fingerprint) { 184 | return &privateKey 185 | } 186 | } 187 | 188 | return nil 189 | } 190 | 191 | // Select implements 192 | // p50, 7.2.1 SELECT, OpenPGP application Version 3.4. 193 | func (card *Interface) Select(file []byte) (rapdu *apdu.RAPDU, _ error) { 194 | rapdu = FileNotFound() 195 | 196 | if bytes.Equal(file, RID) || bytes.Equal(file, card.AID()) { 197 | rapdu = CommandCompleted(nil) 198 | card.selected = true 199 | } else if card.selected { 200 | // A SELECT for a different application sets the status to 'not 201 | // verified' for all PWs. 202 | _, _ = card.Verify(PW_LOCK, PW1_CDS, nil) 203 | _, _ = card.Verify(PW_LOCK, PW1, nil) 204 | _, _ = card.Verify(PW_LOCK, PW3, nil) 205 | card.selected = false 206 | } 207 | 208 | return 209 | } 210 | 211 | // RawCommand parses a buffer representing an APDU command and redirects it to 212 | // the relevant handler. A buffer representing the APDU response is returned. 213 | func (card *Interface) RawCommand(buf []byte) ([]byte, error) { 214 | capdu := &apdu.CAPDU{} 215 | _, err := capdu.Unmarshal(buf) 216 | 217 | if err != nil { 218 | return nil, err 219 | } 220 | 221 | rapdu, err := card.Command(capdu) 222 | 223 | if err != nil { 224 | return nil, err 225 | } 226 | 227 | return rapdu.Marshal() 228 | } 229 | 230 | // Command parses an APDU command and redirects it to the relevant handler. An 231 | // APDU response is returned. 232 | func (card *Interface) Command(capdu *apdu.CAPDU) (rapdu *apdu.RAPDU, err error) { 233 | rapdu = CommandNotAllowed() 234 | 235 | if card.Debug { 236 | log.Printf("<< %+v", capdu) 237 | } 238 | 239 | if capdu.CLA != 0x00 { 240 | return 241 | } 242 | 243 | params := binary.BigEndian.Uint16([]byte{capdu.P1, capdu.P2}) 244 | 245 | // p48, 7.1 Usage of ISO Standard Commands, OpenPGP application Version 3.4 246 | switch capdu.INS { 247 | case SELECT: 248 | rapdu, err = card.Select(capdu.Data) 249 | case GET_DATA: 250 | rapdu, err = card.GetData(params) 251 | case VERIFY: 252 | rapdu, err = card.Verify(capdu.P1, capdu.P2, capdu.Data) 253 | case PUT_DATA_1, PUT_DATA_2: 254 | rapdu, err = card.PutData(params) 255 | case GENERATE_ASYMMETRIC_KEY_PAIR: 256 | rapdu, err = card.GenerateAsymmetricKeyPair(params, capdu.Data) 257 | case GET_CHALLENGE: 258 | rapdu, err = card.GetChallenge(int(capdu.GetLe())) 259 | case PERFORM_SECURITY_OPERATION: 260 | LED("white", true) 261 | defer LED("white", false) 262 | 263 | switch params { 264 | case COMPUTE_DIGITAL_SIGNATURE: 265 | rapdu, err = card.ComputeDigitalSignature(capdu.Data) 266 | case DECIPHER: 267 | rapdu, err = card.Decipher(capdu.Data) 268 | case ENCIPHER: 269 | rapdu, err = card.Encipher(capdu.Data) 270 | default: 271 | log.Printf("unsupported PSO %x", params) 272 | } 273 | default: 274 | log.Printf("unsupported INS %x", capdu.INS) 275 | } 276 | 277 | if rapdu == nil { 278 | rapdu = CommandNotAllowed() 279 | } 280 | 281 | if card.Debug { 282 | log.Printf(">> %+v", rapdu) 283 | } 284 | 285 | return 286 | } 287 | 288 | // Status returns card key fingerprints and encryption status in textual 289 | // format. 290 | func (card *Interface) Status() string { 291 | var status bytes.Buffer 292 | 293 | fmt.Fprintf(&status, "---------------------------------------------------- OpenPGP smartcard ----\n") 294 | fmt.Fprintf(&status, "Initialized ............: %v\n", card.initialized) 295 | fmt.Fprintf(&status, "Secure storage .........: %v\n", card.SNVS) 296 | fmt.Fprintf(&status, "Serial number ..........: %X\n", card.Serial) 297 | fmt.Fprintf(&status, "Digital signature count.: %v\n", card.digitalSignatureCounter) 298 | fmt.Fprintf(&status, "Secret key .............: ") 299 | 300 | r := regexp.MustCompile(`([[:xdigit:]]{4})`) 301 | 302 | if k := card.Key; k != nil { 303 | fp := fmt.Sprintf("%X\n", k.PrimaryKey.Fingerprint) 304 | fmt.Fprint(&status, r.ReplaceAllString(fp, "$1 ")) 305 | } else { 306 | status.WriteString("missing\n") 307 | } 308 | 309 | desc := []string{"Signature subkey .......: ", "Decryption subkey ......: ", "Authentication subkey ..: "} 310 | 311 | for i, sk := range []*openpgp.Subkey{card.Sig, card.Dec, card.Aut} { 312 | status.WriteString(desc[i]) 313 | 314 | if sk != nil { 315 | fp := fmt.Sprintf("%X\n", sk.PublicKey.Fingerprint) 316 | status.WriteString(r.ReplaceAllString(fp, "$1 ")) 317 | 318 | if pk := sk.PrivateKey; pk != nil { 319 | fmt.Fprintf(&status, " encrypted: %v\n", pk.Encrypted) 320 | } 321 | } else { 322 | status.WriteString("missing\n") 323 | } 324 | } 325 | 326 | return status.String() 327 | } 328 | -------------------------------------------------------------------------------- /internal/icc/led.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package icc 11 | 12 | import ( 13 | "github.com/usbarmory/armoryctl/led" 14 | ) 15 | 16 | // LED turns on/off an LED by name. 17 | func LED(name string, on bool) (err error) { 18 | return led.Set(name, on) 19 | } 20 | -------------------------------------------------------------------------------- /internal/icc/led_stubs.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build !tamago 9 | 10 | package icc 11 | 12 | import ( 13 | "log" 14 | ) 15 | 16 | func LED(name string, on bool) (err error) { 17 | log.Printf("LED: %s, %v", name, on) 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /internal/icc/openpgp.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "bytes" 12 | 13 | "github.com/ProtonMail/go-crypto/openpgp" 14 | "github.com/ProtonMail/go-crypto/openpgp/armor" 15 | "github.com/ProtonMail/go-crypto/openpgp/packet" 16 | ) 17 | 18 | func decodeArmoredKey(key []byte) (entity *openpgp.Entity, err error) { 19 | k := bytes.NewBuffer(key) 20 | keyBlock, err := armor.Decode(k) 21 | 22 | if err != nil { 23 | return 24 | } 25 | 26 | reader := packet.NewReader(keyBlock.Body) 27 | entity, err = openpgp.ReadEntity(reader) 28 | 29 | return 30 | } 31 | 32 | func decodeSubkeys(entity *openpgp.Entity) (sig *openpgp.Subkey, dec *openpgp.Subkey, aut *openpgp.Subkey) { 33 | for i, subkey := range entity.Subkeys { 34 | if subkey.Sig == nil || !subkey.Sig.FlagsValid { 35 | continue 36 | } 37 | 38 | if subkey.Sig.FlagSign { 39 | sig = &entity.Subkeys[i] 40 | } 41 | 42 | if subkey.Sig.FlagEncryptStorage && subkey.Sig.FlagEncryptCommunications { 43 | dec = &entity.Subkeys[i] 44 | } 45 | 46 | if subkey.Sig.FlagAuthenticate { 47 | aut = &subkey 48 | } 49 | } 50 | 51 | return 52 | } 53 | 54 | func getOID(name string) (oid []byte) { 55 | // p99, 10 Domain parameter of supported elliptic curves, OpenPGP application Version 3.4 56 | switch name { 57 | case "P-256": 58 | oid = []byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07} 59 | case "P-384": 60 | oid = []byte{0x2B, 0x81, 0x04, 0x00, 0x22} 61 | case "P-521": 62 | oid = []byte{0x2B, 0x81, 0x04, 0x00, 0x23} 63 | case "brainpoolP256r1": 64 | oid = []byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07} 65 | case "brainpoolP384r1": 66 | oid = []byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B} 67 | case "brainpoolP512r1": 68 | oid = []byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D} 69 | default: 70 | oid = []byte{0x2B, 0x81, 0x04, 0x00, 0x23} 71 | } 72 | 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /internal/icc/rpc.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "crypto/ecdsa" 12 | "crypto/rand" 13 | "crypto/rsa" 14 | "crypto/x509" 15 | "crypto/x509/pkix" 16 | "errors" 17 | "fmt" 18 | "io" 19 | "log" 20 | "math/big" 21 | "time" 22 | 23 | "github.com/google/go-p11-kit/p11kit" 24 | ) 25 | 26 | func (card *Interface) getObjects() (objs []p11kit.Object, err error) { 27 | subkey := card.Sig 28 | 29 | if subkey == nil || subkey.PrivateKey == nil { 30 | log.Printf("missing private key for PKCS#11 slot object") 31 | return nil, errors.New("card key not supported") 32 | } 33 | 34 | if subkey.PrivateKey.Encrypted { 35 | log.Printf("security condition not satisfied key for PKCS#11 slot object") 36 | return nil, errors.New("security condition not satisfied") 37 | } 38 | 39 | if PW1_CDS_MULTI == 0 { 40 | defer card.Verify(PW_LOCK, PW1_CDS, nil) 41 | } 42 | 43 | creationTime := subkey.PrivateKey.CreationTime 44 | 45 | certTemplate := x509.Certificate{ 46 | NotBefore: creationTime, 47 | // no well-defined expiration date 48 | NotAfter: time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC), 49 | SerialNumber: big.NewInt(int64(subkey.PrivateKey.KeyId)), 50 | Subject: pkix.Name{ 51 | CommonName: card.Name, 52 | }, 53 | } 54 | 55 | defer func() { 56 | if err != nil { 57 | log.Printf("error, %+v", err) 58 | } 59 | }() 60 | 61 | switch privKey := subkey.PrivateKey.PrivateKey.(type) { 62 | case *rsa.PrivateKey: 63 | der, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &privKey.PublicKey, privKey) 64 | 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | cert, err := x509.ParseCertificate(der) 70 | 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | obj, err := p11kit.NewX509CertificateObject(cert) 76 | 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | objs = append(objs, obj) 82 | 83 | if obj, err = p11kit.NewPrivateKeyObject(privKey); err != nil { 84 | return nil, err 85 | } 86 | 87 | objs = append(objs, obj) 88 | 89 | if obj, err = p11kit.NewPublicKeyObject(privKey.Public()); err != nil { 90 | return nil, err 91 | } 92 | 93 | if err = obj.SetCertificate(cert); err != nil { 94 | return nil, err 95 | } 96 | 97 | objs = append(objs, obj) 98 | case *ecdsa.PrivateKey: 99 | obj, err := p11kit.NewPrivateKeyObject(privKey) 100 | 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | objs = append(objs, obj) 106 | 107 | if obj, err = p11kit.NewPublicKeyObject(privKey.Public()); err != nil { 108 | return nil, err 109 | } 110 | 111 | objs = append(objs, obj) 112 | default: 113 | log.Printf("card key not satisfied for PKCS#11 slot object") 114 | return nil, errors.New("card key not supported") 115 | } 116 | 117 | return 118 | } 119 | 120 | func (card *Interface) initRPC() { 121 | slot := p11kit.Slot{ 122 | ID: 0x01, 123 | Description: "GoKey PKCS#11 RPC", 124 | Label: "GoKey", 125 | Manufacturer: "WithSecure Foundry", 126 | Model: "USB armory Mk II", 127 | Serial: fmt.Sprintf("%X", card.Serial), 128 | HardwareVersion: p11kit.Version{Major: 2, Minor: 0}, 129 | FirmwareVersion: p11kit.Version{Major: 0, Minor: 1}, 130 | GetObjects: card.getObjects, 131 | } 132 | 133 | card.rpc = &p11kit.Handler{ 134 | Manufacturer: "WithSecure Foundry", 135 | Library: "GoKey", 136 | LibraryVersion: p11kit.Version{Major: 0, Minor: 1}, 137 | Slots: []p11kit.Slot{slot}, 138 | } 139 | } 140 | 141 | func (card *Interface) ServeRPC(rw io.ReadWriter) error { 142 | if card.rpc == nil { 143 | card.initRPC() 144 | } 145 | 146 | return card.rpc.Handle(rw) 147 | } 148 | -------------------------------------------------------------------------------- /internal/icc/tlv.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | ) 14 | 15 | func tlv(t int, v []byte) (buf []byte) { 16 | l := len(v) 17 | 18 | if t > 0xff { 19 | // 2nd byte is tag value 20 | buf = make([]byte, 2) 21 | binary.BigEndian.PutUint16(buf, uint16(t)) 22 | } else { 23 | buf = append(buf, byte(t)) 24 | } 25 | 26 | // p39, 4.4.4 Length Field of DOs, OpenPGP application Version 3.4 27 | if l <= 0x7f { 28 | buf = append(buf, uint8(l)) 29 | } else if len(v) <= 0xff { 30 | buf = append(buf, 0x81) 31 | buf = append(buf, uint8(l)) 32 | } else { 33 | buf = append(buf, 0x82) 34 | buf = append(buf, byte((l>>8)&0xff)) 35 | buf = append(buf, byte(l&0xff)) 36 | } 37 | 38 | buf = append(buf, v...) 39 | 40 | return 41 | } 42 | 43 | func v(buf []byte, t int) (v []byte) { 44 | var i int 45 | var l int 46 | 47 | if len(buf) < 1 { 48 | return 49 | } 50 | 51 | if t > 0xff { 52 | // 2nd byte is tag value 53 | if len(buf) < 2 { 54 | return 55 | } 56 | 57 | tag := make([]byte, 2) 58 | binary.BigEndian.PutUint16(tag, uint16(t)) 59 | 60 | if !bytes.Equal(tag, buf[0:2]) { 61 | return 62 | } 63 | 64 | i += 2 65 | } else { 66 | if byte(t) != buf[0] { 67 | return 68 | } 69 | 70 | i += 1 71 | } 72 | 73 | if buf[i] <= 0x7f { 74 | l = int(buf[i]) 75 | i += 1 76 | } else if buf[i] == 0x81 { 77 | if len(buf) < i+1 { 78 | return 79 | } 80 | 81 | l = int(buf[i+1]) 82 | i += 2 83 | } else if buf[i] == 0x82 { 84 | if len(buf) < i+2 { 85 | return 86 | } 87 | 88 | l = int(binary.BigEndian.Uint16(buf[i+1 : i+2])) 89 | i += 2 90 | } 91 | 92 | if len(buf) < i+l { 93 | return 94 | } 95 | 96 | return buf[i : i+l] 97 | } 98 | -------------------------------------------------------------------------------- /internal/icc/verify.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package icc 9 | 10 | import ( 11 | "log" 12 | 13 | "github.com/ProtonMail/go-crypto/openpgp" 14 | "github.com/hsanjuan/go-nfctype4/apdu" 15 | ) 16 | 17 | const ( 18 | PW_VERIFY = 0x00 19 | PW_LOCK = 0xff 20 | 21 | // PW1 only valid for one PSO:CDS command 22 | PW1_CDS_MULTI = 0x00 23 | 24 | PW1_CDS = 0x81 25 | PW1 = 0x82 26 | PW3 = 0x83 27 | ) 28 | 29 | // Verify implements 30 | // p51, 7.2.2 VERIFY, OpenPGP application Version 3.4. 31 | // 32 | // Unlike most smartcards, in this implementation PW1 represents the actual 33 | // private key passphrase and it is used to decrypt the selected OpenPGP 34 | // private subkey. 35 | // 36 | // Therefore the passphrase/PIN verification status matches the presence of a 37 | // decrypted subkey in memory. 38 | // 39 | // Verification of the admin password (PW3) is not supported as, in this 40 | // implementation, card personalization is managed outside OpenPGP 41 | // specifications. 42 | func (card *Interface) Verify(P1 byte, P2 byte, passphrase []byte) (rapdu *apdu.RAPDU, err error) { 43 | var subkey *openpgp.Subkey 44 | 45 | defer card.signalVerificationStatus() 46 | 47 | switch P2 { 48 | case PW1_CDS: 49 | subkey = card.Sig 50 | case PW1: 51 | // Used for PSO:DEC and PSO:ENC, the latter operation does not 52 | // use any OpenPGP key but we still use the same subkey for 53 | // cardholder authentication. 54 | subkey = card.Dec 55 | case PW3: 56 | // PW3 is not implemented as card personalization is managed 57 | // outside OpenPGP specifications. 58 | return CommandNotAllowed(), nil 59 | } 60 | 61 | if subkey == nil || subkey.PrivateKey == nil { 62 | log.Printf("missing key for VERIFY") 63 | return CommandNotAllowed(), nil 64 | } 65 | 66 | var msg string 67 | 68 | switch P1 { 69 | case PW_VERIFY: 70 | switch { 71 | case len(passphrase) == 0: 72 | // return access status when PW empty 73 | if !subkey.PrivateKey.Encrypted { 74 | rapdu = CommandCompleted(nil) 75 | } else { 76 | rapdu = VerifyFail(card.errorCounterPW1) 77 | } 78 | case !subkey.PrivateKey.Encrypted: 79 | // To support the out-of-band `unlock` management 80 | // command over SSH we deviate from specifications. 81 | // 82 | // If the key is already decrypted then we return 83 | // success rather than re-verifying the passphrase. 84 | // 85 | // This prevents plaintext transmission of the passphrase 86 | // (which can be a dummy if already unlocked). 87 | msg = "already unlocked" 88 | case card.errorCounterPW1 == 0: 89 | // for now this counter is volatile across reboots 90 | msg = "error counter blocked, cannot unlock" 91 | rapdu = VerifyFail(card.errorCounterPW1) 92 | case subkey.PrivateKey.Decrypt(passphrase) == nil: 93 | // correct verification sets resets counter to default value 94 | card.errorCounterPW1 = DEFAULT_PW1_ERROR_COUNTER 95 | msg = "unlocked" 96 | default: 97 | // The standard is not clear on the specific conditions 98 | // that decrese the counter as "incorrect usage" is 99 | // mentioned. This implementation only cares to prevent 100 | // passphrase brute forcing. 101 | card.errorCounterPW1 -= 1 102 | msg = "unlock error" 103 | rapdu = VerifyFail(card.errorCounterPW1) 104 | } 105 | case PW_LOCK: 106 | if subkey.PrivateKey.Encrypted { 107 | msg = "already locked" 108 | } else { 109 | subkey.PrivateKey = card.Restore(subkey) 110 | 111 | if subkey.PrivateKey.Encrypted { 112 | msg = "locked" 113 | } else { 114 | msg = "remains unlocked (no passphrase)" 115 | } 116 | } 117 | default: 118 | return CommandNotAllowed(), nil 119 | } 120 | 121 | if msg != "" { 122 | log.Printf("VERIFY: % X %s", subkey.PrivateKey.Fingerprint, msg) 123 | } 124 | 125 | if rapdu == nil { 126 | rapdu = CommandCompleted(nil) 127 | } 128 | 129 | return 130 | } 131 | 132 | func (card *Interface) signalVerificationStatus() { 133 | for _, subkey := range []*openpgp.Subkey{card.Sig, card.Dec} { 134 | if subkey != nil && subkey.PrivateKey != nil && subkey.PrivateKey.PrivateKey != nil && !subkey.PrivateKey.Encrypted { 135 | // at least one key is unlocked 136 | LED("blue", true) 137 | return 138 | } 139 | } 140 | 141 | // all keys are locked 142 | LED("blue", false) 143 | } 144 | -------------------------------------------------------------------------------- /internal/icc/wake.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package icc 11 | 12 | import ( 13 | "time" 14 | 15 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 16 | ) 17 | 18 | // SleepTimeout is the time duration to fall back to low power mode after the 19 | // token is awakened by a command. 20 | const SleepTimeout = 60 * time.Second 21 | 22 | // Wake sets the CPU speed to its highest frequency. After the duration defined 23 | // in the `SleepTimeout` constant the CPU speed is restored to its lowest 24 | // frequency. 25 | func (card *Interface) Wake() { 26 | card.Lock() 27 | defer card.Unlock() 28 | 29 | if card.awake { 30 | return 31 | } 32 | 33 | _ = imx6ul.SetARMFreq(imx6ul.FreqMax) 34 | card.awake = true 35 | 36 | time.AfterFunc(SleepTimeout, func() { 37 | card.Lock() 38 | defer card.Unlock() 39 | 40 | _ = imx6ul.SetARMFreq(imx6ul.FreqLow) 41 | card.awake = false 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /internal/snvs/crypto.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package snvs 9 | 10 | import ( 11 | "crypto/aes" 12 | "crypto/cipher" 13 | "crypto/hmac" 14 | "crypto/sha256" 15 | "errors" 16 | ) 17 | 18 | func encryptCTR(key []byte, iv []byte, input []byte) (output []byte, err error) { 19 | block, err := aes.NewCipher(key) 20 | 21 | if err != nil { 22 | return 23 | } 24 | 25 | output = iv 26 | 27 | mac := hmac.New(sha256.New, key) 28 | mac.Write(iv) 29 | 30 | stream := cipher.NewCTR(block, iv) 31 | output = append(output, make([]byte, len(input))...) 32 | 33 | stream.XORKeyStream(output[len(iv):], input) 34 | mac.Write(output[len(iv):]) 35 | 36 | output = append(output, mac.Sum(nil)...) 37 | 38 | return 39 | } 40 | 41 | func decryptCTR(key []byte, iv []byte, input []byte) (output []byte, err error) { 42 | block, err := aes.NewCipher(key) 43 | 44 | if err != nil { 45 | return 46 | } 47 | 48 | mac := hmac.New(sha256.New, key) 49 | mac.Write(iv) 50 | 51 | if len(input) < mac.Size() { 52 | return nil, errors.New("invalid length") 53 | } 54 | 55 | inputMac := input[len(input)-mac.Size():] 56 | mac.Write(input[0 : len(input)-mac.Size()]) 57 | 58 | if !hmac.Equal(inputMac, mac.Sum(nil)) { 59 | return nil, errors.New("invalid HMAC") 60 | } 61 | 62 | stream := cipher.NewCTR(block, iv) 63 | output = make([]byte, len(input)-mac.Size()) 64 | 65 | stream.XORKeyStream(output, input[0:len(input)-mac.Size()]) 66 | 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /internal/snvs/snvs.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package snvs 11 | 12 | import ( 13 | "crypto/aes" 14 | "crypto/ecdsa" 15 | "crypto/elliptic" 16 | "crypto/hkdf" 17 | "crypto/sha256" 18 | "errors" 19 | 20 | "filippo.io/keygen" 21 | 22 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 23 | "github.com/usbarmory/tamago/soc/nxp/snvs" 24 | ) 25 | 26 | const diversifierDev = "GoKeySNVSDeviceK" 27 | 28 | func init() { 29 | if !imx6ul.SNVS.Available() { 30 | return 31 | } 32 | 33 | // When running natively on i.MX6, and under secure boot, the built-in 34 | // Data Co-Processor (DCP) is used for AES key derivation. 35 | // 36 | // A device specific random 256-bit OTPMK key is fused in each SoC at 37 | // manufacturing time, this key is unreadable and can only be used by 38 | // the DCP for AES encryption/decryption of user data, through the 39 | // Secure Non-Volatile Storage (SNVS) companion block. 40 | // 41 | // This is leveraged to create the AES256 DO used by PSO:DEC and 42 | // PSO:ENC and to allow encrypted bundling of OpenPGP secret keys. 43 | imx6ul.DCP.Init() 44 | 45 | // Disable ARM debug operations 46 | imx6ul.Debug(false) 47 | 48 | imx6ul.SNVS.SetPolicy( 49 | snvs.SecurityPolicy{ 50 | Clock: true, 51 | Temperature: true, 52 | Voltage: true, 53 | SecurityViolation: true, 54 | HardFail: true, 55 | }, 56 | ) 57 | } 58 | 59 | // DeviceKey derives a device key, uniquely and deterministically generated for 60 | // this SoC for attestation purposes. 61 | func DeviceKey() (deviceKey *ecdsa.PrivateKey, err error) { 62 | iv := make([]byte, aes.BlockSize) 63 | key, err := imx6ul.DCP.DeriveKey([]byte(diversifierDev), iv, -1) 64 | 65 | if err != nil { 66 | return 67 | } 68 | 69 | salt := imx6ul.UniqueID() 70 | 71 | if key, err = hkdf.Key(sha256.New, key, salt[:], "", sha256.BlockSize); err != nil { 72 | return 73 | } 74 | 75 | return keygen.ECDSA(elliptic.P256(), key) 76 | } 77 | 78 | // Encrypt performs symmetric AES encryption using AES-256-CTR. The 79 | // initialization vector is prepended to the encrypted file, the HMAC for 80 | // authentication is appended: `iv (16 bytes) || ciphertext || hmac (32 81 | // bytes)`. 82 | // 83 | // The key is derived, with a diversifier, from the SNVS device-specific OTPMK 84 | // secret. 85 | func Encrypt(input []byte, diversifier []byte, iv []byte) (output []byte, err error) { 86 | // It is advised to use only deterministic input data for key 87 | // derivation. 88 | kdfIV := make([]byte, aes.BlockSize) 89 | key, err := imx6ul.DCP.DeriveKey(diversifier, kdfIV, -1) 90 | 91 | if err != nil { 92 | return 93 | } 94 | 95 | if len(input) < aes.BlockSize { 96 | return nil, errors.New("invalid length") 97 | } 98 | 99 | return encryptCTR(key, iv, input) 100 | } 101 | 102 | // Decrypt performs symmetric AES decryption using AES-256-CTR. The 103 | // ciphertext format is expected to have the initialization vector prepended 104 | // and an HMAC for authentication appended: `iv (16 bytes) || ciphertext || 105 | // hmac (32 bytes)`. 106 | // 107 | // The key is derived, with a diversifier, from the SNVS device-specific OTPMK 108 | // secret. 109 | func Decrypt(input []byte, diversifier []byte) (output []byte, err error) { 110 | // It is advised to use only deterministic input data for key 111 | // derivation, therefore we use the empty allocated IV before it being 112 | // filled. 113 | iv := make([]byte, aes.BlockSize) 114 | key, err := imx6ul.DCP.DeriveKey(diversifier, iv, -1) 115 | 116 | if err != nil { 117 | return 118 | } 119 | 120 | if len(input) < aes.BlockSize { 121 | return nil, errors.New("invalid length") 122 | } 123 | 124 | iv = input[0:aes.BlockSize] 125 | 126 | return decryptCTR(key, iv, input[aes.BlockSize:]) 127 | } 128 | -------------------------------------------------------------------------------- /internal/snvs/stubs.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build !tamago 9 | 10 | package snvs 11 | 12 | import ( 13 | "crypto/aes" 14 | "errors" 15 | ) 16 | 17 | func Encrypt(input []byte, key []byte, iv []byte) (output []byte, err error) { 18 | if len(input) < aes.BlockSize { 19 | return nil, errors.New("invalid length") 20 | } 21 | 22 | return encryptCTR(key, iv, input) 23 | } 24 | 25 | func Decrypt(input []byte, diversifier []byte) (output []byte, err error) { 26 | return nil, errors.New("not implemented") 27 | } 28 | -------------------------------------------------------------------------------- /internal/u2f/const.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package u2f 9 | 10 | // Diversifier for hardware key derivation (U2F private key wrapping). 11 | const DiversifierU2F = "GoKeySNVSU2F " 12 | -------------------------------------------------------------------------------- /internal/u2f/counter.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package u2f 11 | 12 | import ( 13 | "encoding/binary" 14 | "errors" 15 | "log" 16 | "runtime" 17 | "time" 18 | 19 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 20 | 21 | "github.com/usbarmory/armoryctl/atecc608" 22 | "github.com/usbarmory/armoryctl/led" 23 | ) 24 | 25 | const ( 26 | cntATECC = iota 27 | cntEMMC 28 | ) 29 | 30 | const ( 31 | read = 0 32 | increment = 1 33 | 34 | // Counter KeyID, #1 is used as it is never attached to any key. 35 | keyID = 0x01 36 | counterCmd = 0x24 37 | 38 | // When no HSM is present/supported (rev. γ) the counter value is saved 39 | // on the internal eMMC. 40 | // 41 | // The value is placed right before the Program Image offset (0x400) in 42 | // an area reserved for NXP optional Secondary Image Table 43 | // (0x200-0x400) but not used by the table itself. 44 | counterLBA = 1 45 | counterOffset = 512 - 4 46 | 47 | // user presence timeout in seconds 48 | timeout = 10 49 | ) 50 | 51 | // Counter represents an ATECC608A based monotonic counter instance. 52 | type Counter struct { 53 | kind int 54 | uid []byte 55 | presence chan bool 56 | } 57 | 58 | // Init initializes an ATECC608A backed U2F counter. A channel can be passed to 59 | // receive user presence notifications, if nil user presence is automatically 60 | // assumed. 61 | func (c *Counter) Init(presence chan bool) (err error) { 62 | boardModel, _ := usbarmory.Model() 63 | 64 | switch boardModel { 65 | case usbarmory.BETA: // ATECC608A 66 | var info string 67 | 68 | if info, err = atecc608.Info(); err != nil { 69 | return 70 | } 71 | 72 | c.uid = []byte(info) 73 | c.kind = cntATECC 74 | case usbarmory.GAMMA, usbarmory.LAN: // NXP SE050 present but not supported 75 | if err = usbarmory.MMC.Detect(); err != nil { 76 | return 77 | } 78 | 79 | cid := usbarmory.MMC.Info().CID 80 | c.uid = cid[:] 81 | c.kind = cntEMMC 82 | default: 83 | errors.New("could not detect hardware model") 84 | } 85 | 86 | c.presence = presence 87 | 88 | return 89 | } 90 | 91 | // Serial returns the unique identifier for the hardware implementing the 92 | // counter. 93 | func (c *Counter) Serial() []byte { 94 | return c.uid 95 | } 96 | 97 | func (c *Counter) counterCmd(mode byte) (cnt uint32, err error) { 98 | switch c.kind { 99 | case cntATECC: 100 | res, err := atecc608.ExecuteCmd(counterCmd, [1]byte{mode}, [2]byte{keyID, 0x00}, nil, true) 101 | 102 | if err != nil { 103 | return 0, err 104 | } 105 | 106 | return binary.LittleEndian.Uint32(res), nil 107 | case cntEMMC: 108 | card := usbarmory.MMC 109 | buf := make([]byte, card.Info().BlockSize) 110 | 111 | if err = card.ReadBlocks(counterLBA, buf); err != nil { 112 | return 113 | } 114 | 115 | cnt = binary.LittleEndian.Uint32(buf[counterOffset:]) 116 | 117 | if mode == read { 118 | return 119 | } 120 | 121 | cnt += 1 122 | binary.LittleEndian.PutUint32(buf[counterOffset:], cnt) 123 | 124 | if err = card.WriteBlocks(counterLBA, buf); err != nil { 125 | return 126 | } 127 | } 128 | 129 | return 130 | } 131 | 132 | // Increment increases the ATECC608A monotonic counter in slot <1> (not attached to any key). 133 | func (c *Counter) Increment(_ []byte, _ []byte, _ []byte) (cnt uint32, err error) { 134 | if cnt, err = c.counterCmd(increment); err != nil { 135 | log.Printf("U2F increment failed, %v", err) 136 | } else { 137 | log.Printf("U2F increment, counter:%d", cnt) 138 | } 139 | 140 | return 141 | } 142 | 143 | // Read reads the ATECC608A monotonic counter in slot <1> (not attached to any key). 144 | func (c *Counter) Read() (cnt uint32, err error) { 145 | return c.counterCmd(read) 146 | } 147 | 148 | // UserPresence verifies the user presence. 149 | func (c *Counter) UserPresence() (present bool) { 150 | if c.presence == nil { 151 | return true 152 | } 153 | 154 | var done = make(chan bool) 155 | go blink(done) 156 | 157 | log.Printf("U2F user presence request, type `p` within %ds to confirm", timeout) 158 | 159 | select { 160 | case <-c.presence: 161 | present = true 162 | case <-time.After(timeout * time.Second): 163 | log.Printf("U2F user presence request timed out") 164 | } 165 | 166 | done <- true 167 | 168 | if present { 169 | log.Printf("U2F user presence confirmed") 170 | } 171 | 172 | return 173 | } 174 | 175 | func blink(done chan bool) { 176 | var on bool 177 | 178 | for { 179 | select { 180 | case <-done: 181 | led.Set("white", false) 182 | return 183 | default: 184 | } 185 | 186 | on = !on 187 | led.Set("white", on) 188 | 189 | runtime.Gosched() 190 | time.Sleep(200 * time.Millisecond) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /internal/u2f/token.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package u2f 11 | 12 | import ( 13 | "bytes" 14 | "crypto/pbkdf2" 15 | "crypto/sha1" 16 | "crypto/sha256" 17 | "errors" 18 | "fmt" 19 | "log" 20 | "regexp" 21 | 22 | "github.com/usbarmory/GoKey/internal/snvs" 23 | 24 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 25 | "github.com/usbarmory/tamago/soc/nxp/usb" 26 | 27 | "github.com/gsora/fidati" 28 | "github.com/gsora/fidati/attestation" 29 | "github.com/gsora/fidati/keyring" 30 | "github.com/gsora/fidati/u2fhid" 31 | "github.com/gsora/fidati/u2ftoken" 32 | ) 33 | 34 | // Token represents an U2F authenticator instance. 35 | type Token struct { 36 | // enable device unique hardware encryption for bundled private keys 37 | SNVS bool 38 | // Attestation certificate 39 | PublicKey []byte 40 | // Attestation private key 41 | PrivateKey []byte 42 | // Presence is a channel used to signal user presence, when undefined 43 | // user presence is implicitly acknowledged. 44 | Presence chan bool 45 | 46 | // Keyring instance 47 | keyring *keyring.Keyring 48 | // Monotonic counter instance 49 | counter *Counter 50 | 51 | // internal state flags 52 | initialized bool 53 | } 54 | 55 | // Configure initializes the U2F token attestation keys and USB configuration. 56 | func Configure(device *usb.Device, token *Token) (err error) { 57 | token.keyring = &keyring.Keyring{} 58 | 59 | if token.SNVS { 60 | token.PrivateKey, err = snvs.Decrypt(token.PrivateKey, []byte(DiversifierU2F)) 61 | 62 | if err != nil { 63 | return fmt.Errorf("U2F key decryption failed, %v", err) 64 | } 65 | } 66 | 67 | t, err := u2ftoken.New(token.keyring, token.PublicKey, token.PrivateKey) 68 | 69 | if err != nil { 70 | return 71 | } 72 | 73 | hid, err := u2fhid.NewHandler(t) 74 | 75 | if err != nil { 76 | return 77 | } 78 | 79 | if err = fidati.ConfigureUSB(device.Configurations[0], device, hid); err != nil { 80 | return 81 | } 82 | 83 | // resolve conflict with Ethernet over USB 84 | numInterfaces := len(device.Configurations[0].Interfaces) 85 | device.Configurations[0].Interfaces[numInterfaces-1].Endpoints[usb.OUT].EndpointAddress = 0x04 86 | device.Configurations[0].Interfaces[numInterfaces-1].Endpoints[usb.IN].EndpointAddress = 0x84 87 | 88 | return 89 | } 90 | 91 | // Init initializes an U2F authenticator instance. 92 | func (token *Token) Init() (err error) { 93 | if token.keyring == nil { 94 | return errors.New("U2F token initialization failed, missing configuration") 95 | } 96 | 97 | counter := &Counter{} 98 | 99 | if err = counter.Init(token.Presence); err != nil { 100 | return 101 | } 102 | 103 | var mk []byte 104 | 105 | if token.SNVS { 106 | if mk, err = imx6ul.DCP.DeriveKey([]byte(DiversifierU2F), make([]byte, 16), -1); err != nil { 107 | return 108 | } 109 | } else { 110 | // On non-secure booted units we derive the master key from the 111 | // counter hardware serial number and the SoC unique ID. 112 | // 113 | // This provides a non-predictable master key which must 114 | // however be assumed compromised if a device is stolen/lost. 115 | uid := imx6ul.UniqueID() 116 | 117 | if mk, err = pbkdf2.Key(sha256.New, string(counter.Serial()), uid[:], 4096, 16); err != nil { 118 | return 119 | } 120 | } 121 | 122 | token.keyring.MasterKey = mk 123 | token.keyring.Counter = counter 124 | token.counter = counter 125 | token.initialized = true 126 | 127 | log.Printf("U2F token initialized") 128 | log.Print(token.Status()) 129 | 130 | return 131 | } 132 | 133 | // Initialized returns the U2F token initialization state. 134 | func (token *Token) Initialized() bool { 135 | return token.initialized 136 | } 137 | 138 | // Status returns attestation certificate fingerprint and counter status in 139 | // textual format. 140 | func (token *Token) Status() string { 141 | var status bytes.Buffer 142 | var s string 143 | 144 | fmt.Fprintf(&status, "------------------------------------------------------------ U2F token ----\n") 145 | fmt.Fprintf(&status, "Initialized ............: %v\n", token.initialized) 146 | fmt.Fprintf(&status, "Secure storage .........: %v\n", token.SNVS) 147 | fmt.Fprintf(&status, "User presence test .....: %v\n", token.Presence != nil) 148 | 149 | if token.initialized { 150 | val, err := token.counter.Read() 151 | 152 | if err != nil { 153 | s = err.Error() 154 | } else { 155 | s = fmt.Sprintf("%d", val) 156 | } 157 | } else { 158 | s = "N/A" 159 | } 160 | 161 | fmt.Fprintf(&status, "Counter ................: %v\n", s) 162 | fmt.Fprintf(&status, "Attestation certificate.: ") 163 | 164 | r := regexp.MustCompile(`([[:xdigit:]]{4})`) 165 | 166 | if k := token.PublicKey; len(k) != 0 { 167 | _, cert, err := attestation.ParseCertificate(k) 168 | 169 | if err != nil { 170 | s = err.Error() 171 | } 172 | 173 | fp := fmt.Sprintf("%X\n", sha1.Sum(cert.Raw)) 174 | status.WriteString(r.ReplaceAllString(fp, "$1 ")) 175 | } else { 176 | status.WriteString("missing\n") 177 | } 178 | 179 | return status.String() 180 | } 181 | -------------------------------------------------------------------------------- /internal/usb/const.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package usb 9 | 10 | // Diversifier for hardware key derivation (SSH private key wrapping). 11 | const DiversifierSSH = "GoKeySNVSOpenSSH" 12 | -------------------------------------------------------------------------------- /internal/usb/device.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package usb 11 | 12 | import ( 13 | "github.com/usbarmory/tamago/soc/nxp/usb" 14 | ) 15 | 16 | const configurationIndex = 0 17 | const maxPacketSize = 512 18 | 19 | // ConfigureDevice configures a basic composite USB device. 20 | func ConfigureDevice(device *usb.Device, serial string) { 21 | // Supported Language Code Zero: English 22 | device.SetLanguageCodes([]uint16{0x0409}) 23 | 24 | // device descriptor 25 | device.Descriptor = &usb.DeviceDescriptor{} 26 | device.Descriptor.SetDefaults() 27 | 28 | // p5, Table 1-1. Device Descriptor Using Class Codes for IAD, 29 | // USB Interface Association Descriptor Device Class Code and Use Model. 30 | device.Descriptor.DeviceClass = 0xef 31 | device.Descriptor.DeviceSubClass = 0x02 32 | device.Descriptor.DeviceProtocol = 0x01 33 | 34 | // http://pid.codes/1209/2702/ 35 | device.Descriptor.VendorId = 0x1209 36 | device.Descriptor.ProductId = 0x2702 37 | 38 | device.Descriptor.Device = 0x0001 39 | 40 | iManufacturer, _ := device.AddString(`WithSecure Foundry`) 41 | device.Descriptor.Manufacturer = iManufacturer 42 | 43 | iProduct, _ := device.AddString(`Composite Ethernet ECM / OpenPGP Smart Card Device`) 44 | device.Descriptor.Product = iProduct 45 | 46 | iSerial, _ := device.AddString(serial) 47 | device.Descriptor.SerialNumber = iSerial 48 | 49 | conf := &usb.ConfigurationDescriptor{} 50 | conf.SetDefaults() 51 | 52 | device.AddConfiguration(conf) 53 | 54 | // device qualifier 55 | device.Qualifier = &usb.DeviceQualifierDescriptor{} 56 | device.Qualifier.SetDefaults() 57 | device.Qualifier.NumConfigurations = uint8(len(device.Configurations)) 58 | } 59 | -------------------------------------------------------------------------------- /internal/usb/irq.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package usb 11 | 12 | import ( 13 | "log" 14 | 15 | "github.com/usbarmory/tamago/arm" 16 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 17 | "github.com/usbarmory/tamago/soc/nxp/usb" 18 | ) 19 | 20 | func StartInterruptHandler(port *usb.USB) { 21 | if port == nil { 22 | return 23 | } 24 | 25 | imx6ul.GIC.Init(true, false) 26 | imx6ul.GIC.EnableInterrupt(port.IRQ, true) 27 | 28 | port.EnableInterrupt(usb.IRQ_URI) // reset 29 | port.EnableInterrupt(usb.IRQ_PCI) // port change detect 30 | port.EnableInterrupt(usb.IRQ_UI) // transfer completion 31 | 32 | isr := func() { 33 | irq := imx6ul.GIC.GetInterrupt(true) 34 | 35 | switch irq { 36 | case port.IRQ: 37 | port.ServiceInterrupts() 38 | default: 39 | log.Printf("internal error, unexpected IRQ %d", irq) 40 | } 41 | } 42 | 43 | arm.ServiceInterrupts(isr) 44 | } 45 | -------------------------------------------------------------------------------- /internal/usb/smartcard.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package usb 11 | 12 | import ( 13 | "github.com/usbarmory/GoKey/internal/ccid" 14 | 15 | "github.com/usbarmory/tamago/soc/nxp/usb" 16 | ) 17 | 18 | var queue = make(chan []byte) 19 | var CCID *ccid.Interface 20 | 21 | // CCIDTx implements the endpoint 1 IN function, used to transmit APDU 22 | // responses from device to host. 23 | func CCIDTx(_ []byte, lastErr error) (in []byte, err error) { 24 | select { 25 | case res := <-queue: 26 | in = res 27 | default: 28 | } 29 | 30 | return 31 | } 32 | 33 | // CCIDRx implements the endpoint 1 OUT function, used to receive APDU 34 | // requests from host to device. 35 | func CCIDRx(out []byte, lastErr error) (_ []byte, err error) { 36 | in, err := CCID.Rx(out) 37 | 38 | if err != nil { 39 | return 40 | } 41 | 42 | queue <- in 43 | 44 | return 45 | } 46 | 47 | // ConfigureCCID configures a Chip/SmartCard interface USB device. 48 | func ConfigureCCID(device *usb.Device, ccidInterface *ccid.Interface) { 49 | CCID = ccidInterface 50 | 51 | // Chip/SmartCard interface 52 | iface := &usb.InterfaceDescriptor{} 53 | iface.SetDefaults() 54 | iface.NumEndpoints = 2 55 | iface.InterfaceClass = usb.SMARTCARD_DEVICE_CLASS 56 | 57 | iInterface, _ := device.AddString(`Smart Card Control`) 58 | iface.Interface = iInterface 59 | 60 | ccid := &usb.CCIDDescriptor{} 61 | ccid.SetDefaults() 62 | // all voltages 63 | ccid.VoltageSupport = 0x7 64 | // T=0, T=1 65 | ccid.Protocols = 0x3 66 | // 4 MHz 67 | ccid.DefaultClock = 4000 68 | // 5 MHz 69 | ccid.MaximumClock = 5000 70 | ccid.DataRate = 9600 71 | // maximum@5MHz according to ISO7816-3 72 | ccid.MaxDataRate = 625000 73 | ccid.MaxIFSD = 0xfe 74 | // Auto configuration based on ATR 75 | // Auto activation on insert 76 | // Auto voltage selection 77 | // Auto clock change 78 | // Auto baud rate change 79 | // Auto parameter negotiation made by CCID 80 | // Short and extended APDU level exchange 81 | ccid.Features = 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x40000 82 | ccid.MaxCCIDMessageLength = usb.DTD_PAGES * usb.DTD_PAGE_SIZE 83 | ccid.MaxIFSD = ccid.MaxCCIDMessageLength 84 | // echo 85 | ccid.ClassGetResponse = 0xff 86 | ccid.ClassEnvelope = 0xff 87 | ccid.MaxCCIDBusySlots = 1 88 | 89 | iface.ClassDescriptors = append(iface.ClassDescriptors, ccid.Bytes()) 90 | 91 | ep3IN := &usb.EndpointDescriptor{} 92 | ep3IN.SetDefaults() 93 | ep3IN.EndpointAddress = 0x83 94 | ep3IN.Attributes = 2 95 | ep3IN.MaxPacketSize = maxPacketSize 96 | ep3IN.Function = CCIDTx 97 | 98 | iface.Endpoints = append(iface.Endpoints, ep3IN) 99 | 100 | ep3OUT := &usb.EndpointDescriptor{} 101 | ep3OUT.SetDefaults() 102 | ep3OUT.EndpointAddress = 0x03 103 | ep3OUT.Attributes = 2 104 | ep3OUT.MaxPacketSize = maxPacketSize 105 | ep3OUT.Function = CCIDRx 106 | 107 | iface.Endpoints = append(iface.Endpoints, ep3OUT) 108 | 109 | device.Configurations[configurationIndex].AddInterface(iface) 110 | } 111 | -------------------------------------------------------------------------------- /internal/usb/ssh.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package usb 11 | 12 | import ( 13 | "bytes" 14 | "crypto/ecdsa" 15 | "crypto/elliptic" 16 | "crypto/rand" 17 | "encoding/binary" 18 | "errors" 19 | "fmt" 20 | "io" 21 | "log" 22 | "net" 23 | "os" 24 | "regexp" 25 | "runtime/debug" 26 | "strings" 27 | 28 | "github.com/usbarmory/GoKey/internal/age" 29 | "github.com/usbarmory/GoKey/internal/icc" 30 | "github.com/usbarmory/GoKey/internal/snvs" 31 | "github.com/usbarmory/GoKey/internal/u2f" 32 | 33 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 34 | 35 | "golang.org/x/crypto/ssh" 36 | "golang.org/x/crypto/ssh/terminal" 37 | ) 38 | 39 | const help = ` 40 | help # this help 41 | exit, quit # close session 42 | rand # gather 32 bytes from TRNG via crypto/rand 43 | reboot # restart 44 | status # display smartcard/token status 45 | build # display build information 46 | 47 | 48 | init # initialize OpenPGP smartcard 49 | lock (all|sig|dec) # OpenPGP key(s) lock 50 | unlock (all|sig|dec) # OpenPGP key(s) unlock, prompts passphrase 51 | 52 | rpc # PKCS#11 RPC socket 53 | # use with 'ssh -L p11kit.sock:127.0.0.1:22' 54 | 55 | age-plugin (gen|identity-v1) # handle age plugin state machine 56 | 57 | u2f # initialize U2F token w/ user presence test 58 | u2f !test # initialize U2F token w/o user presence test 59 | p # confirm user presence 60 | ` 61 | 62 | // Console represents the management SSH server instance. 63 | type Console struct { 64 | // AuthorizedKey is the public key for SSH client authentication, it 65 | // can be bundled at compile time. 66 | AuthorizedKey []byte 67 | 68 | // PrivateKey is the private key for the management SSH server, it can 69 | // be bundled at compile time (encrypted if secure boot is present). 70 | // 71 | // If left empty it is generated at Start() either randomly (w/o secure 72 | // boot) or uniquely for each device (w/ secure boot). 73 | PrivateKey []byte 74 | 75 | // Card is the OpenPGP smartcard instance. 76 | Card *icc.Interface 77 | // Token is the U2F token instance. 78 | Token *u2f.Token 79 | // PLugin is the age plugin instance. 80 | Plugin *age.Plugin 81 | 82 | Started chan bool 83 | Listener net.Listener 84 | Banner string 85 | 86 | term *terminal.Terminal 87 | } 88 | 89 | var lockCommandPattern = regexp.MustCompile(`(lock|unlock) (all|sig|dec)`) 90 | var pageCommandPattern = regexp.MustCompile(`age-plugin (.*)`) 91 | 92 | func (c *Console) lockCommand(op string, arg string) (res string) { 93 | var err error 94 | var pws []byte 95 | 96 | if arg == "sig" || arg == "all" { 97 | pws = append(pws, icc.PW1_CDS) 98 | } 99 | 100 | if arg == "dec" || arg == "all" { 101 | pws = append(pws, icc.PW1) 102 | } 103 | 104 | if len(pws) == 0 { 105 | return 106 | } 107 | 108 | switch op { 109 | case "lock": 110 | if !c.Card.Initialized() { 111 | return "card not initialized" 112 | } 113 | 114 | for _, pw := range pws { 115 | if _, err := c.Card.Verify(icc.PW_LOCK, pw, nil); err != nil { 116 | return err.Error() 117 | } 118 | } 119 | case "unlock": 120 | var passphrase string 121 | 122 | if !c.Card.Initialized() { 123 | if err = c.Card.Init(); err != nil { 124 | break 125 | } 126 | } 127 | 128 | if passphrase, err = c.term.ReadPassword("Passphrase: "); err != nil { 129 | break 130 | } 131 | 132 | for _, pw := range pws { 133 | if _, err = c.Card.Verify(icc.PW_VERIFY, pw, []byte(passphrase)); err != nil { 134 | break 135 | } 136 | } 137 | } 138 | 139 | if err != nil { 140 | return err.Error() 141 | } 142 | 143 | return 144 | } 145 | 146 | func (c *Console) handleTerminal(conn ssh.Channel) { 147 | log.SetOutput(io.MultiWriter(os.Stdout, c.term)) 148 | defer log.SetOutput(os.Stdout) 149 | 150 | fmt.Fprintf(c.term, "%s\n", c.Banner) 151 | fmt.Fprintf(c.term, "%s\n", string(c.term.Escape.Cyan)+help+string(c.term.Escape.Reset)) 152 | 153 | for { 154 | cmd, err := c.term.ReadLine() 155 | 156 | if err == io.EOF { 157 | break 158 | } 159 | 160 | if err != nil { 161 | log.Printf("readline error: %v", err) 162 | continue 163 | } 164 | 165 | err = c.handleCommand(conn, cmd) 166 | 167 | if err == io.EOF { 168 | break 169 | } 170 | 171 | if err != nil { 172 | log.Printf("error: %v", err) 173 | } 174 | } 175 | 176 | log.Printf("closing ssh connection") 177 | conn.Close() 178 | } 179 | 180 | func (c *Console) handleCommand(conn ssh.Channel, cmd string) (err error) { 181 | var res string 182 | 183 | switch cmd { 184 | case "exit", "quit": 185 | res = "logout" 186 | err = io.EOF 187 | case "help": 188 | res = string(c.term.Escape.Cyan) + help + string(c.term.Escape.Reset) 189 | case "init": 190 | err = c.Card.Init() 191 | case "rpc": 192 | return c.Card.ServeRPC(conn) 193 | case "u2f": 194 | c.Token.Presence = make(chan bool) 195 | err = c.Token.Init() 196 | case "u2f !test": 197 | c.Token.Presence = nil 198 | err = c.Token.Init() 199 | case "p": 200 | if !c.Token.Initialized() { 201 | res = "token not initialized, issue 'u2f' first" 202 | } else if c.Token.Presence == nil { 203 | res = "U2F presence not required" 204 | } else { 205 | select { 206 | case c.Token.Presence <- true: 207 | default: 208 | res = "U2F presence not requested" 209 | } 210 | } 211 | case "rand": 212 | buf := make([]byte, 32) 213 | _, _ = rand.Read(buf) 214 | res = string(c.term.Escape.Cyan) + fmt.Sprintf("%x", buf) + string(c.term.Escape.Reset) 215 | case "reboot": 216 | imx6ul.Reset() 217 | case "status": 218 | res = strings.Join([]string{c.Card.Status(), c.Token.Status()}, "") 219 | case "build": 220 | if bi, ok := debug.ReadBuildInfo(); ok { 221 | res = bi.String() 222 | } 223 | default: 224 | if m := pageCommandPattern.FindStringSubmatch(cmd); len(m) == 2 { 225 | if !c.Plugin.Initialized() { 226 | res = "plugin not initialized" 227 | } else { 228 | res = c.Plugin.Handle(conn, m[1]) 229 | } 230 | } else if m := lockCommandPattern.FindStringSubmatch(cmd); len(m) == 3 { 231 | res = c.lockCommand(m[1], m[2]) 232 | } else { 233 | return errors.New("unknown command, type `help`") 234 | } 235 | } 236 | 237 | fmt.Fprintln(c.term, res) 238 | 239 | return 240 | } 241 | 242 | // handleDirectForward forwards the `rpc` command regardless of the request 243 | func (c *Console) handleDirectForward(srvConn *ssh.ServerConn, newChannel ssh.NewChannel) { 244 | conn, _, err := newChannel.Accept() 245 | 246 | if err != nil { 247 | log.Printf("error accepting channel, %v", err) 248 | return 249 | } 250 | 251 | c.handleCommand(conn, "rpc") 252 | conn.Close() 253 | } 254 | 255 | func (c *Console) handleSession(newChannel ssh.NewChannel) { 256 | conn, requests, err := newChannel.Accept() 257 | 258 | if err != nil { 259 | log.Printf("error accepting channel, %v", err) 260 | return 261 | } 262 | 263 | c.term = terminal.NewTerminal(conn, "") 264 | c.term.SetPrompt(string(c.term.Escape.Red) + "> " + string(c.term.Escape.Reset)) 265 | 266 | go func() { 267 | for req := range requests { 268 | reqSize := len(req.Payload) 269 | 270 | switch req.Type { 271 | case "exec": 272 | cmd := string(req.Payload[4:]) 273 | c.handleCommand(conn, cmd) 274 | conn.Close() 275 | return 276 | case "shell": 277 | go c.handleTerminal(conn) 278 | req.Reply(true, nil) 279 | case "pty-req": 280 | // p10, 6.2. Requesting a Pseudo-Terminal, RFC4254 281 | if reqSize < 4 { 282 | log.Printf("malformed pty-req request") 283 | continue 284 | } 285 | 286 | termVariableSize := int(req.Payload[3]) 287 | 288 | if reqSize < 4+termVariableSize+8 { 289 | log.Printf("malformed pty-req request") 290 | continue 291 | } 292 | 293 | w := binary.BigEndian.Uint32(req.Payload[4+termVariableSize:]) 294 | h := binary.BigEndian.Uint32(req.Payload[4+termVariableSize+4:]) 295 | 296 | _ = c.term.SetSize(int(w), int(h)) 297 | _ = req.Reply(true, nil) 298 | case "window-change": 299 | // p10, 6.7. Window Dimension Change Message, RFC4254 300 | if reqSize < 8 { 301 | log.Printf("malformed window-change request") 302 | continue 303 | } 304 | 305 | w := binary.BigEndian.Uint32(req.Payload) 306 | h := binary.BigEndian.Uint32(req.Payload[4:]) 307 | 308 | _ = c.term.SetSize(int(w), int(h)) 309 | } 310 | } 311 | }() 312 | } 313 | 314 | func (c *Console) handleChannel(srvConn *ssh.ServerConn, newChannel ssh.NewChannel) { 315 | switch newChannel.ChannelType() { 316 | case "direct-tcpip": 317 | c.handleDirectForward(srvConn, newChannel) 318 | case "session": 319 | c.handleSession(newChannel) 320 | default: 321 | newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType())) 322 | } 323 | } 324 | 325 | func (c *Console) handleChannels(srvConn *ssh.ServerConn, chans <-chan ssh.NewChannel) { 326 | for newChannel := range chans { 327 | go c.handleChannel(srvConn, newChannel) 328 | } 329 | } 330 | 331 | func (c *Console) start(key interface{}) { 332 | pubKey, _, _, _, err := ssh.ParseAuthorizedKey(c.AuthorizedKey) 333 | 334 | if err != nil { 335 | log.Fatal("invalid authorized key: ", err) 336 | } 337 | 338 | srv := &ssh.ServerConfig{ 339 | PublicKeyCallback: func(meta ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { 340 | if bytes.Equal(key.Marshal(), pubKey.Marshal()) { 341 | return &ssh.Permissions{ 342 | Extensions: map[string]string{ 343 | "pubkey-fp": ssh.FingerprintSHA256(pubKey), 344 | }, 345 | }, nil 346 | } 347 | return nil, fmt.Errorf("unknown public key for %q", meta.User()) 348 | }, 349 | } 350 | 351 | signer, err := ssh.NewSignerFromKey(key) 352 | 353 | if err != nil { 354 | log.Fatal("key conversion error: ", err) 355 | } 356 | 357 | srv.AddHostKey(signer) 358 | 359 | log.Printf("starting ssh server (%s)", ssh.FingerprintSHA256(signer.PublicKey())) 360 | c.Started <- true 361 | 362 | for { 363 | conn, err := c.Listener.Accept() 364 | 365 | c.Card.Wake() 366 | 367 | if err != nil { 368 | log.Printf("error accepting connection, %v", err) 369 | continue 370 | } 371 | 372 | srvConn, chans, reqs, err := ssh.NewServerConn(conn, srv) 373 | 374 | if err != nil { 375 | log.Printf("error accepting handshake, %v", err) 376 | continue 377 | } 378 | 379 | log.Printf("new ssh connection from %s (%s)", srvConn.RemoteAddr(), srvConn.ClientVersion()) 380 | 381 | go ssh.DiscardRequests(reqs) 382 | go c.handleChannels(srvConn, chans) 383 | } 384 | } 385 | 386 | // Start configures and starts the management SSH server. 387 | func (c *Console) Start() (err error) { 388 | var key interface{} 389 | 390 | if len(c.PrivateKey) != 0 { 391 | if c.Card.SNVS || c.Token.SNVS { 392 | c.PrivateKey, _ = snvs.Decrypt(c.PrivateKey, []byte(DiversifierSSH)) 393 | } 394 | 395 | key, err = ssh.ParseRawPrivateKey(c.PrivateKey) 396 | } else if c.Card.SNVS || c.Token.SNVS { 397 | key, err = snvs.DeviceKey() 398 | } else { 399 | key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 400 | } 401 | 402 | if err != nil { 403 | log.Fatal("private key error: ", err) 404 | } 405 | 406 | go func() { 407 | c.start(key) 408 | }() 409 | 410 | return 411 | } 412 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | package main 9 | 10 | //go:generate go run bundle_keys.go 11 | 12 | // SNVS 13 | var ( 14 | SNVS bool 15 | initAtBoot bool 16 | ) 17 | 18 | // SSH 19 | var ( 20 | sshPublicKey []byte 21 | sshPrivateKey []byte 22 | ) 23 | 24 | // OpenPGP 25 | var ( 26 | pgpSecretKey []byte 27 | URL string 28 | NAME string 29 | LANGUAGE string 30 | SEX string 31 | ) 32 | 33 | // U2F 34 | var ( 35 | u2fPublicKey []byte 36 | u2fPrivateKey []byte 37 | ) 38 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | // https://github.com/usbarmory/GoKey 2 | // 3 | // Copyright (c) WithSecure Corporation 4 | // 5 | // Use of this source code is governed by the license 6 | // that can be found in the LICENSE file. 7 | 8 | //go:build tamago && arm 9 | 10 | package main 11 | 12 | import ( 13 | _ "unsafe" 14 | 15 | "github.com/usbarmory/tamago/dma" 16 | ) 17 | 18 | // Override standard memory allocation as having 3 USB endpoints (CDC, ICC, 19 | // U2F) requires more than what the iRAM can handle. 20 | 21 | //go:linkname ramSize runtime.ramSize 22 | var ramSize uint = 0x10000000 - 0x100000 // 256MB - 1MB 23 | var dmaStart uint = 0xa0000000 - 0x100000 24 | 25 | // 1MB 26 | var dmaSize = 0x100000 27 | 28 | func init() { 29 | dma.Init(dmaStart, dmaSize) 30 | } 31 | --------------------------------------------------------------------------------