diff --git a/docs/libssh2_sign_sk.3 b/docs/libssh2_sign_sk.3 new file mode 100644 index 00000000..7aa503d6 --- /dev/null +++ b/docs/libssh2_sign_sk.3 @@ -0,0 +1,85 @@ +.TH libssh2_sign_sk 3 "1 Jun 2022" "libssh2 1.10.0" "libssh2 manual" +.SH NAME +libssh2_sign_sk - Create a signature from a FIDO2 authenticator. +.SH SYNOPSIS +#include +.nf +int libssh2_sign_sk(LIBSSH2_SESSION *session, + unsigned char **sig, + size_t *sig_len, + const unsigned char *data, + size_t data_len, + void **abstract); + +typedef struct _LIBSSH2_PRIVKEY_SK { + int algorithm; + uint8_t flags; + const char *application; + const unsigned char *key_handle; + size_t handle_len; + LIBSSH2_USERAUTH_SK_SIGN_FUNC((*sign_callback)); + void **orig_abstract; +} LIBSSH2_PRIVKEY_SK; + +.SH DESCRIPTION +\fIsession\fP - Session instance as returned by +.BR libssh2_session_init_ex(3) + +\fIsig\fP - A pointer to a buffer in which to place the signature. The caller +is responsible for freeing the signature with LIBSSH2_FREE. + +\fIsig_len\fP - A pointer to the length of the sig parameter. + +\fIdata\fP - The data to sign. + +\fIdata_len\fP - The length of the data parameter. + +\fIabstract\fP - A pointer to a pointer to a LIBSSH2_PRIVKEY_SK. See +description below. + +Create a signature from a FIDO2 authenticator, using either the +sk-ssh-ed25519@openssh.com or sk-ecdsa-sha2-nistp256@openssh.com key +exchange algorithms. + +The abstract parameter is a pointer to a pointer due to the internal workings +of libssh2. The LIBSSH2_PRIVKEY_SK must be completely filled out, and the +caller is responsible for all memory management of its fields. + +\fIalgorithm\fP - The signing algorithm to use. Possible values are +LIBSSH2_HOSTKEY_TYPE_ED25519 and LIBSSH2_HOSTKEY_TYPE_ECDSA_256. + +\fIflags\fP - A bitmask specifying options for the authenticator. When +LIBSSH2_SK_PRESENCE_REQUIRED is set, the authenticator requires a touch. When +LIBSSH2_SK_VERIFICATION_REQUIRED is set, the authenticator requires a PIN. +Many servers and authenticators do not work properly when +LIBSSH2_SK_PRESENCE_REQUIRED is not set. + +\fIapplication\fP - A user-defined string to use as the RP name for the +authenticator. Usually "ssh:". + +\fIkey_handle\fP - The key handle to use for the authenticator's allow list. + +\fIhandle_len\fP - The length of the key_handle parameter. + +\fIabstract\fP - User-defined data. When a PIN is required, use this to pass in +the PIN, or a function pointer to retrieve the PIN. + +\fIkey_handle\fP The decoded key handle from the private key file. + +\fIhandle_len\fP The length of the key_handle parameter. + +\fIsign_callback\fP - Responsible for communicating with the hardware +authenticator to generate a signature. On success, the signature information +must be placed in the `\fIsig_info\fP sig_info parameter and the callback must +return 0. On failure, it should return a negative number. See +.BR libssh2_userauth_publickey_sk(3) + for more information. + +\fIorig_abstract\fP - User-defined data. When a PIN is required, use this to +pass in the PIN, or a function pointer to retrieve the PIN. + +.SH RETURN VALUE +Return 0 on success or negative on failure. + +.SH SEE ALSO +.BR libssh2_userauth_publickey_sk(3) diff --git a/docs/libssh2_userauth_publickey_sk.3 b/docs/libssh2_userauth_publickey_sk.3 new file mode 100644 index 00000000..4ed54c90 --- /dev/null +++ b/docs/libssh2_userauth_publickey_sk.3 @@ -0,0 +1,133 @@ +.TH libssh2_userauth_publickey_sk 3 "1 Jun 2022" "libssh2 1.10.0" "libssh2 manual" +.SH NAME +libssh2_userauth_publickey_sk - authenticate a session with a FIDO2 authenticator +.SH SYNOPSIS +#include +.nf +int libssh2_userauth_publickey_sk(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase, + LIBSSH2_USERAUTH_SK_SIGN_FUNC + ((*sign_callback)), + void **abstract); + +.SH CALLBACK +.nf +#define LIBSSH2_SK_PRESENCE_REQUIRED 0x01 +#define LIBSSH2_SK_VERIFICATION_REQUIRED 0x04 + +typedef struct _LIBSSH2_SK_SIG_INFO { + uint8_t flags; + uint32_t counter; + unsigned char *sig_r; + size_t sig_r_len; + unsigned char *sig_s; + size_t sig_s_len; +} LIBSSH2_SK_SIG_INFO; + +int name(LIBSSH2_SESSION *session, LIBSSH2_SK_SIG_INFO *sig_info, + const unsigned char *data, size_t data_len, int algorithm, + uint8_t flags, const char *application, + const unsigned char *key_handle, size_t handle_len, + void **abstract); +.fi + +.SH DESCRIPTION +\fIsession\fP - Session instance as returned by +.BR libssh2_session_init_ex(3) + +\fIusername\fP - Name of user to attempt authentication for. + +\fIusername_len\fP - Length of username parameter. + +\fIprivatekeydata\fP - Buffer containing the contents of a private key file. + +\fIprivatekeydata_len\fP - Length of private key data. + +\fIpassphrase\fP - Passphrase to use when decoding private key file. + +\fIsign_callback\fP - Callback to communicate with FIDO2 authenticator. + +\fIabstract\fP - User-provided data to pass to callback. + +Attempt FIDO2 authentication. using either the sk-ssh-ed25519@openssh.com or +sk-ecdsa-sha2-nistp256@openssh.com key exchange algorithms. + +This function is only supported when libssh2 is backed by OpenSSL. + +.SH CALLBACK DESCRIPTION +\fIsession\fP - Session instance as returned by +.BR libssh2_session_init_ex(3) + +\fIsig_info\fP - Filled in by the callback with the signature and accompanying +information from the authenticator. + +\fIdata\fP - The data to sign. + +\fIdata_len\fP - The length of the data parameter. + +\fIalgorithm\fP - The signing algorithm to use. Possible values are +LIBSSH2_HOSTKEY_TYPE_ED25519 and LIBSSH2_HOSTKEY_TYPE_ECDSA_256. + +\fIflags\fP - A bitmask specifying options for the authenticator. When +LIBSSH2_SK_PRESENCE_REQUIRED is set, the authenticator requires a touch. When +LIBSSH2_SK_VERIFICATION_REQUIRED is set, the authenticator requires a PIN. +Many servers and authenticators do not work properly when +LIBSSH2_SK_PRESENCE_REQUIRED is not set. + +\fIapplication\fP - A user-defined string to use as the RP name for the +authenticator. Usually "ssh:". + +\fIkey_handle\fP - The key handle to use for the authenticator's allow list. + +\fIhandle_len\fP - The length of the key_handle parameter. + +\fIabstract\fP - User-defined data. When a PIN is required, use this to pass in +the PIN, or a function pointer to retrieve the PIN. + +The \fIsign_callback\fP is responsible for communicating with the hardware +authenticator to generate a signature. On success, the signature information +must be placed in the `\fIsig_info\fP sig_info parameter and the callback must +return 0. On failure, it should return a negative number. + +The fields of the LIBSSH2_SK_SIG_INFO are as follows. + +\fIflags\fP - A bitmask specifying options for the authenticator. This should +be read from the authenticator and not merely copied from the flags parameter +to the callback. + +\fIcounter\fP - A value returned from the authenticator. + +\fIsig_r\fP - For Ed25519 signatures, this contains the entire signature, as +returned directly from the authenticator. For ECDSA signatures, this contains +the r component of the signature in a big-endian binary representation. For +both algorithms, use LIBSSH2_ALLOC to allocate memory. It will be freed by the +caller. + +\fIsig_r_len\fP - The length of the sig_r parameter. + +\fIsig_s\fP - For ECDSA signatures, this contains the s component of the +signature in a big-endian binary representation. Use LIBSSH2_ALLOC to allocate +memory. It will be freed by the caller. For Ed25519 signatures, set this to +NULL. + +\fIsig_s_len\fP - The length of the sig_s parameter. + +.SH RETURN VALUE +Return 0 on success or negative on failure. It returns +LIBSSH2_ERROR_EAGAIN when it would otherwise block. While +LIBSSH2_ERROR_EAGAIN is a negative number, it isn't really a failure per se. + +.SH ERRORS +Some of the errors this function may return include: + +\fILIBSSH2_ERROR_ALLOC\fP - An internal memory allocation call failed. + +\fILIBSSH2_ERROR_SOCKET_SEND\fP - Unable to send data on socket. + +\fILIBSSH2_ERROR_AUTHENTICATION_FAILED\fP - failed, invalid username/key. +.SH SEE ALSO +.BR libssh2_session_init_ex(3) diff --git a/include/libssh2.h b/include/libssh2.h index ef3ce115..b79569da 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -283,6 +283,15 @@ typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE unsigned int length; } LIBSSH2_USERAUTH_KBDINT_RESPONSE; +typedef struct _LIBSSH2_SK_SIG_INFO { + uint8_t flags; + uint32_t counter; + unsigned char *sig_r; + size_t sig_r_len; + unsigned char *sig_s; + size_t sig_s_len; +} LIBSSH2_SK_SIG_INFO; + /* 'publickey' authentication callback */ #define LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC(name) \ int name(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, \ @@ -295,6 +304,17 @@ typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, \ LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) +/* SK authentication callback */ +#define LIBSSH2_USERAUTH_SK_SIGN_FUNC(name) \ +int name(LIBSSH2_SESSION *session, LIBSSH2_SK_SIG_INFO *sig_info, \ +const unsigned char *data, size_t data_len, int algorithm, uint8_t flags, \ +const char *application, const unsigned char *key_handle, size_t handle_len, \ +void **abstract) + +/* Flags for SK authentication */ +#define LIBSSH2_SK_PRESENCE_REQUIRED 0x01 +#define LIBSSH2_SK_VERIFICATION_REQUIRED 0x04 + /* Callbacks for special SSH packets */ #define LIBSSH2_IGNORE_FUNC(name) \ void name(LIBSSH2_SESSION *session, const char *message, int message_len, \ @@ -368,6 +388,25 @@ typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; typedef struct _LIBSSH2_KNOWNHOSTS LIBSSH2_KNOWNHOSTS; typedef struct _LIBSSH2_AGENT LIBSSH2_AGENT; +/* SK signature callback */ +typedef struct _LIBSSH2_PRIVKEY_SK { + int algorithm; + uint8_t flags; + const char *application; + const unsigned char *key_handle; + size_t handle_len; + LIBSSH2_USERAUTH_SK_SIGN_FUNC((*sign_callback)); + void **orig_abstract; +} LIBSSH2_PRIVKEY_SK; + +int +libssh2_sign_sk(LIBSSH2_SESSION *session, + unsigned char **sig, + size_t *sig_len, + const unsigned char *data, + size_t data_len, + void **abstract); + typedef struct _LIBSSH2_POLLFD { unsigned char type; /* LIBSSH2_POLLFD_* below */ @@ -711,6 +750,17 @@ libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION* session, (unsigned int)strlen(username), \ (response_callback)) +LIBSSH2_API int +libssh2_userauth_publickey_sk(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase, + LIBSSH2_USERAUTH_SK_SIGN_FUNC + ((*sign_callback)), + void **abstract); + LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, long timeout); diff --git a/src/crypto.h b/src/crypto.h index 7a99b4f3..0abbd5b8 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -147,12 +147,23 @@ _libssh2_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx ** ecdsactx, const unsigned char *k, size_t k_len, libssh2_curve_type type); + int _libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx, LIBSSH2_SESSION * session, const char *filename, unsigned const char *passphrase); +int +_libssh2_ecdsa_new_private_sk(libssh2_ecdsa_ctx ** ec_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); + int _libssh2_ecdsa_verify(libssh2_ecdsa_ctx * ctx, const unsigned char *r, size_t r_len, @@ -182,6 +193,16 @@ int _libssh2_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx ** ec_ctx, size_t filedata_len, unsigned const char *passphrase); +int _libssh2_ecdsa_new_private_frommemory_sk(libssh2_ecdsa_ctx ** ec_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); + libssh2_curve_type _libssh2_ecdsa_get_curve_type(libssh2_ecdsa_ctx *ec_ctx); @@ -211,6 +232,16 @@ _libssh2_ed25519_new_private(libssh2_ed25519_ctx **ed_ctx, LIBSSH2_SESSION *session, const char *filename, const uint8_t *passphrase); +int +_libssh2_ed25519_new_private_sk(libssh2_ed25519_ctx **ed_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION *session, + const char *filename, + const uint8_t *passphrase); + int _libssh2_ed25519_new_public(libssh2_ed25519_ctx **ed_ctx, LIBSSH2_SESSION *session, @@ -229,6 +260,17 @@ _libssh2_ed25519_new_private_frommemory(libssh2_ed25519_ctx **ed_ctx, size_t filedata_len, unsigned const char *passphrase); +int +_libssh2_ed25519_new_private_frommemory_sk(libssh2_ed25519_ctx **ed_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); + #endif /* LIBSSH2_ED25519 */ @@ -259,6 +301,20 @@ int _libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, const char *passphrase); +int _libssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase); + /** * @function _libssh2_supported_key_sign_algorithms * @abstract Returns supported algorithms used for upgrading public diff --git a/src/libgcrypt.c b/src/libgcrypt.c index f6e9b64a..2e0a8db4 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -627,6 +627,26 @@ _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, "Method unimplemented in libgcrypt backend"); } +int +_libssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public SK key from private key file: " + "Method unimplemented in libgcrypt backend"); +} + void _libssh2_init_aes_ctr(void) { /* no implementation */ diff --git a/src/mbedtls.c b/src/mbedtls.c index 4ff751b3..9de8ed6d 100644 --- a/src/mbedtls.c +++ b/src/mbedtls.c @@ -741,6 +741,26 @@ _libssh2_mbedtls_pub_priv_keyfilememory(LIBSSH2_SESSION *session, return ret; } +int +_libssh2_mbedtls_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public SK key from private key file: " + "Method unimplemented in mbedTLS backend"); +} + void _libssh2_init_aes_ctr(void) { /* no implementation */ diff --git a/src/mbedtls.h b/src/mbedtls.h index e86ebd26..891f07da 100644 --- a/src/mbedtls.h +++ b/src/mbedtls.h @@ -342,6 +342,10 @@ typedef enum { pk, pk_len, pw) \ _libssh2_mbedtls_pub_priv_keyfilememory(s, m, m_len, p, p_len, \ pk, pk_len, pw) +#define _libssh2_sk_pub_keyfilememory(s, m, m_len, p, p_len, alg, app, \ + f, kh, kh_len, pk, pk_len, pw) \ + _libssh2_mbedtls_sk_pub_keyfilememory(s, m, m_len, p, p_len, alg, app, \ + f, kh, kh_len, pk, pk_len, pw) /*******************************************************************/ diff --git a/src/misc.c b/src/misc.c index ae0fc91b..0c84fa4b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -243,6 +243,30 @@ void _libssh2_store_str(unsigned char **buf, const char *str, size_t len) } } +/* _libssh2_store_bignum2_bytes + */ +void _libssh2_store_bignum2_bytes(unsigned char **buf, + const unsigned char *bytes, + size_t len) +{ + int extraByte = 0; + const unsigned char *p; + for(p = bytes; len > 0 && *p == 0; --len, ++p) {} + + extraByte = (len > 0 && (p[0] & 0x80) != 0); + _libssh2_store_u32(buf, len + extraByte); + + if(extraByte) { + *buf[0] = 0; + *buf += 1; + } + + if(len > 0) { + memcpy(*buf, p, len); + *buf += len; + } +} + /* Base64 Conversion */ static const short base64_reverse_table[256] = { diff --git a/src/misc.h b/src/misc.h index d4d62684..4e355fcd 100644 --- a/src/misc.h +++ b/src/misc.h @@ -85,6 +85,9 @@ libssh2_uint64_t _libssh2_ntohu64(const unsigned char *buf); void _libssh2_htonu32(unsigned char *buf, uint32_t val); void _libssh2_store_u32(unsigned char **buf, uint32_t value); void _libssh2_store_str(unsigned char **buf, const char *str, size_t len); +void _libssh2_store_bignum2_bytes(unsigned char **buf, + const unsigned char *bytes, + size_t len); void *_libssh2_calloc(LIBSSH2_SESSION *session, size_t size); void _libssh2_explicit_zero(void *buf, size_t size); diff --git a/src/openssl.c b/src/openssl.c index 857110f3..ef56e938 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -57,6 +57,23 @@ read_openssh_private_key_from_memory(void **key_ctx, LIBSSH2_SESSION *session, size_t filedata_len, unsigned const char *passphrase); +static int +_libssh2_sk_pub_openssh_keyfilememory(LIBSSH2_SESSION *session, + void **key_ctx, + const char *key_type, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + unsigned const char *passphrase); + static unsigned char * write_bn(unsigned char *buf, const BIGNUM *bn, int bn_bytes) { @@ -1498,6 +1515,34 @@ _libssh2_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx ** ec_ctx, return rc; } +int _libssh2_ecdsa_new_private_frommemory_sk(libssh2_ecdsa_ctx ** ec_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase) +{ + int algorithm; + return _libssh2_sk_pub_openssh_keyfilememory(session, + (void **)ec_ctx, + "sk-ecdsa-sha2-nistp256@openssh.com", + NULL, + NULL, + NULL, + NULL, + &algorithm, + flags, + application, + key_handle, + handle_len, + filedata, + filedata_len, + passphrase); +} + #endif /* LIBSSH2_ECDSA */ @@ -1780,6 +1825,160 @@ clean_exit: return -1; } +static int +gen_publickey_from_sk_ed25519_openssh_priv_data(LIBSSH2_SESSION *session, + struct string_buf *decrypted, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + libssh2_ed25519_ctx **out_ctx) +{ + const char *key_type = "sk-ssh-ed25519@openssh.com"; + + libssh2_ed25519_ctx *ctx = NULL; + unsigned char *method_buf = NULL; + unsigned char *key = NULL; + int ret = 0; + unsigned char *pub_key, *app; + size_t key_len = 0, app_len = 0, tmp_len = 0; + unsigned char *p; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing sk-ED25519 keys from private key data"); + + if(_libssh2_get_string(decrypted, &pub_key, &tmp_len) || + tmp_len != LIBSSH2_ED25519_KEY_LEN) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Wrong public key length"); + return -1; + } + + if(_libssh2_get_string(decrypted, &app, &app_len)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No SK application."); + return -1; + } + + if(flags != NULL && _libssh2_get_byte(decrypted, flags)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No SK flags."); + return -1; + } + + if(key_handle != NULL && handle_len != NULL) { + unsigned char *handle = NULL; + if(_libssh2_get_string(decrypted, &handle, handle_len)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No SK key_handle."); + return -1; + } + + if(*handle_len > 0) { + *key_handle = LIBSSH2_ALLOC(session, *handle_len); + + if(key_handle) { + memcpy((void *)*key_handle, handle, *handle_len); + } + } + } + + ctx = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + (const unsigned char *)pub_key, + LIBSSH2_ED25519_KEY_LEN); + + if(ret == 0) { + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from ED25519 " + "private key envelope"); + + /* sk-ssh-ed25519@openssh.com. */ + method_buf = LIBSSH2_ALLOC(session, strlen(key_type)); + if(method_buf == NULL) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for ED25519 key"); + goto clean_exit; + } + + /* Key form is: type_len(4) + type(26) + pub_key_len(4) + + pub_key(32) + application_len(4) + application(X). */ + key_len = LIBSSH2_ED25519_KEY_LEN + 38 + app_len; + key = LIBSSH2_CALLOC(session, key_len); + if(key == NULL) { + _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for ED25519 key"); + goto clean_exit; + } + + p = key; + + _libssh2_store_str(&p, key_type, strlen(key_type)); + _libssh2_store_str(&p, (const char *)pub_key, LIBSSH2_ED25519_KEY_LEN); + _libssh2_store_str(&p, (const char *)app, app_len); + + if(application != NULL && app_len > 0) { + *application = (const char *)LIBSSH2_ALLOC(session, app_len + 1); + _libssh2_explicit_zero((void *)*application, app_len + 1); + memcpy((void *)*application, app, app_len); + } + + memcpy(method_buf, key_type, strlen(key_type)); + + if(method != NULL) + *method = method_buf; + else + LIBSSH2_FREE(session, method_buf); + + if(method_len != NULL) + *method_len = strlen(key_type); + + if(pubkeydata != NULL) + *pubkeydata = key; + else if(key != NULL) + LIBSSH2_FREE(session, key); + + if(pubkeydata_len != NULL) + *pubkeydata_len = key_len; + + if(out_ctx != NULL) + *out_ctx = ctx; + else if(ctx != NULL) + _libssh2_ed25519_free(ctx); + + return 0; + } + +clean_exit: + + if(ctx) + _libssh2_ed25519_free(ctx); + + if(method_buf) + LIBSSH2_FREE(session, method_buf); + + if(key) + LIBSSH2_FREE(session, key); + + if(application != NULL && *application != NULL) { + LIBSSH2_FREE(session, (void *)application); + *application = NULL; + } + + if(key_handle != NULL && *key_handle != NULL) { + LIBSSH2_FREE(session, (void *)key_handle); + *key_handle = NULL; + } + + return -1; +} + + int _libssh2_ed25519_new_private(libssh2_ed25519_ctx ** ed_ctx, LIBSSH2_SESSION * session, @@ -1847,6 +2046,82 @@ _libssh2_ed25519_new_private(libssh2_ed25519_ctx ** ed_ctx, return rc; } +int +_libssh2_ed25519_new_private_sk(libssh2_ed25519_ctx **ed_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION *session, + const char *filename, + const uint8_t *passphrase) +{ + int rc; + FILE *fp; + unsigned char *buf; + struct string_buf *decrypted = NULL; + libssh2_ed25519_ctx *ctx = NULL; + + if(session == NULL) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Session is required"); + return -1; + } + + _libssh2_init_if_needed(); + + fp = fopen(filename, "r"); + if(!fp) { + _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to open ED25519 SK private key file"); + return -1; + } + + rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted); + fclose(fp); + if(rc) { + return rc; + } + + /* We have a new key file, now try and parse it using supported types */ + rc = _libssh2_get_string(decrypted, &buf, NULL); + + if(rc != 0 || buf == NULL) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Public key type in decrypted key data not found"); + return -1; + } + + if(strcmp("sk-ssh-ed25519@openssh.com", (const char *)buf) == 0) { + rc = gen_publickey_from_sk_ed25519_openssh_priv_data(session, + decrypted, + NULL, + NULL, + NULL, + NULL, + flags, + application, + key_handle, + handle_len, + &ctx); + } + else { + rc = -1; + } + + if(decrypted) + _libssh2_string_buf_free(session, decrypted); + + if(rc == 0) { + if(ed_ctx != NULL) + *ed_ctx = ctx; + else if(ctx != NULL) + _libssh2_ed25519_free(ctx); + } + + return rc; +} + int _libssh2_ed25519_new_private_frommemory(libssh2_ed25519_ctx ** ed_ctx, LIBSSH2_SESSION * session, @@ -1878,6 +2153,35 @@ _libssh2_ed25519_new_private_frommemory(libssh2_ed25519_ctx ** ed_ctx, passphrase); } +int +_libssh2_ed25519_new_private_frommemory_sk(libssh2_ed25519_ctx **ed_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION *session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase) +{ + int algorithm; + return _libssh2_sk_pub_openssh_keyfilememory(session, + (void **)ed_ctx, + "sk-ssh-ed25519@openssh.com", + NULL, + NULL, + NULL, + NULL, + &algorithm, + flags, + application, + key_handle, + handle_len, + filedata, + filedata_len, + passphrase); +} + int _libssh2_ed25519_new_public(libssh2_ed25519_ctx ** ed_ctx, LIBSSH2_SESSION * session, @@ -2318,6 +2622,7 @@ gen_publickey_from_ec_evp(LIBSSH2_SESSION *session, size_t *method_len, unsigned char **pubkeydata, size_t *pubkeydata_len, + int is_sk, EVP_PKEY *pk) { int rc = 0; @@ -2351,18 +2656,25 @@ gen_publickey_from_ec_evp(LIBSSH2_SESSION *session, group = EC_KEY_get0_group(ec); type = _libssh2_ecdsa_get_curve_type(ec); - method_buf = LIBSSH2_ALLOC(session, 19); + if(is_sk) + *method_len = 34; + else + *method_len = 19; + + method_buf = LIBSSH2_ALLOC(session, *method_len); if(method_buf == NULL) { return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, "out of memory"); } - if(type == LIBSSH2_EC_CURVE_NISTP256) - memcpy(method_buf, "ecdsa-sha2-nistp256", 19); + if(is_sk) + memcpy(method_buf, "sk-ecdsa-sha2-nistp256@openssh.com", *method_len); + else if(type == LIBSSH2_EC_CURVE_NISTP256) + memcpy(method_buf, "ecdsa-sha2-nistp256", *method_len); else if(type == LIBSSH2_EC_CURVE_NISTP384) - memcpy(method_buf, "ecdsa-sha2-nistp384", 19); + memcpy(method_buf, "ecdsa-sha2-nistp384", *method_len); else if(type == LIBSSH2_EC_CURVE_NISTP521) - memcpy(method_buf, "ecdsa-sha2-nistp521", 19); + memcpy(method_buf, "ecdsa-sha2-nistp521", *method_len); else { _libssh2_debug(session, LIBSSH2_TRACE_ERROR, @@ -2393,9 +2705,9 @@ gen_publickey_from_ec_evp(LIBSSH2_SESSION *session, goto clean_exit; } - /* Key form is: type_len(4) + type(19) + domain_len(4) + domain(8) + - pub_key_len(4) + pub_key(~65). */ - key_len = 4 + 19 + 4 + 8 + 4 + octal_len; + /* Key form is: type_len(4) + type(method_len) + domain_len(4) + domain(8) + + pub_key_len(4) + pub_key(~65). */ + key_len = 4 + *method_len + 4 + 8 + 4 + octal_len; key = LIBSSH2_ALLOC(session, key_len); if(key == NULL) { rc = -1; @@ -2406,16 +2718,20 @@ gen_publickey_from_ec_evp(LIBSSH2_SESSION *session, p = key; /* Key type */ - _libssh2_store_str(&p, (const char *)method_buf, 19); + _libssh2_store_str(&p, (const char *)method_buf, *method_len); /* Name domain */ - _libssh2_store_str(&p, (const char *)method_buf + 11, 8); + if(is_sk) { + _libssh2_store_str(&p, "nistp256", 8); + } + else { + _libssh2_store_str(&p, (const char *)method_buf + 11, 8); + } /* Public key */ _libssh2_store_str(&p, (const char *)octal_value, octal_len); *method = method_buf; - *method_len = 19; *pubkeydata = key; *pubkeydata_len = key_len; @@ -2504,7 +2820,7 @@ gen_publickey_from_ecdsa_openssh_priv_data(LIBSSH2_SESSION *session, rc = gen_publickey_from_ec_evp(session, method, method_len, pubkeydata, pubkeydata_len, - pk); + 0, pk); if(pk) EVP_PKEY_free(pk); @@ -2524,6 +2840,144 @@ fail: return rc; } +static int +gen_publickey_from_sk_ecdsa_openssh_priv_data(LIBSSH2_SESSION *session, + struct string_buf *decrypted, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + uint8_t *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + libssh2_ecdsa_ctx **ec_ctx) +{ + int rc = 0; + size_t curvelen, pointlen, key_len, app_len; + unsigned char *curve, *point_buf, *p, *key, *app; + EC_KEY *ec_key = NULL; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Extracting ECDSA-SK public key"); + + if(_libssh2_get_string(decrypted, &curve, &curvelen) || + curvelen == 0) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "ECDSA no curve"); + return -1; + } + + if(_libssh2_get_string(decrypted, &point_buf, &pointlen)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "ECDSA no point"); + return -1; + } + + if((rc = _libssh2_ecdsa_curve_name_with_octal_new(&ec_key, point_buf, + pointlen, LIBSSH2_EC_CURVE_NISTP256)) != 0) { + rc = -1; + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "ECDSA could not create key"); + goto fail; + } + + if(_libssh2_get_string(decrypted, &app, &app_len)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No SK application."); + goto fail; + } + + if(flags != NULL && _libssh2_get_byte(decrypted, flags)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No SK flags."); + goto fail; + } + + if(key_handle != NULL && handle_len != NULL) { + unsigned char *handle = NULL; + if(_libssh2_get_string(decrypted, &handle, handle_len)) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "No SK key_handle."); + goto fail; + } + + if(*handle_len > 0) { + *key_handle = LIBSSH2_ALLOC(session, *handle_len); + + if(*key_handle) { + memcpy((void *)*key_handle, handle, *handle_len); + } + } + } + + if(rc == 0 && ec_key != NULL && pubkeydata != NULL && method != NULL) { + EVP_PKEY *pk = EVP_PKEY_new(); + EVP_PKEY_set1_EC_KEY(pk, ec_key); + + rc = gen_publickey_from_ec_evp(session, method, method_len, + pubkeydata, pubkeydata_len, + 1, pk); + + if(pk) + EVP_PKEY_free(pk); + } + + if(rc == 0 && pubkeydata != NULL) { + key_len = *pubkeydata_len + app_len + 4; + key = LIBSSH2_ALLOC(session, key_len); + + if(key == NULL) { + rc = -1; + goto fail; + } + + p = key + *pubkeydata_len; + + memcpy(key, *pubkeydata, *pubkeydata_len); + _libssh2_store_str(&p, (const char *)app, app_len); + + if(application != NULL && app_len > 0) { + *application = (const char *)LIBSSH2_ALLOC(session, app_len + 1); + _libssh2_explicit_zero((void *)*application, app_len + 1); + memcpy((void *)*application, app, app_len); + } + + LIBSSH2_FREE(session, *pubkeydata); + *pubkeydata_len = key_len; + + if(pubkeydata != NULL) + *pubkeydata = key; + else if(key != NULL) + LIBSSH2_FREE(session, key); + } + + if(ec_ctx != NULL) + *ec_ctx = ec_key; + else + EC_KEY_free(ec_key); + + return rc; + +fail: + if(ec_key != NULL) + EC_KEY_free(ec_key); + + if(application != NULL && *application != NULL) { + LIBSSH2_FREE(session, (void *)application); + *application = NULL; + } + + if(key_handle != NULL && *key_handle != NULL) { + LIBSSH2_FREE(session, (void *)key_handle); + *key_handle = NULL; + } + + return rc; +} + + static int _libssh2_ecdsa_new_openssh_private(libssh2_ecdsa_ctx ** ec_ctx, LIBSSH2_SESSION * session, @@ -2583,6 +3037,72 @@ _libssh2_ecdsa_new_openssh_private(libssh2_ecdsa_ctx ** ec_ctx, return rc; } +static int +_libssh2_ecdsa_new_openssh_private_sk(libssh2_ecdsa_ctx ** ec_ctx, + uint8_t *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase) +{ + FILE *fp; + int rc; + unsigned char *buf = NULL; + struct string_buf *decrypted = NULL; + + if(session == NULL) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Session is required"); + return -1; + } + + _libssh2_init_if_needed(); + + fp = fopen(filename, "r"); + if(!fp) { + _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to open OpenSSH ECDSA private key file"); + return -1; + } + + rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted); + fclose(fp); + if(rc) { + return rc; + } + + /* We have a new key file, now try and parse it using supported types */ + rc = _libssh2_get_string(decrypted, &buf, NULL); + + if(rc != 0 || buf == NULL) { + _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Public key type in decrypted key data not found"); + return -1; + } + + if(strcmp("sk-ecdsa-sha2-nistp256@openssh.com", (const char *)buf) == 0) { + rc = gen_publickey_from_sk_ecdsa_openssh_priv_data(session, + decrypted, + NULL, 0, + NULL, 0, + flags, + application, + key_handle, + handle_len, + ec_ctx); + } + else { + rc = -1; + } + + if(decrypted) + _libssh2_string_buf_free(session, decrypted); + + return rc; +} + int _libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx, LIBSSH2_SESSION * session, @@ -2605,6 +3125,40 @@ _libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx, return rc; } +int +_libssh2_ecdsa_new_private_sk(libssh2_ecdsa_ctx ** ec_ctx, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase) +{ + int rc; + + pem_read_bio_func read_ec = (pem_read_bio_func) &PEM_read_bio_ECPrivateKey; + + _libssh2_init_if_needed(); + + rc = read_private_key_from_file((void **) ec_ctx, read_ec, + filename, passphrase); + + if(rc) { + return _libssh2_ecdsa_new_openssh_private_sk(ec_ctx, + flags, + application, + key_handle, + handle_len, + session, + filename, + passphrase); + } + + return rc; +} + + /* * _libssh2_ecdsa_create_key * @@ -3078,7 +3632,7 @@ _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, #if LIBSSH2_ECDSA case EVP_PKEY_EC : st = gen_publickey_from_ec_evp( - session, method, method_len, pubkeydata, pubkeydata_len, pk); + session, method, method_len, pubkeydata, pubkeydata_len, 0, pk); break; #endif @@ -3153,6 +3707,23 @@ _libssh2_pub_priv_openssh_keyfilememory(LIBSSH2_SESSION *session, (libssh2_ed25519_ctx**)key_ctx); } } + + if(strcmp("sk-ssh-ed25519@openssh.com", (const char *)buf) == 0) { + if(key_type == NULL || + strcmp("sk-ssh-ed25519@openssh.com", key_type) == 0) { + rc = gen_publickey_from_sk_ed25519_openssh_priv_data(session, + decrypted, + method, + method_len, + pubkeydata, + pubkeydata_len, + NULL, + NULL, + NULL, + NULL, + (libssh2_ed25519_ctx**)key_ctx); + } + } #endif #if LIBSSH2_RSA if(strcmp("ssh-rsa", (const char *)buf) == 0) { @@ -3180,14 +3751,25 @@ _libssh2_pub_priv_openssh_keyfilememory(LIBSSH2_SESSION *session, { libssh2_curve_type type; - if(_libssh2_ecdsa_curve_type_from_name((const char *)buf, &type) == 0) { - if(key_type == NULL || strcmp("ssh-ecdsa", key_type) == 0) { - rc = gen_publickey_from_ecdsa_openssh_priv_data(session, type, - decrypted, + if(strcmp("sk-ecdsa-sha2-nistp256@openssh.com", (const char *)buf) == 0) { + rc = gen_publickey_from_sk_ecdsa_openssh_priv_data(session, decrypted, method, method_len, pubkeydata, pubkeydata_len, + NULL, + NULL, NULL, + NULL, (libssh2_ecdsa_ctx**)key_ctx); + } + else if(_libssh2_ecdsa_curve_type_from_name((const char *)buf, &type) + == 0) { + if(key_type == NULL || strcmp("ssh-ecdsa", key_type) == 0) { + rc = gen_publickey_from_ecdsa_openssh_priv_data(session, type, + decrypted, + method, method_len, + pubkeydata, + pubkeydata_len, + (libssh2_ecdsa_ctx**)key_ctx); } } } @@ -3204,6 +3786,104 @@ _libssh2_pub_priv_openssh_keyfilememory(LIBSSH2_SESSION *session, return rc; } +static int +_libssh2_sk_pub_openssh_keyfilememory(LIBSSH2_SESSION *session, + void **key_ctx, + const char *key_type, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + unsigned const char *passphrase) +{ + int rc; + unsigned char *buf = NULL; + struct string_buf *decrypted = NULL; + + if(key_ctx != NULL) + *key_ctx = NULL; + + if(session == NULL) + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Session is required"); + + if(key_type != NULL && strlen(key_type) < 7) + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "type is invalid"); + + _libssh2_init_if_needed(); + + rc = _libssh2_openssh_pem_parse_memory(session, passphrase, + privatekeydata, + privatekeydata_len, &decrypted); + + if(rc) + return rc; + + /* We have a new key file, now try and parse it using supported types */ + rc = _libssh2_get_string(decrypted, &buf, NULL); + + if(rc != 0 || buf == NULL) + return _libssh2_error(session, LIBSSH2_ERROR_PROTO, + "Public key type in decrypted " + "key data not found"); + + rc = LIBSSH2_ERROR_FILE; + +#if LIBSSH2_ED25519 + if(strcmp("sk-ssh-ed25519@openssh.com", (const char *)buf) == 0) { + *algorithm = LIBSSH2_HOSTKEY_TYPE_ED25519; + if(key_type == NULL || + strcmp("sk-ssh-ed25519@openssh.com", key_type) == 0) { + rc = gen_publickey_from_sk_ed25519_openssh_priv_data(session, + decrypted, + method, + method_len, + pubkeydata, + pubkeydata_len, + flags, + application, + key_handle, + handle_len, + (libssh2_ed25519_ctx**)key_ctx); + } + } +#endif +#if LIBSSH2_ECDSA +{ + if(strcmp("sk-ecdsa-sha2-nistp256@openssh.com", (const char *)buf) == 0) { + *algorithm = LIBSSH2_HOSTKEY_TYPE_ECDSA_256; + rc = gen_publickey_from_sk_ecdsa_openssh_priv_data(session, decrypted, + method, method_len, + pubkeydata, + pubkeydata_len, + flags, + application, + key_handle, + handle_len, + (libssh2_ecdsa_ctx**)key_ctx); + } +} +#endif + + if(rc == LIBSSH2_ERROR_FILE) + rc = _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key from private key file: " + "invalid/unrecognized private key file format"); + + if(decrypted) + _libssh2_string_buf_free(session, decrypted); + + return rc; +} + int read_openssh_private_key_from_memory(void **key_ctx, LIBSSH2_SESSION *session, const char *key_type, @@ -3286,7 +3966,8 @@ _libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, #if LIBSSH2_ECDSA case EVP_PKEY_EC : st = gen_publickey_from_ec_evp(session, method, method_len, - pubkeydata, pubkeydata_len, pk); + pubkeydata, pubkeydata_len, + 0, pk); break; #endif /* LIBSSH2_ECDSA */ default : @@ -3302,6 +3983,58 @@ _libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, return st; } +int +_libssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + int st = -1; + BIO* bp; + EVP_PKEY* pk; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from private key."); + + bp = BIO_new_mem_buf((char *)privatekeydata, privatekeydata_len); + if(!bp) + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory when" + "computing public key"); + BIO_reset(bp); + pk = PEM_read_bio_PrivateKey(bp, NULL, NULL, (void *)passphrase); + BIO_free(bp); + + if(pk == NULL) { + /* Try OpenSSH format */ + st = _libssh2_sk_pub_openssh_keyfilememory(session, NULL, NULL, + method, + method_len, + pubkeydata, + pubkeydata_len, + algorithm, + flags, + application, + key_handle, + handle_len, + privatekeydata, + privatekeydata_len, + (unsigned const char *)passphrase); + } + + return st; +} + void _libssh2_dh_init(_libssh2_dh_ctx *dhctx) { diff --git a/src/os400qc3.c b/src/os400qc3.c index daed1e5d..22955a15 100644 --- a/src/os400qc3.c +++ b/src/os400qc3.c @@ -2359,6 +2359,26 @@ _libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, return ret; } +int +_libssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public SK key from private key file: " + "Method unimplemented in OS/400 QC3 backend"); +} + int _libssh2_rsa_sha1_verify(libssh2_rsa_ctx *rsa, const unsigned char *sig, unsigned long sig_len, diff --git a/src/userauth.c b/src/userauth.c index a09c3e9f..bbd22f84 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -890,7 +890,106 @@ sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, return 0; } +int +libssh2_sign_sk(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + int rc = LIBSSH2_ERROR_DECRYPT; + LIBSSH2_PRIVKEY_SK *sk_info = (LIBSSH2_PRIVKEY_SK *) (*abstract); + LIBSSH2_SK_SIG_INFO sig_info = { 0 }; + if(sk_info->handle_len <= 0) { + return LIBSSH2_ERROR_DECRYPT; + } + + rc = sk_info->sign_callback(session, + &sig_info, + data, + data_len, + sk_info->algorithm, + sk_info->flags, + sk_info->application, + sk_info->key_handle, + sk_info->handle_len, + sk_info->orig_abstract); + + if(rc == 0 && sig_info.sig_r_len > 0 && sig_info.sig_r) { + unsigned char *p = NULL; + + if(sig_info.sig_s_len > 0 && sig_info.sig_s) { + /* sig length, sig_r, sig_s, flags, counter, plus 4 bytes for each + component's length, and up to 1 extra byte for each component */ + *sig_len = 4 + 5 + sig_info.sig_r_len + 5 + sig_info.sig_s_len + 5; + *sig = LIBSSH2_ALLOC(session, *sig_len); + + if(*sig) { + unsigned char *x = *sig; + p = *sig; + + _libssh2_store_u32(&p, 0); + + _libssh2_store_bignum2_bytes(&p, + sig_info.sig_r, + sig_info.sig_r_len); + + _libssh2_store_bignum2_bytes(&p, + sig_info.sig_s, + sig_info.sig_s_len); + + *sig_len = p - *sig; + + _libssh2_store_u32(&x, *sig_len - 4); + } + else { + _libssh2_debug(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate ecdsa-sk signature."); + rc = LIBSSH2_ERROR_ALLOC; + } + } + else { + /* sig, flags, counter, plus 4 bytes for sig length. */ + *sig_len = 4 + sig_info.sig_r_len + 1 + 4; + *sig = LIBSSH2_ALLOC(session, *sig_len); + + if(*sig) { + p = *sig; + + _libssh2_store_str(&p, + (const char *)sig_info.sig_r, + sig_info.sig_r_len); + } + else { + _libssh2_debug(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate ed25519-sk signature."); + rc = LIBSSH2_ERROR_ALLOC; + } + } + + if(p) { + *p = sig_info.flags; + ++p; + _libssh2_store_u32(&p, sig_info.counter); + + *sig_len = p - *sig; + } + + LIBSSH2_FREE(session, sig_info.sig_r); + + if(sig_info.sig_s != NULL) { + LIBSSH2_FREE(session, sig_info.sig_s); + } + } + else { + _libssh2_debug(session, + LIBSSH2_ERROR_DECRYPT, + "sign_callback failed or returned invalid signature."); + *sig_len = 0; + } + + return rc; +} /* userauth_hostbased_fromfile * Authenticate using a keypair found in the named files @@ -1603,16 +1702,32 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, plain_method_len((const char *)session->userauth_pblc_method, session->userauth_pblc_method_len); - _libssh2_store_u32(&s, - 4 + session->userauth_pblc_method_len + 4 + - sig_len); - _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, - session->userauth_pblc_method_len); + if(strncmp((const char *)session->userauth_pblc_method, + "sk-ecdsa-sha2-nistp256@openssh.com", + session->userauth_pblc_method_len) == 0 || + strncmp((const char *)session->userauth_pblc_method, + "sk-ssh-ed25519@openssh.com", + session->userauth_pblc_method_len) == 0) { + _libssh2_store_u32(&s, + 4 + session->userauth_pblc_method_len + + sig_len); + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + memcpy(s, sig, sig_len); + s += sig_len; + } + else { + _libssh2_store_u32(&s, + 4 + session->userauth_pblc_method_len + 4 + + sig_len); + _libssh2_store_str(&s, (const char *)session->userauth_pblc_method, + session->userauth_pblc_method_len); + _libssh2_store_str(&s, (const char *)sig, sig_len); + } LIBSSH2_FREE(session, session->userauth_pblc_method); session->userauth_pblc_method = NULL; - _libssh2_store_str(&s, (const char *)sig, sig_len); LIBSSH2_FREE(session, sig); _libssh2_debug(session, LIBSSH2_TRACE_AUTH, @@ -2164,3 +2279,68 @@ libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, response_callback)); return rc; } + +/* libssh2_userauth_publickey_sk + * Authenticate using an external callback function + */ +LIBSSH2_API int +libssh2_userauth_publickey_sk(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase, + LIBSSH2_USERAUTH_SK_SIGN_FUNC + ((*sign_callback)), + void **abstract) +{ + unsigned char *pubkeydata = NULL; + size_t pubkeydata_len = 0; + LIBSSH2_PRIVKEY_SK sk_info = { 0 }; + void *sign_abstract = &sk_info; + int rc; + + sk_info.sign_callback = sign_callback; + sk_info.orig_abstract = abstract; + + if(privatekeydata_len && privatekeydata) { + + if(_libssh2_sk_pub_keyfilememory(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + &(sk_info.algorithm), + &(sk_info.flags), + &(sk_info.application), + &(sk_info.key_handle), + &(sk_info.handle_len), + privatekeydata, privatekeydata_len, + passphrase)) + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key."); + } + else { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public and private key."); + } + + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + libssh2_sign_sk, &sign_abstract); + + while(rc == LIBSSH2_ERROR_EAGAIN) { + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + libssh2_sign_sk, &sign_abstract); + } + + if(pubkeydata) + LIBSSH2_FREE(session, pubkeydata); + + if(sk_info.application) { + LIBSSH2_FREE(session, (void *)sk_info.application); + } + + return rc; +} diff --git a/src/wincng.c b/src/wincng.c index ebf79295..ad1e52ab 100644 --- a/src/wincng.c +++ b/src/wincng.c @@ -1834,6 +1834,26 @@ _libssh2_wincng_pub_priv_keyfilememory(LIBSSH2_SESSION *session, #endif /* HAVE_LIBCRYPT32 */ } +int +_libssh2_wincng_sk_pub_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + int *algorithm, + unsigned char *flags, + const char **application, + const unsigned char **key_handle, + size_t *handle_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public SK key from private key file: " + "Method unimplemented in Windows CNG backend"); +} + /*******************************************************************/ /* * Windows CNG backend: Cipher functions diff --git a/src/wincng.h b/src/wincng.h index a5f4506f..f9791650 100644 --- a/src/wincng.h +++ b/src/wincng.h @@ -304,7 +304,10 @@ typedef struct __libssh2_wincng_key_ctx { pk, pk_len, pw) \ _libssh2_wincng_pub_priv_keyfilememory(s, m, m_len, p, p_len, \ pk, pk_len, pw) - +#define _libssh2_sk_pub_keyfilememory(s, m, m_len, p, p_len, alg, app, \ + f, kh, kh_len, pk, pk_len, pw) \ + _libssh2_wincng_sk_pub_keyfilememory(s, m, m_len, p, p_len, alg, app, \ + f, kh, kh_len, pk, pk_len, pw) /*******************************************************************/ /* diff --git a/tests/test_keyboard_interactive_auth_fails_with_wrong_response.c b/tests/test_keyboard_interactive_auth_fails_with_wrong_response.c index 56b1ba54..c1ab9730 100644 --- a/tests/test_keyboard_interactive_auth_fails_with_wrong_response.c +++ b/tests/test_keyboard_interactive_auth_fails_with_wrong_response.c @@ -19,8 +19,8 @@ static void kbd_callback(const char *name, int name_len, fprintf(stdout, "Kb-int name: %.*s\n", name_len, name); fprintf(stdout, "Kb-int instruction: %.*s\n", instruct_len, instruct); for(i = 0; i < num_prompts; ++i) { - fprintf(stdout, "Kb-int prompt %d: %.*s\n", i, prompts[i].length, - prompts[i].text); + fprintf(stdout, "Kb-int prompt %d: %.*s\n", i, + (int)prompts[i].length, prompts[i].text); } if(num_prompts == 1) { diff --git a/tests/test_keyboard_interactive_auth_succeeds_with_correct_response.c b/tests/test_keyboard_interactive_auth_succeeds_with_correct_response.c index 0ccf5dd9..093e4a22 100644 --- a/tests/test_keyboard_interactive_auth_succeeds_with_correct_response.c +++ b/tests/test_keyboard_interactive_auth_succeeds_with_correct_response.c @@ -21,8 +21,8 @@ static void kbd_callback(const char *name, int name_len, fprintf(stdout, "Kb-int name: %.*s\n", name_len, name); fprintf(stdout, "Kb-int instruction: %.*s\n", instruct_len, instruct); for(i = 0; i < num_prompts; ++i) { - fprintf(stdout, "Kb-int prompt %d: %.*s\n", i, prompts[i].length, - prompts[i].text); + fprintf(stdout, "Kb-int prompt %d: %.*s\n", i, + (int)prompts[i].length, prompts[i].text); } if(num_prompts == 1) {