diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 694b8b129..7001fb3bb 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -26,12 +26,22 @@ enum { /// already-initialized session, or when using the wrong context handle in a context-bound /// session SessionStateError = 102, + TooManyClientCerts = 203, + NotImplemented = 1012, }; } const ResultCode ERROR_STATE_ERROR = // 0xD8A0A066 ResultCode(ErrCodes::SessionStateError, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent); +const ResultCode ERROR_NOT_IMPLEMENTED = // 0xD960A3F4 + ResultCode(ErrCodes::NotImplemented, ErrorModule::HTTP, ErrorSummary::Internal, + ErrorLevel::Permanent); +const ResultCode ERROR_TOO_MANY_CLIENT_CERTS = // 0xD8A0A0CB + ResultCode(ErrCodes::TooManyClientCerts, ErrorModule::HTTP, ErrorSummary::InvalidState, + ErrorLevel::Permanent); +const ResultCode ERROR_WRONG_CERT_ID = // 0xD8E0B839 + ResultCode(57, ErrorModule::SSL, ErrorSummary::InvalidArgument, ErrorLevel::Permanent); void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x1, 1, 4); @@ -55,6 +65,7 @@ void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { } session_data->initialized = true; + session_data->session_id = ++session_counter; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); // This returns 0xd8a0a046 if no network connection is available. @@ -87,6 +98,7 @@ void HTTP_C::InitializeConnectionSession(Kernel::HLERequestContext& ctx) { } session_data->initialized = true; + session_data->session_id = ++session_counter; // Bind the context to the current session. session_data->current_http_context = context_handle; @@ -159,6 +171,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) { // TODO(Subv): Find a correct default value for this field. contexts[context_counter].socket_buffer_size = 0; contexts[context_counter].handle = context_counter; + contexts[context_counter].session_id = session_data->session_id; session_data->num_http_contexts++; @@ -285,6 +298,150 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) { context_handle); } +void HTTP_C::OpenClientCertContext(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x32, 2, 4); + u32 cert_size = rp.Pop(); + u32 key_size = rp.Pop(); + Kernel::MappedBuffer& cert_buffer = rp.PopMappedBuffer(); + Kernel::MappedBuffer& key_buffer = rp.PopMappedBuffer(); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + ResultCode result(RESULT_SUCCESS); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Command called without Initialize"); + result = ERROR_STATE_ERROR; + } else if (session_data->current_http_context != boost::none) { + LOG_ERROR(Service_HTTP, "Command called with a bound context"); + result = ERROR_NOT_IMPLEMENTED; + } else if (session_data->num_client_certs >= 2) { + LOG_ERROR(Service_HTTP, "tried to load more then 2 client certs"); + result = ERROR_TOO_MANY_CLIENT_CERTS; + } else { + ++client_certs_counter; + client_certs[client_certs_counter].handle = client_certs_counter; + client_certs[client_certs_counter].certificate.resize(cert_size); + cert_buffer.Read(&client_certs[client_certs_counter].certificate[0], 0, cert_size); + client_certs[client_certs_counter].private_key.resize(key_size); + cert_buffer.Read(&client_certs[client_certs_counter].private_key[0], 0, key_size); + client_certs[client_certs_counter].session_id = session_data->session_id; + + ++session_data->num_client_certs; + } + + LOG_DEBUG(Service_HTTP, "called, cert_size {}, key_size {}", cert_size, key_size); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(result); + rb.PushMappedBuffer(cert_buffer); + rb.PushMappedBuffer(key_buffer); +} + +void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x33, 1, 0); + u8 cert_id = rp.Pop(); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Command called without Initialize"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_STATE_ERROR); + return; + } + + if (session_data->current_http_context != boost::none) { + LOG_ERROR(Service_HTTP, "Command called with a bound context"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_NOT_IMPLEMENTED); + return; + } + + if (session_data->num_client_certs >= 2) { + LOG_ERROR(Service_HTTP, "tried to load more then 2 client certs"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_TOO_MANY_CLIENT_CERTS); + return; + } + + constexpr u8 default_cert_id = 0x40; + if (cert_id != default_cert_id) { + LOG_ERROR(Service_HTTP, "called with invalid cert_id {}", cert_id); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERROR_WRONG_CERT_ID); + return; + } + + if (!ClCertA.init) { + LOG_ERROR(Service_HTTP, "called but ClCertA is missing"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(static_cast(-1)); + return; + } + + const auto& it = std::find_if(client_certs.begin(), client_certs.end(), + [default_cert_id, &session_data](const auto& i) { + return default_cert_id == i.second.cert_id && + session_data->session_id == i.second.session_id; + }); + + if (it != client_certs.end()) { + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(it->first); + + LOG_DEBUG(Service_HTTP, "called, with an already loaded cert_id={}", cert_id); + return; + } + + ++client_certs_counter; + client_certs[client_certs_counter].handle = client_certs_counter; + client_certs[client_certs_counter].certificate = ClCertA.certificate; + client_certs[client_certs_counter].private_key = ClCertA.private_key; + client_certs[client_certs_counter].session_id = session_data->session_id; + ++session_data->num_client_certs; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(client_certs_counter); + + LOG_DEBUG(Service_HTTP, "called, cert_id={}", cert_id); +} + +void HTTP_C::CloseClientCertContext(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x34, 1, 0); + ClientCertContext::Handle cert_handle = rp.Pop(); + + auto* session_data = GetSessionData(ctx.Session()); + ASSERT(session_data); + + if (client_certs.find(cert_handle) == client_certs.end()) { + LOG_ERROR(Service_HTTP, "Command called with a unkown client cert handle {}", cert_handle); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + // This just return success without doing anything + rb.Push(RESULT_SUCCESS); + return; + } + + if (client_certs[cert_handle].session_id != session_data->session_id) { + LOG_ERROR(Service_HTTP, "called from another main session"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + // This just return success without doing anything + rb.Push(RESULT_SUCCESS); + return; + } + + client_certs.erase(cert_handle); + session_data->num_client_certs--; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_HTTP, "called, cert_handle={}", cert_handle); +} + void HTTP_C::DecryptClCertA() { static constexpr u32 iv_length = 16; @@ -416,9 +573,9 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { {0x002F0082, nullptr, "RootCertChainAddCert"}, {0x00300080, nullptr, "RootCertChainAddDefaultCert"}, {0x00310080, nullptr, "RootCertChainRemoveCert"}, - {0x00320084, nullptr, "OpenClientCertContext"}, - {0x00330040, nullptr, "OpenDefaultClientCertContext"}, - {0x00340040, nullptr, "CloseClientCertContext"}, + {0x00320084, &HTTP_C::OpenClientCertContext, "OpenClientCertContext"}, + {0x00330040, &HTTP_C::OpenDefaultClientCertContext, "OpenDefaultClientCertContext"}, + {0x00340040, &HTTP_C::CloseClientCertContext, "CloseClientCertContext"}, {0x00350186, nullptr, "SetDefaultProxy"}, {0x00360000, nullptr, "ClearDNSCache"}, {0x00370080, nullptr, "SetKeepAlive"}, diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h index 033ed5aef..2c452cc71 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -42,6 +42,8 @@ enum class RequestState : u8 { struct ClientCertContext { using Handle = u32; Handle handle; + u32 session_id; + u8 cert_id; std::vector certificate; std::vector private_key; }; @@ -53,11 +55,13 @@ struct RootCertChain { struct RootCACert { using Handle = u32; Handle handle; + u32 session_id; std::vector certificate; }; using Handle = u32; Handle handle; + u32 session_id; std::vector certificates; }; @@ -104,6 +108,7 @@ public: }; Handle handle; + u32 session_id; std::string url; RequestMethod method; RequestState state = RequestState::NotStarted; @@ -120,6 +125,8 @@ struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { /// has been bound. Certain commands can only be called on a session with a bound context. boost::optional current_http_context; + u32 session_id; + /// Number of HTTP contexts that are currently opened in this session. u32 num_http_contexts = 0; /// Number of ClientCert contexts that are currently opened in this session. @@ -196,10 +203,46 @@ private: */ void AddRequestHeader(Kernel::HLERequestContext& ctx); + /** + * HTTP_C::OpenClientCertContext service function + * Inputs: + * 1 : Cert size + * 2 : Key size + * 3 : (CertSize<<4) | 10 + * 4 : Pointer to input cert + * 5 : (KeySize<<4) | 10 + * 6 : Pointer to input key + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void OpenClientCertContext(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::OpenDefaultClientCertContext service function + * Inputs: + * 1 : CertID + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Client Cert context handle + */ + void OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx); + + /** + * HTTP_C::CloseClientCertContext service function + * Inputs: + * 1 : ClientCert Handle + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void CloseClientCertContext(Kernel::HLERequestContext& ctx); + void DecryptClCertA(); Kernel::SharedPtr shared_memory = nullptr; + /// The next number to use when a new HTTP session is initalized. + u32 session_counter = 0; + /// The next handle number to use when a new HTTP context is created. Context::Handle context_counter = 0;