3

I get this error when using python 2.7.10 or 2.7.9 but works with python 2.7.6 (haven't tested other versions)

OpenSLL version:

openssl version -a
OpenSSL 1.0.1f 6 Jan 2014
built on: Thu Jun 11 15:30:15 UTC 2015
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx)
compiler: cc -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/lib/ssl"

Python:

import ssl
print ssl.OPENSSL_VERSION_NUMBER
268439663L
print get_server_certificate(('someInternalIp', 443), ssl_version=ssl.PROTOCOL_SSLv23)

StackTrace:

File "C:\Python27\lib\ssl.py", line 985, in get_server_certificate
  with closing(context.wrap_socket(sock)) as sslsock:
File "C:\Python27\lib\ssl.py", line 352, in wrap_socket
  _context=self)
File "C:\Python27\lib\ssl.py", line 579, in __init__
  self.do_handshake()
File "C:\Python27\lib\ssl.py", line 808, in do_handshake
  self._sslobj.do_handshake()
ssl.SSLError: [SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:590)
Sjuul Janssen
  • 1,772
  • 1
  • 14
  • 28

4 Answers4

6

If you run your requests in debian-based OS or docker image you need to change your SSL config. Debian by default works with SSL v1.2 + at the moment.

You can edit openssl.cnf manually if you want but there's a quicker solution:

sed -i 's/MinProtocol = TLSv1.2/MinProtocol = TLSv1.0/' /etc/ssl/openssl.cnf

It helped in my case, requests to URLs with old SSL now return nice 200 response.

Evgeniy
  • 101
  • 1
  • 3
3

Apparently python 2.7.6 used this cipher suite:

DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2

and now it uses:

# Disable weak or insecure ciphers by default
# (OpenSSL's default setting is 'DEFAULT:!aNULL:!eNULL')
# Enable a better set of ciphers by default
# This list has been explicitly chosen to:
#   * Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE)
#   * Prefer ECDHE over DHE for better performance
#   * Prefer any AES-GCM over any AES-CBC for better performance and security
#   * Then Use HIGH cipher suites as a fallback
#   * Then Use 3DES as fallback which is secure but slow
#   * Disable NULL authentication, NULL encryption, and MD5 MACs for security
#     reasons
_DEFAULT_CIPHERS = (
    'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
    'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
    '!eNULL:!MD5'
)

I had to build the following code that informs me in case an old cipher suite was used. I could do this because I'm interested in retrieving the certificate

from socket import socket
from ssl import SSLContext
from ssl import PROTOCOL_SSLv23
from ssl import DER_cert_to_PEM_cert

WEAK_CTX = SSLContext(PROTOCOL_SSLv23)
WEAK_CTX.set_ciphers('ALL:!aNULL:!eNULL')

NORMAL_CTX = SSLContext(PROTOCOL_SSLv23)
NORMAL_CTX.set_ciphers(
    'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
    'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
    '!eNULL:!MD5'
)

def getCertificate(addr):
    sock = socket()
    sock.connect(addr)
    isWeakCipher = False
    try:
        sslobj = NORMAL_CTX._wrap_socket(sock._sock, server_side=False)
        sslobj.do_handshake()
    except Exception as ex:
        if hasattr(ex, 'reason') and ex.reason == 'SSLV3_ALERT_HANDSHAKE_FAILURE':
            sock.close()
            sock = socket()
            sock.connect(addr)
            sslobj = WEAK_CTX._wrap_socket(sock._sock, server_side=False)
            sslobj.do_handshake()
            isWeakCipher = True
        else:
            raise
    cipher = sslobj.cipher()

    cert = sslobj.peer_certificate(True)
    sock.close()
    return isWeakCipher, cipher, cert 
Sjuul Janssen
  • 1,772
  • 1
  • 14
  • 28
  • 3
    Can someone please explain this answer further? What do I do when I get that error? – Yathi Sep 15 '16 at 15:16
  • 1
    In most cases, what you want is to change the settings on the target machine. Most likely it's using an older protocol version. The code above tries to "get in regardless" which is not something you should do in a normal production situation. Try to set your target machine to use TLS. Preferably 1.2 as this is currently the latest. – Sjuul Janssen Sep 17 '16 at 06:45
  • Saved my day. I can confirm the setting ciphers is mandatory in order to use SSLv3 connection. – Khamidulla May 25 '17 at 06:56
  • In my case I had to use `ssl.PROTOCOL_TLSv1` to force the use of TLS v1.0. I'm not sure what's going on, as the description for `PROTOCOL_TLS` (synonym for `PROTOCOL_SSLv23`) says it should automatically choose a compatible option: https://docs.python.org/3/library/ssl.html#ssl.PROTOCOL_TLS – jtpereyda Feb 02 '19 at 19:10
0

Probably your server is exposing an old, insecure version of the TLS protocol, which modern OpenSSLs are configured not to allow.

https://stackoverflow.com/a/53065682/6214034 might help.

Apurva jain
  • 1,460
  • 13
  • 14
0

This is how I solved it using python 3.8 on Ubuntu 20.04.

import ssl
import socket

def get_ssl_data(self, host, port=443):
    context = ssl.create_default_context()
    context.set_ciphers('ALL:@SECLEVEL=1') # this magic line allows us to connect with anything under the sun!!! It took me 3 hours to find it.

    with socket.socket(socket.AF_INET) as sock:
        with context.wrap_socket( sock, server_hostname=host ) as conn:
            conn.settimeout(3.0)
            conn.connect((host, port))
            ssl_info = conn.getpeercert()
            # print(json.dumps(ssl_info, indent=2, sort_keys=True))
            return ssl_info
Marcos Diez
  • 181
  • 3
  • 4