I've implemeneted a stream class which inheretes abstract stream. This class called sniStream which I want to add Sni facility to c#.
The source of sniStream is like this, and it uses a native c++ liberary which I've implemented:
public class SniStream : Stream
{
[DllImport(@"SniReceiver.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int write_ssl(string data);
[DllImport(@"SniReceiver.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern int read_ssl_sni(byte[] data, int offset, int count);
[DllImport(@"SniReceiver.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void cleanup_openssl();
[DllImport(@"SniReceiver.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr get_domainname(IntPtr sock);
[DllImport(@"SniReceiver.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int get_ssl_pending();
[DllImport(@"SniReceiver.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr load_all_ssl_certificates(string path);
public SniStream(Stream innerStream, bool leaveInnerStreamOpen)
{
innerStream = this;
CanWrite = CanRead = true;
Position = 0;
CanSeek = false;
}
}
public override long Position { get; set; }
public override long Length { get; }
public override bool CanWrite { get; }
public override bool CanSeek { get; }
public override bool CanRead { get; }
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count)
{
int writed = write_ssl(Encoding.UTF8.GetString(buffer));
Position += writed;
//Array.Clear(buffer, 0, buffer.Length);
}
public override int Read(byte[] buffer, int offset, int count)
{
int received = 0;
string s = "";
Array.Clear(buffer, 0, buffer.Length);
received = read_ssl_sni(buffer, offset, count);
Position += received;
return received;
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return new ValueTask(new Task(() => {
int read= write_ssl(Encoding.UTF8.GetString(buffer.ToArray()));
Position += read;
})
);
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
int revieved = 0;
string s = "";
Array.Clear(buffer.ToArray(), 0, buffer.Length);
revieved=read_ssl_sni(buffer.ToArray(), 0, 0);
Position += revieved;
return new ValueTask<int>(revieved);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int revieved = 0;
string s = "";
Array.Clear(buffer, 0, buffer.Length);
revieved = read_ssl_sni(buffer, offset, count);
Position += revieved;
return Task.FromResult(revieved);
}
public string GetDomainName(Socket sClient)
{
return Marshal.PtrToStringAnsi(get_domainname(sClient.Handle));
}
public static string LoadAllCertificates(string path)
{
string res = Marshal.PtrToStringAnsi(load_all_ssl_certificates(path));
return "";
}
public override void Flush()
{
//cleanup_openssl();
Position = 0;
}
The c++ SniReader is like this:
#include "stdafx.h"
#include "SniReceiver.h"
#include <algorithm>
#include <cctype>
#include <string>
SSL *ssl;
SSL_CTX *create_context(SSL_CTX *ctx)
{
const SSL_METHOD *method;
method = TLSv1_2_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
const char * load_all_ssl_certificates(char* certicatesPath)
{
//load certificates from a folder
}
void init_openssl()
{
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
}
void cleanup_openssl()
{
EVP_cleanup();
SSL_free(ssl);
}
static int ssl_servername_cb(SSL *s, int *ad, void *arg)
{
const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
domainname = servername;
if (servername != NULL) {
SSL_CTX * cc = certificates.at(servername);
SSL_set_SSL_CTX(s, cc);
}
return SSL_TLSEXT_ERR_OK;
}
extern "C" __declspec(dllexport) const char * __cdecl get_domainname(DWORD socket)
{
SSL_CTX *ctx = NULL;
init_openssl();
ctx = create_context(ctx);
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
ssl = SSL_new(ctx);
int x = SSL_set_fd(ssl, socket);
int hand = SSL_do_handshake(ssl);
int acc = SSL_accept(ssl);
return domainname;
}
extern "C" __declspec(dllexport) int __stdcall write_ssl(char data[])
{
int writed=SSL_write(ssl, data, strlen(data));
return writed;
}
extern "C" __declspec(dllexport) int __stdcall read_ssl_sni(char recvb[],int offset,int count)
{
const int recvbuflen = 4096;
char recvbuf[recvbuflen];
return(SSL_read(ssl, recvb, count));
}
And header file is this:
#include <WinSock2.h>
#include "minwindef.h"
#include <iostream>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <map>
#include <tuple>
#include <filesystem> //New in C++ 17 //old one include <dirent.h> for windows
namespace fs = std::filesystem; // work with vs 2019
std::map<std::string, SSL_CTX *> certificates;
const char *domainname;
char *readResult;
extern "C" __declspec(dllexport) char * __cdecl receive_socket(DWORD socket);
extern "C" __declspec(dllexport) const char * __cdecl load_all_ssl_certificates(char* path);
extern "C" __declspec(dllexport) int __stdcall add_num(int a,int b);
extern "C" __declspec(dllexport) int __stdcall write_ssl(char* data);
extern "C" __declspec(dllexport) int __stdcall read_ssl_sni(char recvbuf[], int offset, int count);
extern "C" __declspec(dllexport) const char * __cdecl get_domainname(DWORD socket);
extern "C" __declspec(dllexport) int __stdcall get_ssl_pending();
extern "C" __declspec(dllexport) void __cdecl cleanup_openssl();
The problem is c# program uses sniStream in async mode. I have 2 pages, one is called fulsh and the other flush2. (flush is just names, not flushing stream).
flush returns flush, and flush2 returns flush2.
when I call each one first and before loading first one, calling second one in new tab in browser, content of second page come before first. I think something in buffer is overlapped. I've read at msdn that implemetors of stream class for async methods should call Read and Write methods inside it.
Any Idea? should I create a byte array which is shared in sniStream as buffer?