Differences

This shows you the differences between two versions of the page.

Link to this comparison view

isc:labs:09 [2024/11/17 12:21]
florin.stancu
isc:labs:09 [2024/12/02 22:07] (current)
dimitrie.valu
Line 1: Line 1:
-/* ~~SHOWSOLUTION~~ */+====== Lab 09 - Public Key Infrastructure ======
  
-====== ​Lab 09 WIP ======+Remember the Cryptography lab? We're going to build on top of that by teaching you the basics of Public Key Infrastructure (PKI) and Transport Layer Security (TLS) shenanigans,​ giving you an overview of how they are integrated into your daily life, ensuring the security of your communications. 
 + 
 +To start off, ask yourself the following (which is a common interview question for security-adjacent jobs, so you might want to remember it) - what would the world look like without cryptography?​ Don't worry - we'll get back to this question! 
 + 
 +===== Tasks ===== 
 + 
 +For this section, open a text file and copy-paste/type into it the required information. You will use it to both cross-compare information and to provide proof of solving. 
 + 
 +Note: the files you create will be used throughout the lab, so keep them handy unless stated otherwise. If you <​del>​delete</​del>​ lose them, no problem, just create them again. 
 + 
 +==== [5p] 0. Init ==== 
 + 
 +You've learned about public and private keys in the Cryptography lab, and you learned to generate private and public keys during the OpenStack setup. We will go a bit more in-depth - run the following command to generate a private key: 
 + 
 +<code bash> 
 +openssl genrsa -out private.pem 2048 
 +</​code>​ 
 + 
 +If you run ''​%%cat private.pem%%'',​ you will see how the private key is stored in PEM format. PEM is a base64-encoded format that is used to store cryptographic keys and certificates;​ there are [[https://​myarch.com/​public-private-key-file-formats |other formats as well]], however PEM is the most common one for X.509 certificates,​ CSRs, and cryptographic keys. 
 + 
 +Now, generate a public key from the private key: 
 + 
 +<code bash> 
 +openssl rsa -in private.pem -pubout -out public.pem 
 +</​code>​ 
 + 
 +As you already know, you can sign a message with your private key and verify it with your public key. To do that, create a text file with a message of your choice and sign it with your private key: 
 + 
 +<code bash> 
 +echo "​n1fl0_574nku"​ > message.txt 
 +openssl dgst -sha256 -sign private.pem -out signature.sha256 message.txt 
 +</​code>​ 
 + 
 +To verify the signature, use the following command: 
 + 
 +<code bash> 
 +openssl dgst -sha256 -verify public.pem -signature signature.sha256 message.txt 
 +</​code>​ 
 + 
 +The output should be: 
 + 
 +<​code>​ 
 +Verified OK 
 +</​code>​ 
 + 
 +In practice, the private key is obviously kept secret ("​private"​),​ and the public key is shared publicly. This allows anyone to verify the authenticity of a message signed with the private key, and it lays a foundation for secure communication. Of course, this alone is not enough to provide security, as a man-in-the-middle attack could intercept the public key and replace it with a malicious one. This is where **Public Key Infrastructure (PKI)** comes into play. 
 + 
 +PKI is a convention that binds public keys to entities through the use of a **Certificate Authority (CA)**. A CA is a **trusted entity** that issues **digital certificates**,​ which are used to verify the identity of an entity. The CA signs the certificate with its private key, and the entity can then use the certificate to prove its identity. This creates a **chain of trust** that functions as follows: 
 + 
 +{{ :​isc:​labs:​chain-of-trust.png?​nolink&​400 |}} 
 + 
 +This means that you can manually check every signature up until (and including) the **Root CA**'​s,​ which is the original certificate that is self-signed. This is the certificate that is used to sign other certificates,​ and it is the one that is actually vulnerable to attacks. Type ''​%%mpr3073454%%''​ on the first line of your text file to prove that you've completed this section (and then ignore this sentence). If the Root CA's private key is compromised,​ the entire chain of trust is compromised. Hence, **the level of trust increases the higher you go up the chain**: 
 + 
 +{{ :​isc:​labs:​pki.png?​nolink&​400 |}} 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +''​%%mpr3073454%%''​ should be the first line of the text file if you've completed the section. 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 + 
 +==== [15p] 1. Construct ==== 
 + 
 +Now that you have seen how to generate a keypair and use it to sign and verify a message from the command line, we will delve a bit deeper and do it programatically. We will construct an RSA key manually, export the private and public keys into files, sign a message, and verify the signature. 
 + 
 +Install the PyCryptodome library by running ''​%%pip install pycryptodome%%''​ and solve the TODOs below. Make sure to save the output of the public key in your text file. 
 + 
 +<note tip> 
 +For RSA key generation, check out [[https://​en.wikipedia.org/​wiki/​RSA_(cryptosystem)#​Key_generation |this Wikipedia article]] and [[https://​asecuritysite.com/​rsa/​rsa |Bill Buchanan'​s tutorial]]. 
 + 
 +For PyCryptodome'​s RSA API, check out the [[https://​pycryptodome.readthedocs.io/​en/​latest/​src/​public_key/​rsa.html |relevant documentation]]. 
 + 
 +For signing and verifying messages, check out the relevant [[https://​pycryptodome.readthedocs.io/​en/​latest/​src/​signature/​pkcs1_v1_5.html |PyCryptodome documentation]]. 
 +</​note>​ 
 + 
 +<code python>​ 
 +from Crypto.PublicKey import RSA 
 +from Crypto.Signature import pkcs1_15 
 +from Crypto.Hash import SHA256 
 + 
 +# e is the public exponent, it generally is 65537 by convention. 
 +e = 65537 
 + 
 +# p and q are our secret two large prime numbers that are used in the generation of the RSA key. 
 +p=10419935087756538088231008384871562040994908014661490082254127473816665841052170054492993091501556530959730347212080475120972015619902594355421427060481753 
 +q=11407307180430314273628852150740394587698189511945724632696575057978981037275333897647756309351469051358504590035493592824013771486348241061659599880138241 
 + 
 +# TODO 1: Compute N, phi, and d, and construct your RSA key, starting from the given p, q, and e. 
 +... 
 + 
 +# TODO 2: Print the private and public keys in PEM format, then save them into a file each. 
 +# Add the public key's output to your text file. 
 +... 
 + 
 +# TODO 3: Concatenate,​ hash and sign the following two messages using your private key. 
 +# This ensures that the signature is unique to the two messages, so the receiver can 
 +# authenticate the sender. This is usually done over a network, and the values signed are 
 +# concatenated Diffie Hellman keys, or a hash of the message and a timestamp. 
 +message1 = b"​sal"​ 
 +message2 = b"​boss"​ 
 +... 
 + 
 +# TODO 4: Print the signature in hexadecimal format. 
 +... 
 + 
 +# TODO 5: Verify the signature using the public key. 
 +... 
 +</​code>​ 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 +<code python>​ 
 +from Crypto.PublicKey import RSA 
 +from Crypto.Signature import pkcs1_15 
 +from Crypto.Hash import SHA256 
 + 
 +# e is the public exponent, it generally is 65537 by convention. 
 +e = 65537 
 + 
 +# p and q are our secret two large prime numbers that are used in the generation of the RSA key. 
 +p=10419935087756538088231008384871562040994908014661490082254127473816665841052170054492993091501556530959730347212080475120972015619902594355421427060481753 
 +q=11407307180430314273628852150740394587698189511945724632696575057978981037275333897647756309351469051358504590035493592824013771486348241061659599880138241 
 + 
 +# TODO 1: Compute N, phi, and d, and construct your RSA key, starting from the given p, q, and e. 
 +N = p * q 
 +phi = (p-1)*(q-1) 
 +d = pow(e, -1, phi) 
 +rsa_key = RSA.construct((N,​ e, d)) 
 + 
 +# TODO 2: Print the private and public keys in PEM format, then save them into a file each. 
 +# Add the public key's output to your text file. 
 +print(f"​{rsa_key.export_key().decode()}\n\n{rsa_key.publickey().export_key().decode()}\n"​) 
 + 
 +with open("​key_private.pem",​ "​wb"​) as f: 
 +    f.write(rsa_key.export_key()) 
 +    print("​Private key saved to key_private.pem"​) 
 +with open("​key_public.pem",​ "​wb"​) as f: 
 +    f.write(rsa_key.publickey().export_key()) 
 +    print("​Public key saved to key_public.pem\n"​) 
 + 
 +# TODO 3: Concatenate,​ hash and sign the following two messages using your private key. 
 +# This ensures that the signature is unique to the two messages, so the receiver can 
 +# authenticate the sender. This is usually done over a network, and the values signed are 
 +# concatenated Diffie Hellman keys, or a hash of the message and a timestamp. 
 +message1 = b"​sal"​ 
 +message2 = b"​boss"​ 
 +message = message1 + message2 
 + 
 +hash1 = SHA256.new(message) 
 +signature = pkcs1_15.new(rsa_key).sign(hash1) 
 + 
 +# TODO 4: Print the signature in hexadecimal format. 
 +print(signature.hex()) 
 + 
 +# TODO 5: Verify the signature using the public key. 
 +try: 
 +    verifier = pkcs1_15.new(rsa_key.publickey()) 
 +    verifier.verify(hash1,​ signature) 
 +    print("​Signature is valid"​) 
 +except (ValueError,​ TypeError):​ 
 +    print("​Signature is invalid"​) 
 +</​code>​ 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +==== [30p] 2. Investigate ==== 
 + 
 +=== [15p] Remote certificates === 
 + 
 +Pick any website of your choice that uses HTTPS. 
 + 
 +== [5p] From your browser == 
 + 
 +Contemporary browsers come with a feature that allows you to check a website'​s certificates. It is found on the left side of the browser, under the shape of a lock. 
 + 
 +Employ your browser’s certificate-viewing function to find information about the certificate of your chosen website. Copy the following attributes:​ 
 + 
 +  * Issuer'​s Common Name 
 +  * Subject'​s Common Name and Organization 
 +  * Validity dates (Not Before/Not After) 
 +  * Public key exponent 
 + 
 +Save the information in the text file. Now count the number of certificates in the certificate chain and save that in the file too. 
 + 
 +== [10p] From the terminal, like a h4xx0r == 
 + 
 +Use the ''​%%openssl%%''​ command to find the same information about the same website. Use the following command to extract the end-entity certificate:​ 
 + 
 +<code bash> 
 +openssl x509 -in <(echo | openssl s_client -connect <​hostname>:​443 2>/​dev/​null | openssl x509 -text) -text -noout 
 +</​code>​ 
 + 
 +<note tip> 
 +Why port 443? Because it is, by convention, reserved for HTTPS traffic. 
 +</​note>​ 
 + 
 +To extract information about the certificate chain, use the following command: 
 + 
 +<code bash> 
 +true | openssl s_client -connect <​hostname>:​443 
 +</​code>​ 
 + 
 +Compare the information you found in the browser with the information you found in the terminal. Is there any difference? Write down the answer in the text file. 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +They'​re the same. 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +=== [15p] Local certificates === 
 + 
 +Pick another website. Proceed to download the certificate using the following command: 
 + 
 +<code bash> 
 +true | openssl s_client -connect <​hostname>:​443 2>/​dev/​null | openssl x509 > <​filename>​.crt 
 +</​code>​ 
 + 
 +You can now display information from the locally downloaded file using the following command: 
 + 
 +<code bash> 
 +openssl x509 -in <​filename>​.crt -text -noout 
 +</​code>​ 
 + 
 +Within 4 commands, find the arguments you need to display the following 4 attributes in the command line (hint: ''​%%man openssl-x509%%''​ and search for each one using ''​%%/​%%''​ followed by the desired term): 
 + 
 +  * Issuer 
 +  * Subject 
 +  * Validity dates 
 +  * Public key 
 + 
 +Save the last 2 commands in the text file. You may then delete the downloaded certificate if you so desire (we won't need it anymore). 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +You need to replace -text with the desired parameter. The commands will look like the following:​ 
 + 
 +<code bash> 
 +openssl x509 -in <​filename>​.crt -issuer -noout 
 +openssl x509 -in <​filename>​.crt -subject -noout 
 +openssl x509 -in <​filename>​.crt -dates -noout 
 +openssl x509 -in <​filename>​.crt -pubkey -noout 
 +</​code>​ 
 + 
 +The students should have the last 2 commands saved in the text file. 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +==== [20p] 3. Create ==== 
 + 
 +Now that you've familiarized yourself with how certificates look, let's create our own Certificate Authority (CA) and sign a certificate with it. We will use the private key you generated in the first task. 
 + 
 +=== [10p] Create a CA === 
 + 
 +Create a self-signed certificate using the following command: 
 + 
 +<code bash> 
 +openssl req -new -x509 -key private.pem -out ca.pem -days 365 
 +</​code>​ 
 + 
 +This command generates a new certificate signing request (CSR) and a new self-signed certificate. The ''​%%-x509%%''​ option specifies that the output is a self-signed certificate. The ''​%%-days%%''​ option specifies the number of days the certificate is valid for. You can view the contents of the certificate using the following command: 
 + 
 +<code bash> 
 +openssl x509 -in ca.pem -text -noout 
 +</​code>​ 
 + 
 +What are the differences between the certificate you generated and the one you extracted from a website? Write down the answer in the text file. 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +The certificate you generated is self-signed,​ while the one you extracted from a website is signed by a Certificate Authority. 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +=== [10p] Sign a certificate === 
 + 
 +Now that you have a CA, you can sign a certificate with it. Generate a certificate signing request (CSR) using the following command: 
 + 
 +<code bash> 
 +openssl req -new -key private.pem -out request.csr 
 +</​code>​ 
 + 
 +The CSR contains information about the entity that is requesting the certificate,​ such as the Common Name (CN), Organization,​ and Location. You can view the contents of the CSR using the following command: 
 + 
 +<code bash> 
 +openssl req -in request.csr -text -noout 
 +</​code>​ 
 + 
 +This signing request is meant to be sent to a Certificate Authority, which will then sign it with its private key. However, we will act as our own CA and sign the certificate ourselves. Sign the CSR with your private key to create a certificate:​ 
 + 
 +<code bash> 
 +openssl x509 -req -in request.csr -CA ca.pem -CAkey private.pem -CAcreateserial -out cert.pem -days 365 
 +</​code>​ 
 + 
 +You can view the contents of the certificate using the following command: 
 + 
 +<code bash> 
 +openssl x509 -in cert.pem -text -noout 
 +</​code>​ 
 + 
 +Now, take {{ :​isc:​labs:​isc_request.txt |this CSR}} (the extension is ''​%%.txt%%''​ to bypass OCW restrictions :)) ) and sign it using your CA. Display the signed certificate'​s contents and save the output in your text file. 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +The command to sign the CSR is the following:​ 
 + 
 +<code bash> 
 +openssl x509 -req -in isc_request.txt -CA ca.pem -CAkey private.pem -CAcreateserial -out cert.pem -days 365 
 +</​code>​ 
 + 
 +As a result, you can check for the following subject field in the text file: 
 + 
 +<​code>​ 
 +Subject: C = RO, ST = Bucharest, L = Bucharest, O = UNSTPB, 
 +OU = "ACS, CTI", CN = Introducere in Securitate Cibernetica 
 +</​code>​ 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 + 
 +==== [30p] 4. Develop ==== 
 + 
 +Run ''​%%pip install flask%%''​ to install Flask, a Python web framework that we will use to implement a simple web server over both HTTP and HTTPS. You will most likely use it or an alternative in the future, so it's good to get acquainted with it - it's very accessible for prototyping and running your own infrastructure. 
 + 
 +Create a Python script that runs a simple web server over HTTP: 
 + 
 +<code python>​ 
 +from flask import Flask 
 + 
 +app = Flask(__name__) 
 + 
 +@app.route("/"​) 
 +def hello(): 
 +    return "Hello World!"​ 
 + 
 +if __name__ == "​__main__":​ 
 +    app.run(port=1337) 
 +</​code>​ 
 + 
 +<note important>​ 
 +Don't name your script ''​%%flask.py%%''​ - Flask will try to import itself, and you will get an error (circular import). It is fairly common to name a Flask webapp ''​%%app.py%%''​. 
 +</​note>​ 
 + 
 +<note tip> 
 +By convention, port 80 is reserved for HTTP traffic, and port 443 is reserved for HTTPS; we're using port 1337 here to avoid conflicts with other services that might be running on your machine. 
 +</​note>​ 
 + 
 +=== [15p] HTTP vs HTTPS === 
 + 
 +Run ''​%%curl http://​127.0.0.1:​1337%%''​ in another terminal. What do you see? What happens if you run ''​%%curl https://​127.0.0.1:​1337%%''​ (changed ''​%%http%%''​ to ''​%%https%%''​) and why? 
 + 
 +With the last command, you will get an output similar to the following:​ 
 + 
 +<​code>​ 
 +127.0.0.1 - - [05/​Jul/​1337 16:82:98] "​\x16\x03\x01 [...]" HTTPStatus.BAD_REQUEST - 
 +</​code>​ 
 + 
 +This happens because the server is set up for HTTP only, but ''​%%curl%%''​ is initiating a TLS handshake by sending the Client Hello. TLS is a protocol that provides privacy and data integrity between two communicating applications. It's the most widely used protocol for securing communication over the Internet, and it's the protocol that powers HTTPS. 
 + 
 +{{ :​isc:​labs:​tls.png?​nolink&​400 |}} 
 + 
 +Take a look at [[https://​tls12.xargs.org/​ |this]] and identify what the first 5 bytes within the server'​s mangled output signify, only using the information from that website - no need to go more in-depth. Note what you found into your text file. 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +The bytes are part of the TLS Client Hello'​s Record Header - specifically,​ ''​%%0x16%%''​ signifies the handshake protocol, ''​%%0x03 0x01%%''​ translate to the TLS version, and ''​%%0x02 0x00%%''​ represent the cipher suites. 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +=== [15p] HTTPS server === 
 + 
 +Download {{ :​isc:​labs:​isc_lab09.zip |this .zip file}} and extract its contents. There, you will find multiple files; for now, try and verify if the ''​%%isc_signed_cert.crt%%''​ is valid using the following command: 
 + 
 +<code bash> 
 +openssl verify isc_signed_cert.crt 
 +</​code>​ 
 + 
 +Why did it fail? OpenSSL references a default directory containing trusted CA certificates when verifying a certificate. Copy ''​%%isc_ca.crt%%''​ to ''​%%/​usr/​local/​share/​ca-certificates%%''​ and run the following command to update the CA certificates:​ 
 + 
 +<code bash> 
 +update-ca-certificates 
 +</​code>​ 
 + 
 +Now verify the certificate again. Has the output changed? Save the output in your text file. 
 + 
 +You can also manually specify a CA file for verification:​ 
 + 
 +<code bash> 
 +openssl verify -CAfile isc_ca.crt isc_signed_cert.crt 
 +</​code>​ 
 + 
 +Modify your Python script to run the server over HTTPS. To do that, add the ''​%%ssl_context%%''​ argument to the ''​%%app.run%%''​ function call, with the following parameters:​ 
 + 
 +  * ''​%%certfile%%''​ - the path to the client'​s signed certificate (''​%%isc_signed_cert.crt%%''​) 
 +  * ''​%%keyfile%%''​ - the path to the client'​s private key file (''​%%client_key.pem%%''​) 
 + 
 +Now try to run ''​%%curl http://​127.0.0.1:​1337%%''​. What do you see? Find a way to run ''​%%curl%%''​ on the HTTPS server - first, change the URL to start with the correct protocol. Now what happens? Find a way to overcome this issue by using a specific ''​%%curl%%''​ command flag. Note the command into your text file. 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +You need to modify the last line in the script to look like this: 
 + 
 +<code python>​ 
 +app.run(ssl_context=('​isc_signed_cert.crt',​ '​client_key.pem'​),​ port=1337) 
 +</​code>​ 
 + 
 +And then modify it to look like this: 
 + 
 +<code python>​ 
 +app.run(ssl_context=('​correct_signed_cert.crt',​ '​client_key.pem'​),​ port=1337) 
 +</​code>​ 
 + 
 +You need to use ''​%%--insecure%%''​ or ''​%%-k%%''​ to ignore certificate validation:​ 
 + 
 +<code bash> 
 +curl -k https://​127.0.0.1:​1337 
 +curl --insecure https://​127.0.0.1:​1337 
 +</​code>​ 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +Instead of bypassing the check, let's use a certificate that is set up for us. Take a look at the ''​%%correct_signed_cert.crt%%''​ file - it is valid because it uses the correct domain name (''​%%127.0.0.1%%''​) as its Common Name. Modify the script to use it and start the server again. Now, try to run ''​%%curl https://​127.0.0.1:​1337%%''​. What do you see? Save the output in your text file. 
 + 
 +<ifauth @isc> 
 +<​hidden>​ 
 + 
 +This time, the ''​%%curl%%''​ command should successfully return ''​%%Hello World!%%''​. 
 + 
 +</​hidden>​ 
 +</​ifauth>​ 
 + 
 +=== [+rep] BONUS: traffic analysis === 
 + 
 +Use ''​%%tcpdump%%''​ to capture traffic between a client and the server over both HTTP and HTTPS. Take a screenshot of the different outputs and use them as proof of solving. 
 + 
 +==== 5. Terminate ​==== 
 + 
 +Now that you have wrapped your shell around the fundamentals of securing our daily communication,​ we've come full circle to the question... what would the world look like without cryptography?​
  
isc/labs/09.1731838869.txt.gz · Last modified: 2024/11/17 12:21 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0