зеркало из https://github.com/Azure/sonic-openssh.git
upstream: Protocol documentation for U2F/FIDO keys in OpenSSH
OpenBSD-Commit-ID: 8f3247317c2909870593aeb306dff848bc427915
This commit is contained in:
Родитель
f4fdcd2b7a
Коммит
57ecc10628
|
@ -0,0 +1,224 @@
|
|||
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 generatted 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 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@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 an 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:
|
||||
|
||||
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 some 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 wholy new messages to the agent
|
||||
protocol we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message
|
||||
with a new a 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
|
||||
|
||||
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(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(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.
|
||||
|
Загрузка…
Ссылка в новой задаче