├── internal ├── ota │ ├── disable_auth.go │ ├── auth.go │ ├── archive.go │ ├── ft.go │ └── ota.go ├── ble │ ├── session.go │ ├── crypto.go │ ├── pairing.go │ ├── ble.go │ ├── data.go │ └── api.go ├── crypto │ ├── dcp.go │ ├── config.go │ ├── keyring.go │ └── cipher.go ├── ums │ ├── ums.go │ ├── pairing.go │ ├── usb.go │ └── scsi.go └── hab │ └── hab.go ├── AUTHORS ├── mem.go ├── api ├── api.go └── armory.proto ├── cmd └── armory-drive-install │ ├── fixup.go │ ├── ft.go │ ├── hab.go │ ├── sdp.go │ ├── github.go │ ├── const.go │ └── main.go ├── go.mod ├── assets └── keys.go ├── LICENSE ├── console.go ├── main.go ├── README.md ├── Makefile └── go.sum /internal/ota/disable_auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // +build disable_fr_auth 7 | 8 | package ota 9 | 10 | const DisableAuth = true 11 | 12 | var FRPublicKey []byte 13 | var LogPublicKey []byte 14 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of armory-drive significant contributors. 2 | # 3 | # This does not necessarily list everyone who has contributed code, 4 | # especially since many employees of one corporation may be contributing. 5 | # To see the full list of contributors, see the revision history in 6 | # source control. 7 | Andrea Barisani 8 | Andrej Rosano 9 | -------------------------------------------------------------------------------- /internal/ble/session.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ble 7 | 8 | import ( 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Session struct { 14 | sync.Mutex 15 | 16 | Last int64 17 | Skew time.Duration 18 | Active bool 19 | Data []byte 20 | } 21 | 22 | func (s *Session) Reset() { 23 | s.Active = false 24 | s.Data = nil 25 | } 26 | 27 | func (s *Session) Time() int64 { 28 | return time.Now().Add(s.Skew).UnixNano() / (1000 * 1000) 29 | } 30 | -------------------------------------------------------------------------------- /internal/ota/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // +build !disable_fr_auth 7 | 8 | package ota 9 | 10 | import ( 11 | _ "embed" 12 | ) 13 | 14 | const DisableAuth = false 15 | 16 | // FRPublicKey represents the firmware releases manifest authentication key. 17 | //go:embed armory-drive.pub 18 | var FRPublicKey []byte 19 | 20 | // LogPublicKey represents the firmware releases transparency log. 21 | // authentication key. 22 | //go:embed armory-drive-log.pub 23 | var LogPublicKey []byte 24 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | _ "unsafe" 10 | 11 | "github.com/usbarmory/tamago/dma" 12 | ) 13 | 14 | // Override standard memory allocation, as this application requires large DMA 15 | // descriptors. 16 | 17 | //go:linkname ramSize runtime.ramSize 18 | var ramSize uint = 0x10000000 // 256MB 19 | // 2nd half of external RAM (256MB) 20 | var dmaStart uint = 0x90000000 21 | 22 | // 256MB 23 | var dmaSize = 0x10000000 24 | 25 | func init() { 26 | dma.Init(dmaStart, dmaSize) 27 | } 28 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package api 7 | 8 | import ( 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | func (msg *Message) Bytes() (buf []byte) { 13 | buf, _ = proto.Marshal(msg) 14 | return 15 | } 16 | 17 | func (env *Envelope) Bytes() (buf []byte) { 18 | buf, _ = proto.Marshal(env) 19 | return 20 | } 21 | 22 | func (kex *KeyExchange) Bytes() (buf []byte) { 23 | buf, _ = proto.Marshal(kex) 24 | return 25 | } 26 | 27 | func (qr *PairingQRCode) Bytes() (buf []byte) { 28 | buf, _ = proto.Marshal(qr) 29 | return 30 | } 31 | 32 | func (status *Status) Bytes() (buf []byte) { 33 | buf, _ = proto.Marshal(status) 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /internal/ble/crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ble 7 | 8 | import ( 9 | "github.com/usbarmory/armory-drive/api" 10 | ) 11 | 12 | func (b *BLE) verifyEnvelope(env *api.Envelope) (err error) { 13 | return b.Keyring.VerifyECDSA(env.Message, env.Signature, b.session.Active) 14 | } 15 | 16 | func (b *BLE) signEnvelope(env *api.Envelope) (err error) { 17 | env.Signature, err = b.Keyring.SignECDSA(env.Message, b.session.Active) 18 | return 19 | } 20 | 21 | func (b *BLE) encryptPayload(msg *api.Message) (err error) { 22 | msg.Payload, err = b.Keyring.EncryptOFB(msg.Payload) 23 | return 24 | } 25 | 26 | func (b *BLE) decryptPayload(msg *api.Message) (err error) { 27 | msg.Payload, err = b.Keyring.DecryptOFB(msg.Payload) 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/fixup.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "log" 11 | 12 | "github.com/usbarmory/armory-drive/assets" 13 | ) 14 | 15 | func fixupSRKHash(buf []byte, srk []byte) []byte { 16 | dummySRKHash := assets.DummySRKHash() 17 | 18 | if !bytes.Contains(buf, dummySRKHash) { 19 | log.Fatal("could not locate dummy SRK hash") 20 | } 21 | 22 | buf = bytes.ReplaceAll(buf, dummySRKHash, srk) 23 | 24 | if bytes.Contains(buf, dummySRKHash) || !bytes.Contains(buf, srk) { 25 | log.Fatal("could not set SRK hash") 26 | } 27 | 28 | return buf 29 | } 30 | 31 | func clearFRPublicKey(buf []byte, key []byte) []byte { 32 | if !bytes.Contains(buf, key) { 33 | log.Fatal("could not locate OTA public key") 34 | } 35 | 36 | buf = bytes.ReplaceAll(buf, key, make([]byte, len(key))) 37 | 38 | if bytes.Contains(buf, key) { 39 | log.Fatal("could not clear OTA public key") 40 | } 41 | 42 | return buf 43 | } 44 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/usbarmory/armory-drive 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/google/go-github/v34 v34.0.0 7 | github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff 8 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 9 | github.com/transparency-dev/formats v0.0.0-20250210090712-477e999072fe 10 | github.com/transparency-dev/merkle v0.0.2 11 | github.com/transparency-dev/serverless-log v0.0.0-20250211100151-ced5625f6e2e 12 | github.com/usbarmory/armory-boot v0.0.0-20250207173330-3cfbfb527102 13 | github.com/usbarmory/armory-drive-log v0.0.0-20231011074148-e10fca283f67 14 | github.com/usbarmory/crucible v0.0.0-20250123114515-fa91eabf75f0 15 | github.com/usbarmory/hid v0.0.0-20210318233634-85ced88a1ffe 16 | github.com/usbarmory/tamago v0.0.0-20250212123402-5facf762488d 17 | golang.org/x/crypto v0.33.0 18 | golang.org/x/mod v0.23.0 19 | golang.org/x/oauth2 v0.26.0 20 | golang.org/x/sync v0.11.0 21 | google.golang.org/protobuf v1.36.5 22 | ) 23 | 24 | require ( 25 | github.com/ghodss/yaml v1.0.0 // indirect 26 | github.com/google/go-querystring v1.0.0 // indirect 27 | github.com/smallstep/pkcs7 v0.0.0-20240911091500-b1cae6277023 // indirect 28 | gopkg.in/yaml.v2 v2.4.0 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /assets/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package assets 7 | 8 | import ( 9 | "crypto/sha256" 10 | ) 11 | 12 | // SRKHash represents the Secure Boot SRK fuse table, this value is the output 13 | // of DummySRKHash(). 14 | var SRKHash = []byte{ 15 | 0x63, 0x0d, 0xcd, 0x29, 0x66, 0xc4, 0x33, 0x66, 0x91, 0x12, 0x54, 0x48, 0xbb, 0xb2, 0x5b, 0x4f, 16 | 0xf4, 0x12, 0xa4, 0x9c, 0x73, 0x2d, 0xb2, 0xc8, 0xab, 0xc1, 0xb8, 0x58, 0x1b, 0xd7, 0x10, 0xdd, 17 | } 18 | 19 | // Revision represents the firmware version. 20 | var Revision string 21 | 22 | // DefaultLogOrigin contains the default Firmware Transparency log origin name. 23 | const DefaultLogOrigin = "Armory Drive Prod 2" 24 | 25 | // DummySRKHash generates a known placeholder for the SRK hash to allow its 26 | // identification and replacement within the binary, by `armory-drive-install`, 27 | // with OEM or user secure boot key information. 28 | func DummySRKHash() []byte { 29 | var dummySRK []byte 30 | 31 | for i := 0; i < sha256.Size; i++ { 32 | dummySRK = append(dummySRK, byte(i)) 33 | } 34 | 35 | dummySRKHash := sha256.Sum256(dummySRK) 36 | 37 | return dummySRKHash[:] 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) The armory-drive authors. All Rights Reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Reversec Consulting AB nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /internal/ble/pairing.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ble 7 | 8 | import ( 9 | "encoding/binary" 10 | 11 | "github.com/usbarmory/armory-drive/api" 12 | "github.com/usbarmory/armory-drive/internal/crypto" 13 | 14 | "github.com/skip2/go-qrcode" 15 | ) 16 | 17 | const pairingCodeSize = 117 18 | 19 | func (b *BLE) PairingMode() (code []byte, err error) { 20 | // Generate a new UA longterm key, it will be saved only on successful 21 | // pairings. 22 | if err = b.Keyring.NewLongtermKey(); err != nil { 23 | return 24 | } 25 | 26 | key, err := b.Keyring.Export(crypto.UA_LONGTERM_KEY, false) 27 | 28 | if err != nil { 29 | return 30 | } 31 | 32 | b.pairingMode = true 33 | b.pairingNonce = binary.BigEndian.Uint64(crypto.Rand(8)) 34 | 35 | pb := &api.PairingQRCode{ 36 | BLEName: b.name, 37 | Nonce: b.pairingNonce, 38 | PubKey: key, 39 | } 40 | 41 | if err = b.signPairingCode(pb); err != nil { 42 | return 43 | } 44 | 45 | qr, err := qrcode.New(string(pb.Bytes()), qrcode.Medium) 46 | 47 | if err != nil { 48 | return 49 | } 50 | 51 | return qr.PNG(pairingCodeSize) 52 | } 53 | 54 | func (b *BLE) signPairingCode(qr *api.PairingQRCode) (err error) { 55 | var data []byte 56 | 57 | nonce := make([]byte, 8) 58 | binary.BigEndian.PutUint64(nonce, qr.Nonce) 59 | 60 | data = append(data, []byte(qr.BLEName)...) 61 | data = append(data, nonce...) 62 | data = append(data, qr.PubKey...) 63 | 64 | qr.Signature, err = b.Keyring.SignECDSA(data, false) 65 | 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /internal/crypto/dcp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package crypto 7 | 8 | import ( 9 | "crypto/aes" 10 | "crypto/cipher" 11 | 12 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 13 | ) 14 | 15 | type dcpCipher struct { 16 | keyIndex int 17 | } 18 | 19 | // NewDCPKeyRAMCipher creates and returns a new cipher.Block. The keyIndex 20 | // argument represents a key RAM slot, set with either dcp.DeriveKey() or 21 | // dcp.SetKey(), for hardware accelerated AES-128 encryption. 22 | func newDCPKeyRAMCipher(keyIndex int) (c cipher.Block, err error) { 23 | c = &dcpCipher{ 24 | keyIndex: keyIndex, 25 | } 26 | 27 | return 28 | } 29 | 30 | // NewDCPCipher creates and returns a new cipher.Block. The key argument should 31 | // be a 16 bytes AES key for hardware accelerated AES-128. 32 | // 33 | // The passed key is placed in DCP RAM slot 0 for use. 34 | func newDCPCipher(key []byte) (c cipher.Block, err error) { 35 | c = &dcpCipher{ 36 | keyIndex: BLOCK_KEY, 37 | } 38 | 39 | return c, imx6ul.DCP.SetKey(BLOCK_KEY, key) 40 | } 41 | 42 | // BlockSize returns the AES block size in bytes. 43 | func (c *dcpCipher) BlockSize() int { 44 | return aes.BlockSize 45 | } 46 | 47 | // Encrypt performs in-place buffer encryption using AES-128-CBC. 48 | func (c *dcpCipher) Encrypt(_ []byte, buf []byte) { 49 | imx6ul.DCP.Encrypt(buf, c.keyIndex, zero) 50 | } 51 | 52 | // Decrypt performs in-place buffer decryption using AES-128-CBC. 53 | func (c *dcpCipher) Decrypt(_ []byte, buf []byte) { 54 | imx6ul.DCP.Decrypt(buf, c.keyIndex, zero) 55 | } 56 | -------------------------------------------------------------------------------- /internal/ota/archive.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ota 7 | 8 | import ( 9 | "archive/zip" 10 | "bytes" 11 | "fmt" 12 | "io" 13 | ) 14 | 15 | const ( 16 | imxPath = "armory-drive.imx" 17 | csfPath = "armory-drive.csf" 18 | logPath = "armory-drive.log" 19 | ) 20 | 21 | func open(reader *zip.Reader, p string) (buf []byte, err error) { 22 | f, err := reader.Open(p) 23 | 24 | if err != nil { 25 | return 26 | } 27 | defer f.Close() 28 | 29 | return io.ReadAll(f) 30 | } 31 | 32 | func extract(buf []byte) (imx []byte, csf []byte, proof []byte, err error) { 33 | r := bytes.NewReader(buf) 34 | 35 | reader, err := zip.NewReader(r, r.Size()) 36 | 37 | if err != nil { 38 | return 39 | } 40 | 41 | if imx, err = open(reader, imxPath); err != nil { 42 | err = fmt.Errorf("could not open %s, %v", imxPath, err) 43 | return 44 | } 45 | 46 | if len(imx) == 0 { 47 | err = fmt.Errorf("could not open %s, empty file", imxPath) 48 | return 49 | } 50 | 51 | if csf, err = open(reader, csfPath); err != nil { 52 | err = fmt.Errorf("could not open %s, %v", csfPath, err) 53 | return 54 | } 55 | 56 | if len(csf) == 0 { 57 | err = fmt.Errorf("could not open %s, empty file", csfPath) 58 | return 59 | } 60 | 61 | if proofEnabled() { 62 | if proof, err = open(reader, logPath); err != nil { 63 | err = fmt.Errorf("could not open %s, %v", logPath, err) 64 | return 65 | } 66 | 67 | if len(proof) == 0 { 68 | err = fmt.Errorf("could not open %s, empty file", logPath) 69 | return 70 | } 71 | } 72 | 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "io" 10 | "log" 11 | "os" 12 | "time" 13 | _ "unsafe" 14 | 15 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 16 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 17 | ) 18 | 19 | // The USB armory Mk II serial console is exposed through a debug accessory 20 | // which requires an I2C command to the receptacle port controller to be 21 | // accessed. While such an explicit initialization is required, a malicious 22 | // party could inject I2C commands externally, by tampering with the board bus 23 | // and therefore forcibly enabling serial logging. 24 | // 25 | // This firmware does not log any sensitive information to the serial console, 26 | // however it is desirable to silence any potential stack trace or runtime 27 | // errors to avoid unwanted information leaks. To this end the following steps 28 | // are required to disable the serial console securely. 29 | // 30 | // The TamaGo board support for the USB armory Mk II enables the serial console 31 | // (UART2) at runtime initialization, which therefore invokes 32 | // imx6ul.UART2.Init() before init(). 33 | // 34 | // To this end the runtime printk function, responsible for all console logging 35 | // operations (i.e. stdout/stderr), is overridden with a NOP. Secondarily UART2 36 | // is disabled at the first opportunity (init()). 37 | 38 | // by default any serial output is supressed before UART2 disabling 39 | var serialTx = func(c byte) {} 40 | 41 | func init() { 42 | if imx6ul.SNVS.Available() { 43 | // disable console 44 | usbarmory.UART2.Disable() 45 | 46 | // silence logging 47 | log.SetOutput(io.Discard) 48 | return 49 | } 50 | 51 | log.SetOutput(os.Stdout) 52 | 53 | serialTx = func(c byte) { 54 | usbarmory.UART2.Tx(c) 55 | } 56 | 57 | debugConsole, _ := usbarmory.DetectDebugAccessory(250 * time.Millisecond) 58 | <-debugConsole 59 | } 60 | 61 | //go:linkname printk runtime.printk 62 | func printk(c byte) { 63 | serialTx(c) 64 | } 65 | -------------------------------------------------------------------------------- /internal/ums/ums.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ums 7 | 8 | import ( 9 | "github.com/usbarmory/armory-drive/api" 10 | "github.com/usbarmory/armory-drive/internal/crypto" 11 | 12 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 13 | "github.com/usbarmory/tamago/soc/nxp/usdhc" 14 | ) 15 | 16 | const ( 17 | // exactly 8 bytes required, historical value kept 18 | VendorID = "F-Secure" 19 | // exactly 16 bytes required 20 | ProductID = "USB armory Mk II" 21 | // exactly 4 bytes required 22 | ProductRevision = "1.00" 23 | ) 24 | 25 | type Card interface { 26 | Detect() error 27 | Info() usdhc.CardInfo 28 | ReadBlocks(int, []byte) error 29 | WriteBlocks(int, []byte) error 30 | } 31 | 32 | // Drive represents an encrypted drive instance. 33 | type Drive struct { 34 | // Cipher controls whether FDE should be applied 35 | Cipher bool 36 | 37 | // Keyring instance 38 | Keyring *crypto.Keyring 39 | 40 | // Ready represents the logical device status 41 | Ready bool 42 | 43 | // PairingComplete signals pairing completion 44 | PairingComplete chan bool 45 | 46 | // Mult is the block multiplier 47 | Mult int 48 | 49 | // Card represents the underlying storage instance 50 | card Card 51 | 52 | // send is the queue for IN device responses 53 | send chan []byte 54 | 55 | // free is the queue for IN device DMA buffers for later release 56 | free chan uint 57 | 58 | // dataPending is the buffer for write commands which spawn across 59 | // multiple USB transfers 60 | dataPending *writeOp 61 | } 62 | 63 | func (d *Drive) Init(card Card) (err error) { 64 | if err = card.Detect(); err != nil { 65 | return 66 | } 67 | 68 | d.card = card 69 | d.PairingComplete = make(chan bool) 70 | d.send = make(chan []byte, 2) 71 | d.free = make(chan uint, 1) 72 | 73 | return 74 | } 75 | 76 | func (d *Drive) Capacity() uint64 { 77 | info := d.card.Info() 78 | return uint64(info.Blocks) * uint64(info.BlockSize) 79 | } 80 | 81 | func (d *Drive) Lock() (err error) { 82 | // invalidate the drive 83 | d.Ready = false 84 | 85 | // clear FDE key 86 | if err = d.Keyring.SetCipher(api.Cipher_NONE, nil); err != nil { 87 | return 88 | } 89 | 90 | usbarmory.LED("white", false) 91 | 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /internal/ota/ft.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ota 7 | 8 | import ( 9 | "bytes" 10 | "encoding/json" 11 | "errors" 12 | 13 | "github.com/usbarmory/armory-drive/assets" 14 | "github.com/usbarmory/armory-drive-log/api" 15 | "github.com/usbarmory/armory-drive-log/api/verify" 16 | 17 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 18 | 19 | "golang.org/x/mod/sumdb/note" 20 | ) 21 | 22 | // proofEnabled returns whether OTA updates should be verified or not. The 23 | // verification happens only on firmware images compiled with the necessary key 24 | // material and when no fixup is present. 25 | // 26 | // Key material is cleared on unsigned/test images only while a zero fixup is 27 | // performed only on user signed images as they cannot be authenticated with 28 | // OTA keys. 29 | func proofEnabled() bool { 30 | return !DisableAuth && !bytes.Equal(FRPublicKey, make([]byte, len(FRPublicKey))) 31 | } 32 | 33 | func verifyProof(imx []byte, csf []byte, proof []byte, oldProof *api.ProofBundle) (pb *api.ProofBundle, err error) { 34 | if len(proof) == 0 { 35 | return nil, errors.New("missing proof") 36 | } 37 | 38 | pb = &api.ProofBundle{} 39 | 40 | if err = json.Unmarshal(proof, pb); err != nil { 41 | return 42 | } 43 | 44 | logSigV, err := note.NewVerifier(string(LogPublicKey)) 45 | 46 | if err != nil { 47 | return 48 | } 49 | 50 | frSigV, err := note.NewVerifier(string(FRPublicKey)) 51 | 52 | if err != nil { 53 | return 54 | } 55 | 56 | var oldCP api.Checkpoint 57 | 58 | if oldProof != nil { 59 | verifiers := note.VerifierList(logSigV) 60 | 61 | if n, _ := note.Open(oldProof.NewCheckpoint, verifiers); n != nil { 62 | if err = oldCP.Unmarshal([]byte(n.Text)); err != nil { 63 | return 64 | } 65 | } 66 | } 67 | 68 | imxHash, err := imx6ul.DCP.Sum256(imx) 69 | 70 | if err != nil { 71 | return 72 | } 73 | 74 | csfHash, err := imx6ul.DCP.Sum256(csf) 75 | 76 | if err != nil { 77 | return 78 | } 79 | 80 | hashes := map[string][]byte{ 81 | imxPath: imxHash[:], 82 | csfPath: csfHash[:], 83 | } 84 | 85 | if err = verify.Bundle(*pb, oldCP, logSigV, frSigV, hashes, assets.DefaultLogOrigin); err != nil { 86 | return 87 | } 88 | 89 | // leaf hashes are not needed so we can save space 90 | pb.LeafHashes = nil 91 | 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /internal/crypto/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package crypto 7 | 8 | import ( 9 | "bytes" 10 | "encoding/gob" 11 | 12 | logapi "github.com/usbarmory/armory-drive-log/api" 13 | "github.com/usbarmory/armory-drive/api" 14 | 15 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 16 | ) 17 | 18 | const ( 19 | MMC_CONF_BLOCK = 2097152 20 | CONF_BLOCKS_V1 = 2 21 | CONF_BLOCKS_V2 = 2048 22 | ) 23 | 24 | type PersistentConfiguration struct { 25 | // serialized long term BLE peer authentication keys 26 | ArmoryLongterm []byte 27 | MobileLongterm []byte 28 | 29 | // BLE API Configuration 30 | Settings *api.Configuration 31 | 32 | // Transparency Log Checkpoint 33 | ProofBundle *logapi.ProofBundle 34 | } 35 | 36 | func (k *Keyring) reset() (err error) { 37 | var armoryLongterm []byte 38 | 39 | if k.ArmoryLongterm == nil { 40 | if err = k.NewLongtermKey(); err != nil { 41 | return 42 | } 43 | } 44 | 45 | if armoryLongterm, err = k.Export(UA_LONGTERM_KEY, true); err != nil { 46 | return 47 | } 48 | 49 | k.Conf = &PersistentConfiguration{ 50 | ArmoryLongterm: armoryLongterm, 51 | Settings: &api.Configuration{ 52 | Cipher: api.Cipher_AES128_CBC_PLAIN, 53 | }, 54 | } 55 | 56 | return k.Save() 57 | } 58 | 59 | func (k *Keyring) loadAt(lba int, blocks int) (err error) { 60 | blockSize := usbarmory.MMC.Info().BlockSize 61 | snvs := make([]byte, blocks*blockSize) 62 | 63 | if err = usbarmory.MMC.ReadBlocks(lba, snvs); err != nil { 64 | return 65 | } 66 | 67 | buf, err := k.decryptSNVS(snvs) 68 | 69 | if err != nil { 70 | return 71 | } 72 | 73 | k.Conf = &PersistentConfiguration{} 74 | err = gob.NewDecoder(bytes.NewBuffer(buf)).Decode(k.Conf) 75 | 76 | return 77 | } 78 | 79 | func (k *Keyring) Load() (err error) { 80 | if err = k.loadAt(MMC_CONF_BLOCK, CONF_BLOCKS_V2); err == nil { 81 | return 82 | } 83 | 84 | return k.loadAt(MMC_CONF_BLOCK, CONF_BLOCKS_V1) 85 | } 86 | 87 | func (k *Keyring) Save() (err error) { 88 | blockSize := usbarmory.MMC.Info().BlockSize 89 | 90 | buf := new(bytes.Buffer) 91 | 92 | if err = gob.NewEncoder(buf).Encode(k.Conf); err != nil { 93 | return 94 | } 95 | 96 | snvs, err := k.encryptSNVS(buf.Bytes(), CONF_BLOCKS_V2*blockSize) 97 | 98 | if err != nil { 99 | return 100 | } 101 | 102 | return usbarmory.MMC.WriteBlocks(MMC_CONF_BLOCK, snvs) 103 | } 104 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/ft.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "context" 10 | "crypto/sha256" 11 | "encoding/json" 12 | "errors" 13 | "os" 14 | "path" 15 | 16 | "github.com/usbarmory/armory-drive-log/api" 17 | "github.com/usbarmory/armory-drive-log/api/verify" 18 | 19 | "github.com/google/go-github/v34/github" 20 | "github.com/transparency-dev/formats/log" 21 | "github.com/transparency-dev/merkle/rfc6962" 22 | "github.com/transparency-dev/serverless-log/client" 23 | "golang.org/x/mod/sumdb/note" 24 | ) 25 | 26 | func verifyRelease(release *github.RepositoryRelease, a *releaseAssets) (err error) { 27 | var oldCP *log.Checkpoint 28 | var checkpoints []log.Checkpoint 29 | 30 | ctx := context.Background() 31 | 32 | if len(a.logPub) == 0 { 33 | return errors.New("FT log public key not found, could not verify release") 34 | } 35 | 36 | logSigV, err := note.NewVerifier(string(a.logPub)) 37 | 38 | if err != nil { 39 | return 40 | } 41 | 42 | newCP, newCPRaw, _, err := client.FetchCheckpoint(ctx, logFetcher, logSigV, conf.logOrigin) 43 | 44 | if err != nil { 45 | return 46 | } 47 | 48 | if cacheDir, e := os.UserCacheDir(); e == nil { 49 | p := path.Join(cacheDir, checkpointCachePath) 50 | 51 | buf, err := os.ReadFile(p) 52 | 53 | if err == nil { 54 | oldCP = &log.Checkpoint{} 55 | oldCP.Unmarshal(buf) 56 | } 57 | 58 | defer func() { 59 | if err != nil && len(newCPRaw) > 0 { 60 | _ = os.WriteFile(p, newCPRaw, 0600) 61 | } 62 | }() 63 | } 64 | 65 | if oldCP != nil { 66 | checkpoints = append(checkpoints, *oldCP) 67 | } 68 | 69 | if len(checkpoints) > 0 { 70 | checkpoints = append(checkpoints, *newCP) 71 | 72 | if err = client.CheckConsistency(ctx, rfc6962.DefaultHasher, logFetcher, checkpoints); err != nil { 73 | return 74 | } 75 | } 76 | 77 | return verifyProof(a) 78 | } 79 | 80 | func verifyProof(a *releaseAssets) (err error) { 81 | if len(a.log) == 0 { 82 | return errors.New("missing proof") 83 | } 84 | 85 | pb := &api.ProofBundle{} 86 | 87 | if err = json.Unmarshal(a.log, pb); err != nil { 88 | return 89 | } 90 | 91 | logSigV, err := note.NewVerifier(string(a.logPub)) 92 | 93 | if err != nil { 94 | return 95 | } 96 | 97 | frSigV, err := note.NewVerifier(string(a.frPub)) 98 | 99 | if err != nil { 100 | return 101 | } 102 | 103 | imxHash := sha256.Sum256(a.imx) 104 | csfHash := sha256.Sum256(a.csf) 105 | 106 | hashes := map[string][]byte{ 107 | imxPath: imxHash[:], 108 | csfPath: csfHash[:], 109 | } 110 | 111 | if err = verify.Bundle(*pb, api.Checkpoint{}, logSigV, frSigV, hashes, conf.logOrigin); err != nil { 112 | return 113 | } 114 | 115 | // leaf hashes are not needed so we can save space 116 | pb.LeafHashes = nil 117 | 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /internal/ota/ota.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ota 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "io" 12 | "log" 13 | "os" 14 | "runtime" 15 | "time" 16 | 17 | "github.com/usbarmory/armory-drive-log/api" 18 | "github.com/usbarmory/armory-drive/internal/crypto" 19 | 20 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 21 | 22 | "github.com/mitchellh/go-fs" 23 | "github.com/mitchellh/go-fs/fat" 24 | ) 25 | 26 | const updatePath = "UPDATE.ZIP" 27 | 28 | func Check(buf []byte, path string, off int, keyring *crypto.Keyring) { 29 | img, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0600) 30 | 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | if _, err = img.Write(buf[off:]); err != nil { 36 | panic(err) 37 | } 38 | 39 | img.Seek(0, 0) 40 | 41 | dev, err := fs.NewFileDisk(img) 42 | 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | f, err := fat.New(dev) 48 | 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | root, err := f.RootDir() 54 | 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | for _, entry := range root.Entries() { 60 | if entry.Name() == updatePath { 61 | update(entry, keyring) 62 | return 63 | } 64 | } 65 | } 66 | 67 | func update(entry fs.DirectoryEntry, keyring *crypto.Keyring) { 68 | var err error 69 | var exit = make(chan bool) 70 | 71 | defer func() { 72 | exit <- true 73 | }() 74 | 75 | file, err := entry.File() 76 | 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | go func() { 82 | var on bool 83 | 84 | for { 85 | select { 86 | case <-exit: 87 | usbarmory.LED("white", false) 88 | 89 | if err != nil { 90 | log.Printf("firmware update error, %v", err) 91 | usbarmory.LED("blue", true) 92 | } 93 | 94 | return 95 | default: 96 | } 97 | 98 | on = !on 99 | usbarmory.LED("white", on) 100 | 101 | runtime.Gosched() 102 | time.Sleep(100 * time.Millisecond) 103 | } 104 | }() 105 | 106 | buf, err := io.ReadAll(file) 107 | 108 | if err != nil { 109 | err = errors.New("could not read update") 110 | return 111 | } 112 | 113 | imx, csf, proof, err := extract(buf) 114 | 115 | if err != nil { 116 | err = fmt.Errorf("could not extract archive, %v", err) 117 | return 118 | } 119 | 120 | if len(proof) > 0 { 121 | var pb *api.ProofBundle 122 | 123 | // firmware authentication 124 | pb, err = verifyProof(imx, csf, proof, keyring.Conf.ProofBundle) 125 | 126 | if err != nil { 127 | err = fmt.Errorf("could not verify proof, %v", err) 128 | return 129 | } 130 | 131 | keyring.Conf.ProofBundle = pb 132 | keyring.Save() 133 | } 134 | 135 | // append HAB signature 136 | imx = append(imx, csf...) 137 | 138 | if err = usbarmory.MMC.WriteBlocks(2, imx); err != nil { 139 | err = fmt.Errorf("could not write to MMC, %v", err) 140 | return 141 | } 142 | 143 | log.Println("firmware update complete") 144 | 145 | usbarmory.LED("blue", false) 146 | usbarmory.LED("white", false) 147 | } 148 | -------------------------------------------------------------------------------- /internal/hab/hab.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code uuis governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package hab 7 | 8 | import ( 9 | "bytes" 10 | "crypto/sha256" 11 | "fmt" 12 | "log" 13 | 14 | "github.com/usbarmory/armory-drive/assets" 15 | 16 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 17 | 18 | "github.com/usbarmory/crucible/otp" 19 | "github.com/usbarmory/crucible/util" 20 | ) 21 | 22 | // Init activates secure boot by following the procedure described at: 23 | // https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)#activating-hab 24 | // 25 | // IMPORTANT: enabling secure boot functionality on the USB armory SoC, unlike 26 | // similar features on modern PCs, is an irreversible action that permanently 27 | // fuses verification keys hashes on the device. This means that any errors in 28 | // the process or loss of the signing PKI will result in a bricked device 29 | // incapable of executing unsigned code. This is a security feature, not a bug. 30 | func Init() { 31 | switch { 32 | case imx6ul.SNVS.Available(): 33 | return 34 | case len(assets.SRKHash) != sha256.Size: 35 | return 36 | case bytes.Equal(assets.SRKHash, make([]byte, len(assets.SRKHash))): 37 | return 38 | case bytes.Equal(assets.SRKHash, assets.DummySRKHash()): 39 | return 40 | default: 41 | // Enable High Assurance Boot (i.e. secure boot) 42 | hab(assets.SRKHash) 43 | } 44 | } 45 | 46 | func fuse(name string, bank int, word int, off int, size int, val []byte) { 47 | log.Printf("fusing %s bank:%d word:%d off:%d size:%d val:%x", name, bank, word, off, size, val) 48 | 49 | if err := otp.BlowOCOTP(bank, word, off, size, val); err != nil { 50 | panic(err) 51 | } 52 | 53 | if res, err := otp.ReadOCOTP(bank, word, off, size); err != nil || !bytes.Equal(val, res) { 54 | panic(fmt.Sprintf("readback error for %s, val:%x res:%x err:%v\n", name, val, res, err)) 55 | } 56 | } 57 | 58 | func hab(srk []byte) { 59 | if len(srk) != sha256.Size { 60 | panic("fatal error, invalid SRK hash") 61 | } 62 | 63 | // fuse HAB public keys hash 64 | fuse("SRK_HASH", 3, 0, 0, 256, util.SwitchEndianness(srk)) 65 | 66 | // lock HAB public keys hash 67 | fuse("SRK_LOCK", 0, 0, 14, 1, []byte{1}) 68 | 69 | // set device in Closed Configuration (IMX6ULRM Table 8-2, p245) 70 | fuse("SEC_CONFIG", 0, 6, 0, 2, []byte{0b11}) 71 | 72 | // disable NXP reserved mode (IMX6ULRM 8.2.6, p244) 73 | fuse("DIR_BT_DIS", 0, 6, 3, 1, []byte{1}) 74 | 75 | // Disable debugging features (IMX6ULRM Table 5-9, p216) 76 | 77 | // disable Secure JTAG controller 78 | fuse("SJC_DISABLE", 0, 6, 20, 1, []byte{1}) 79 | 80 | // disable JTAG debug mode 81 | fuse("JTAG_SMODE", 0, 6, 22, 2, []byte{0b11}) 82 | 83 | // disable HAB ability to enable JTAG 84 | fuse("JTAG_HEO", 0, 6, 27, 1, []byte{1}) 85 | 86 | // disable tracing 87 | fuse("KTE", 0, 6, 26, 1, []byte{1}) 88 | 89 | // Further reduce the attack surface 90 | 91 | // disable Serial Download Protocol (SDP) READ_REGISTER command (IMX6ULRM 8.9.3, p310) 92 | fuse("SDP_READ_DISABLE", 0, 6, 18, 1, []byte{1}) 93 | 94 | // disable SDP over UART (IMX6ULRM 8.9, p305) 95 | fuse("UART_SERIAL_DOWNLOAD_DISABLE", 0, 7, 4, 1, []byte{1}) 96 | } 97 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/hab.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "crypto/rsa" 10 | "crypto/x509" 11 | "encoding/pem" 12 | "errors" 13 | "log" 14 | "os" 15 | 16 | "github.com/usbarmory/crucible/hab" 17 | ) 18 | 19 | func checkHABArguments() { 20 | if len(conf.srkKey) > 0 && len(conf.srkCrt) > 0 && len(conf.table) > 0 && conf.index > 0 { 21 | return 22 | } 23 | 24 | log.Fatal(secureBootHelp) 25 | } 26 | 27 | func genCerts() (CSFSigner, IMGSigner *rsa.PrivateKey, CSFCert, IMGCert *x509.Certificate, err error) { 28 | var signingKey *rsa.PrivateKey 29 | 30 | SRKKeyPEMBlock, err := os.ReadFile(conf.srkKey) 31 | 32 | if err != nil { 33 | return 34 | } 35 | 36 | SRKCertPEMBlock, err := os.ReadFile(conf.srkCrt) 37 | 38 | if err != nil { 39 | return 40 | } 41 | 42 | caKey, _ := pem.Decode(SRKKeyPEMBlock) 43 | 44 | if caKey == nil { 45 | err = errors.New("failed to parse SRK key PEM") 46 | return 47 | } 48 | 49 | caCert, _ := pem.Decode(SRKCertPEMBlock) 50 | 51 | if caCert == nil { 52 | err = errors.New("failed to parse SRK certificate PEM") 53 | return 54 | } 55 | 56 | ca, err := x509.ParseCertificate(caCert.Bytes) 57 | 58 | if err != nil { 59 | return 60 | } 61 | 62 | caPriv, err := x509.ParsePKCS8PrivateKey(caKey.Bytes) 63 | 64 | if err != nil { 65 | return 66 | } 67 | 68 | switch k := caPriv.(type) { 69 | case *rsa.PrivateKey: 70 | signingKey = k 71 | default: 72 | err = errors.New("failed to parse SRK key") 73 | return 74 | } 75 | 76 | log.Printf("generating and signing CSF keypair") 77 | if CSFSigner, CSFCert, _, _, err = hab.NewCertificate("CSF", hab.DEFAULT_KEY_LENGTH, hab.DEFAULT_KEY_EXPIRY, ca, signingKey); err != nil { 78 | return 79 | } 80 | 81 | log.Printf("generating and signing IMG keypair") 82 | if IMGSigner, IMGCert, _, _, err = hab.NewCertificate("IMG", hab.DEFAULT_KEY_LENGTH, hab.DEFAULT_KEY_EXPIRY, ca, signingKey); err != nil { 83 | return 84 | } 85 | 86 | return 87 | } 88 | 89 | func sign(assets *releaseAssets) (err error) { 90 | opts := hab.SignOptions{ 91 | Index: conf.index, 92 | DCD: hab.DCD_OFFSET, 93 | Engine: hab.HAB_ENG_SW, 94 | } 95 | 96 | if opts.Table, err = os.ReadFile(conf.table); err != nil { 97 | return 98 | } 99 | 100 | log.Printf("generating ephemeral CSF/IMG certificates") 101 | if opts.CSFSigner, opts.IMGSigner, opts.CSFCert, opts.IMGCert, err = genCerts(); err != nil { 102 | return 103 | } 104 | 105 | // On user signed releases we disable OTA authentication to 106 | // simplify key management. This has no security impact as the 107 | // executable is authenticated at boot using secure boot. 108 | assets.log = nil 109 | assets.imx = clearFRPublicKey(assets.imx, assets.frPub) 110 | 111 | log.Printf("generating HAB signatures") 112 | if assets.csf, err = hab.Sign(assets.imx, opts); err != nil { 113 | return 114 | } 115 | 116 | opts.SDP = true 117 | 118 | log.Printf("generating HAB recovery signatures") 119 | if assets.sdp, err = hab.Sign(assets.imx, opts); err != nil { 120 | return 121 | } 122 | 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /internal/ble/ble.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ble 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "regexp" 12 | "runtime" 13 | "time" 14 | 15 | "github.com/usbarmory/armory-drive/internal/crypto" 16 | "github.com/usbarmory/armory-drive/internal/ums" 17 | 18 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 19 | ) 20 | 21 | var BLEStartupPattern = regexp.MustCompile(`(\+STARTUP)`) 22 | var BLENamePattern = regexp.MustCompile(`\+UBTLN:"([^"]+)"`) 23 | 24 | type eventHandler func([]byte) []byte 25 | 26 | type BLE struct { 27 | Drive *ums.Drive 28 | Keyring *crypto.Keyring 29 | 30 | name string 31 | session *Session 32 | 33 | pairingMode bool 34 | pairingNonce uint64 35 | 36 | anna *usbarmory.ANNA 37 | data []byte 38 | } 39 | 40 | func (b *BLE) txPacket(buf []byte) { 41 | // detect USB armory Mk II β errata fix 42 | if b.anna.UART.Flow { 43 | b.anna.UART.Write(buf) 44 | return 45 | } 46 | 47 | for i := 0; i < len(buf); i++ { 48 | for !b.anna.RTS() { 49 | } 50 | 51 | b.anna.UART.Tx(buf[i]) 52 | } 53 | } 54 | 55 | func (b *BLE) rxPackets() { 56 | var pkt []byte 57 | var length uint16 58 | 59 | next := true 60 | 61 | for { 62 | // detect USB armory Mk II β errata fix 63 | if b.anna.UART.Flow { 64 | buf := make([]byte, 1024) 65 | n, _ := b.anna.UART.Read(buf) 66 | 67 | if n != 0 { 68 | pkt = append(pkt, buf[:n]...) 69 | } 70 | } else { 71 | b.anna.CTS(true) 72 | c, ok := b.anna.UART.Rx() 73 | b.anna.CTS(false) 74 | 75 | if ok { 76 | pkt = append(pkt, c) 77 | } 78 | } 79 | 80 | // look for the beginning of packet 81 | if next { 82 | i := bytes.IndexByte(pkt, EDM_START) 83 | 84 | if i < 0 { 85 | pkt = []byte{} 86 | 87 | runtime.Gosched() 88 | continue 89 | } 90 | 91 | pkt = pkt[i:] 92 | next = false 93 | length = 0 94 | } 95 | 96 | if len(pkt) >= 3 && length == 0 { 97 | length = binary.BigEndian.Uint16(pkt[1:3]) 98 | 99 | if length == 0 || length > PAYLOAD_MAX_LENGTH { 100 | pkt = []byte{} 101 | next = true 102 | } else { 103 | // from payload length to packet length 104 | length += 4 105 | } 106 | } else if length != 0 && len(pkt) >= int(length) { 107 | if pkt[length-1] == EDM_STOP { 108 | b.handleEvent(pkt[3 : length-1]) 109 | } 110 | 111 | pkt = pkt[length:] 112 | next = true 113 | } 114 | } 115 | } 116 | 117 | func (b *BLE) rxATResponse(pattern *regexp.Regexp) (match [][]byte) { 118 | var buf []byte 119 | 120 | for len(match) == 0 { 121 | b.anna.CTS(true) 122 | 123 | c, ok := b.anna.UART.Rx() 124 | 125 | if !ok { 126 | continue 127 | } 128 | 129 | b.anna.CTS(false) 130 | 131 | buf = append(buf, c) 132 | match = pattern.FindSubmatch(buf) 133 | } 134 | 135 | return 136 | } 137 | 138 | func (b *BLE) Init() (err error) { 139 | b.anna = usbarmory.BLE 140 | 141 | if err = b.anna.Init(); err != nil { 142 | return 143 | } 144 | 145 | time.Sleep(usbarmory.RESET_GRACE_TIME) 146 | b.rxATResponse(BLEStartupPattern) 147 | 148 | b.anna.UART.Write([]byte("AT+UBTLN?\r")) 149 | m := b.rxATResponse(BLENamePattern) 150 | 151 | b.name = string(m[1]) 152 | b.session = &Session{} 153 | 154 | // enter data mode 155 | b.anna.UART.Write([]byte("ATO2\r")) 156 | 157 | usbarmory.LED("blue", true) 158 | 159 | go func() { 160 | b.rxPackets() 161 | }() 162 | 163 | return 164 | } 165 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "runtime" 12 | "time" 13 | 14 | "github.com/usbarmory/armory-drive/internal/ble" 15 | "github.com/usbarmory/armory-drive/internal/crypto" 16 | "github.com/usbarmory/armory-drive/internal/hab" 17 | "github.com/usbarmory/armory-drive/internal/ums" 18 | 19 | "github.com/usbarmory/tamago/arm" 20 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 21 | "github.com/usbarmory/tamago/soc/nxp/usb" 22 | 23 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 24 | ) 25 | 26 | func init() { 27 | if err := imx6ul.SetARMFreq(900); err != nil { 28 | panic(fmt.Sprintf("WARNING: error setting ARM frequency: %v\n", err)) 29 | } 30 | 31 | imx6ul.DCP.Init() 32 | imx6ul.DCP.EnableInterrupt() 33 | 34 | log.SetFlags(0) 35 | } 36 | 37 | func startInterruptHandler(port *usb.USB) { 38 | irq := imx6ul.GIC.GetInterrupt(true) 39 | 40 | imx6ul.GIC.EnableInterrupt(port.IRQ, true) 41 | imx6ul.GIC.EnableInterrupt(imx6ul.DCP.IRQ, true) 42 | 43 | isr := func() { 44 | switch irq { 45 | case port.IRQ: 46 | port.ServiceInterrupts() 47 | case imx6ul.DCP.IRQ: 48 | imx6ul.DCP.ServiceInterrupt() 49 | default: 50 | log.Printf("internal error, unexpected IRQ %d", irq) 51 | } 52 | } 53 | 54 | arm.ServiceInterrupts(isr) 55 | } 56 | 57 | func main() { 58 | usbarmory.LED("blue", false) 59 | usbarmory.LED("white", false) 60 | 61 | if err := usbarmory.MMC.Detect(); err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | keyring := &crypto.Keyring{} 66 | 67 | if err := keyring.Init(false); err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | drive := &ums.Drive{ 72 | Cipher: true, 73 | Keyring: keyring, 74 | Mult: ums.BLOCK_SIZE_MULTIPLIER, 75 | } 76 | 77 | ble := &ble.BLE{ 78 | Drive: drive, 79 | Keyring: keyring, 80 | } 81 | ble.Init() 82 | 83 | if drive.Init(usbarmory.SD) != nil { 84 | var code []byte 85 | var err error 86 | 87 | // provision Secure Boot as required 88 | hab.Init() 89 | 90 | // Do not offer pairing code on first time installs (or 91 | // recovery) as that pairing might become invalid at reboot if 92 | // Secure Boot has been just activated, rather offer pairing 93 | // only by firmware booted internally. 94 | if !imx6ul.SDP { 95 | code, err = ble.PairingMode() 96 | 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | } 101 | 102 | drive.Cipher = false 103 | drive.Mult = 1 104 | drive.Ready = true 105 | 106 | drive.Init(ums.Pairing(code, keyring)) 107 | 108 | go pairingFeedback(drive.PairingComplete) 109 | } 110 | 111 | port := imx6ul.USB1 112 | 113 | port.Device = drive.ConfigureUSB() 114 | port.Init() 115 | 116 | // To further reduce the attack surface, start the USB stack only when 117 | // the card is unlocked (or in pairing mode). 118 | for !drive.Ready { 119 | runtime.Gosched() 120 | time.Sleep(10 * time.Millisecond) 121 | } 122 | 123 | port.DeviceMode() 124 | 125 | port.EnableInterrupt(usb.IRQ_URI) // reset 126 | port.EnableInterrupt(usb.IRQ_PCI) // port change detect 127 | port.EnableInterrupt(usb.IRQ_UI) // transfer completion 128 | 129 | startInterruptHandler(port) 130 | } 131 | 132 | func pairingFeedback(done chan bool) { 133 | var on bool 134 | 135 | for { 136 | select { 137 | case <-done: 138 | usbarmory.LED("blue", false) 139 | return 140 | default: 141 | } 142 | 143 | on = !on 144 | usbarmory.LED("blue", on) 145 | 146 | runtime.Gosched() 147 | time.Sleep(1 * time.Second) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /internal/ble/data.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ble 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | ) 12 | 13 | const ( 14 | // Packet (4 bytes + []byte) max: 251 15 | // Data Event | Data Command (3 bytes + []byte) max: 247 16 | // Fragment (2 bytes + []byte) max: 244 17 | // protobuf max: 242 18 | 19 | PAYLOAD_MAX_LENGTH = 247 20 | FRAGMENT_MAX_LENGTH = 244 21 | PROTOBUF_MAX_LENGTH = 242 22 | 23 | EDM_START = 0xAA 24 | EDM_STOP = 0x55 25 | 26 | DATA_EVENT = 0x31 27 | DATA_COMMAND = 0x36 28 | ) 29 | 30 | type Packet struct { 31 | Start uint8 32 | Length uint16 33 | Payload []byte 34 | Stop uint8 35 | } 36 | 37 | func (pkt *Packet) SetDefaults() { 38 | pkt.Start = EDM_START 39 | pkt.Stop = EDM_STOP 40 | } 41 | 42 | func (pkt *Packet) SetPayload(buf []byte) { 43 | pkt.Length = uint16(len(buf)) 44 | pkt.Payload = buf 45 | } 46 | 47 | func (pkt *Packet) Bytes() []byte { 48 | buf := new(bytes.Buffer) 49 | 50 | binary.Write(buf, binary.BigEndian, pkt.Start) 51 | binary.Write(buf, binary.BigEndian, pkt.Length) 52 | buf.Write(pkt.Payload) 53 | binary.Write(buf, binary.BigEndian, pkt.Stop) 54 | 55 | return buf.Bytes() 56 | } 57 | 58 | type Data struct { 59 | Kind uint16 60 | ChannelId uint8 61 | Data []byte 62 | } 63 | 64 | func (cmd *Data) SetDefaults() { 65 | cmd.Kind = DATA_COMMAND 66 | } 67 | 68 | func (cmd *Data) Bytes() []byte { 69 | buf := new(bytes.Buffer) 70 | 71 | binary.Write(buf, binary.BigEndian, cmd.Kind) 72 | binary.Write(buf, binary.BigEndian, cmd.ChannelId) 73 | buf.Write(cmd.Data) 74 | 75 | return buf.Bytes() 76 | } 77 | 78 | type Fragment struct { 79 | Total uint8 80 | Seq uint8 81 | Data []byte 82 | } 83 | 84 | func (frg *Fragment) Parse(data []byte) { 85 | frg.Total = uint8(data[0]) 86 | frg.Seq = uint8(data[1]) 87 | frg.Data = data[2:] 88 | } 89 | 90 | func (frg *Fragment) Bytes() []byte { 91 | buf := new(bytes.Buffer) 92 | 93 | buf.WriteByte(frg.Total) 94 | buf.WriteByte(frg.Seq) 95 | buf.Write(frg.Data) 96 | 97 | return buf.Bytes() 98 | } 99 | 100 | func handleFragment(data []byte) (event []byte) { 101 | fragment := &Fragment{} 102 | fragment.Parse(data) 103 | 104 | if fragment.Total == 1 { 105 | return fragment.Data 106 | } 107 | 108 | if fragment.Seq > 1 && len(data) == 0 || fragment.Seq > fragment.Total { 109 | data = nil 110 | return 111 | } 112 | 113 | if fragment.Seq == 1 { 114 | data = make([]byte, fragment.Total*PROTOBUF_MAX_LENGTH) 115 | } 116 | 117 | data = append(data, fragment.Data...) 118 | 119 | if fragment.Seq == fragment.Total { 120 | event = data 121 | data = nil 122 | } 123 | 124 | return 125 | } 126 | 127 | func (b *BLE) handleEvent(buf []byte) { 128 | var fragments [][]byte 129 | 130 | if len(buf) < 3+2 { 131 | return 132 | } 133 | 134 | // decode Data Event 135 | kind := binary.BigEndian.Uint16(buf[0:2]) 136 | channel := buf[2] 137 | data := buf[3:] 138 | 139 | if kind != DATA_EVENT { 140 | return 141 | } 142 | 143 | event := handleFragment(data) 144 | 145 | if len(event) == 0 { 146 | return 147 | } 148 | 149 | res := b.handleEnvelope(event) 150 | 151 | for i := 0; i < len(res); i += PROTOBUF_MAX_LENGTH { 152 | if i+PROTOBUF_MAX_LENGTH > len(res) { 153 | fragments = append(fragments, res[i:]) 154 | } else { 155 | fragments = append(fragments, res[i:PROTOBUF_MAX_LENGTH]) 156 | } 157 | } 158 | 159 | for i := range fragments { 160 | fragment := &Fragment{ 161 | Total: uint8(len(fragments)), 162 | Seq: uint8(i + 1), 163 | Data: fragments[i], 164 | } 165 | 166 | // prepare Data Command 167 | payload := &Data{} 168 | payload.SetDefaults() 169 | payload.ChannelId = channel 170 | payload.Data = fragment.Bytes() 171 | 172 | // prepare response Packet 173 | pkt := &Packet{} 174 | pkt.SetDefaults() 175 | pkt.SetPayload(payload.Bytes()) 176 | 177 | b.txPacket(pkt.Bytes()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /internal/ums/pairing.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ums 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | "os" 13 | 14 | "github.com/usbarmory/armory-drive/assets" 15 | "github.com/usbarmory/armory-drive/internal/crypto" 16 | 17 | "github.com/usbarmory/tamago/soc/nxp/usdhc" 18 | 19 | "github.com/mitchellh/go-fs" 20 | "github.com/mitchellh/go-fs/fat" 21 | ) 22 | 23 | const readme = ` 24 | Please download the F-Secure Armory Drive application from the iOS App Store 25 | and scan file QR.png 26 | ` 27 | 28 | // pairing disk paths (8.3 format) 29 | const ( 30 | codePath = "QR.PNG" 31 | readmePath = "README.TXT" 32 | versionPath = "VERSION.TXT" 33 | checkpointPath = "LASTCHKP.BIN" 34 | ) 35 | 36 | const ( 37 | blockSize = 512 38 | 39 | pairingDiskPath = "pairing.disk" 40 | pairingDiskOffset = 2048 * blockSize 41 | pairingDiskBlocks = 16800 42 | 43 | bootSignature = 0xaa55 44 | ) 45 | 46 | type MBR struct { 47 | Bootstrap [446]byte 48 | Partitions [4]Partition 49 | BootSignature uint16 50 | } 51 | 52 | type Partition struct { 53 | Status byte 54 | FirstCHS [3]byte 55 | Type byte 56 | LastCHS [3]byte 57 | FirstLBA [4]byte 58 | Sectors [4]byte 59 | } 60 | 61 | func (mbr *MBR) Bytes() []byte { 62 | buf := new(bytes.Buffer) 63 | binary.Write(buf, binary.LittleEndian, mbr) 64 | return buf.Bytes() 65 | } 66 | 67 | type PairingDisk struct { 68 | Data []byte 69 | } 70 | 71 | func (q *PairingDisk) Detect() error { 72 | return nil 73 | } 74 | 75 | func (q *PairingDisk) Info() (info usdhc.CardInfo) { 76 | info.SD = true 77 | info.BlockSize = blockSize 78 | info.Blocks = pairingDiskBlocks 79 | 80 | return 81 | } 82 | 83 | func (q *PairingDisk) ReadBlocks(lba int, buf []byte) (err error) { 84 | start := lba * blockSize 85 | end := start + len(buf) 86 | 87 | if end > len(q.Data) { 88 | return errors.New("read operation exceeds disk size") 89 | } 90 | 91 | copy(buf[:], q.Data[start:end]) 92 | 93 | return 94 | } 95 | 96 | func (q *PairingDisk) WriteBlocks(lba int, buf []byte) (err error) { 97 | start := lba * blockSize 98 | 99 | if start+len(buf) > len(q.Data) { 100 | return errors.New("write operation exceeds disk size") 101 | } 102 | 103 | copy(q.Data[start:], buf) 104 | 105 | return 106 | } 107 | 108 | func Pairing(code []byte, keyring *crypto.Keyring) (card *PairingDisk) { 109 | img, err := os.OpenFile(pairingDiskPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600) 110 | 111 | if err != nil { 112 | panic(err) 113 | } 114 | 115 | if err = img.Truncate(pairingDiskBlocks * blockSize); err != nil { 116 | panic(err) 117 | } 118 | 119 | dev, err := fs.NewFileDisk(img) 120 | 121 | if err != nil { 122 | panic(err) 123 | } 124 | 125 | conf := &fat.SuperFloppyConfig{ 126 | FATType: fat.FAT16, 127 | Label: VendorID, 128 | OEMName: VendorID, 129 | } 130 | 131 | if err = fat.FormatSuperFloppy(dev, conf); err != nil { 132 | panic(err) 133 | } 134 | 135 | f, err := fat.New(dev) 136 | 137 | if err != nil { 138 | panic(err) 139 | } 140 | 141 | root, err := f.RootDir() 142 | 143 | if err != nil { 144 | panic(err) 145 | } 146 | 147 | if len(code) > 0 { 148 | if err = addFile(root, codePath, code); err != nil { 149 | panic(err) 150 | } 151 | 152 | _ = addFile(root, readmePath, []byte(readme)) 153 | } 154 | 155 | _ = addFile(root, versionPath, []byte(assets.Revision)) 156 | 157 | if pb := keyring.Conf.ProofBundle; pb != nil { 158 | if err = addFile(root, checkpointPath, pb.NewCheckpoint); err != nil { 159 | panic(err) 160 | } 161 | } 162 | 163 | img.Close() 164 | 165 | partitionData, err := os.ReadFile(img.Name()) 166 | 167 | if err != nil { 168 | panic(err) 169 | } 170 | 171 | // go-fs implements a partition-less msdos floppy, therefore we must 172 | // move its partition in a partitioned disk. 173 | partition := Partition{ 174 | FirstCHS: [3]byte{0x00, 0x21, 0x18}, 175 | Type: 0x06, 176 | LastCHS: [3]byte{0x01, 0x2a, 0xc7}, 177 | FirstLBA: [4]byte{0x00, 0x08, 0x00, 0x00}, 178 | Sectors: [4]byte{0xa0, 0x39, 0x00, 0x00}, 179 | } 180 | 181 | mbr := &MBR{} 182 | mbr.Partitions[0] = partition 183 | mbr.BootSignature = bootSignature 184 | 185 | data := mbr.Bytes() 186 | data = append(data, make([]byte, pairingDiskOffset-blockSize)...) 187 | data = append(data, partitionData...) 188 | 189 | card = &PairingDisk{ 190 | Data: data, 191 | } 192 | 193 | return 194 | } 195 | 196 | func addFile(root fs.Directory, path string, data []byte) (err error) { 197 | entry, err := root.AddFile(path) 198 | 199 | if err != nil { 200 | return 201 | } 202 | 203 | file, err := entry.File() 204 | 205 | if err != nil { 206 | return 207 | } 208 | 209 | _, err = file.Write(data) 210 | 211 | return 212 | } 213 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/sdp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "log" 12 | "runtime" 13 | "time" 14 | 15 | "github.com/usbarmory/armory-boot/sdp" 16 | 17 | "github.com/usbarmory/hid" 18 | ) 19 | 20 | const ( 21 | // USB vendor ID for all supported devices 22 | FreescaleVendorID = 0x15a2 23 | 24 | // On-Chip RAM (OCRAM/iRAM) address for payload staging 25 | iramOffset = 0x00910000 26 | 27 | // USB command timeout in seconds 28 | timeout = 10 29 | ) 30 | 31 | // This tool should work with all SoCs from the i.MX series capable of USB HID 32 | // based SDP, only tested devices are listed as supported, Pull Requests are 33 | // welcome to expand this set. 34 | var supportedDevices = map[uint16]string{ 35 | 0x007d: "Freescale SemiConductor Inc SE Blank 6UL", 36 | 0x0080: "Freescale SemiConductor Inc SE Blank 6ULL", 37 | } 38 | 39 | // detect compatible devices in SDP mode 40 | func detect() (err error) { 41 | devices, err := hid.Devices() 42 | 43 | if err != nil { 44 | return 45 | } 46 | 47 | for _, d := range devices { 48 | if d.VendorID != FreescaleVendorID { 49 | continue 50 | } 51 | 52 | if product, ok := supportedDevices[d.ProductID]; ok { 53 | log.Printf("Found device %04x:%04x %s", d.VendorID, d.ProductID, product) 54 | } else { 55 | continue 56 | } 57 | 58 | conf.dev, err = d.Open() 59 | 60 | if err != nil { 61 | return 62 | } 63 | 64 | break 65 | } 66 | 67 | if conf.dev == nil { 68 | return errors.New("no device found, target missing or invalid permissions (forgot admin shell?)") 69 | } 70 | 71 | return 72 | } 73 | 74 | func sendHIDReport(n int, buf []byte, wait int) (res []byte, err error) { 75 | err = conf.dev.Write(append([]byte{byte(n)}, buf...)) 76 | 77 | if err != nil || wait < 0 { 78 | return 79 | } 80 | 81 | ok := false 82 | timer := time.After(time.Duration(timeout) * time.Second) 83 | 84 | for { 85 | select { 86 | case res, ok = <-conf.dev.ReadCh(): 87 | if !ok { 88 | return nil, errors.New("error reading response") 89 | } 90 | 91 | if len(res) > 0 && res[0] == byte(wait) { 92 | return 93 | } 94 | case <-timer: 95 | return nil, errors.New("command timeout") 96 | } 97 | } 98 | } 99 | 100 | func dcdWrite(dcd []byte, addr uint32) (err error) { 101 | r1, r2 := sdp.BuildDCDWriteReport(dcd, addr) 102 | 103 | _, err = sendHIDReport(1, r1, -1) 104 | 105 | if err != nil { 106 | return 107 | } 108 | 109 | _, err = sendHIDReport(2, r2, 4) 110 | 111 | return 112 | } 113 | 114 | func fileWrite(imx []byte, addr uint32) (err error) { 115 | r1, r2 := sdp.BuildFileWriteReport(imx, addr) 116 | 117 | _, err = sendHIDReport(1, r1, -1) 118 | 119 | if err != nil { 120 | return 121 | } 122 | 123 | wait := -1 124 | timer := time.After(time.Duration(timeout) * time.Second) 125 | 126 | for i, r := range r2 { 127 | if i == len(r2)-1 { 128 | wait = 4 129 | } 130 | send: 131 | _, err = sendHIDReport(2, r, wait) 132 | 133 | if err != nil && runtime.GOOS == "darwin" && err.Error() == "hid: general error" { 134 | // On macOS access contention with the OS causes 135 | // errors, as a workaround we retry from the transfer 136 | // that got caught up. 137 | select { 138 | case <-timer: 139 | return 140 | default: 141 | off := uint32(i) * 1024 142 | r1 := &sdp.SDP{ 143 | CommandType: sdp.WriteFile, 144 | Address: addr + off, 145 | DataCount: uint32(len(imx)) - off, 146 | } 147 | 148 | if _, err = sendHIDReport(1, r1.Bytes(), -1); err != nil { 149 | return 150 | } 151 | 152 | goto send 153 | } 154 | } 155 | 156 | if err != nil { 157 | break 158 | } 159 | } 160 | 161 | return 162 | } 163 | 164 | func jumpAddress(addr uint32) (err error) { 165 | r1 := sdp.BuildJumpAddressReport(addr) 166 | _, err = sendHIDReport(1, r1, -1) 167 | 168 | return 169 | } 170 | 171 | func imxLoad(imx []byte) (err error) { 172 | for { 173 | if err = detect(); err != nil { 174 | time.Sleep(100 * time.Millisecond) 175 | continue 176 | } 177 | 178 | break 179 | } 180 | 181 | ivt, err := sdp.ParseIVT(imx) 182 | 183 | if err != nil { 184 | return fmt.Errorf("IVT parsing error: %v", err) 185 | } 186 | 187 | dcd, err := sdp.ParseDCD(imx, ivt) 188 | 189 | if err != nil { 190 | return fmt.Errorf("DCD parsing error: %v", err) 191 | } 192 | 193 | log.Printf("loading DCD at %#08x (%d bytes)", iramOffset, len(dcd)) 194 | err = dcdWrite(dcd, iramOffset) 195 | 196 | if err != nil { 197 | return 198 | } 199 | 200 | log.Printf("loading imx to %#08x (%d bytes)", ivt.Self, len(imx)) 201 | err = fileWrite(imx, ivt.Self) 202 | 203 | if err != nil { 204 | return 205 | } 206 | 207 | log.Printf("jumping to %#08x", ivt.Self) 208 | err = jumpAddress(ivt.Self) 209 | 210 | if err != nil { 211 | return 212 | } 213 | 214 | return 215 | } 216 | -------------------------------------------------------------------------------- /internal/crypto/keyring.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package crypto 7 | 8 | import ( 9 | "crypto/cipher" 10 | "crypto/ecdsa" 11 | "crypto/elliptic" 12 | "crypto/rand" 13 | "crypto/sha256" 14 | "crypto/x509" 15 | "errors" 16 | "io" 17 | "sync" 18 | 19 | "golang.org/x/crypto/hkdf" 20 | "golang.org/x/crypto/xts" 21 | ) 22 | 23 | // DCP key RAM indices 24 | const ( 25 | BLOCK_KEY = iota 26 | ESSIV_KEY 27 | SNVS_KEY 28 | ) 29 | 30 | // BLE key indices 31 | const ( 32 | UA_LONGTERM_KEY = iota 33 | UA_EPHEMERAL_KEY 34 | MD_LONGTERM_KEY 35 | MD_EPHEMERAL_KEY 36 | ) 37 | 38 | type Keyring struct { 39 | // FDE function 40 | Cipher func(buf []byte, lba int, blocks int, blockSize int, enc bool, wg *sync.WaitGroup) 41 | 42 | // Configuration instance 43 | Conf *PersistentConfiguration 44 | 45 | // long term BLE peer authentication keys 46 | ArmoryLongterm *ecdsa.PrivateKey 47 | MobileLongterm *ecdsa.PublicKey 48 | 49 | // ephemeral BLE peer session keys 50 | armoryEphemeral *ecdsa.PrivateKey 51 | mobileEphemeral *ecdsa.PublicKey 52 | 53 | // BLE shared pre-master secret 54 | preMaster []byte 55 | // BLE shared session key 56 | sessionKey []byte 57 | 58 | // CPU bound ESSIV cipher 59 | cbiv cipher.Block 60 | // CPU bound block cipher 61 | cb cipher.Block 62 | // CPU bound xts block cipher 63 | cbxts *xts.Cipher 64 | 65 | // IV encryption key for ESSIV computation 66 | salt []byte 67 | // persistent storage encryption key 68 | snvs []byte 69 | } 70 | 71 | func (k *Keyring) Init(overwrite bool) (err error) { 72 | // derive persistent storage encryption key 73 | if k.snvs, err = k.deriveKey([]byte(SNVS_DIV), SNVS_KEY, true); err != nil { 74 | return 75 | } 76 | 77 | err = k.Load() 78 | 79 | if err != nil || overwrite { 80 | err = k.reset() 81 | 82 | if err != nil { 83 | return 84 | } 85 | } 86 | 87 | err = k.Import(UA_LONGTERM_KEY, true, k.Conf.ArmoryLongterm) 88 | 89 | if err != nil { 90 | return 91 | } 92 | 93 | // we might not be paired yet, so ignore errors 94 | k.Import(MD_LONGTERM_KEY, false, k.Conf.MobileLongterm) 95 | 96 | // Derive salt, used for ESSIV computation as well as BLOCK_KEY derivation. 97 | if k.salt, err = k.deriveKey([]byte(ESSIV_DIV), ESSIV_KEY, true); err != nil { 98 | return 99 | } 100 | 101 | return 102 | } 103 | 104 | func (k *Keyring) Export(index int, private bool) ([]byte, error) { 105 | var pubKey *ecdsa.PublicKey 106 | var privKey *ecdsa.PrivateKey 107 | 108 | switch index { 109 | case UA_LONGTERM_KEY: 110 | privKey = k.ArmoryLongterm 111 | case UA_EPHEMERAL_KEY: 112 | privKey = k.armoryEphemeral 113 | case MD_LONGTERM_KEY: 114 | pubKey = k.MobileLongterm 115 | case MD_EPHEMERAL_KEY: 116 | pubKey = k.mobileEphemeral 117 | default: 118 | return nil, errors.New("invalid key index") 119 | } 120 | 121 | if !private && pubKey == nil && privKey != nil { 122 | pubKey = &privKey.PublicKey 123 | } 124 | 125 | if private { 126 | if privKey == nil { 127 | return nil, errors.New("invalid key") 128 | } 129 | 130 | return x509.MarshalECPrivateKey(privKey) 131 | } else { 132 | if pubKey == nil { 133 | return nil, errors.New("invalid key") 134 | } 135 | 136 | return x509.MarshalPKIXPublicKey(pubKey) 137 | } 138 | } 139 | 140 | func (k *Keyring) Import(index int, private bool, der []byte) (err error) { 141 | var pubKey *ecdsa.PublicKey 142 | var privKey *ecdsa.PrivateKey 143 | 144 | if private { 145 | privKey, err = x509.ParseECPrivateKey(der) 146 | } else { 147 | var pk interface{} 148 | 149 | pk, err = x509.ParsePKIXPublicKey(der) 150 | 151 | if err == nil { 152 | switch key := pk.(type) { 153 | case *ecdsa.PublicKey: 154 | pubKey = key 155 | default: 156 | return errors.New("incompatible key type") 157 | } 158 | } 159 | } 160 | 161 | if err != nil { 162 | return 163 | } 164 | 165 | switch index { 166 | case UA_LONGTERM_KEY: 167 | k.ArmoryLongterm = privKey 168 | case MD_LONGTERM_KEY: 169 | k.MobileLongterm = pubKey 170 | case MD_EPHEMERAL_KEY: 171 | k.mobileEphemeral = pubKey 172 | default: 173 | return errors.New("invalid key index") 174 | } 175 | 176 | return 177 | } 178 | 179 | func (k *Keyring) NewLongtermKey() (err error) { 180 | k.ArmoryLongterm, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 181 | return 182 | } 183 | 184 | func (k *Keyring) NewSessionKeys(nonce []byte) (err error) { 185 | k.armoryEphemeral, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 186 | 187 | if err != nil { 188 | return 189 | } 190 | 191 | peerX := k.mobileEphemeral.X 192 | peerY := k.mobileEphemeral.Y 193 | privX := k.armoryEphemeral.D.Bytes() 194 | 195 | length := (k.mobileEphemeral.Params().BitSize + 7) >> 3 196 | k.preMaster = make([]byte, length) 197 | 198 | s, _ := k.mobileEphemeral.ScalarMult(peerX, peerY, privX) 199 | shared := s.Bytes() 200 | 201 | copy(k.preMaster[len(k.preMaster)-len(shared):], shared) 202 | 203 | hkdf := hkdf.New(sha256.New, k.preMaster, nonce, nil) 204 | 205 | k.sessionKey = make([]byte, 32) 206 | _, err = io.ReadFull(hkdf, k.sessionKey) 207 | 208 | return 209 | } 210 | 211 | func (k *Keyring) ClearSessionKeys() { 212 | k.sessionKey = []byte{} 213 | k.armoryEphemeral = nil 214 | k.mobileEphemeral = nil 215 | } 216 | -------------------------------------------------------------------------------- /internal/ums/usb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ums 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "encoding/hex" 12 | "fmt" 13 | "strings" 14 | 15 | "github.com/usbarmory/tamago/dma" 16 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 17 | "github.com/usbarmory/tamago/soc/nxp/usb" 18 | ) 19 | 20 | const maxPacketSize = 512 21 | 22 | func (d *Drive) ConfigureUSB() (device *usb.Device) { 23 | device = &usb.Device{ 24 | Setup: d.setup, 25 | } 26 | 27 | // Supported Language Code Zero: English 28 | device.SetLanguageCodes([]uint16{0x0409}) 29 | 30 | // device descriptor 31 | device.Descriptor = &usb.DeviceDescriptor{} 32 | device.Descriptor.SetDefaults() 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(VendorID) 41 | device.Descriptor.Manufacturer = iManufacturer 42 | 43 | iProduct, _ := device.AddString(ProductID) 44 | device.Descriptor.Product = iProduct 45 | 46 | // p9, 4.1.1 Serial Number, USB Mass Storage Class 1.0 47 | // 48 | // The serial number format is [0-9A-F]{12,}, the NXP Unique 49 | // ID is converted accordingly. 50 | uid := imx6ul.UniqueID() 51 | serial := strings.ToUpper(hex.EncodeToString(uid[:])) 52 | 53 | iSerial, _ := device.AddString(serial) 54 | device.Descriptor.SerialNumber = iSerial 55 | 56 | conf := &usb.ConfigurationDescriptor{} 57 | conf.SetDefaults() 58 | 59 | device.AddConfiguration(conf) 60 | 61 | // device qualifier 62 | device.Qualifier = &usb.DeviceQualifierDescriptor{} 63 | device.Qualifier.SetDefaults() 64 | device.Qualifier.NumConfigurations = uint8(len(device.Configurations)) 65 | 66 | // interface 67 | iface := &usb.InterfaceDescriptor{} 68 | iface.SetDefaults() 69 | iface.NumEndpoints = 2 70 | iface.InterfaceClass = usb.MASS_STORAGE_CLASS 71 | iface.InterfaceSubClass = usb.SCSI_CLASS 72 | iface.InterfaceProtocol = usb.BULK_ONLY_TRANSPORT_PROTOCOL 73 | iface.Interface = 0 74 | 75 | // EP1 IN endpoint (bulk) 76 | ep1IN := &usb.EndpointDescriptor{} 77 | ep1IN.SetDefaults() 78 | ep1IN.EndpointAddress = 0x81 79 | ep1IN.Attributes = 2 80 | ep1IN.MaxPacketSize = maxPacketSize 81 | ep1IN.Zero = false 82 | ep1IN.Function = d.tx 83 | 84 | iface.Endpoints = append(iface.Endpoints, ep1IN) 85 | 86 | // EP2 OUT endpoint (bulk) 87 | ep1OUT := &usb.EndpointDescriptor{} 88 | ep1OUT.SetDefaults() 89 | ep1OUT.EndpointAddress = 0x01 90 | ep1OUT.Attributes = 2 91 | ep1OUT.MaxPacketSize = maxPacketSize 92 | ep1OUT.Zero = false 93 | ep1OUT.Function = d.rx 94 | 95 | iface.Endpoints = append(iface.Endpoints, ep1OUT) 96 | 97 | device.Configurations[0].AddInterface(iface) 98 | 99 | return 100 | } 101 | 102 | // setup handles the class specific control requests specified at 103 | // p7, 3.1 - 3.2, USB Mass Storage Class 1.0 104 | func (d *Drive) setup(setup *usb.SetupData) (in []byte, ack bool, done bool, err error) { 105 | switch setup.Request { 106 | case usb.BULK_ONLY_MASS_STORAGE_RESET: 107 | // For we ack this request without resetting. 108 | case usb.GET_MAX_LUN: 109 | in = []byte{0x00} 110 | } 111 | 112 | return 113 | } 114 | 115 | func parseCBW(buf []byte) (cbw *usb.CBW, err error) { 116 | if len(buf) == 0 { 117 | return 118 | } 119 | 120 | if len(buf) != usb.CBW_LENGTH { 121 | return nil, fmt.Errorf("invalid CBW size %d != %d", len(buf), usb.CBW_LENGTH) 122 | } 123 | 124 | cbw = &usb.CBW{} 125 | err = binary.Read(bytes.NewReader(buf), binary.LittleEndian, cbw) 126 | 127 | if err != nil { 128 | return 129 | } 130 | 131 | if cbw.Length < 6 || cbw.Length > usb.CBW_CB_MAX_LENGTH { 132 | return nil, fmt.Errorf("invalid Command Block Length %d", cbw.Length) 133 | } 134 | 135 | if cbw.Signature != usb.CBW_SIGNATURE { 136 | return nil, fmt.Errorf("invalid CBW signature %x", cbw.Signature) 137 | } 138 | 139 | return 140 | } 141 | 142 | func (d *Drive) rx(buf []byte, lastErr error) (res []byte, err error) { 143 | var cbw *usb.CBW 144 | 145 | if d.dataPending != nil { 146 | defer dma.Release(d.dataPending.addr) 147 | err = d.handleWrite() 148 | 149 | if err != nil { 150 | return 151 | } 152 | 153 | csw := d.dataPending.csw 154 | csw.DataResidue = 0 155 | 156 | d.send <- d.dataPending.csw.Bytes() 157 | 158 | d.dataPending = nil 159 | 160 | return 161 | } 162 | 163 | cbw, err = parseCBW(buf) 164 | 165 | if err != nil { 166 | return 167 | } 168 | 169 | csw, data, err := d.handleCDB(cbw.CommandBlock, cbw) 170 | 171 | defer func() { 172 | if csw != nil { 173 | d.send <- csw.Bytes() 174 | } 175 | }() 176 | 177 | if err != nil { 178 | csw.DataResidue = cbw.DataTransferLength 179 | csw.Status = usb.CSW_STATUS_COMMAND_FAILED 180 | return 181 | } 182 | 183 | if len(data) > 0 { 184 | d.send <- data 185 | } 186 | 187 | if d.dataPending != nil { 188 | d.dataPending.addr, d.dataPending.buf = dma.Reserve(d.dataPending.size, usb.DTD_PAGE_SIZE) 189 | res = d.dataPending.buf 190 | } 191 | 192 | return 193 | } 194 | 195 | func (d *Drive) tx(_ []byte, lastErr error) (in []byte, err error) { 196 | select { 197 | case buf := <-d.free: 198 | dma.Release(buf) 199 | default: 200 | } 201 | 202 | in = <-d.send 203 | 204 | if reserved, addr := dma.Reserved(in); reserved { 205 | d.free <- addr 206 | } 207 | 208 | return 209 | } 210 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/github.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "context" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "log" 14 | "net/http" 15 | "os" 16 | 17 | "github.com/google/go-github/v34/github" 18 | "golang.org/x/oauth2" 19 | ) 20 | 21 | const ( 22 | org = "usbarmory" 23 | releaseRepo = "armory-drive" 24 | logRepo = "armory-drive-log" 25 | 26 | checkpointPath = "log/" 27 | keysPath = "keys/" 28 | logKeyName = "armory-drive-log.pub" 29 | frKeyName = "armory-drive.pub" 30 | checkpointCachePath = "armory-drive-install.lastCheckpoint" 31 | ) 32 | 33 | type releaseAssets struct { 34 | // firmware binary 35 | imx []byte 36 | // secure boot fuse table 37 | srk []byte 38 | // secure boot signature for eMMC boot mode 39 | csf []byte 40 | // secure boot signature for serial download mode 41 | sdp []byte 42 | // firmware transparency proof 43 | log []byte 44 | 45 | // manifest authentication key 46 | frPub []byte 47 | // transparency log authentication key 48 | logPub []byte 49 | } 50 | 51 | func (a *releaseAssets) complete() bool { 52 | return (len(a.imx) > 0 && 53 | len(a.srk) > 0 && 54 | len(a.csf) > 0 && 55 | len(a.sdp) > 0 && 56 | len(a.log) > 0) 57 | } 58 | 59 | func githubClient() (*github.Client, bool) { 60 | var httpClient *http.Client 61 | 62 | // A GITHUB_TOKEN environment variable can be set to avoid GitHub API 63 | // rate limiting. 64 | token := os.Getenv("GITHUB_TOKEN") 65 | 66 | if len(token) == 0 { 67 | return github.NewClient(nil), false 68 | } 69 | 70 | ts := oauth2.StaticTokenSource( 71 | &oauth2.Token{AccessToken: token}, 72 | ) 73 | 74 | httpClient = oauth2.NewClient(context.Background(), ts) 75 | 76 | return github.NewClient(httpClient), true 77 | } 78 | 79 | func downloadRelease(version string) (a *releaseAssets, err error) { 80 | var release *github.RepositoryRelease 81 | 82 | ctx := context.Background() 83 | client, auth := githubClient() 84 | 85 | if version == "latest" { 86 | release, _, err = client.Repositories.GetLatestRelease(ctx, org, releaseRepo) 87 | } else { 88 | release, _, err = client.Repositories.GetReleaseByTag(ctx, org, releaseRepo, version) 89 | } 90 | 91 | if err != nil { 92 | return 93 | } 94 | 95 | if !auth { 96 | // If we do not have a GitHub API token make unauthenticated 97 | // downloads. 98 | client = nil 99 | } 100 | 101 | a = &releaseAssets{} 102 | 103 | for _, asset := range release.Assets { 104 | switch *asset.Name { 105 | case "armory-drive.imx": 106 | if a.imx, err = downloadAsset("binary release", release, asset, client); err != nil { 107 | return 108 | } 109 | case "armory-drive.srk": 110 | if a.srk, err = downloadAsset("SRK table hash", release, asset, client); err != nil { 111 | return 112 | } 113 | case "armory-drive.csf": 114 | if a.csf, err = downloadAsset("HAB signature", release, asset, client); err != nil { 115 | return 116 | } 117 | case "armory-drive.sdp": 118 | if a.sdp, err = downloadAsset("recovery signature", release, asset, client); err != nil { 119 | return 120 | } 121 | case "armory-drive.proofbundle": 122 | if a.log, err = downloadAsset("proof bundle", release, asset, client); err != nil { 123 | return 124 | } 125 | } 126 | } 127 | 128 | if !a.complete() { 129 | return nil, errors.New("incomplete release") 130 | } 131 | 132 | log.Printf("\nDownloaded verified release assets") 133 | 134 | if len(conf.frPublicKey) > 0 { 135 | log.Printf("Using %s as manifest authentication key", conf.frPublicKey) 136 | a.frPub, err = os.ReadFile(conf.frPublicKey) 137 | } else { 138 | a.frPub, err = downloadKey("manifest authentication key", keysPath+frKeyName, client) 139 | } 140 | 141 | if err != nil { 142 | return nil, fmt.Errorf("could not load key, %v", err) 143 | } 144 | 145 | if len(conf.logPublicKey) > 0 { 146 | log.Printf("Using %s as log authentication key", conf.logPublicKey) 147 | a.logPub, err = os.ReadFile(conf.logPublicKey) 148 | } else { 149 | a.logPub, err = downloadKey("transparency log authentication key", keysPath+logKeyName, client) 150 | } 151 | 152 | if err != nil { 153 | return nil, fmt.Errorf("could not load key, %v", err) 154 | } 155 | 156 | if err := verifyRelease(release, a); err != nil { 157 | return nil, fmt.Errorf("invalid release: %v", err) 158 | } 159 | 160 | return 161 | } 162 | 163 | func logFetcher(ctx context.Context, path string) (buf []byte, err error) { 164 | client, _ := githubClient() 165 | 166 | opts := &github.RepositoryContentGetOptions{ 167 | Ref: conf.branch, 168 | } 169 | 170 | res, _, err := client.Repositories.DownloadContents(ctx, org, logRepo, checkpointPath+path, opts) 171 | 172 | if err != nil { 173 | return 174 | } 175 | 176 | return io.ReadAll(res) 177 | } 178 | 179 | func downloadAsset(tag string, release *github.RepositoryRelease, asset *github.ReleaseAsset, client *github.Client) ([]byte, error) { 180 | log.Printf("\nFound %s", tag) 181 | log.Printf(" Tag: %s", release.GetTagName()) 182 | log.Printf(" Author: %s", asset.GetUploader().GetLogin()) 183 | log.Printf(" Date: %s", asset.CreatedAt) 184 | log.Printf(" Link: %s", release.GetHTMLURL()) 185 | log.Printf(" URL: %s", asset.GetBrowserDownloadURL()) 186 | 187 | log.Printf("Downloading %s %d bytes...", asset.GetName(), asset.GetSize()) 188 | 189 | if client != nil { 190 | res, _, err := client.Repositories.DownloadReleaseAsset(context.Background(), org, releaseRepo, asset.GetID(), http.DefaultClient) 191 | 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | return io.ReadAll(res) 197 | } 198 | 199 | res, err := http.Get(asset.GetBrowserDownloadURL()) 200 | 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | return io.ReadAll(res.Body) 206 | } 207 | 208 | func downloadKey(tag string, path string, client *github.Client) ([]byte, error) { 209 | log.Printf("Downloading %s from %s", tag, fmt.Sprintf("%s/%s/%s", org, logRepo, path)) 210 | 211 | if client != nil { 212 | res, _, err := client.Repositories.DownloadContents(context.Background(), org, logRepo, path, nil) 213 | 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | return io.ReadAll(res) 219 | } 220 | 221 | url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s", org, logRepo, conf.branch, path) 222 | 223 | res, err := http.Get(url) 224 | 225 | if err != nil { 226 | return nil, err 227 | } 228 | 229 | return io.ReadAll(res.Body) 230 | } 231 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/const.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | const ( 9 | unsigned = iota 10 | signedByFSecure 11 | signedByUser 12 | ) 13 | 14 | const ( 15 | otaPath = "UPDATE.ZIP" 16 | imxPath = "armory-drive.imx" 17 | csfPath = "armory-drive.csf" 18 | logPath = "armory-drive.log" 19 | ) 20 | 21 | const usage = `Usage: habtool [OPTIONS] 22 | -h show this help 23 | 24 | -I first time install 25 | -R recovery install 26 | -U int 27 | upgrade (unsigned: 0, OEM keys: 1, user keys: 2) (default -1) 28 | 29 | -C string 30 | SRK private key in PEM format 31 | -c string 32 | SRK public key in PEM format 33 | -t string 34 | SRK table 35 | -T string 36 | SRK table hash 37 | -x int 38 | index for SRK key (default -1) 39 | 40 | -p string 41 | transparency log authentication key 42 | -f string 43 | manifest authentication key 44 | -l string 45 | firmware transparency log origin (default "Armory Drive Prod 2") 46 | -b string 47 | release branch (default "master") 48 | -r string 49 | release version (default "latest") 50 | ` 51 | 52 | const welcome = ` 53 | Welcome to the Armory Drive installer! 54 | 55 | For more information or support on Armory Drive see: 56 | https://github.com/usbarmory/armory-drive/wiki 57 | 58 | This program will install or upgrade Armory Drive on your USB armory.` 59 | 60 | const secureBootNotice = ` 61 | ████████████████████████████████████████████████████████████████████████████████ 62 | 63 | This installer supports installation of unsigned or signed Armory Drive 64 | releases on the USB armory. 65 | 66 | *** Option #1: signed releases *** 67 | 68 | The installation of signed releases activates Secure Boot on the target USB 69 | armory, fully converting the device to exclusive operation with signed 70 | executables. 71 | 72 | If the signed releases option is chosen you will be given the option of using 73 | OEM signing keys or your own. 74 | 75 | *** Option #2: unsigned releases *** 76 | 77 | The installation of unsigned releases does not leverage on Secure Boot and does 78 | not permanently modify the USB armory security state. 79 | 80 | Unsigned releases however cannot guarantee device security as hardware bound 81 | key material will use default test keys, lacking protection for stored armory 82 | communication keys and leaving data encryption key freshness only to the mobile 83 | application. 84 | 85 | Unsigned releases are recommended only for test/evaluation purposes and are not 86 | recommended for protection of sensitive data where device tampering is a risk. 87 | 88 | ████████████████████████████████████████████████████████████████████████████████ 89 | ` 90 | 91 | const unsignedFirmwareWarning = ` 92 | ████████████████████████████████████████████████████████████████████████████████ 93 | 94 | *** Armory Drive Programming Utility *** 95 | *** READ CAREFULLY *** 96 | 97 | This will provision unsigned Armory Drive firmware on your USB armory. 98 | 99 | This firmware *cannot guarantee device security* as hardware bound key material 100 | will use *default test keys*, lacking protection for stored armory communication 101 | keys and leaving data encryption key freshness only to the mobile application. 102 | 103 | Unsigned releases are therefore recommended exclusively for test/evaluation 104 | purposes and are *not recommended for protection of sensitive data*. 105 | 106 | To enable the full security model install a signed release, which enables 107 | Secure Boot. 108 | 109 | ████████████████████████████████████████████████████████████████████████████████ 110 | ` 111 | 112 | const fscSignedFirmwareWarning = ` 113 | ████████████████████████████████████████████████████████████████████████████████ 114 | 115 | *** Armory Drive Programming Utility *** 116 | *** READ CAREFULLY *** 117 | 118 | This will provision OEM signed Armory Drive firmware on your USB armory. By 119 | doing so, secure boot will be activated on the USB armory with permanent OTP 120 | fusing of OEM public secure boot keys. 121 | 122 | Fusing OTP's is an **irreversible** action that permanently fuses values on the 123 | device. This means that your USB armory will be able to only execute OEM signed 124 | Armory Drive firmware after programming is completed. 125 | 126 | In other words your USB armory will stop acting as a generic purpose device and 127 | will be converted to *exclusive use of OEM signed Armory Drive releases*. 128 | 129 | ████████████████████████████████████████████████████████████████████████████████ 130 | ` 131 | 132 | const userSignedFirmwareWarning = ` 133 | ████████████████████████████████████████████████████████████████████████████████ 134 | 135 | *** Armory Drive Programming Utility *** 136 | *** READ CAREFULLY *** 137 | 138 | This will provision user signed Armory Drive firmware on your USB armory. By 139 | doing so, secure boot will be activated on the USB armory with permanent OTP 140 | fusing of your own public secure boot keys. 141 | 142 | Fusing OTP's is an **irreversible** action that permanently fuses values on the 143 | device. This means that your USB armory will be able to only execute firmware 144 | signed with your own secure boot keys after programming is completed. 145 | 146 | In other words your USB armory will stop acting as a generic purpose device and 147 | will be converted to *exclusive use of your own signed firmware releases*. 148 | 149 | ████████████████████████████████████████████████████████████████████████████████ 150 | ` 151 | 152 | const secureBootHelp = ` 153 | ████████████████████████████████████████████████████████████████████████████████ 154 | 155 | To sign releases on your own the installer needs access to your SRK private 156 | (-C) and public (-c) keys, the SRK table (-t), the SRK table hash (-T) and the 157 | SRK keypair index (-x) within the table. 158 | 159 | These flags must passed to the installer when signing your own releases (launch 160 | installer with -h flag to see all availale flags). 161 | 162 | If you want to use previously generated secure boot keys, please set these 163 | flags according to your environment. 164 | 165 | If you have not yet generated secure boot keys, you can do so with the 166 | following tool: 167 | 168 | https://github.com/usbarmory/crucible/tree/master/cmd/habtool 169 | 170 | Example: 171 | 172 | # SRK keys generation 173 | habtool -C SRK_1_key.pem -c SRK_1_crt.pem 174 | habtool -C SRK_2_key.pem -c SRK_2_crt.pem 175 | habtool -C SRK_3_key.pem -c SRK_3_crt.pem 176 | habtool -C SRK_4_key.pem -c SRK_4_crt.pem 177 | 178 | # SRK table and table hash generation 179 | habtool -1 SRK_1_crt.pem -2 SRK_2_crt.pem -3 SRK_3_crt.pem -4 SRK_4_crt.pem -t SRK_1_2_3_4_table.bin -o SRK_1_2_3_4_fuse.bin 180 | 181 | # installer invocation with generated key material 182 | armory-drive-install -C SRK_1_key.pem -c SRK_1_crt.pem -t SRK_1_2_3_4_table.bin -T SRK_1_2_3_4_fuse.bin -x 1 183 | 184 | ████████████████████████████████████████████████████████████████████████████████ 185 | ` 186 | -------------------------------------------------------------------------------- /internal/ble/api.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ble 7 | 8 | import ( 9 | "crypto/aes" 10 | "encoding/binary" 11 | "errors" 12 | "log" 13 | "time" 14 | 15 | "github.com/usbarmory/armory-drive/api" 16 | "github.com/usbarmory/armory-drive/assets" 17 | "github.com/usbarmory/armory-drive/internal/crypto" 18 | 19 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 20 | 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | func (b *BLE) parseEnvelope(buf []byte) (msg *api.Message, err error) { 25 | env := &api.Envelope{} 26 | 27 | if err = proto.Unmarshal(buf, env); err != nil { 28 | return 29 | } 30 | 31 | msg = &api.Message{} 32 | 33 | if err = proto.Unmarshal(env.Message, msg); err != nil { 34 | return 35 | } 36 | 37 | if msg.OpCode == api.OpCode_SESSION { 38 | b.Keyring.ClearSessionKeys() 39 | b.session.Reset() 40 | } 41 | 42 | if !b.pairingMode && b.Keyring.MobileLongterm != nil { 43 | if err = b.verifyEnvelope(env); err != nil { 44 | return 45 | } 46 | } 47 | 48 | if msg.OpCode != api.OpCode_PAIR && msg.OpCode != api.OpCode_SESSION { 49 | if err = b.decryptPayload(msg); err != nil { 50 | return 51 | } 52 | } 53 | 54 | if msg.Timestamp <= b.session.Last { 55 | return nil, errors.New("invalid timestamp") 56 | } 57 | 58 | b.session.Last = msg.Timestamp 59 | 60 | return 61 | } 62 | 63 | func (b *BLE) handleEnvelope(req []byte) (res []byte) { 64 | resMsg := &api.Message{ 65 | Timestamp: b.session.Time(), 66 | Response: true, 67 | OpCode: api.OpCode_NULL, 68 | } 69 | 70 | defer func() { 71 | var err error 72 | 73 | if resMsg.OpCode != api.OpCode_PAIR && resMsg.OpCode != api.OpCode_SESSION { 74 | if err = b.encryptPayload(resMsg); err != nil { 75 | return 76 | } 77 | } 78 | 79 | resEnv := &api.Envelope{ 80 | Message: resMsg.Bytes(), 81 | } 82 | 83 | if err = b.signEnvelope(resEnv); err != nil { 84 | return 85 | } 86 | 87 | if resMsg.OpCode == api.OpCode_SESSION && resMsg.Error == 0 { 88 | b.session.Active = true 89 | } 90 | 91 | res = resEnv.Bytes() 92 | }() 93 | 94 | reqMsg, err := b.parseEnvelope(req) 95 | 96 | if err != nil { 97 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 98 | return 99 | } 100 | 101 | resMsg.OpCode = reqMsg.OpCode 102 | b.handleMessage(reqMsg, resMsg) 103 | 104 | return 105 | } 106 | 107 | func (b *BLE) handleMessage(reqMsg *api.Message, resMsg *api.Message) { 108 | switch { 109 | case b.pairingMode: 110 | if reqMsg.OpCode != api.OpCode_PAIR { 111 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 112 | return 113 | } 114 | 115 | b.pair(reqMsg, resMsg) 116 | return 117 | case b.Keyring.MobileLongterm == nil: 118 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 119 | return 120 | case reqMsg.OpCode == api.OpCode_SESSION: 121 | b.newSession(reqMsg, resMsg) 122 | return 123 | case !b.session.Active: 124 | resMsg.Error = api.ErrorCode_INVALID_SESSION 125 | return 126 | } 127 | 128 | switch reqMsg.OpCode { 129 | case api.OpCode_UNLOCK: 130 | b.unlock(reqMsg, resMsg) 131 | case api.OpCode_LOCK: 132 | b.lock(reqMsg, resMsg) 133 | case api.OpCode_STATUS: 134 | b.status(reqMsg, resMsg) 135 | case api.OpCode_CONFIGURATION: 136 | b.configuration(reqMsg, resMsg) 137 | default: 138 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 139 | } 140 | } 141 | 142 | func (b *BLE) pair(reqMsg *api.Message, resMsg *api.Message) { 143 | keyExchange := &api.KeyExchange{} 144 | err := proto.Unmarshal(reqMsg.Payload, keyExchange) 145 | 146 | if err != nil { 147 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 148 | return 149 | } 150 | 151 | defer func() { 152 | if err != nil { 153 | log.Printf("err: %v", err) 154 | resMsg.Error = api.ErrorCode_PAIRING_KEY_NEGOTIATION_FAILED 155 | } 156 | }() 157 | 158 | if keyExchange.Nonce != b.pairingNonce { 159 | err = errors.New("nonce mismatch") 160 | return 161 | } 162 | 163 | // At this point pairing is considered successful, therefore overwrite 164 | // previous keyring with the newly generated UA longterm key. 165 | b.Keyring.Init(true) 166 | 167 | // Import the MD longterm key. 168 | if err = b.Keyring.Import(crypto.MD_LONGTERM_KEY, false, keyExchange.Key); err != nil { 169 | return 170 | } 171 | 172 | // Save the received MD longterm key in persistent storage. 173 | b.Keyring.Conf.MobileLongterm = keyExchange.Key 174 | err = b.Keyring.Save() 175 | 176 | b.Drive.PairingComplete <- true 177 | } 178 | 179 | func (b *BLE) newSession(reqMsg *api.Message, resMsg *api.Message) { 180 | keyExchange := &api.KeyExchange{} 181 | err := proto.Unmarshal(reqMsg.Payload, keyExchange) 182 | 183 | if err != nil { 184 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 185 | return 186 | } 187 | 188 | defer func() { 189 | if err != nil { 190 | // invalidate previous session on any error 191 | b.Keyring.ClearSessionKeys() 192 | b.session.Reset() 193 | resMsg.Error = api.ErrorCode_SESSION_KEY_NEGOTIATION_FAILED 194 | } 195 | }() 196 | 197 | if err = b.Keyring.Import(crypto.MD_EPHEMERAL_KEY, false, keyExchange.Key); err != nil { 198 | return 199 | } 200 | 201 | nonce := crypto.Rand(8) 202 | 203 | if err = b.Keyring.NewSessionKeys(nonce); err != nil { 204 | return 205 | } 206 | 207 | key, err := b.Keyring.Export(crypto.UA_EPHEMERAL_KEY, false) 208 | 209 | if err != nil { 210 | return 211 | } 212 | 213 | b.session.Skew = time.Until(time.Unix(0, reqMsg.Timestamp*1000*1000)) 214 | 215 | keyExchange = &api.KeyExchange{ 216 | Key: key, 217 | Nonce: binary.BigEndian.Uint64(nonce), 218 | } 219 | 220 | resMsg.Timestamp = b.session.Time() 221 | resMsg.Payload = keyExchange.Bytes() 222 | } 223 | 224 | func (b *BLE) unlock(reqMsg *api.Message, resMsg *api.Message) { 225 | keyExchange := &api.KeyExchange{} 226 | err := proto.Unmarshal(reqMsg.Payload, keyExchange) 227 | 228 | b.session.Lock() 229 | 230 | defer func() { 231 | b.Drive.Ready = (err == nil) 232 | usbarmory.LED("white", b.Drive.Ready) 233 | 234 | // rate limit unlock operation 235 | time.Sleep(1 * time.Second) 236 | b.session.Unlock() 237 | }() 238 | 239 | if err != nil { 240 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 241 | return 242 | } 243 | 244 | defer func() { 245 | if err != nil { 246 | resMsg.Error = api.ErrorCode_UNLOCK_FAILED 247 | } 248 | }() 249 | 250 | if len(keyExchange.Key) < aes.BlockSize { 251 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 252 | return 253 | } 254 | 255 | err = b.Keyring.SetCipher(b.Keyring.Conf.Settings.Cipher, keyExchange.Key) 256 | } 257 | 258 | func (b *BLE) lock(reqMsg *api.Message, resMsg *api.Message) { 259 | if err := b.Drive.Lock(); err != nil { 260 | resMsg.Error = api.ErrorCode_GENERIC_ERROR 261 | } 262 | } 263 | 264 | func (b *BLE) status(reqMsg *api.Message, resMsg *api.Message) { 265 | s := &api.Status{ 266 | Version: assets.Revision, 267 | Capacity: b.Drive.Capacity(), 268 | Locked: !b.Drive.Ready, 269 | Configuration: b.Keyring.Conf.Settings, 270 | } 271 | 272 | resMsg.Payload = s.Bytes() 273 | } 274 | 275 | func (b *BLE) configuration(reqMsg *api.Message, resMsg *api.Message) { 276 | settings := &api.Configuration{} 277 | err := proto.Unmarshal(reqMsg.Payload, settings) 278 | 279 | if err != nil || b.Drive.Ready { 280 | resMsg.Error = api.ErrorCode_INVALID_MESSAGE 281 | return 282 | } 283 | 284 | b.Keyring.Conf.Settings = settings 285 | b.Keyring.Save() 286 | } 287 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Armory Drive provides a pocket encrypted drive solution based on the 5 | [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki). 6 | 7 | It allows one-tap unlock of a microSD backed encrypted USB drive through a 8 | companion mobile application. 9 | 10 | The USB armory firmware is a [TamaGo](https://github.com/usbarmory/tamago) based unikernel 11 | which allows encrypted USB Mass Storage interfacing for any plugged in microSD card. 12 | 13 | The encrypted storage setup and authentication is meant to be performed with 14 | the [F-Secure Armory Drive iOS app](https://apps.apple.com/us/app/f-secure-armory-drive/id1571708524) 15 | over Bluetooth (BLE). 16 | 17 | To understand the firmware capabilities and use see this 18 | [Tutorial](https://github.com/usbarmory/armory-drive/wiki/Tutorial). 19 | 20 | Security Model 21 | ============== 22 | 23 | See the [detailed specifications](https://github.com/usbarmory/armory-drive/wiki/Specifications) 24 | for full explanation of the security model. 25 | 26 | Installation of pre-compiled releases 27 | ===================================== 28 | 29 | Binary releases are [available](https://github.com/usbarmory/armory-drive/releases) 30 | for the Armory Drive firmware. 31 | 32 | The binary release includes the `armory-drive-install` tool (for Linux, Windows 33 | and macOS) to guide through initial installation of such releases and Secure 34 | Boot activation. 35 | 36 | > [!WARNING] 37 | > :lock: loading signed releases triggers secure boot activation which 38 | > is an *irreversible operation* to be performed **at your own risk**, carefully 39 | > read and understand the following instructions. 40 | 41 | The installer supports the following installation modes: 42 | 43 | * OEM signed releases: the installation of such firmware images 44 | causes OEM secure boot public keys to be *permanently fused* on the target 45 | USB armory, fully converting the device to exclusive use with Armory Drive 46 | releases signed by the USB armory project maintainers. 47 | 48 | These releases also enable [authenticated updates](https://github.com/usbarmory/armory-drive/wiki/Firmware-Transparency) 49 | through [tamper-evident logs](https://github.com/usbarmory/armory-drive-log) 50 | powered by Google [transparency](https://binary.transparency.dev/) framework. 51 | 52 | * User signed releases: the installation of such firmware images 53 | causes user own secure boot keys to be created and *permanently fused* on the 54 | target USB armory, fully converting the device to exclusive use with user 55 | signed binaries. 56 | 57 | * Unsigned releases: such firmware images do *not* leverage on Secure Boot and 58 | can be installed on standard USB armory devices. 59 | 60 | Such releases however *cannot guarantee device security* as hardware bound 61 | key material will use *default test keys*, lacking protection for stored armory 62 | communication keys and leaving data encryption key freshness only to the mobile 63 | application. 64 | 65 | Unsigned releases are recommended only for test/evaluation purposes and are 66 | *not recommended for protection of sensitive data* where device tampering is a 67 | risk. 68 | 69 | The `armory-drive-install` provides interactive installation for all modes and 70 | is the recommended way to use the Armory Drive firmware. 71 | 72 | Expert users can compile and sign their own releases with the information 73 | included in section _Installation of self-compiled releases_. 74 | 75 | Documentation 76 | ============= 77 | 78 | The main documentation can be found on the 79 | [project wiki](https://github.com/usbarmory/armory-drive/wiki). 80 | 81 | Operation 82 | ========= 83 | 84 | Pairing and initialization 85 | -------------------------- 86 | 87 | See the [Tutorial](https://github.com/usbarmory/armory-drive/wiki/Tutorial). 88 | 89 | Disk access 90 | ----------- 91 | 92 | When running with a microSD card inserted, the USB armory Mk II can be used 93 | like any standard USB drive when unlocked through its paired companion iOS app. 94 | 95 | | LED | on | off | blinking | 96 | |-------|------------------|----------------|-----------------------------| 97 | | blue | BLE active | BLE inactive | pairing in progress | 98 | | white | SD card unlocked | SD card locked | firmware update in progress | 99 | 100 | Firmware update 101 | --------------- 102 | 103 | The `armory-drive-install` provides interactive upgrade of all installation 104 | modes and is the recommended way to upgrade the Armory Drive firmware. 105 | 106 | Alternatively *only users of OEM signed releases or unsigned releases* can use 107 | the following procedure on USB armory devices which have been already 108 | initialized with the Armory Drive firmware as shown in _Pairing and 109 | initialization_. 110 | 111 | 1. Download file `update.zip` from the [latest binary release](https://github.com/usbarmory/armory-drive/releases/latest) 112 | 2. If the USB armory contains an SD card, remove it. 113 | 3. Plug the USB armory. 114 | 4. An "F-Secure" disk volume should appear. 115 | 6. Copy `update.zip` to the "F-Secure" disk. 116 | 7. Eject the "F-Secure" disk. 117 | 8. The white LED blinks during the update and turns off on success, a solid blue LED indicates an error. 118 | 9. Put the SD card back in. 119 | 120 | Installation of self-compiled releases 121 | ====================================== 122 | 123 | > [!WARNING] 124 | > These instructions are for *expert users only*, it is recommended 125 | > to use `armory-drive-install` if you don't know what you are doing. 126 | 127 | Compiling 128 | --------- 129 | 130 | Ensure that `make`, a recent version of `go` and `protoc` are installed. 131 | 132 | Install, or update, the following dependency (ensure that the `GOPATH` variable 133 | is set accordingly): 134 | 135 | ``` 136 | go get -u google.golang.org/protobuf/cmd/protoc-gen-go 137 | ``` 138 | 139 | Build the [TamaGo compiler](https://github.com/usbarmory/tamago-go) 140 | (or use the [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)): 141 | 142 | ``` 143 | wget https://github.com/usbarmory/tamago-go/archive/refs/tags/latest.zip 144 | unzip latest.zip 145 | cd tamago-go-latest/src && ./all.bash 146 | cd ../bin && export TAMAGO=`pwd`/go 147 | ``` 148 | 149 | The firmware is meant to be executed on secure booted systems, therefore 150 | [secure boot keys](https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)) 151 | should be created and passed with the `HAB_KEYS` environment variable. 152 | 153 | Build the `armory-drive-signed.imx` application executable: 154 | 155 | ``` 156 | make DISABLE_FR_AUTH=1 HAB_KEYS= imx_signed 157 | ``` 158 | 159 | An unsigned test/development binary can be compiled with the `imx` target. 160 | 161 | Installing 162 | ---------- 163 | 164 | To permanently install `armory-drive-signed.imx` on internal non-volatile memory, 165 | follow [these instructions](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#flashing-bootable-images-on-externalinternal-media) 166 | for internal eMMC flashing. 167 | 168 | > [!WARNING] 169 | > Once loaded, even through [Serial Download Protocol](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#serial-download-protocol-sdp), 170 | > the firmware initializes its configuration by writing on the internal eMMC, therefore corrupting its previous contents. 171 | 172 | Support 173 | ======= 174 | 175 | If you require support, please email us at usbarmory@inversepath.com. 176 | 177 | Authors 178 | ======= 179 | 180 | Andrea Barisani 181 | andrea@inversepath.com 182 | 183 | License 184 | ======= 185 | 186 | Copyright (c) The armory-drive authors. All Rights Reserved. 187 | 188 | This project is distributed under the BSD-style license found in the 189 | [LICENSE](https://github.com/usbarmory/armory-drive/blob/master/LICENSE) file. 190 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) The armory-drive authors. All Rights Reserved. 2 | # 3 | # Use of this source code is governed by the license 4 | # that can be found in the LICENSE file. 5 | 6 | BUILD_TAGS = "linkramsize,linkprintk" 7 | LOG_URL = https://raw.githubusercontent.com/usbarmory/armory-drive-log/master/log/ 8 | LOG_ORIGIN = "Armory Drive Prod 2" 9 | PKG = github.com/usbarmory/armory-drive 10 | 11 | SHELL = /bin/bash 12 | PROTOC ?= /usr/bin/protoc 13 | 14 | APP := armory-drive 15 | GOENV := GO_EXTLINK_ENABLED=0 CGO_ENABLED=0 GOOS=tamago GOARM=7 GOARCH=arm 16 | TEXT_START := 0x80010000 # ramStart (defined in imx6/imx6ul/memory.go) + 0x10000 17 | 18 | # Set revision to git tag, if unset use short commit hash. 19 | REV = $(shell git tag --points-at HEAD 2> /dev/null) 20 | ifeq ("$(REV)","") 21 | REV = $(shell git rev-parse --short HEAD 2> /dev/null) 22 | endif 23 | 24 | .PHONY: proto clean 25 | .PRECIOUS: %.srk 26 | 27 | #### primary targets #### 28 | 29 | all: $(APP) $(APP)-install 30 | 31 | imx: $(APP).imx 32 | 33 | imx_signed: $(APP)-signed.imx 34 | 35 | %-install: GOFLAGS = -tags netgo,osusergo -trimpath -ldflags "-linkmode external -extldflags -static -s -w" 36 | %-install: 37 | @if [ "${TAMAGO}" != "" ]; then \ 38 | cd $(CURDIR) && ${TAMAGO} build -o $@ $(GOFLAGS) ./cmd/$*-install; \ 39 | else \ 40 | cd $(CURDIR) && go build -o $@ $(GOFLAGS) ./cmd/$*-install; \ 41 | fi 42 | 43 | %-install.exe: GOFLAGS = -trimpath 44 | %-install.exe: BUILD_OPTS := GOOS=windows CGO_ENABLED=1 CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc 45 | %-install.exe: 46 | @if [ "${TAMAGO}" != "" ]; then \ 47 | cd $(CURDIR) && $(BUILD_OPTS) ${TAMAGO} build -o $@ $(GOFLAGS) ./cmd/$*-install; \ 48 | else \ 49 | cd $(CURDIR) && $(BUILD_OPTS) go build -o $@ $(GOFLAGS) ./cmd/$*-install; \ 50 | fi 51 | 52 | %-install_darwin-amd64: GOFLAGS = -trimpath 53 | %-install_darwin-amd64: 54 | cd $(CURDIR) && GOOS=darwin GOARCH=amd64 go build -o $(CURDIR)/$*-install_darwin-amd64 $(GOFLAGS) ./cmd/$*-install 55 | 56 | %-install.dmg: %-install_darwin-amd64 57 | $(eval TMPDIR := $(shell mktemp -d)) 58 | mkdir $(TMPDIR)/dmg && \ 59 | lipo -create -output $(TMPDIR)/dmg/$*-install $(CURDIR)/$*-install_darwin-amd64 && \ 60 | hdiutil create $(TMPDIR)/tmp.dmg -ov -volname "Armory Drive Install" -fs HFS+ -srcfolder $(TMPDIR)/dmg && \ 61 | hdiutil convert $(TMPDIR)/tmp.dmg -format UDZO -o $(TMPDIR)/$*-install.dmg && \ 62 | mv $(TMPDIR)/$*-install.dmg $(CURDIR) 63 | rmdir $(TMPDIR) 64 | 65 | #### utilities #### 66 | 67 | check_tamago: 68 | @if [ "${TAMAGO}" == "" ] || [ ! -f "${TAMAGO}" ]; then \ 69 | echo 'You need to set the TAMAGO variable to a compiled version of https://github.com/usbarmory/tamago-go'; \ 70 | exit 1; \ 71 | fi 72 | 73 | check_hab_keys: 74 | @if [ "${HAB_KEYS}" == "" ]; then \ 75 | echo 'You need to set the HAB_KEYS variable to the path of secure boot keys'; \ 76 | echo 'See https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)'; \ 77 | exit 1; \ 78 | fi 79 | 80 | check_git_clean: 81 | @if [ "$(shell git status -s | grep -v 'armory-drive-log.pub\|armory-drive.pub')" != "" ]; then \ 82 | echo 'Dirty git checkout directory detected. Aborting.'; \ 83 | exit 1; \ 84 | fi 85 | 86 | proto: 87 | @echo "generating protobuf classes" 88 | -rm -f *.pb.go 89 | PATH=$(shell echo ${GOPATH} | awk -F":" '{print $$1"/bin"}') cd $(CURDIR)/api && ${PROTOC} --go_out=. armory.proto 90 | 91 | clean: 92 | @rm -fr $(APP) $(APP).bin $(APP).imx $(APP)-signed.imx $(APP).sig $(APP).csf $(APP).sdp $(APP).dcd $(APP).srk 93 | @rm -fr $(APP)-fixup-signed.imx $(APP)-fixup.csf $(APP)-fixup.sdp 94 | @rm -fr $(CURDIR)/api/*.pb.go 95 | @rm -fr $(APP)-install $(APP)-install.exe $(APP)-install_darwin-amd64 $(APP)-install.dmg 96 | @rm -fr $(APP).release $(APP).proofbundle update.zip 97 | 98 | #### dependencies #### 99 | 100 | $(APP): BUILD_TAGS := $(or $(shell ( [ ! -z "${DISABLE_FR_AUTH}" ] ) && echo "$(BUILD_TAGS),disable_fr_auth"),$(BUILD_TAGS)) 101 | $(APP): GOFLAGS = -tags ${BUILD_TAGS} -trimpath -ldflags "-s -w -T $(TEXT_START) -R 0x1000 -X '${PKG}/assets.Revision=${REV}'" 102 | $(APP): check_tamago proto 103 | @if [ "${DISABLE_FR_AUTH}" == "" ]; then \ 104 | echo '** WARNING ** Enabling firmware updates authentication (fr:internal/ota/armory-drive.pub, log:internal/ota/armory-drive-log.pub)'; \ 105 | else \ 106 | echo '** WARNING ** firmware updates authentication is disabled'; \ 107 | fi 108 | cd $(CURDIR) && $(GOENV) $(TAMAGO) build $(GOFLAGS) -o $(CURDIR)/${APP} 109 | 110 | %.dcd: check_tamago 111 | %.dcd: GOMODCACHE = $(shell ${TAMAGO} env GOMODCACHE) 112 | %.dcd: TAMAGO_PKG = $(shell grep "github.com/usbarmory/tamago v" go.mod | awk '{print $$1"@"$$2}') 113 | %.dcd: 114 | echo $(GOMODCACHE) 115 | echo $(TAMAGO_PKG) 116 | cp -f $(GOMODCACHE)/$(TAMAGO_PKG)/board/usbarmory/mk2/imximage.cfg $(APP).dcd 117 | 118 | %.bin: CROSS_COMPILE=arm-none-eabi- 119 | %.bin: % 120 | $(CROSS_COMPILE)objcopy -j .text -j .rodata -j .shstrtab -j .typelink \ 121 | -j .itablink -j .gopclntab -j .go.buildinfo -j .noptrdata -j .data \ 122 | -j .bss --set-section-flags .bss=alloc,load,contents \ 123 | -j .noptrbss --set-section-flags .noptrbss=alloc,load,contents \ 124 | $< -O binary $@ 125 | 126 | %.imx: % %.bin %.dcd 127 | mkimage -n $*.dcd -T imximage -e $(TEXT_START) -d $*.bin $@ 128 | # Copy entry point from ELF file 129 | dd if=$< of=$@ bs=1 count=4 skip=24 seek=4 conv=notrunc 130 | 131 | #### secure boot #### 132 | 133 | %-signed.imx: check_hab_keys %.imx 134 | ${TAMAGO} install github.com/usbarmory/crucible/cmd/habtool 135 | $(shell ${TAMAGO} env GOPATH)/bin/habtool \ 136 | -A ${HAB_KEYS}/CSF_1_key.pem \ 137 | -a ${HAB_KEYS}/CSF_1_crt.pem \ 138 | -B ${HAB_KEYS}/IMG_1_key.pem \ 139 | -b ${HAB_KEYS}/IMG_1_crt.pem \ 140 | -t ${HAB_KEYS}/SRK_1_2_3_4_table.bin \ 141 | -x 1 \ 142 | -s \ 143 | -i $*.imx \ 144 | -o $*.sdp && \ 145 | $(shell ${TAMAGO} env GOPATH)/bin/habtool \ 146 | -A ${HAB_KEYS}/CSF_1_key.pem \ 147 | -a ${HAB_KEYS}/CSF_1_crt.pem \ 148 | -B ${HAB_KEYS}/IMG_1_key.pem \ 149 | -b ${HAB_KEYS}/IMG_1_crt.pem \ 150 | -t ${HAB_KEYS}/SRK_1_2_3_4_table.bin \ 151 | -x 1 \ 152 | -i $*.imx \ 153 | -o $*.csf && \ 154 | cat $*.imx $*.csf > $@ 155 | 156 | #### SRK fixup #### 157 | 158 | # Replace SRK hash before signing. 159 | # For OEM releases the OEM SRK will be used. 160 | 161 | %.srk: check_hab_keys 162 | %.srk: ${HAB_KEYS}/SRK_1_2_3_4_fuse.bin 163 | cp ${HAB_KEYS}/SRK_1_2_3_4_fuse.bin $*.srk 164 | 165 | # See assets/keys.go for the meaning of the dummy hash. 166 | %-fixup.imx: DUMMY_SRK_HASH=630DCD2966C4336691125448BBB25B4FF412A49C732DB2C8ABC1B8581BD710DD 167 | %-fixup.imx: check_hab_keys 168 | %-fixup.imx: %.imx %.srk 169 | OFFSET=$(shell bgrep -b "${DUMMY_SRK_HASH}" $<) && \ 170 | if [[ -z $$OFFSET ]]; then \ 171 | echo "Dummy srk hash not found. Aborting."; \ 172 | exit 1; \ 173 | fi && \ 174 | echo "Found dummy srk hash at offset: 0x$$OFFSET" && \ 175 | cp $< $@ && \ 176 | dd if=$*.srk of=$@ seek=$$((0x$$OFFSET)) bs=1 conv=notrunc 177 | 178 | srk_fixup: $(APP)-signed.imx $(APP)-fixup-signed.imx 179 | mv $(APP)-fixup.sdp $(APP).sdp 180 | 181 | #### firmware release #### 182 | 183 | $(APP).release: PLATFORM = UA-MKII-ULZ 184 | $(APP).release: TAG = $(shell git tag --points-at HEAD) 185 | $(APP).release: check_git_clean srk_fixup 186 | @if [ "${FR_PRIVKEY}" == "" ]; then \ 187 | echo 'FR_PRIVKEY must be set. Aborting.'; \ 188 | exit 1; \ 189 | fi 190 | @if [ "${TAG}" == "" ]; then \ 191 | echo 'No release tag defined on checked-out commit. Aborting.'; \ 192 | exit 1; \ 193 | fi 194 | ${TAMAGO} install github.com/usbarmory/armory-drive-log/cmd/create_release 195 | ${TAMAGO} install github.com/usbarmory/armory-drive-log/cmd/create_proofbundle 196 | $(shell ${TAMAGO} env GOPATH)/bin/create_release \ 197 | --logtostderr \ 198 | --output $(APP).release \ 199 | --description="$(APP) ${TAG}" \ 200 | --platform_id=${PLATFORM} \ 201 | --commit_hash=${REV} \ 202 | --tool_chain="tama$(shell ${TAMAGO} version)" \ 203 | --revision_tag=${TAG} \ 204 | --artifacts='$(CURDIR)/$(APP).imx $(CURDIR)/$(APP).csf $(CURDIR)/$(APP).sdp' \ 205 | --private_key=${FR_PRIVKEY} 206 | @echo "$(APP).release created." 207 | @read -p "Please, add release to the log, then press enter to continue." 208 | $(shell ${TAMAGO} env GOPATH)/bin/create_proofbundle \ 209 | --logtostderr \ 210 | --output $(APP).proofbundle \ 211 | --release $(APP).release \ 212 | --log_origin ${LOG_ORIGIN} \ 213 | --log_url $(LOG_URL) \ 214 | --log_pubkey_file internal/ota/armory-drive-log.pub 215 | @echo "$(APP).proofbundle created." 216 | @cp $(APP).proofbundle $(APP).log && zip update.zip $(APP).{imx,csf,log} && rm $(APP).log 217 | @echo "update.zip created." 218 | -------------------------------------------------------------------------------- /cmd/armory-drive-install/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "archive/zip" 10 | "bytes" 11 | "flag" 12 | "fmt" 13 | "log" 14 | "os" 15 | "path" 16 | 17 | "github.com/usbarmory/hid" 18 | 19 | "github.com/usbarmory/armory-drive/assets" 20 | ) 21 | 22 | type Mode int 23 | 24 | type Config struct { 25 | branch string 26 | release string 27 | install bool 28 | upgrade int 29 | recovery bool 30 | 31 | logPublicKey string 32 | frPublicKey string 33 | logOrigin string 34 | 35 | table string 36 | tableHash string 37 | srkKey string 38 | srkCrt string 39 | index int 40 | 41 | dev hid.Device 42 | } 43 | 44 | var conf *Config 45 | 46 | func init() { 47 | log.SetFlags(0) 48 | log.SetOutput(os.Stdout) 49 | 50 | conf = &Config{} 51 | 52 | flag.Usage = func() { 53 | fmt.Println(usage) 54 | } 55 | 56 | flag.StringVar(&conf.branch, "b", "master", "release branch") 57 | flag.StringVar(&conf.release, "r", "latest", "release version") 58 | flag.BoolVar(&conf.install, "I", false, "first time install") 59 | flag.IntVar(&conf.upgrade, "U", -1, "upgrade (unsigned: 0, OEM keys: 1, user keys: 2)") 60 | flag.BoolVar(&conf.recovery, "R", false, "recovery install") 61 | 62 | flag.StringVar(&conf.logPublicKey, "p", "", "transparency log authentication key") 63 | flag.StringVar(&conf.frPublicKey, "f", "", "manifest authentication key") 64 | flag.StringVar(&conf.logOrigin, "l", assets.DefaultLogOrigin, "firmware transparency log origin") 65 | 66 | flag.StringVar(&conf.srkKey, "C", "", "SRK private key in PEM format") 67 | flag.StringVar(&conf.srkCrt, "c", "", "SRK public key in PEM format") 68 | flag.StringVar(&conf.table, "t", "", "SRK table") 69 | flag.StringVar(&conf.tableHash, "T", "", "SRK table hash") 70 | flag.IntVar(&conf.index, "x", -1, "index for SRK key") 71 | } 72 | 73 | func confirm(msg string) bool { 74 | var res string 75 | 76 | fmt.Printf("\n%s (y/N): ", msg) 77 | fmt.Scanln(&res) 78 | 79 | return res == "y" 80 | } 81 | 82 | func prompt(msg string) (res string) { 83 | fmt.Printf("\n%s: ", msg) 84 | fmt.Scanln(&res) 85 | return 86 | } 87 | 88 | func main() { 89 | flag.Parse() 90 | 91 | log.Println(welcome) 92 | 93 | switch { 94 | case conf.recovery: 95 | if confirm("Are you recovering an Armory Drive installation on a Secure Booted USB armory?") { 96 | recovery() 97 | } 98 | case conf.install || 99 | conf.upgrade < 0 && confirm("Are you installing Armory Drive for the first time on the target USB armory?"): 100 | install() 101 | case conf.upgrade >= 0 || confirm("Are you upgrading Armory Drive on a USB armory already running Armory Drive firmware?"): 102 | upgrade() 103 | } 104 | 105 | log.Printf("\nGoodbye") 106 | } 107 | 108 | func recovery() { 109 | switch { 110 | case confirm("Is Secure Boot enabled on your USB armory using OEM signing keys?"): 111 | installFirmware(signedByFSecure) 112 | case confirm("Is Secure Boot enabled on your USB armory using your own signing keys?"): 113 | checkHABArguments() 114 | installFirmware(signedByUser) 115 | default: 116 | log.Fatal("Goodbye") 117 | } 118 | } 119 | 120 | func install() { 121 | log.Println(secureBootNotice) 122 | 123 | if confirm("Would you like to use unsigned releases, *without enabling* Secure Boot on the USB armory?") { 124 | installFirmware(unsigned) 125 | return 126 | } 127 | 128 | if !confirm("Would you like to *permanently enable* Secure Boot on the USB armory?") { 129 | log.Fatal("Goodbye") 130 | } 131 | 132 | switch { 133 | case confirm("Would you like to use OEM signed releases, enabling Secure Boot on the USB armory with permanent fusing of OEM public keys?"): 134 | installFirmware(signedByFSecure) 135 | case confirm("Would you like to sign releases on your own, enabling Secure Boot on the USB armory with your own public keys?"): 136 | checkHABArguments() 137 | installFirmware(signedByUser) 138 | default: 139 | log.Fatal("Goodbye") 140 | } 141 | } 142 | 143 | func upgrade() { 144 | switch { 145 | case conf.upgrade == unsigned || 146 | conf.upgrade < 0 && !confirm("Is Secure Boot enabled on your USB armory?"): 147 | upgradeFirmware(unsigned) 148 | return 149 | case conf.upgrade == signedByFSecure || 150 | conf.upgrade < 0 && confirm("Is Secure Boot enabled on your USB armory using OEM signing keys?"): 151 | upgradeFirmware(signedByFSecure) 152 | case conf.upgrade == signedByUser || 153 | confirm("Is Secure Boot enabled on your USB armory using your own signing keys?"): 154 | checkHABArguments() 155 | upgradeFirmware(signedByUser) 156 | default: 157 | log.Fatal("Goodbye") 158 | } 159 | } 160 | 161 | func ota(assets *releaseAssets) { 162 | log.Printf("\nWait for the USB armory blue LED to blink to indicate pairing mode.") 163 | log.Printf("\nAn Armory drive should appear on your system.") 164 | 165 | if conf.install { 166 | log.Printf("\nDo *not* pair with mobile application at this time.") 167 | } 168 | 169 | mountPoint := prompt("Please specify the path of the mounted Armory drive") 170 | 171 | log.Printf("\nCreating firmware update archive.") 172 | 173 | otaFile := new(bytes.Buffer) 174 | w := zip.NewWriter(otaFile) 175 | 176 | var files = []struct { 177 | Name string 178 | Body []byte 179 | }{ 180 | {imxPath, assets.imx}, 181 | {csfPath, assets.csf}, 182 | {logPath, assets.log}, 183 | } 184 | 185 | for _, file := range files { 186 | f, err := w.Create(file.Name) 187 | 188 | if err != nil { 189 | log.Fatal(err) 190 | } 191 | 192 | if _, err = f.Write(file.Body); err != nil { 193 | log.Fatal(err) 194 | } 195 | } 196 | 197 | if err := w.Close(); err != nil { 198 | log.Fatal(err) 199 | } 200 | 201 | log.Printf("Copying firmware to USB armory in pairing mode at %s", mountPoint) 202 | 203 | if err := os.WriteFile(path.Join(mountPoint, otaPath), otaFile.Bytes(), 0600); err != nil { 204 | log.Fatal(err) 205 | } 206 | log.Printf("\nCopied %d bytes to %s", otaFile.Len(), path.Join(mountPoint, otaPath)) 207 | 208 | log.Printf("\n1. Please eject the drive mounted at %s to flash the firmware.", mountPoint) 209 | log.Printf("2. Wait for the white LED to turn on and then off for the update to complete.") 210 | log.Printf("3. Once the update is complete unplug the USB armory and set eMMC boot mode as explained at:") 211 | log.Printf(" https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)") 212 | 213 | log.Printf("\nAfter doing so you can use your new Armory Drive installation, following this tutorial:") 214 | log.Printf(" https://github.com/usbarmory/armory-drive/wiki/Tutorial") 215 | } 216 | 217 | func installFirmware(mode Mode) { 218 | var imx []byte 219 | 220 | assets, err := downloadRelease(conf.release) 221 | 222 | if err != nil { 223 | log.Fatalf("Download error, %v", err) 224 | } 225 | 226 | switch mode { 227 | case unsigned: 228 | log.Println(unsignedFirmwareWarning) 229 | 230 | if !confirm("Proceed?") { 231 | log.Fatal("Goodbye") 232 | } 233 | 234 | imx = assets.imx 235 | case signedByFSecure: 236 | log.Println(fscSignedFirmwareWarning) 237 | 238 | if !confirm("Proceed?") { 239 | log.Fatal("Goodbye") 240 | } 241 | 242 | imx = fixupSRKHash(assets.imx, assets.srk) 243 | case signedByUser: 244 | log.Println(userSignedFirmwareWarning) 245 | 246 | if !confirm("Proceed?") { 247 | log.Fatal("Goodbye") 248 | } 249 | 250 | if assets.srk, err = os.ReadFile(conf.tableHash); err != nil { 251 | log.Fatal(err) 252 | } 253 | 254 | if err = sign(assets); err != nil { 255 | log.Fatal(err) 256 | } 257 | 258 | imx = fixupSRKHash(assets.imx, assets.srk) 259 | default: 260 | log.Fatal("invalid installation mode") 261 | } 262 | 263 | if conf.recovery { 264 | if mode == signedByUser { 265 | // In case of recovery and user signature the SDP 266 | // signature is performed without fixup (which we don't 267 | // need anyway on recovery). 268 | imx = assets.imx 269 | } 270 | 271 | imx = append(imx, assets.sdp...) 272 | } 273 | 274 | log.Printf("\nFollow instructions at https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)") 275 | log.Printf("to set the target USB armory in SDP mode.") 276 | 277 | log.Printf("\nWaiting for target USB armory to be plugged to this computer in SDP mode.") 278 | 279 | if err = imxLoad(imx); err != nil { 280 | log.Fatal(err) 281 | } 282 | 283 | ota(assets) 284 | } 285 | 286 | func upgradeFirmware(mode Mode) { 287 | assets, err := downloadRelease(conf.release) 288 | 289 | if err != nil { 290 | log.Fatalf("Download error, %v", err) 291 | } 292 | 293 | if mode == signedByUser { 294 | if err = sign(assets); err != nil { 295 | log.Fatal(err) 296 | } 297 | } 298 | 299 | log.Printf("\nFollow instructions at https://github.com/usbarmory/armory-drive/wiki/Firmware-Updates") 300 | log.Printf("to set the Armory Drive firmware in pairing mode.") 301 | 302 | if !confirm("Confirm that target USB armory is plugged to this computer in pairing mode.") { 303 | log.Fatal("Goodbye") 304 | } 305 | 306 | ota(assets) 307 | } 308 | -------------------------------------------------------------------------------- /api/armory.proto: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) The armory-drive authors. All Rights Reserved. 4 | 5 | Use of this source code is governed by the license 6 | that can be found in the LICENSE file. 7 | 8 | */ 9 | 10 | syntax = "proto3"; 11 | 12 | option go_package = "./;api"; 13 | 14 | package main; 15 | 16 | /* 17 | 18 | Inter-exchange envelope 19 | 20 | The message envelope is used to carry authenticated messages, all messages must 21 | be signed with the sender EC private key. 22 | 23 | The receiver must verify each message using the peer EC public key (long term 24 | pre-session establishment, ephemeral post-session establishment). 25 | 26 | */ 27 | message Envelope { 28 | bytes Message = 1; 29 | Signature Signature = 2; 30 | } 31 | 32 | /* 33 | 34 | Signature format 35 | 36 | All messages before session key negotiation are ECDSA signed with long-term EC 37 | keys, all messages after session key negotiation are ECDSA signed with 38 | ephemeral EC keys. 39 | 40 | The signed data consists of the message SHA-256 digest. 41 | 42 | */ 43 | message Signature { 44 | bytes Data = 1; 45 | bytes R = 2; 46 | bytes S = 3; 47 | } 48 | 49 | /* 50 | 51 | Inter-exchange message 52 | 53 | Each message includes a timestamp reference to allow the receiver to mitigate 54 | replay attacks. 55 | 56 | During a session each party must ensure that the timestamp of the received 57 | message is older than the previous one, as well as not too far off in the 58 | future. 59 | 60 | To this end the UA sets its internal clock using the timestamp of the first 61 | received valid message, from power-up, from the MD. 62 | 63 | All payloads are encrypted, with the exception of PAIR and SESSION ones, using 64 | AES-256 and the negotiated symmetric session encryption key. A random IV is 65 | prepended to the encrypted payload. 66 | 67 | The payload format depends on the specific operation code. 68 | 69 | */ 70 | message Message { 71 | // timestamp is epoch in milliseconds 72 | int64 Timestamp = 1; 73 | bool Response = 2; 74 | ErrorCode Error = 3; 75 | OpCode OpCode = 4; 76 | bytes Payload = 5; 77 | } 78 | 79 | /* 80 | 81 | Generic response 82 | 83 | A generic response to be returned as message payload for all operation 84 | responses, or errors, without a specific format. 85 | 86 | */ 87 | message Response { 88 | repeated string Text = 1; 89 | } 90 | 91 | /* 92 | 93 | 1. Pairing sequence 94 | 95 | The pairing procedure is a pre-requisite for session establishment. 96 | 97 | Request, OpCode: PAIR, signed with MD long-term EC private key: 98 | MD > UA: KeyExchange{Key:, Nonce:} 99 | 100 | Response, OpCode: PAIR, signed with UA long-term EC private key: 101 | MD < UA: standard response 102 | 103 | This sequence can only be used in pairing mode, the device must be restarted 104 | to exit pairing mode. 105 | 106 | 2. Session negotiation sequence 107 | 108 | New sessions can be negotiated at any time by the MD, invalidating the 109 | previous one. Sessions are volatile and therefore not persistent across UA 110 | reboots. 111 | 112 | Request, OpCode: SESSION, signed with MD long-term EC private key: 113 | MD > UA: KeyExchange{Key:} 114 | 115 | Response, OpCode: SESSION, signed with UA long-term EC private key: 116 | MD < UA: KeyExchange{Key:, Nonce:} 117 | 118 | 3. Encrypted storage unlock 119 | 120 | This MD request instructs the UA to unlock its encrypted storage and 121 | expose it as USB Mass Storage. 122 | 123 | Request, OpCode: UNLOCK, signed with MD ephemeral EC private key, encrypted with session key 124 | MD > UA: KeyExchange{Key:} 125 | 126 | Response, OpCode: UNLOCK, signed with UA ephemeral EC private key, encrypted with session key 127 | MD < UA: standard response 128 | 129 | 4. Encrypted storage lock 130 | 131 | Request, OpCode: LOCK, signed with MD ephemeral EC private key, encrypted with session key 132 | MD > UA: empty payload 133 | 134 | Response, OpCode: LOCK, signed with UA ephemeral EC private key, encrypted with session key 135 | MD < UA: standard response 136 | 137 | */ 138 | message KeyExchange { 139 | bytes Key = 1; 140 | uint64 Nonce = 2; 141 | } 142 | 143 | /* 144 | 145 | Status information request 146 | 147 | Request, OpCode: STATUS, signed with MD ephemeral EC private key, encrypted with session key 148 | MD > UA: empty payload 149 | 150 | Response, OpCode: STATUS, signed with UA ephemeral EC private key, encrypted with session key 151 | MD < UA: Status{Version:, Capacity:, Locked:} 152 | 153 | Any error condition, not tied to a specific request, can be return as an error 154 | to the status request. 155 | 156 | */ 157 | message Status { 158 | string Version = 1; 159 | uint64 Capacity = 2; 160 | bool Locked = 3; 161 | Configuration Configuration = 4; 162 | } 163 | 164 | /* 165 | 166 | Configuration change request 167 | 168 | Any configuration change should be issued only while encrypted storage is 169 | locked, otherwise an error is returned. 170 | 171 | Request, OpCode: CONFIGURATION, signed with MD ephemeral EC private key, encrypted with session key 172 | MD > UA: Configuration{ } 173 | 174 | Response, OpCode: CONFIGURATION, signed with UA ephemeral EC private key, encrypted with session key 175 | MD < UA: Configuration{ } 176 | 177 | Any error condition, not tied to a specific request, can be return as an error 178 | to the status request. 179 | 180 | */ 181 | message Configuration { 182 | // Select encryption/decryption algorithm. 183 | Cipher Cipher = 1; 184 | } 185 | 186 | /* 187 | 188 | Pairing QR code format 189 | 190 | The pairing QR code embeds a binary blob which can be decoded with this message 191 | format. 192 | 193 | The message includes the BLE name, the pairing nonce, the UA long-term EC key 194 | and their signature created with the UA long-term EC key. 195 | 196 | */ 197 | message PairingQRCode { 198 | string BLEName = 1; 199 | uint64 Nonce = 2; 200 | bytes PubKey = 3; 201 | Signature Signature = 4; 202 | } 203 | 204 | /* 205 | 206 | Recovery QR code format 207 | 208 | The recovery QR code embeds a binary blob which can be decoded with this 209 | message format. 210 | 211 | The message includes the BLE name, recovery information and their signature 212 | created with the MD long-term EC key. 213 | 214 | The recovery information includes the UA long-term MC, the MD long-term EC key 215 | pair and the KEK. 216 | 217 | */ 218 | message RecoveryQRCode { 219 | string BLEName = 1; 220 | bool Deprecated = 2; 221 | bytes Recovery = 3; 222 | } 223 | 224 | message Recovery { 225 | bytes PubKey = 1; 226 | bytes MDPrivate = 2; 227 | bytes MDPublic = 3; 228 | bytes KEK = 4; 229 | } 230 | 231 | enum OpCode { 232 | // Plaintext messages 233 | 234 | NULL = 0; 235 | // Pairing sequence 236 | PAIR = 1; 237 | // Session negotiation sequence 238 | SESSION = 2; 239 | 240 | // Encrypted messages 241 | 242 | // Encrypted storage unlock 243 | UNLOCK = 3; 244 | // Encrypted storage lock 245 | LOCK = 4; 246 | // Status information request 247 | STATUS = 5; 248 | // Configuration change request 249 | CONFIGURATION = 6; 250 | 251 | // Pro version only 252 | LIST = 7; 253 | SET_VISIBILITY = 8; 254 | } 255 | 256 | /* 257 | 258 | Error codes 259 | 260 | The MD must always check the `ErrorCode` field in the response `Message` 261 | payload to determine if an error occurred. 262 | 263 | */ 264 | enum ErrorCode { 265 | NO_ERROR = 0; 266 | GENERIC_ERROR = 1; 267 | 268 | // INVALID_SESSION is returned if the UA is unable to authenticate or 269 | // decrypt messages from the MD. 270 | // 271 | // When this happens the MD should establish a new session. 272 | INVALID_SESSION = 2; 273 | 274 | // SESSION_KEY_NEGOTIATION_FAILED is returned if the MD 275 | // fails to negotiate a new session key with the paired USB armory. 276 | // 277 | // This happens at long-term keys mismatch between MD and UA. 278 | // 279 | // The MD should advise the user to: 280 | // 281 | // 1. Try rebooting the UA. 282 | // 2. If the problem persists reset MD with recovery data. 283 | // 3. If the problem persists, or recovery data is not available, 284 | // perform a full reset of MD+UA (this results in data loss). 285 | SESSION_KEY_NEGOTIATION_FAILED = 4; 286 | 287 | // PAIRING_KEY_NEGOTIATION_FAILED is returned if the MD fails to 288 | // negotiate the long-term (pairing) keys. 289 | // 290 | // When this happens the MD should instruct the user to unplug and 291 | // reinsert the USB armory and restart the pairing procedure. 292 | PAIRING_KEY_NEGOTIATION_FAILED = 5; 293 | 294 | // UNLOCK_FAILED is returned if the UA fails to unlock encrypted 295 | // storage with the KEK received from the MD. 296 | // 297 | // When this happens the MD should suggest the user that the inserted 298 | // microSD might not be matching the UA it has been inserted to. 299 | UNLOCK_FAILED = 6; 300 | 301 | // INVALID_MESSAGE is returned by the UA when received protobuf 302 | // cannot be parsed or authenticated correctly. 303 | INVALID_MESSAGE = 7; 304 | } 305 | 306 | enum Cipher { 307 | // Ciphers are list from most performant to least performant one. 308 | 309 | // AES-128 CBC mode (hardware accelerated) with plain IVs 310 | AES128_CBC_PLAIN = 0; 311 | // AES-128 CBC mode (hardware accelerated) with ESSIV 312 | AES128_CBC_ESSIV = 1; 313 | // AES-128 XTS mode (CPU bound) with plain IVs 314 | AES128_XTS_PLAIN = 2; 315 | // AES-256 XTS mode (CPU bound) with plain IVs 316 | AES256_XTS_PLAIN = 3; 317 | 318 | NONE = 255; 319 | } 320 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 2 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 3 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 4 | github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 5 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 6 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 8 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/google/go-github/v34 v34.0.0 h1:/siYFImY8KwGc5QD1gaPf+f8QX6tLwxNIco2RkYxoFA= 10 | github.com/google/go-github/v34 v34.0.0/go.mod h1:w/2qlrXUfty+lbyO6tatnzIw97v1CM+/jZcwXMDiPQQ= 11 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 12 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 13 | github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff h1:bFJ74ac7ZK/jyislqiWdzrnENesFt43sNEBRh1xk/+g= 14 | github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU= 15 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= 16 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= 17 | github.com/smallstep/pkcs7 v0.0.0-20240911091500-b1cae6277023 h1:klMnoL/Mrw9MJaAZdGUuEAKSskSoy14KIUpRwGOd4Vo= 18 | github.com/smallstep/pkcs7 v0.0.0-20240911091500-b1cae6277023/go.mod h1:CM5KrX7rxWgwDdMj9yef/pJB2OPgy/56z4IEx2UIbpc= 19 | github.com/transparency-dev/formats v0.0.0-20230914071414-5732692f1e50/go.mod h1:J2NdDb6IhKIvF6MwCvKikz9/QStRylEtS2mv+En+jBg= 20 | github.com/transparency-dev/formats v0.0.0-20250210090712-477e999072fe h1:gs30NcOGphgMkANo/NFf88fNmCaeckcsyCizB1bJ1EM= 21 | github.com/transparency-dev/formats v0.0.0-20250210090712-477e999072fe/go.mod h1:L3gsp3Jy9rTEh6wefvBYShFxQxA9/NUJibOamrudjeA= 22 | github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= 23 | github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= 24 | github.com/transparency-dev/serverless-log v0.0.0-20230928095427-7971d931e8f5/go.mod h1:FWvVqFb4YXC41AzWnwZ5O11kWNtWoZ5jBMbfgHd9zH4= 25 | github.com/transparency-dev/serverless-log v0.0.0-20250211100151-ced5625f6e2e h1:Dfnb3aQqYOziO+5rRBWNMM80wcQgG5KFpVvst7Jn8lM= 26 | github.com/transparency-dev/serverless-log v0.0.0-20250211100151-ced5625f6e2e/go.mod h1:bjIPI/IJYSzisXq9bUXsUK5zdZDcf/MspslIMlUr8Bs= 27 | github.com/usbarmory/armory-boot v0.0.0-20250207173330-3cfbfb527102 h1:5KSHfi54+D65yJZbidAa10cX3j5u+q0oV60KhzfKkuk= 28 | github.com/usbarmory/armory-boot v0.0.0-20250207173330-3cfbfb527102/go.mod h1:sImXzIRRKl04CGGrOGFWH2a89G6/Bjxf62L08mg4bdU= 29 | github.com/usbarmory/armory-drive-log v0.0.0-20231011074148-e10fca283f67 h1:2vHGZ6lHZCYLl8fjYifrsoS5Kl551dBi9z/cgRWK0PM= 30 | github.com/usbarmory/armory-drive-log v0.0.0-20231011074148-e10fca283f67/go.mod h1:gkv9jqec3pf7MBk1BYeo0VcR+/6ntzFPeVpNm9HenKk= 31 | github.com/usbarmory/crucible v0.0.0-20250123114515-fa91eabf75f0 h1:X6UaAe4EVDwu07Lq1jK2LU+kpe23/fjQZTDXkLd8ACI= 32 | github.com/usbarmory/crucible v0.0.0-20250123114515-fa91eabf75f0/go.mod h1:l+Z8AwERB0qaITbQ+82/Zwg+Fi0WRXOSiVhULMJY/c0= 33 | github.com/usbarmory/hid v0.0.0-20210318233634-85ced88a1ffe h1:WWop+y0QNxBEZAKu9xlNiZWgo5+y5VkzlyngVMViBlE= 34 | github.com/usbarmory/hid v0.0.0-20210318233634-85ced88a1ffe/go.mod h1:8ScVLh9aty8MLtypACxU7JdF5u2y2rEfhx3zJPS/tPc= 35 | github.com/usbarmory/tamago v0.0.0-20250212123402-5facf762488d h1:V3R6r4ofDFTZAlz2iGpeSXJ97OMZcBhdS0bskfPbwxw= 36 | github.com/usbarmory/tamago v0.0.0-20250212123402-5facf762488d/go.mod h1:NL88q9ZsIPYFzXaosAeKgu1Kr5i1k4Rau3wnbNBL5bY= 37 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 40 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 41 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 42 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 43 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 44 | golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= 45 | golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 46 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 47 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 48 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 49 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 50 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 51 | golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= 52 | golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 53 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 54 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 55 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 56 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 57 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 58 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 59 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 60 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 61 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 62 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 63 | golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= 64 | golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 65 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 66 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 67 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 68 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 69 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 70 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 71 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 72 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 73 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 74 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 75 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 77 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 78 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 79 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 83 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 84 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 85 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 86 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 87 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 88 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 89 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 90 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 91 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 92 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 93 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 94 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 95 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 96 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 97 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 98 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 99 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 100 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 101 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 102 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 103 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 104 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 105 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 106 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 107 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 108 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 109 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 110 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 111 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 112 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 113 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 114 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 115 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 116 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 117 | k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 118 | -------------------------------------------------------------------------------- /internal/ums/scsi.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package ums 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "fmt" 12 | "sync" 13 | 14 | "github.com/usbarmory/armory-drive/internal/ota" 15 | 16 | "github.com/usbarmory/tamago/dma" 17 | "github.com/usbarmory/tamago/soc/nxp/usb" 18 | 19 | "golang.org/x/sync/errgroup" 20 | ) 21 | 22 | const ( 23 | // p65, 3. Direct Access Block commands (SPC-5 and SBC-4), SCSI Commands Reference Manual, Rev. J 24 | TEST_UNIT_READY = 0x00 25 | REQUEST_SENSE = 0x03 26 | INQUIRY = 0x12 27 | MODE_SENSE_6 = 0x1a 28 | START_STOP_UNIT = 0x1b 29 | MODE_SENSE_10 = 0x5a 30 | READ_CAPACITY_10 = 0x25 31 | READ_10 = 0x28 32 | WRITE_10 = 0x2a 33 | REPORT_LUNS = 0xa0 34 | 35 | // service actions 36 | SERVICE_ACTION = 0x9e 37 | READ_CAPACITY_16 = 0x10 38 | 39 | // 04-349r1 SPC-3 MMC-5 Merge PREVENT ALLOW MEDIUM REMOVAL commands 40 | PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e 41 | 42 | // p33, 4.10, USB Mass Storage Class – UFI Command Specification Rev. 1.0 43 | READ_FORMAT_CAPACITIES = 0x23 44 | 45 | // To speed up FDE it is beneficial to report a larger block size, to 46 | // reduce the number of encryption/decryption iterations caused by 47 | // per-block IV computation. 48 | BLOCK_SIZE_MULTIPLIER = 8 49 | 50 | // These parameters control how many blocks are read/written before 51 | // being offloaded to DCP for decryption/encryption in a goroutine. 52 | // 53 | // Values should be tuned for optimum pipeline performance, to minimize 54 | // overhead while the DCP works in parallel with the next batch of 55 | // uSDHC read/write. 56 | READ_PIPELINE_SIZE = 12 57 | WRITE_PIPELINE_SIZE = 20 58 | ) 59 | 60 | type writeOp struct { 61 | csw *usb.CSW 62 | lba int 63 | blocks int 64 | size int 65 | addr uint 66 | buf []byte 67 | } 68 | 69 | // p94, 3.6.2 Standard INQUIRY data, SCSI Commands Reference Manual, Rev. J 70 | func (d *Drive) inquiry(length int) (data []byte) { 71 | data = make([]byte, 5) 72 | 73 | // device connected, direct access block device 74 | data[0] = 0x00 75 | 76 | if !d.Ready { 77 | // device not connected 78 | data[0] |= (0b001 << 5) 79 | } 80 | 81 | // Removable Media 82 | data[1] = 0x80 83 | // SPC-3 compliant 84 | data[2] = 0x05 85 | // response data format (only 2 is allowed) 86 | data[3] = 0x02 87 | // additional length 88 | data[4] = byte(length - 5) 89 | 90 | // unused or obsolete flags 91 | data = append(data, make([]byte, 3)...) 92 | 93 | data = append(data, []byte(VendorID)...) 94 | data = append(data, []byte(ProductID)...) 95 | data = append(data, []byte(ProductRevision)...) 96 | 97 | if length > len(data) { 98 | // pad up to requested transfer length 99 | data = append(data, make([]byte, length-len(data))...) 100 | } else { 101 | data = data[0:length] 102 | } 103 | 104 | return 105 | } 106 | 107 | // p56, 2.4.1.2 Fixed format sense data, SCSI Commands Reference Manual, Rev. J 108 | func (d *Drive) sense(length int) (data []byte, err error) { 109 | data = make([]byte, 18) 110 | 111 | if !d.Ready { 112 | // sense key: NOT READY 113 | data[2] = 0x02 114 | // additional sense code: MEDIUM NOT PRESENT 115 | data[12] = 0x3a 116 | } 117 | 118 | // error code 119 | data[0] = 0x70 120 | // additional sense length 121 | data[7] = byte(len(data) - 1 - 7) 122 | 123 | if length < len(data) { 124 | return nil, fmt.Errorf("unsupported REQUEST_SENSE transfer length %d > %d", length, len(data)) 125 | } 126 | 127 | return 128 | } 129 | 130 | // p111, 3.11 MODE SENSE(6) command, SCSI Commands Reference Manual, Rev. J 131 | func modeSense(length int) (data []byte, err error) { 132 | // Unsupported, an empty response is returned on all requests. 133 | data = make([]byte, length) 134 | 135 | // p378, 5.3.3 Mode parameter header formats, SCSI Commands Reference Manual, Rev. J 136 | data[0] = byte(length) 137 | 138 | return 139 | } 140 | 141 | // p179, 3.33 REPORT LUNS command, SCSI Commands Reference Manual, Rev. J 142 | func reportLUNs(length int) (data []byte, err error) { 143 | buf := new(bytes.Buffer) 144 | luns := 1 145 | 146 | binary.Write(buf, binary.BigEndian, uint32(luns*8)) 147 | buf.Write(make([]byte, 4)) 148 | 149 | for lun := 0; lun < luns; lun++ { 150 | // The information conforms to the Logical Unit Address Method defined 151 | // in SCC-2, and supports only First Level addressing (for each LUN, 152 | // only the second byte is used and contains the assigned LUN)." 153 | buf.WriteByte(0x00) 154 | binary.Write(buf, binary.BigEndian, uint8(lun)) 155 | buf.Write(make([]byte, 6)) 156 | } 157 | 158 | data = buf.Bytes() 159 | 160 | if length < buf.Len() { 161 | data = data[0:length] 162 | } 163 | 164 | return 165 | } 166 | 167 | // p155, 3.22 READ CAPACITY (10) command, SCSI Commands Reference Manual, Rev. J 168 | func (d *Drive) readCapacity10() (data []byte, err error) { 169 | info := d.card.Info() 170 | 171 | if info.Blocks <= 0 { 172 | return nil, fmt.Errorf("invalid block count %d", info.Blocks) 173 | } 174 | 175 | blocks := uint32(info.Blocks / d.Mult) 176 | blockSize := uint32(info.BlockSize * d.Mult) 177 | 178 | buf := new(bytes.Buffer) 179 | 180 | binary.Write(buf, binary.BigEndian, blocks-1) 181 | binary.Write(buf, binary.BigEndian, blockSize) 182 | 183 | return buf.Bytes(), nil 184 | } 185 | 186 | // p157, 3.23 READ CAPACITY (16) command, SCSI Commands Reference Manual, Rev. J 187 | func (d *Drive) readCapacity16(length int) (data []byte, err error) { 188 | info := d.card.Info() 189 | buf := new(bytes.Buffer) 190 | 191 | if info.Blocks <= 0 { 192 | return nil, fmt.Errorf("invalid block count %d", info.Blocks) 193 | } 194 | 195 | binary.Write(buf, binary.BigEndian, uint64(info.Blocks)-1) 196 | binary.Write(buf, binary.BigEndian, uint64(info.BlockSize)) 197 | buf.Grow(32 - buf.Len()) 198 | 199 | data = buf.Bytes() 200 | 201 | if length < buf.Len() { 202 | data = data[0:length] 203 | } 204 | 205 | return 206 | } 207 | 208 | // p33, 4.10, USB Mass Storage Class – UFI Command Specification Rev. 1.0 209 | func (d *Drive) readFormatCapacities() (data []byte, err error) { 210 | info := d.card.Info() 211 | 212 | blocks := uint32(info.Blocks / d.Mult) 213 | blockSize := uint32(info.BlockSize * d.Mult) 214 | 215 | buf := new(bytes.Buffer) 216 | 217 | // capacity list length 218 | binary.Write(buf, binary.BigEndian, uint32(8)) 219 | // number of blocks 220 | binary.Write(buf, binary.BigEndian, blocks) 221 | // descriptor code: formatted media | block length 222 | binary.Write(buf, binary.BigEndian, uint32(0b10<<24|blockSize&0xffffff)) 223 | 224 | return buf.Bytes(), nil 225 | } 226 | 227 | func (d *Drive) read(lba int, blocks int) (err error) { 228 | batch := READ_PIPELINE_SIZE 229 | info := d.card.Info() 230 | 231 | blockSize := info.BlockSize * d.Mult 232 | 233 | if !d.Ready { 234 | d.send <- make([]byte, blocks*blockSize) 235 | return 236 | } 237 | 238 | addr, buf := dma.Reserve(blocks*blockSize, usb.DTD_PAGE_SIZE) 239 | 240 | wg := &sync.WaitGroup{} 241 | 242 | for i := 0; i < blocks; i += batch { 243 | if i+batch > blocks { 244 | batch = blocks - i 245 | } 246 | 247 | start := i * blockSize 248 | end := start + blockSize*batch 249 | slice := buf[start:end] 250 | 251 | err = d.card.ReadBlocks((lba+i)*d.Mult, slice) 252 | 253 | if err != nil { 254 | dma.Release(addr) 255 | return 256 | } 257 | 258 | if d.Cipher { 259 | wg.Add(1) 260 | go d.Keyring.Cipher(slice, lba+i, batch, blockSize, false, wg) 261 | } 262 | } 263 | 264 | wg.Wait() 265 | d.send <- buf 266 | 267 | return 268 | } 269 | 270 | func (d *Drive) write(lba int, buf []byte) (err error) { 271 | batch := WRITE_PIPELINE_SIZE 272 | info := d.card.Info() 273 | 274 | blockSize := info.BlockSize * d.Mult 275 | blocks := len(buf) / blockSize 276 | 277 | if !d.Ready { 278 | return 279 | } 280 | 281 | eg := &errgroup.Group{} 282 | 283 | for i := 0; i < blocks; i += batch { 284 | if i+batch > blocks { 285 | batch = blocks - i 286 | } 287 | 288 | start := i * blockSize 289 | end := start + blockSize*batch 290 | slice := buf[start:end] 291 | 292 | if d.Cipher { 293 | d.Keyring.Cipher(slice, lba+i, batch, blockSize, true, nil) 294 | } 295 | 296 | sliceBlock := (lba + i) * d.Mult 297 | 298 | eg.Go(func() error { 299 | return d.card.WriteBlocks(sliceBlock, slice) 300 | }) 301 | } 302 | 303 | return eg.Wait() 304 | } 305 | 306 | func (d *Drive) handleCDB(cmd [16]byte, cbw *usb.CBW) (csw *usb.CSW, data []byte, err error) { 307 | op := cmd[0] 308 | length := int(cbw.DataTransferLength) 309 | 310 | // p8, 3.3 Host/Device Packet Transfer Order, USB Mass Storage Class 1.0 311 | csw = &usb.CSW{Tag: cbw.Tag} 312 | csw.SetDefaults() 313 | 314 | lun := int(cbw.LUN) 315 | 316 | if int(lun+1) > 1 { 317 | err = fmt.Errorf("invalid LUN") 318 | return 319 | } 320 | 321 | switch op { 322 | case TEST_UNIT_READY: 323 | if !d.Ready { 324 | csw.Status = usb.CSW_STATUS_COMMAND_FAILED 325 | } 326 | case INQUIRY: 327 | data = d.inquiry(length) 328 | case REQUEST_SENSE: 329 | data, err = d.sense(length) 330 | case START_STOP_UNIT: 331 | start := (cmd[4]&1 == 1) 332 | 333 | if !d.Ready && start { 334 | // locked drive cannot be started 335 | csw.Status = usb.CSW_STATUS_COMMAND_FAILED 336 | // lock drive at eject 337 | } else if d.Ready && !start && d.Cipher { 338 | d.Lock() 339 | } else { 340 | d.Ready = start 341 | } 342 | 343 | if !d.Ready && !d.Cipher { 344 | d.PairingComplete <- true 345 | 346 | go func() { 347 | card := d.card.(*PairingDisk) 348 | ota.Check(card.Data, pairingDiskPath, pairingDiskOffset, d.Keyring) 349 | }() 350 | } 351 | case MODE_SENSE_6, MODE_SENSE_10: 352 | data, err = modeSense(length) 353 | case REPORT_LUNS: 354 | data, err = reportLUNs(length) 355 | case READ_FORMAT_CAPACITIES: 356 | data, err = d.readFormatCapacities() 357 | case READ_CAPACITY_10: 358 | data, err = d.readCapacity10() 359 | case READ_10, WRITE_10: 360 | if !d.Ready { 361 | csw.Status = usb.CSW_STATUS_COMMAND_FAILED 362 | } 363 | 364 | lba := int(binary.BigEndian.Uint32(cmd[2:])) 365 | blocks := int(binary.BigEndian.Uint16(cmd[7:])) 366 | 367 | if op == READ_10 { 368 | err = d.read(lba, blocks) 369 | } else { 370 | blockSize := d.card.Info().BlockSize * d.Mult 371 | size := int(cbw.DataTransferLength) 372 | 373 | if blockSize*blocks != size { 374 | err = fmt.Errorf("unexpected %d blocks write transfer length (%d)", blocks, size) 375 | } 376 | 377 | d.dataPending = &writeOp{ 378 | csw: csw, 379 | lba: lba, 380 | blocks: blocks, 381 | size: size, 382 | } 383 | 384 | csw = nil 385 | } 386 | case SERVICE_ACTION: 387 | switch cmd[1] { 388 | case READ_CAPACITY_16: 389 | data, err = d.readCapacity16(length) 390 | default: 391 | err = fmt.Errorf("unsupported service action %#x %+v", op, cbw) 392 | } 393 | case PREVENT_ALLOW_MEDIUM_REMOVAL: 394 | // ignored events 395 | default: 396 | err = fmt.Errorf("unsupported CDB Operation Code %#x %+v", op, cbw) 397 | } 398 | 399 | return 400 | } 401 | 402 | func (d *Drive) handleWrite() (err error) { 403 | if len(d.dataPending.buf) != d.dataPending.size { 404 | return fmt.Errorf("len(buf) != size (%d != %d)", len(d.dataPending.buf), d.dataPending.size) 405 | } 406 | 407 | return d.write(d.dataPending.lba, d.dataPending.buf) 408 | } 409 | -------------------------------------------------------------------------------- /internal/crypto/cipher.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) The armory-drive authors. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package crypto 7 | 8 | import ( 9 | "bytes" 10 | "crypto/aes" 11 | "crypto/cipher" 12 | "crypto/ecdsa" 13 | "crypto/hmac" 14 | "crypto/rand" 15 | "crypto/sha256" 16 | "encoding/binary" 17 | "errors" 18 | "io" 19 | "log" 20 | "math/big" 21 | "sync" 22 | 23 | "github.com/usbarmory/armory-drive/api" 24 | 25 | "github.com/usbarmory/tamago/dma" 26 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 27 | 28 | "golang.org/x/crypto/pbkdf2" 29 | "golang.org/x/crypto/xts" 30 | ) 31 | 32 | const ( 33 | // flag to select DCP for on supported block ciphers 34 | DCP = true 35 | // flag to allow DCP, when flagged, for XTS computation 36 | DCPXTS = false 37 | // flag to select DCP for ESSIV computation 38 | DCPIV = false 39 | 40 | // key derivation iteration count 41 | PBKDF2_ITER = 4096 42 | 43 | // DEK key derivation diversifier 44 | DEK_DIV = "floppyDEK" 45 | // ESSIV key derivation diversifier 46 | ESSIV_DIV = "floppyESSIV" 47 | // SNVS key derivation diversifier 48 | SNVS_DIV = "floppySNVS" 49 | ) 50 | 51 | // flag to select ESSIV on AES-128 CBC ciphers 52 | var ESSIV = false 53 | 54 | // IV buffer 55 | var iv = make([]byte, aes.BlockSize) 56 | 57 | // IV encryption IV for ESSIV computation and IV reset 58 | var zero = make([]byte, aes.BlockSize) 59 | 60 | func (k *Keyring) deriveKey(diversifier []byte, index int, export bool) (key []byte, err error) { 61 | if index == BLOCK_KEY { 62 | var armoryLongterm []byte 63 | 64 | // We want to diversify block cipher key derivation across different 65 | // pairings, to do so we combine the diversifier with the UA long term 66 | // public key, which is recreated at each pairing. 67 | armoryLongterm, err = k.Export(UA_LONGTERM_KEY, false) 68 | 69 | if err != nil { 70 | return 71 | } 72 | 73 | diversifier = append(diversifier, armoryLongterm...) 74 | 75 | // We re-use the ESSIV "salt" (unfortunate name collision here, it's 76 | // not actually the PBKDF2 salt, or a salt at all) as it is random and 77 | // unknown, the PBKDF2 salt is random but known (as it should be). 78 | diversifier = pbkdf2.Key(k.salt, diversifier, PBKDF2_ITER, aes.BlockSize, sha256.New) 79 | } 80 | 81 | // It is advised to use only deterministic input data for key 82 | // derivation, therefore we use the empty allocated IV before it being 83 | // filled. 84 | iv := make([]byte, aes.BlockSize) 85 | 86 | if export { 87 | key, err = imx6ul.DCP.DeriveKey(diversifier, iv, -1) 88 | } else { 89 | // Move the derived key directly to the internal DCP key RAM 90 | // slot, without ever exposing it to external RAM or the Go 91 | // runtime. 92 | _, err = imx6ul.DCP.DeriveKey(diversifier, iv, index) 93 | } 94 | 95 | if err != nil { 96 | return 97 | } 98 | 99 | if export { 100 | err = imx6ul.DCP.SetKey(index, key) 101 | } 102 | 103 | return 104 | } 105 | 106 | func (k *Keyring) SetCipher(kind api.Cipher, diversifier []byte) (err error) { 107 | var dek []byte 108 | 109 | // We need to zero out the IV buffer when switching away from ESSIV, as 110 | // it fills its entirety. 111 | ESSIV = false 112 | copy(iv, zero) 113 | 114 | switch kind { 115 | case api.Cipher_AES128_CBC_PLAIN, api.Cipher_AES128_CBC_ESSIV: 116 | if kind == api.Cipher_AES128_CBC_ESSIV { 117 | ESSIV = true 118 | } 119 | 120 | if DCP { 121 | if _, err = k.deriveKey(diversifier, BLOCK_KEY, false); err != nil { 122 | return 123 | } 124 | 125 | k.Cipher = k.cipherDCP 126 | } else { 127 | if dek, err = k.deriveKey(diversifier, BLOCK_KEY, true); err != nil { 128 | return 129 | } 130 | 131 | if k.cb, err = aes.NewCipher(dek); err != nil { 132 | return 133 | } 134 | 135 | k.Cipher = k.cipherAES 136 | } 137 | 138 | if ESSIV && !DCPIV { 139 | k.cbiv, err = aes.NewCipher(k.salt) 140 | return 141 | } 142 | case api.Cipher_AES128_XTS_PLAIN, api.Cipher_AES256_XTS_PLAIN: 143 | var size int 144 | cbxts := aes.NewCipher 145 | 146 | if kind == api.Cipher_AES256_XTS_PLAIN { 147 | size = 32 * 2 148 | } else { 149 | size = 16 * 2 150 | 151 | if DCPXTS && DCP { 152 | cbxts = newDCPCipher 153 | } 154 | } 155 | 156 | dek, err = k.deriveKey(diversifier, BLOCK_KEY, true) 157 | 158 | if err != nil { 159 | return 160 | } 161 | 162 | dk := pbkdf2.Key(dek, k.salt, PBKDF2_ITER, size, sha256.New) 163 | k.cbxts, err = xts.NewCipher(cbxts, dk) 164 | 165 | if err != nil { 166 | return 167 | } 168 | 169 | k.Cipher = k.cipherXTS 170 | case api.Cipher_NONE: 171 | k.deriveKey(zero, BLOCK_KEY, false) 172 | k.cbiv = nil 173 | k.cb = nil 174 | k.cbxts = nil 175 | k.Cipher = nil 176 | default: 177 | err = errors.New("unsupported cipher") 178 | } 179 | 180 | return 181 | } 182 | 183 | // equivalent to aes-cbc-essiv:md5 184 | func (k *Keyring) essiv(buf []byte, iv []byte) (err error) { 185 | if DCPIV { 186 | err = imx6ul.DCP.Encrypt(buf, ESSIV_KEY, iv) 187 | } else { 188 | encrypter := cipher.NewCBCEncrypter(k.cbiv, iv) 189 | encrypter.CryptBlocks(buf, buf) 190 | } 191 | 192 | return 193 | } 194 | 195 | // equivalent to aes-cbc-plain (hw) 196 | func (k *Keyring) cipherDCP(buf []byte, lba int, blocks int, blockSize int, enc bool, wg *sync.WaitGroup) { 197 | addr, ivs := dma.Reserve(blocks*aes.BlockSize, 4) 198 | defer dma.Release(addr) 199 | 200 | for i := 0; i < blocks; i++ { 201 | off := i * aes.BlockSize 202 | 203 | // fill unused 64-bits as reserved buffers are not initialized 204 | binary.BigEndian.PutUint64(ivs[off+8:], 0) 205 | binary.BigEndian.PutUint64(ivs[off:], uint64(lba+i)) 206 | 207 | if ESSIV { 208 | if err := k.essiv(ivs[off:], zero); err != nil { 209 | log.Fatal(err) 210 | } 211 | } 212 | } 213 | 214 | err := imx6ul.DCP.CipherChain(buf, ivs, blocks, blockSize, BLOCK_KEY, enc) 215 | 216 | if err != nil { 217 | log.Fatal(err) 218 | } 219 | 220 | if wg != nil { 221 | wg.Done() 222 | } 223 | } 224 | 225 | // equivalent to aes-cbc-plain (sw) 226 | func (k *Keyring) cipherAES(buf []byte, lba int, blocks int, blockSize int, enc bool, wg *sync.WaitGroup) { 227 | var mode cipher.BlockMode 228 | 229 | for i := 0; i < blocks; i++ { 230 | start := i * blockSize 231 | end := start + blockSize 232 | slice := buf[start:end] 233 | 234 | binary.BigEndian.PutUint64(iv, uint64(lba+i)) 235 | 236 | if ESSIV { 237 | if err := k.essiv(iv, zero); err != nil { 238 | log.Fatal(err) 239 | } 240 | } 241 | 242 | if enc { 243 | mode = cipher.NewCBCEncrypter(k.cb, iv) 244 | } else { 245 | mode = cipher.NewCBCDecrypter(k.cb, iv) 246 | } 247 | 248 | mode.CryptBlocks(slice, slice) 249 | } 250 | 251 | if wg != nil { 252 | wg.Done() 253 | } 254 | } 255 | 256 | // equivalent to aes-xts-plain64 (sw) 257 | func (k *Keyring) cipherXTS(buf []byte, lba int, blocks int, blockSize int, enc bool, wg *sync.WaitGroup) { 258 | for i := 0; i < blocks; i++ { 259 | start := i * blockSize 260 | end := start + blockSize 261 | slice := buf[start:end] 262 | 263 | if enc { 264 | k.cbxts.Encrypt(slice, slice, uint64(lba+i)) 265 | } else { 266 | k.cbxts.Decrypt(slice, slice, uint64(lba+i)) 267 | } 268 | } 269 | 270 | if wg != nil { 271 | wg.Done() 272 | } 273 | } 274 | 275 | func (k *Keyring) encryptSNVS(input []byte, length int) (output []byte, err error) { 276 | block, err := aes.NewCipher(k.snvs) 277 | 278 | if err != nil { 279 | return 280 | } 281 | 282 | iv := Rand(aes.BlockSize) 283 | // pad to block size, accounting for IV and HMAC length 284 | length -= len(iv) + sha256.Size 285 | 286 | if len(input) < length { 287 | pad := make([]byte, length-len(input)) 288 | input = append(input, pad...) 289 | } 290 | 291 | output = iv 292 | 293 | mac := hmac.New(sha256.New, k.snvs) 294 | mac.Write(iv) 295 | 296 | stream := cipher.NewOFB(block, iv) 297 | output = append(output, make([]byte, len(input))...) 298 | 299 | stream.XORKeyStream(output[len(iv):], input) 300 | mac.Write(output[len(iv):]) 301 | 302 | output = append(output, mac.Sum(nil)...) 303 | 304 | return 305 | } 306 | 307 | func (k *Keyring) decryptSNVS(input []byte) (output []byte, err error) { 308 | if len(input) < aes.BlockSize { 309 | return nil, errors.New("invalid length for decrypt") 310 | } 311 | 312 | iv := input[0:aes.BlockSize] 313 | input = input[aes.BlockSize:] 314 | 315 | block, err := aes.NewCipher(k.snvs) 316 | 317 | if err != nil { 318 | return 319 | } 320 | 321 | mac := hmac.New(sha256.New, k.snvs) 322 | mac.Write(iv) 323 | 324 | if len(input) < mac.Size() { 325 | return nil, errors.New("invalid length for decrypt") 326 | } 327 | 328 | inputMac := input[len(input)-mac.Size():] 329 | mac.Write(input[0 : len(input)-mac.Size()]) 330 | 331 | if !hmac.Equal(inputMac, mac.Sum(nil)) { 332 | return nil, errors.New("invalid HMAC") 333 | } 334 | 335 | stream := cipher.NewOFB(block, iv) 336 | output = make([]byte, len(input)-mac.Size()) 337 | 338 | stream.XORKeyStream(output, input[0:len(input)-mac.Size()]) 339 | 340 | return 341 | } 342 | 343 | func (k *Keyring) EncryptOFB(plaintext []byte) (ciphertext []byte, err error) { 344 | block, err := aes.NewCipher(k.sessionKey) 345 | 346 | if err != nil { 347 | return 348 | } 349 | 350 | in := bytes.NewReader(plaintext) 351 | out := new(bytes.Buffer) 352 | 353 | iv := Rand(aes.BlockSize) 354 | stream := cipher.NewOFB(block, iv) 355 | reader := &cipher.StreamReader{S: stream, R: in} 356 | 357 | if _, err = io.Copy(out, reader); err != nil { 358 | return 359 | } 360 | 361 | ciphertext = iv 362 | ciphertext = append(ciphertext, out.Bytes()...) 363 | 364 | return 365 | } 366 | 367 | func (k *Keyring) DecryptOFB(ciphertext []byte) (plaintext []byte, err error) { 368 | if len(ciphertext) < aes.BlockSize { 369 | return nil, errors.New("invalid message") 370 | } 371 | 372 | block, err := aes.NewCipher(k.sessionKey) 373 | 374 | if err != nil { 375 | return 376 | } 377 | 378 | iv := ciphertext[0:aes.BlockSize] 379 | in := bytes.NewReader(ciphertext[aes.BlockSize:]) 380 | out := new(bytes.Buffer) 381 | 382 | stream := cipher.NewOFB(block, iv) 383 | writer := &cipher.StreamWriter{S: stream, W: out} 384 | 385 | if _, err = io.Copy(writer, in); err != nil { 386 | return 387 | } 388 | 389 | plaintext = out.Bytes() 390 | 391 | return 392 | } 393 | 394 | func (k *Keyring) SignECDSA(data []byte, ephemeral bool) (sig *api.Signature, err error) { 395 | var sigKey *ecdsa.PrivateKey 396 | 397 | if ephemeral { 398 | sigKey = k.armoryEphemeral 399 | } else { 400 | sigKey = k.ArmoryLongterm 401 | } 402 | 403 | h := sha256.New() 404 | h.Write(data) 405 | sum := h.Sum(nil) 406 | 407 | r, s, err := ecdsa.Sign(rand.Reader, sigKey, sum) 408 | 409 | if err != nil { 410 | return 411 | } 412 | 413 | sig = &api.Signature{ 414 | Data: sum, 415 | R: r.Bytes(), 416 | S: s.Bytes(), 417 | } 418 | 419 | return 420 | } 421 | 422 | func (k *Keyring) VerifyECDSA(data []byte, sig *api.Signature, ephemeral bool) (err error) { 423 | var verKey *ecdsa.PublicKey 424 | 425 | if ephemeral { 426 | verKey = k.mobileEphemeral 427 | } else { 428 | verKey = k.MobileLongterm 429 | } 430 | 431 | h := sha256.New() 432 | h.Write(data) 433 | 434 | if !bytes.Equal(sig.Data, h.Sum(nil)) { 435 | return errors.New("signature error, data mismatch") 436 | } 437 | 438 | R := big.NewInt(0) 439 | S := big.NewInt(0) 440 | 441 | R.SetBytes(sig.R) 442 | S.SetBytes(sig.S) 443 | 444 | valid := ecdsa.Verify(verKey, sig.Data, R, S) 445 | 446 | if !valid { 447 | return errors.New("signature error, invalid") 448 | } 449 | 450 | return 451 | } 452 | 453 | func Rand(n int) []byte { 454 | buf := make([]byte, n) 455 | 456 | if _, err := rand.Read(buf); err != nil { 457 | log.Fatal(err) 458 | } 459 | 460 | return buf 461 | } 462 | --------------------------------------------------------------------------------