From 3f13c06a9fd0388c542452327b22418bb10de18b Mon Sep 17 00:00:00 2001 From: Christian Ulrich Date: Mon, 9 Nov 2020 15:04:35 +0100 Subject: [PATCH] introduce Connection type to store the peer's certchain; move openssl wrappers to dedicated module; add certificate utils --- certificate.nim | 29 +++++++++++++ openssl_additional.nim | 57 ++++++++++++++++++++++++ quicp2p.nim | 99 +++++++++++++++++++----------------------- 3 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 certificate.nim create mode 100644 openssl_additional.nim diff --git a/certificate.nim b/certificate.nim new file mode 100644 index 0000000..28273bc --- /dev/null +++ b/certificate.nim @@ -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) + diff --git a/openssl_additional.nim b/openssl_additional.nim new file mode 100644 index 0000000..73f97b1 --- /dev/null +++ b/openssl_additional.nim @@ -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.} diff --git a/quicp2p.nim b/quicp2p.nim index 4976ba7..fc5d30a 100644 --- a/quicp2p.nim +++ b/quicp2p.nim @@ -2,8 +2,12 @@ import asyncdispatch import asyncnet +import certificate import net import os +import openssl_additional +import picotls/picotls +import picotls/openssl as ptls_openssl import quicly/quicly import quicly/cid import quicly/constants @@ -11,9 +15,8 @@ import quicly/defaults import quicly/recvstate import quicly/sendstate import quicly/streambuf -import picotls/picotls -import picotls/openssl as ptls_openssl import strformat +import strutils from nativesockets import SockAddr, Sockaddr_storage, SockLen, getHostByName from posix import IOVec @@ -33,12 +36,14 @@ from openssl import const serverCertChainPath = "./certs/server-certchain.pem" const serverKeyPath = "./certs/server-cert.key" -const clientCertChainPath = "./certs/server-certchain.pem" -const clientKeyPath = "./certs/server-cert.key" - -const X509_V_FLAG_CHECK_SS_SIGNATURE = 0x00004000 +const clientCertChainPath = "./certs/client-certchain.pem" +const clientKeyPath = "./certs/client-cert.key" type + Connection = ref object + conn: ptr quicly_conn_t + certs: seq[Certificate] + QuicP2PContext = ref object sock: AsyncSocket streamOpen: quicly_stream_open_t @@ -47,43 +52,7 @@ type verifyCertsCb: ptls_verify_certificate_t tlsCtx: ptls_context_t quiclyCtx: quicly_context_t - connections: seq[ptr quicly_conn_t] - - 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.} + connections: seq[Connection] proc getRelativeTimeout(ctx: QuicP2PContext): int32 = ## 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 now = ctx.quiclyCtx.now.cb(ctx.quiclyCtx.now) for c in ctx.connections: - let connTimeout = quicly_get_first_timeout(c) + let connTimeout = quicly_get_first_timeout(c.conn) if connTimeout < nextTimeout: nextTimeout = connTimeout 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) var msg = newString(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: discard quicly_streambuf_egress_write(stream, input.base, input.len) 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 msg = newString(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: discard quicly_close(stream.conn, 0, "") 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) result = opensslVerifier.super.cb(addr opensslVerifier.super, tls, 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) X509_STORE_free(store) X509_free(caCert) @@ -228,6 +207,19 @@ proc initContext(sock: AsyncSocket, certChainPath: string, keyPath: string, EVP_PKEY_free(privateKey) 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) = if ctx.connections.len == 0: return @@ -237,7 +229,7 @@ proc sendPackets(ctx: QuicP2PContext) = var dgrams: array[10, IOVec] var dgramCount = dgrams.len().csize_t 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 dgramsBuf[0], dgramsBuf.len().csize_t) case sendResult: @@ -249,9 +241,8 @@ proc sendPackets(ctx: QuicP2PContext) = asyncCheck sendTo(ctx.sock.getFd().AsyncFD, dgrams[j].iov_base, dgrams[j].iov_len.int, addr dstAddr.sa, sockLen) of QUICLY_ERROR_FREE_CONNECTION: - let c = ctx.connections[i] - ctx.connections.del(i) - quicly_free(c) + echo "deleting connection" + ctx.delConnection(i) else: raise newException(ValueError, &"quicly_send returned {sendResult}") @@ -267,15 +258,15 @@ proc handleMsg(ctx: QuicP2PContext, msg: string, peerAddr: ptr SockAddr, return var conn: ptr quicly_conn_t = nil for c in ctx.connections: - if quicly_is_destination(c, nil, peerAddr, addr decoded) != 0: - conn = c + if quicly_is_destination(c.conn, nil, peerAddr, addr decoded) != 0: + conn = c.conn break if conn != nil: discard quicly_receive(conn, nil, peerAddr, addr decoded) elif isServer: discard quicly_accept(addr conn, addr ctx.quiclyCtx, nil, peerAddr, addr decoded, nil, addr ctx.nextCid, nil) - ctx.connections.add(conn) + ctx.addConnection(conn) proc receive(ctx: QuicP2PContext, isServer: bool) {.async.} = while true: @@ -330,9 +321,9 @@ proc main() = if connectResult != 0: echo "quicly_connect failed: ", connectResult quit(3) - ctx.connections.add(conn) var stream: ptr quicly_stream_t discard quicly_open_stream(conn, addr stream, 0) + ctx.addConnection(conn) asyncCheck receive(ctx, false) else: