mirror of
https://github.com/vlang/v.git
synced 2025-08-03 17:57:59 -04:00
net.mbedtls: support Server Name Indication (SNI) (#22012)
This commit is contained in:
parent
ac7fedba29
commit
d7bdb72b48
22
examples/ssl_server/cert/makecerts.sh
Normal file
22
examples/ssl_server/cert/makecerts.sh
Normal file
@ -0,0 +1,22 @@
|
||||
# generates the certificates used by the server_sni_advanced.v example
|
||||
|
||||
# default
|
||||
gen_key type=rsa rsa_keysize=4096 filename=0x.dk.key
|
||||
cert_write selfsign=1 issuer_key=0x.dk.key \
|
||||
issuer_name=CN=0x.dk,O=0x,C=DK \
|
||||
not_before=20130101000000 not_after=20251231235959 \
|
||||
is_ca=1 max_pathlen=0 output_file=0x.dk.crt
|
||||
|
||||
# 1x.dk
|
||||
gen_key type=rsa rsa_keysize=4096 filename=1x.dk.key
|
||||
cert_write selfsign=1 issuer_key=1x.dk.key \
|
||||
issuer_name=CN=1x.dk,O=1x.dk,C=DK \
|
||||
not_before=20130101000000 not_after=20251231235959 \
|
||||
is_ca=1 max_pathlen=0 output_file=1x.dk.crt
|
||||
|
||||
# 2x.dk
|
||||
gen_key type=rsa rsa_keysize=4096 filename=2x.dk.key
|
||||
cert_write selfsign=1 issuer_key=2x.dk.key \
|
||||
issuer_name=CN=2x.dk,O=2x.dk,C=DK \
|
||||
not_before=20130101000000 not_after=20251231235959 \
|
||||
is_ca=1 max_pathlen=0 output_file=2x.dk.crt
|
70
examples/ssl_server/server_sni.v
Normal file
70
examples/ssl_server/server_sni.v
Normal file
@ -0,0 +1,70 @@
|
||||
import io
|
||||
import os
|
||||
import net.mbedtls
|
||||
|
||||
// This example shows a very minimal HTTP server, that supports SNI.
|
||||
// Server Name Indication (SNI) is defined in RFC 6066.
|
||||
// See https://mbed-tls.readthedocs.io/en/latest/kb/how-to/use-sni/
|
||||
|
||||
// Use the following commands, in separate shells, to test that it works,
|
||||
// after you start the server:
|
||||
// curl -ik --resolve 1x.dk:8443:0.0.0.0 https://1x.dk:8443/abcd
|
||||
// curl -ik --resolve example.com:8443:0.0.0.0 https://example.com:8443/xyz
|
||||
// Both of them should work, and the server should show that it tried to load
|
||||
// certificates, responding to the given domain name in each of the requests.
|
||||
|
||||
// callback to return the certificates to use for the connection
|
||||
fn get_cert(mut l mbedtls.SSLListener, host string) !&mbedtls.SSLCerts {
|
||||
println('reading certificates for ${host} ...')
|
||||
|
||||
// For our example, just always use the same certificates. In a real server,
|
||||
// that should be dependent on the host parameter, and perhaps there should
|
||||
// be some caching, so that they are not read each time for each request from
|
||||
// the filesystem.
|
||||
|
||||
cert := os.read_file('cert/server.crt') or {
|
||||
return error('Failed to read certificate ${host}: ${err}')
|
||||
}
|
||||
key := os.read_file('cert/server.key') or { return error('Failed to read key ${host}: ${err}') }
|
||||
println('read certs for ${host}')
|
||||
|
||||
if mut c := mbedtls.new_sslcerts_in_memory('', cert, key) {
|
||||
return c
|
||||
} else {
|
||||
return error('mbedtls.new_sslcerts_in_memory err: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut server := mbedtls.new_ssl_listener('0.0.0.0:8443', mbedtls.SSLConnectConfig{
|
||||
verify: os.resource_abs_path('cert/ca.crt')
|
||||
cert: os.resource_abs_path('cert/server.crt')
|
||||
cert_key: os.resource_abs_path('cert/server.key')
|
||||
validate: false
|
||||
get_certificate: get_cert // set the SNI callback to enable this feature
|
||||
})!
|
||||
println('Listening on https://0.0.0.0:8443/ ...')
|
||||
for {
|
||||
mut client := server.accept() or {
|
||||
println('accept error: ${err}')
|
||||
continue
|
||||
}
|
||||
mut reader := io.new_buffered_reader(reader: client)
|
||||
for {
|
||||
line := reader.read_line() or { break }
|
||||
if line.len == 0 {
|
||||
break
|
||||
}
|
||||
println(line)
|
||||
}
|
||||
client.write_string('HTTP/1.1 200 OK\r\n') or { continue }
|
||||
|
||||
client.write_string('Content-Type: text/html\r\n') or { continue }
|
||||
client.write_string('Connection: close\r\n') or { continue }
|
||||
client.write_string('\r\n') or { continue }
|
||||
|
||||
client.write_string('Hello\n') or { continue }
|
||||
client.shutdown()!
|
||||
}
|
||||
server.shutdown()!
|
||||
}
|
120
examples/ssl_server/server_sni_advanced.v
Normal file
120
examples/ssl_server/server_sni_advanced.v
Normal file
@ -0,0 +1,120 @@
|
||||
import net.mbedtls
|
||||
import os
|
||||
import io
|
||||
import net.http
|
||||
|
||||
// first run cert/makecerts.sh to generate the certificates for this server
|
||||
|
||||
// start the server with:
|
||||
// v run server_sni_advanced.v
|
||||
|
||||
// test using curl:
|
||||
|
||||
// connection using the 1x.dk certificate
|
||||
// curl -vik --resolve 1x.dk:8443:0.0.0.0 https://1x.dk:8443/
|
||||
|
||||
// connection using the 2x.dk certificate
|
||||
// curl -vik --resolve 2x.dk:8443:0.0.0.0 https://2x.dk:8443/
|
||||
|
||||
// default certificate (no sni callback)
|
||||
// curl -vik https://0.0.0.0:8443/
|
||||
|
||||
@[heap]
|
||||
struct CertManager {
|
||||
mut:
|
||||
// cache for the certificates
|
||||
certs map[string]&mbedtls.SSLCerts
|
||||
}
|
||||
|
||||
fn (mut cm CertManager) get_cert(mut l mbedtls.SSLListener, host string) !&mbedtls.SSLCerts {
|
||||
println('${host}')
|
||||
|
||||
if c := cm.certs[host] {
|
||||
println('read certs for ${host} already ready')
|
||||
return c
|
||||
} else {
|
||||
cert := os.read_file('cert/${host}.crt') or {
|
||||
return error('Failed to read certificate ${host}: ${err}')
|
||||
}
|
||||
key := os.read_file('cert/${host}.key') or {
|
||||
return error('Failed to read key ${host}: ${err}')
|
||||
}
|
||||
println('read certs for ${host}')
|
||||
|
||||
if mut c := mbedtls.new_sslcerts_in_memory('', cert, key) {
|
||||
cm.certs[host] = c
|
||||
return c
|
||||
} else {
|
||||
return error('mbedtls.new_sslcerts_in_memory err: ${err}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Load the default certificates
|
||||
cert := os.read_file('cert/0x.dk.crt') or {
|
||||
eprintln('Failed to read certificate: ${err}')
|
||||
return
|
||||
}
|
||||
key := os.read_file('cert/0x.dk.key') or {
|
||||
eprintln('Failed to read key: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
cm := CertManager{}
|
||||
|
||||
// Create the SSL configuration
|
||||
mut config := mbedtls.SSLConnectConfig{
|
||||
cert: cert
|
||||
cert_key: key
|
||||
in_memory_verification: true // !importent
|
||||
get_certificate: cm.get_cert
|
||||
}
|
||||
|
||||
mut server := mbedtls.new_ssl_listener('0.0.0.0:8443', config) or {
|
||||
println('new_ssl_listener : ${err.msg()} : ${err.code()}')
|
||||
return
|
||||
}
|
||||
println('Listening on https://0.0.0.0:8443')
|
||||
|
||||
// Accept and handle connections
|
||||
for {
|
||||
mut client := server.accept() or {
|
||||
println('accept : ${err.msg()} : ${err.code()}')
|
||||
continue
|
||||
}
|
||||
go handle_connection(mut client)
|
||||
}
|
||||
|
||||
server.shutdown() or {
|
||||
println('server.shutdown : ${err.msg()} : ${err.code()}')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_connection(mut ssl_conn mbedtls.SSLConn) {
|
||||
mut reader := io.new_buffered_reader(reader: ssl_conn)
|
||||
|
||||
request := http.parse_request(mut reader) or {
|
||||
println('parse_request failed : ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
println('Received request: ${request.url}')
|
||||
|
||||
// Respond to the request
|
||||
body := 'Hello, HTTPS!'
|
||||
res := http.new_response(http.ResponseConfig{
|
||||
body: body
|
||||
})
|
||||
|
||||
ssl_conn.write(res.bytes()) or {
|
||||
eprintln('Failed to write response: ${err}')
|
||||
return
|
||||
}
|
||||
|
||||
ssl_conn.shutdown() or {
|
||||
eprintln('Failed to shutdown connection: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
@ -182,6 +182,10 @@ fn C.mbedtls_ssl_free(&C.mbedtls_ssl_context)
|
||||
fn C.mbedtls_ssl_config_init(&C.mbedtls_ssl_config)
|
||||
fn C.mbedtls_ssl_config_defaults(&C.mbedtls_ssl_config, int, int, int) int
|
||||
fn C.mbedtls_ssl_config_free(&C.mbedtls_ssl_config)
|
||||
fn C.mbedtls_ssl_conf_sni(&C.mbedtls_ssl_config, fn (voidptr, &C.mbedtls_ssl_context, &char, int) int, voidptr)
|
||||
fn C.mbedtls_ssl_set_hs_ca_chain(&C.mbedtls_ssl_config, &C.mbedtls_x509_crt, &C.mbedtls_x509_crl)
|
||||
fn C.mbedtls_ssl_set_hs_own_cert(&C.mbedtls_ssl_context, &C.mbedtls_x509_crt, &C.mbedtls_pk_context) int
|
||||
fn C.mbedtls_ssl_set_hs_authmode(&C.mbedtls_ssl_context, int)
|
||||
|
||||
fn C.mbedtls_pk_init(&C.mbedtls_pk_context)
|
||||
fn C.mbedtls_pk_free(&C.mbedtls_pk_context)
|
||||
|
@ -24,12 +24,84 @@ fn init() {
|
||||
}
|
||||
}
|
||||
|
||||
struct SSLCerts {
|
||||
// SSLCerts represents a pair of CA and client certificates + key
|
||||
pub struct SSLCerts {
|
||||
pub mut:
|
||||
cacert C.mbedtls_x509_crt
|
||||
client_cert C.mbedtls_x509_crt
|
||||
client_key C.mbedtls_pk_context
|
||||
}
|
||||
|
||||
// new_sslcerts initializes and returns a pair of SSL certificates and key
|
||||
pub fn new_sslcerts() &SSLCerts {
|
||||
mut certs := SSLCerts{}
|
||||
C.mbedtls_x509_crt_init(&certs.cacert)
|
||||
C.mbedtls_x509_crt_init(&certs.client_cert)
|
||||
C.mbedtls_pk_init(&certs.client_key)
|
||||
return &certs
|
||||
}
|
||||
|
||||
// new_sslcerts_in_memory creates a pair of SSL certificates, given their contents (not paths).
|
||||
pub fn new_sslcerts_in_memory(verify string, cert string, cert_key string) !&SSLCerts {
|
||||
mut certs := new_sslcerts()
|
||||
if verify != '' {
|
||||
ret := C.mbedtls_x509_crt_parse(&certs.cacert, verify.str, verify.len + 1)
|
||||
if ret != 0 {
|
||||
return error_with_code('mbedtls_x509_crt_parse error', ret)
|
||||
}
|
||||
}
|
||||
if cert != '' {
|
||||
ret := C.mbedtls_x509_crt_parse(&certs.client_cert, cert.str, cert.len + 1)
|
||||
if ret != 0 {
|
||||
return error_with_code('mbedtls_x509_crt_parse error', ret)
|
||||
}
|
||||
}
|
||||
if cert_key != '' {
|
||||
unsafe {
|
||||
ret := C.mbedtls_pk_parse_key(&certs.client_key, cert_key.str, cert_key.len + 1,
|
||||
0, 0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
|
||||
if ret != 0 {
|
||||
return error_with_code('v error', ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
// new_sslcerts_from_file creates a new pair of SSL certificates, given their paths on the filesystem.
|
||||
pub fn new_sslcerts_from_file(verify string, cert string, cert_key string) !&SSLCerts {
|
||||
mut certs := new_sslcerts()
|
||||
if verify != '' {
|
||||
ret := C.mbedtls_x509_crt_parse_file(&certs.cacert, &char(verify.str))
|
||||
if ret != 0 {
|
||||
return error_with_code('mbedtls_x509_crt_parse error', ret)
|
||||
}
|
||||
}
|
||||
if cert != '' {
|
||||
ret := C.mbedtls_x509_crt_parse_file(&certs.client_cert, &char(cert.str))
|
||||
if ret != 0 {
|
||||
return error_with_code('mbedtls_x509_crt_parse error', ret)
|
||||
}
|
||||
}
|
||||
if cert_key != '' {
|
||||
unsafe {
|
||||
ret := C.mbedtls_pk_parse_keyfile(&certs.client_key, &char(cert_key.str),
|
||||
0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
|
||||
if ret != 0 {
|
||||
return error_with_code('v error', ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
// cleanup frees the SSL certificates
|
||||
pub fn (mut c SSLCerts) cleanup() {
|
||||
C.mbedtls_x509_crt_free(&c.cacert)
|
||||
C.mbedtls_x509_crt_free(&c.client_cert)
|
||||
C.mbedtls_pk_free(&c.client_key)
|
||||
}
|
||||
|
||||
// SSLConn is the current connection
|
||||
pub struct SSLConn {
|
||||
pub:
|
||||
@ -77,9 +149,7 @@ pub fn (mut l SSLListener) shutdown() ! {
|
||||
eprintln(@METHOD)
|
||||
}
|
||||
if unsafe { l.certs != nil } {
|
||||
C.mbedtls_x509_crt_free(&l.certs.cacert)
|
||||
C.mbedtls_x509_crt_free(&l.certs.client_cert)
|
||||
C.mbedtls_pk_free(&l.certs.client_key)
|
||||
l.certs.cleanup()
|
||||
}
|
||||
C.mbedtls_ssl_free(&l.ssl)
|
||||
C.mbedtls_ssl_config_free(&l.conf)
|
||||
@ -115,28 +185,12 @@ fn (mut l SSLListener) init() ! {
|
||||
mut ret := 0
|
||||
|
||||
if l.config.in_memory_verification {
|
||||
if l.config.verify != '' {
|
||||
ret = C.mbedtls_x509_crt_parse(&l.certs.cacert, l.config.verify.str,
|
||||
l.config.verify.len + 1)
|
||||
}
|
||||
if l.config.cert != '' {
|
||||
ret = C.mbedtls_x509_crt_parse(&l.certs.client_cert, l.config.cert.str,
|
||||
l.config.cert.len + 1)
|
||||
}
|
||||
if l.config.cert_key != '' {
|
||||
unsafe {
|
||||
ret = C.mbedtls_pk_parse_key(&l.certs.client_key, l.config.cert_key.str,
|
||||
l.config.cert_key.len + 1, 0, 0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
|
||||
}
|
||||
l.certs = new_sslcerts_in_memory(l.config.verify, l.config.cert, l.config.cert_key) or {
|
||||
return error('Cert failure')
|
||||
}
|
||||
} else {
|
||||
if l.config.verify != '' {
|
||||
ret = C.mbedtls_x509_crt_parse_file(&l.certs.cacert, &char(l.config.verify.str))
|
||||
}
|
||||
ret = C.mbedtls_x509_crt_parse_file(&l.certs.client_cert, &char(l.config.cert.str))
|
||||
unsafe {
|
||||
ret = C.mbedtls_pk_parse_keyfile(&l.certs.client_key, &char(l.config.cert_key.str),
|
||||
0, C.mbedtls_ctr_drbg_random, &mbedtls.ctr_drbg)
|
||||
l.certs = new_sslcerts_from_file(l.config.verify, l.config.cert, l.config.cert_key) or {
|
||||
return error('Cert failure')
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +228,25 @@ fn (mut l SSLListener) init() ! {
|
||||
if ret != 0 {
|
||||
return error_with_code("can't setup ssl", ret)
|
||||
}
|
||||
|
||||
if get_cert_callback := l.config.get_certificate {
|
||||
l.init_sni(get_cert_callback)
|
||||
}
|
||||
}
|
||||
|
||||
// setup SNI callback
|
||||
fn (mut l SSLListener) init_sni(get_cert_callback fn (mut SSLListener, string) !&SSLCerts) {
|
||||
$if trace_ssl ? {
|
||||
eprintln(@METHOD)
|
||||
}
|
||||
C.mbedtls_ssl_conf_sni(&l.conf, fn [get_cert_callback, mut l] (p_info voidptr, ssl &C.mbedtls_ssl_context, name &char, lng int) int {
|
||||
host := unsafe { name.vstring_literal_with_len(lng) }
|
||||
if certs := get_cert_callback(mut l, host) {
|
||||
return C.mbedtls_ssl_set_hs_own_cert(ssl, &certs.client_cert, &certs.client_key)
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}, &l.conf)
|
||||
}
|
||||
|
||||
// accepts a new connection and returns a SSLConn of the connected client
|
||||
@ -223,6 +296,8 @@ pub:
|
||||
validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid
|
||||
|
||||
in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
|
||||
|
||||
get_certificate ?fn (mut SSLListener, string) !&SSLCerts
|
||||
}
|
||||
|
||||
// new_ssl_conn returns a new SSLConn with the given config.
|
||||
|
Loading…
x
Reference in New Issue
Block a user