-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcertificate.py
More file actions
113 lines (103 loc) · 5.16 KB
/
certificate.py
File metadata and controls
113 lines (103 loc) · 5.16 KB
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
# encoding: utf-8
# author: LY
# license: Feitian Technologies Co., Ltd. 2019-2022
# file: certificate.py
# time: 2021-11-05 13:26
# desc:
import getpass
import datetime
import sys
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from fido2 import ctap
from fido2 import ctap2
from fido2 import hid
OPENSK_VID_PID = (0x1915, 0x521F)
OPENSK_VENDOR_CONFIGURE = 0x40
class Certificate:
@staticmethod
def get_opensk_devices(batch_mode):
devices = []
for dev in hid.CtapHidDevice.list_devices():
if (dev.descriptor.vid, dev.descriptor.pid) == OPENSK_VID_PID:
if dev.capabilities & hid.CAPABILITY.CBOR:
if batch_mode:
devices.append(ctap2.CTAP2(dev))
else:
return [ctap2.CTAP2(dev)]
return devices
def get_private_key(self, data, password=None):
# First we try without password.
try:
return serialization.load_pem_private_key(data, password=None)
except TypeError:
# Maybe we need a password then.
if sys.stdin.isatty():
password = getpass.getpass(prompt="Private key password: ")
else:
password = sys.stdin.readline().rstrip()
return self.get_private_key(data, password=password.encode(sys.stdin.encoding))
def update(self, certificate_file, private_key_file):
try:
cbor_data = {1: False}
with open(private_key_file, 'rb') as f:
priv_key = self.get_private_key(f.read())
if not isinstance(priv_key, ec.EllipticCurvePrivateKey):
raise Exception("Private key must be an Elliptic Curve one.")
if not isinstance(priv_key.curve, ec.SECP256R1):
raise Exception("Private key must use Secp256r1 curve.")
if priv_key.key_size != 256:
raise Exception("Private key must be 256 bits long.")
with open(certificate_file, 'rb') as f:
cert = x509.load_pem_x509_certificate(f.read())
# Some sanity/validity checks
now = datetime.datetime.utcnow()
if cert.not_valid_before > now:
raise Exception("Certificate validity starts in the future.")
if cert.not_valid_after <= now:
raise Exception("Certificate expired.")
pub_key = cert.public_key()
if not isinstance(pub_key, ec.EllipticCurvePublicKey):
raise Exception("Certificate public key must be an Elliptic Curve one.")
if not isinstance(pub_key.curve, ec.SECP256R1):
raise Exception("Certificate public key must use Secp256r1 curve.")
if pub_key.key_size != 256:
raise Exception("Certificate public key must be 256 bits long.")
if pub_key.public_numbers() != priv_key.public_key().public_numbers():
raise Exception("Certificate public doesn't match with the private key.")
cbor_data[2] = {
1:
cert.public_bytes(serialization.Encoding.DER),
2:
priv_key.private_numbers().private_value.to_bytes(
length=32, byteorder='big', signed=False)
}
for authenticator in self.get_opensk_devices(False):
# If the device supports it, wink to show which device
# we're going to program.
if authenticator.device.capabilities & hid.CAPABILITY.WINK:
authenticator.device.wink()
try:
result = authenticator.send_cbor(
OPENSK_VENDOR_CONFIGURE,
data=cbor_data,
)
if not result[1]:
raise Exception("Certificate: Missing")
if not result[2]:
raise Exception("Private Key: Missing")
except ctap.CtapError as ex:
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
raise Exception("Failed to configure OpenSK (unsupported command).")
elif ex.code.value == 0xF2: # VENDOR_INTERNAL_ERROR
raise Exception("Failed to configure OpenSK (lockdown conditions not met or hardware error).")
elif ex.code.value == ctap.CtapError.ERR.INVALID_PARAMETER:
raise Exception("Failed to configure OpenSK (device is partially programmed but "
"the given cert/key don't match the ones currently programmed).")
else:
raise Exception("Failed to configure OpenSK (unknown error: {}".format(ex))
return
raise Exception("Only openSK devices are supported.")
except Exception as e:
raise e