From c6bdcf72a434a5d633c158b24efd1d4fa91a7210 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Thu, 9 Aug 2018 22:51:36 +0200 Subject: [PATCH 1/4] service::HTTP_C: Implement OpenClientCertContext, OpenDefaultClientCertContext, CloseClientCertContext --- src/core/hle/service/http_c.cpp | 176 +++++++++++++++++++++++++++++++- src/core/hle/service/http_c.h | 43 ++++++++ 2 files changed, 216 insertions(+), 3 deletions(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 54b72437a9..ffdd36c520 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -27,12 +27,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 = // 0xD8A0A0CB + ResultCode(57, ErrorModule::SSL, ErrorSummary::InvalidArgument, ErrorLevel::Permanent); void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x1, 1, 4); @@ -56,6 +66,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. @@ -88,6 +99,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; @@ -160,6 +172,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++; @@ -286,6 +299,163 @@ 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); + + if (!session_data->initialized) { + LOG_ERROR(Service_HTTP, "Command called without Initialize"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(ERROR_STATE_ERROR); + rb.PushMappedBuffer(cert_buffer); + rb.PushMappedBuffer(key_buffer); + 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, 4); + rb.Push(ERROR_NOT_IMPLEMENTED); + rb.PushMappedBuffer(cert_buffer); + rb.PushMappedBuffer(key_buffer); + 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, 4); + rb.Push(ERROR_TOO_MANY_CLIENT_CERTS); + rb.PushMappedBuffer(cert_buffer); + rb.PushMappedBuffer(key_buffer); + return; + } + + ++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_SUCCESS); + rb.PushMappedBuffer(cert_buffer); + rb.PushMappedBuffer(key_buffer); +} + +void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) { + constexpr u8 default_cert_id = 0x40; + 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; + } + + 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; + } + + 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); + + 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; @@ -417,9 +587,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 1e089d5f04..861e659a54 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -43,6 +43,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; }; @@ -54,11 +56,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; }; @@ -105,6 +109,7 @@ public: }; Handle handle; + u32 session_id; std::string url; RequestMethod method; RequestState state = RequestState::NotStarted; @@ -121,6 +126,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. @@ -197,10 +204,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; From c431b922d0ba763cca7bc4681845d1647ada126a Mon Sep 17 00:00:00 2001 From: B3n30 Date: Thu, 9 Aug 2018 23:55:04 +0200 Subject: [PATCH 2/4] fix error code comment --- src/core/hle/service/http_c.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index ffdd36c520..9dd713e634 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -41,7 +41,7 @@ const ResultCode ERROR_NOT_IMPLEMENTED = // 0xD960A3F4 const ResultCode ERROR_TOO_MANY_CLIENT_CERTS = // 0xD8A0A0CB ResultCode(ErrCodes::TooManyClientCerts, ErrorModule::HTTP, ErrorSummary::InvalidState, ErrorLevel::Permanent); -const ResultCode ERROR_WRONG_CERT_ID = // 0xD8A0A0CB +const ResultCode ERROR_WRONG_CERT_ID = // 0xD8E0B839 ResultCode(57, ErrorModule::SSL, ErrorSummary::InvalidArgument, ErrorLevel::Permanent); void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) { From 10ef3109a477dc56213277cc2a2edc1c45f21015 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Fri, 10 Aug 2018 09:55:59 +0200 Subject: [PATCH 3/4] fix num_client_certs in CloseClientCertContext --- src/core/hle/service/http_c.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 9dd713e634..f33a03dfbf 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -449,6 +449,7 @@ void HTTP_C::CloseClientCertContext(Kernel::HLERequestContext& ctx) { } client_certs.erase(cert_handle); + session_data->num_client_certs--; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); From dfb82b4dfd397038afc7cfed2854c44960a88cd0 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Tue, 4 Sep 2018 08:16:25 +0200 Subject: [PATCH 4/4] fixup! fix num_client_certs in CloseClientCertContext --- src/core/hle/service/http_c.cpp | 62 +++++++++++++-------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index f33a03dfbf..bc88df28a7 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -309,52 +309,37 @@ void HTTP_C::OpenClientCertContext(Kernel::HLERequestContext& ctx) { 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"); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(ERROR_STATE_ERROR); - rb.PushMappedBuffer(cert_buffer); - rb.PushMappedBuffer(key_buffer); - return; - } - - if (session_data->current_http_context != boost::none) { + result = ERROR_STATE_ERROR; + } else if (session_data->current_http_context != boost::none) { LOG_ERROR(Service_HTTP, "Command called with a bound context"); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(ERROR_NOT_IMPLEMENTED); - rb.PushMappedBuffer(cert_buffer); - rb.PushMappedBuffer(key_buffer); - return; - } - - if (session_data->num_client_certs >= 2) { + result = ERROR_NOT_IMPLEMENTED; + } else 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, 4); - rb.Push(ERROR_TOO_MANY_CLIENT_CERTS); - rb.PushMappedBuffer(cert_buffer); - rb.PushMappedBuffer(key_buffer); - return; + 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; } - ++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_SUCCESS); + rb.Push(result); rb.PushMappedBuffer(cert_buffer); rb.PushMappedBuffer(key_buffer); } void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) { - constexpr u8 default_cert_id = 0x40; IPC::RequestParser rp(ctx, 0x33, 1, 0); u8 cert_id = rp.Pop(); @@ -382,6 +367,7 @@ void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) { 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); @@ -396,11 +382,11 @@ void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) { return; } - 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; - }); + 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);