Projekt

Obecné

Profil

Akce

Knowledge-Base

Main source of general X.509 info: https://gist.github.com/Soarez/9688998
OCSP and certificate revocation using raw openssl: https://bhashineen.medium.com/create-your-own-ocsp-server-ffb212df8e63

X.509 certificates

What is a certificate (authority)?

Certificate = a structure belonging to a subject containing his ID and public key. It is signed (= encrypted) by an issuer. Everyone trusts issuer, so by using issuer's public key to decrypt the certificate, one obtains subject's public key without worrying about MITM attacks. Now, whenever Alice sends data to Bob, Alice encrypts data using her private key and sends her certificate, Bob decrypts certificate using CA's public key (obtaining Alice's public key and ensuring himself Alice is actually Alice), and uses Alice's public key to decrypt Alice's data.

Certificate authority (=CA) = an entity capable of generating certificates. Must:
  1. be trusted by all communicating parties
  2. be able to fill in certificate fields according to subject's needs
  3. be able to set itself as an issuer of that certificate
  4. be able to encrypt the certificate by its own private key
  5. allow every communicating party to acquire the CA's own public key (for decryption of certificates)

Root CA = A CA which generates its own certificate for signing "from scratch"; that is, it signs its own certificate (= self-signed certificate).

Intermediate CA = A CA which uses another CA to sign its certificate.

End-entity certificate = A certificate unable to sign other certificates. Cannot be used by CA's (as opposed to the above cases), but can be used for other purposes, such as client validation, document signature, etc.

Structure (version 3)

Person structure

  • Country code [C] (2 capital characters, "EN", "CZ", etc.)
  • Province [ST] (string; province/region, like "Pilsen Region")
  • Locality [L] (string; city/town/municipality, e.g. "Pilsen")
  • Organization [O] (string)
  • Organizational unit [OU] (string; section name, department of org., etc.)
  • Common name [CN] (string; a person's identifier)
  • Email address [emailAddress] (string; that person's contact info, ideally -- an email address)

Certificate structure

  • Data
    • Version (1, 2, or 3, corresponding respectively to the RFC over which it is designed: 1422, 2459, or 5280)
    • Serial number (uniquely identifies certificates issued by a single CA)
    • Signature algorithm (how was the certificate signed, like "sha256 with RSA")
    • Issuer (person) (certificate authority, equal to subject if self-signed)
      • Person structure
    • Validity (time interval)
      • Not before (beginning of closed interval of validity)
      • Not after (end of closed interval of validity)
    • Subject (who is the certificate issued to)
      • Person structure
    • Subject public key info (certificates care only that the cipher is asymmetric)
      • Public key algorithm: (what the actual asymmetric implementation is)
        • algorithm name
        • key definition
    • X509v3 extensions
      • constraints (bool whether certificate subject is CA)
      • identifiers (alternative to serial numbers) of authority and subject
      • key usage (how the key will be used)
        • for CA -> signing certificates & CRLs
        • for end certificates -> ...
  • Signature algorithm (again; identical as above, helps prevent substitution attacks)

OpenSSL

  • crypto library
  • Apache (free for commertial purposes)
  • CLI (awkward calls)
    os.system("openssl ...")
    subprocess.run("openssl ...", input=bytes(...))
    
  • very fast - proud enough to include benchmarks in command set. Declares certificates and certificate authorities very quickly (basic setup including end certificate takes 100-200 ms)

Example usage (key generation):

def make_private_key(name, passphrase):
    subprocess.run(["openssl", "genrsa", "-des3", "-out", name + ".key", "2048"],
                   input=bytes(f'{passphrase}\n{passphrase}\n', encoding='utf-8'))

The code is not very readable, but can be formatted in the following way to add readability:

subprocess.run(["openssl",
                    "genrsa",               # generate a private key for RSA encryption scheme
                    "-des3",                # use DES3 for encryption by passphrase
                    "-out", f"{name}.key",  # output specification
                    "2048"],                # bits

                   input=bytes(             # input required to interact with openssl's CLI
                       f'{passphrase}\n'    # openssl queries for passphrase, respond and return
                       f'{passphrase}\n',   # openssl queries for passphrase verification, respond and return
                       encoding='utf-8')    # use standard encoding for input stream
                   )

  • vast amount of customization & config files, while pretty straightforward and easy to use if special features and customization deemed unnecessary
  • contains bugs that users must handle (e.g. entering an incorrect password causes the program to fail checking of future passwords regardless of their correctness)
  • overall handling of "return values" must be parsed from openssl's stdout, which can be a nuisance
  • however, we can safely estimate the upper bound for the amount of unique openssl commands we will have to call to be 10 (CA creation, certificate sign/revoke, key creation, crl, ocsp, ...),
    which have predictable output formats
  • huge documentation at our disposal
  • implicitly defaults certain variables to smart values, e.g. allows keys to be generated randomly without explicitly specifying details about exponents in RSA

cryptography (Python lib)

  • crypto library
  • low-level OpenSSL utility
  • very well documented
  • tutorials
  • contains useful pre-implemented classes, like Certificate and CertificateIssuer, but these will have to be extended in order to be linked together in a tree structure
  • however, it seems to have been designed with ease-of-use and OOP in mind as opposed to performance; overly complex design patterns like Builder, performs a multitude of actions and function calls before actually calling the backend (= openssl); this causes the same result achieved by openssl to arrive about 2x slower
  • if raw openssl calls are formatted in the way specified above, it uses cca the same amount of lines of code while being more readable, but larger

This tutorial code is perhaps slightly more readable than raw openssl calls.

key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)
with open("cert/key.pem", "wb") as f:
    f.write(key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.TraditionalOpenSSL,
        encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"),
    ))

  • depends on cffi, which depends on pycparser -> three new libraries are required to be installed on the target machine

Aktualizováno uživatelem Michal Seják před více než 3 roky(ů) · 3 revizí