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:
Matt Haggard 2023-02-21 13:38:53 -05:00 committed by GitHub
parent 0f70a6b8ee
commit 1b69b5e808
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 4 deletions

View File

@ -60,6 +60,9 @@ type
PEMContext = ref object
data: seq[byte]
TrustAnchorStore* = ref object
anchors: seq[X509TrustAnchor]
TLSStreamWriter* = ref object of AsyncStreamWriter
case kind: TLSStreamKind
of TLSStreamKind.Client:
@ -89,6 +92,7 @@ type
reader*: TLSStreamReader
writer*: TLSStreamWriter
mainLoop*: Future[void]
trustAnchors: TrustAnchorStore
SomeTLSStreamType* = TLSStreamReader|TLSStreamWriter|TLSAsyncStream
@ -135,6 +139,13 @@ proc newTLSStreamProtocolError[T](message: T): ref TLSStreamProtocolError =
proc raiseTLSStreamProtocolError[T](message: T) {.noreturn, noinline.} =
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,
writer: TLSStreamWriter): Future[TLSResult] {.async.} =
try:
@ -448,7 +459,9 @@ proc newTLSClientAsyncStream*(rsource: AsyncStreamReader,
bufferSize = SSL_BUFSIZE_BIDI,
minVersion = 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
## 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.
##
## ``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 reader = TLSStreamReader(
kind: TLSStreamKind.Client,
@ -484,9 +506,15 @@ proc newTLSClientAsyncStream*(rsource: AsyncStreamReader,
x509NoanchorInit(res.xwc, addr res.x509.vtable)
sslEngineSetX509(res.ccontext.eng, addr res.xwc.vtable)
else:
when trustAnchors is TrustAnchorStore:
res.trustAnchors = trustAnchors
sslClientInitFull(res.ccontext, addr res.x509,
unsafeAddr MozillaTrustAnchors[0],
uint(len(MozillaTrustAnchors)))
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)
res.sbuffer = newSeq[byte](size)

View File

@ -6,6 +6,7 @@
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import unittest2
import bearssl/[x509]
import ../chronos
import ../chronos/streams/[tlsstream, chunkstream, boundstream]
@ -72,6 +73,70 @@ N8r5CwGcIX/XPC3lKazzbZ8baA==
-----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] =
var res = newSeq[byte](size)
for i in 0 ..< len(res):
@ -914,6 +979,49 @@ suite "TLSStream test suite":
let res = waitFor(checkSSLServer(initTAddress("127.0.0.1:43808"),
SelfSignedRsaKey, SelfSignedRsaCert))
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":
check:
getTracker("async.stream.reader").isLeaked() == false