diff --git a/nx/include/switch/services/ssl.h b/nx/include/switch/services/ssl.h index 106710ab..d2e4dead 100644 --- a/nx/include/switch/services/ssl.h +++ b/nx/include/switch/services/ssl.h @@ -1,6 +1,6 @@ /** * @file fs.h - * @brief SSL service IPC wrapper. + * @brief SSL service IPC wrapper, for using client-mode TLS. See also: https://switchbrew.org/wiki/SSL_services * @author yellows8 * @copyright libnx Authors */ @@ -87,7 +87,7 @@ typedef enum { /// InternalPki typedef enum { - SslInternalPki_DeviceClientCertDefault = 1, ///< DeviceClientCertDefault + SslInternalPki_DeviceClientCertDefault = 1, ///< DeviceClientCertDefault. Enables using the DeviceCert and the CertStore. } SslInternalPki; /// ContextOption @@ -136,7 +136,7 @@ typedef enum { /// OptionType. The default bool flags value for these at the time of \ref sslContextCreateConnection is cleared. typedef enum { SslOptionType_DoNotCloseSocket = 0, ///< DoNotCloseSocket. See \ref sslConnectionSetSocketDescriptor. This is only available if \ref sslConnectionSetSocketDescriptor wasn't used yet. - SslOptionType_GetServerCertChain = 1, ///< [3.0.0+] GetServerCertChain + SslOptionType_GetServerCertChain = 1, ///< [3.0.0+] GetServerCertChain. See \ref sslConnectionDoHandshake. SslOptionType_SkipDefaultVerify = 2, ///< [5.0.0+] SkipDefaultVerify. Checked by \ref sslConnectionSetVerifyOption, see \ref SslVerifyOption. 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; @@ -168,6 +168,19 @@ typedef struct { u8 *cert_data; ///< CertificateData (converted from an offset to a ptr), in DER format. } SslBuiltInCertificateInfo; +/// SslServerCertDetailHeader +typedef struct { + u64 magicnum; ///< Magicnum. + u32 cert_total; ///< Total certs. + u32 pad; ///< Padding. +} SslServerCertDetailHeader; + +/// SslServerCertDetailEntry +typedef struct { + u32 size; ///< Size. + u32 offset; ///< Offset. +} SslServerCertDetailEntry; + /// CipherInfo typedef struct { char cipher[0x40]; ///< Cipher string. @@ -292,20 +305,20 @@ Result sslContextGetConnectionCount(SslContext *c, u32 *out); * @param[in] buffer Input buffer, must not be NULL. * @param[in] size Input buffer size. * @param[in] format \ref SslCertificateFormat - * @param[out] id Output Id. + * @param[out] id Output Id. Optional, can be NULL. */ Result sslContextImportServerPki(SslContext *c, const void* buffer, u32 size, SslCertificateFormat format, u64 *id); /** * @brief ImportClientPki * @param c \ref SslContext - * @param[in] buf0 First input buffer, must not be NULL. - * @param[in] size0 First input buffer size. - * @param[in] buf1 Second input buffer, this can only be NULL if size1 is 0. - * @param[in] size1 Second input buffer size, this can only be 0 if buf1 is NULL. - * @param[out] id Output Id. + * @param[in] pkcs12 PKCS#12 input buffer, must not be NULL. + * @param[in] pkcs12_size pkcs12 buffer size. + * @param[in] pw ASCII password string buffer, this can only be NULL if pw_size is 0. This will be internally copied to another buffer which was allocated with size=pw_size+1, for NUL-termination. + * @param[in] pw_size Password buffer size, this can only be 0 if pw is NULL. + * @param[out] id Output Id. Optional, can be NULL. */ -Result sslContextImportClientPki(SslContext *c, const void* buf0, u32 size0, const void* buf1, u32 size1, u64 *id); +Result sslContextImportClientPki(SslContext *c, const void* pkcs12, u32 pkcs12_size, const char *pw, u32 pw_size, u64 *id); /** * @brief Remove the specified *Pki, or on [3.0.0+] Crl. @@ -318,7 +331,7 @@ Result sslContextRemovePki(SslContext *c, u64 id); * @brief RegisterInternalPki * @param c \ref SslContext * @param[in] internal_pki \ref SslInternalPki - * @param[out] id Output Id. + * @param[out] id Output Id. Optional, can be NULL. */ Result sslContextRegisterInternalPki(SslContext *c, SslInternalPki internal_pki, u64 *id); @@ -328,7 +341,7 @@ Result sslContextRegisterInternalPki(SslContext *c, SslInternalPki internal_pki, * @param[in] str Input string. * @param[in] str_bufsize String buffer size, excluding NUL-terminator (must not match the string length). Hence, this should be actual_bufsize-1. This must not be >0xff. */ -Result sslContextAddPolicyOid(SslContext *c, const char* str, u32 str_bufsize); +Result sslContextAddPolicyOid(SslContext *c, const char *str, u32 str_bufsize); /** * @brief ImportCrl @@ -336,7 +349,7 @@ Result sslContextAddPolicyOid(SslContext *c, const char* str, u32 str_bufsize); * @param c \ref SslContext * @param[in] buffer Input buffer, must not be NULL. * @param[in] size Input buffer size. - * @param[out] id Output Id. + * @param[out] id Output Id. Optional, can be NULL. */ Result sslContextImportCrl(SslContext *c, const void* buffer, u32 size, u64 *id); @@ -399,7 +412,7 @@ Result sslConnectionGetSocketDescriptor(SslConnection *c, int *sockfd); * @param[in] str_bufsize String buffer size, must be large enough for the entire output string. * @param[out] out Output string length. */ -Result sslConnectionGetHostName(SslConnection *c, char* str, u32 str_bufsize, u32 *out); +Result sslConnectionGetHostName(SslConnection *c, char *str, u32 str_bufsize, u32 *out); /** * @brief GetVerifyOption @@ -421,12 +434,22 @@ Result sslConnectionGetIoMode(SslConnection *c, SslIoMode *out); * @note \ref sslConnectionSetHostName must have been used previously with a non-empty string when ::SslVerifyOption_HostName is set. * @note The DoHandshakeGetServerCert cmd is only used if both server_certbuf/server_certbuf_size are set, otherwise the DoHandshake cmd is used (in which case out0/out1 will be left at value 0). * @param c \ref SslConnection - * @param[out] out0 Optional first output value, can be NULL. - * @param[out] out1 Optional second output value, can be NULL. - * @param[out] server_certbuf Optional output server cert buffer, can be NULL. + * @param[out] out_size Total data size which was written to server_certbuf. Optional, can be NULL. + * @param[out] total_certs Total certs which were written to server_certbuf, can be NULL. + * @param[out] server_certbuf Optional output server cert buffer, can be NULL. Normally this just contains the server cert DER, however with ::SslOptionType_GetServerCertChain set this will contain the full chain (\ref sslConnectionGetServerCertDetail can be used to parse that). * @param[in] server_certbuf_size Optional output server cert buffer size, can be 0. */ -Result sslConnectionDoHandshake(SslConnection *c, u32 *out0, u32 *out1, void* server_certbuf, u32 server_certbuf_size); +Result sslConnectionDoHandshake(SslConnection *c, u32 *out_size, u32 *total_certs, void* server_certbuf, u32 server_certbuf_size); + +/** + * @brief Parses the output server_certbuf from \ref sslConnectionDoHandshake where ::SslOptionType_GetServerCertChain is set. + * @param[in] certbuf server_certbuf from \ref sslConnectionDoHandshake, must not be NULL. + * @param[in] certbuf_size out_size from \ref sslConnectionDoHandshake. + * @param[in] cert_index Cert index, must be within the range of certs stored in certbuf. + * @param[out] cert Ptr for the ouput DER cert, must not be NULL. + * @param[out] cert_size Size for the ouput cert, must not be NULL. + */ +Result sslConnectionGetServerCertDetail(const void* certbuf, u32 certbuf_size, u32 cert_index, void** cert, u32 *cert_size); /** * @brief Read @@ -434,9 +457,9 @@ Result sslConnectionDoHandshake(SslConnection *c, u32 *out0, u32 *out1, void* se * @param c \ref SslConnection * @param[out] buffer Output buffer, must not be NULL. * @param[in] size Output buffer size, must not be 0. - * @param[out] out Output value. + * @param[out] out_size Actual transferred size. */ -Result sslConnectionRead(SslConnection *c, void* buffer, u32 size, u32 *out); +Result sslConnectionRead(SslConnection *c, void* buffer, u32 size, u32 *out_size); /** * @brief Write @@ -444,9 +467,9 @@ Result sslConnectionRead(SslConnection *c, void* buffer, u32 size, u32 *out); * @param c \ref SslConnection * @param[in] buffer Input buffer, must not be NULL. * @param[in] size Input buffer size, must not be 0. - * @param[out] out Output value. + * @param[out] out_size Actual transferred size. */ -Result sslConnectionWrite(SslConnection *c, const void* buffer, u32 size, u32 *out); +Result sslConnectionWrite(SslConnection *c, const void* buffer, u32 size, u32 *out_size); /** * @brief Pending @@ -462,9 +485,9 @@ Result sslConnectionPending(SslConnection *c, s32 *out); * @param c \ref SslConnection * @param[out] buffer Output buffer, must not be NULL. * @param[in] size Output buffer size, must not be 0. - * @param[out] out Output value. + * @param[out] out_size Output size. */ -Result sslConnectionPeek(SslConnection *c, void* buffer, u32 size, u32 *out); +Result sslConnectionPeek(SslConnection *c, void* buffer, u32 size, u32 *out_size); /** * @brief Poll @@ -568,19 +591,21 @@ Result sslConnectionGetCipherInfo(SslConnection *c, SslCipherInfo *out); * @brief SetNextAlpnProto * @note Only available on [9.0.0+]. * @note \ref sslConnectionSetSocketDescriptor must have been used prior to this successfully. + * @note ::SslOptionType_EnableAlpn should be set at the time of using \ref sslConnectionDoHandshake, otherwise using this cmd will have no affect. * @param c \ref SslConnection * @param[in] buffer Input buffer, must not be NULL. * @param[in] size Input buffer size, must not be 0. Must be at least 0x2. */ -Result sslConnectionSetNextAlpnProto(SslConnection *c, const u8* buffer, u32 size); +Result sslConnectionSetNextAlpnProto(SslConnection *c, const u8 *buffer, u32 size); /** * @brief GetNextAlpnProto * @note Only available on [9.0.0+]. * @note \ref sslConnectionSetSocketDescriptor must have been used prior to this successfully. + * @note The output will be all-zero/empty if not available - such as when this was used before \ref sslConnectionDoHandshake. * @param c \ref SslConnection * @param[out] state \ref SslAlpnProtoState - * @param[out] out Output value. + * @param[out] out Output string length. * @param[out] buffer Output buffer, must not be NULL. * @param[in] size Output buffer size, must not be 0. */ diff --git a/nx/source/services/ssl.c b/nx/source/services/ssl.c index dedb9940..a5024095 100644 --- a/nx/source/services/ssl.c +++ b/nx/source/services/ssl.c @@ -378,11 +378,11 @@ Result sslContextImportServerPki(SslContext *c, const void* buffer, u32 size, Ss ); } -Result sslContextImportClientPki(SslContext *c, const void* buf0, u32 size0, const void* buf1, u32 size1, u64 *id) { +Result sslContextImportClientPki(SslContext *c, const void* pkcs12, u32 pkcs12_size, const char *pw, u32 pw_size, u64 *id) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); - if (buf0==NULL || (buf1==NULL && size1) || (buf1!=NULL && !size1)) + if (pkcs12==NULL || (pw==NULL && pw_size) || (pw!=NULL && !pw_size)) return MAKERESULT(Module_Libnx, LibnxError_BadInput); return _sslObjectDispatchOut(&c->s, 5, *id, @@ -391,8 +391,8 @@ Result sslContextImportClientPki(SslContext *c, const void* buf0, u32 size0, con SfBufferAttr_HipcMapAlias | SfBufferAttr_In, }, .buffers = { - { buf0, size0 }, - { buf1, size1 }, + { pkcs12, pkcs12_size }, + { pw, pw_size }, }, ); } @@ -429,7 +429,7 @@ Result sslContextRegisterInternalPki(SslContext *c, SslInternalPki internal_pki, return _sslObjectDispatchInOut(&c->s, 8, tmp, *id); } -Result sslContextAddPolicyOid(SslContext *c, const char* str, u32 str_bufsize) { +Result sslContextAddPolicyOid(SslContext *c, const char *str, u32 str_bufsize) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); @@ -468,7 +468,7 @@ Result sslConnectionSetSocketDescriptor(SslConnection *c, int sockfd, int *out_s return _sslObjectDispatchInOut(&c->s, 0, sockfd, *out_sockfd); } -Result sslConnectionSetHostName(SslConnection *c, const char* str, u32 str_bufsize) { +Result sslConnectionSetHostName(SslConnection *c, const char *str, u32 str_bufsize) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); @@ -499,7 +499,7 @@ Result sslConnectionGetSocketDescriptor(SslConnection *c, int *sockfd) { return _sslCmdNoInOutU32(&c->s, (u32*)sockfd, 4); } -Result sslConnectionGetHostName(SslConnection *c, char* str, u32 str_bufsize, u32 *out) { +Result sslConnectionGetHostName(SslConnection *c, char *str, u32 str_bufsize, u32 *out) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); @@ -526,37 +526,59 @@ Result sslConnectionGetIoMode(SslConnection *c, SslIoMode *out) { return rc; } -Result sslConnectionDoHandshake(SslConnection *c, u32 *out0, u32 *out1, void* server_certbuf, u32 server_certbuf_size) { +Result sslConnectionDoHandshake(SslConnection *c, u32 *out_size, u32 *total_certs, void* server_certbuf, u32 server_certbuf_size) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); - if (out0) *out0 = 0; - if (out1) *out1 = 0; + if (out_size) *out_size = 0; + if (total_certs) *total_certs = 0; if (server_certbuf==NULL || !server_certbuf_size) return _sslCmdNoIO(&c->s, 8); // DoHandshake - return _sslCmdNoInOutBufTwoOutU32s(&c->s, out0, out1, server_certbuf, server_certbuf_size, 9); // DoHandshakeGetServerCert + return _sslCmdNoInOutBufTwoOutU32s(&c->s, out_size, total_certs, server_certbuf, server_certbuf_size, 9); // DoHandshakeGetServerCert } -Result sslConnectionRead(SslConnection *c, void* buffer, u32 size, u32 *out) { +Result sslConnectionGetServerCertDetail(const void* certbuf, u32 certbuf_size, u32 cert_index, void** cert, u32 *cert_size) { + if (certbuf==NULL || cert==NULL || cert_size==NULL || certbuf_size < sizeof(SslServerCertDetailHeader) + cert_index*sizeof(SslServerCertDetailEntry)) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + // sdknso doesn't have a certbuf_size param for this. sdknso also uses a struct for output, but that just contains a size/addr anyway. + + SslServerCertDetailHeader *hdr = (SslServerCertDetailHeader*)certbuf; + if (hdr->magicnum != 0x4E4D684374726543 || hdr->cert_total <= cert_index) // "CertChMN" + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + SslServerCertDetailEntry *entry = (SslServerCertDetailEntry*)(++hdr); + entry+= cert_index; + + if (certbuf_size <= entry->offset || certbuf_size < entry->size || certbuf_size < entry->offset + entry->size) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + *cert = (void*)certbuf + entry->offset; + *cert_size = entry->size; + + return 0; +} + +Result sslConnectionRead(SslConnection *c, void* buffer, u32 size, u32 *out_size) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); if (buffer==NULL || !size) return MAKERESULT(Module_Libnx, LibnxError_BadInput); - return _sslCmdOutBufOutU32(&c->s, buffer, size, out, 10); + return _sslCmdOutBufOutU32(&c->s, buffer, size, out_size, 10); } -Result sslConnectionWrite(SslConnection *c, const void* buffer, u32 size, u32 *out) { +Result sslConnectionWrite(SslConnection *c, const void* buffer, u32 size, u32 *out_size) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); if (buffer==NULL || !size) return MAKERESULT(Module_Libnx, LibnxError_BadInput); - return _sslCmdInBufOutU32(&c->s, buffer, size, out, 11); + return _sslCmdInBufOutU32(&c->s, buffer, size, out_size, 11); } Result sslConnectionPending(SslConnection *c, s32 *out) { @@ -566,14 +588,14 @@ Result sslConnectionPending(SslConnection *c, s32 *out) { return _sslCmdNoInOutU32(&c->s, (u32*)out, 12); } -Result sslConnectionPeek(SslConnection *c, void* buffer, u32 size, u32 *out) { +Result sslConnectionPeek(SslConnection *c, void* buffer, u32 size, u32 *out_size) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); if (buffer==NULL || !size) return MAKERESULT(Module_Libnx, LibnxError_BadInput); - return _sslCmdOutBufOutU32(&c->s, buffer, size, out, 13); + return _sslCmdOutBufOutU32(&c->s, buffer, size, out_size, 13); } Result sslConnectionPoll(SslConnection *c, u32 in_pollevent, u32 *out_pollevent, u32 timeout) { @@ -697,7 +719,7 @@ Result sslConnectionGetCipherInfo(SslConnection *c, SslCipherInfo *out) { ); } -Result sslConnectionSetNextAlpnProto(SslConnection *c, const u8* buffer, u32 size) { +Result sslConnectionSetNextAlpnProto(SslConnection *c, const u8 *buffer, u32 size) { if (!serviceIsActive(&c->s)) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);