File: base.py

package info (click to toggle)
solo1-cli 0.1.1-6
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 464 kB
  • sloc: python: 2,168; makefile: 36
file content (159 lines) | stat: -rw-r--r-- 4,434 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import struct

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from fido2.attestation import Attestation
from fido2.ctap2 import Ctap2, CredentialManagement
from fido2.ctap2.pin import ClientPin
from fido2.hid import CTAPHID
from fido2.utils import hmac_sha256
from fido2.webauthn import PublicKeyCredentialCreationOptions

from .. import helpers


# Base class
# Currently some methods are implemented here since they are the same in both devices.
class SoloClient:
    def __init__(
        self,
    ):
        self.origin = "https://example.org"
        self.host = "example.org"
        self.user_id = b"they"
        self.do_reboot = True

    def set_reboot(self, val):
        """option to reboot after programming"""
        self.do_reboot = val

    def reboot(
        self,
    ):
        pass

    def find_device(self, dev=None, solo_serial=None):
        pass

    def get_current_hid_device(
        self,
    ):
        """Return current device class for CTAPHID interface if available."""
        pass

    def get_current_fido_client(
        self,
    ):
        """Return current fido2 client if available."""
        pass

    def send_data_hid(self, cmd, data):
        if not isinstance(data, bytes):
            data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
        with helpers.Timeout(1.0) as event:
            return self.get_current_hid_device().call(cmd, data, event)

    def bootloader_version(
        self,
    ):
        pass

    def solo_version(
        self,
    ):
        pass

    def get_rng(self, num=0):
        pass

    def wink(
        self,
    ):
        self.send_data_hid(CTAPHID.WINK, b"")

    def ping(self, data="pong"):
        return self.send_data_hid(CTAPHID.PING, data)

    def reset(
        self,
    ):
        Ctap2(self.get_current_hid_device()).reset()

    def change_pin(self, old_pin, new_pin):
        client = ClientPin(self.ctap2)
        client.change_pin(old_pin, new_pin)

    def set_pin(self, new_pin):
        client = ClientPin(self.ctap2)
        client.set_pin(new_pin)

    def make_credential(self):
        client = self.get_current_fido_client()
        rp = {"id": self.host, "name": "example site"}
        user = {"id": self.user_id, "name": "example user"}
        challenge = b"Y2hhbGxlbmdl"
        options = PublicKeyCredentialCreationOptions(
            rp,
            user,
            challenge,
            [{"type": "public-key", "alg": -8}, {"type": "public-key", "alg": -7}],
        )
        result = client.make_credential(options)
        attest = result.attestation_object
        data = result.client_data
        try:
            attest.verify(data.hash)
        except AttributeError:
            verifier = Attestation.for_type(attest.fmt)
            verifier().verify(attest.att_stmt, attest.auth_data, data.hash)
        print("Register valid")
        x5c = attest.att_stmt["x5c"][0]
        cert = x509.load_der_x509_certificate(x5c, default_backend())

        return cert

    def cred_mgmt(self, pin):
        client = ClientPin(self.ctap2)
        token = client.get_pin_token(pin)
        return CredentialManagement(self.ctap2, client.protocol, token)

    def enter_solo_bootloader(
        self,
    ):
        """
        If solo is configured as solo hacker or something similar,
        this command will tell the token to boot directly to the bootloader
        so it can be reprogrammed
        """
        pass

    def enter_bootloader_or_die(self):
        pass

    def is_solo_bootloader(
        self,
    ):
        """For now, solo bootloader could be the NXP bootrom on Solo v2."""
        pass

    def program_kbd(self, cmd):
        ctap2 = Ctap2(self.get_current_hid_device())
        return ctap2.send_cbor(0x51, cmd)

    def sign_hash(self, credential_id, dgst, pin):
        ctap2 = Ctap2(self.get_current_hid_device())
        client = ClientPin(ctap2)
        if pin:
            pin_token = client.get_pin_token(pin)
            pin_auth = hmac_sha256(pin_token, dgst)[:16]
            return ctap2.send_cbor(
                0x50,
                {1: dgst, 2: {"id": credential_id, "type": "public-key"}, 3: pin_auth},
            )
        else:
            return ctap2.send_cbor(
                0x50, {1: dgst, 2: {"id": credential_id, "type": "public-key"}}
            )

    def program_file(self, name):
        pass