From f64d2059c4e2b80da8178a10e9fd169c843739e8 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 9 Mar 2023 23:14:40 -0500 Subject: [PATCH] ssl: Add support for the new 16.0.0 functionality. --- nx/include/switch/runtime/devices/socket.h | 3 + nx/include/switch/services/ssl.h | 117 ++++++++++++++ nx/source/runtime/devices/socket.c | 33 ++++ nx/source/services/ssl.c | 170 ++++++++++++++++++++- 4 files changed, 316 insertions(+), 7 deletions(-) diff --git a/nx/include/switch/runtime/devices/socket.h b/nx/include/switch/runtime/devices/socket.h index ad99be4a..84028d12 100644 --- a/nx/include/switch/runtime/devices/socket.h +++ b/nx/include/switch/runtime/devices/socket.h @@ -46,6 +46,9 @@ int socketSslConnectionSetSocketDescriptor(SslConnection *c, int sockfd); /// Wrapper for \ref sslConnectionGetSocketDescriptor. Returns the output sockfd on success and -1 on error. int socketSslConnectionGetSocketDescriptor(SslConnection *c); +/// Wrapper for \ref sslConnectionSetDtlsSocketDescriptor. Returns the output sockfd on success and -1 on error. errno==ENOENT indicates that no sockfd was returned, this error must be ignored. +int socketSslConnectionSetDtlsSocketDescriptor(SslConnection *c, int sockfd, const struct sockaddr *addr, socklen_t addrlen); + /// Wrapper for \ref nifmRequestRegisterSocketDescriptor. Returns 0 on success and -1 on error. int socketNifmRequestRegisterSocketDescriptor(NifmRequest* r, int sockfd); diff --git a/nx/include/switch/services/ssl.h b/nx/include/switch/services/ssl.h index 6af37089..0e7cc935 100644 --- a/nx/include/switch/services/ssl.h +++ b/nx/include/switch/services/ssl.h @@ -177,6 +177,11 @@ typedef enum { SslOptionType_EnableAlpn = 3, ///< [9.0.0+] EnableAlpn. Only available with \ref sslConnectionSetOption. \ref sslConnectionSetSocketDescriptor should have been used prior to this - this will optionally use state setup by that, without throwing an error if that cmd wasn't used. } SslOptionType; +/// PrivateOptionType +typedef enum { + SslPrivateOptionType_DtlsSession = 1, ///< \ref sslConnectionSetSessionCacheMode will throw an error if the input ::SslSessionCacheMode is non-zero and this option flag is set. +} SslPrivateOptionType; + /// AlpnProtoState typedef enum { SslAlpnProtoState_NoSupport = 0, ///< NoSupport @@ -223,6 +228,15 @@ typedef struct { char protocol_version[0x8]; ///< Protocol version string. } SslCipherInfo; +/// KeyAndCertParams +typedef struct { + u32 unk_x0; ///< Must be value 1. + s32 key_size; ///< Key size in bits. + u64 public_exponent; ///< Public exponent, must be non-zero. Only the low 4-bytes are used. + char common_name[0x40]; ///< CN (Common Name) NUL-terminated string. + u32 common_name_len; ///< Length of common_name excluding NUL-terminator. Must be 0x1-0x3F. +} SslKeyAndCertParams; + /// Initialize ssl. A default value of 0x3 can be used for num_sessions. This must be 0x1-0x4. Result sslInitialize(u32 num_sessions); @@ -413,6 +427,34 @@ Result sslContextAddPolicyOid(SslContext *c, const char *str, u32 str_bufsize); */ Result sslContextImportCrl(SslContext *c, const void* buffer, u32 size, u64 *id); +/** + * @brief ImportClientCertKeyPki + * @note Only available on [16.0.0+]. + * @param c \ref SslContext + * @param[in] cert Input cert buffer, + * @param[in] cert_size Size of the cert buffer. + * @param[in] key Input key buffer. + * @param[in] key_size Size of the key buffer. + * @param[in] format \ref SslCertificateFormat for the cert and key. + * @param[out] id Output Id. Optional, can be NULL. + */ +Result sslContextImportClientCertKeyPki(SslContext *c, const void* cert, u32 cert_size, const void* key, u32 key_size, SslCertificateFormat format, u64 *id); + +/** + * @brief GeneratePrivateKeyAndCert + * @note Only available on [16.0.0+]. + * @param c \ref SslContext + * @param[out] cert Output cert buffer, + * @param[in] cert_size Size of the cert buffer. + * @param[out] key Output key buffer. + * @param[in] key_size Size of the key buffer. + * @param[in] val Must be value 1. + * @param[in] params \ref SslKeyAndCertParams + * @param[out] out_certsize Actual size of the generated cert data. + * @param[out] out_keysize Actual size of the generated key data. + */ +Result sslContextGeneratePrivateKeyAndCert(SslContext *c, void* cert, u32 cert_size, void* key, u32 key_size, u32 val, const SslKeyAndCertParams *params, u32 *out_certsize, u32 *out_keysize); + /** * @brief CreateConnectionForSystem * @note Only available on [15.0.0+] with ::SslServiceType_System. @@ -680,5 +722,80 @@ Result sslConnectionSetNextAlpnProto(SslConnection *c, const u8 *buffer, u32 siz */ Result sslConnectionGetNextAlpnProto(SslConnection *c, SslAlpnProtoState *state, u32 *out, u8 *buffer, u32 size); +/** + * @brief SetDtlsSocketDescriptor. Do not use directly, use \ref socketSslConnectionSetDtlsSocketDescriptor instead. + * @note Only available on [16.0.0+]. + * @note An error is thrown if this was used previously. + * @param c \ref SslConnection + * @param[in] sockfd sockfd + * @param[in] Input sockaddr. + * @param[in] size Input sockaddr size. + * @param[out] out_sockfd sockfd. Prior to using \ref sslConnectionClose, this must be closed if it's not negative (it will be -1 if ::SslOptionType_DoNotCloseSocket is set). + */ +Result sslConnectionSetDtlsSocketDescriptor(SslConnection *c, int sockfd, const void* buf, size_t size, int *out_sockfd); + +/** + * @brief GetDtlsHandshakeTimeout + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[out] out Output nanoseconds value. + */ +Result sslConnectionGetDtlsHandshakeTimeout(SslConnection *c, u64 *out); + +/** + * @brief SetPrivateOption + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[in] option \ref SslPrivateOptionType + * @param[in] flag Input flag value. + */ +Result sslConnectionSetPrivateOption(SslConnection *c, SslPrivateOptionType option, bool flag); + +/** + * @brief SetSrtpCiphers + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[in] ciphers Input array of u16s. Each entry must be value 1-2, otherwise the entry is ignored. + * @param[in] count Total entries in the ciphers array, the maximum is 4. + */ +Result sslConnectionSetSrtpCiphers(SslConnection *c, const u16 *ciphers, u32 count); + +/** + * @brief GetSrtpCipher + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[out] out Output value. + */ +Result sslConnectionGetSrtpCipher(SslConnection *c, u16 *out); + +/** + * @brief ExportKeyingMaterial + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[out] outbuf Output buffer. + * @param[in] outbuf_size Output buffer size. + * @param[in] label Input label string. + * @param[in] label_size Size of the label buffer excluding NUL-terminator. + * @param[in] context Optional input context buffer, can be NULL. + * @param[in] context_size Size of context, if specified this must be <0xFFFF. + */ +Result sslConnectionExportKeyingMaterial(SslConnection *c, u8 *outbuf, u32 outbuf_size, const char *label, u32 label_size, const void* context, u32 context_size); + +/** + * @brief SetIoTimeout + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[in] timeout Input timeout value. + */ +Result sslConnectionSetIoTimeout(SslConnection *c, u32 timeout); + +/** + * @brief GetIoTimeout + * @note Only available on [16.0.0+]. + * @param c \ref SslConnection + * @param[out] out Output timeout value. + */ +Result sslConnectionGetIoTimeout(SslConnection *c, u32 *out); + ///@} diff --git a/nx/source/runtime/devices/socket.c b/nx/source/runtime/devices/socket.c index 8f8f9e69..52d0cfb0 100644 --- a/nx/source/runtime/devices/socket.c +++ b/nx/source/runtime/devices/socket.c @@ -199,6 +199,39 @@ int socketSslConnectionGetSocketDescriptor(SslConnection *c) { return fd; } +int socketSslConnectionSetDtlsSocketDescriptor(SslConnection *c, int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + int dev; + int fd = _socketGetFd(sockfd); + + if (fd==-1) + return -1; + + int tmpfd=0; + Result rc = sslConnectionSetDtlsSocketDescriptor(c, fd, addr, addrlen, &tmpfd); + if (R_FAILED(rc)) { + g_bsdResult = rc; + errno = EIO; + return -1; + } + + if (tmpfd==-1) { // The cmd didn't return a sockfd. This error must be ignored. + errno = ENOENT; + return -1; + } + + dev = FindDevice("soc:"); + if(dev == -1) + return -1; + + fd = __alloc_handle(dev); + if(fd == -1) + return -1; + + *(int *)__get_handle(fd)->fileStruct = tmpfd; + + return fd; +} + int socketNifmRequestRegisterSocketDescriptor(NifmRequest* r, int sockfd) { int fd = _socketGetFd(sockfd); diff --git a/nx/source/services/ssl.c b/nx/source/services/ssl.c index 25b13412..05243a6b 100644 --- a/nx/source/services/ssl.c +++ b/nx/source/services/ssl.c @@ -125,6 +125,16 @@ static Result _sslCmdInU64NoOut(Service* srv, u64 inval, u32 cmd_id) { return _sslObjectDispatchIn(srv, cmd_id, inval); } +static Result _sslCmdInU8U32NoOut(Service* srv, u8 val0, u32 val1, u32 cmd_id) { + const struct { + u8 val0; + u8 pad[3]; + u32 val1; + } in = { val0, {0}, val1 }; + + return _sslObjectDispatchIn(srv, cmd_id, in); +} + static Result _sslCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id) { return _sslObjectDispatchOut(srv, cmd_id, *out); } @@ -502,6 +512,57 @@ Result sslContextImportCrl(SslContext *c, const void* buffer, u32 size, u64 *id) ); } +Result sslContextImportClientCertKeyPki(SslContext *c, const void* cert, u32 cert_size, const void* key, u32 key_size, SslCertificateFormat format, u64 *id) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + u32 tmp=format; + return _sslObjectDispatchInOut(&c->s, 12, tmp, *id, + .buffer_attrs = { + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + }, + .buffers = { + { cert, cert_size }, + { key, key_size }, + }, + ); +} + +Result sslContextGeneratePrivateKeyAndCert(SslContext *c, void* cert, u32 cert_size, void* key, u32 key_size, u32 val, const SslKeyAndCertParams *params, u32 *out_certsize, u32 *out_keysize) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + struct { + u32 out_certsize; + u32 out_keysize; + } out; + + Result rc = _sslObjectDispatchInOut(&c->s, 13, val, out, + .buffer_attrs = { + SfBufferAttr_HipcMapAlias | SfBufferAttr_Out, + SfBufferAttr_HipcMapAlias | SfBufferAttr_Out, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + }, + .buffers = { + { cert, cert_size }, + { key, key_size }, + { params, sizeof(*params) }, + }, + ); + if (R_SUCCEEDED(rc)) { + if (out_certsize) *out_certsize = out.out_certsize; + if (out_keysize) *out_keysize = out.out_keysize; + } + return rc; +} + Result sslContextCreateConnectionForSystem(SslContext *c, SslConnection *conn) { if (hosversionBefore(15,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); @@ -729,13 +790,7 @@ Result sslConnectionSetOption(SslConnection *c, SslOptionType option, bool flag) if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); - const struct { - u8 flag; - u8 pad[3]; - u32 option; - } in = { flag!=0, {0}, option }; - - return _sslObjectDispatchIn(&c->s, 22, in); + return _sslCmdInU8U32NoOut(&c->s, flag!=0, option, 22); } Result sslConnectionGetOption(SslConnection *c, SslOptionType option, bool *out) { @@ -812,3 +867,104 @@ Result sslConnectionGetNextAlpnProto(SslConnection *c, SslAlpnProtoState *state, return rc; } +Result sslConnectionSetDtlsSocketDescriptor(SslConnection *c, int sockfd, const void* buf, size_t size, int *out_sockfd) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslObjectDispatchInOut(&c->s, 28, sockfd, *out_sockfd, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { + { buf, size }, + }, + ); +} + +Result sslConnectionGetDtlsHandshakeTimeout(SslConnection *c, u64 *out) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslObjectDispatch(&c->s, 29, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { + { out, sizeof(*out) }, + }, + ); +} + +Result sslConnectionSetPrivateOption(SslConnection *c, SslPrivateOptionType option, bool flag) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslCmdInU8U32NoOut(&c->s, flag!=0, option, 30); +} + +Result sslConnectionSetSrtpCiphers(SslConnection *c, const u16 *ciphers, u32 count) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslCmdInBufNoOut(&c->s, ciphers, count*sizeof(u16), 31); +} + +Result sslConnectionGetSrtpCipher(SslConnection *c, u16 *out) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslObjectDispatchOut(&c->s, 32, *out); +} + +Result sslConnectionExportKeyingMaterial(SslConnection *c, u8 *outbuf, u32 outbuf_size, const char *label, u32 label_size, const void* context, u32 context_size) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslObjectDispatch(&c->s, 33, + .buffer_attrs = { + SfBufferAttr_HipcMapAlias | SfBufferAttr_Out, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + }, + .buffers = { + { outbuf, outbuf_size }, + { label, label_size }, + { context, context_size }, + }, + ); +} + +Result sslConnectionSetIoTimeout(SslConnection *c, u32 timeout) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslCmdInU32NoOut(&c->s, timeout, 34); +} + +Result sslConnectionGetIoTimeout(SslConnection *c, u32 *out) { + if (!serviceIsActive(&c->s)) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + if (hosversionBefore(16,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _sslCmdNoInOutU32(&c->s, out, 35); +} +