Allow passing in trustAnchors to newTLSClientAsyncStream (#355)
* Allow passing in trustAnchors to newTLSClientAsyncStream * Store X509TrustAnchors in a memory-secure, but memory-friendly way * Remove forgotten import * Change some waitFor to await in tests; add comment about trustAnchors reuse * Remove use of result in newTrustAnchorStore
This commit is contained in:
parent
0f70a6b8ee
commit
1b69b5e808
|
@ -59,6 +59,9 @@ type
|
||||||
|
|
||||||
PEMContext = ref object
|
PEMContext = ref object
|
||||||
data: seq[byte]
|
data: seq[byte]
|
||||||
|
|
||||||
|
TrustAnchorStore* = ref object
|
||||||
|
anchors: seq[X509TrustAnchor]
|
||||||
|
|
||||||
TLSStreamWriter* = ref object of AsyncStreamWriter
|
TLSStreamWriter* = ref object of AsyncStreamWriter
|
||||||
case kind: TLSStreamKind
|
case kind: TLSStreamKind
|
||||||
|
@ -89,6 +92,7 @@ type
|
||||||
reader*: TLSStreamReader
|
reader*: TLSStreamReader
|
||||||
writer*: TLSStreamWriter
|
writer*: TLSStreamWriter
|
||||||
mainLoop*: Future[void]
|
mainLoop*: Future[void]
|
||||||
|
trustAnchors: TrustAnchorStore
|
||||||
|
|
||||||
SomeTLSStreamType* = TLSStreamReader|TLSStreamWriter|TLSAsyncStream
|
SomeTLSStreamType* = TLSStreamReader|TLSStreamWriter|TLSAsyncStream
|
||||||
|
|
||||||
|
@ -135,6 +139,13 @@ proc newTLSStreamProtocolError[T](message: T): ref TLSStreamProtocolError =
|
||||||
proc raiseTLSStreamProtocolError[T](message: T) {.noreturn, noinline.} =
|
proc raiseTLSStreamProtocolError[T](message: T) {.noreturn, noinline.} =
|
||||||
raise newTLSStreamProtocolImpl(message)
|
raise newTLSStreamProtocolImpl(message)
|
||||||
|
|
||||||
|
proc new*(T: typedesc[TrustAnchorStore], anchors: openArray[X509TrustAnchor]): TrustAnchorStore =
|
||||||
|
var res: seq[X509TrustAnchor]
|
||||||
|
for anchor in anchors:
|
||||||
|
res.add(anchor)
|
||||||
|
doAssert(unsafeAddr(anchor) != unsafeAddr(res[^1]), "Anchors should be copied")
|
||||||
|
return TrustAnchorStore(anchors: res)
|
||||||
|
|
||||||
proc tlsWriteRec(engine: ptr SslEngineContext,
|
proc tlsWriteRec(engine: ptr SslEngineContext,
|
||||||
writer: TLSStreamWriter): Future[TLSResult] {.async.} =
|
writer: TLSStreamWriter): Future[TLSResult] {.async.} =
|
||||||
try:
|
try:
|
||||||
|
@ -448,7 +459,9 @@ proc newTLSClientAsyncStream*(rsource: AsyncStreamReader,
|
||||||
bufferSize = SSL_BUFSIZE_BIDI,
|
bufferSize = SSL_BUFSIZE_BIDI,
|
||||||
minVersion = TLSVersion.TLS12,
|
minVersion = TLSVersion.TLS12,
|
||||||
maxVersion = TLSVersion.TLS12,
|
maxVersion = TLSVersion.TLS12,
|
||||||
flags: set[TLSFlags] = {}): TLSAsyncStream =
|
flags: set[TLSFlags] = {},
|
||||||
|
trustAnchors: TrustAnchorStore | openArray[X509TrustAnchor] = MozillaTrustAnchors
|
||||||
|
): TLSAsyncStream =
|
||||||
## Create new TLS asynchronous stream for outbound (client) connections
|
## Create new TLS asynchronous stream for outbound (client) connections
|
||||||
## using reading stream ``rsource`` and writing stream ``wsource``.
|
## using reading stream ``rsource`` and writing stream ``wsource``.
|
||||||
##
|
##
|
||||||
|
@ -465,6 +478,15 @@ proc newTLSClientAsyncStream*(rsource: AsyncStreamReader,
|
||||||
## ``minVersion`` of bigger then ``maxVersion`` you will get an error.
|
## ``minVersion`` of bigger then ``maxVersion`` you will get an error.
|
||||||
##
|
##
|
||||||
## ``flags`` - custom TLS connection flags.
|
## ``flags`` - custom TLS connection flags.
|
||||||
|
##
|
||||||
|
## ``trustAnchors`` - use this if you want to use certificate trust
|
||||||
|
## anchors other than the default Mozilla trust anchors. If you pass
|
||||||
|
## a ``TrustAnchorStore`` you should reuse the same instance for
|
||||||
|
## every call to avoid making a copy of the trust anchors per call.
|
||||||
|
when trustAnchors is TrustAnchorStore:
|
||||||
|
doAssert(len(trustAnchors.anchors) > 0, "Empty trust anchor list is invalid")
|
||||||
|
else:
|
||||||
|
doAssert(len(trustAnchors) > 0, "Empty trust anchor list is invalid")
|
||||||
var res = TLSAsyncStream()
|
var res = TLSAsyncStream()
|
||||||
var reader = TLSStreamReader(
|
var reader = TLSStreamReader(
|
||||||
kind: TLSStreamKind.Client,
|
kind: TLSStreamKind.Client,
|
||||||
|
@ -484,9 +506,15 @@ proc newTLSClientAsyncStream*(rsource: AsyncStreamReader,
|
||||||
x509NoanchorInit(res.xwc, addr res.x509.vtable)
|
x509NoanchorInit(res.xwc, addr res.x509.vtable)
|
||||||
sslEngineSetX509(res.ccontext.eng, addr res.xwc.vtable)
|
sslEngineSetX509(res.ccontext.eng, addr res.xwc.vtable)
|
||||||
else:
|
else:
|
||||||
sslClientInitFull(res.ccontext, addr res.x509,
|
when trustAnchors is TrustAnchorStore:
|
||||||
unsafeAddr MozillaTrustAnchors[0],
|
res.trustAnchors = trustAnchors
|
||||||
uint(len(MozillaTrustAnchors)))
|
sslClientInitFull(res.ccontext, addr res.x509,
|
||||||
|
unsafeAddr trustAnchors.anchors[0],
|
||||||
|
uint(len(trustAnchors.anchors)))
|
||||||
|
else:
|
||||||
|
sslClientInitFull(res.ccontext, addr res.x509,
|
||||||
|
unsafeAddr trustAnchors[0],
|
||||||
|
uint(len(trustAnchors)))
|
||||||
|
|
||||||
let size = max(SSL_BUFSIZE_BIDI, bufferSize)
|
let size = max(SSL_BUFSIZE_BIDI, bufferSize)
|
||||||
res.sbuffer = newSeq[byte](size)
|
res.sbuffer = newSeq[byte](size)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
import unittest2
|
import unittest2
|
||||||
|
import bearssl/[x509]
|
||||||
import ../chronos
|
import ../chronos
|
||||||
import ../chronos/streams/[tlsstream, chunkstream, boundstream]
|
import ../chronos/streams/[tlsstream, chunkstream, boundstream]
|
||||||
|
|
||||||
|
@ -72,6 +73,70 @@ N8r5CwGcIX/XPC3lKazzbZ8baA==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# This is the X509TrustAnchor for the SelfSignedRsaCert above
|
||||||
|
# Generate by doing the following:
|
||||||
|
# 1. Compile `brssl` from BearSSL
|
||||||
|
# 2. Run `brssl ta filewithSelfSignedRsaCert.pem`
|
||||||
|
# 3. Paste the output in the emit block below
|
||||||
|
# 4. Rename `TAs` to `SelfSignedTAs`
|
||||||
|
{.emit: """
|
||||||
|
static const unsigned char TA0_DN[] = {
|
||||||
|
0x30, 0x5F, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||||
|
0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
|
||||||
|
0x0C, 0x0A, 0x53, 0x6F, 0x6D, 0x65, 0x2D, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||||
|
0x31, 0x21, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x18, 0x49,
|
||||||
|
0x6E, 0x74, 0x65, 0x72, 0x6E, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
|
||||||
|
0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4C, 0x74, 0x64, 0x31,
|
||||||
|
0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x0F, 0x31, 0x32,
|
||||||
|
0x37, 0x2E, 0x30, 0x2E, 0x30, 0x2E, 0x31, 0x3A, 0x34, 0x33, 0x38, 0x30,
|
||||||
|
0x38
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char TA0_RSA_N[] = {
|
||||||
|
0xA7, 0xEE, 0xD5, 0xC6, 0x2C, 0xA3, 0x08, 0x33, 0x33, 0x86, 0xB5, 0x5C,
|
||||||
|
0xD4, 0x8B, 0x16, 0xB1, 0xD7, 0xF7, 0xED, 0x95, 0x22, 0xDC, 0xA4, 0x40,
|
||||||
|
0x24, 0x64, 0xC3, 0x91, 0xBA, 0x20, 0x82, 0x9D, 0x88, 0xED, 0x20, 0x98,
|
||||||
|
0x46, 0x65, 0xDC, 0xD1, 0x15, 0x90, 0xBC, 0x7C, 0x19, 0x5F, 0x00, 0x96,
|
||||||
|
0x69, 0x2C, 0x80, 0x0E, 0x7D, 0x7D, 0x8B, 0xD9, 0xFD, 0x49, 0x66, 0xEC,
|
||||||
|
0x29, 0xC0, 0x39, 0x0E, 0x22, 0xF3, 0x6A, 0x28, 0xC0, 0x6B, 0x97, 0x93,
|
||||||
|
0x2F, 0x92, 0x5E, 0x5A, 0xCC, 0xF4, 0xF4, 0xAE, 0xD9, 0xE3, 0xBB, 0x0A,
|
||||||
|
0xDC, 0xA8, 0xDE, 0x4D, 0x16, 0xD6, 0xE6, 0x64, 0xF2, 0x85, 0x62, 0xF6,
|
||||||
|
0xE3, 0x7B, 0x1D, 0x9A, 0x5C, 0x6A, 0xA3, 0x97, 0x93, 0x16, 0x9D, 0x02,
|
||||||
|
0x2C, 0xFD, 0x90, 0x3E, 0xF8, 0x35, 0x44, 0x5E, 0x66, 0x8D, 0xF6, 0x80,
|
||||||
|
0xF1, 0x71, 0x9B, 0x2F, 0x44, 0xC0, 0xCA, 0x7E, 0xB1, 0x90, 0x7F, 0xD8,
|
||||||
|
0x8B, 0x7A, 0x85, 0x4B, 0xE3, 0xB1, 0xB1, 0xF4, 0xAA, 0x6A, 0x36, 0xA0,
|
||||||
|
0xFF, 0x24, 0xB2, 0x27, 0xE0, 0xBA, 0x62, 0x7A, 0xE9, 0x95, 0xC9, 0x88,
|
||||||
|
0x9D, 0x9B, 0xAB, 0xA4, 0x4C, 0xEA, 0x87, 0x46, 0xFA, 0xD6, 0x9B, 0x7E,
|
||||||
|
0xB2, 0xE9, 0x5B, 0xCA, 0x5B, 0x84, 0xC4, 0xF7, 0xB4, 0xC7, 0x69, 0xC5,
|
||||||
|
0x0B, 0x9A, 0x47, 0x9A, 0x86, 0xD4, 0xDF, 0xF3, 0x30, 0xC9, 0x6D, 0xB8,
|
||||||
|
0x78, 0x10, 0xEF, 0xA0, 0x89, 0xF8, 0x30, 0x80, 0x9D, 0x96, 0x05, 0x44,
|
||||||
|
0xB4, 0xFB, 0x98, 0x4C, 0x71, 0x6B, 0xBC, 0xD7, 0x5D, 0x66, 0x5E, 0x66,
|
||||||
|
0xA7, 0x94, 0xE5, 0x65, 0x72, 0x85, 0xBC, 0x7C, 0x7F, 0x11, 0x98, 0xF8,
|
||||||
|
0xCB, 0xD5, 0xE2, 0xB5, 0x67, 0x78, 0xF7, 0x49, 0x51, 0xC4, 0x7F, 0xBA,
|
||||||
|
0x16, 0x66, 0xD2, 0x15, 0x5B, 0x98, 0x06, 0x03, 0x48, 0xD0, 0x9D, 0xF0,
|
||||||
|
0x38, 0x2B, 0x9D, 0x51
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char TA0_RSA_E[] = {
|
||||||
|
0x01, 0x00, 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
static const br_x509_trust_anchor SelfSignedTAs[1] = {
|
||||||
|
{
|
||||||
|
{ (unsigned char *)TA0_DN, sizeof TA0_DN },
|
||||||
|
BR_X509_TA_CA,
|
||||||
|
{
|
||||||
|
BR_KEYTYPE_RSA,
|
||||||
|
{ .rsa = {
|
||||||
|
(unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N,
|
||||||
|
(unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E,
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
""".}
|
||||||
|
var SelfSignedTrustAnchors {.importc: "SelfSignedTAs", nodecl.}: array[1, X509TrustAnchor]
|
||||||
|
|
||||||
proc createBigMessage(message: string, size: int): seq[byte] =
|
proc createBigMessage(message: string, size: int): seq[byte] =
|
||||||
var res = newSeq[byte](size)
|
var res = newSeq[byte](size)
|
||||||
for i in 0 ..< len(res):
|
for i in 0 ..< len(res):
|
||||||
|
@ -914,6 +979,49 @@ suite "TLSStream test suite":
|
||||||
let res = waitFor(checkSSLServer(initTAddress("127.0.0.1:43808"),
|
let res = waitFor(checkSSLServer(initTAddress("127.0.0.1:43808"),
|
||||||
SelfSignedRsaKey, SelfSignedRsaCert))
|
SelfSignedRsaKey, SelfSignedRsaCert))
|
||||||
check res == true
|
check res == true
|
||||||
|
|
||||||
|
test "Custom TrustAnchors test":
|
||||||
|
proc checkTrustAnchors(testMessage: string): Future[string] {.async.} =
|
||||||
|
var key = TLSPrivateKey.init(SelfSignedRsaKey)
|
||||||
|
var cert = TLSCertificate.init(SelfSignedRsaCert)
|
||||||
|
let trustAnchors = TrustAnchorStore.new(SelfSignedTrustAnchors)
|
||||||
|
let address = initTAddress("127.0.0.1:43808")
|
||||||
|
|
||||||
|
proc serveClient(server: StreamServer,
|
||||||
|
transp: StreamTransport) {.async.} =
|
||||||
|
var reader = newAsyncStreamReader(transp)
|
||||||
|
var writer = newAsyncStreamWriter(transp)
|
||||||
|
var sstream = newTLSServerAsyncStream(reader, writer, key, cert)
|
||||||
|
await handshake(sstream)
|
||||||
|
await sstream.writer.write(testMessage & "\r\n")
|
||||||
|
await sstream.writer.finish()
|
||||||
|
await sstream.writer.closeWait()
|
||||||
|
await sstream.reader.closeWait()
|
||||||
|
await reader.closeWait()
|
||||||
|
await writer.closeWait()
|
||||||
|
await transp.closeWait()
|
||||||
|
server.stop()
|
||||||
|
server.close()
|
||||||
|
|
||||||
|
var server = createStreamServer(address, serveClient, {ReuseAddr})
|
||||||
|
server.start()
|
||||||
|
var conn = await connect(address)
|
||||||
|
var creader = newAsyncStreamReader(conn)
|
||||||
|
var cwriter = newAsyncStreamWriter(conn)
|
||||||
|
let flags = {NoVerifyServerName}
|
||||||
|
var cstream = newTLSClientAsyncStream(creader, cwriter, "", flags = flags,
|
||||||
|
trustAnchors = trustAnchors)
|
||||||
|
let res = await cstream.reader.read()
|
||||||
|
await cstream.reader.closeWait()
|
||||||
|
await cstream.writer.closeWait()
|
||||||
|
await creader.closeWait()
|
||||||
|
await cwriter.closeWait()
|
||||||
|
await conn.closeWait()
|
||||||
|
await server.join()
|
||||||
|
return cast[string](res)
|
||||||
|
let res = waitFor checkTrustAnchors("Some message")
|
||||||
|
check res == "Some message\r\n"
|
||||||
|
|
||||||
test "TLSStream leaks test":
|
test "TLSStream leaks test":
|
||||||
check:
|
check:
|
||||||
getTracker("async.stream.reader").isLeaked() == false
|
getTracker("async.stream.reader").isLeaked() == false
|
||||||
|
|
Loading…
Reference in New Issue