This shows you the differences between two versions of the page.
|
isc:labs:09 [2024/11/30 19:33] dimitrie.valu Fix redundancy |
isc:labs:09 [2025/12/03 15:27] (current) florin.stancu [[30p] 4. Develop] |
||
|---|---|---|---|
| Line 11: | Line 11: | ||
| 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. | 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. | ||
| - | ==== [20p] 0. Init ==== | + | ==== [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: | 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: | ||
| Line 19: | Line 19: | ||
| </code> | </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. | + | 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: | Now, generate a public key from the private key: | ||
| Line 65: | Line 65: | ||
| - | ==== [30p] 1. Investigate ==== | + | ==== [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 === | === [15p] Remote certificates === | ||
| Line 152: | Line 256: | ||
| </ifauth> | </ifauth> | ||
| - | ==== [20p] 2. Create ==== | + | ==== [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. | 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. | ||
| - | Generate a certificate signing request (CSR) using the following command: | + | === [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> | <code bash> | ||
| Line 171: | Line 301: | ||
| <code bash> | <code bash> | ||
| - | openssl x509 -req -days 365 -in request.csr -signkey private.pem -out cert.pem | + | openssl x509 -req -in request.csr -CA ca.pem -CAkey private.pem -CAcreateserial -out cert.pem -days 365 |
| </code> | </code> | ||
| Line 180: | Line 310: | ||
| </code> | </code> | ||
| - | What are the differences between the CSR and the certificate? Write down the answer in the text file. | + | 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> | <ifauth @isc> | ||
| <hidden> | <hidden> | ||
| - | The CSR contains information about the entity that is requesting the certificate, and the certificate includes that and added bits, such as the following: | + | The command to sign the CSR is the following: |
| <code bash> | <code bash> | ||
| - | Serial Number and Signature Algorithm | + | openssl x509 -req -in isc_request.txt -CA ca.pem -CAkey private.pem -CAcreateserial -out cert.pem -days 365 |
| - | Issuer | + | </code> |
| - | Validity | + | |
| + | 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> | </code> | ||
| Line 197: | Line 332: | ||
| - | ==== [30p] 3. Develop ==== | + | ==== [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. | 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. | ||
| Line 223: | Line 358: | ||
| 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. | 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> | </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? | 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? | ||
| Line 236: | Line 373: | ||
| {{ :isc:labs:tls.png?nolink&400 |}} | {{ :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. | + | 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> | <ifauth @isc> | ||
| Line 246: | Line 383: | ||
| </ifauth> | </ifauth> | ||
| - | Now modify the 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: | + | === [15p] HTTPS server === |
| - | * ''%%certfile%%'' - the path to the certificate file | + | 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: |
| - | * ''%%keyfile%%'' - the path to the private key file | + | |
| - | <ifauth @isc> | + | <code bash> |
| - | <hidden> | + | openssl verify isc_signed_cert.crt |
| + | </code> | ||
| - | You need to modify the last line in the script to look like this: | + | 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 python> | + | <code bash> |
| - | app.run(ssl_context=('cert.pem', 'key.pem'), port=1337) | + | update-ca-certificates |
| </code> | </code> | ||
| - | </hidden> | + | Now verify the certificate again. Has the output changed? Save the output in your text file. |
| - | </ifauth> | + | |
| + | You can also manually specify a CA file for verification: | ||
| - | Now 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. | + | <code bash> |
| + | openssl verify -CAfile isc_ca.crt isc_signed_cert.crt | ||
| + | </code> | ||
| + | |||
| + | Unfortunately, the certificate has expired :( To see when this happened, use: | ||
| + | |||
| + | <code bash> | ||
| + | openssl x509 -in sc_signed_cert.crt -text | ||
| + | </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> | <ifauth @isc> | ||
| <hidden> | <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: | You need to use ''%%--insecure%%'' or ''%%-k%%'' to ignore certificate validation: | ||
| Line 273: | Line 437: | ||
| <code bash> | <code bash> | ||
| curl -k https://127.0.0.1:1337 | curl -k https://127.0.0.1:1337 | ||
| + | curl --insecure https://127.0.0.1:1337 | ||
| </code> | </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> | </hidden> | ||
| Line 282: | Line 457: | ||
| 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. | 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. | ||
| - | ==== 4. Terminate ==== | + | ==== 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? | 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? | ||