diff options
author | Christian Ulrich <christian@ulrich.earth> | 2020-03-22 21:00:37 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-22 21:00:37 +0100 |
commit | 0ac9c7bb642aefc18a9ca1a7b58f22aea6cf61ce (patch) | |
tree | 4095d436e1d61c088119463380ca54d65ba73a05 /lib | |
parent | ef2566218e701e6294608c6c30a367624786dcd6 (diff) | |
download | Nim-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.nim | 13 | ||||
-rw-r--r-- | lib/pure/net.nim | 34 | ||||
-rw-r--r-- | lib/wrappers/openssl.nim | 54 |
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") |