mirror of
https://github.com/vlang/v.git
synced 2025-08-04 02:07:28 -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_init(&C.mbedtls_ssl_config)
|
||||||
fn C.mbedtls_ssl_config_defaults(&C.mbedtls_ssl_config, int, int, int) int
|
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_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_init(&C.mbedtls_pk_context)
|
||||||
fn C.mbedtls_pk_free(&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
|
cacert C.mbedtls_x509_crt
|
||||||
client_cert C.mbedtls_x509_crt
|
client_cert C.mbedtls_x509_crt
|
||||||
client_key C.mbedtls_pk_context
|
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
|
// SSLConn is the current connection
|
||||||
pub struct SSLConn {
|
pub struct SSLConn {
|
||||||
pub:
|
pub:
|
||||||
@ -77,9 +149,7 @@ pub fn (mut l SSLListener) shutdown() ! {
|
|||||||
eprintln(@METHOD)
|
eprintln(@METHOD)
|
||||||
}
|
}
|
||||||
if unsafe { l.certs != nil } {
|
if unsafe { l.certs != nil } {
|
||||||
C.mbedtls_x509_crt_free(&l.certs.cacert)
|
l.certs.cleanup()
|
||||||
C.mbedtls_x509_crt_free(&l.certs.client_cert)
|
|
||||||
C.mbedtls_pk_free(&l.certs.client_key)
|
|
||||||
}
|
}
|
||||||
C.mbedtls_ssl_free(&l.ssl)
|
C.mbedtls_ssl_free(&l.ssl)
|
||||||
C.mbedtls_ssl_config_free(&l.conf)
|
C.mbedtls_ssl_config_free(&l.conf)
|
||||||
@ -115,28 +185,12 @@ fn (mut l SSLListener) init() ! {
|
|||||||
mut ret := 0
|
mut ret := 0
|
||||||
|
|
||||||
if l.config.in_memory_verification {
|
if l.config.in_memory_verification {
|
||||||
if l.config.verify != '' {
|
l.certs = new_sslcerts_in_memory(l.config.verify, l.config.cert, l.config.cert_key) or {
|
||||||
ret = C.mbedtls_x509_crt_parse(&l.certs.cacert, l.config.verify.str,
|
return error('Cert failure')
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if l.config.verify != '' {
|
l.certs = new_sslcerts_from_file(l.config.verify, l.config.cert, l.config.cert_key) or {
|
||||||
ret = C.mbedtls_x509_crt_parse_file(&l.certs.cacert, &char(l.config.verify.str))
|
return error('Cert failure')
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +228,25 @@ fn (mut l SSLListener) init() ! {
|
|||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
return error_with_code("can't setup ssl", ret)
|
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
|
// 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
|
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
|
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.
|
// new_ssl_conn returns a new SSLConn with the given config.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user