thttpclient_ssl_remotenetwork.nim 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #
  2. #
  3. # Nim - SSL integration tests
  4. # (c) Copyright 2017 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Test with:
  10. ## nim r --putenv:NIM_TESTAMENT_REMOTE_NETWORKING:1 -d:ssl -p:. --threads:on tests/untestable/thttpclient_ssl_remotenetwork.nim
  11. ##
  12. ## See https://github.com/FedericoCeratto/ssl-comparison/blob/master/README.md
  13. ## for a comparison with other clients.
  14. from stdtest/testutils import enableRemoteNetworking
  15. when enableRemoteNetworking and (defined(nimTestsEnableFlaky) or not defined(windows) and not defined(openbsd)):
  16. # Not supported on Windows due to old openssl version
  17. import
  18. httpclient,
  19. net,
  20. strutils,
  21. threadpool,
  22. unittest
  23. type
  24. # bad and dubious tests should not pass SSL validation
  25. # "_broken" mark the test as skipped. Some tests have different
  26. # behavior depending on OS and SSL version!
  27. # TODO: chase and fix the broken tests
  28. Category = enum
  29. good, bad, dubious, good_broken, bad_broken, dubious_broken
  30. CertTest = tuple[url:string, category:Category, desc: string]
  31. # XXX re-enable when badssl fixes certs, some expired as of 2023-04-23 (#21709)
  32. when false:
  33. const certificate_tests: array[0..54, CertTest] = [
  34. ("https://wrong.host.badssl.com/", bad, "wrong.host"),
  35. ("https://captive-portal.badssl.com/", bad, "captive-portal"),
  36. ("https://expired.badssl.com/", bad, "expired"),
  37. ("https://google.com/", good, "good"),
  38. ("https://self-signed.badssl.com/", bad, "self-signed"),
  39. ("https://untrusted-root.badssl.com/", bad, "untrusted-root"),
  40. ("https://revoked.badssl.com/", bad_broken, "revoked"),
  41. ("https://pinning-test.badssl.com/", bad_broken, "pinning-test"),
  42. ("https://no-common-name.badssl.com/", bad, "no-common-name"),
  43. ("https://no-subject.badssl.com/", bad, "no-subject"),
  44. ("https://sha1-intermediate.badssl.com/", bad, "sha1-intermediate"),
  45. ("https://sha256.badssl.com/", good, "sha256"),
  46. ("https://sha384.badssl.com/", bad, "sha384"),
  47. ("https://sha512.badssl.com/", bad, "sha512"),
  48. ("https://1000-sans.badssl.com/", bad, "1000-sans"),
  49. ("https://10000-sans.badssl.com/", good_broken, "10000-sans"),
  50. ("https://ecc256.badssl.com/", good_broken, "ecc256"),
  51. ("https://ecc384.badssl.com/", good_broken, "ecc384"),
  52. ("https://rsa2048.badssl.com/", good, "rsa2048"),
  53. ("https://rsa8192.badssl.com/", dubious_broken, "rsa8192"),
  54. ("http://http.badssl.com/", good, "regular http"),
  55. ("https://http.badssl.com/", bad_broken, "http on https URL"), # FIXME
  56. ("https://cbc.badssl.com/", dubious, "cbc"),
  57. ("https://rc4-md5.badssl.com/", bad, "rc4-md5"),
  58. ("https://rc4.badssl.com/", bad, "rc4"),
  59. ("https://3des.badssl.com/", bad, "3des"),
  60. ("https://null.badssl.com/", bad, "null"),
  61. ("https://mozilla-old.badssl.com/", bad_broken, "mozilla-old"),
  62. ("https://mozilla-intermediate.badssl.com/", dubious_broken, "mozilla-intermediate"),
  63. ("https://mozilla-modern.badssl.com/", good, "mozilla-modern"),
  64. ("https://dh480.badssl.com/", bad, "dh480"),
  65. ("https://dh512.badssl.com/", bad, "dh512"),
  66. ("https://dh1024.badssl.com/", dubious_broken, "dh1024"),
  67. ("https://dh2048.badssl.com/", good, "dh2048"),
  68. ("https://dh-small-subgroup.badssl.com/", bad_broken, "dh-small-subgroup"),
  69. ("https://dh-composite.badssl.com/", bad_broken, "dh-composite"),
  70. ("https://static-rsa.badssl.com/", dubious, "static-rsa"),
  71. ("https://tls-v1-0.badssl.com:1010/", dubious, "tls-v1-0"),
  72. ("https://tls-v1-1.badssl.com:1011/", dubious, "tls-v1-1"),
  73. ("https://invalid-expected-sct.badssl.com/", bad, "invalid-expected-sct"),
  74. ("https://hsts.badssl.com/", good, "hsts"),
  75. ("https://upgrade.badssl.com/", good, "upgrade"),
  76. ("https://preloaded-hsts.badssl.com/", good, "preloaded-hsts"),
  77. ("https://subdomain.preloaded-hsts.badssl.com/", bad, "subdomain.preloaded-hsts"),
  78. ("https://https-everywhere.badssl.com/", good, "https-everywhere"),
  79. ("https://long-extended-subdomain-name-containing-many-letters-and-dashes.badssl.com/", good,
  80. "long-extended-subdomain-name-containing-many-letters-and-dashes"),
  81. ("https://longextendedsubdomainnamewithoutdashesinordertotestwordwrapping.badssl.com/", good,
  82. "longextendedsubdomainnamewithoutdashesinordertotestwordwrapping"),
  83. ("https://superfish.badssl.com/", bad, "(Lenovo) Superfish"),
  84. ("https://edellroot.badssl.com/", bad, "(Dell) eDellRoot"),
  85. ("https://dsdtestprovider.badssl.com/", bad, "(Dell) DSD Test Provider"),
  86. ("https://preact-cli.badssl.com/", bad, "preact-cli"),
  87. ("https://webpack-dev-server.badssl.com/", bad, "webpack-dev-server"),
  88. ("https://mitm-software.badssl.com/", bad, "mitm-software"),
  89. ("https://sha1-2016.badssl.com/", dubious, "sha1-2016"),
  90. ("https://sha1-2017.badssl.com/", bad, "sha1-2017"),
  91. ]
  92. else:
  93. const certificate_tests: array[0..0, CertTest] = [
  94. ("https://google.com/", good, "good")
  95. ]
  96. template evaluate(exception_msg: string, category: Category, desc: string) =
  97. # Evaluate test outcome. Tests flagged as `_broken` are evaluated and skipped
  98. let raised = (exception_msg.len > 0)
  99. let should_not_raise = category in {good, dubious_broken, bad_broken}
  100. if should_not_raise xor raised:
  101. # we are seeing a known behavior
  102. if category in {good_broken, dubious_broken, bad_broken}:
  103. skip()
  104. if raised:
  105. # check exception_msg == "No SSL certificate found." or
  106. doAssert exception_msg == "No SSL certificate found." or
  107. exception_msg == "SSL Certificate check failed." or
  108. exception_msg.contains("certificate verify failed") or
  109. exception_msg.contains("key too small") or
  110. exception_msg.contains("alert handshake failure") or
  111. exception_msg.contains("bad dh p length") or
  112. # TODO: This one should only triggers for 10000-sans
  113. exception_msg.contains("excessive message size"), exception_msg
  114. else:
  115. # this is unexpected
  116. var fatal = true
  117. var msg = ""
  118. if raised:
  119. msg = " $# ($#) raised: $#" % [desc, $category, exception_msg]
  120. if "500 Internal Server Error" in exception_msg:
  121. # refs https://github.com/nim-lang/Nim/issues/16338#issuecomment-804300278
  122. # we got: `good (good) raised: 500 Internal Server Error`
  123. fatal = false
  124. msg.add " (http 500 => assuming this is not our problem)"
  125. else:
  126. msg = " $# ($#) did not raise" % [desc, $category]
  127. if category in {good, dubious, bad} and fatal:
  128. echo "D20210322T121353: error: " & msg
  129. fail()
  130. else:
  131. echo "D20210322T121353: warning: " & msg
  132. suite "SSL certificate check - httpclient":
  133. for i, ct in certificate_tests:
  134. test ct.desc:
  135. var ctx = newContext(verifyMode=CVerifyPeer)
  136. var client = newHttpClient(sslContext=ctx)
  137. let exception_msg =
  138. try:
  139. let a = $client.getContent(ct.url)
  140. ""
  141. except:
  142. getCurrentExceptionMsg()
  143. evaluate(exception_msg, ct.category, ct.desc)
  144. # threaded tests
  145. type
  146. TTOutcome = ref object
  147. desc, exception_msg: string
  148. category: Category
  149. proc run_t_test(ct: CertTest): TTOutcome {.thread.} =
  150. ## Run test in a {.thread.} - return by ref
  151. result = TTOutcome(desc:ct.desc, exception_msg:"", category: ct.category)
  152. try:
  153. var ctx = newContext(verifyMode=CVerifyPeer)
  154. var client = newHttpClient(sslContext=ctx)
  155. let a = $client.getContent(ct.url)
  156. except:
  157. result.exception_msg = getCurrentExceptionMsg()
  158. suite "SSL certificate check - httpclient - threaded":
  159. when defined(nimTestsEnableFlaky) or not defined(linux): # xxx pending bug #16338
  160. # Spawn threads before the "test" blocks
  161. var outcomes = newSeq[FlowVar[TTOutcome]](certificate_tests.len)
  162. for i, ct in certificate_tests:
  163. let t = spawn run_t_test(ct)
  164. outcomes[i] = t
  165. # create "test" blocks and handle thread outputs
  166. for t in outcomes:
  167. let outcome = ^t # wait for a thread to terminate
  168. test outcome.desc:
  169. evaluate(outcome.exception_msg, outcome.category, outcome.desc)
  170. else:
  171. echo "skipped test"
  172. # net tests
  173. type NetSocketTest = tuple[hostname: string, port: Port, category:Category, desc: string]
  174. # XXX re-enable when badssl fixes certs, some expired as of 2023-04-23 (#21709)
  175. when false:
  176. const net_tests:array[0..3, NetSocketTest] = [
  177. ("imap.gmail.com", 993.Port, good, "IMAP"),
  178. ("wrong.host.badssl.com", 443.Port, bad, "wrong.host"),
  179. ("captive-portal.badssl.com", 443.Port, bad, "captive-portal"),
  180. ("expired.badssl.com", 443.Port, bad, "expired"),
  181. ]
  182. else:
  183. const net_tests: array[0..0, NetSocketTest] = [
  184. ("imap.gmail.com", 993.Port, good, "IMAP")
  185. ]
  186. # TODO: ("null.badssl.com", 443.Port, bad_broken, "null"),
  187. suite "SSL certificate check - sockets":
  188. for ct in net_tests:
  189. test ct.desc:
  190. var sock = newSocket()
  191. var ctx = newContext()
  192. ctx.wrapSocket(sock)
  193. let exception_msg =
  194. try:
  195. sock.connect(ct.hostname, ct.port)
  196. ""
  197. except:
  198. getCurrentExceptionMsg()
  199. evaluate(exception_msg, ct.category, ct.desc)