This shows you the differences between two versions of the page.
isc:labs:09 [2024/11/30 18:40] dimitrie.valu |
isc:labs:09 [2024/12/02 22:07] (current) dimitrie.valu |
||
---|---|---|---|
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 ==== |
- | For this section, open a text file and copy-paste/type into it the required information. You will use it both to cross-compare information and to provide a proof of solving. | + | 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 154: | 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 173: | 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 182: | 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 199: | 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 225: | 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 238: | 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 248: | 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> | ||
+ | |||
+ | 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 275: | Line 431: | ||
<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 284: | Line 451: | ||
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? | ||