client certificate support

This commit is contained in:
Adam D. Ruppe 2022-02-03 11:40:42 -05:00
parent 2d4025f7eb
commit 3bc691b1b4
1 changed files with 129 additions and 0 deletions

129
http2.d
View File

@ -2286,6 +2286,19 @@ enum HttpVerb {
MERGE
}
/++
Supported file formats for [HttpClient.setClientCert]. These are loaded by OpenSSL
in the current implementation.
History:
Added February 3, 2022 (dub v10.6)
+/
enum CertificateFileFormat {
guess, /// try to guess the format from the file name and/or contents
pem, /// the files are specifically in PEM format
der /// the files are specifically in DER format
}
/++
HttpClient keeps cookies, location, and some other state to reuse connections, when possible, like a web browser.
You can use it as your entry point to make http requests.
@ -2298,6 +2311,33 @@ class HttpClient {
bool acceptGzip = true; ///
bool keepAlive = true; ///
/++
Sets the client certificate used as a log in identifier on https connections.
The certificate and key must be unencrypted at this time and both must be in
the same file format.
Bugs:
The current implementation sets the filenames into a static variable,
meaning it is shared across all clients and connections.
Errors in the cert or key are only reported if the server reports an
authentication failure. Make sure you are passing correct filenames
and formats of you do see a failure.
History:
Added February 2, 2022 (dub v10.6)
+/
void setClientCert(string certFilename, string keyFilename, CertificateFileFormat certFormat = CertificateFileFormat.guess) {
this.certFilename = certFilename;
this.keyFilename = keyFilename;
this.certFormat = certFormat;
}
// FIXME: try to not make these static
private static string certFilename;
private static string keyFilename;
private static CertificateFileFormat certFormat;
///
@property Uri location() {
return currentUrl;
@ -2739,8 +2779,25 @@ version(use_openssl) {
SSL_METHOD* function() TLS_client_method;
void function(SSL_CTX*, void function(SSL*, char* line)) SSL_CTX_set_keylog_callback;
// client cert things
void function (SSL_CTX *ctx, int function(SSL *ssl, X509 **x509, EVP_PKEY **pkey)) SSL_CTX_set_client_cert_cb;
X509* function(FILE *fp, X509 **x, pem_password_cb *cb, void *u) PEM_read_X509;
EVP_PKEY* function(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void* userPointer) PEM_read_PrivateKey;
EVP_PKEY* function(FILE *fp, EVP_PKEY **a) d2i_PrivateKey_fp;
X509* function(FILE *fp, X509 **x) d2i_X509_fp;
}
}
// copy it into the buf[0 .. size] and return actual length you read.
// rwflag == 0 when reading, 1 when writing.
extern(C)
alias pem_password_cb = int function(char* buffer, int bufferSize, int rwflag, void* userPointer);
struct X509;
struct EVP_PKEY;
import core.stdc.config;
@ -2773,6 +2830,38 @@ version(use_openssl) {
return ossllib.SSL_set_fd(a, b);
else throw new Exception("SSL_set_fd not loaded");
}
extern(C)
alias client_cert_cb = int function(SSL *ssl, X509 **x509, EVP_PKEY **pkey);
void SSL_CTX_set_client_cert_cb(SSL_CTX *ctx, client_cert_cb cb) {
if(ossllib.SSL_CTX_set_client_cert_cb)
return ossllib.SSL_CTX_set_client_cert_cb(ctx, cb);
else throw new Exception("SSL_CTX_set_client_cert_cb not loaded");
}
X509* PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u) {
if(ossllib.PEM_read_X509)
return ossllib.PEM_read_X509(fp, x, cb, u);
else throw new Exception("PEM_read_X509 not loaded");
}
EVP_PKEY* PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u) {
if(ossllib.PEM_read_PrivateKey)
return ossllib.PEM_read_PrivateKey(fp, x, cb, u);
else throw new Exception("PEM_read_PrivateKey not loaded");
}
EVP_PKEY* d2i_PrivateKey_fp(FILE *fp, EVP_PKEY **a) {
if(ossllib.d2i_PrivateKey_fp)
return ossllib.d2i_PrivateKey_fp(fp, a);
else throw new Exception("d2i_PrivateKey_fp not loaded");
}
X509* d2i_X509_fp(FILE *fp, X509 **x) {
if(ossllib.d2i_X509_fp)
return ossllib.d2i_X509_fp(fp, x);
else throw new Exception("d2i_X509_fp not loaded");
}
int SSL_connect(SSL* a) {
if(ossllib.SSL_connect)
return ossllib.SSL_connect(a);
@ -2987,6 +3076,46 @@ version(use_openssl) {
if(!verifyPeer)
SSL_set_verify(ssl, SSL_VERIFY_NONE, null);
SSL_set_fd(ssl, cast(int) this.handle); // on win64 it is necessary to truncate, but the value is never large anyway see http://openssl.6102.n7.nabble.com/Sockets-windows-64-bit-td36169.html
SSL_CTX_set_client_cert_cb(ctx, &cb);
}
extern(C)
static int cb(SSL* ssl, X509** x509, EVP_PKEY** pkey) {
if(HttpClient.certFilename.length && HttpClient.keyFilename.length) {
FILE* fpCert = fopen((HttpClient.certFilename ~ "\0").ptr, "rb");
if(fpCert is null)
return 0;
scope(exit)
fclose(fpCert);
FILE* fpKey = fopen((HttpClient.keyFilename ~ "\0").ptr, "rb");
if(fpKey is null)
return 0;
scope(exit)
fclose(fpKey);
with(CertificateFileFormat)
final switch(HttpClient.certFormat) {
case guess:
if(HttpClient.certFilename.endsWith(".pem") || HttpClient.keyFilename.endsWith(".pem"))
goto case pem;
else
goto case der;
case pem:
*x509 = PEM_read_X509(fpCert, null, null, null);
*pkey = PEM_read_PrivateKey(fpKey, null, null, null);
break;
case der:
*x509 = d2i_X509_fp(fpCert, null);
*pkey = d2i_PrivateKey_fp(fpKey, null);
break;
}
return 1;
}
return 0;
}
bool dataPending() {