introduce Connection type to store the peer's certchain; move openssl wrappers to dedicated module; add certificate utils

This commit is contained in:
Christian Ulrich 2020-11-09 15:04:35 +01:00
parent 8441c02a57
commit 3f13c06a9f
No known key found for this signature in database
GPG Key ID: 8241BE099775A097
3 changed files with 131 additions and 54 deletions

29
certificate.nim Normal file
View File

@ -0,0 +1,29 @@
from posix import Tm, mktime
import
openssl,
openssl_additional,
times
type
Certificate* = string
proc getPublicKey*(cert: Certificate): string =
let x509 = d2i_X509(cert)
let pubKey = X509_get0_pubkey_bitstr(x509)
let pubKeyLen = ASN1_STRING_length(pubKey)
result = newString(pubKeyLen)
copyMem(addr result[0], ASN1_STRING_get0_data(pubKey), pubKeyLen)
X509_free(x509)
proc getValidityPeriod*(cert: Certificate): tuple[notBefore: Time, notAfter: Time] =
let x509 = d2i_X509(cert)
let notBeforeAsn1 = X509_get0_notBefore(x509)
let notAfterAsn1 = X509_get0_notAfter(x509)
var notBeforeTm, notAfterTm: Tm
discard ASN1_TIME_to_tm(notBeforeAsn1, addr notBeforeTm)
discard ASN1_TIME_to_tm(notAfterAsn1, addr notAfterTm)
let notBeforeUnix = cast[int64](mktime(notBeforeTm))
let notAfterUnix = cast[int64](mktime(notAfterTm))
result = (fromUnix(notBeforeUnix), fromUnix(notAfterUnix))
X509_free(x509)

57
openssl_additional.nim Normal file
View File

@ -0,0 +1,57 @@
import openssl
from posix import Tm
const
X509_V_FLAG_CHECK_SS_SIGNATURE* = 0x00004000
type
PASN1_STRING* = SslPtr
PASN1_BIT_STRING* = PASN1_STRING
PASN1_TIME* = PASN1_STRING
PASN1_INTEGER* = PASN1_STRING
PX509_STORE_CTX* = SslPtr
PX509_VERIFY_PARAM* = SslPtr
proc X509_free*(a: PX509) {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_get0_pubkey_bitstr*(x: PX509): PASN1_BIT_STRING {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_get0_notBefore*(x: PX509): PASN1_TIME {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_get0_notAfter*(x: PX509): PASN1_TIME {.importc, dynlib: DLLSSLName, cdecl.}
proc ASN1_STRING_length*(x: PASN1_STRING): int {.importc, dynlib: DLLSSLName, cdecl.}
proc ASN1_STRING_get0_data*(x: PASN1_STRING): ptr cuchar {.importc, dynlib: DLLSSLName, cdecl.}
proc ASN1_TIME_to_tm*(s: PASN1_TIME, tm: ptr Tm): int {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_verify_cert*(ctx: PX509_STORE_CTX): int {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_get0_untrusted*(ctx: PX509_STORE_CTX): PSTACK {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_get0_store*(ctx: PX509_STORE_CTX): PX509_STORE {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_get0_param*(ctx: PX509_STORE_CTX): PX509_VERIFY_PARAM {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_VERIFY_PARAM_get_flags*(param: PX509_VERIFY_PARAM): culong {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_VERIFY_PARAM_set_flags*(param: PX509_VERIFY_PARAM,
flags: culong): int {.importc, dynlib: DLLSSLName, cdecl.}
proc PEM_read_PrivateKey*(fp: File, x: ptr EVP_PKEY,
cb: proc(buf: cstring, size: cint, rwflag: cint, u: pointer): cint {.cdecl.},
u: pointer): EVP_PKEY
{.importc, dynlib: DLLSSLName, cdecl.}
proc EVP_PKEY_free*(key: EVP_PKEY) {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_new*(): PX509_STORE_CTX
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_free*(ctx: PX509_STORE_CTX)
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_init*(ctx: PX509_STORE_CTX, store: PX509_STORE, x509: PX509,
chain: PSTACK): cint
{.importc, dynlib: DLLSSLName, cdecl.}

View File

@ -2,8 +2,12 @@
import asyncdispatch import asyncdispatch
import asyncnet import asyncnet
import certificate
import net import net
import os import os
import openssl_additional
import picotls/picotls
import picotls/openssl as ptls_openssl
import quicly/quicly import quicly/quicly
import quicly/cid import quicly/cid
import quicly/constants import quicly/constants
@ -11,9 +15,8 @@ import quicly/defaults
import quicly/recvstate import quicly/recvstate
import quicly/sendstate import quicly/sendstate
import quicly/streambuf import quicly/streambuf
import picotls/picotls
import picotls/openssl as ptls_openssl
import strformat import strformat
import strutils
from nativesockets import SockAddr, Sockaddr_storage, SockLen, getHostByName from nativesockets import SockAddr, Sockaddr_storage, SockLen, getHostByName
from posix import IOVec from posix import IOVec
@ -33,12 +36,14 @@ from openssl import
const serverCertChainPath = "./certs/server-certchain.pem" const serverCertChainPath = "./certs/server-certchain.pem"
const serverKeyPath = "./certs/server-cert.key" const serverKeyPath = "./certs/server-cert.key"
const clientCertChainPath = "./certs/server-certchain.pem" const clientCertChainPath = "./certs/client-certchain.pem"
const clientKeyPath = "./certs/server-cert.key" const clientKeyPath = "./certs/client-cert.key"
const X509_V_FLAG_CHECK_SS_SIGNATURE = 0x00004000
type type
Connection = ref object
conn: ptr quicly_conn_t
certs: seq[Certificate]
QuicP2PContext = ref object QuicP2PContext = ref object
sock: AsyncSocket sock: AsyncSocket
streamOpen: quicly_stream_open_t streamOpen: quicly_stream_open_t
@ -47,43 +52,7 @@ type
verifyCertsCb: ptls_verify_certificate_t verifyCertsCb: ptls_verify_certificate_t
tlsCtx: ptls_context_t tlsCtx: ptls_context_t
quiclyCtx: quicly_context_t quiclyCtx: quicly_context_t
connections: seq[ptr quicly_conn_t] connections: seq[Connection]
PX509_STORE_CTX = SslPtr
PX509_VERIFY_PARAM = SslPtr
proc PEM_read_PrivateKey(fp: File, x: ptr EVP_PKEY,
cb: proc(buf: cstring, size: cint, rwflag: cint, u: pointer): cint {.cdecl.},
u: pointer): EVP_PKEY
{.importc, dynlib: DLLSSLName, cdecl.}
proc EVP_PKEY_free(key: EVP_PKEY) {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_free(a: PX509) {.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_new(): PX509_STORE_CTX
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_free(ctx: PX509_STORE_CTX)
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_init(ctx: PX509_STORE_CTX, store: PX509_STORE, x509: PX509,
chain: PSTACK): cint
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_STORE_CTX_get0_param(ctx: PX509_STORE_CTX): PX509_VERIFY_PARAM
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_VERIFY_PARAM_get_flags(param: PX509_VERIFY_PARAM): culong
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_VERIFY_PARAM_set_flags(param: PX509_VERIFY_PARAM,
flags: culong): int
{.importc, dynlib: DLLSSLName, cdecl.}
proc X509_verify_cert(ctx: PX509_STORE_CTX): int
{.importc, dynlib: DLLSSLName, cdecl.}
proc getRelativeTimeout(ctx: QuicP2PContext): int32 = proc getRelativeTimeout(ctx: QuicP2PContext): int32 =
## Obtain the absolute int64 timeout from quicly and convert it to the ## Obtain the absolute int64 timeout from quicly and convert it to the
@ -92,7 +61,7 @@ proc getRelativeTimeout(ctx: QuicP2PContext): int32 =
var nextTimeout = int64.high var nextTimeout = int64.high
var now = ctx.quiclyCtx.now.cb(ctx.quiclyCtx.now) var now = ctx.quiclyCtx.now.cb(ctx.quiclyCtx.now)
for c in ctx.connections: for c in ctx.connections:
let connTimeout = quicly_get_first_timeout(c) let connTimeout = quicly_get_first_timeout(c.conn)
if connTimeout < nextTimeout: if connTimeout < nextTimeout:
nextTimeout = connTimeout nextTimeout = connTimeout
if now < nextTimeout: if now < nextTimeout:
@ -114,7 +83,8 @@ proc onServerReceive(stream: ptr quicly_stream_t, offset: csize_t, src: pointer,
let input = quicly_streambuf_ingress_get(stream) let input = quicly_streambuf_ingress_get(stream)
var msg = newString(input.len) var msg = newString(input.len)
copyMem(addr msg[0], input.base, input.len) copyMem(addr msg[0], input.base, input.len)
echo &"received message from client: \"{msg}\"" let conn = cast[Connection](quicly_get_data(stream.conn)[])
echo &"client {conn.certs[1].getPublicKey().toHex()} sends \"{msg}\""
if quicly_sendstate_is_open(addr stream.sendstate) != 0 and input.len > 0: if quicly_sendstate_is_open(addr stream.sendstate) != 0 and input.len > 0:
discard quicly_streambuf_egress_write(stream, input.base, input.len) discard quicly_streambuf_egress_write(stream, input.base, input.len)
if quicly_recvstate_transfer_complete(addr stream.recvstate) != 0: if quicly_recvstate_transfer_complete(addr stream.recvstate) != 0:
@ -128,7 +98,8 @@ proc onClientReceive(stream: ptr quicly_stream_t, offset: csize_t,
let input = quicly_streambuf_ingress_get(stream) let input = quicly_streambuf_ingress_get(stream)
let msg = newString(input.len) let msg = newString(input.len)
copyMem(msg.cstring, input.base, input.len) copyMem(msg.cstring, input.base, input.len)
echo &"received message from server: \"{msg}\"" let conn = cast[Connection](quicly_get_data(stream.conn)[])
echo &"server {conn.certs[1].getPublicKey().toHex()} sends \"{msg}\""
if quicly_recvstate_transfer_complete(addr stream.recvstate) != 0: if quicly_recvstate_transfer_complete(addr stream.recvstate) != 0:
discard quicly_close(stream.conn, 0, "") discard quicly_close(stream.conn, 0, "")
quicly_streambuf_ingress_shift(stream, input.len) quicly_streambuf_ingress_shift(stream, input.len)
@ -198,6 +169,14 @@ proc verifyCerts(self: ptr ptls_verify_certificate_t, tls: ptr ptls_t,
discard ptls_openssl_init_verify_certificate(addr opensslVerifier, store) discard ptls_openssl_init_verify_certificate(addr opensslVerifier, store)
result = opensslVerifier.super.cb(addr opensslVerifier.super, tls, result = opensslVerifier.super.cb(addr opensslVerifier.super, tls,
verify_sign, verify_data, certs, num_certs) verify_sign, verify_data, certs, num_certs)
if result == 0:
let quiclyConn = cast[ptr quicly_conn_t](ptls_get_data_ptr(tls)[])
let conn = cast[Connection](quicly_get_data(quiclyConn)[])
for i in 0 .. num_certs - 1:
let iovec = cast[ptr ptls_iovec_t](cast[ByteAddress](certs) + i.int * sizeof(ptls_iovec_t))
var cert = newString(iovec.len)
copyMem(cert.cstring, iovec.base, iovec.len)
conn.certs.add(cert)
ptls_openssl_dispose_verify_certificate(addr opensslVerifier) ptls_openssl_dispose_verify_certificate(addr opensslVerifier)
X509_STORE_free(store) X509_STORE_free(store)
X509_free(caCert) X509_free(caCert)
@ -228,6 +207,19 @@ proc initContext(sock: AsyncSocket, certChainPath: string, keyPath: string,
EVP_PKEY_free(privateKey) EVP_PKEY_free(privateKey)
result.tlsCtx.sign_certificate = addr result.signCertCb.super result.tlsCtx.sign_certificate = addr result.signCertCb.super
proc addConnection(ctx: QuicP2PContext, connPtr: ptr quicly_conn_t) =
assert(not connPtr.isNil)
let data = quicly_get_data(connPtr)
var conn = Connection(conn: connPtr)
data[] = addr conn[]
ctx.connections.add(conn)
proc delConnection(ctx: QuicP2PContext, index: int) =
assert(index >= 0 and index < ctx.connections.len)
let c = ctx.connections[index]
ctx.connections.del(index)
quicly_free(c.conn)
proc sendPackets(ctx: QuicP2PContext) = proc sendPackets(ctx: QuicP2PContext) =
if ctx.connections.len == 0: if ctx.connections.len == 0:
return return
@ -237,7 +229,7 @@ proc sendPackets(ctx: QuicP2PContext) =
var dgrams: array[10, IOVec] var dgrams: array[10, IOVec]
var dgramCount = dgrams.len().csize_t var dgramCount = dgrams.len().csize_t
var dgramsBuf = newString(dgramCount * ctx.quiclyCtx.transport_params.max_udp_payload_size) var dgramsBuf = newString(dgramCount * ctx.quiclyCtx.transport_params.max_udp_payload_size)
let sendResult = quicly_send(conns[i], addr dstAddr, addr srcAddr, let sendResult = quicly_send(conns[i].conn, addr dstAddr, addr srcAddr,
addr dgrams[0], addr dgramCount, addr dgrams[0], addr dgramCount,
addr dgramsBuf[0], dgramsBuf.len().csize_t) addr dgramsBuf[0], dgramsBuf.len().csize_t)
case sendResult: case sendResult:
@ -249,9 +241,8 @@ proc sendPackets(ctx: QuicP2PContext) =
asyncCheck sendTo(ctx.sock.getFd().AsyncFD, dgrams[j].iov_base, asyncCheck sendTo(ctx.sock.getFd().AsyncFD, dgrams[j].iov_base,
dgrams[j].iov_len.int, addr dstAddr.sa, sockLen) dgrams[j].iov_len.int, addr dstAddr.sa, sockLen)
of QUICLY_ERROR_FREE_CONNECTION: of QUICLY_ERROR_FREE_CONNECTION:
let c = ctx.connections[i] echo "deleting connection"
ctx.connections.del(i) ctx.delConnection(i)
quicly_free(c)
else: else:
raise newException(ValueError, &"quicly_send returned {sendResult}") raise newException(ValueError, &"quicly_send returned {sendResult}")
@ -267,15 +258,15 @@ proc handleMsg(ctx: QuicP2PContext, msg: string, peerAddr: ptr SockAddr,
return return
var conn: ptr quicly_conn_t = nil var conn: ptr quicly_conn_t = nil
for c in ctx.connections: for c in ctx.connections:
if quicly_is_destination(c, nil, peerAddr, addr decoded) != 0: if quicly_is_destination(c.conn, nil, peerAddr, addr decoded) != 0:
conn = c conn = c.conn
break break
if conn != nil: if conn != nil:
discard quicly_receive(conn, nil, peerAddr, addr decoded) discard quicly_receive(conn, nil, peerAddr, addr decoded)
elif isServer: elif isServer:
discard quicly_accept(addr conn, addr ctx.quiclyCtx, nil, peerAddr, discard quicly_accept(addr conn, addr ctx.quiclyCtx, nil, peerAddr,
addr decoded, nil, addr ctx.nextCid, nil) addr decoded, nil, addr ctx.nextCid, nil)
ctx.connections.add(conn) ctx.addConnection(conn)
proc receive(ctx: QuicP2PContext, isServer: bool) {.async.} = proc receive(ctx: QuicP2PContext, isServer: bool) {.async.} =
while true: while true:
@ -330,9 +321,9 @@ proc main() =
if connectResult != 0: if connectResult != 0:
echo "quicly_connect failed: ", connectResult echo "quicly_connect failed: ", connectResult
quit(3) quit(3)
ctx.connections.add(conn)
var stream: ptr quicly_stream_t var stream: ptr quicly_stream_t
discard quicly_open_stream(conn, addr stream, 0) discard quicly_open_stream(conn, addr stream, 0)
ctx.addConnection(conn)
asyncCheck receive(ctx, false) asyncCheck receive(ctx, false)
else: else: