# # 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, 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 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, 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