# Self-signed ssl certs verification

**URL:** <https://discuss.python.org/t/self-signed-ssl-certs-verification/85211>\
**Category:** Python Help\
**Created:** [March 19, 2025, 9:29pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211 "2025-03-19T21:29:46Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![StrangeTcy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/strangetcy/32/17837_2.png) [@StrangeTcy](https://discuss.python.org/u/StrangeTcy)\
**Post date:** [March 19, 2025, 9:29pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/1 "2025-03-19T21:29:46Z")

</div>

I’ve generated a private key and a self-signed certificate 3 different ways: using command-line openssl, pyopenssl and pyca/cryptography. Then I’ve used them to create ssl context for a simple flask app.  
The flask app itself runs fine, but when I try to send a file to it using `requests.post(url, files={"file": my_file}, verify=my_cert)`, I get an ssl error:

```python
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1007)'))

```

Trying to mess around with certificate extensions hasn’t solved the issue so far. Using `verify=False` would be equal to giving up.  
The surprising part (to me) is that all the methods of self-signed cert generation lead to the same error: the cert being recognised as self-signed.

I can add further details if needed (python version, pyopenssl version, system ssl version and so on).

Any pointers’d be appreciated.

Here’s a function that creates the self-signed certificates:

```python
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import datetime
import ipaddress

def create_self_signed_cert(cert_file, key_file, ip_address):
    # Generate private key - same as OpenSSL's -newkey rsa:4096
    key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
    
    # Simple subject/issuer like OpenSSL
    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, ip_address),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Some Name"),
    ])
    
    # Build certificate - match OpenSSL's basic self-signed cert
    cert_builder = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=365)
    )

    # Add the exact extensions that OpenSSL adds for server certs
    cert_builder = cert_builder.add_extension(
        x509.SubjectAlternativeName([
            x509.DNSName(ip_address),
            x509.IPAddress(ipaddress.ip_address(ip_address))
        ]),
        critical=False
    )
    
    # Basic Constraints marked as critical - this is a CA cert
    cert_builder = cert_builder.add_extension(
        x509.BasicConstraints(ca=True, path_length=None),
        critical=True
    )

    # Key Usage extension
    cert_builder = cert_builder.add_extension(
        x509.KeyUsage(
            digital_signature=True,
            content_commitment=False,
            key_encipherment=True,
            data_encipherment=False,
            key_agreement=False,
            key_cert_sign=True,
            crl_sign=True,
            encipher_only=False,
            decipher_only=False
        ),
        critical=True
    )

    # Extended Key Usage
    cert_builder = cert_builder.add_extension(
        x509.ExtendedKeyUsage([
            x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
            x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH
        ]),
        critical=False
    )
    
    # Subject Key Identifier
    cert_builder = cert_builder.add_extension(
        x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
        critical=False
    )

    # Authority Key Identifier
    cert_builder = cert_builder.add_extension(
        x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()),
        critical=False
    )
    
    # Sign with SHA512 
    cert = cert_builder.sign(private_key=key, algorithm=hashes.SHA512())
    
    # Write certificate and key in PEM format
    with open(cert_file, "wb") as f:
        f.write(cert.public_bytes(serialization.Encoding.PEM))
    
    with open(key_file, "wb") as f:
        f.write(key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption()
        ))

create_self_signed_cert("selfsigned.crt", "private.key", "192.168.1.172")

```

(the whole experiment is setup in a local network)

The flask app is pretty basic:

```python
from flask import Flask, request
from pathlib import Path

from crypto_common import verify_descriptor
from utils.networking_utils import get_ip_address_2

app = Flask( __name__ )

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route("/tor", methods=["GET", "POST"])
def accept_descriptor():
    if request.method == "GET":
        return "<p>You're supposed to send descriptors here</p>"
    if request.method == "POST":
        # print(f"we've got this POSTed to us: {request.data}")
        print(f"the files we've got are {request.files}")
        print("Saving our file...")
        file = request.files["file"]
        our_folder = Path("received_descriptors/")
        our_filepath = our_folder / file.filename
        our_folder.mkdir(exist_ok=True, parents=True)
        descriptor_content = file.read().decode("utf-8")
        if verify_descriptor(descriptor_content) == 0:
            print("Saving a valid descriptor to file...")
            with our_filepath.open("w", encoding="utf-8") as f:
                f.write(descriptor_content)
            return "OK"
        else:
            print("Descriptor verification failed;\nnot saving.")
            return "Not OK"    

app.debug = False

def run_flask_app(dir_port=10330, 
                  cert="selfsigned.crt", 
                  key="private.key"):
    #interface = "wlp4s0"
    ip = get_ip_address_2()[1]

    app.run(host=ip, port=dir_port, ssl_context=(cert, key))

```

And then setup a request:

```python
import requests

files = {"file": b"example"}

requests.post("192.168.1.172", files=file, verify=selfsigned.crt)

```

And there you go.

The line using openssl is pretty standard:

```python
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365

```

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [March 20, 2025, 8:01am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/2 "2025-03-20T08:01:18Z")

</div>

A web search for “ python openssl self signed certificate error” leads to this stackoverflow answer [How to get Python requests to trust a self signed SSL certificate? - Stack Overflow](https://stackoverflow.com/questions/30405867/how-to-get-python-requests-to-trust-a-self-signed-ssl-certificate) which suggests this

```python
r = requests.post(url, data=data, verify='/path/to/public_key.pem')

```

Does that work for you code?

---

<div class="post-metadata">

**Author:** ![StrangeTcy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/strangetcy/32/17837_2.png) [@StrangeTcy](https://discuss.python.org/u/StrangeTcy)\
**Post date:** [March 20, 2025, 12:48pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/3 "2025-03-20T12:48:10Z")

</div>

In so many words: no.

Please see above for the part where I do basically the same

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [March 20, 2025, 1:18pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/4 "2025-03-20T13:18:06Z")

</div>

This [ssl - Can't verify an openssl certificate against a self signed openssl certificate? - Super User](https://superuser.com/questions/1428012/cant-verify-an-openssl-certificate-against-a-self-signed-openssl-certificate) suggests that there are important config settings that are needed for the self-signed cert to work.

But I’m not sure this is the problem.

As an aside I side stepped these issues by running my own CA using the easy-rsa software. So each of my systems has a certificate that can be verified against my root CA cert.

---

<div class="post-metadata">

**Author:** ![StrangeTcy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/strangetcy/32/17837_2.png) [@StrangeTcy](https://discuss.python.org/u/StrangeTcy)\
**Post date:** [April 5, 2025, 9:29am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/5 "2025-04-05T09:29:50Z")

</div>

Well, sure, the problem can be sidestepped, but I’d like to find out why it occurs in the first place, and how it can be fixed

---

<div class="post-metadata">

**Author:** ![ajoino](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ajoino/32/6907_2.png) [@ajoino](https://discuss.python.org/u/ajoino)\
**Post date:** [April 5, 2025, 10:45am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/6 "2025-04-05T10:45:16Z")

</div>

One thing you could try to make sure your Python code is not the culprit is to generate the same certs using the openssl command line tools. If it works using cli generated tools it might help you figure out what’s wrong with the Python generated ones.

---

<div class="post-metadata">

**Author:** ![StrangeTcy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/strangetcy/32/17837_2.png) [@StrangeTcy](https://discuss.python.org/u/StrangeTcy)\
**Post date:** [April 5, 2025, 10:51am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/7 "2025-04-05T10:51:12Z")

</div>

True, and I did. My python code recognizes the certs generated with console `openssl` as self-signed as well.  
My current guess is that there’s some check that fails somewhere deep inside the C/Cpython part of requests. But I don’t know enough C/C++ to find it in `_ssl.c`

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [April 5, 2025, 1:54pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/8 "2025-04-05T13:54:06Z")

</div>

The rules for valid certificates keep being tighten up.  
I suspect your self sign cert is missing one of the newer requirements.

I use curl to test a url that is failing as curl provides lots of information about what it is doing and what the errors are.

Try curl, does it work?

---

<div class="post-metadata">

**Author:** ![StrangeTcy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/strangetcy/32/17837_2.png) [@StrangeTcy](https://discuss.python.org/u/StrangeTcy)\
**Post date:** [April 5, 2025, 3:19pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/9 "2025-04-05T15:19:18Z")

</div>

Nope. Here’re the logs

```python
curl --data "stuff" --verbose https://192.168.1.172:10330/tor
* Trying 192.168.1.172:10330...
* Connected to 192.168.1.172 (192.168.1.172) port 10330 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS header, Unknown (21):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: self-signed certificate
* Closing connection 0
curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

```

Guess I should visit that page.  
ETA:

> If the remote server uses a self-signed certificate, if you do not install a CA cert store, if the server uses a certificate signed by a CA that is not included in the store you use or if the remote host is an impostor impersonating your favorite site, the certificate check fails and reports an error.

Okay, that makes sense, but that basically dooms self-signed certs forever, and that’s not cool

---

<div class="post-metadata">

**Author:** ![StrangeTcy](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/strangetcy/32/17837_2.png) [@StrangeTcy](https://discuss.python.org/u/StrangeTcy)\
**Post date:** [April 5, 2025, 3:42pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/10 "2025-04-05T15:42:59Z")

</div>

Okay, I’ve just created a fresh conda environment, and there everything worked like a charm.  
This is getting interesting, if not really any clearer

---

<div class="post-metadata">

**Author:** ![stb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stb/32/28229_2.png) [@stb](https://discuss.python.org/u/stb)\
**Post date:** [May 29, 2025, 10:17am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/11 "2025-05-29T10:17:42Z")

</div>

Did you ever figure out what the issue is? I’ve been banging my head against this for a few hours, and I can’t figure out what OpenSSL doesn’t like about by self-signed cert.

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [May 29, 2025, 10:48am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/12 "2025-05-29T10:48:29Z")

</div>

Openssl requires the signing cert’s public key to be in a trust store.  
If you have added the self-signed cert to your trust store it cannot be validated.

I have been using easy-rsa to setup my own certificate authority (CA) and added the CA to all client trust stores. Then I issue certs to each server in my network.

---

<div class="post-metadata">

**Author:** ![stb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stb/32/28229_2.png) [@stb](https://discuss.python.org/u/stb)\
**Post date:** [May 29, 2025, 11:28am UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/13 "2025-05-29T11:28:21Z")

</div>

@barry-scott thanks, but that’t not an answer to the original (and my) question.

It used to be that you could tell OpenSSL to use the self-signed certificate as a root certificate by configuring this one cert as the CA cert list. It looks to me that OpenSSL does not allow that anymore? If that is indeed the case, a very common use case for self-signed certificates would be blocked, as Maxim pointed out.

I do not want to create my own CA and start managing certs and CA config all over the place. I just want to configure the one client I have to trust the cert that this one server has. It used to be simple to do that.

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [May 29, 2025, 12:09pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/14 "2025-05-29T12:09:28Z")

</div>

I world guess that the self signed cert is missing an important property. For example does it have an SNI for the host?

Also rules on valid certs are getting tougher all the time.  
Latest is a move to not allow certs with lifetime above 47 days, due to be enforced in a couple of years. I see failures for certs with lifetimes above 390(?) days already from Apple systems.

---

<div class="post-metadata">

**Author:** ![graingert](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/graingert/32/26847_2.png) [@graingert](https://discuss.python.org/u/graingert)\
**Post date:** [May 29, 2025, 1:43pm UTC](https://discuss.python.org/t/self-signed-ssl-certs-verification/85211/15 "2025-05-29T13:43:15Z")

</div>

I’d recommend building test certificates with `trustme`

Here’s an example using it with requests: [GitHub - python-trio/trustme: #1 quality TLS certs while you wait, for the discerning tester](https://github.com/python-trio/trustme?tab=readme-ov-file#cheat-sheet)
