Created
June 22, 2021 14:23
-
-
Save elix22/601461e5dc1bc5d5561166fe330fc470 to your computer and use it in GitHub Desktop.
Nakama client , custom certificate validation of self signed server certificate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Urho; | |
using System; | |
using System.Threading.Tasks; | |
using Nakama; | |
namespace NakamaNetworking | |
{ | |
public class NakamaClient | |
{ | |
// Secured SSL | |
public string Scheme = "https"; | |
public int Port = 7350; | |
public string ServerKey = "defaultkey"; | |
public IClient Client = null; | |
public ISession Session = null; | |
public ISocket Socket = null; | |
private const string SessionPrefName = "nakama.session"; | |
private const string DeviceIdentifierPrefName = "nakama.deviceUniqueIdentifier"; | |
public string currentMatchmakingTicket = String.Empty; | |
public string matchID = String.Empty; | |
/// <summary> | |
/// Connects to the Nakama server using device authentication and opens socket for realtime communication. | |
/// </summary> | |
public async Task Connect(string Host , string UserName = null) | |
{ | |
try | |
{ | |
// Connect to the Nakama server , with a callback to custom validation of self signed Certificate. | |
Client = new Nakama.Client(Scheme, Host, Port, ServerKey,SSLHttpRequestAdapter.WithGzip()); | |
Attempt to restore an existing user session. | |
var authToken = PlayerPrefs.GetString(SessionPrefName); | |
if (!string.IsNullOrEmpty(authToken)) | |
{ | |
var session = Nakama.Session.Restore(authToken); | |
if (!session.IsExpired) | |
{ | |
Session = session; | |
} | |
} | |
// If we weren't able to restore an existing session, authenticate to create a new user session. | |
if (Session == null) | |
{ | |
string deviceId; | |
// If we've already stored a device identifier in PlayerPrefs then use that. | |
if (PlayerPrefs.HasKey(DeviceIdentifierPrefName)) | |
{ | |
deviceId = PlayerPrefs.GetString(DeviceIdentifierPrefName); | |
} | |
else | |
{ | |
deviceId = System.Guid.NewGuid().ToString(); | |
// Store the device identifier to ensure we use the same one each time from now on. | |
PlayerPrefs.SetString(DeviceIdentifierPrefName, deviceId); | |
} | |
// Use Nakama Device authentication to create a new session using the device identifier. | |
Session = await Client.AuthenticateDeviceAsync(deviceId,UserName); | |
// Store the auth token that comes back so that we can restore the session later if necessary. | |
PlayerPrefs.SetString(SessionPrefName, Session.AuthToken); | |
} | |
// Open a new Socket for realtime communication with a callback to custom validation of self signed Certificate | |
Socket = Nakama.Socket.From(Client,ServerCertificateCustomValidation.ValidateCertificate); | |
if (OnSocketConnected != null) | |
Socket.Connected += OnSocketConnected; | |
if (OnSocketClosed != null) | |
Socket.Closed += OnSocketClosed; | |
await Socket.ConnectAsync(Session, true); | |
LogSharp.Debug(Session.ToString()); | |
LogSharp.Debug(Socket.ToString()); | |
} | |
catch (Exception e) | |
{ | |
LogSharp.Error(e.ToString()); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Net.Http; | |
using System.Net.Security; | |
using System.Security.Cryptography.X509Certificates; | |
namespace Nakama | |
{ | |
public static class ServerCertificateCustomValidation | |
{ | |
public static bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) | |
{ | |
// Basically I can validate the entire certificate data and not just the fingerprint | |
string fingerprint = certificate.GetCertHashString(); | |
if (Global.ServerFingerPrint != "" && fingerprint == Global.ServerFingerPrint) | |
{ | |
return true; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
// Basically I can validate the entire certificate data and not just the fingerprint | |
public static bool ValidateCertificate(HttpRequestMessage requestMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors sslErrors) | |
{ | |
string fingerprint = certificate.GetCertHashString(); | |
if (Global.ServerFingerPrint != "" && fingerprint == Global.ServerFingerPrint) | |
{ | |
return true; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright 2019 The Nakama Authors | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Net; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Nakama.TinyJson; | |
namespace Nakama | |
{ | |
/// <summary> | |
/// HTTP Request adapter which uses the .NET HttpClient to send requests. | |
/// </summary> | |
/// <remarks> | |
/// Accept header is always set as 'application/json'. | |
/// </remarks> | |
public class SSLHttpRequestAdapter : IHttpAdapter | |
{ | |
/// <inheritdoc cref="IHttpAdapter.Logger"/> | |
public ILogger Logger { get; set; } | |
private readonly HttpClient _httpClient; | |
public SSLHttpRequestAdapter(HttpClient httpClient) | |
{ | |
_httpClient = httpClient; | |
// remove cap of max timeout on HttpClient from 100 seconds. | |
_httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan; | |
} | |
/// <inheritdoc cref="IHttpAdapter"/> | |
public async Task<string> SendAsync(string method, Uri uri, IDictionary<string, string> headers, byte[] body, | |
int timeout) | |
{ | |
var request = new HttpRequestMessage | |
{ | |
RequestUri = uri, | |
Method = new HttpMethod(method), | |
Headers = | |
{ | |
Accept = {new MediaTypeWithQualityHeaderValue("application/json")} | |
} | |
}; | |
foreach (var kv in headers) | |
{ | |
request.Headers.Add(kv.Key, kv.Value); | |
} | |
if (body != null) | |
{ | |
request.Content = new ByteArrayContent(body); | |
} | |
var timeoutToken = new CancellationTokenSource(); | |
timeoutToken.CancelAfter(TimeSpan.FromSeconds(timeout)); | |
Logger?.InfoFormat("Send: method='{0}', uri='{1}', body='{2}'", method, uri, body); | |
var response = await _httpClient.SendAsync(request, timeoutToken.Token); | |
var contents = await response.Content.ReadAsStringAsync(); | |
response.Content?.Dispose(); | |
Logger?.InfoFormat("Received: status={0}, contents='{1}'", response.StatusCode, contents); | |
if (response.IsSuccessStatusCode) | |
{ | |
return contents; | |
} | |
var decoded = contents.FromJson<Dictionary<string, object>>(); | |
string message = decoded.ContainsKey("message") ? decoded["message"].ToString() : string.Empty; | |
int grpcCode = decoded.ContainsKey("code") ? (int) decoded["code"] : -1; | |
var exception = new ApiResponseException((int) response.StatusCode, message, grpcCode); | |
if (decoded.ContainsKey("error")) | |
{ | |
IHttpAdapterUtil.CopyResponseError(this, decoded["error"], exception); | |
} | |
throw exception; | |
} | |
/// <summary> | |
/// A new HTTP adapter with configuration for gzip support in the underlying HTTP client. | |
/// </summary> | |
/// <remarks> | |
/// NOTE Decompression does not work with Mono AOT on Android. | |
/// </remarks> | |
/// <param name="decompression">If automatic decompression should be enabled with the HTTP adapter.</param> | |
/// <param name="compression">If automatic compression should be enabled with the HTTP adapter.</param> | |
/// <returns>A new HTTP adapter.</returns> | |
public static IHttpAdapter WithGzip(bool decompression = false, bool compression = false) | |
{ | |
var handler = new HttpClientHandler(); | |
if (handler.SupportsAutomaticDecompression && decompression) | |
{ | |
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; | |
} | |
// Set custom server validation callback | |
handler.ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation.ValidateCertificate; | |
var client = | |
new HttpClient(compression ? (HttpMessageHandler) new GZipHttpClientHandler(handler) : handler); | |
return new HttpRequestAdapter(client); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment