bpo-28182: Expose OpenSSL verification results (#3412)

The SSL module now raises SSLCertVerificationError when OpenSSL fails to
verify the peer's certificate. The exception contains more information about
the error.

Original patch by Chi Hsuan Yen

Signed-off-by: Christian Heimes <christian@python.org>
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 1f3a31a..062e802 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -104,7 +104,7 @@
 from _ssl import _SSLContext, MemoryBIO, SSLSession
 from _ssl import (
     SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
-    SSLSyscallError, SSLEOFError,
+    SSLSyscallError, SSLEOFError, SSLCertVerificationError
     )
 from _ssl import txt2obj as _txt2obj, nid2obj as _nid2obj
 from _ssl import RAND_status, RAND_add, RAND_bytes, RAND_pseudo_bytes
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index fe9f693..99fd80b 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -2530,6 +2530,29 @@
         finally:
             t.join()
 
+    def test_ssl_cert_verify_error(self):
+        if support.verbose:
+            sys.stdout.write("\n")
+
+        server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+        server_context.load_cert_chain(SIGNED_CERTFILE)
+
+        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+
+        server = ThreadedEchoServer(context=server_context, chatty=True)
+        with server:
+            with context.wrap_socket(socket.socket(),
+                                     server_hostname="localhost") as s:
+                try:
+                    s.connect((HOST, server.port))
+                except ssl.SSLError as e:
+                    msg = 'unable to get local issuer certificate'
+                    self.assertIsInstance(e, ssl.SSLCertVerificationError)
+                    self.assertEqual(e.verify_code, 20)
+                    self.assertEqual(e.verify_message, msg)
+                    self.assertIn(msg, repr(e))
+                    self.assertIn('certificate verify failed', repr(e))
+
     @skip_if_broken_ubuntu_ssl
     @unittest.skipUnless(hasattr(ssl, 'PROTOCOL_SSLv2'),
                          "OpenSSL is compiled without SSLv2 support")