Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Backports:SLE-15-SP4:FactoryCandidates
tpm-fido
tpm-fido-20230621.5f8828b.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File tpm-fido-20230621.5f8828b.obscpio of Package tpm-fido
07070100000000000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002200000000tpm-fido-20230621.5f8828b/.github07070100000001000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002C00000000tpm-fido-20230621.5f8828b/.github/workflows07070100000002000081A40000000000000000000000016493935C000002F4000000000000000000000000000000000000003300000000tpm-fido-20230621.5f8828b/.github/workflows/go.ymlname: Go on: [push, pull_request] jobs: build: name: Build/Test runs-on: ${{ matrix.os }} timeout-minutes: 3 strategy: matrix: go-version: [1.19.10, 1.20.5] os: [ubuntu-latest] steps: - name: Set up Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: Build run: go build -v ./... - name: Test run: go test -v ./... --timeout 60s - name: Cross test for i386 run: env GOOS=linux GOARCH=386 go test -v ./... --timeout 60s - name: Cross compile for arm (RPI) run: env GOOS=linux GOARCH=arm GOARM=5 go build -v ./... 07070100000003000081A40000000000000000000000016493935C00000438000000000000000000000000000000000000002200000000tpm-fido-20230621.5f8828b/LICENSEThe MIT License (MIT) Copyright (c) 2021 Peter Sanford Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 07070100000004000081A40000000000000000000000016493935C000008B3000000000000000000000000000000000000002400000000tpm-fido-20230621.5f8828b/Readme.md# tpm-fido tpm-fido is FIDO token implementation for Linux that protects the token keys by using your system's TPM. tpm-fido uses Linux's [uhid](https://github.com/psanford/uhid) facility to emulate a USB HID device so that it is properly detected by browsers. ## Implementation details tpm-fido uses the TPM 2.0 API. The overall design is as follows: On registration tpm-fido generates a new P256 primary key under the Owner hierarchy on the TPM. To ensure that the key is unique per site and registration, tpm-fido generates a random 20 byte seed for each registration. The primary key template is populated with unique values from a sha256 hkdf of the 20 byte random seed and the application parameter provided by the browser. A signing child key is then generated from that primary key. The key handle returned to the caller is a concatenation of the child key's public and private key handles and the 20 byte seed. On an authentication request, tpm-fido will attempt to load the primary key by initializing the hkdf in the same manner as above. It will then attempt to load the child key from the provided key handle. Any incorrect values or values created by a different TPM will fail to load. ## Status tpm-fido has been tested to work with Chrome and Firefox on Linux. ## Building ``` # in the root directory of tpm-fido run: go build ``` ## Running In order to run `tpm-fido` you will need permission to access `/dev/tpmrm0`. On Ubuntu and Arch, you can add your user to the `tss` group. Your user also needs permission to access `/dev/uhid` so that `tpm-fido` can appear to be a USB device. I use the following udev rule to set the appropriate `uhid` permissions: ``` KERNEL=="uhid", SUBSYSTEM=="misc", GROUP="SOME_UHID_GROUP_MY_USER_BELONGS_TO", MODE="0660" ``` To ensure the above udev rule gets triggered, I also add the `uhid` module to `/etc/modules-load.d/uhid.conf` so that it loads at boot. To run: ``` # as a user that has permission to read and write to /dev/tpmrm0: ./tpm-fido ``` Note: do not run with `sudo` or as root, as it will not work. ## Dependencies tpm-fido requires `pinentry` to be available on the system. If you have gpg installed you most likely already have `pinentry`. 07070100000005000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002600000000tpm-fido-20230621.5f8828b/attestation07070100000006000081A40000000000000000000000016493935C000005E0000000000000000000000000000000000000003500000000tpm-fido-20230621.5f8828b/attestation/attestation.gopackage attestation import ( "crypto/ecdsa" "crypto/x509" "encoding/pem" ) // this is the same cert used by github/SoftU2F // https://github.com/github/SoftU2F/blob/master/SelfSignedCertificate/SelfSignedCertificate.m // We resuse that cert to help reduce the ability to track // an individual using this cert var attestationPrivateKeyPem = `-----BEGIN PRIVATE KEY----- MHcCAQEEIAOEKsf0zeNn3qBWxk9/OxXqfUvEg8rGl58qMZOtVzEJoAoGCCqGSM49 AwEHoUQDQgAE9pyrJBRLtO+H9w8jHFzU9XgErPjgxrKz41IYPYA5H2vSedJqTINk dObC2iOT/6wdUDRsXCOQZVeTPsuT/27e0Q== -----END PRIVATE KEY-----` var attestationCertPem = `-----BEGIN CERTIFICATE----- MIIBfjCCASSgAwIBAgIBATAKBggqhkjOPQQDAjA8MREwDwYDVQQDDAhTb2Z0IFUy RjEUMBIGA1UECgwLR2l0SHViIEluYy4xETAPBgNVBAsMCFNlY3VyaXR5MB4XDTE3 MDcyNjIwMDkwOFoXDTI3MDcyNDIwMDkwOFowPDERMA8GA1UEAwwIU29mdCBVMkYx FDASBgNVBAoMC0dpdEh1YiBJbmMuMREwDwYDVQQLDAhTZWN1cml0eTBZMBMGByqG SM49AgEGCCqGSM49AwEHA0IABPacqyQUS7Tvh/cPIxxc1PV4BKz44Mays+NSGD2A OR9r0nnSakyDZHTmwtojk/+sHVA0bFwjkGVXkz7Lk/9u3tGjFzAVMBMGCysGAQQB guUcAgEBBAQDAgMIMAoGCCqGSM49BAMCA0gAMEUCIQD+Ih2XuOrqErufQhSFD0gX ZbXglZNeoaPWbQ+xbzn3IgIgZNfcL1xsOCr3ZfV4ajmwsUqXRSjvfd8hAhUbiErU QXo= -----END CERTIFICATE-----` var ( CertDer []byte PrivateKey *ecdsa.PrivateKey ) func init() { certDer, _ := pem.Decode([]byte(attestationCertPem)) CertDer = certDer.Bytes privKeyDer, _ := pem.Decode([]byte(attestationPrivateKeyPem)) var err error PrivateKey, err = x509.ParseECPrivateKey(privKeyDer.Bytes) if err != nil { panic(err) } } 07070100000007000081A40000000000000000000000016493935C000009FE000000000000000000000000000000000000003A00000000tpm-fido-20230621.5f8828b/attestation/attestation_test.gopackage attestation import ( "bytes" "testing" ) func TestAttestationCert(t *testing.T) { rawCert := []byte{ 0x30, 0x82, 0x01, 0x7e, 0x30, 0x82, 0x01, 0x24, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x3c, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x08, 0x53, 0x6f, 0x66, 0x74, 0x20, 0x55, 0x32, 0x46, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x37, 0x32, 0x36, 0x32, 0x30, 0x30, 0x39, 0x30, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x37, 0x30, 0x37, 0x32, 0x34, 0x32, 0x30, 0x30, 0x39, 0x30, 0x38, 0x5a, 0x30, 0x3c, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x08, 0x53, 0x6f, 0x66, 0x74, 0x20, 0x55, 0x32, 0x46, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xf6, 0x9c, 0xab, 0x24, 0x14, 0x4b, 0xb4, 0xef, 0x87, 0xf7, 0x0f, 0x23, 0x1c, 0x5c, 0xd4, 0xf5, 0x78, 0x04, 0xac, 0xf8, 0xe0, 0xc6, 0xb2, 0xb3, 0xe3, 0x52, 0x18, 0x3d, 0x80, 0x39, 0x1f, 0x6b, 0xd2, 0x79, 0xd2, 0x6a, 0x4c, 0x83, 0x64, 0x74, 0xe6, 0xc2, 0xda, 0x23, 0x93, 0xff, 0xac, 0x1d, 0x50, 0x34, 0x6c, 0x5c, 0x23, 0x90, 0x65, 0x57, 0x93, 0x3e, 0xcb, 0x93, 0xff, 0x6e, 0xde, 0xd1, 0xa3, 0x17, 0x30, 0x15, 0x30, 0x13, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x03, 0x08, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xfe, 0x22, 0x1d, 0x97, 0xb8, 0xea, 0xea, 0x12, 0xbb, 0x9f, 0x42, 0x14, 0x85, 0x0f, 0x48, 0x17, 0x65, 0xb5, 0xe0, 0x95, 0x93, 0x5e, 0xa1, 0xa3, 0xd6, 0x6d, 0x0f, 0xb1, 0x6f, 0x39, 0xf7, 0x22, 0x02, 0x20, 0x64, 0xd7, 0xdc, 0x2f, 0x5c, 0x6c, 0x38, 0x2a, 0xf7, 0x65, 0xf5, 0x78, 0x6a, 0x39, 0xb0, 0xb1, 0x4a, 0x97, 0x45, 0x28, 0xef, 0x7d, 0xdf, 0x21, 0x02, 0x15, 0x1b, 0x88, 0x4a, 0xd4, 0x41, 0x7a, } if !bytes.Equal(CertDer, rawCert) { t.Fatalf("cert der mismatch") } } 07070100000008000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002300000000tpm-fido-20230621.5f8828b/fidoauth07070100000009000081A40000000000000000000000016493935C000009AA000000000000000000000000000000000000002F00000000tpm-fido-20230621.5f8828b/fidoauth/fidoauth.go// fidoauth implements the fido1 authentication API package fidoauth import ( "fmt" ) const ( CmdRegister = 0x01 CmdAuthenticate = 0x02 CmdVersion = 0x03 CtrlCheckOnly AuthCtrl = 0x07 // Check if the provided key is valid CtrlEnforeUserPresenceAndSign AuthCtrl = 0x03 // confirm with user then sign CtrlDontEnforeUserPresenceAndSign AuthCtrl = 0x08 // just sign without confirming ) type AuthenticatorRequest struct { Command uint8 Param1 uint8 Param2 uint8 Size int Data []byte Register *AuthenticatorRegisterReq Authenticate *AuthenticatorAuthReq } type AuthenticatorRegisterReq struct { ChallengeParam [32]byte ApplicationParam [32]byte } type AuthenticatorResponse struct { Data []byte Status uint16 } type AuthCtrl uint8 type AuthenticatorAuthReq struct { Ctrl AuthCtrl ChallengeParam [32]byte ApplicationParam [32]byte KeyHandle []byte } func DecodeAuthenticatorRequest(raw []byte) (*AuthenticatorRequest, error) { if len(raw) < 7 { return nil, fmt.Errorf("authenticator request too short") } req := AuthenticatorRequest{ Command: raw[1], Param1: raw[2], Param2: raw[3], Size: (int(raw[4]) << 16) | (int(raw[5]) << 8) | int(raw[6]), Data: raw[7:], } if req.Command == CmdRegister { var reg AuthenticatorRegisterReq if len(req.Data) < len(reg.ChallengeParam)+len(reg.ApplicationParam) { return nil, fmt.Errorf("register request incorrect size: %d", len(req.Data)) } copy(reg.ChallengeParam[:], req.Data[:32]) copy(reg.ApplicationParam[:], req.Data[32:]) req.Register = ® } else if req.Command == CmdAuthenticate { var auth AuthenticatorAuthReq if len(req.Data) < len(auth.ChallengeParam)+len(auth.ApplicationParam)+2 { return nil, fmt.Errorf("authenticate request too small: %d", len(req.Data)) } auth.Ctrl = AuthCtrl(req.Param1) switch auth.Ctrl { case CtrlCheckOnly, CtrlEnforeUserPresenceAndSign, CtrlDontEnforeUserPresenceAndSign: default: return nil, fmt.Errorf("unknown ctrl type: %02x", auth.Ctrl) } data := req.Data copy(auth.ChallengeParam[:], data[:32]) data = data[32:] copy(auth.ApplicationParam[:], data[:32]) data = data[32:] khLen := data[0] data = data[1:] if len(data) < int(khLen) { return nil, fmt.Errorf("key handle len too short %d vs %d", len(data), int(khLen)) } auth.KeyHandle = data[:khLen] req.Authenticate = &auth } return &req, nil } 0707010000000A000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002200000000tpm-fido-20230621.5f8828b/fidohid0707010000000B000081A40000000000000000000000016493935C000029B2000000000000000000000000000000000000002D00000000tpm-fido-20230621.5f8828b/fidohid/fidohid.gopackage fidohid import ( "bytes" "context" "crypto/rand" "encoding/binary" "fmt" "io" "log" "github.com/psanford/tpm-fido/fidoauth" "github.com/psanford/uhid" ) func New(ctx context.Context, name string) (*SoftToken, error) { d, err := uhid.NewDevice(name, rdesc) if err != nil { return nil, err } d.Data.Bus = busUSB d.Data.VendorID = vendorID d.Data.ProductID = productID evtChan, err := d.Open(ctx) if err != nil { return nil, err } t := SoftToken{ device: d, evtChan: evtChan, authEvent: make(chan AuthEvent), } return &t, nil } type SoftToken struct { device *uhid.Device evtChan chan uhid.Event authEvent chan AuthEvent authFunc func() } type AuthEvent struct { chanID uint32 cmd CmdType Req *fidoauth.AuthenticatorRequest Error error } func (t *SoftToken) Events() chan AuthEvent { return t.authEvent } func (t *SoftToken) Run(ctx context.Context) { channels := make(map[uint32]bool) allocateChan := func() (uint32, bool) { for k := uint32(1); k < (1<<32)-1; k++ { inUse := channels[k] if !inUse { channels[k] = true return k, true } } return 0, false } pktChan := make(chan Packet) go parsePackets(ctx, t.evtChan, pktChan) for { var ( innerMsg []byte needSize uint16 reqChanID uint32 cmd CmdType ) for pkt := range pktChan { if pkt.IsInitial { if len(innerMsg) > 0 { log.Print("new initial packet while pending packets still exist") innerMsg = make([]byte, 0) needSize = 0 } needSize = pkt.TotalSize reqChanID = pkt.ChannelID cmd = pkt.Command } innerMsg = append(innerMsg, pkt.Data...) if len(innerMsg) >= int(needSize) { break } } innerMsg = innerMsg[:int(needSize)] switch cmd { case CmdInit: chanID, ok := allocateChan() if !ok { log.Fatalf("Channel id exhaustion") } var nonce [8]byte copy(nonce[:], innerMsg) resp := newInitResponse(chanID, nonce) err := writeRespose(t.device, reqChanID, CmdInit, resp.Marshal(), 0) if err != nil { log.Printf("Write Init resp err: %s", err) continue } case CmdMsg: req, err := fidoauth.DecodeAuthenticatorRequest(innerMsg) evt := AuthEvent{ chanID: reqChanID, cmd: cmd, Req: req, Error: err, } select { case t.authEvent <- evt: case <-ctx.Done(): return } default: log.Printf("unsuppoted cmd: %s %d", cmd, cmd) writeRespose(t.device, reqChanID, cmd, nil, swInsNotSupported) } } } const ( busUSB = 0x03 vendorID = 0x15d9 productID = 0x0a37 frameTypeInit = 0x80 frameTypeCont = 0x00 CmdPing CmdType = 0x01 // Echo data through local processor only CmdMsg CmdType = 0x03 // Send U2F message frame CmdLock CmdType = 0x04 // Send lock channel command CmdInit CmdType = 0x06 // Channel initialization CmdWink CmdType = 0x08 // Send device identification wink CmdCbor CmdType = 0x10 // Send encapsulated CTAP CBOR CmdSync CmdType = 0x3c // Protocol resync command CmdError CmdType = 0x3f // Error response vendorSpecificFirstCmd = 0x40 vendorSpecificLastCmd = 0x7f reportLen = 64 initialPacketDataLen = reportLen - 7 contPacketDataLen = reportLen - 5 u2fProtocolVersion = 2 deviceMajor = 1 deviceMinor = 0 deviceBuild = 0 winkCapability = 0x01 lockCapability = 0x02 cborCapability = 0x04 nmsgCapability = 0x08 swInsNotSupported = 0x6D00 // The Instruction of the request is not supported ) type CmdType uint8 func (c CmdType) IsVendorSpecific() bool { return c >= vendorSpecificFirstCmd && c <= vendorSpecificLastCmd } func (c CmdType) String() string { switch c { case CmdPing: return "CmdPing" case CmdMsg: return "CmdMsg" case CmdLock: return "CmdLock" case CmdInit: return "CmdInit" case CmdWink: return "CmdWink" case CmdSync: return "CmdSync" case CmdError: return "CmdError" case CmdCbor: return "CmdCbor" } if c >= vendorSpecificFirstCmd && c <= vendorSpecificLastCmd { return fmt.Sprintf("CmdVendor<%d>", c) } return fmt.Sprintf("CmdUnknown<%d>", c) } // src: http://www.usb.org/developers/hidpage/HUTRR48.pdf var rdesc = []byte{ 0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance) 0x09, 0x01, // USAGE (U2F HID Authenticator Device) 0xa1, 0x01, // COLLECTION (Application) 0x09, 0x20, // USAGE (Input Report Data) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x40, // REPORT_COUNT (64) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x09, 0x21, // USAGE (Output Report Data) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x40, // REPORT_COUNT (64) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0xc0, // END_COLLECTION } func parsePackets(ctx context.Context, evtChan chan uhid.Event, pktChan chan Packet) { for { var ( evt uhid.Event ok bool ) select { case evt, ok = <-evtChan: if !ok { return } case <-ctx.Done(): return } if evt.Err != nil { log.Fatalf("got evt err: %s", evt.Err) } // Output means the kernel has sent us data if evt.Type == uhid.Output { r := newPacketReader(bytes.NewReader(evt.Data)) b1 := make([]byte, 1) r.ReadFull(b1) // ignore first byte var channelID uint32 r.Read(binary.BigEndian, &channelID) _, err := r.ReadFull(b1) if err != nil { log.Printf("U2F protocol read error") continue } typeOrSeqNo := b1[0] if typeOrSeqNo&frameTypeInit == frameTypeInit { typ := typeOrSeqNo cmd := typ ^ frameTypeInit var totalSize uint16 r.Read(binary.BigEndian, &totalSize) data := make([]byte, initialPacketDataLen) _, err := r.ReadFull(data) if err != nil { log.Printf("U2F protocol read error") continue } p := Packet{ ChannelID: channelID, IsInitial: true, Command: CmdType(cmd), TotalSize: totalSize, Data: data, } select { case pktChan <- p: case <-ctx.Done(): return } } else { seqNo := typeOrSeqNo data := make([]byte, contPacketDataLen) _, err := r.ReadFull(data) if err != nil { log.Printf("U2F protocol read error") continue } p := Packet{ ChannelID: channelID, SeqNo: seqNo, Data: data, } select { case pktChan <- p: case <-ctx.Done(): return } } } } } type Packet struct { ChannelID uint32 IsInitial bool Command CmdType SeqNo byte TotalSize uint16 Data []byte } func newPacketReader(r io.Reader) *packetReader { return &packetReader{ r: r, } } type packetReader struct { r io.Reader err error } func (r *packetReader) Error() error { return r.err } func (pr *packetReader) Read(order binary.ByteOrder, data interface{}) error { if pr.err != nil { return pr.err } err := binary.Read(pr.r, order, data) if err != nil { pr.err = err } return err } func (pr *packetReader) ReadFull(b []byte) (int, error) { if pr.err != nil { return 0, pr.err } n, err := io.ReadFull(pr.r, b) if err != nil { pr.err = err return n, err } return n, nil } func mustRand(size int) []byte { b := make([]byte, size) if _, err := rand.Read(b); err != nil { panic(err) } return b } type frameInit struct { ChannelID uint32 Command uint8 Data []byte TotalPayloadLen uint16 } func (fi *frameInit) Marshal() []byte { buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, fi.ChannelID) buf.WriteByte(fi.Command) binary.Write(buf, binary.BigEndian, fi.TotalPayloadLen) buf.Write(fi.Data) if len(fi.Data) < initialPacketDataLen { pad := make([]byte, initialPacketDataLen-len(fi.Data)) buf.Write(pad) } return buf.Bytes() } type frameCont struct { ChannelID uint32 SeqNo uint8 Data []byte } func (fi *frameCont) Marshal() []byte { buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, fi.ChannelID) buf.WriteByte(fi.SeqNo) buf.Write(fi.Data) if len(fi.Data) < contPacketDataLen { pad := make([]byte, contPacketDataLen-len(fi.Data)) buf.Write(pad) } return buf.Bytes() } type initResponse struct { Nonce [8]byte Channel uint32 ProtocolVersion byte MajorDeviceVersion byte MinorDeviceVersion byte BuildDeviceVersion byte RawCapabilities byte } func newInitResponse(channelID uint32, nonce [8]byte) *initResponse { return &initResponse{ Nonce: nonce, Channel: channelID, ProtocolVersion: u2fProtocolVersion, MajorDeviceVersion: deviceMajor, MinorDeviceVersion: deviceMinor, BuildDeviceVersion: deviceBuild, // RawCapabilities: winkCapability, } } func (resp *initResponse) Marshal() []byte { buf := new(bytes.Buffer) buf.Write(resp.Nonce[:]) binary.Write(buf, binary.BigEndian, resp.Channel) buf.Write([]byte{ resp.ProtocolVersion, resp.MajorDeviceVersion, resp.MinorDeviceVersion, resp.BuildDeviceVersion, resp.RawCapabilities, }) return buf.Bytes() } func (t *SoftToken) WriteResponse(ctx context.Context, evt AuthEvent, data []byte, status uint16) error { return writeRespose(t.device, evt.chanID, evt.cmd, data, status) } func writeRespose(d *uhid.Device, chanID uint32, cmd CmdType, data []byte, status uint16) error { initial := true pktSize := initialPacketDataLen if status > 0 { statusBytes := make([]byte, 2) binary.BigEndian.PutUint16(statusBytes, status) data = append(data, statusBytes...) } totalSize := uint16(len(data)) var seqNo uint8 for len(data) > 0 { sliceSize := pktSize if len(data) < sliceSize { sliceSize = len(data) } pktData := data[:sliceSize] data = data[sliceSize:] if initial { initial = false pktSize = contPacketDataLen frame := frameInit{ ChannelID: chanID, Command: uint8(cmd) | frameTypeInit, Data: pktData, TotalPayloadLen: totalSize, } payload := frame.Marshal() resp := uhid.Input2Request{ RequestType: uhid.Input2, DataSize: uint16(len(payload)), } copy(resp.Data[:], payload) err := d.WriteEvent(resp) if err != nil { return err } } else { frame := frameCont{ ChannelID: chanID, SeqNo: seqNo, Data: pktData, } payload := frame.Marshal() resp := uhid.Input2Request{ RequestType: uhid.Input2, DataSize: uint16(len(payload)), } copy(resp.Data[:], payload) err := d.WriteEvent(resp) if err != nil { return err } seqNo++ } } return nil } 0707010000000C000081A40000000000000000000000016493935C000000F4000000000000000000000000000000000000002100000000tpm-fido-20230621.5f8828b/go.modmodule github.com/psanford/tpm-fido go 1.16 require ( github.com/foxcpp/go-assuan v1.0.0 github.com/google/go-tpm v0.3.2 github.com/psanford/uhid v0.0.0-20210426002309-4864eff247db golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a ) 0707010000000D000081A40000000000000000000000016493935C00004AD6000000000000000000000000000000000000002100000000tpm-fido-20230621.5f8828b/go.sumcloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/foxcpp/go-assuan v1.0.0 h1:KDSEmEGdHOzOjKmFUoaX/FGk1NxG/mHNAIdyksM8e4A= github.com/foxcpp/go-assuan v1.0.0/go.mod h1:dQEJ9EaSSrUjd/g2PVaVEuV0TaoMDGwoh9XIDH1Q/Sc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= github.com/google/go-tpm v0.3.2 h1:3iQQ2dlEf+1no7CLlfLPYzxhQy7j2G/emBqU5okydaw= github.com/google/go-tpm v0.3.2/go.mod h1:j71sMBTfp3X5jPHz852ZOfQMUOf65Gb/Th8pRmp7fvg= github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/psanford/uhid v0.0.0-20210426002309-4864eff247db h1:V67//QISkDEEV+VrYgIzmtG+UCjDX8gQiMuf7f50i8A= github.com/psanford/uhid v0.0.0-20210426002309-4864eff247db/go.mod h1:l7AmGL56H606RdpAQZ3qizn5ur7SoO9uQA2ZT7ZJ4RI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d h1:MiWWjyhUzZ+jvhZvloX6ZrUsdEghn8a64Upd8EMHglE= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 0707010000000E000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002300000000tpm-fido-20230621.5f8828b/internal0707010000000F000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002B00000000tpm-fido-20230621.5f8828b/internal/lencode07070100000010000081A40000000000000000000000016493935C00000D58000000000000000000000000000000000000003600000000tpm-fido-20230621.5f8828b/internal/lencode/lencode.go// This is a fork of github.com/psanford/lencode // with the len prefix changed to a single byte package lencode import ( "bytes" "errors" "io" ) type Encoder struct { w io.Writer separator []byte err error } type Option struct { encoderOpt func(e *Encoder) decoderOpt func(e *Decoder) } // Specify a record separator to use as an integrity // check when decoding. This defaults to \x6c\x65\x6e\x63 ("lenc"). // Set to nil to disable. func SeparatorOpt(s []byte) Option { return Option{ encoderOpt: func(e *Encoder) { e.separator = s }, decoderOpt: func(e *Decoder) { e.separator = s }, } } var defaultSeparator = []byte{'l', 'e', 'n', 'c'} var separatorMismatchErr = errors.New("Separator mismatch") func NewEncoder(w io.Writer, opts ...Option) *Encoder { e := &Encoder{ w: w, separator: defaultSeparator, } for _, opt := range opts { opt.encoderOpt(e) } return e } // Encode a message to the underlying writer. It is not safe // to call this method concurrently. func (e *Encoder) Encode(msg []byte) error { if e.err != nil { return e.err } msgLen := int64(len(msg)) if msgLen > 0xff { e.err = errors.New("Message too long to encode length in 1 byte") return e.err } if e.separator != nil { e.write(e.separator) } e.write([]byte{byte(msgLen)}) e.write(msg) return e.err } func (e *Encoder) write(b []byte) error { if e.err != nil { return e.err } _, err := e.w.Write(b) if err != nil { e.err = err } return err } type Decoder struct { r io.Reader separator []byte prefixBuf []byte err error pendingPrefix bool pendingLen int } func NewDecoder(r io.Reader, opts ...Option) *Decoder { d := &Decoder{ r: r, separator: defaultSeparator, } for _, opt := range opts { opt.decoderOpt(d) } d.prefixBuf = make([]byte, len(d.separator)+1) return d } // Decode the next message from the io.Reader. func (d *Decoder) Decode() ([]byte, error) { if err := d.readPrefix(); err != nil { return nil, err } buf := make([]byte, d.pendingLen) if err := d.DecodeInto(buf); err != nil { return nil, err } return buf, nil } // Decode the next message into a provided byte slice. // Use NextLen() to ensure the slice is large enough // for the message. func (d *Decoder) DecodeInto(b []byte) error { if err := d.readPrefix(); err != nil { return err } if d.pendingLen < len(b) { d.err = errors.New("Buffer not large enough for next message") return d.err } if _, err := io.ReadFull(d.r, b[:d.pendingLen]); err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } d.err = err return d.err } d.pendingPrefix = false d.pendingLen = 0 return nil } // Return the length of the next message func (d *Decoder) NextLen() (int, error) { if d.err != nil { return 0, d.err } if !d.pendingPrefix { if err := d.readPrefix(); err != nil { return 0, err } } return d.pendingLen, nil } func (d *Decoder) readPrefix() error { if d.err != nil { return d.err } if d.pendingPrefix { return nil } if _, err := io.ReadFull(d.r, d.prefixBuf); err != nil { d.err = err return err } if len(d.separator) > 0 { if !bytes.Equal(d.prefixBuf[:len(d.separator)], d.separator) { d.err = separatorMismatchErr return d.err } } l := d.prefixBuf[len(d.separator)] d.pendingPrefix = true d.pendingLen = int(l) return d.err } 07070100000011000081A40000000000000000000000016493935C00000905000000000000000000000000000000000000003B00000000tpm-fido-20230621.5f8828b/internal/lencode/lencode_test.gopackage lencode import ( "bytes" "io" "testing" ) func TestEncodeDecode(t *testing.T) { var buf bytes.Buffer enc := NewEncoder(&buf) payload0 := make([]byte, 16) for i := 0; i < len(payload0); i++ { payload0[i] = 'a' } if err := enc.Encode(payload0); err != nil { t.Fatal(err) } payload1 := make([]byte, 255) for i := 0; i < len(payload1); i++ { payload1[i] = 'b' } if err := enc.Encode(payload1); err != nil { t.Fatal(err) } dec := NewDecoder(&buf) n, err := dec.NextLen() if err != nil { t.Fatal(err) } if n != len(payload0) { t.Fatalf("payload0 len mismatch %d vs %d", n, len(payload0)) } got0, err := dec.Decode() if err != nil { t.Fatal(err) } if !bytes.Equal(got0, payload0) { t.Fatalf("Payload0 mismatch %v vs %v", got0, payload0) } got1, err := dec.Decode() if err != nil { t.Fatal(err) } if !bytes.Equal(got1, payload1) { t.Fatalf("Payload1 mismatch %v vs %v", got1, payload1) } got2, err := dec.Decode() if err != io.EOF { t.Fatalf("Expected EOF, got %s %v", err, got2) } } func TestSeparatorMismatch(t *testing.T) { var buf bytes.Buffer enc := NewEncoder(&buf, SeparatorOpt(nil)) payload0 := make([]byte, 16) for i := 0; i < len(payload0); i++ { payload0[i] = 'a' } if err := enc.Encode(payload0); err != nil { t.Fatal(err) } r := bytes.NewReader(buf.Bytes()) dec := NewDecoder(r) _, err := dec.Decode() if err != separatorMismatchErr { t.Fatalf("Expected %s got %s", separatorMismatchErr, err) } r = bytes.NewReader(buf.Bytes()) dec = NewDecoder(r, SeparatorOpt(nil)) got0, err := dec.Decode() if err != nil { t.Fatal(err) } if !bytes.Equal(got0, payload0) { t.Fatalf("Payload0 mismatch %v vs %v", got0, payload0) } } func TestDecodeUnexpectedEOF(t *testing.T) { payload := []byte{0x01} r := bytes.NewReader(payload) dec := NewDecoder(r, SeparatorOpt(nil)) _, err := dec.Decode() if err != io.ErrUnexpectedEOF { t.Fatalf("Short read should trigger UnexpectedEOF error but was %s", err) } } func TestTooLong(t *testing.T) { var buf bytes.Buffer enc := NewEncoder(&buf) payload := bytes.Repeat([]byte{'a'}, 256) err := enc.Encode(payload) expect := "Message too long to encode length in 1 byte" if err.Error() != expect { t.Fatalf("Expected too long error, got: %s", err) } } 07070100000012000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002100000000tpm-fido-20230621.5f8828b/memory07070100000013000081A40000000000000000000000016493935C000009D1000000000000000000000000000000000000002B00000000tpm-fido-20230621.5f8828b/memory/memory.gopackage memory import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "fmt" "math/big" "golang.org/x/crypto/chacha20poly1305" ) type Mem struct { masterPrivateKey []byte signCounter uint32 } func New() (*Mem, error) { return &Mem{ masterPrivateKey: mustRand(chacha20poly1305.KeySize), }, nil } func (m *Mem) Counter() uint32 { m.signCounter++ return m.signCounter } func (m *Mem) RegisterKey(applicationParam []byte) ([]byte, *big.Int, *big.Int, error) { curve := elliptic.P256() childPrivateKey, x, y, err := elliptic.GenerateKey(curve, rand.Reader) if err != nil { return nil, nil, nil, fmt.Errorf("gen key err: %w", err) } metadata := []byte("fido_wrapping_key") metadata = append(metadata, applicationParam...) h := sha256.New() h.Write(metadata) sum := h.Sum(nil) aead, err := chacha20poly1305.NewX(m.masterPrivateKey) if err != nil { return nil, nil, nil, fmt.Errorf("chacha NewX err: %w", err) } nonce := mustRand(chacha20poly1305.NonceSizeX) encryptedChildPrivateKey := aead.Seal(nil, nonce, childPrivateKey, sum) keyHandle := make([]byte, 0, len(nonce)+len(encryptedChildPrivateKey)) keyHandle = append(keyHandle, nonce...) keyHandle = append(keyHandle, encryptedChildPrivateKey...) if len(keyHandle) > 255 { panic("keyHandle is too big") } return keyHandle, x, y, nil } func (m *Mem) SignASN1(keyHandle, applicationParam, digest []byte) ([]byte, error) { aead, err := chacha20poly1305.NewX(m.masterPrivateKey) if err != nil { panic(err) } if len(keyHandle) < chacha20poly1305.NonceSizeX { return nil, fmt.Errorf("incorrect size for key handle: %d smaller than nonce)", len(keyHandle)) } nonce := keyHandle[:chacha20poly1305.NonceSizeX] cipherText := keyHandle[chacha20poly1305.NonceSizeX:] metadata := []byte("fido_wrapping_key") metadata = append(metadata, applicationParam[:]...) h := sha256.New() h.Write(metadata) sum := h.Sum(nil) childPrivateKey, err := aead.Open(nil, nonce, cipherText, sum) if err != nil { return nil, fmt.Errorf("open child private key err: %w", err) } var ecdsaKey ecdsa.PrivateKey ecdsaKey.D = new(big.Int).SetBytes(childPrivateKey) ecdsaKey.PublicKey.Curve = elliptic.P256() ecdsaKey.PublicKey.X, ecdsaKey.PublicKey.Y = ecdsaKey.PublicKey.Curve.ScalarBaseMult(ecdsaKey.D.Bytes()) return ecdsa.SignASN1(rand.Reader, &ecdsaKey, digest) } func mustRand(size int) []byte { b := make([]byte, size) if _, err := rand.Read(b); err != nil { panic(err) } return b } 07070100000014000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002300000000tpm-fido-20230621.5f8828b/pinentry07070100000015000081A40000000000000000000000016493935C00000E55000000000000000000000000000000000000002F00000000tpm-fido-20230621.5f8828b/pinentry/pinentry.gopackage pinentry import ( "context" "errors" "fmt" "log" "os/exec" "sync" "time" assuan "github.com/foxcpp/go-assuan/client" "github.com/foxcpp/go-assuan/pinentry" ) func New() *Pinentry { return &Pinentry{} } type Pinentry struct { mu sync.Mutex activeRequest *request } type request struct { timeout time.Duration pendingResult chan Result extendTimeout chan time.Duration challengeParam [32]byte applicationParam [32]byte } type Result struct { OK bool Error error } func (pe *Pinentry) ConfirmPresence(prompt string, challengeParam, applicationParam [32]byte) (chan Result, error) { pe.mu.Lock() defer pe.mu.Unlock() timeout := 2 * time.Second if pe.activeRequest != nil { if challengeParam != pe.activeRequest.challengeParam || applicationParam != pe.activeRequest.applicationParam { return nil, errors.New("other request already in progress") } extendTimeoutChan := pe.activeRequest.extendTimeout go func() { select { case extendTimeoutChan <- timeout: case <-time.After(timeout): } }() return pe.activeRequest.pendingResult, nil } pe.activeRequest = &request{ timeout: timeout, challengeParam: challengeParam, applicationParam: applicationParam, pendingResult: make(chan Result), extendTimeout: make(chan time.Duration), } go pe.prompt(pe.activeRequest, prompt) return pe.activeRequest.pendingResult, nil } func (pe *Pinentry) prompt(req *request, prompt string) { sendResult := func(r Result) { select { case req.pendingResult <- r: case <-time.After(req.timeout): // we expect requests to come in every ~750ms. // If we've been waiting for 2 seconds the client // is likely gone. } pe.mu.Lock() pe.activeRequest = nil pe.mu.Unlock() } childCtx, cancel := context.WithCancel(context.Background()) defer cancel() p, cmd, err := launchPinEntry(childCtx) if err != nil { sendResult(Result{ OK: false, Error: fmt.Errorf("failed to start pinentry: %w", err), }) return } defer func() { cancel() cmd.Wait() }() defer p.Shutdown() p.SetTitle("TPM-FIDO") p.SetPrompt("TPM-FIDO") p.SetDesc(prompt) promptResult := make(chan bool) go func() { err := p.Confirm() promptResult <- err == nil }() timer := time.NewTimer(req.timeout) for { select { case ok := <-promptResult: sendResult(Result{ OK: ok, }) return case <-timer.C: sendResult(Result{ OK: false, Error: errors.New("request timed out"), }) return case d := <-req.extendTimeout: if !timer.Stop() { <-timer.C } timer.Reset(d) } } } func FindPinentryGUIPath() string { candidates := []string{ "pinentry-gnome3", "pinentry-qt5", "pinentry-qt4", "pinentry-qt", "pinentry-gtk-2", "pinentry-x11", "pinentry-fltk", } for _, candidate := range candidates { p, _ := exec.LookPath(candidate) if p != "" { return p } } return "" } func launchPinEntry(ctx context.Context) (*pinentry.Client, *exec.Cmd, error) { pinEntryCmd := FindPinentryGUIPath() if pinEntryCmd == "" { log.Printf("Failed to detect gui pinentry binary. Falling back to default `pinentry`") pinEntryCmd = "pinentry" } cmd := exec.CommandContext(ctx, pinEntryCmd) stdout, err := cmd.StdoutPipe() if err != nil { return nil, nil, err } stdin, err := cmd.StdinPipe() if err != nil { return nil, nil, err } if err := cmd.Start(); err != nil { return nil, nil, err } var c pinentry.Client c.Session, err = assuan.Init(assuan.ReadWriteCloser{ ReadCloser: stdout, WriteCloser: stdin, }) if err != nil { return nil, nil, err } return &c, cmd, nil } 07070100000016000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002900000000tpm-fido-20230621.5f8828b/sitesignatures07070100000017000081A40000000000000000000000016493935C00000828000000000000000000000000000000000000003B00000000tpm-fido-20230621.5f8828b/sitesignatures/sitesignatures.gopackage sitesignatures import ( "crypto/sha256" "fmt" ) // from https://github.com/danstiner/rust-u2f/blob/master/u2f-core/src/known_app_ids.rs // and https://github.com/github/SoftU2F/blob/master/SoftU2FTool/KnownFacets.swift var reverseSignatures = map[[32]byte]string{ hashURL("https://api-9dcf9b83.duosecurity.com"): "duosecurity.com", hashURL("https://dashboard.stripe.com"): "dashboard.stripe.com", hashURL("https://demo.yubico.com"): "demo.yubico.com", hashURL("https://github.com/u2f/trusted_facets"): "github.com", hashURL("https://gitlab.com"): "gitlab.com", hashURL("https://id.fedoraproject.org/u2f-origins.json"): "id.fedoraproject.org", hashURL("https://keepersecurity.com"): "keepersecurity.com", hashURL("https://lastpass.com"): "lastpass.com", hashURL("https://mdp.github.io"): "mdp.github.io", hashURL("https://personal.vanguard.com"): "vanguard.com", hashURL("https://u2f.aws.amazon.com/app-id.json"): "aws.amazon.com", hashURL("https://u2f.bin.coffee"): "u2f.bin.coffee", hashURL("https://vault.bitwarden.com/app-id.json"): "vault.bitwarden.com", hashURL("https://www.dropbox.com/u2f-app-id.json"): "dropbox.com", hashURL("https://www.fastmail.com"): "www.fastmail.com", hashURL("https://www.gstatic.com/securitykey/origins.json"): "google.com", hashURL("bin.coffee"): "bin.coffee", hashURL("coinbase.com"): "coinbase.com", hashURL("demo.yubico.com"): "demo.yubico.com", hashURL("github.com"): "github.com", hashURL("webauthn.bin.coffee"): "webauthn.bin.coffee", hashURL("webauthn.io"): "webauthn.io", } func hashURL(url string) [32]byte { return sha256.Sum256([]byte(url)) } func FromAppParam(sig [32]byte) string { site := reverseSignatures[sig] if site == "" { site = fmt.Sprintf("<unknown %x>", sig) } return site } 07070100000018000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000002500000000tpm-fido-20230621.5f8828b/statuscode07070100000019000081A40000000000000000000000016493935C000002BE000000000000000000000000000000000000003300000000tpm-fido-20230621.5f8828b/statuscode/statuscode.gopackage statuscode // status codes from section 3.3 of // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html const ( NoError = 0x9000 // The command completed successfully without error. ConditionsNotSatisfied = 0x6985 // The request was rejected due to test-of-user-presence being required. WrongData = 0x6A80 // The request was rejected due to an invalid key handle. WrongLength = 0x6700 // The length of the request was invalid ClaNotSupported = 0x6E00 // The Class byte of the request is not supported InsNotSupported = 0x6D00 // The Instruction of the request is not supported ) 0707010000001A000041ED0000000000000000000000016493935C00000000000000000000000000000000000000000000001E00000000tpm-fido-20230621.5f8828b/tpm0707010000001B000081A40000000000000000000000016493935C000014F0000000000000000000000000000000000000002500000000tpm-fido-20230621.5f8828b/tpm/tpm.gopackage tpm import ( "bytes" "crypto/rand" "crypto/sha256" "fmt" "io" "math/big" "sync" "time" "github.com/google/go-tpm/tpm2" "github.com/psanford/tpm-fido/internal/lencode" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" "golang.org/x/crypto/hkdf" ) var ( separator = []byte("TPM") seedSizeBytes = 20 ) type TPM struct { devicePath string mu sync.Mutex } func (t *TPM) open() (io.ReadWriteCloser, error) { return tpm2.OpenTPM(t.devicePath) } func New(devicePath string) (*TPM, error) { t := &TPM{ devicePath: devicePath, } tpm, err := t.open() if err != nil { return nil, err } tpm.Close() return t, nil } func primaryKeyTmpl(seed, applicationParam []byte) tpm2.Public { info := append([]byte("tpm-fido-application-key"), applicationParam...) r := hkdf.New(sha256.New, seed, []byte{}, info) unique := tpm2.ECPoint{ XRaw: make([]byte, 32), YRaw: make([]byte, 32), } if _, err := io.ReadFull(r, unique.XRaw); err != nil { panic(err) } if _, err := io.ReadFull(r, unique.YRaw); err != nil { panic(err) } return tpm2.Public{ Type: tpm2.AlgECC, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagRestricted | tpm2.FlagDecrypt | tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth, ECCParameters: &tpm2.ECCParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, CurveID: tpm2.CurveNISTP256, Point: unique, }, } } var baseTime = time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) func (t *TPM) Counter() uint32 { unix := time.Now().Unix() return uint32(unix - baseTime.Unix()) } // Register a new key with the TPM for the given applicationParam. // RegisterKey returns the KeyHandle or an error. func (t *TPM) RegisterKey(applicationParam []byte) ([]byte, *big.Int, *big.Int, error) { t.mu.Lock() defer t.mu.Unlock() tpm, err := t.open() if err != nil { return nil, nil, nil, fmt.Errorf("open tpm err: %w", err) } defer tpm.Close() randSeed := mustRand(seedSizeBytes) primaryTmpl := primaryKeyTmpl(randSeed, applicationParam) childTmpl := tpm2.Public{ Type: tpm2.AlgECC, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagSign, ECCParameters: &tpm2.ECCParams{ Sign: &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA256, }, CurveID: tpm2.CurveNISTP256, Point: tpm2.ECPoint{ XRaw: make([]byte, 32), YRaw: make([]byte, 32), }, }, } parentHandle, _, err := tpm2.CreatePrimary(tpm, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", primaryTmpl) if err != nil { return nil, nil, nil, fmt.Errorf("CreatePrimary key err: %w", err) } defer tpm2.FlushContext(tpm, parentHandle) private, public, _, _, _, err := tpm2.CreateKey(tpm, parentHandle, tpm2.PCRSelection{}, "", "", childTmpl) if err != nil { return nil, nil, nil, fmt.Errorf("CreateKey (child) err: %w", err) } var out bytes.Buffer enc := lencode.NewEncoder(&out, lencode.SeparatorOpt(separator)) enc.Encode(private) enc.Encode(public) enc.Encode(randSeed) keyHandle, _, err := tpm2.Load(tpm, parentHandle, "", public, private) if err != nil { return nil, nil, nil, fmt.Errorf("load child key err: %w", err) } defer tpm2.FlushContext(tpm, keyHandle) pub, _, _, err := tpm2.ReadPublic(tpm, keyHandle) if err != nil { return nil, nil, nil, fmt.Errorf("read public key err: %w", err) } x := new(big.Int).SetBytes(pub.ECCParameters.Point.XRaw) y := new(big.Int).SetBytes(pub.ECCParameters.Point.YRaw) return out.Bytes(), x, y, nil } func (t *TPM) SignASN1(keyHandle, applicationParam, digest []byte) ([]byte, error) { t.mu.Lock() defer t.mu.Unlock() tpm, err := t.open() if err != nil { return nil, fmt.Errorf("open tpm err: %w", err) } defer tpm.Close() dec := lencode.NewDecoder(bytes.NewReader(keyHandle), lencode.SeparatorOpt(separator)) invalidHandleErr := fmt.Errorf("invalid key handle") private, err := dec.Decode() if err != nil { return nil, invalidHandleErr } public, err := dec.Decode() if err != nil { return nil, invalidHandleErr } seed, err := dec.Decode() if err != nil { return nil, invalidHandleErr } _, err = dec.Decode() if err != io.EOF { return nil, invalidHandleErr } srkTemplate := primaryKeyTmpl(seed, applicationParam) parentHandle, _, err := tpm2.CreatePrimary(tpm, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", srkTemplate) if err != nil { return nil, fmt.Errorf("CreatePrimary key err: %w", err) } defer tpm2.FlushContext(tpm, parentHandle) key, _, err := tpm2.Load(tpm, parentHandle, "", public, private) if err != nil { return nil, fmt.Errorf("Load err: %w", err) } defer tpm2.FlushContext(tpm, key) scheme := &tpm2.SigScheme{ Alg: tpm2.AlgECDSA, Hash: tpm2.AlgSHA256, } sig, err := tpm2.Sign(tpm, key, "", digest[:], nil, scheme) if err != nil { return nil, fmt.Errorf("sign err: %w", err) } var b cryptobyte.Builder b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { b.AddASN1BigInt(sig.ECC.R) b.AddASN1BigInt(sig.ECC.S) }) return b.Bytes() } func mustRand(size int) []byte { b := make([]byte, size) if _, err := rand.Read(b); err != nil { panic(err) } return b } 0707010000001C000081A40000000000000000000000016493935C00002268000000000000000000000000000000000000002500000000tpm-fido-20230621.5f8828b/tpmfido.gopackage main import ( "bytes" "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "encoding/binary" "flag" "log" "math/big" "time" "github.com/psanford/tpm-fido/attestation" "github.com/psanford/tpm-fido/fidoauth" "github.com/psanford/tpm-fido/fidohid" "github.com/psanford/tpm-fido/memory" "github.com/psanford/tpm-fido/pinentry" "github.com/psanford/tpm-fido/sitesignatures" "github.com/psanford/tpm-fido/statuscode" "github.com/psanford/tpm-fido/tpm" ) var backend = flag.String("backend", "tpm", "tpm|memory") var device = flag.String("device", "/dev/tpmrm0", "TPM device path") func main() { flag.Parse() s := newServer() s.run() } type server struct { pe *pinentry.Pinentry signer Signer } type Signer interface { RegisterKey(applicationParam []byte) ([]byte, *big.Int, *big.Int, error) SignASN1(keyHandle, applicationParam, digest []byte) ([]byte, error) Counter() uint32 } func newServer() *server { s := server{ pe: pinentry.New(), } if *backend == "tpm" { signer, err := tpm.New(*device) if err != nil { panic(err) } s.signer = signer } else if *backend == "memory" { signer, err := memory.New() if err != nil { panic(err) } s.signer = signer } return &s } func (s *server) run() { ctx := context.Background() if pinentry.FindPinentryGUIPath() == "" { log.Printf("warning: no gui pinentry binary detected in PATH. tpm-fido may not work correctly without a gui based pinentry") } token, err := fidohid.New(ctx, "tpm-fido") if err != nil { log.Fatalf("create fido hid error: %s", err) } go token.Run(ctx) for evt := range token.Events() { if evt.Error != nil { log.Printf("got token error: %s", err) continue } req := evt.Req if req.Command == fidoauth.CmdAuthenticate { log.Printf("got AuthenticateCmd site=%s", sitesignatures.FromAppParam(req.Authenticate.ApplicationParam)) s.handleAuthenticate(ctx, token, evt) } else if req.Command == fidoauth.CmdRegister { log.Printf("got RegisterCmd site=%s", sitesignatures.FromAppParam(req.Register.ApplicationParam)) s.handleRegister(ctx, token, evt) } else if req.Command == fidoauth.CmdVersion { log.Print("got VersionCmd") s.handleVersion(ctx, token, evt) } else { log.Printf("unsupported request type: 0x%02x\n", req.Command) // send a not supported error for any commands that we don't understand. // Browsers depend on this to detect what features the token supports // (i.e. the u2f backwards compatibility) token.WriteResponse(ctx, evt, nil, statuscode.ClaNotSupported) } } } func (s *server) handleVersion(parentCtx context.Context, token *fidohid.SoftToken, evt fidohid.AuthEvent) { token.WriteResponse(parentCtx, evt, []byte("U2F_V2"), statuscode.NoError) } func (s *server) handleAuthenticate(parentCtx context.Context, token *fidohid.SoftToken, evt fidohid.AuthEvent) { req := evt.Req keyHandle := req.Authenticate.KeyHandle appParam := req.Authenticate.ApplicationParam[:] dummySig := sha256.Sum256([]byte("meticulously-Bacardi")) _, err := s.signer.SignASN1(keyHandle, appParam, dummySig[:]) if err != nil { log.Printf("invalid key: %s (key handle size: %d)", err, len(keyHandle)) err := token.WriteResponse(parentCtx, evt, nil, statuscode.WrongData) if err != nil { log.Printf("send bad key handle msg err: %s", err) } return } switch req.Authenticate.Ctrl { case fidoauth.CtrlCheckOnly, fidoauth.CtrlDontEnforeUserPresenceAndSign, fidoauth.CtrlEnforeUserPresenceAndSign: default: log.Printf("unknown authenticate control value: %d", req.Authenticate.Ctrl) err := token.WriteResponse(parentCtx, evt, nil, statuscode.WrongData) if err != nil { log.Printf("send wrong-data msg err: %s", err) } return } if req.Authenticate.Ctrl == fidoauth.CtrlCheckOnly { // check if the provided key is known by the token log.Printf("check-only success") // test-of-user-presence-required: note that despite the name this signals a success condition err := token.WriteResponse(parentCtx, evt, nil, statuscode.ConditionsNotSatisfied) if err != nil { log.Printf("send bad key handle msg err: %s", err) } return } var userPresent uint8 if req.Authenticate.Ctrl == fidoauth.CtrlEnforeUserPresenceAndSign { pinResultCh, err := s.pe.ConfirmPresence("FIDO Confirm Auth", req.Authenticate.ChallengeParam, req.Authenticate.ApplicationParam) if err != nil { log.Printf("pinentry err: %s", err) token.WriteResponse(parentCtx, evt, nil, statuscode.ConditionsNotSatisfied) return } childCtx, cancel := context.WithTimeout(parentCtx, 750*time.Millisecond) defer cancel() select { case result := <-pinResultCh: if result.OK { userPresent = 0x01 } else { if result.Error != nil { log.Printf("Got pinentry result err: %s", result.Error) } // Got user cancelation, we want to propagate that so the browser gives up. // This isn't normally supported by a key so there's no status code for this. // WrongData seems like the least incorrect status code ¯\_(ツ)_/¯ err := token.WriteResponse(parentCtx, evt, nil, statuscode.WrongData) if err != nil { log.Printf("Write WrongData resp err: %s", err) } return } case <-childCtx.Done(): err := token.WriteResponse(parentCtx, evt, nil, statuscode.ConditionsNotSatisfied) if err != nil { log.Printf("Write swConditionsNotSatisfied resp err: %s", err) } return } } signCounter := s.signer.Counter() var toSign bytes.Buffer toSign.Write(req.Authenticate.ApplicationParam[:]) toSign.WriteByte(userPresent) binary.Write(&toSign, binary.BigEndian, signCounter) toSign.Write(req.Authenticate.ChallengeParam[:]) sigHash := sha256.New() sigHash.Write(toSign.Bytes()) sig, err := s.signer.SignASN1(keyHandle, appParam, sigHash.Sum(nil)) if err != nil { log.Fatalf("auth sign err: %s", err) } var out bytes.Buffer out.WriteByte(userPresent) binary.Write(&out, binary.BigEndian, signCounter) out.Write(sig) err = token.WriteResponse(parentCtx, evt, out.Bytes(), statuscode.NoError) if err != nil { log.Printf("write auth response err: %s", err) return } } func (s *server) handleRegister(parentCtx context.Context, token *fidohid.SoftToken, evt fidohid.AuthEvent) { ctx, cancel := context.WithTimeout(parentCtx, 750*time.Millisecond) defer cancel() req := evt.Req pinResultCh, err := s.pe.ConfirmPresence("FIDO Confirm Register", req.Register.ChallengeParam, req.Register.ApplicationParam) if err != nil { log.Printf("pinentry err: %s", err) token.WriteResponse(ctx, evt, nil, statuscode.ConditionsNotSatisfied) return } select { case result := <-pinResultCh: if !result.OK { if result.Error != nil { log.Printf("Got pinentry result err: %s", result.Error) } // Got user cancelation, we want to propagate that so the browser gives up. // This isn't normally supported by a key so there's no status code for this. // WrongData seems like the least incorrect status code ¯\_(ツ)_/¯ err := token.WriteResponse(ctx, evt, nil, statuscode.WrongData) if err != nil { log.Printf("Write WrongData resp err: %s", err) return } return } s.registerSite(parentCtx, token, evt) case <-ctx.Done(): err := token.WriteResponse(ctx, evt, nil, statuscode.ConditionsNotSatisfied) if err != nil { log.Printf("Write swConditionsNotSatisfied resp err: %s", err) return } } } func (s *server) registerSite(ctx context.Context, token *fidohid.SoftToken, evt fidohid.AuthEvent) { req := evt.Req keyHandle, x, y, err := s.signer.RegisterKey(req.Register.ApplicationParam[:]) if err != nil { log.Printf("RegisteKey err: %s", err) return } if len(keyHandle) > 255 { log.Printf("Error: keyHandle too large: %d, max=255", len(keyHandle)) return } childPubKey := elliptic.Marshal(elliptic.P256(), x, y) var toSign bytes.Buffer toSign.WriteByte(0) toSign.Write(req.Register.ApplicationParam[:]) toSign.Write(req.Register.ChallengeParam[:]) toSign.Write(keyHandle) toSign.Write(childPubKey) sigHash := sha256.New() sigHash.Write(toSign.Bytes()) sum := sigHash.Sum(nil) sig, err := ecdsa.SignASN1(rand.Reader, attestation.PrivateKey, sum) if err != nil { log.Fatalf("attestation sign err: %s", err) } var out bytes.Buffer out.WriteByte(0x05) // reserved value out.Write(childPubKey) out.WriteByte(byte(len(keyHandle))) out.Write(keyHandle) out.Write(attestation.CertDer) out.Write(sig) err = token.WriteResponse(ctx, evt, out.Bytes(), statuscode.NoError) if err != nil { log.Printf("write register response err: %s", err) return } } func mustRand(size int) []byte { b := make([]byte, size) if _, err := rand.Read(b); err != nil { panic(err) } return b } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!145 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor