summaryrefslogtreecommitdiff
path: root/PROTOCOL.u2f
blob: bd60f9fac5ff9be56f1a75e7af170f84c72d6e02 (plain)
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
This document describes OpenSSH's support for U2F/FIDO security keys.

Background
----------

U2F is an open standard for two-factor authentication hardware, widely
used for user authentication to websites. U2F tokens are ubiquitous,
available from a number of manufacturers and are currently by far the
cheapest way for users to achieve hardware-backed credential storage.

The U2F protocol however cannot be trivially used as an SSH protocol key
type as both the inputs to the signature operation and the resultant
signature differ from those specified for SSH. For similar reasons,
integration of U2F devices cannot be achieved via the PKCS#11 API.

U2F also offers a number of features that are attractive in the context
of SSH authentication. They can be configured to require indication
of "user presence" for each signature operation (typically achieved
by requiring the user touch the key). They also offer an attestation
mechanism at key enrollment time that can be used to prove that a
given key is backed by hardware. Finally the signature format includes
a monotonic signature counter that can be used (at scale) to detect
concurrent use of a private key, should it be extracted from hardware.

U2F private keys are generated through an enrollment operation,
which takes an application ID - a URL-like string, typically "ssh:"
in this case, but a HTTP origin for the case of web authentication,
and a challenge string (typically randomly generated). The enrollment
operation returns a public key, a key handle that must be used to invoke
the hardware-backed private key, some flags and signed attestation
information that may be used to verify that a private key is hosted on a
particular hardware instance.

It is common for U2F hardware to derive private keys from the key handle
in conjunction with a small per-device secret that is unique to the
hardware, thus requiring little on-device storage for an effectively
unlimited number of supported keys. This drives the requirement that
the key handle be supplied for each signature operation. U2F tokens
primarily use ECDSA signatures in the NIST-P256 field.

SSH U2F Key formats
-------------------

OpenSSH integrates U2F as a new key and corresponding certificate type:

	sk-ecdsa-sha2-nistp256@openssh.com
	sk-ecdsa-sha2-nistp256-cert-v01@openssh.com

These key types are supported only for user authentication with the
"publickey" method. They are not used for host-based user authentication
or server host key authentication.

While each uses ecdsa-sha256-nistp256 as the underlying signature primitive,
keys require extra information in the public and private keys, and in
the signature object itself. As such they cannot be made compatible with
the existing ecdsa-sha2-nistp* key types.

The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is:

	string		"sk-ecdsa-sha2-nistp256@openssh.com"
	ec_point	Q
	string		application (user-specified, but typically "ssh:")

The corresponding private key contains:

	string		"sk-ecdsa-sha2-nistp256@openssh.com"
	ec_point	Q
	string		application (user-specified, but typically "ssh:")
	string		key_handle
	uint32		flags
	string		reserved

The certificate form of a SSH U2F key appends the usual certificate
information to the public key:

	string		"sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"
	string		nonce
	ec_point	Q
	string		application
	uint64		serial
	uint32		type
	string		key id
	string		valid principals
	uint64		valid after
	uint64		valid before
	string		critical options
	string		extensions
	string		reserved
	string		signature key
	string		signature

During key generation, the hardware also returns attestation information
that may be used to cryptographically prove that a given key is
hardware-backed. Unfortunately, the protocol required for this proof is
not privacy-preserving and may be used to identify U2F tokens with at
least manufacturer and batch number granularity. For this reason, we
choose not to include this information in the public key or save it by
default.

Attestation information is very useful however in an organisational
context, where it may be used by a CA as part of certificate
issuance. In this case, exposure to the CA of hardware identity is
desirable. To support this case, OpenSSH optionally allows retaining the
attestation information at the time of key generation. It will take the
following format:

	string		"sk-attest-v00"
	uint32		version		(1 for U2F, 2 for FIDO2 in future)
	string		attestation certificate
	string		enrollment signature

SSH U2F signatures
------------------

In addition to the message to be signed, the U2F signature operation 
requires a few additional parameters:

	byte		control bits (e.g. "user presence required" flag)
	byte[32]	SHA256(message)
	byte[32]	SHA256(application)
	byte		key_handle length
	byte[]		key_handle

This signature is signed over a blob that consists of:

	byte[32]	SHA256(application)
	byte		flags (including "user present", extensions present)
	uint32		counter
	byte[]		extensions
	byte[32]	SHA256(message)

The signature returned from U2F hardware takes the following format:

	byte		flags (including "user present")
	uint32		counter
	byte[32]	ecdsa_signature (in X9.62 format).

For use in the SSH protocol, we wish to avoid server-side parsing of ASN.1
format data in the pre-authentication attack surface. Therefore, the
signature format used on the wire in SSH2_USERAUTH_REQUEST packets will
be reformatted slightly and the ecdsa_signature_blob value has the encoding:

	mpint		r
	mpint		s
	byte		flags
	uint32		counter

Where 'r' and 's' are extracted by the client or token middleware from the
ecdsa_signature field returned from the hardware.

ssh-agent protocol extensions
-----------------------------

ssh-agent requires a protocol extension to support U2F keys. At
present the closest analogue to Security Keys in ssh-agent are PKCS#11
tokens, insofar as they require a middleware library to communicate with
the device that holds the keys. Unfortunately, the protocol message used
to add PKCS#11 keys to ssh-agent does not include any way to send the
key handle to the agent as U2F keys require.

To avoid this, without having to add wholly new messages to the agent
protocol, we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message
with a new key constraint extension to encode a path to the middleware
library for the key. The format of this constraint extension would be:

	byte		SSH_AGENT_CONSTRAIN_EXTENSION
	string		sk@openssh.com
	string		middleware path

This constraint-based approach does not present any compatibility
problems.

OpenSSH integration
-------------------

U2F tokens may be attached via a number of means, including USB and NFC.
The USB interface is standardised around a HID protocol, but we want to
be able to support other transports as well as dummy implementations for
regress testing. For this reason, OpenSSH shall perform all U2F operations
via a dynamically-loaded middleware library.

The middleware library need only expose a handful of functions:

	/* Flags */
	#define SSH_SK_USER_PRESENCE_REQD	0x01

	/* Algs */
	#define SSH_SK_ECDSA                   0x00
	#define SSH_SK_ED25519                 0x01

	struct sk_enroll_response {
		uint8_t *public_key;
		size_t public_key_len;
		uint8_t *key_handle;
		size_t key_handle_len;
		uint8_t *signature;
		size_t signature_len;
		uint8_t *attestation_cert;
		size_t attestation_cert_len;
	};

	struct sk_sign_response {
		uint8_t flags;
		uint32_t counter;
		uint8_t *sig_r;
		size_t sig_r_len;
		uint8_t *sig_s;
		size_t sig_s_len;
	};

	/* Return the version of the middleware API */
	uint32_t sk_api_version(void);

	/* Enroll a U2F key (private key generation) */
	int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
	    const char *application, uint8_t flags,
	    struct sk_enroll_response **enroll_response);

	/* Sign a challenge */
	int sk_sign(int alg, const uint8_t *message, size_t message_len,
	    const char *application,
	    const uint8_t *key_handle, size_t key_handle_len,
	    uint8_t flags, struct sk_sign_response **sign_response);

In OpenSSH, these will be invoked by generalising the existing
ssh-pkcs11-helper mechanism to provide containment of the middleware from
ssh-agent.