Annotation of src/usr.bin/ssh/PROTOCOL.u2f, Revision 1.6
1.1 djm 1: This document describes OpenSSH's support for U2F/FIDO security keys.
2:
3: Background
4: ----------
5:
6: U2F is an open standard for two-factor authentication hardware, widely
7: used for user authentication to websites. U2F tokens are ubiquitous,
8: available from a number of manufacturers and are currently by far the
9: cheapest way for users to achieve hardware-backed credential storage.
10:
11: The U2F protocol however cannot be trivially used as an SSH protocol key
12: type as both the inputs to the signature operation and the resultant
13: signature differ from those specified for SSH. For similar reasons,
14: integration of U2F devices cannot be achieved via the PKCS#11 API.
15:
16: U2F also offers a number of features that are attractive in the context
17: of SSH authentication. They can be configured to require indication
18: of "user presence" for each signature operation (typically achieved
19: by requiring the user touch the key). They also offer an attestation
20: mechanism at key enrollment time that can be used to prove that a
21: given key is backed by hardware. Finally the signature format includes
22: a monotonic signature counter that can be used (at scale) to detect
23: concurrent use of a private key, should it be extracted from hardware.
24:
1.2 naddy 25: U2F private keys are generated through an enrollment operation,
1.1 djm 26: which takes an application ID - a URL-like string, typically "ssh:"
27: in this case, but a HTTP origin for the case of web authentication,
28: and a challenge string (typically randomly generated). The enrollment
29: operation returns a public key, a key handle that must be used to invoke
30: the hardware-backed private key, some flags and signed attestation
1.2 naddy 31: information that may be used to verify that a private key is hosted on a
1.1 djm 32: particular hardware instance.
33:
34: It is common for U2F hardware to derive private keys from the key handle
35: in conjunction with a small per-device secret that is unique to the
36: hardware, thus requiring little on-device storage for an effectively
37: unlimited number of supported keys. This drives the requirement that
38: the key handle be supplied for each signature operation. U2F tokens
39: primarily use ECDSA signatures in the NIST-P256 field.
40:
41: SSH U2F Key formats
42: -------------------
43:
44: OpenSSH integrates U2F as a new key and corresponding certificate type:
45:
46: sk-ecdsa-sha2-nistp256@openssh.com
47: sk-ecdsa-sha2-nistp256-cert-v01@openssh.com
48:
49: These key types are supported only for user authentication with the
50: "publickey" method. They are not used for host-based user authentication
51: or server host key authentication.
52:
53: While each uses ecdsa-sha256-nistp256 as the underlying signature primitive,
54: keys require extra information in the public and private keys, and in
55: the signature object itself. As such they cannot be made compatible with
56: the existing ecdsa-sha2-nistp* key types.
57:
58: The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is:
59:
60: string "sk-ecdsa-sha2-nistp256@openssh.com"
1.5 djm 61: string curve name
1.1 djm 62: ec_point Q
63: string application (user-specified, but typically "ssh:")
64:
65: The corresponding private key contains:
66:
67: string "sk-ecdsa-sha2-nistp256@openssh.com"
1.5 djm 68: string curve name
1.1 djm 69: ec_point Q
70: string application (user-specified, but typically "ssh:")
1.6 ! djm 71: uint8 flags
1.1 djm 72: string key_handle
73: string reserved
74:
75: The certificate form of a SSH U2F key appends the usual certificate
76: information to the public key:
77:
1.2 naddy 78: string "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"
1.1 djm 79: string nonce
1.5 djm 80: string curve name
1.1 djm 81: ec_point Q
82: string application
83: uint64 serial
84: uint32 type
85: string key id
86: string valid principals
87: uint64 valid after
88: uint64 valid before
89: string critical options
90: string extensions
91: string reserved
92: string signature key
93: string signature
94:
95: During key generation, the hardware also returns attestation information
96: that may be used to cryptographically prove that a given key is
97: hardware-backed. Unfortunately, the protocol required for this proof is
98: not privacy-preserving and may be used to identify U2F tokens with at
99: least manufacturer and batch number granularity. For this reason, we
100: choose not to include this information in the public key or save it by
101: default.
102:
103: Attestation information is very useful however in an organisational
1.2 naddy 104: context, where it may be used by a CA as part of certificate
1.1 djm 105: issuance. In this case, exposure to the CA of hardware identity is
106: desirable. To support this case, OpenSSH optionally allows retaining the
107: attestation information at the time of key generation. It will take the
108: following format:
109:
110: string "sk-attest-v00"
111: uint32 version (1 for U2F, 2 for FIDO2 in future)
112: string attestation certificate
113: string enrollment signature
114:
115: SSH U2F signatures
116: ------------------
117:
118: In addition to the message to be signed, the U2F signature operation
119: requires a few additional parameters:
120:
121: byte control bits (e.g. "user presence required" flag)
122: byte[32] SHA256(message)
123: byte[32] SHA256(application)
124: byte key_handle length
125: byte[] key_handle
126:
127: This signature is signed over a blob that consists of:
128:
129: byte[32] SHA256(application)
130: byte flags (including "user present", extensions present)
131: uint32 counter
132: byte[] extensions
133: byte[32] SHA256(message)
134:
135: The signature returned from U2F hardware takes the following format:
136:
137: byte flags (including "user present")
138: uint32 counter
139: byte[32] ecdsa_signature (in X9.62 format).
140:
141: For use in the SSH protocol, we wish to avoid server-side parsing of ASN.1
142: format data in the pre-authentication attack surface. Therefore, the
143: signature format used on the wire in SSH2_USERAUTH_REQUEST packets will
1.3 markus 144: be reformatted slightly and the ecdsa_signature_blob value has the encoding:
1.1 djm 145:
146: mpint r
147: mpint s
148: byte flags
149: uint32 counter
150:
151: Where 'r' and 's' are extracted by the client or token middleware from the
152: ecdsa_signature field returned from the hardware.
153:
1.4 markus 154: For Ed25519 keys the signature is encoded as:
155:
156: string "sk-ssh-ed25519@openssh.com"
157: string signature
158: byte flags
159: uint32 counter
160:
161:
1.1 djm 162: ssh-agent protocol extensions
163: -----------------------------
164:
1.2 naddy 165: ssh-agent requires a protocol extension to support U2F keys. At
1.1 djm 166: present the closest analogue to Security Keys in ssh-agent are PKCS#11
167: tokens, insofar as they require a middleware library to communicate with
168: the device that holds the keys. Unfortunately, the protocol message used
169: to add PKCS#11 keys to ssh-agent does not include any way to send the
170: key handle to the agent as U2F keys require.
171:
1.2 naddy 172: To avoid this, without having to add wholly new messages to the agent
173: protocol, we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message
174: with a new key constraint extension to encode a path to the middleware
1.1 djm 175: library for the key. The format of this constraint extension would be:
176:
177: byte SSH_AGENT_CONSTRAIN_EXTENSION
178: string sk@openssh.com
179: string middleware path
180:
181: This constraint-based approach does not present any compatibility
182: problems.
183:
184: OpenSSH integration
185: -------------------
186:
187: U2F tokens may be attached via a number of means, including USB and NFC.
188: The USB interface is standardised around a HID protocol, but we want to
189: be able to support other transports as well as dummy implementations for
190: regress testing. For this reason, OpenSSH shall perform all U2F operations
191: via a dynamically-loaded middleware library.
192:
193: The middleware library need only expose a handful of functions:
194:
195: /* Flags */
196: #define SSH_SK_USER_PRESENCE_REQD 0x01
197:
1.3 markus 198: /* Algs */
199: #define SSH_SK_ECDSA 0x00
200: #define SSH_SK_ED25519 0x01
201:
1.1 djm 202: struct sk_enroll_response {
203: uint8_t *public_key;
204: size_t public_key_len;
205: uint8_t *key_handle;
206: size_t key_handle_len;
207: uint8_t *signature;
208: size_t signature_len;
209: uint8_t *attestation_cert;
210: size_t attestation_cert_len;
211: };
212:
213: struct sk_sign_response {
214: uint8_t flags;
215: uint32_t counter;
216: uint8_t *sig_r;
217: size_t sig_r_len;
218: uint8_t *sig_s;
219: size_t sig_s_len;
220: };
221:
222: /* Return the version of the middleware API */
223: uint32_t sk_api_version(void);
224:
225: /* Enroll a U2F key (private key generation) */
1.3 markus 226: int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
1.1 djm 227: const char *application, uint8_t flags,
228: struct sk_enroll_response **enroll_response);
229:
230: /* Sign a challenge */
1.3 markus 231: int sk_sign(int alg, const uint8_t *message, size_t message_len,
1.1 djm 232: const char *application,
233: const uint8_t *key_handle, size_t key_handle_len,
234: uint8_t flags, struct sk_sign_response **sign_response);
235:
236: In OpenSSH, these will be invoked by generalising the existing
237: ssh-pkcs11-helper mechanism to provide containment of the middleware from
238: ssh-agent.
239: