nim-chronos/chronos/streams/tlsstream.nim

820 lines
28 KiB
Nim

#
# Chronos Asynchronous TLS Stream
# (c) Copyright 2019-Present
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
## This module implements Transport Layer Security (TLS) stream. This module
## uses sources of BearSSL <https://www.bearssl.org> by Thomas Pornin.
{.push raises: [].}
import
bearssl/[brssl, ec, errors, pem, rsa, ssl, x509],
bearssl/certs/cacert
import ".."/[asyncloop, asyncsync, config, timer]
import asyncstream, ../transports/[stream, common]
export asyncloop, asyncsync, timer, asyncstream
const
TLSSessionCacheBufferSize* = chronosTLSSessionCacheBufferSize
type
TLSStreamKind {.pure.} = enum
Client, Server
TLSVersion* {.pure.} = enum
TLS10 = 0x0301, TLS11 = 0x0302, TLS12 = 0x0303
TLSFlags* {.pure.} = enum
NoVerifyHost, # Client: Skip remote certificate check
NoVerifyServerName, # Client: Skip Server Name Indication (SNI) check
EnforceServerPref, # Server: Enforce server preferences
NoRenegotiation, # Server: Reject renegotiations requests
TolerateNoClientAuth, # Server: Disable strict client authentication
FailOnAlpnMismatch # Server: Fail on application protocol mismatch
TLSKeyType {.pure.} = enum
RSA, EC
TLSResult {.pure.} = enum
Success, Error, Stopped, WriteEof, ReadEof
TLSPrivateKey* = ref object
case kind: TLSKeyType
of RSA:
rsakey: RsaPrivateKey
of EC:
eckey: EcPrivateKey
storage: seq[byte]
TLSCertificate* = ref object
certs: seq[X509Certificate]
storage: seq[byte]
TLSSessionCache* = ref object
storage: seq[byte]
context: SslSessionCacheLru
PEMElement* = object
name*: string
data*: seq[byte]
PEMContext = ref object
data: seq[byte]
TrustAnchorStore* = ref object
anchors: seq[X509TrustAnchor]
TLSStreamWriter* = ref object of AsyncStreamWriter
case kind: TLSStreamKind
of TLSStreamKind.Client:
ccontext: ptr SslClientContext
of TLSStreamKind.Server:
scontext: ptr SslServerContext
stream*: TLSAsyncStream
handshaked*: bool
handshakeFut*: Future[void].Raising([CancelledError, AsyncStreamError])
TLSStreamReader* = ref object of AsyncStreamReader
case kind: TLSStreamKind
of TLSStreamKind.Client:
ccontext: ptr SslClientContext
of TLSStreamKind.Server:
scontext: ptr SslServerContext
stream*: TLSAsyncStream
handshaked*: bool
handshakeFut*: Future[void].Raising([CancelledError, AsyncStreamError])
TLSAsyncStream* = ref object of RootRef
xwc*: X509NoanchorContext
ccontext*: SslClientContext
scontext*: SslServerContext
sbuffer*: seq[byte]
x509*: X509MinimalContext
reader*: TLSStreamReader
writer*: TLSStreamWriter
mainLoop*: Future[void].Raising([])
trustAnchors: TrustAnchorStore
SomeTLSStreamType* = TLSStreamReader|TLSStreamWriter|TLSAsyncStream
SomeTrustAnchorType* = TrustAnchorStore | openArray[X509TrustAnchor]
TLSStreamError* = object of AsyncStreamError
TLSStreamHandshakeError* = object of TLSStreamError
TLSStreamInitError* = object of TLSStreamError
TLSStreamReadError* = object of TLSStreamError
TLSStreamWriteError* = object of TLSStreamError
TLSStreamProtocolError* = object of TLSStreamError
errCode*: int
proc newTLSStreamWriteError(p: ref AsyncStreamError): ref TLSStreamWriteError {.
noinline.} =
var w = newException(TLSStreamWriteError, "Write stream failed")
w.msg = w.msg & ", originated from [" & $p.name & "] " & p.msg
w.parent = p
w
template newTLSStreamProtocolImpl[T](message: T): ref TLSStreamProtocolError =
var msg = ""
var code = 0
when T is string:
msg.add(message)
elif T is cint:
msg.add(sslErrorMsg(message) & " (code: " & $int(message) & ")")
code = int(message)
elif T is int:
msg.add(sslErrorMsg(message) & " (code: " & $message & ")")
code = message
else:
msg.add("Internal Error")
var err = newException(TLSStreamProtocolError, msg)
err.errCode = code
err
template newTLSUnexpectedProtocolError(): ref TLSStreamProtocolError =
newException(TLSStreamProtocolError, "Unexpected internal error")
proc newTLSStreamProtocolError[T](message: T): ref TLSStreamProtocolError =
newTLSStreamProtocolImpl(message)
proc raiseTLSStreamProtocolError[T](message: T) {.
noreturn, noinline, raises: [TLSStreamProtocolError].} =
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")
TrustAnchorStore(anchors: res)
proc tlsWriteRec(engine: ptr SslEngineContext,
writer: TLSStreamWriter): Future[TLSResult] {.
async: (raises: []).} =
try:
var length = 0'u
var buf = sslEngineSendrecBuf(engine[], length)
doAssert(length != 0 and not isNil(buf))
await writer.wsource.write(buf, int(length))
sslEngineSendrecAck(engine[], length)
TLSResult.Success
except AsyncStreamError as exc:
writer.state = AsyncStreamState.Error
writer.error = exc
TLSResult.Error
except CancelledError:
if writer.state == AsyncStreamState.Running:
writer.state = AsyncStreamState.Stopped
TLSResult.Stopped
proc tlsWriteApp(engine: ptr SslEngineContext,
writer: TLSStreamWriter): Future[TLSResult] {.
async: (raises: []).} =
try:
var item = await writer.queue.get()
if item.size > 0:
var length = 0'u
var buf = sslEngineSendappBuf(engine[], length)
if isNil(buf) or (length == 0):
# This situation could happen when connection is closing, no
# application data can be sent, but some can still be received
# (and discarded).
writer.state = AsyncStreamState.Finished
return TLSResult.WriteEof
let toWrite = min(int(length), item.size)
copyOut(buf, item, toWrite)
if int(length) >= item.size:
# BearSSL is ready to accept whole item size.
sslEngineSendappAck(engine[], uint(item.size))
sslEngineFlush(engine[], 0)
item.future.complete()
else:
# BearSSL is not ready to accept whole item, so we will send
# only part of item and adjust offset.
item.offset = item.offset + int(length)
item.size = item.size - int(length)
try:
writer.queue.addFirstNoWait(item)
except AsyncQueueFullError:
raiseAssert "AsyncQueue should not be full at this moment"
sslEngineSendappAck(engine[], length)
TLSResult.Success
else:
sslEngineClose(engine[])
item.future.complete()
TLSResult.Success
except CancelledError:
if writer.state == AsyncStreamState.Running:
writer.state = AsyncStreamState.Stopped
TLSResult.Stopped
proc tlsReadRec(engine: ptr SslEngineContext,
reader: TLSStreamReader): Future[TLSResult] {.
async: (raises: []).} =
try:
var length = 0'u
var buf = sslEngineRecvrecBuf(engine[], length)
let res = await reader.rsource.readOnce(buf, int(length))
sslEngineRecvrecAck(engine[], uint(res))
if res == 0:
sslEngineClose(engine[])
TLSResult.ReadEof
else:
TLSResult.Success
except AsyncStreamError as exc:
reader.state = AsyncStreamState.Error
reader.error = exc
TLSResult.Error
except CancelledError:
if reader.state == AsyncStreamState.Running:
reader.state = AsyncStreamState.Stopped
TLSResult.Stopped
proc tlsReadApp(engine: ptr SslEngineContext,
reader: TLSStreamReader): Future[TLSResult] {.
async: (raises: []).} =
try:
var length = 0'u
var buf = sslEngineRecvappBuf(engine[], length)
await upload(reader.buffer, buf, int(length))
sslEngineRecvappAck(engine[], length)
TLSResult.Success
except CancelledError:
if reader.state == AsyncStreamState.Running:
reader.state = AsyncStreamState.Stopped
TLSResult.Stopped
template readAndReset(fut: untyped) =
if fut.finished():
let res = fut.value()
case res
of TLSResult.Success, TLSResult.WriteEof, TLSResult.Stopped:
fut = nil
continue
of TLSResult.Error:
fut = nil
if loopState == AsyncStreamState.Running:
loopState = AsyncStreamState.Error
break
of TLSResult.ReadEof:
fut = nil
if loopState == AsyncStreamState.Running:
loopState = AsyncStreamState.Finished
break
proc dumpState*(state: cuint): string =
var res = ""
if (state and SSL_CLOSED) == SSL_CLOSED:
if len(res) > 0: res.add(", ")
res.add("SSL_CLOSED")
if (state and SSL_SENDREC) == SSL_SENDREC:
if len(res) > 0: res.add(", ")
res.add("SSL_SENDREC")
if (state and SSL_SENDAPP) == SSL_SENDAPP:
if len(res) > 0: res.add(", ")
res.add("SSL_SENDAPP")
if (state and SSL_RECVREC) == SSL_RECVREC:
if len(res) > 0: res.add(", ")
res.add("SSL_RECVREC")
if (state and SSL_RECVAPP) == SSL_RECVAPP:
if len(res) > 0: res.add(", ")
res.add("SSL_RECVAPP")
"{" & res & "}"
proc tlsLoop*(stream: TLSAsyncStream) {.async: (raises: []).} =
var
sendRecFut, sendAppFut: Future[TLSResult].Raising([])
recvRecFut, recvAppFut: Future[TLSResult].Raising([])
let engine =
case stream.reader.kind
of TLSStreamKind.Server:
addr stream.scontext.eng
of TLSStreamKind.Client:
addr stream.ccontext.eng
var loopState = AsyncStreamState.Running
while true:
var waiting: seq[Future[TLSResult].Raising([])]
var state = sslEngineCurrentState(engine[])
if (state and SSL_CLOSED) == SSL_CLOSED:
if loopState == AsyncStreamState.Running:
loopState = AsyncStreamState.Finished
break
if isNil(sendRecFut):
if (state and SSL_SENDREC) == SSL_SENDREC:
sendRecFut = tlsWriteRec(engine, stream.writer)
else:
sendRecFut.readAndReset()
if isNil(sendAppFut):
if (state and SSL_SENDAPP) == SSL_SENDAPP:
if stream.writer.state == AsyncStreamState.Running:
# Application data can be sent over stream.
if not(stream.writer.handshaked):
stream.reader.handshaked = true
stream.writer.handshaked = true
if not(isNil(stream.writer.handshakeFut)):
stream.writer.handshakeFut.complete()
sendAppFut = tlsWriteApp(engine, stream.writer)
else:
sendAppFut.readAndReset()
if isNil(recvRecFut):
if (state and SSL_RECVREC) == SSL_RECVREC:
recvRecFut = tlsReadRec(engine, stream.reader)
else:
recvRecFut.readAndReset()
if isNil(recvAppFut):
if (state and SSL_RECVAPP) == SSL_RECVAPP:
recvAppFut = tlsReadApp(engine, stream.reader)
else:
recvAppFut.readAndReset()
if not(isNil(sendRecFut)):
waiting.add(sendRecFut)
if not(isNil(sendAppFut)):
waiting.add(sendAppFut)
if not(isNil(recvRecFut)):
waiting.add(recvRecFut)
if not(isNil(recvAppFut)):
waiting.add(recvAppFut)
if len(waiting) > 0:
try:
discard await one(waiting)
except ValueError:
raiseAssert "array should not be empty at this moment"
except CancelledError:
if loopState == AsyncStreamState.Running:
loopState = AsyncStreamState.Stopped
if loopState != AsyncStreamState.Running:
break
# Cancelling and waiting and all the pending operations
var pending: seq[FutureBase]
if not(isNil(sendRecFut)) and not(sendRecFut.finished()):
pending.add(sendRecFut.cancelAndWait())
if not(isNil(sendAppFut)) and not(sendAppFut.finished()):
pending.add(sendAppFut.cancelAndWait())
if not(isNil(recvRecFut)) and not(recvRecFut.finished()):
pending.add(recvRecFut.cancelAndWait())
if not(isNil(recvAppFut)) and not(recvAppFut.finished()):
pending.add(recvAppFut.cancelAndWait())
await noCancel(allFutures(pending))
# Calculating error
let error =
case loopState
of AsyncStreamState.Stopped:
newAsyncStreamUseClosedError()
of AsyncStreamState.Error:
if not(isNil(stream.writer.error)):
stream.writer.error
elif not(isNil(stream.reader.error)):
newTLSStreamWriteError(stream.reader.error)
else:
newTLSUnexpectedProtocolError()
of AsyncStreamState.Finished:
let err = engine[].sslEngineLastError()
if err != 0:
newTLSStreamProtocolError(err)
else:
nil
of AsyncStreamState.Running:
nil
else:
nil
# Syncing state for reader and writer
stream.writer.state = loopState
stream.reader.state = loopState
if loopState == AsyncStreamState.Error:
if isNil(stream.reader.error):
stream.reader.state = AsyncStreamState.Finished
if not(isNil(error)):
# Completing all pending writes
while(not(stream.writer.queue.empty())):
let item =
try:
stream.writer.queue.popFirstNoWait()
except AsyncQueueEmptyError:
raiseAssert "AsyncQueue should not be empty at this moment"
if not(item.future.finished()):
item.future.fail(error)
# Completing handshake
if not(stream.writer.handshaked):
if not(isNil(stream.writer.handshakeFut)):
if not(stream.writer.handshakeFut.finished()):
stream.writer.handshakeFut.fail(error)
else:
if not(stream.writer.handshaked):
if not(isNil(stream.writer.handshakeFut)):
if not(stream.writer.handshakeFut.finished()):
stream.writer.handshakeFut.fail(
newTLSStreamProtocolError(
"Connection to the remote peer has been lost")
)
# Completing readers
stream.reader.buffer.forget()
proc tlsWriteLoop(stream: AsyncStreamWriter) {.async: (raises: []).} =
var wstream = TLSStreamWriter(stream)
wstream.state = AsyncStreamState.Running
await noCancel(sleepAsync(0.milliseconds))
if isNil(wstream.stream.mainLoop):
wstream.stream.mainLoop = tlsLoop(wstream.stream)
await wstream.stream.mainLoop
proc tlsReadLoop(stream: AsyncStreamReader) {.async: (raises: []).} =
var rstream = TLSStreamReader(stream)
rstream.state = AsyncStreamState.Running
await noCancel(sleepAsync(0.milliseconds))
if isNil(rstream.stream.mainLoop):
rstream.stream.mainLoop = tlsLoop(rstream.stream)
await rstream.stream.mainLoop
proc getSignerAlgo(xc: X509Certificate): int =
## Get certificate's signing algorithm.
var dc: X509DecoderContext
x509DecoderInit(dc, nil, nil)
x509DecoderPush(dc, xc.data, xc.dataLen)
let err = x509DecoderLastError(dc)
if err != 0:
-1
else:
int(x509DecoderGetSignerKeyType(dc))
proc newTLSClientAsyncStream*(
rsource: AsyncStreamReader,
wsource: AsyncStreamWriter,
serverName: string,
bufferSize = SSL_BUFSIZE_BIDI,
minVersion = TLSVersion.TLS12,
maxVersion = TLSVersion.TLS12,
flags: set[TLSFlags] = {},
trustAnchors: SomeTrustAnchorType = MozillaTrustAnchors
): TLSAsyncStream {.raises: [TLSStreamInitError].} =
## Create new TLS asynchronous stream for outbound (client) connections
## using reading stream ``rsource`` and writing stream ``wsource``.
##
## You can specify remote server name using ``serverName``, if while
## handshake server reports different name you will get an error. If
## ``serverName`` is empty string, remote server name checking will be
## disabled.
##
## ``bufferSize`` - is SSL/TLS buffer which is used for encoding/decoding
## incoming data.
##
## ``minVersion`` and ``maxVersion`` are TLS versions which will be used
## for handshake with remote server. If server's version will be lower then
## ``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,
stream: res,
ccontext: addr res.ccontext
)
var writer = TLSStreamWriter(
kind: TLSStreamKind.Client,
stream: res,
ccontext: addr res.ccontext
)
res.reader = reader
res.writer = writer
if TLSFlags.NoVerifyHost in flags:
sslClientInitFull(res.ccontext, addr res.x509, nil, 0)
x509NoanchorInit(res.xwc,
X509ClassPointerConst(addr res.x509.vtable))
sslEngineSetX509(res.ccontext.eng,
X509ClassPointerConst(addr res.xwc.vtable))
else:
when trustAnchors is TrustAnchorStore:
res.trustAnchors = trustAnchors
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)
res.sbuffer = newSeq[byte](size)
sslEngineSetBuffer(res.ccontext.eng, addr res.sbuffer[0],
uint(len(res.sbuffer)), 1)
sslEngineSetVersions(res.ccontext.eng, uint16(minVersion),
uint16(maxVersion))
if TLSFlags.NoVerifyServerName in flags:
let err = sslClientReset(res.ccontext, nil, 0)
if err == 0:
raise newException(TLSStreamInitError, "Could not initialize TLS layer")
else:
if len(serverName) == 0:
raise newException(TLSStreamInitError,
"serverName must not be empty string")
let err = sslClientReset(res.ccontext, serverName, 0)
if err == 0:
raise newException(TLSStreamInitError, "Could not initialize TLS layer")
init(AsyncStreamWriter(res.writer), wsource, tlsWriteLoop,
bufferSize)
init(AsyncStreamReader(res.reader), rsource, tlsReadLoop,
bufferSize)
res
proc newTLSServerAsyncStream*(rsource: AsyncStreamReader,
wsource: AsyncStreamWriter,
privateKey: TLSPrivateKey,
certificate: TLSCertificate,
bufferSize = SSL_BUFSIZE_BIDI,
minVersion = TLSVersion.TLS11,
maxVersion = TLSVersion.TLS12,
cache: TLSSessionCache = nil,
flags: set[TLSFlags] = {}): TLSAsyncStream {.
raises: [TLSStreamInitError, TLSStreamProtocolError].} =
## Create new TLS asynchronous stream for inbound (server) connections
## using reading stream ``rsource`` and writing stream ``wsource``.
##
## You need to specify local private key ``privateKey`` and certificate
## ``certificate``.
##
## ``bufferSize`` - is SSL/TLS buffer which is used for encoding/decoding
## incoming data.
##
## ``minVersion`` and ``maxVersion`` are TLS versions which will be used
## for handshake with remote server. If server's version will be lower then
## ``minVersion`` of bigger then ``maxVersion`` you will get an error.
##
## ``flags`` - custom TLS connection flags.
if isNil(privateKey) or privateKey.kind notin {TLSKeyType.RSA, TLSKeyType.EC}:
raiseTLSStreamProtocolError("Incorrect private key")
if isNil(certificate) or len(certificate.certs) == 0:
raiseTLSStreamProtocolError("Incorrect certificate")
var res = TLSAsyncStream()
var reader = TLSStreamReader(
kind: TLSStreamKind.Server,
stream: res,
scontext: addr res.scontext
)
var writer = TLSStreamWriter(
kind: TLSStreamKind.Server,
stream: res,
scontext: addr res.scontext
)
res.reader = reader
res.writer = writer
if privateKey.kind == TLSKeyType.EC:
let algo = getSignerAlgo(certificate.certs[0])
if algo == -1:
raiseTLSStreamProtocolError("Could not decode certificate")
sslServerInitFullEc(res.scontext, addr certificate.certs[0],
uint(len(certificate.certs)), cuint(algo),
addr privateKey.eckey)
elif privateKey.kind == TLSKeyType.RSA:
sslServerInitFullRsa(res.scontext, addr certificate.certs[0],
uint(len(certificate.certs)), addr privateKey.rsakey)
let size = max(SSL_BUFSIZE_BIDI, bufferSize)
res.sbuffer = newSeq[byte](size)
sslEngineSetBuffer(res.scontext.eng, addr res.sbuffer[0],
uint(len(res.sbuffer)), 1)
sslEngineSetVersions(res.scontext.eng, uint16(minVersion),
uint16(maxVersion))
if not isNil(cache):
sslServerSetCache(
res.scontext, SslSessionCacheClassPointerConst(addr cache.context.vtable))
if TLSFlags.EnforceServerPref in flags:
sslEngineAddFlags(res.scontext.eng, OPT_ENFORCE_SERVER_PREFERENCES)
if TLSFlags.NoRenegotiation in flags:
sslEngineAddFlags(res.scontext.eng, OPT_NO_RENEGOTIATION)
if TLSFlags.TolerateNoClientAuth in flags:
sslEngineAddFlags(res.scontext.eng, OPT_TOLERATE_NO_CLIENT_AUTH)
if TLSFlags.FailOnAlpnMismatch in flags:
sslEngineAddFlags(res.scontext.eng, OPT_FAIL_ON_ALPN_MISMATCH)
let err = sslServerReset(res.scontext)
if err == 0:
raise newException(TLSStreamInitError, "Could not initialize TLS layer")
init(AsyncStreamWriter(res.writer), wsource, tlsWriteLoop, bufferSize)
init(AsyncStreamReader(res.reader), rsource, tlsReadLoop, bufferSize)
res
proc copyKey(src: RsaPrivateKey): TLSPrivateKey =
## Creates copy of RsaPrivateKey ``src``.
var offset = 0'u
let keySize = src.plen + src.qlen + src.dplen + src.dqlen + src.iqlen
var res = TLSPrivateKey(kind: TLSKeyType.RSA, storage: newSeq[byte](keySize))
copyMem(addr res.storage[offset], src.p, src.plen)
res.rsakey.p = addr res.storage[offset]
res.rsakey.plen = src.plen
offset = offset + src.plen
copyMem(addr res.storage[offset], src.q, src.qlen)
res.rsakey.q = addr res.storage[offset]
res.rsakey.qlen = src.qlen
offset = offset + src.qlen
copyMem(addr res.storage[offset], src.dp, src.dplen)
res.rsakey.dp = addr res.storage[offset]
res.rsakey.dplen = src.dplen
offset = offset + src.dplen
copyMem(addr res.storage[offset], src.dq, src.dqlen)
res.rsakey.dq = addr res.storage[offset]
res.rsakey.dqlen = src.dqlen
offset = offset + src.dqlen
copyMem(addr res.storage[offset], src.iq, src.iqlen)
res.rsakey.iq = addr res.storage[offset]
res.rsakey.iqlen = src.iqlen
res.rsakey.nBitlen = src.nBitlen
res
proc copyKey(src: EcPrivateKey): TLSPrivateKey =
## Creates copy of EcPrivateKey ``src``.
var offset = 0
let keySize = src.xlen
var res = TLSPrivateKey(kind: TLSKeyType.EC, storage: newSeq[byte](keySize))
copyMem(addr res.storage[offset], src.x, src.xlen)
res.eckey.x = addr res.storage[offset]
res.eckey.xlen = src.xlen
res.eckey.curve = src.curve
res
proc init*(tt: typedesc[TLSPrivateKey], data: openArray[byte]): TLSPrivateKey {.
raises: [TLSStreamProtocolError].} =
## Initialize TLS private key from array of bytes ``data``.
##
## This procedure initializes private key using raw, DER-encoded format,
## or wrapped in an unencrypted PKCS#8 archive (again DER-encoded).
var ctx: SkeyDecoderContext
if len(data) == 0:
raiseTLSStreamProtocolError("Incorrect private key")
skeyDecoderInit(ctx)
skeyDecoderPush(ctx, cast[pointer](unsafeAddr data[0]), uint(len(data)))
let err = skeyDecoderLastError(ctx)
if err != 0:
raiseTLSStreamProtocolError(err)
let keyType = skeyDecoderKeyType(ctx)
let res =
if keyType == KEYTYPE_RSA:
copyKey(ctx.key.rsa)
elif keyType == KEYTYPE_EC:
copyKey(ctx.key.ec)
else:
raiseTLSStreamProtocolError("Unknown key type (" & $keyType & ")")
res
proc pemDecode*(data: openArray[char]): seq[PEMElement] {.
raises: [TLSStreamProtocolError].} =
## Decode PEM encoded string and get array of binary blobs.
if len(data) == 0:
raiseTLSStreamProtocolError("Empty PEM message")
var pctx = new PEMContext
var res = newSeq[PEMElement]()
proc itemAppend(ctx: pointer, pbytes: pointer, nbytes: uint) {.cdecl.} =
var p = cast[PEMContext](ctx)
var o = uint(len(p.data))
p.data.setLen(o + nbytes)
copyMem(addr p.data[o], pbytes, nbytes)
var offset = 0
var inobj = false
var elem: PEMElement
var ctx: PemDecoderContext
ctx.init()
ctx.setdest(itemAppend, cast[pointer](pctx))
while offset < data.len:
let tlen = ctx.push(data.toOpenArray(offset, data.high))
offset = offset + tlen
let event = ctx.lastEvent()
if event == PEM_BEGIN_OBJ:
inobj = true
elem.name = ctx.banner()
pctx.data.setLen(0)
elif event == PEM_END_OBJ:
if inobj:
elem.data = pctx.data
res.add(elem)
inobj = false
else:
break
else:
raiseTLSStreamProtocolError("Invalid PEM encoding")
res
proc init*(tt: typedesc[TLSPrivateKey], data: openArray[char]): TLSPrivateKey {.
raises: [TLSStreamProtocolError].} =
## Initialize TLS private key from string ``data``.
##
## This procedure initializes private key using unencrypted PKCS#8 PEM
## encoded string.
##
## Note that PKCS#1 PEM encoded objects are not supported.
var res: TLSPrivateKey
var items = pemDecode(data)
for item in items:
if item.name == "PRIVATE KEY":
res = TLSPrivateKey.init(item.data)
break
if isNil(res):
raiseTLSStreamProtocolError("Could not find private key")
res
proc init*(tt: typedesc[TLSCertificate],
data: openArray[char]): TLSCertificate {.
raises: [TLSStreamProtocolError].} =
## Initialize TLS certificates from string ``data``.
##
## This procedure initializes array of certificates from PEM encoded string.
var items = pemDecode(data)
# storage needs to be big enough for input data
var res = TLSCertificate(storage: newSeqOfCap[byte](data.len))
for item in items:
if item.name == "CERTIFICATE" and len(item.data) > 0:
let offset = len(res.storage)
res.storage.add(item.data)
let cert = X509Certificate(
data: addr res.storage[offset],
dataLen: uint(len(item.data))
)
let ares = getSignerAlgo(cert)
if ares == -1:
raiseTLSStreamProtocolError("Could not decode certificate")
elif ares != KEYTYPE_RSA and ares != KEYTYPE_EC:
raiseTLSStreamProtocolError(
"Unsupported signing key type in certificate")
res.certs.add(cert)
if len(res.storage) == 0:
raiseTLSStreamProtocolError("Could not find any certificates")
res
proc init*(tt: typedesc[TLSSessionCache],
size: int = TLSSessionCacheBufferSize): TLSSessionCache =
## Create new TLS session cache with size ``size``.
##
## One cached item is near 100 bytes size.
let rsize = min(size, 4096)
var res = TLSSessionCache(storage: newSeq[byte](rsize))
sslSessionCacheLruInit(addr res.context, addr res.storage[0], rsize)
res
proc handshake*(rws: SomeTLSStreamType): Future[void] {.
async: (raw: true, raises: [CancelledError, AsyncStreamError]).} =
## Wait until initial TLS handshake will be successfully performed.
let retFuture = Future[void].Raising([CancelledError, AsyncStreamError])
.init("tlsstream.handshake")
when rws is TLSStreamReader:
if rws.handshaked:
retFuture.complete()
else:
rws.handshakeFut = retFuture
rws.stream.writer.handshakeFut = retFuture
elif rws is TLSStreamWriter:
if rws.handshaked:
retFuture.complete()
else:
rws.handshakeFut = retFuture
rws.stream.reader.handshakeFut = retFuture
elif rws is TLSAsyncStream:
if rws.reader.handshaked:
retFuture.complete()
else:
rws.reader.handshakeFut = retFuture
rws.writer.handshakeFut = retFuture
retFuture