cert_hero package

Submodules

cert_hero.cert_hero module

Main module.

class cert_hero.cert_hero.CertHero[source]

Bases: dict

CertHero represents the (resolved) SSL certificate of a server or hostname; it subclasses from builtin dict, so it is essentially the same as a dict object with convenience methods and a more human-readable __repr__() method, for example.

This means that a CertHero object is inherently JSON serializable:

>>> import cert_hero, json
>>> cert = cert_hero.CertHero({'key': 'value'})
>>> cert
CertHero(
  {
    "key": "value"
  }
)
>>> cert['key']
'value'
>>> json.dumps(cert)  # or, easier: str(cert)
'{"key": "value"}'
classmethod from_dict(o: dict, _from_iso_format=<built-in method fromisoformat of type object>)[source]

Convert a serialized dict to a CertHero object.

property not_after_date: date

The Cert Not After Date (e.g. Valid Until)

property not_before_date: date

The Cert Not Before Date (e.g. Valid From)

cert_hero.cert_hero.cert_please(hostname: str, context: SSLContext = None, user_agent: str | None = None, default_encoding='latin-1') CertHero[str, str | int | dict[str, str | bool]] | None[source]

Retrieve the SSL certificate for a given hostname - works even in the case of expired or self-signed certificates.

Usage:

>>> import cert_hero
>>> cert = cert_hero.cert_please('google.com')
>>> cert.not_after_date
datetime.date(2023, 10, 28)
>>> f'Cert is Valid Till: {cert.not_after_date.isoformat()}'
'Cert is Valid Till: 2023-10-28'
>>> cert
CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "753DD6FF20CB1B4510CB4C1EA27DA2EB",
    "Subject Name": {
      "Common Name": "*.google.com"
    },
    "Issuer Name": {
      "Country": "US",
      "State/Province": "California",
      "Organization": "Zscaler Inc.",
      "Organization Unit": "Zscaler Inc.",
      "Common Name": "Zscaler Intermediate Root CA (zscalerthree.net) (t) "
    },
    "Validity": {
      "Not After": "2023-10-28",
      "Not Before": "2023-10-14"
    },
    "Wildcard": true,
    "Signature Algorithm": "SHA256WITHRSA",
    "Key Algorithm": "RSA-2048",
    "Subject Alt Names": [
      "*.google.com",
      "*.appengine.google.com",
      "youtu.be",
      "*.youtube.com",
      ...
    ],
    "Location": "https://www.google.com/",
    "Status": 301
  }
)
>>> cert_hero.set_expired(cert)
>>> cert['Validity']
{'Not After': '2023-10-28', 'Not Before': '2023-10-14', 'Expired': False}

Rationale:

The builtin Python module ssl can be used to retrieve a certificate from a server via getpeercert, but it’ll work only if the certificate of interest can be successfully verified (source).

If, for any reason, verification fails, like, for example, with expired or a self-signed certificate, we’ll get ssl.SSLCertVerificationError instead of the requested info.

We can work around this by asking for the certificate in the binary form:

getpeercert(binary_form=True)

But now we have to convert it, and thus we can use a third party asn1crypto module, instead of the (bulkier) cryptography module.

Additionally, if the host redirects the client to another URL, this info is captured in the Location and Status fields.

Parameters:
  • hostname – Host (or server) to retrieve SSL Certificate for

  • context – (Optional) Shared SSL Context

  • user_agent – A custom user agent to use for the HTTP call to retrieve Location and Status. Defaults to python-requests/{version}, or a random user agent if the fake_useragent module is installed (via the fake-ua extra).

  • default_encoding – Encoding used to decode bytes for the HTTP call to retrieve Location and Status. Defaults to latin-1 (or ISO-8859-1).

cert_hero.cert_hero.certs_please(hostnames: list[str] | tuple[str] | set[str], context: SSLContext = None, num_threads: int = 25, user_agent: str | None = None) dict[str, CertHero][source]

Retrieve (concurrently) the SSL certificate(s) for a list of hostnames - works even in the case of expired or self-signed certificates.

Usage:

>>> import cert_hero, json
>>> host_to_cert = cert_hero.certs_please(['google.com', 'cnn.com', 'www.yahoo.co.in', 'youtu.be'])
>>> cert_hero.set_expired(host_to_cert)
>>> host_to_cert
{'google.com': CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "753DD6FF20CB1B4510CB4C1EA27DA2EB",
    ...
  }
), 'cnn.com': CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "7F2F3E5C350554D71A6784CCFE6E8315",
    ...
  }
), ...
}
>>> json.dumps(host_to_cert)
{"google.com": {"Cert Status": "SUCCESS", ...}, "cnn.com": {"Cert Status": "SUCCESS", ...}, ...}
Parameters:
  • hostnames – List of hosts to retrieve SSL Certificate(s) for

  • context – (Optional) Shared SSL Context

  • num_threads – Max number of concurrent threads

  • user_agent – A custom user agent to use for the HTTP call to retrieve Location and Status. Defaults to python-requests/{version}, or a random user agent if the fake_useragent module is installed (via the fake-ua extra).

Returns:

A mapping of hostname to the SSL Certificate (e.g. CertHero) for that host

cert_hero.cert_hero.create_ssl_context() SSLContext[source]
cert_hero.cert_hero.get_user_agent() str[source]

Return a random user agent using the fake_useragent module.

cert_hero.cert_hero.set_expired(certs: ~cert_hero.cert_hero.CertHero | dict[str, str | int | dict[str, str | bool]] | dict[str, ~cert_hero.cert_hero.CertHero] | dict[str, dict[str, str | int | dict[str, str | bool]]] | ~typing.Iterable[~cert_hero.cert_hero.CertHero] | ~typing.Iterable[dict[str, str | int | dict[str, str | bool]]] | None, _date_from_iso_str=<built-in method fromisoformat of type object>) None[source]

Set or update the value for Validity > Expired (:type:`bool`) on each cert in a response from cert_please() or certs_please(), or a serialized version thereof (e.g. json.dumps > json.loads).

Example Usage:

>>> from cert_hero import cert_please, set_expired
>>> cert = cert_please('google.com')
>>> assert 'Expired' not in cert['Validity']
>>> set_expired(cert)
>>> assert 'Expired' in cert['Validity']

cert_hero.cli module

Console script for cert_hero.

cert_hero.cli.main()[source]

Console script for cert_hero.

Module contents

Cert Hero

Python Stand-alone Library to Download the SSL Certificate for Any Host™

Sample Usage:

>>> import cert_hero
>>> cert = cert_hero.cert_please('google.com')
>>> cert.not_after_date
datetime.date(2023, 10, 28)
>>> f'Cert is Valid Till: {cert.not_after_date.isoformat()}'
'Cert is Valid Till: 2023-10-28'
>>> cert
CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "753DD6FF20CB1B4510CB4C1EA27DA2EB",
    "Subject Name": {
      "Common Name": "*.google.com"
    },
    "Issuer Name": {
      "Country": "US",
      "State/Province": "California",
      "Organization": "Zscaler Inc.",
      "Organization Unit": "Zscaler Inc.",
      "Common Name": "Zscaler Intermediate Root CA (zscalerthree.net) (t) "
    },
    "Validity": {
      "Not After": "2023-10-28",
      "Not Before": "2023-10-14"
    },
    "Wildcard": true,
    "Signature Algorithm": "SHA256WITHRSA",
    "Key Algorithm": "RSA-2048",
    "Subject Alt Names": [
      "*.google.com",
      "*.appengine.google.com",
      "youtu.be",
      "*.youtube.com",
      ...
    ],
    "Location": "https://www.google.com/",
    "Status": 301
  }
)
>>> cert_hero.set_expired(cert)
>>> cert['Validity']
{'Not After': '2023-10-28', 'Not Before': '2023-10-14', 'Expired': False}

For full documentation and more advanced usage, please see <https://cert-hero.readthedocs.io>.

copyright:
  1. 2023 by Ritvik Nag.

:license:MIT, see LICENSE for more details.

class cert_hero.CertHero[source]

Bases: dict

CertHero represents the (resolved) SSL certificate of a server or hostname; it subclasses from builtin dict, so it is essentially the same as a dict object with convenience methods and a more human-readable __repr__() method, for example.

This means that a CertHero object is inherently JSON serializable:

>>> import cert_hero, json
>>> cert = cert_hero.CertHero({'key': 'value'})
>>> cert
CertHero(
  {
    "key": "value"
  }
)
>>> cert['key']
'value'
>>> json.dumps(cert)  # or, easier: str(cert)
'{"key": "value"}'
classmethod from_dict(o: dict, _from_iso_format=<built-in method fromisoformat of type object>)[source]

Convert a serialized dict to a CertHero object.

property not_after_date: date

The Cert Not After Date (e.g. Valid Until)

property not_before_date: date

The Cert Not Before Date (e.g. Valid From)

cert_hero.cert_please(hostname: str, context: SSLContext = None, user_agent: str | None = None, default_encoding='latin-1') CertHero[str, str | int | dict[str, str | bool]] | None[source]

Retrieve the SSL certificate for a given hostname - works even in the case of expired or self-signed certificates.

Usage:

>>> import cert_hero
>>> cert = cert_hero.cert_please('google.com')
>>> cert.not_after_date
datetime.date(2023, 10, 28)
>>> f'Cert is Valid Till: {cert.not_after_date.isoformat()}'
'Cert is Valid Till: 2023-10-28'
>>> cert
CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "753DD6FF20CB1B4510CB4C1EA27DA2EB",
    "Subject Name": {
      "Common Name": "*.google.com"
    },
    "Issuer Name": {
      "Country": "US",
      "State/Province": "California",
      "Organization": "Zscaler Inc.",
      "Organization Unit": "Zscaler Inc.",
      "Common Name": "Zscaler Intermediate Root CA (zscalerthree.net) (t) "
    },
    "Validity": {
      "Not After": "2023-10-28",
      "Not Before": "2023-10-14"
    },
    "Wildcard": true,
    "Signature Algorithm": "SHA256WITHRSA",
    "Key Algorithm": "RSA-2048",
    "Subject Alt Names": [
      "*.google.com",
      "*.appengine.google.com",
      "youtu.be",
      "*.youtube.com",
      ...
    ],
    "Location": "https://www.google.com/",
    "Status": 301
  }
)
>>> cert_hero.set_expired(cert)
>>> cert['Validity']
{'Not After': '2023-10-28', 'Not Before': '2023-10-14', 'Expired': False}

Rationale:

The builtin Python module ssl can be used to retrieve a certificate from a server via getpeercert, but it’ll work only if the certificate of interest can be successfully verified (source).

If, for any reason, verification fails, like, for example, with expired or a self-signed certificate, we’ll get ssl.SSLCertVerificationError instead of the requested info.

We can work around this by asking for the certificate in the binary form:

getpeercert(binary_form=True)

But now we have to convert it, and thus we can use a third party asn1crypto module, instead of the (bulkier) cryptography module.

Additionally, if the host redirects the client to another URL, this info is captured in the Location and Status fields.

Parameters:
  • hostname – Host (or server) to retrieve SSL Certificate for

  • context – (Optional) Shared SSL Context

  • user_agent – A custom user agent to use for the HTTP call to retrieve Location and Status. Defaults to python-requests/{version}, or a random user agent if the fake_useragent module is installed (via the fake-ua extra).

  • default_encoding – Encoding used to decode bytes for the HTTP call to retrieve Location and Status. Defaults to latin-1 (or ISO-8859-1).

cert_hero.certs_please(hostnames: list[str] | tuple[str] | set[str], context: SSLContext = None, num_threads: int = 25, user_agent: str | None = None) dict[str, CertHero][source]

Retrieve (concurrently) the SSL certificate(s) for a list of hostnames - works even in the case of expired or self-signed certificates.

Usage:

>>> import cert_hero, json
>>> host_to_cert = cert_hero.certs_please(['google.com', 'cnn.com', 'www.yahoo.co.in', 'youtu.be'])
>>> cert_hero.set_expired(host_to_cert)
>>> host_to_cert
{'google.com': CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "753DD6FF20CB1B4510CB4C1EA27DA2EB",
    ...
  }
), 'cnn.com': CertHero(
  {
    "Cert Status": "SUCCESS",
    "Serial": "7F2F3E5C350554D71A6784CCFE6E8315",
    ...
  }
), ...
}
>>> json.dumps(host_to_cert)
{"google.com": {"Cert Status": "SUCCESS", ...}, "cnn.com": {"Cert Status": "SUCCESS", ...}, ...}
Parameters:
  • hostnames – List of hosts to retrieve SSL Certificate(s) for

  • context – (Optional) Shared SSL Context

  • num_threads – Max number of concurrent threads

  • user_agent – A custom user agent to use for the HTTP call to retrieve Location and Status. Defaults to python-requests/{version}, or a random user agent if the fake_useragent module is installed (via the fake-ua extra).

Returns:

A mapping of hostname to the SSL Certificate (e.g. CertHero) for that host

cert_hero.create_ssl_context() SSLContext[source]
cert_hero.set_expired(certs: ~cert_hero.cert_hero.CertHero | dict[str, str | int | dict[str, str | bool]] | dict[str, ~cert_hero.cert_hero.CertHero] | dict[str, dict[str, str | int | dict[str, str | bool]]] | ~typing.Iterable[~cert_hero.cert_hero.CertHero] | ~typing.Iterable[dict[str, str | int | dict[str, str | bool]]] | None, _date_from_iso_str=<built-in method fromisoformat of type object>) None[source]

Set or update the value for Validity > Expired (:type:`bool`) on each cert in a response from cert_please() or certs_please(), or a serialized version thereof (e.g. json.dumps > json.loads).

Example Usage:

>>> from cert_hero import cert_please, set_expired
>>> cert = cert_please('google.com')
>>> assert 'Expired' not in cert['Validity']
>>> set_expired(cert)
>>> assert 'Expired' in cert['Validity']