Practical: Certificates and a Secure Session, End to End
Chapter Ninety-Eight
Syllabus topic Practical 5, "including certificate management and secure session establishment"
Pages 651 to 656 of 678
In one line
Making a certificate and serving over TLS is easy; the practical's point is that encryption and identity are different things, and a self-signed certificate gives you the first without the second.
Aim
To generate a key pair and a certificate, serve a TLS session with them, then read the certificate back field by field and run the checks a client runs, showing which pass and which does not.
What is needed
- OpenSSL, on any operating system.
- Python 3 for the reading-back program. Nothing else.
- A free port to listen on: 44398 is used here.
Theory in four lines
- A certificate binds a name to a public key, and is signed by an issuer.
- A client trusts a certificate when it can build a chain from it to a root it already trusts, and when the name, the dates and the signatures are all right.
- A self-signed certificate is its own issuer, so the chain ends where it began and there is nothing to trust.
- TLS then uses the key in the certificate to prove that the server holds the matching private key, and to agree session keys. Encryption without identity is still a private conversation with a stranger.
Procedure
- Generate a key pair and a self-signed certificate naming the server.
- Start a TLS server using them.
- Connect with a client, and record the protocol version, the cipher suite and the verification result.
- Read the certificate back: subject, issuer, serial number, validity, the names it covers, the key, the signature algorithm.
- Run the four checks a client runs, and see which fails.
- Record all of it, including the failure, in the journal.
The commands, and what they said
# 1. a key pair and a certificate that names the server, valid for a year
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=exam.college.example/O=Example College/C=IN" \
-addext "subjectAltName=DNS:exam.college.example"
# 2. serve with them
openssl s_server -cert cert.pem -key key.pem -accept 44398 -www
# 3. connect, and read what the client made of it
openssl s_client -connect 127.0.0.1:44398 -servername exam.college.example -showcertsWhat the client reported:
subject=CN=exam.college.example, O=Example College, C=IN
issuer=CN=exam.college.example, O=Example College, C=IN
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Verify return code: 18 (self-signed certificate)Read the last line before celebrating the first three. The session is TLS 1.3 with a strong cipher suite, and the identity behind it has not been established at all.
The program: reading the certificate back
The listing takes the certificate the server presented and reads it from the bytes up: it walks the DER, prints what the certificate says, and runs the checks a client runs, including verifying the signature itself.
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIUXxxy5QtLlUG3jt6WglloPWjkRR8wDQYJKoZIhvcNAQEL
BQAwRjEdMBsGA1UEAwwUZXhhbS5jb2xsZWdlLmV4YW1wbGUxGDAWBgNVBAoMD0V4
YW1wbGUgQ29sbGVnZTELMAkGA1UEBhMCSU4wHhcNMjYwOTMwMTI1MjQzWhcNMjcw
OTMwMTI1MjQzWjBGMR0wGwYDVQQDDBRleGFtLmNvbGxlZ2UuZXhhbXBsZTEYMBYG
A1UECgwPRXhhbXBsZSBDb2xsZWdlMQswCQYDVQQGEwJJTjCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAI7xyrE1VicqjN0ffWJUzYVRsGMxaqI6vfpgJAWE
GaPk/XHD9ibIHt+S+4wHKZIORR7SabGB0SMLcYFnZk+S4ZnjOUkXYovw2F6vWGHx
jQ3Opp7Wcm/XojnF51sXpsp+Blbn2O1o0IDM+sPio8PaEMHPPT0V6uMkbsv7QEc2
doMuftI1Ubru+nLDvPi3rW+ycaNoi7GqAbur5jgb/NzJ7Wzlf3FPxb/q/gAQUpdt
JSE7OxFg1lF8+wnzcEEoHN2JZWL9eOSDRKgdBi+Ze6j+Z82/roc//G8Pb623oVUo
TgS7e+JOqPxyJcaxdn8ssBvkrs3JlD7YVEzX54E1omZwdukCAwEAAaN0MHIwHQYD
VR0OBBYEFEz2vP+p1JSi2yCVkp0rryoV5FPDMB8GA1UdIwQYMBaAFEz2vP+p1JSi
2yCVkp0rryoV5FPDMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0RBBgwFoIUZXhhbS5j
b2xsZWdlLmV4YW1wbGUwDQYJKoZIhvcNAQELBQADggEBAG5mg9UJNWfgDxrVKUYy
FQQ8KNAtmdbCAv/vqHRGNQVUnwUhNVL6w4pDq1DNsD5cZu0RLm801NBpTgKYFbMV
xsqJiqSoZOLzaJQfeA+iVYroxqwlLSEM5nAOKqViH9iS/I9hJAjzcCniZtZfQGLI
0foGbW/d+V7lz8kGubigbbibWs64EnJgoJc2DgjcGsGhszTysGG9l+eZPYWjlFP8
HmEoC8eBIYzbzwTZm/TeoMDw9+0uD5XJrz5PlyETcPgCOa7uziNPxzjSOfIakPmT
WqKs4rVWV541UgDkpvu75IsmjVcoU9yPjPSQMpgAZ1noxbEAoaJ757gahM8Q7tet
2cc=
-----END CERTIFICATE-----Practical: Certificates and a Secure Session, End to End
# Practical 5, exercise 7: read back the certificate the server presented, field by field,
# and run the checks a client runs. Standard library only; the file is the real one, made in
# the laboratory with openssl req, and served by openssl s_server.
import base64, datetime, hashlib
PEM = open('exam-cert.pem').read()
der = base64.b64decode(''.join(l for l in PEM.splitlines() if 'CERTIFICATE' not in l))
# ---- a DER reader: every field is a tag, a length and a value ---------------------------------
def parse(buf, at=0):
"""Return (tag, value bytes, index after this field)."""
tag, length, at = buf[at], buf[at + 1], at + 2
if length & 0x80:
count = length & 0x7F
length = int.from_bytes(buf[at:at + count], 'big')
at += count
return tag, buf[at:at + length], at + length
def items(buf):
at = 0
while at < len(buf):
tag, value, at = parse(buf, at)
yield tag, value
def as_int(value):
return int.from_bytes(value, 'big')
OID_NAMES = {'550403': 'CN', '55040a': 'O', '550406': 'C'}
OID_SAN = '551d11'
OID_SHA256_RSA = '2a864886f70d01010b'
def read_name(value):
"""A Name is a sequence of sets of (OID, string)."""
out = []
for _, rdn in items(value):
for _, pair in items(rdn):
fields = [v for _, v in items(pair)]
key = OID_NAMES.get(fields[0].hex(), fields[0].hex())
out.append('%s=%s' % (key, fields[1].decode('utf-8', 'replace')))
return ', '.join(out)
def read_time(value):
stamp = value.decode()
year = int(stamp[:2])
return datetime.date(year + (2000 if year < 50 else 1900), int(stamp[2:4]), int(stamp[4:6]))
# certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue }
_, cert_body, _ = parse(der)
_, tbs_value, after_tbs = parse(cert_body)
tbs_bytes = cert_body[:after_tbs] # exactly the bytes that were signed
parts = list(items(cert_body))
sig_alg = parts[1][1]
signature = parts[2][1][1:] # BIT STRING, minus the unused-bits byte
fields = list(items(tbs_value))
offset = 1 if fields[0][0] == 0xA0 else 0 # [0] EXPLICIT version, when present
serial = as_int(fields[offset][1])
issuer = read_name(fields[offset + 2][1])
not_before, not_after = [read_time(v) for _, v in items(fields[offset + 3][1])]
subject = read_name(fields[offset + 4][1])
spki = fields[offset + 5][1]
extensions = next((v for t, v in fields if t == 0xA3), b'')
# the public key: SEQUENCE { algorithm, BIT STRING { SEQUENCE { modulus, exponent } } }
key_bits = list(items(spki))[1][1][1:]
modulus, exponent = [as_int(v) for _, v in items(list(items(key_bits))[0][1])]
# subject alternative names: extnValue is an OCTET STRING wrapping GeneralNames
names = []
for _, extension_list in items(extensions): # [3] EXPLICIT wraps a SEQUENCE
for _, extension in items(extension_list):
parsed = [v for _, v in items(extension)]
if parsed[0].hex() != OID_SAN:
continue
_, general_names, _ = parse(parsed[-1]) # extnValue wraps GeneralNames
names += [v.decode() for t, v in items(general_names) if t == 0x82]
print('1. THE FILE')
print(' %d bytes of DER inside the PEM, signed over %d of them' % (len(der), len(tbs_bytes)))
print(' SHA-256 fingerprint %s' % hashlib.sha256(der).hexdigest())
print()
print('2. WHAT IT SAYS')
print(' subject %s' % subject)
print(' issuer %s' % issuer)
print(' serial %d' % serial)
print(' valid %s to %s' % (not_before, not_after))
print(' names %s' % (', '.join(names) or 'none given'))
print(' key RSA, %d bits, exponent %d' % (modulus.bit_length(), exponent))
print(' signed with SHA-256 and RSA: %s'
% (list(items(sig_alg))[0][1].hex() == OID_SHA256_RSA))
# ---- the checks a client makes -----------------------------------------------------------------
SHA256_PREFIX = bytes.fromhex('3031300d060960864801650304020105000420')
def signature_is_intact():
"""Open the signature with the key in this certificate and compare with the block
RFC 8017 says should be there. For a self-signed certificate, the key is its own."""
width = (modulus.bit_length() + 7) // 8
opened = pow(as_int(signature), exponent, modulus).to_bytes(width, 'big')
digest = hashlib.sha256(tbs_bytes).digest()
tail = SHA256_PREFIX + digest
expected = b'\x00\x01' + b'\xff' * (width - len(tail) - 3) + b'\x00' + tail
return opened == expected
TODAY = datetime.date(2026, 9, 30) # the day of the practical
WANTED = 'exam.college.example'
CHECKS = [('the name we asked for is in the certificate', WANTED in names),
('today falls inside the validity period', not_before <= TODAY <= not_after),
('the signature on it is intact', signature_is_intact()),
('the issuer is someone other than the subject', issuer != subject)]
print()
print('3. THE CHECKS A CLIENT MAKES')
for what, passed in CHECKS:
print(' %-46s %s' % (what, 'yes' if passed else 'NO'))
passed = sum(ok for _, ok in CHECKS)
print()
print(' %d of %d pass. The one that fails is the one that matters: this certificate was'
% (passed, len(CHECKS)))
print(' signed by its own key, so it vouches for nobody. openssl s_client said the same:')
print(' "Verify return code: 18 (self-signed certificate)". The session is encrypted;')
print(' the identity behind it is unproved.')Practical: Certificates and a Secure Session, End to End
1. THE FILE
914 bytes of DER inside the PEM, signed over 634 of them
SHA-256 fingerprint c98fa841cbcf2cce86c51165843730ad263de12d49543bf19c41628d161b0757
2. WHAT IT SAYS
subject CN=exam.college.example, O=Example College, C=IN
issuer CN=exam.college.example, O=Example College, C=IN
serial 542988552834095815316831686345382359302521767199
valid 2026-09-30 to 2027-09-30
names exam.college.example
key RSA, 2048 bits, exponent 65537
signed with SHA-256 and RSA: True
3. THE CHECKS A CLIENT MAKES
the name we asked for is in the certificate yes
today falls inside the validity period yes
the signature on it is intact yes
the issuer is someone other than the subject NO
3 of 4 pass. The one that fails is the one that matters: this certificate was
signed by its own key, so it vouches for nobody. openssl s_client said the same:
"Verify return code: 18 (self-signed certificate)". The session is encrypted;
the identity behind it is unproved.What the output proves
A certificate is a structure, not a magic file. 914 bytes, of which 634 are the part that was signed. Every field can be read with a few lines of code: subject, issuer, serial, validity, the names it covers, the public key and the algorithm used to sign it.
Practical: Certificates and a Secure Session, End to End
The signature is real and it checks out. The program opens the signature with the key in the certificate and compares the result with the block RFC 8017 says should be there. It matches, which proves the certificate has not been altered since it was signed.
Three checks pass and the fourth fails. The name is right, the dates are right, the signature is intact, and the issuer is the subject. That last failure is the whole point: nothing outside this file vouches for it. A browser would show a warning, and it would be right to.
OpenSSL agrees, in its own words. "Verify return code: 18 (self-signed certificate)."
How to make the fourth check pass
Two honest ways, and one dishonest one.
- Be your own certification authority for a laboratory: make a CA key and certificate, sign the server certificate with it, and install the CA certificate as trusted on the client machines. The chain then has two links and the client can follow it. This is what an organisation does for its internal systems.
- Get a certificate from a public CA, which will require you to prove control of the name.
- Click through the warning, which is how people are trained to ignore the one check that detects an impostor.
Questions the examiner asks
Why did the client complain although the encryption was strong? Because encryption and authentication are separate. The handshake agreed keys with whoever presented the certificate; it did not establish who that was.
What would an attacker do against a self-signed setup? Present their own self-signed certificate. If users are used to clicking through warnings, nothing distinguishes the attacker from the real server.
Where does the private key go? Nowhere: it stays on the server, readable only by the account that needs it. In this practical it was deleted afterwards and never copied into the journal.
What is the subject alternative name for? It carries the names the certificate is valid for. Modern clients check it and ignore the common name for this purpose, so a certificate without it is rejected by browsers even if the common name matches.
Common mistakes in the laboratory
- Recording the private key in the journal. Never; the key is the secret the whole thing rests on.
- Omitting the subject alternative name and wondering why a browser refuses a certificate whose common name is correct.
- Testing with the same machine's OpenSSL only, and never seeing a browser's warning, which is the more instructive failure.
- Calling the exercise finished when the page loads. The verification result is part of the result.
- Letting the certificate expire mid-semester and concluding that TLS is broken.
Practical: Certificates and a Secure Session, End to End
Quick revision
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodesmakes both.openssl s_server -cert cert.pem -key key.pem -accept PORT -wwwserves;openssl s_client -connect HOST:PORTconnects.- A client checks: name, dates, signature, chain to a trusted root.
- Self-signed means issuer equals subject: the chain has one link and proves nothing.
- Verify return code 18 is "self-signed certificate".
- Encryption without identity is a private conversation with a stranger.
Test yourself
1. What does a certificate contain, and which parts are signed? It contains the version, a serial number, the algorithm used to sign it, the issuer's name, the validity period, the subject's name, the subject's public key, and extensions such as the subject alternative name and basic constraints. All of those together form the TBSCertificate, and it is exactly those bytes that are hashed and signed; the signature algorithm identifier and the signature value are appended outside them, which is why a verifier hashes the TBSCertificate and not the whole file.
2. Why did the TLS session succeed while the certificate check failed? Because the handshake only needs the server to prove it holds the private key matching the certificate it presented, and to agree session keys; that succeeded, so the traffic is encrypted. The certificate check asks a different question, whether the name in the certificate is vouched for by an authority the client already trusts, and a self-signed certificate is signed by its own key, so no chain leads to any trusted root and the client reports return code 18.
3. List the checks a client makes on a server certificate. That the name requested appears in the certificate, normally in the subject alternative name; that the current time lies between the notBefore and notAfter dates; that the signature on the certificate is intact, verified with the issuer's public key; that a chain can be built from the certificate through any intermediates to a root certificate the client already trusts, with each link's signature verified and each intermediate permitted to act as a CA; and that the certificate has not been revoked.
4. How would you make a laboratory setup in which the client accepts the certificate without warnings? By creating a small certification authority: generate a CA key and a self-signed CA certificate, then generate a key and a certificate signing request for the server, sign that request with the CA key, and configure the server with the resulting certificate. The CA certificate is then installed in the trust store of each client machine. The chain from the server certificate to the CA certificate can be followed, the CA is trusted because it was installed deliberately, and the client accepts it.
Practical: Certificates and a Secure Session, End to End
5. What is the danger of teaching students to click through certificate warnings? The warning is the only check that distinguishes the real server from an impostor presenting its own certificate. A user trained to click through it will do so when an attacker is in the middle, so the attacker's certificate is accepted, the session is established with the attacker, and everything sent, including passwords, is read and forwarded. The encryption remains perfect and entirely useless.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.