diff --git a/libctru/include/3ds/services/sslc.h b/libctru/include/3ds/services/sslc.h index b603507..ca6f9cd 100644 --- a/libctru/include/3ds/services/sslc.h +++ b/libctru/include/3ds/services/sslc.h @@ -4,23 +4,180 @@ */ #pragma once -/// HTTP context. +/// sslc context. typedef struct { Handle servhandle; ///< Service handle. u32 sslchandle; ///< SSLC handle. } sslcContext; +typedef enum { + SSLC_DefaultRootCert_Nintendo_CA = 0x1, //"Nintendo CA" + SSLC_DefaultRootCert_Nintendo_CA_G2 = 0x2, //"Nintendo CA - G2" + SSLC_DefaultRootCert_Nintendo_CA_G3 = 0x3, //"Nintendo CA - G3" + SSLC_DefaultRootCert_Nintendo_Class2_CA = 0x4, //"Nintendo Class 2 CA" + SSLC_DefaultRootCert_Nintendo_Class2_CA_G2 = 0x5, //"Nintendo Class 2 CA - G2" + SSLC_DefaultRootCert_Nintendo_Class2_CA_G3 = 0x6, //"Nintendo Class 2 CA - G3" + SSLC_DefaultRootCert_CyberTrust = 0x7, //"GTE CyberTrust Global Root" + SSLC_DefaultRootCert_AddTrust_External_CA = 0x8, //"AddTrust External CA Root" + SSLC_DefaultRootCert_COMODO = 0x9, //"COMODO RSA Certification Authority" + SSLC_DefaultRootCert_USERTrust = 0xA, //"USERTrust RSA Certification Authority" + SSLC_DefaultRootCert_DigiCert_EV = 0xB //"DigiCert High Assurance EV Root CA" +} SSLC_DefaultRootCert; + +typedef enum { + SSLC_DefaultClientCert_ClCertA = 0x40 +} SSLC_DefaultClientCert; + /// Initializes SSLC. Normally session_handle should be 0. When non-zero this will use the specified handle for the main-service-session without using the Initialize command, instead of using srvGetServiceHandle. Result sslcInit(Handle session_handle); /// Exits SSLC. void sslcExit(void); +/** + * @brief Creates a RootCertChain. + * @param RootCertChain_contexthandle Output contexthandle. + */ +Result sslcCreateRootCertChain(u32 *RootCertChain_contexthandle); + +/** + * @brief Destroys a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain contexthandle. + */ +Result sslcDestroyRootCertChain(u32 RootCertChain_contexthandle); + /** * @brief Adds a trusted RootCA cert to a RootCertChain. * @param RootCertChain_contexthandle RootCertChain to use. - * @param cert Pointer to DER cert. + * @param cert Pointer to the DER cert. * @param certsize Size of the DER cert. */ -Result sslcAddTrustedRootCA(u32 RootCertChain_contexthandle, u8 *cert, u32 certsize); +Result sslcAddTrustedRootCA(u32 RootCertChain_contexthandle, u8 *cert, u32 certsize, u32 *cert_contexthandle); + +/** + * @brief Adds a default RootCA cert to a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param certID ID of the cert to add. + * @param cert_contexthandle Optional, the cert contexthandle can be written here. + */ +Result sslcRootCertChainAddDefaultCert(u32 RootCertChain_contexthandle, SSLC_DefaultRootCert certID, u32 *cert_contexthandle); + +/** + * @brief Removes the specified cert from the RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param cert_contexthandle Cert contexthandle to remove from the RootCertChain. + */ +Result sslcRootCertChainRemoveCert(u32 RootCertChain_contexthandle, u32 cert_contexthandle); + +/** + * @brief Opens a new ClientCert-context. + * @param cert Pointer to the DER cert. + * @param certsize Size of the DER cert. + * @param key Pointer to the DER key. + * @param keysize Size of the DER key. + * @param ClientCert_contexthandle Output contexthandle. + */ +Result sslcOpenClientCertContext(u8 *cert, u32 certsize, u8 *key, u32 keysize, u32 *ClientCert_contexthandle); + +/** + * @brief Opens a ClientCert-context with a default certID. + * @param certID ID of the ClientCert to use. + * @param ClientCert_contexthandle Output contexthandle. + */ +Result sslcOpenDefaultClientCertContext(SSLC_DefaultClientCert certID, u32 *ClientCert_contexthandle); + +/** + * @brief Closes the specified ClientCert-context. + * @param ClientCert_contexthandle ClientCert-context to use. + */ +Result sslcCloseClientCertContext(u32 ClientCert_contexthandle); + +/** + * @brief This uses ps:ps SeedRNG internally. + */ +Result sslcSeedRNG(void); + +/** + * @brief This uses ps:ps GenerateRandomData internally. + * @param buf Output buffer. + * @param size Output size. + */ +Result sslcGenerateRandomData(u8 *buf, u32 size); + +/** + * @brief Creates a sslc context. + * @param context sslc context. + * @param sockfd Socket fd, this code automatically uses the required SOC command before using the actual sslc command. + * @param input_opt Input sslc options bitmask. The default value for this param is 0. + * @param hostname Server hostname. + */ +Result sslcCreateContext(sslcContext *context, int sockfd, u32 input_opt, char *hostname); + +/* + * @brief Destroys a sslc context. The associated sockfd must be closed manually. + * @param context sslc context. + */ +Result sslcDestroyContext(sslcContext *context); + +/* + * @brief Starts the TLS connection. If successful, this will not return until the connection is ready for data-transfer via sslcRead/sslcWrite. + * @param context sslc context. + * @param internal_retval Optional ptr where the internal_retval will be written. The value is only copied to here by this function when no error occurred. + * @param out Optional ptr where an output u32 will be written. The value is only copied to here by this function when no error occurred. + */ +Result sslcStartConnection(sslcContext *context, int *internal_retval, u32 *out); + +/* + * @brief Receive data over the network connection. + * @param context sslc context. + * @param buf Output buffer. + * @param len Size to receive. + * @param peek When set, this is equivalent to setting the recv() MSG_PEEK flag. + * @return When this isn't an error-code, this is the total transferred data size. + */ +Result sslcRead(sslcContext *context, void *buf, size_t len, bool peek); + +/* + * @brief Send data over the network connection. + * @param context sslc context. + * @param buf Input buffer. + * @param len Size to send. + * @return When this isn't an error-code, this is the total transferred data size. + */ +Result sslcWrite(sslcContext *context, void *buf, size_t len); + +/* + * @brief Set the RootCertChain for the specified sslc context. + * @param context sslc context. + * @param handle RootCertChain contexthandle. + */ +Result sslcContextSetRootCertChain(sslcContext *context, u32 handle); + +/* + * @brief Set the ClientCert-context for the specified sslc context. + * @param context sslc context. + * @param handle ClientCert contexthandle. + */ +Result sslcContextSetClientCert(sslcContext *context, u32 handle); + +/* + * @brief Set the context which was created by command 0x00080000, for the specified sslc context. This needs updated once it's known what this context is for. + * @param context sslc context. + * @param handle contexthandle. + */ +Result sslcContextSetHandle8(sslcContext *context, u32 handle); + +/* + * @brief Clears the options field bits for the context using the specified bitmask. + * @param context sslc context. + * @param bitmask opt bitmask. + */ +Result sslcContextClearOpt(sslcContext *context, u32 bitmask); + +/* + * @brief This loads an u32 from the specified context state. This needs updated once it's known what this field is for. + * @param context sslc context. + * @param out Output ptr to write the value to. + */ +Result sslcContextGetState(sslcContext *context, u32 *out); diff --git a/libctru/source/services/sslc.c b/libctru/source/services/sslc.c index 2fe0044..99a90d8 100644 --- a/libctru/source/services/sslc.c +++ b/libctru/source/services/sslc.c @@ -9,10 +9,12 @@ #include <3ds/services/sslc.h> #include <3ds/ipc.h> +#include "soc/soc_common.h" + Handle __sslc_servhandle; static int __sslc_refcount; -Result SSLC_Initialize(void); +static Result sslcipc_Initialize(void); Result sslcInit(Handle session_handle) { @@ -23,7 +25,7 @@ Result sslcInit(Handle session_handle) __sslc_servhandle = session_handle; if(__sslc_servhandle==0)ret = srvGetServiceHandle(&__sslc_servhandle, "ssl:C"); - if(session_handle==0 && R_SUCCEEDED(ret))ret = SSLC_Initialize(); + if(session_handle==0 && R_SUCCEEDED(ret))ret = sslcipc_Initialize(); if (R_FAILED(ret)) AtomicDecrement(&__sslc_refcount); return ret; @@ -36,7 +38,7 @@ void sslcExit(void) svcCloseHandle(__sslc_servhandle); } -Result SSLC_Initialize(void) +static Result sslcipc_Initialize(void) { u32* cmdbuf=getThreadCommandBuffer(); @@ -49,7 +51,56 @@ Result SSLC_Initialize(void) return cmdbuf[1]; } -Result sslcAddTrustedRootCA(u32 RootCertChain_contexthandle, u8 *cert, u32 certsize) +static Result sslcipc_CreateContext(sslcContext *context, int sockfd, u32 input_opt, char *hostname) +{ + u32* cmdbuf=getThreadCommandBuffer(); + u32 size = strlen(hostname)+1; + + cmdbuf[0]=IPC_MakeHeader(0x2,3,2); // 0x200C2 + cmdbuf[1]=(u32)sockfd; + cmdbuf[2]=input_opt; + cmdbuf[3]=size; + cmdbuf[4]=IPC_Desc_Buffer(size, IPC_BUFFER_R); + cmdbuf[5]=(u32)hostname; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret))context->sslchandle = cmdbuf[2]; + + return ret; +} + +Result sslcCreateRootCertChain(u32 *RootCertChain_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x3,0,0); // 0x30000 + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret))*RootCertChain_contexthandle = cmdbuf[2]; + + return ret; +} + +Result sslcDestroyRootCertChain(u32 RootCertChain_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x4,1,0); // 0x40040 + cmdbuf[1]=RootCertChain_contexthandle; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + + return cmdbuf[1]; +} + +Result sslcAddTrustedRootCA(u32 RootCertChain_contexthandle, u8 *cert, u32 certsize, u32 *cert_contexthandle) { u32* cmdbuf=getThreadCommandBuffer(); @@ -59,9 +110,311 @@ Result sslcAddTrustedRootCA(u32 RootCertChain_contexthandle, u8 *cert, u32 certs cmdbuf[3]=IPC_Desc_Buffer(certsize, IPC_BUFFER_R); cmdbuf[4]=(u32)cert; + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret) && cert_contexthandle)*cert_contexthandle = cmdbuf[2]; + + return ret; +} + +Result sslcRootCertChainAddDefaultCert(u32 RootCertChain_contexthandle, SSLC_DefaultRootCert certID, u32 *cert_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x6,2,0); // 0x60080 + cmdbuf[1]=RootCertChain_contexthandle; + cmdbuf[2]=certID; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret) && cert_contexthandle)*cert_contexthandle = cmdbuf[2]; + + return ret; +} + +Result sslcRootCertChainRemoveCert(u32 RootCertChain_contexthandle, u32 cert_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x7,2,0); // 0x70080 + cmdbuf[1]=RootCertChain_contexthandle; + cmdbuf[2]=cert_contexthandle; + Result ret=0; if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; return cmdbuf[1]; } +Result sslcOpenClientCertContext(u8 *cert, u32 certsize, u8 *key, u32 keysize, u32 *ClientCert_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0xD,2,4); // 0xD0084 + cmdbuf[1]=certsize; + cmdbuf[2]=keysize; + cmdbuf[3]=IPC_Desc_Buffer(certsize, IPC_BUFFER_R); + cmdbuf[4]=(u32)cert; + cmdbuf[5]=IPC_Desc_Buffer(keysize, IPC_BUFFER_R); + cmdbuf[6]=(u32)key; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret))*ClientCert_contexthandle = cmdbuf[2]; + + return ret; +} + +Result sslcOpenDefaultClientCertContext(SSLC_DefaultClientCert certID, u32 *ClientCert_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0xE,1,0); // 0xE0040 + cmdbuf[1]=certID; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret))*ClientCert_contexthandle = cmdbuf[2]; + + return ret; +} + +Result sslcCloseClientCertContext(u32 ClientCert_contexthandle) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0xF,1,0); // 0xF0040 + cmdbuf[1]=ClientCert_contexthandle; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + + return cmdbuf[1]; +} + +Result sslcSeedRNG(void) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x10,0,0); // 0x100000 + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + + return cmdbuf[1]; +} + +Result sslcGenerateRandomData(u8 *buf, u32 size) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x11,1,2); // 0x110042 + cmdbuf[1]=size; + cmdbuf[2]=IPC_Desc_Buffer(size, IPC_BUFFER_W); + cmdbuf[3]=(u32)buf; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + + return cmdbuf[1]; +} + +static Result sslcipc_InitializeConnectionSession(sslcContext *context) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x12,1,2); // 0x120042 + cmdbuf[1]=context->sslchandle; + cmdbuf[2]=IPC_Desc_CurProcessHandle(); + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(context->servhandle)))return ret; + + return cmdbuf[1]; +} + +static Result sslcipc_StartConnection(sslcContext *context) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x13,1,0); // 0x130040 + cmdbuf[1]=context->sslchandle; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(context->servhandle)))return ret; + + return cmdbuf[1]; +} + +static Result sslcipc_StartConnectionGetOut(sslcContext *context, int *internal_retval, u32 *out) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x14,1,0); // 0x140040 + cmdbuf[1]=context->sslchandle; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(context->servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret)) + { + if(internal_retval)*internal_retval = cmdbuf[2]; + if(out)*out = cmdbuf[3]; + } + + return ret; +} + +static Result sslcipc_DataTransfer(sslcContext *context, void *buf, size_t len, u32 type) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + if(type >= 3)return -1; + + cmdbuf[0]=IPC_MakeHeader(0x15 + type,2,2); // 0x150082 + cmdbuf[1]=context->sslchandle; + cmdbuf[2]=len; + if(type<2)cmdbuf[3]=IPC_Desc_Buffer(len, IPC_BUFFER_W); + if(type==2)cmdbuf[3]=IPC_Desc_Buffer(len, IPC_BUFFER_R); + cmdbuf[4]=(u32)buf; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(context->servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret))ret = cmdbuf[2]; + + return ret; +} + +static Result sslcipc_ContextSetValue(sslcContext *context, u32 type, u32 value) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + if(type >= 4)return -1; + + cmdbuf[0]=IPC_MakeHeader(0x18 + type,2,0); // 0x180080 + cmdbuf[1]=context->sslchandle; + cmdbuf[2]=value; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(context->servhandle)))return ret; + + return cmdbuf[1]; +} + +Result sslcContextGetState(sslcContext *context, u32 *out) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x1D,1,0); // 0x1D0040 + cmdbuf[1]=context->sslchandle; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(context->servhandle)))return ret; + ret = cmdbuf[1]; + + if(R_SUCCEEDED(ret))*out = cmdbuf[2]; + + return ret; +} + +static Result sslcipc_DestroyContext(sslcContext *context) +{ + u32* cmdbuf=getThreadCommandBuffer(); + + cmdbuf[0]=IPC_MakeHeader(0x1E,1,0); // 0x1E0040 + cmdbuf[1]=context->sslchandle; + + Result ret=0; + if(R_FAILED(ret=svcSendSyncRequest(__sslc_servhandle)))return ret; + + return cmdbuf[1]; +} + +Result sslcCreateContext(sslcContext *context, int sockfd, u32 input_opt, char *hostname) +{ + Result ret=0; + + ret = SOCU_AddGlobalSocket(sockfd); + if(R_FAILED(ret))return ret; + + sockfd = soc_get_fd(sockfd); + if(sockfd < 0) { + errno = -sockfd; + return -1; + } + + ret = sslcipc_CreateContext(context, sockfd, input_opt, hostname); + if(R_FAILED(ret))return ret; + + ret = srvGetServiceHandle(&context->servhandle, "ssl:C"); + if(R_FAILED(ret)) { + sslcipc_DestroyContext(context); + return ret; + } + + ret = sslcipc_InitializeConnectionSession(context); + if(R_FAILED(ret)) { + svcCloseHandle(context->servhandle); + sslcipc_DestroyContext(context); + } + + return ret; +} + +Result sslcDestroyContext(sslcContext *context) +{ + Result ret=0; + + svcCloseHandle(context->servhandle); + ret = sslcipc_DestroyContext(context); + + return ret; +} + +Result sslcStartConnection(sslcContext *context, int *internal_retval, u32 *out) +{ + if(internal_retval || out)return sslcipc_StartConnectionGetOut(context, internal_retval, out); + return sslcipc_StartConnection(context); +} + +Result sslcRead(sslcContext *context, void *buf, size_t len, bool peek) +{ + return sslcipc_DataTransfer(context, buf, len, peek); +} + +Result sslcWrite(sslcContext *context, void *buf, size_t len) +{ + return sslcipc_DataTransfer(context, buf, len, 2); +} + +Result sslcContextSetRootCertChain(sslcContext *context, u32 handle) +{ + return sslcipc_ContextSetValue(context, 0, handle); +} + +Result sslcContextSetClientCert(sslcContext *context, u32 handle) +{ + return sslcipc_ContextSetValue(context, 1, handle); +} + +Result sslcContextSetHandle8(sslcContext *context, u32 handle) +{ + return sslcipc_ContextSetValue(context, 2, handle); +} + +Result sslcContextClearOpt(sslcContext *context, u32 bitmask) +{ + return sslcipc_ContextSetValue(context, 3, bitmask); +} +