Skip to content

Instantly share code, notes, and snippets.

@elix22
Created June 22, 2021 14:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save elix22/601461e5dc1bc5d5561166fe330fc470 to your computer and use it in GitHub Desktop.
Save elix22/601461e5dc1bc5d5561166fe330fc470 to your computer and use it in GitHub Desktop.
Nakama client , custom certificate validation of self signed server certificate
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());
}
}
}
}
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;
}
}
}
}
/**
* 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