lib.tls

  1import hashlib
  2import re
  3import shlex
  4import ssl
  5
  6from SRE.lib_sre import Grade0, NetScheme0
  7
  8
  9def _cert_fingerprint_sha256(pem: str) -> str:
 10    """Return the SHA256 fingerprint of a PEM certificate as XX:XX:... uppercase hex."""
 11    der = ssl.PEM_cert_to_DER_cert(pem.strip())
 12    digest = hashlib.sha256(der).hexdigest()
 13    return ':'.join(digest[i:i + 2].upper() for i in range(0, len(digest), 2))
 14
 15
 16def _val(line, prefix):
 17    m = re.search(rf'{prefix}=(.+)', line)
 18    return m.group(1).strip() if m else ''
 19
 20
 21def _cert_dict(subject_out, issuer_out, dates_out, serial_out, fingerprint_out):
 22    not_before_m = re.search(r'notBefore=(.+)', dates_out)
 23    not_after_m  = re.search(r'notAfter=(.+)',  dates_out)
 24    subject = _val(subject_out, "subject")
 25    cn_m = re.search(r'(?:^|[,/]\s*)CN\s*=\s*([^,/]+)', subject)
 26    return {
 27        "subject":      subject,
 28        "issuer":       _val(issuer_out,      "issuer"),
 29        "common_name":  cn_m.group(1).strip() if cn_m else '',
 30        "not_before":   not_before_m.group(1).strip() if not_before_m else '',
 31        "not_after":    not_after_m.group(1).strip()  if not_after_m  else '',
 32        "serial":       _val(serial_out,      "serial"),
 33        "fingerprint":  _val(fingerprint_out, "SHA256 Fingerprint"),
 34    }
 35
 36
 37def eval_rsa_private_key(grade: Grade0, machine_name: str, key_file: str,
 38                         password: str | None = None, bits: int = 4096,
 39                         cipher: str = "AES-256-CBC",
 40                         step: int = 1) -> bool:
 41    """Check that key_file is an RSA private key with the expected properties.
 42
 43    Verifies:
 44    - The file is an RSA private key (decryptable with password if provided).
 45    - The key size matches bits.
 46    - The encryption cipher in the PEM header matches cipher (case-insensitive).
 47      This applies to traditional PEM format (BEGIN RSA PRIVATE KEY); for
 48      PKCS#8 (BEGIN ENCRYPTED PRIVATE KEY) the cipher check is skipped.
 49      The cipher check is also skipped when password is None.
 50
 51    Args:
 52        grade:        the Grade0 instance.
 53        machine_name: name of the virtual machine to inspect.
 54        key_file:     absolute path to the private key file on the machine.
 55        password:     passphrase protecting the private key, or None if unencrypted.
 56        bits:         expected RSA key size (default: 4096).
 57        cipher:       expected PEM encryption cipher (default: 'AES-256-CBC').
 58        step:         step number passed to grade.test() (default: 1).
 59
 60    Returns:
 61        True if all checks pass, False otherwise.
 62    """
 63    passin = f"-passin {shlex.quote(f'pass:{password}')}" if password is not None else ""
 64    q_key_file = shlex.quote(key_file)
 65    key_text, key_code = grade.test(
 66        machine_name=machine_name,
 67        command=f"openssl rsa -in {q_key_file} {passin} -noout -text 2>&1",
 68        step=step,
 69        allow_error=True,
 70    )
 71    dek_info, _ = grade.test(
 72        machine_name=machine_name,
 73        command=f"grep 'DEK-Info' {q_key_file}",
 74        step=step,
 75        allow_error=True,
 76    )
 77
 78    if key_code != 0:
 79        return False
 80
 81    bits_m = re.search(r'Private-Key:\s*\((\d+)\s*bit', key_text)
 82    if not bits_m or int(bits_m.group(1)) != bits:
 83        return False
 84
 85    # DEK-Info line only present in traditional PEM format; skip cipher check for PKCS#8
 86    if dek_info.strip():
 87        dek_m = re.search(r'DEK-Info:\s*([^,\s]+)', dek_info)
 88        if not dek_m or dek_m.group(1).upper() != cipher.upper():
 89            return False
 90
 91    return True
 92
 93
 94def set_rsa_private_key(net_scheme: NetScheme0, machine_name: str, key_file: str,
 95                        password: str, bits: int = 4096,
 96                        cipher: str = "AES-256-CBC"):
 97    """Generate an RSA private key on machine_name.
 98
 99    Args:
100        net_scheme:   the NetScheme0 instance.
101        machine_name: name of the virtual machine.
102        key_file:     absolute path where the key will be written on the machine.
103        password:     passphrase to protect the key.
104        bits:         RSA key size in bits (default: 4096).
105        cipher:       PEM encryption cipher (default: 'AES-256-CBC').
106    """
107    net_scheme.cmd(machine_name,
108                   f"openssl genrsa -{shlex.quote(cipher.lower())} -passout {shlex.quote(f'pass:{password}')} -out {shlex.quote(key_file)} {bits}")
109
110
111def eval_self_signed_certificate(grade: Grade0, machine_name: str,
112                                 key_file: str, cert_file: str,
113                                 password: str,
114                                 step: int = 1) -> dict | None:
115    """Check that key_file and cert_file are a password-protected TLS key and
116    a matching self-signed certificate on machine_name.
117
118    Verifies:
119    - key_file is a valid PEM private key decryptable with password.
120    - cert_file is a valid PEM certificate whose Issuer equals its Subject
121      (self-signed).
122    - The public key in the certificate matches the private key.
123
124    Args:
125        grade:        the Grade0 instance.
126        machine_name: name of the virtual machine to inspect.
127        key_file:     absolute path to the private key file on the machine.
128        cert_file:    absolute path to the certificate file on the machine.
129        password:     passphrase protecting the private key.
130        step:         step number passed to grade.test() (default: 1).
131
132    Returns:
133        A dict with certificate fields (subject, issuer, not_before, not_after,
134        serial, fingerprint) if all checks pass, None otherwise.
135    """
136    # All grade.test() calls must be made unconditionally so they are registered
137    # in the first (registration) pass and carry real results in the second pass.
138    q_key_file = shlex.quote(key_file)
139    q_cert_file = shlex.quote(cert_file)
140    q_passin = shlex.quote(f'pass:{password}')
141    _, key_code = grade.test(
142        machine_name=machine_name,
143        command=f"openssl pkey -in {q_key_file} -passin {q_passin} -noout",
144        step=step,
145        allow_error=True,
146    )
147    cert_text, cert_code = grade.test(
148        machine_name=machine_name,
149        command=f"openssl x509 -in {q_cert_file} -noout -text -fingerprint -sha256",
150        step=step,
151        allow_error=True,
152    )
153    cert_pubkey, cert_pubkey_code = grade.test(
154        machine_name=machine_name,
155        command=f"openssl x509 -in {q_cert_file} -noout -pubkey",
156        step=step,
157        allow_error=True,
158    )
159    key_pubkey, key_pubkey_code = grade.test(
160        machine_name=machine_name,
161        command=f"openssl pkey -in {q_key_file} -passin {q_passin} -pubout",
162        step=step,
163        allow_error=True,
164    )
165    subject_out, _ = grade.test(
166        machine_name=machine_name,
167        command=f"openssl x509 -in {q_cert_file} -noout -subject",
168        step=step,
169        allow_error=True,
170    )
171    issuer_out, _ = grade.test(
172        machine_name=machine_name,
173        command=f"openssl x509 -in {q_cert_file} -noout -issuer",
174        step=step,
175        allow_error=True,
176    )
177    dates_out, _ = grade.test(
178        machine_name=machine_name,
179        command=f"openssl x509 -in {q_cert_file} -noout -dates",
180        step=step,
181        allow_error=True,
182    )
183    serial_out, _ = grade.test(
184        machine_name=machine_name,
185        command=f"openssl x509 -in {q_cert_file} -noout -serial",
186        step=step,
187        allow_error=True,
188    )
189    fingerprint_out, _ = grade.test(
190        machine_name=machine_name,
191        command=f"openssl x509 -in {q_cert_file} -noout -fingerprint -sha256",
192        step=step,
193        allow_error=True,
194    )
195
196    if key_code != 0 or cert_code != 0 or cert_pubkey_code != 0 or key_pubkey_code != 0:
197        return None
198
199    if cert_pubkey.strip() != key_pubkey.strip():
200        return None
201
202    # Check self-signed: Subject must equal Issuer
203    subject_m = re.search(r'Subject:\s*(.+)', cert_text)
204    issuer_m  = re.search(r'Issuer:\s*(.+)',  cert_text)
205    if not subject_m or not issuer_m:
206        return None
207    if subject_m.group(1).strip() != issuer_m.group(1).strip():
208        return None
209
210    return _cert_dict(subject_out, issuer_out, dates_out, serial_out, fingerprint_out)
211
212
213def eval_certificate(grade: Grade0, machine_name: str,
214                     key_file: str, cert_file: str,
215                     step: int = 1) -> dict | None:
216    """Check that key_file and cert_file are a matching TLS key pair on machine_name.
217
218    Verifies:
219    - cert_file is a valid PEM certificate.
220    - The public key in the certificate matches the private key in key_file.
221
222    Args:
223        grade:        the Grade0 instance.
224        machine_name: name of the virtual machine to inspect.
225        key_file:     absolute path to the private key file on the machine.
226        cert_file:    absolute path to the certificate file on the machine.
227        step:         step number passed to grade.test() (default: 1).
228
229    Returns:
230        A dict with certificate fields (subject, issuer, common_name,
231        not_before, not_after, serial, fingerprint) if all checks pass,
232        None otherwise.
233    """
234    # All grade.test() calls must be made unconditionally so they are registered
235    # in the first (registration) pass and carry real results in the second pass.
236    q_key_file = shlex.quote(key_file)
237    q_cert_file = shlex.quote(cert_file)
238    _, cert_code = grade.test(
239        machine_name=machine_name,
240        command=f"openssl x509 -in {q_cert_file} -noout -text -fingerprint -sha256",
241        step=step,
242        allow_error=True,
243    )
244    cert_pubkey, cert_pubkey_code = grade.test(
245        machine_name=machine_name,
246        command=f"openssl x509 -in {q_cert_file} -noout -pubkey",
247        step=step,
248        allow_error=True,
249    )
250    key_pubkey, key_pubkey_code = grade.test(
251        machine_name=machine_name,
252        command=f"openssl pkey -in {q_key_file} -pubout",
253        step=step,
254        allow_error=True,
255    )
256    subject_out, _ = grade.test(
257        machine_name=machine_name,
258        command=f"openssl x509 -in {q_cert_file} -noout -subject",
259        step=step,
260        allow_error=True,
261    )
262    issuer_out, _ = grade.test(
263        machine_name=machine_name,
264        command=f"openssl x509 -in {q_cert_file} -noout -issuer",
265        step=step,
266        allow_error=True,
267    )
268    dates_out, _ = grade.test(
269        machine_name=machine_name,
270        command=f"openssl x509 -in {q_cert_file} -noout -dates",
271        step=step,
272        allow_error=True,
273    )
274    serial_out, _ = grade.test(
275        machine_name=machine_name,
276        command=f"openssl x509 -in {q_cert_file} -noout -serial",
277        step=step,
278        allow_error=True,
279    )
280    fingerprint_out, _ = grade.test(
281        machine_name=machine_name,
282        command=f"openssl x509 -in {q_cert_file} -noout -fingerprint -sha256",
283        step=step,
284        allow_error=True,
285    )
286
287    if cert_code != 0 or cert_pubkey_code != 0 or key_pubkey_code != 0:
288        return None
289
290    if cert_pubkey.strip() != key_pubkey.strip():
291        return None
292
293    return _cert_dict(subject_out, issuer_out, dates_out, serial_out, fingerprint_out)
294
295
296def eval_certificate_validity(grade: Grade0, machine_name: str,
297                              cert_file: str, ca_cert_file: str,
298                              step: int = 1) -> bool:
299    """Check that cert_file is signed by ca_cert_file on machine_name.
300
301    Args:
302        grade:        the Grade0 instance.
303        machine_name: name of the virtual machine to inspect.
304        cert_file:    absolute path to the certificate file on the machine.
305        ca_cert_file: absolute path to the CA certificate file on the machine.
306        step:         step number passed to grade.test() (default: 1).
307
308    Returns:
309        True if cert_file is a valid certificate signed by ca_cert_file,
310        False otherwise.
311    """
312    _, verify_code = grade.test(
313        machine_name=machine_name,
314        command=f"openssl verify -CAfile {shlex.quote(ca_cert_file)} {shlex.quote(cert_file)}",
315        step=step,
316        allow_error=True,
317    )
318    return verify_code == 0
319
320
321def eval_https_server(grade: Grade0, machine_name: str, url: str,
322                      server_ip: str, cert: str,
323                      server_port: int = 443, step: int = 1) -> bool:
324    """Check that an HTTPS server at server_ip responds correctly and presents
325    the expected certificate.
326
327    Verifies:
328    - A GET request to url (routed to server_ip:server_port) succeeds (2xx).
329    - The certificate presented by the server matches cert.
330
331    Args:
332        grade:        the Grade0 instance.
333        machine_name: name of the virtual machine from which to connect.
334        url:          full URL to request (e.g. https://myserver/index.html).
335        server_ip:    IP address of the HTTPS server to connect to.
336        cert:         PEM certificate content to verify against the server.
337        server_port:  HTTPS port (default: 443).
338        step:         step number passed to grade.test() (default: 1).
339
340    Returns:
341        True if all checks pass, False otherwise.
342    """
343    # All grade.test() calls must be made unconditionally so they are registered
344    # in the first (registration) pass and carry real results in the second pass.
345    _, http_code = grade.test(
346        machine_name=machine_name,
347        command=f"curl -k -L --fail --connect-to ::{server_ip}:{server_port}"
348                f" -s -o /dev/null {url}",
349        step=step,
350        allow_error=True,
351    )
352    server_fp, server_fp_code = grade.test(
353        machine_name=machine_name,
354        command=f"openssl s_client -connect {server_ip}:{server_port}"
355                f" </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256",
356        step=step,
357        allow_error=True,
358    )
359
360    if http_code != 0:
361        return False
362    if server_fp_code != 0:
363        return False
364
365    try:
366        cert_fp_val = _cert_fingerprint_sha256(cert)
367    except Exception:
368        return False
369
370    server_fp_val = _val(server_fp, "SHA256 Fingerprint")
371    return bool(server_fp_val) and server_fp_val == cert_fp_val
def eval_rsa_private_key( grade: SRE.lib_sre.Grade0, machine_name: str, key_file: str, password: str | None = None, bits: int = 4096, cipher: str = 'AES-256-CBC', step: int = 1) -> bool:
38def eval_rsa_private_key(grade: Grade0, machine_name: str, key_file: str,
39                         password: str | None = None, bits: int = 4096,
40                         cipher: str = "AES-256-CBC",
41                         step: int = 1) -> bool:
42    """Check that key_file is an RSA private key with the expected properties.
43
44    Verifies:
45    - The file is an RSA private key (decryptable with password if provided).
46    - The key size matches bits.
47    - The encryption cipher in the PEM header matches cipher (case-insensitive).
48      This applies to traditional PEM format (BEGIN RSA PRIVATE KEY); for
49      PKCS#8 (BEGIN ENCRYPTED PRIVATE KEY) the cipher check is skipped.
50      The cipher check is also skipped when password is None.
51
52    Args:
53        grade:        the Grade0 instance.
54        machine_name: name of the virtual machine to inspect.
55        key_file:     absolute path to the private key file on the machine.
56        password:     passphrase protecting the private key, or None if unencrypted.
57        bits:         expected RSA key size (default: 4096).
58        cipher:       expected PEM encryption cipher (default: 'AES-256-CBC').
59        step:         step number passed to grade.test() (default: 1).
60
61    Returns:
62        True if all checks pass, False otherwise.
63    """
64    passin = f"-passin {shlex.quote(f'pass:{password}')}" if password is not None else ""
65    q_key_file = shlex.quote(key_file)
66    key_text, key_code = grade.test(
67        machine_name=machine_name,
68        command=f"openssl rsa -in {q_key_file} {passin} -noout -text 2>&1",
69        step=step,
70        allow_error=True,
71    )
72    dek_info, _ = grade.test(
73        machine_name=machine_name,
74        command=f"grep 'DEK-Info' {q_key_file}",
75        step=step,
76        allow_error=True,
77    )
78
79    if key_code != 0:
80        return False
81
82    bits_m = re.search(r'Private-Key:\s*\((\d+)\s*bit', key_text)
83    if not bits_m or int(bits_m.group(1)) != bits:
84        return False
85
86    # DEK-Info line only present in traditional PEM format; skip cipher check for PKCS#8
87    if dek_info.strip():
88        dek_m = re.search(r'DEK-Info:\s*([^,\s]+)', dek_info)
89        if not dek_m or dek_m.group(1).upper() != cipher.upper():
90            return False
91
92    return True

Check that key_file is an RSA private key with the expected properties.

Verifies:

  • The file is an RSA private key (decryptable with password if provided).
  • The key size matches bits.
  • The encryption cipher in the PEM header matches cipher (case-insensitive). This applies to traditional PEM format (BEGIN RSA PRIVATE KEY); for PKCS#8 (BEGIN ENCRYPTED PRIVATE KEY) the cipher check is skipped. The cipher check is also skipped when password is None.
Arguments:
  • grade: the Grade0 instance.
  • machine_name: name of the virtual machine to inspect.
  • key_file: absolute path to the private key file on the machine.
  • password: passphrase protecting the private key, or None if unencrypted.
  • bits: expected RSA key size (default: 4096).
  • cipher: expected PEM encryption cipher (default: 'AES-256-CBC').
  • step: step number passed to grade.test() (default: 1).
Returns:

True if all checks pass, False otherwise.

def set_rsa_private_key( net_scheme: SRE.lib_sre.NetScheme0, machine_name: str, key_file: str, password: str, bits: int = 4096, cipher: str = 'AES-256-CBC'):
 95def set_rsa_private_key(net_scheme: NetScheme0, machine_name: str, key_file: str,
 96                        password: str, bits: int = 4096,
 97                        cipher: str = "AES-256-CBC"):
 98    """Generate an RSA private key on machine_name.
 99
100    Args:
101        net_scheme:   the NetScheme0 instance.
102        machine_name: name of the virtual machine.
103        key_file:     absolute path where the key will be written on the machine.
104        password:     passphrase to protect the key.
105        bits:         RSA key size in bits (default: 4096).
106        cipher:       PEM encryption cipher (default: 'AES-256-CBC').
107    """
108    net_scheme.cmd(machine_name,
109                   f"openssl genrsa -{shlex.quote(cipher.lower())} -passout {shlex.quote(f'pass:{password}')} -out {shlex.quote(key_file)} {bits}")

Generate an RSA private key on machine_name.

Arguments:
  • net_scheme: the NetScheme0 instance.
  • machine_name: name of the virtual machine.
  • key_file: absolute path where the key will be written on the machine.
  • password: passphrase to protect the key.
  • bits: RSA key size in bits (default: 4096).
  • cipher: PEM encryption cipher (default: 'AES-256-CBC').
def eval_self_signed_certificate( grade: SRE.lib_sre.Grade0, machine_name: str, key_file: str, cert_file: str, password: str, step: int = 1) -> dict | None:
112def eval_self_signed_certificate(grade: Grade0, machine_name: str,
113                                 key_file: str, cert_file: str,
114                                 password: str,
115                                 step: int = 1) -> dict | None:
116    """Check that key_file and cert_file are a password-protected TLS key and
117    a matching self-signed certificate on machine_name.
118
119    Verifies:
120    - key_file is a valid PEM private key decryptable with password.
121    - cert_file is a valid PEM certificate whose Issuer equals its Subject
122      (self-signed).
123    - The public key in the certificate matches the private key.
124
125    Args:
126        grade:        the Grade0 instance.
127        machine_name: name of the virtual machine to inspect.
128        key_file:     absolute path to the private key file on the machine.
129        cert_file:    absolute path to the certificate file on the machine.
130        password:     passphrase protecting the private key.
131        step:         step number passed to grade.test() (default: 1).
132
133    Returns:
134        A dict with certificate fields (subject, issuer, not_before, not_after,
135        serial, fingerprint) if all checks pass, None otherwise.
136    """
137    # All grade.test() calls must be made unconditionally so they are registered
138    # in the first (registration) pass and carry real results in the second pass.
139    q_key_file = shlex.quote(key_file)
140    q_cert_file = shlex.quote(cert_file)
141    q_passin = shlex.quote(f'pass:{password}')
142    _, key_code = grade.test(
143        machine_name=machine_name,
144        command=f"openssl pkey -in {q_key_file} -passin {q_passin} -noout",
145        step=step,
146        allow_error=True,
147    )
148    cert_text, cert_code = grade.test(
149        machine_name=machine_name,
150        command=f"openssl x509 -in {q_cert_file} -noout -text -fingerprint -sha256",
151        step=step,
152        allow_error=True,
153    )
154    cert_pubkey, cert_pubkey_code = grade.test(
155        machine_name=machine_name,
156        command=f"openssl x509 -in {q_cert_file} -noout -pubkey",
157        step=step,
158        allow_error=True,
159    )
160    key_pubkey, key_pubkey_code = grade.test(
161        machine_name=machine_name,
162        command=f"openssl pkey -in {q_key_file} -passin {q_passin} -pubout",
163        step=step,
164        allow_error=True,
165    )
166    subject_out, _ = grade.test(
167        machine_name=machine_name,
168        command=f"openssl x509 -in {q_cert_file} -noout -subject",
169        step=step,
170        allow_error=True,
171    )
172    issuer_out, _ = grade.test(
173        machine_name=machine_name,
174        command=f"openssl x509 -in {q_cert_file} -noout -issuer",
175        step=step,
176        allow_error=True,
177    )
178    dates_out, _ = grade.test(
179        machine_name=machine_name,
180        command=f"openssl x509 -in {q_cert_file} -noout -dates",
181        step=step,
182        allow_error=True,
183    )
184    serial_out, _ = grade.test(
185        machine_name=machine_name,
186        command=f"openssl x509 -in {q_cert_file} -noout -serial",
187        step=step,
188        allow_error=True,
189    )
190    fingerprint_out, _ = grade.test(
191        machine_name=machine_name,
192        command=f"openssl x509 -in {q_cert_file} -noout -fingerprint -sha256",
193        step=step,
194        allow_error=True,
195    )
196
197    if key_code != 0 or cert_code != 0 or cert_pubkey_code != 0 or key_pubkey_code != 0:
198        return None
199
200    if cert_pubkey.strip() != key_pubkey.strip():
201        return None
202
203    # Check self-signed: Subject must equal Issuer
204    subject_m = re.search(r'Subject:\s*(.+)', cert_text)
205    issuer_m  = re.search(r'Issuer:\s*(.+)',  cert_text)
206    if not subject_m or not issuer_m:
207        return None
208    if subject_m.group(1).strip() != issuer_m.group(1).strip():
209        return None
210
211    return _cert_dict(subject_out, issuer_out, dates_out, serial_out, fingerprint_out)

Check that key_file and cert_file are a password-protected TLS key and a matching self-signed certificate on machine_name.

Verifies:

  • key_file is a valid PEM private key decryptable with password.
  • cert_file is a valid PEM certificate whose Issuer equals its Subject (self-signed).
  • The public key in the certificate matches the private key.
Arguments:
  • grade: the Grade0 instance.
  • machine_name: name of the virtual machine to inspect.
  • key_file: absolute path to the private key file on the machine.
  • cert_file: absolute path to the certificate file on the machine.
  • password: passphrase protecting the private key.
  • step: step number passed to grade.test() (default: 1).
Returns:

A dict with certificate fields (subject, issuer, not_before, not_after, serial, fingerprint) if all checks pass, None otherwise.

def eval_certificate( grade: SRE.lib_sre.Grade0, machine_name: str, key_file: str, cert_file: str, step: int = 1) -> dict | None:
214def eval_certificate(grade: Grade0, machine_name: str,
215                     key_file: str, cert_file: str,
216                     step: int = 1) -> dict | None:
217    """Check that key_file and cert_file are a matching TLS key pair on machine_name.
218
219    Verifies:
220    - cert_file is a valid PEM certificate.
221    - The public key in the certificate matches the private key in key_file.
222
223    Args:
224        grade:        the Grade0 instance.
225        machine_name: name of the virtual machine to inspect.
226        key_file:     absolute path to the private key file on the machine.
227        cert_file:    absolute path to the certificate file on the machine.
228        step:         step number passed to grade.test() (default: 1).
229
230    Returns:
231        A dict with certificate fields (subject, issuer, common_name,
232        not_before, not_after, serial, fingerprint) if all checks pass,
233        None otherwise.
234    """
235    # All grade.test() calls must be made unconditionally so they are registered
236    # in the first (registration) pass and carry real results in the second pass.
237    q_key_file = shlex.quote(key_file)
238    q_cert_file = shlex.quote(cert_file)
239    _, cert_code = grade.test(
240        machine_name=machine_name,
241        command=f"openssl x509 -in {q_cert_file} -noout -text -fingerprint -sha256",
242        step=step,
243        allow_error=True,
244    )
245    cert_pubkey, cert_pubkey_code = grade.test(
246        machine_name=machine_name,
247        command=f"openssl x509 -in {q_cert_file} -noout -pubkey",
248        step=step,
249        allow_error=True,
250    )
251    key_pubkey, key_pubkey_code = grade.test(
252        machine_name=machine_name,
253        command=f"openssl pkey -in {q_key_file} -pubout",
254        step=step,
255        allow_error=True,
256    )
257    subject_out, _ = grade.test(
258        machine_name=machine_name,
259        command=f"openssl x509 -in {q_cert_file} -noout -subject",
260        step=step,
261        allow_error=True,
262    )
263    issuer_out, _ = grade.test(
264        machine_name=machine_name,
265        command=f"openssl x509 -in {q_cert_file} -noout -issuer",
266        step=step,
267        allow_error=True,
268    )
269    dates_out, _ = grade.test(
270        machine_name=machine_name,
271        command=f"openssl x509 -in {q_cert_file} -noout -dates",
272        step=step,
273        allow_error=True,
274    )
275    serial_out, _ = grade.test(
276        machine_name=machine_name,
277        command=f"openssl x509 -in {q_cert_file} -noout -serial",
278        step=step,
279        allow_error=True,
280    )
281    fingerprint_out, _ = grade.test(
282        machine_name=machine_name,
283        command=f"openssl x509 -in {q_cert_file} -noout -fingerprint -sha256",
284        step=step,
285        allow_error=True,
286    )
287
288    if cert_code != 0 or cert_pubkey_code != 0 or key_pubkey_code != 0:
289        return None
290
291    if cert_pubkey.strip() != key_pubkey.strip():
292        return None
293
294    return _cert_dict(subject_out, issuer_out, dates_out, serial_out, fingerprint_out)

Check that key_file and cert_file are a matching TLS key pair on machine_name.

Verifies:

  • cert_file is a valid PEM certificate.
  • The public key in the certificate matches the private key in key_file.
Arguments:
  • grade: the Grade0 instance.
  • machine_name: name of the virtual machine to inspect.
  • key_file: absolute path to the private key file on the machine.
  • cert_file: absolute path to the certificate file on the machine.
  • step: step number passed to grade.test() (default: 1).
Returns:

A dict with certificate fields (subject, issuer, common_name, not_before, not_after, serial, fingerprint) if all checks pass, None otherwise.

def eval_certificate_validity( grade: SRE.lib_sre.Grade0, machine_name: str, cert_file: str, ca_cert_file: str, step: int = 1) -> bool:
297def eval_certificate_validity(grade: Grade0, machine_name: str,
298                              cert_file: str, ca_cert_file: str,
299                              step: int = 1) -> bool:
300    """Check that cert_file is signed by ca_cert_file on machine_name.
301
302    Args:
303        grade:        the Grade0 instance.
304        machine_name: name of the virtual machine to inspect.
305        cert_file:    absolute path to the certificate file on the machine.
306        ca_cert_file: absolute path to the CA certificate file on the machine.
307        step:         step number passed to grade.test() (default: 1).
308
309    Returns:
310        True if cert_file is a valid certificate signed by ca_cert_file,
311        False otherwise.
312    """
313    _, verify_code = grade.test(
314        machine_name=machine_name,
315        command=f"openssl verify -CAfile {shlex.quote(ca_cert_file)} {shlex.quote(cert_file)}",
316        step=step,
317        allow_error=True,
318    )
319    return verify_code == 0

Check that cert_file is signed by ca_cert_file on machine_name.

Arguments:
  • grade: the Grade0 instance.
  • machine_name: name of the virtual machine to inspect.
  • cert_file: absolute path to the certificate file on the machine.
  • ca_cert_file: absolute path to the CA certificate file on the machine.
  • step: step number passed to grade.test() (default: 1).
Returns:

True if cert_file is a valid certificate signed by ca_cert_file, False otherwise.

def eval_https_server( grade: SRE.lib_sre.Grade0, machine_name: str, url: str, server_ip: str, cert: str, server_port: int = 443, step: int = 1) -> bool:
322def eval_https_server(grade: Grade0, machine_name: str, url: str,
323                      server_ip: str, cert: str,
324                      server_port: int = 443, step: int = 1) -> bool:
325    """Check that an HTTPS server at server_ip responds correctly and presents
326    the expected certificate.
327
328    Verifies:
329    - A GET request to url (routed to server_ip:server_port) succeeds (2xx).
330    - The certificate presented by the server matches cert.
331
332    Args:
333        grade:        the Grade0 instance.
334        machine_name: name of the virtual machine from which to connect.
335        url:          full URL to request (e.g. https://myserver/index.html).
336        server_ip:    IP address of the HTTPS server to connect to.
337        cert:         PEM certificate content to verify against the server.
338        server_port:  HTTPS port (default: 443).
339        step:         step number passed to grade.test() (default: 1).
340
341    Returns:
342        True if all checks pass, False otherwise.
343    """
344    # All grade.test() calls must be made unconditionally so they are registered
345    # in the first (registration) pass and carry real results in the second pass.
346    _, http_code = grade.test(
347        machine_name=machine_name,
348        command=f"curl -k -L --fail --connect-to ::{server_ip}:{server_port}"
349                f" -s -o /dev/null {url}",
350        step=step,
351        allow_error=True,
352    )
353    server_fp, server_fp_code = grade.test(
354        machine_name=machine_name,
355        command=f"openssl s_client -connect {server_ip}:{server_port}"
356                f" </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256",
357        step=step,
358        allow_error=True,
359    )
360
361    if http_code != 0:
362        return False
363    if server_fp_code != 0:
364        return False
365
366    try:
367        cert_fp_val = _cert_fingerprint_sha256(cert)
368    except Exception:
369        return False
370
371    server_fp_val = _val(server_fp, "SHA256 Fingerprint")
372    return bool(server_fp_val) and server_fp_val == cert_fp_val

Check that an HTTPS server at server_ip responds correctly and presents the expected certificate.

Verifies:

  • A GET request to url (routed to server_ip:server_port) succeeds (2xx).
  • The certificate presented by the server matches cert.
Arguments:
  • grade: the Grade0 instance.
  • machine_name: name of the virtual machine from which to connect.
  • url: full URL to request (e.g. https://myserver/index.html).
  • server_ip: IP address of the HTTPS server to connect to.
  • cert: PEM certificate content to verify against the server.
  • server_port: HTTPS port (default: 443).
  • step: step number passed to grade.test() (default: 1).
Returns:

True if all checks pass, False otherwise.