Skip to content

Commit

Permalink
introduce getPeerCertificates, fixes nim-lang#13299
Browse files Browse the repository at this point in the history
getPeerCertificates retrieves the verified certificate chain of the peer
we are connected to through an SSL-wrapped Socket/AsyncSocket. This
introduces the new type Certificate which stores a DER-encoded X509 certificate.
  • Loading branch information
royneary committed Mar 21, 2020
1 parent 586ebb0 commit 9125719
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 0 deletions.
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ echo f
- Added a new module, `std / compilesettings` for querying the compiler about
diverse configuration settings.
- `base64` adds URL-Safe Base64, implements RFC-4648 Section-7.
- Added `net.getPeerCertificates` and `asyncnet.getPeerCertificates` for
retrieving the verified certificate chain of the peer we are connected to
through an SSL-wrapped `Socket`/`AsyncSocket`.


## Library changes
Expand Down
13 changes: 13 additions & 0 deletions lib/pure/asyncnet.nim
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
## runForever()
##

include "system/inclrtl"

import asyncdispatch
import nativesockets
import net
Expand Down Expand Up @@ -743,6 +745,17 @@ when defineSsl:
of handshakeAsServer:
sslSetAcceptState(socket.sslHandle)

proc getPeerCertificates*(socket: AsyncSocket): seq[Certificate] {.since: (1, 1).} =
## Returns the certificate chain received by the peer we are connected to
## through the given socket.
## The handshake must have been completed and the certificate chain must
## have been verified successfully or else an empty sequence is returned.
## The chain is ordered from leaf certificate to root certificate.
if not socket.isSsl:
result = newSeq[Certificate]()
else:
result = getPeerCertificates(socket.sslHandle)

proc getSockOpt*(socket: AsyncSocket, opt: SOBool, level = SOL_SOCKET): bool {.
tags: [ReadIOEffect].} =
## Retrieves option ``opt`` as a boolean value.
Expand Down
38 changes: 38 additions & 0 deletions lib/pure/net.nim
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
## socket.acceptAddr(client, address)
## echo("Client connected from: ", address)

include "system/inclrtl"

{.deadCodeElim: on.} # dce option deprecated
import nativesockets, os, strutils, parseutils, times, sets, options,
std/monotimes
Expand All @@ -82,6 +84,8 @@ when defineSsl:

when defineSsl:
type
Certificate* = string ## DER encoded certificate

SslError* = object of Exception

SslCVerifyMode* = enum
Expand Down Expand Up @@ -747,6 +751,40 @@ when defineSsl:
let ret = SSL_accept(socket.sslHandle)
socketError(socket, ret)

proc getPeerCertificates*(sslHandle: SslPtr): seq[Certificate] {.since: (1, 1).} =
## Returns the certificate chain received by the peer we are connected to
## through the OpenSSL connection represented by ``sslHandle``.
## The handshake must have been completed and the certificate chain must
## have been verified successfully or else an empty sequence is returned.
## The chain is ordered from leaf certificate to root certificate.
result = newSeq[Certificate]()
if SSL_get_verify_result(sslHandle) != X509_V_OK:
return
let stack = SSL_get0_verified_chain(sslHandle)
if stack == nil:
return
let length = OPENSSL_sk_num(stack)
if length == 0:
return
for i in 0 .. length - 1:
var der: ptr cuchar = nil
let derLen = i2d_X509(cast[PX509](OPENSSL_sk_value(stack, i)), addr der)
var cert = newString(derLen)
copyMem(addr cert[0], der, derLen)
CRYPTO_free(der, instantiationInfo().filename, instantiationInfo().line)
result.add(cert)

proc getPeerCertificates*(socket: Socket): seq[Certificate] {.since: (1, 1).} =
## Returns the certificate chain received by the peer we are connected to
## through the given socket.
## The handshake must have been completed and the certificate chain must
## have been verified successfully or else an empty sequence is returned.
## The chain is ordered from leaf certificate to root certificate.
if not socket.isSsl:
result = newSeq[Certificate]()
else:
result = getPeerCertificates(socket.sslHandle)

proc getSocketError*(socket: Socket): OSErrorCode =
## Checks ``osLastError`` for a valid error. If it has been reset it uses
## the last error stored in the socket object.
Expand Down
14 changes: 14 additions & 0 deletions lib/wrappers/openssl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type
PSslPtr* = ptr SslPtr
SslCtx* = SslPtr
PSSL_METHOD* = SslPtr
PSTACK* = SslPtr
PX509* = SslPtr
PX509_NAME* = SslPtr
PEVP_MD* = SslPtr
Expand Down Expand Up @@ -359,6 +360,8 @@ proc SSL_new*(context: SslCtx): SslPtr{.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_free*(ssl: SslPtr){.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_get_SSL_CTX*(ssl: SslPtr): SslCtx {.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_set_SSL_CTX*(ssl: SslPtr, ctx: SslCtx): SslCtx {.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_get0_verified_chain*(ssl: SslPtr): PSTACK {.cdecl,
dynlib: DLLSSLName, importc.}
proc SSL_CTX_new*(meth: PSSL_METHOD): SslCtx{.cdecl,
dynlib: DLLSSLName, importc.}
proc SSL_CTX_load_verify_locations*(ctx: SslCtx, CAfile: cstring,
Expand Down Expand Up @@ -426,6 +429,14 @@ proc ERR_peek_last_error*(): cint{.cdecl, dynlib: DLLUtilName, importc.}

proc OPENSSL_config*(configName: cstring){.cdecl, dynlib: DLLSSLName, importc.}

proc OPENSSL_sk_num*(stack: PSTACK): int {.cdecl, dynlib: DLLSSLName, importc.}

proc OPENSSL_sk_value*(stack: PSTACK, index: int): pointer {.cdecl,
dynlib: DLLSSLName, importc.}

proc i2d_X509*(x: PX509, output: ptr ptr cuchar): int {.cdecl,
dynlib: DLLSSLName, importc.}

when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL):
proc CRYPTO_set_mem_functions(a,b,c: pointer){.cdecl,
dynlib: DLLUtilName, importc.}
Expand All @@ -443,6 +454,9 @@ proc CRYPTO_malloc_init*() =
when not useWinVersion and not defined(macosx) and not defined(android) and not defined(nimNoAllocForSSL):
CRYPTO_set_mem_functions(allocWrapper, reallocWrapper, deallocWrapper)

proc CRYPTO_free*(p: pointer, file: cstring, line: int) {.cdecl,
dynlib: DLLSSLName, importc.}

proc SSL_CTX_ctrl*(ctx: SslCtx, cmd: cint, larg: int, parg: pointer): int{.
cdecl, dynlib: DLLSSLName, importc.}

Expand Down

0 comments on commit 9125719

Please sign in to comment.