summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorChristian Ulrich <christian@ulrich.earth>2020-03-22 21:00:37 +0100
committerGitHub <noreply@github.com>2020-03-22 21:00:37 +0100
commit0ac9c7bb642aefc18a9ca1a7b58f22aea6cf61ce (patch)
tree4095d436e1d61c088119463380ca54d65ba73a05 /lib
parentef2566218e701e6294608c6c30a367624786dcd6 (diff)
downloadNim-0ac9c7bb642aefc18a9ca1a7b58f22aea6cf61ce.tar.gz
introduce getPeerCertificates, fixes #13299 (#13650)
* make i2d_X509 and d2i_X509 always available

i2d_X509 and d2i_X509 have been available in all versions of OpenSSL, so
make them available even if nimDisableCertificateValidation is set.

* introduce getPeerCertificates, fixes #13299

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.
Diffstat (limited to 'lib')
-rw-r--r--lib/pure/asyncnet.nim13
-rw-r--r--lib/pure/net.nim34
-rw-r--r--lib/wrappers/openssl.nim54
3 files changed, 79 insertions, 22 deletions
diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim
index 88852fb84..8bdab88b1 100644
--- a/lib/pure/asyncnet.nim
+++ b/lib/pure/asyncnet.nim
@@ -95,6 +95,8 @@
 ##   runForever()
 ##
 
+include "system/inclrtl"
+
 import asyncdispatch
 import nativesockets
 import net
@@ -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.
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index ca3d37259..2f43573ac 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -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
@@ -82,6 +84,8 @@ when defineSsl:
 
 when defineSsl:
   type
+    Certificate* = string ## DER encoded certificate
+
     SslError* = object of Exception
 
     SslCVerifyMode* = enum
@@ -747,6 +751,36 @@ 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:
+      let x509 = cast[PX509](OPENSSL_sk_value(stack, i))
+      result.add(i2d_X509(x509))
+
+  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.
diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim
index 789b3611c..94da4fc79 100644
--- a/lib/wrappers/openssl.nim
+++ b/lib/wrappers/openssl.nim
@@ -91,6 +91,7 @@ type
   PSslPtr* = ptr SslPtr
   SslCtx* = SslPtr
   PSSL_METHOD* = SslPtr
+  PSTACK* = SslPtr
   PX509* = SslPtr
   PX509_NAME* = SslPtr
   PEVP_MD* = SslPtr
@@ -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,
@@ -426,6 +429,35 @@ 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 d2i_X509*(px: ptr PX509, i: ptr ptr cuchar, len: cint): PX509 {.cdecl,
+    dynlib: DLLSSLName, importc.}
+
+proc i2d_X509*(cert: PX509; o: ptr ptr cuchar): cint {.cdecl,
+    dynlib: DLLSSLName, importc.}
+
+proc d2i_X509*(b: string): PX509 =
+  ## decode DER/BER bytestring into X.509 certificate struct
+  var bb = b.cstring
+  let i = cast[ptr ptr cuchar](addr bb)
+  let ret = d2i_X509(addr result, i, b.len.cint)
+  if ret.isNil:
+    raise newException(Exception, "X.509 certificate decoding failed")
+
+proc i2d_X509*(cert: PX509): string =
+  ## encode `cert` to DER string
+  let encoded_length = i2d_X509(cert, nil)
+  result = newString(encoded_length)
+  var q = result.cstring
+  let o = cast[ptr ptr cuchar](addr q)
+  let length = i2d_X509(cert, o)
+  if length.int <= 0:
+    raise newException(Exception, "X.509 certificate encoding failed")
+
 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.}
@@ -688,30 +720,8 @@ when not defined(nimDisableCertificateValidation) and not defined(windows):
   proc X509_STORE_set_trust*(ctx: PX509_STORE; trust: cint): cint
   proc X509_STORE_add_cert*(ctx: PX509_STORE; x: PX509): cint
 
-  proc d2i_X509*(px: ptr PX509, i: ptr ptr cuchar, len: cint): PX509
-
-  proc i2d_X509*(cert: PX509; o: ptr ptr cuchar): cint
-
   {.pop.}
 
-  proc d2i_X509*(b: string): PX509 =
-    ## decode DER/BER bytestring into X.509 certificate struct
-    var bb = b.cstring
-    let i = cast[ptr ptr cuchar](addr bb)
-    let ret = d2i_X509(addr result, i, b.len.cint)
-    if ret.isNil:
-      raise newException(Exception, "X.509 certificate decoding failed")
-
-  proc i2d_X509*(cert: PX509): string =
-    ## encode `cert` to DER string
-    let encoded_length = i2d_X509(cert, nil)
-    result = newString(encoded_length)
-    var q = result.cstring
-    let o = cast[ptr ptr cuchar](addr q)
-    let length = i2d_X509(cert, o)
-    if length.int <= 0:
-      raise newException(Exception, "X.509 certificate encoding failed")
-
   when isMainModule:
     # A simple certificate test
     let certbytes = readFile("certificate.der")