Merge branch 'main' of gon:Uni/CSI-ES-2324

This commit is contained in:
Afonso Franco 2024-03-19 23:02:14 +00:00
commit eacfa5363f
Signed by: afonso
SSH key fingerprint: SHA256:aiLbdlPwXKJS5wMnghdtod0SPy8imZjlVvCyUX9DJNk
23 changed files with 875 additions and 49 deletions

View file

@ -1 +0,0 @@
panda é fixe!!!

View file

@ -1 +1 @@
…[n Í"[v©Õ·µ4ZÍ![n{V­ƒ#<23>ÿì<C3BF>» US9ÃhMé„(#c…™b¸ÎÙoe@]<5D>.Jµ?K.Óké<6B>|Da¡vz>Z:’‚¯"]

View file

@ -0,0 +1 @@
panda é fixe!!!

View file

@ -0,0 +1,109 @@
#!/usr/bin/env python3
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
import os
import argparse
def encrypt(input_file, password):
inp = open(input_file,"rb")
out = open(f"{input_file}.enc","wb")
plaintext = inp.read()
print(f"plaintext len : {len(plaintext)}")
# Derive the key from the password using PBKDF2
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000
)
key = kdf.derive(password.encode('utf-8'))
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext)
ciphertext = salt + iv + ciphertext
print(f"plaintext len : {len(plaintext)}")
print(f"ciphertext len : {len(ciphertext)}")
print(f"iv len : {len(iv)}")
out.write(ciphertext)
inp.close()
out.close()
def decrypt(input_file,password):
inp = open(f"{input_file}","rb")
out = open(f"{input_file}.dec","wb")
input_bytes = inp.read()
salt = input_bytes[:16]
iv = input_bytes[16:32]
ciphertext = input_bytes[32:]
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000
)
print(f"plaintext len : {len(ciphertext)}")
print(f"iv len : {len(iv)}")
print(f"salt len : {len(salt)}")
key = kdf.derive(password.encode('utf-8'))
# FIX: block size for aes must be 16 bytes
# plaintext needs padding
cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext)
out.write(plaintext)
inp.close()
out.close()
def main():
parser = argparse.ArgumentParser(
description="Program to perform operations using AES cipher on files",
)
subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
# Encrypt subcommand
enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
enc_parser.add_argument("fich", help="File to be encrypted")
enc_parser.add_argument("password", help="Pass-phrase to derive the key")
# Decrypt subcommand
dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
dec_parser.add_argument("fich", help="File to be decrypted")
dec_parser.add_argument("password", help="Pass-phrase to derive the key")
args = parser.parse_args()
match args.operation:
case "enc":
input_file = args.fich
password = args.password
encrypt(input_file,password)
case "dec":
input_file = args.fich
password = args.password
decrypt(input_file,password)
if __name__ == "__main__":
main()

8
TPs/TP03/Readme.md Normal file
View file

@ -0,0 +1,8 @@
# Questao 1
Ao utilizar o programa 'chacha20_int_attck.py' sobre um criptograma produzido por 'pbenc_chacha20_poly1305', the decrpyt function will raise the execption :
- 'cryptography.exceptions.InvalidTag' If the authentication tag doesnt validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong.
We can try this by encrypting a message and then changing the ciphertext with the attack program:
![Failed Attack](https://github.com/uminho-mei-es/2324-G05/blob/main/TPs/TP03/attack_fail.png)

BIN
TPs/TP03/attack_fail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
import sys
def attack(fctxt, pos, plainAtPos, newPlainAtPos):
f = open(fctxt,"rb")
ciphertext = f.read()
f.close()
plainAtPos = plainAtPos.encode()
newPlainAtPos = newPlainAtPos.encode()
txt_len = len(plainAtPos)
diff = bytes([a ^ b for (a,b) in zip(plainAtPos,newPlainAtPos)])
cipher_diff = bytes([a ^ b for (a,b) in zip(diff,ciphertext[pos:pos+txt_len])])
new_ciphertext = ciphertext[:pos] + cipher_diff + ciphertext[pos+txt_len:]
with open(fctxt+".attck","wb") as f:
f.write(new_ciphertext)
def main():
argv = sys.argv[1:]
argc = len(argv)
if argc < 3 or argc > 5:
sys.exit("Needs 4 arguments <fctxt> <pos> <ptxtAtPos> <newPtxtAtPos>")
attack(argv[0],int(argv[1]),argv[2],argv[3])
if __name__ == "__main__":
main()

View file

@ -0,0 +1,128 @@
#!/usr/bin/env python3
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import hmac
import os
import argparse
def encrypt(input_file, password):
inp = open(input_file,"rb")
out = open(f"{input_file}.enc","wb")
plaintext = inp.read()
print(f"plaintext len : {len(plaintext)}")
# Derive the key from the password using PBKDF2
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=64,
salt=salt,
iterations=100000
)
# FIX: key length must be 32 bytes
derived_key = kdf.derive(password.encode('utf-8'))
key = derived_key[:32]
hmac_key = derived_key[32:]
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext)
ciphertext = salt + iv + ciphertext
print(f"plaintext len : {len(plaintext)}")
print(f"ciphertext len : {len(ciphertext)}")
print(f"iv len : {len(iv)}")
h = hmac.HMAC(hmac_key,hashes.SHA256())
h.update(ciphertext)
tag = h.finalize()
ciphertext = ciphertext + tag
out.write(ciphertext)
inp.close()
out.close()
def decrypt(input_file,password):
inp = open(f"{input_file}","rb")
out = open(f"{input_file}.dec","wb")
input_bytes = inp.read()
salt = input_bytes[:16]
iv = input_bytes[16:32]
ciphertext = input_bytes[32:-32]
tag = input_bytes[-32:]
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=64,
salt=salt,
iterations=100000
)
print(f"plaintext len : {len(ciphertext)}")
print(f"iv len : {len(iv)}")
print(f"salt len : {len(salt)}")
derived_key = kdf.derive(password.encode('utf-8'))
hmac_key = derived_key[32:]
key = derived_key[:32]
# FIX: block size for aes must be 16 bytes
# plaintext needs padding
cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext)
h = hmac.HMAC(hmac_key,hashes.SHA256())
h.update(salt + iv + ciphertext)
if (h.finalize() != tag):
print("Error: HMAC verification failed")
return
out.write(plaintext)
inp.close()
out.close()
def main():
parser = argparse.ArgumentParser(
description="Program to perform operations using AES cipher on files",
)
subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
# Encrypt subcommand
enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
enc_parser.add_argument("fich", help="File to be encrypted")
enc_parser.add_argument("password", help="Pass-phrase to derive the key")
# Decrypt subcommand
dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
dec_parser.add_argument("fich", help="File to be decrypted")
dec_parser.add_argument("password", help="Pass-phrase to derive the key")
args = parser.parse_args()
match args.operation:
case "enc":
input_file = args.fich
password = args.password
encrypt(input_file,password)
case "dec":
input_file = args.fich
password = args.password
decrypt(input_file,password)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,76 @@
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import argparse
def setup(key_file):
key = AESGCM.generate_key(bit_length=128)
with open(key_file, "wb") as f:
f.write(key)
def encrypt(input_file, key_file):
with open(input_file, "rb") as f:
plaintext = f.read()
with open(key_file, "rb") as f:
key = f.read()
aad = b"authenticated but unencrypted data"
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ct = aesgcm.encrypt(nonce, plaintext, aad)
with open(f"{input_file}.enc", "wb") as f:
f.write(nonce)
f.write(ct)
def decrypt(input_file, key_file):
with open(input_file, "rb") as f:
nonce = f.read(12)
ct = f.read()
with open(key_file, "rb") as f:
key = f.read()
aad = b"authenticated but unencrypted data"
aesgcm = AESGCM(key)
pt = aesgcm.decrypt(nonce, ct, aad)
with open(f"{input_file}.dec", "wb") as f:
f.write(pt)
def main():
parser = argparse.ArgumentParser(
description="Program to perform operations using AES-GCM cipher on files",
)
subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
# Encrypt subcommand
enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
enc_parser.add_argument("fich", help="File to be encrypted")
enc_parser.add_argument("password", help="Pass-phrase to derive the key")
# Decrypt subcommand
dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
dec_parser.add_argument("fich", help="File to be decrypted")
dec_parser.add_argument("password", help="Pass-phrase to derive the key")
args = parser.parse_args()
match args.operation:
case "enc":
input_file = args.fich
password = args.password
encrypt(input_file,password)
case "dec":
input_file = args.fich
password = args.password
decrypt(input_file,password)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,93 @@
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import argparse
import os
def setup(key_file):
key = ChaCha20Poly1305.generate_key()
with open(key_file, "wb") as f:
f.write(key)
def encrypt(input_file, key_file):
with open(key_file, "rb") as f:
key = f.read(32)
with open(input_file, "rb") as f:
plaintext = f.read()
aad = b"authenticated but unencrypted data"
nonce = os.urandom(12)
cipher = ChaCha20Poly1305(key)
ciphertext = cipher.encrypt(nonce, plaintext, aad)
with open(f"{input_file}.enc", "wb") as f:
f.write(nonce + ciphertext)
def decrypt(input_file, key_file):
with open(key_file, "rb") as f:
key = f.read(32)
with open(input_file, "rb") as f:
input_bytes = f.read()
aad = b"authenticated but unencrypted data"
nonce = input_bytes[:12]
ciphertext = input_bytes[12:]
cipher = ChaCha20Poly1305(key)
try:
plaintext = cipher.decrypt(nonce, ciphertext, aad)
except Exception as e:
print(f"Could not validate the authentication: {e}")
return
with open(f"{input_file}.dec", "wb") as f:
f.write(plaintext)
def main():
parser = argparse.ArgumentParser(
description="Program to perform operations using Authenticated ChaCha20 cipher on files",
)
subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
# Setup subcommand
setup_parser = subparsers.add_parser("setup", help="Setup a key file")
setup_parser.add_argument("fkey", help="File to contain the appropriate key for the ChaCha20 cipher")
# Encrypt subcommand
enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
enc_parser.add_argument("fich", help="File to be encrypted")
enc_parser.add_argument("fkey", help="File containing the key for the ChaCha20 cipher")
# Decrypt subcommand
dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
dec_parser.add_argument("fich", help="File to be decrypted")
dec_parser.add_argument("fkey", help="File containing the key for the ChaCha20 cipher")
args = parser.parse_args()
match args.operation:
case "setup":
key_file = args.fkey
setup(key_file)
case "enc":
input_file = args.fich
key_file = args.fkey
encrypt(input_file,key_file)
case "dec":
input_file = args.fich
key_file = args.fkey
decrypt(input_file,key_file)
case "help":
parser.print_help()
if __name__ == "__main__":
main()

View file

@ -0,0 +1 @@
not very safe

Binary file not shown.

View file

@ -0,0 +1 @@
not very safe

View file

@ -1,11 +1,11 @@
import argparse import argparse
import os import os
import cryptography.hazmat.primitives.serialization as serialization
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dh from cryptography.hazmat.primitives.asymmetric import dh
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.hkdf import HKDF
import cryptography.hazmat.primitives.serialization as serialization
from cryptography.hazmat.primitives.serialization import ( from cryptography.hazmat.primitives.serialization import (
Encoding, Encoding,
NoEncryption, NoEncryption,
@ -13,106 +13,125 @@ from cryptography.hazmat.primitives.serialization import (
PublicFormat, PublicFormat,
) )
p = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF
g = 2
params = dh.DHParameterNumbers(p, g).parameters()
def setup(user): def setup(user):
parameters = dh.generate_parameters(generator=2, key_size=512) private_key = params.generate_private_key()
private_key = parameters.generate_private_key()
public_key = private_key.public_key() public_key = private_key.public_key()
skey = open(f"{user}.sk", 'wb') skey = open(f"{user}.sk", "wb")
pkey = open(f"{user}.pk", 'wb') pkey = open(f"{user}.pk", "wb")
skey.write(private_key.private_bytes(encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=NoEncryption())) skey.write(
pkey.write(public_key.public_bytes(encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo)) private_key.private_bytes(
encoding=Encoding.PEM,
format=PrivateFormat.PKCS8,
encryption_algorithm=NoEncryption(),
)
)
pkey.write(
public_key.public_bytes(
encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo
)
)
skey.close() skey.close()
pkey.close() pkey.close()
def encrypt(user, filename): def encrypt(user, filename):
peer_pkey_file = open(f"{user}.pk", 'rb') peer_pkey_file = open(f"{user}.pk", "rb")
if peer_pkey_file is None: if peer_pkey_file is None:
print(f"No public key found for {user}") print(f"No public key found for {user}")
return return
file_r = open(filename, 'rb') file_r = open(filename, "rb")
if file_r is None: if file_r is None:
print(f"File {filename} not found") print(f"File {filename} not found")
return return
# FIX: Add try
parameters = dh.generate_parameters(generator=2, key_size=512) skey = params.generate_private_key()
skey = parameters.generate_private_key()
peer_pkey = serialization.load_pem_public_key(peer_pkey_file.read()) peer_pkey = serialization.load_pem_public_key(peer_pkey_file.read())
derived_key = None if not isinstance(peer_pkey, dh.DHPublicKey):
if isinstance(peer_pkey, dh.DHPublicKey):
shared_key = skey.exchange(peer_pkey)
derived_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b'handshake data').derive(shared_key)
else:
print("Error: Invalid key type") print("Error: Invalid key type")
return return
shared_key = skey.exchange(peer_pkey)
derived_key = HKDF(
algorithm=hashes.SHA256(), length=32, salt=None, info=None
).derive(shared_key)
message = file_r.read() message = file_r.read()
chacha = ChaCha20Poly1305(derived_key) chacha = ChaCha20Poly1305(derived_key)
nonce = os.urandom(12) nonce = os.urandom(12)
ciphertext = chacha.encrypt(nonce, message, None) ciphertext = chacha.encrypt(nonce, message, None)
file_w = open(f"{filename}.enc", 'wb') file_w = open(f"{filename}.enc", "wb")
nonce_ciphertext = mkpair(nonce, ciphertext) nonce_ciphertext = nonce + ciphertext
to_send = mkpair(peer_pkey.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo), nonce_ciphertext) pkey_bytes = skey.public_key().public_bytes(
Encoding.DER, PublicFormat.SubjectPublicKeyInfo
)
to_send = mkpair(pkey_bytes, nonce_ciphertext)
file_w.write(to_send) file_w.write(to_send)
file_r.close() file_r.close()
file_w.close() file_w.close()
skey_file.close()
peer_pkey_file.close() peer_pkey_file.close()
def decrypt(user, filename): def decrypt(user, filename):
skey_file = open(f"{user}.sk", 'rb') skey_file = open(f"{user}.sk", "rb")
if skey_file is None: if skey_file is None:
print(f"No private key found for {user}") print(f"No private key found for {user}")
return return
file_r = open(filename, 'rb') file_r = open(filename, "rb")
if file_r is None: if file_r is None:
print(f"File {filename} not found") print(f"File {filename} not found")
return return
message_bytes = file_r.read() message_bytes = file_r.read()
peer_pkey_bytes, ciphertext = unpair(message_bytes) peer_pkey_bytes, ciphertext = unpair(message_bytes)
# FIX: Add try
skey = serialization.load_pem_private_key(skey_file.read(), None) skey = serialization.load_pem_private_key(skey_file.read(), None)
peer_pkey = serialization.load_pem_public_key(peer_pkey_bytes) peer_pkey = serialization.load_der_public_key(peer_pkey_bytes)
derived_key = None if not isinstance(skey, dh.DHPrivateKey) or not isinstance(
if isinstance(skey, dh.DHPrivateKey) and isinstance(peer_pkey, dh.DHPublicKey): peer_pkey, dh.DHPublicKey
shared_key = skey.exchange(peer_pkey) ):
derived_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b'handshake data').derive(shared_key)
else:
print("Error: Invalid key type") print("Error: Invalid key type")
return return
nonce, encrypted_message = unpair(ciphertext) shared_key = skey.exchange(peer_pkey)
derived_key = HKDF(
algorithm=hashes.SHA256(), length=32, salt=None, info=None
).derive(shared_key)
nonce = ciphertext[:12]
encrypted_message = ciphertext[12:]
chacha = ChaCha20Poly1305(derived_key) chacha = ChaCha20Poly1305(derived_key)
message = chacha.decrypt(nonce, encrypted_message, None) message = chacha.decrypt(nonce, encrypted_message, None)
file_w = open(f"{filename}.decrypt", 'wb') file_w = open(f"{filename}.dec", "wb")
file_w.write(message) file_w.write(message)
file_r.close() file_r.close()
file_w.close() file_w.close()
skey_file.close() skey_file.close()
def mkpair(x, y): def mkpair(x, y):
"""produz uma byte-string contendo o tuplo '(x,y)' ('x' e 'y' são byte-strings)""" """produz uma byte-string contendo o tuplo '(x,y)' ('x' e 'y' são byte-strings)"""
len_x = len(x) len_x = len(x)
len_x_bytes = len_x.to_bytes(2, 'little') len_x_bytes = len_x.to_bytes(2, "little")
return len_x_bytes + x + y return len_x_bytes + x + y
def unpair(xy): def unpair(xy):
"""extrai componentes de um par codificado com 'mkpair'""" """extrai componentes de um par codificado com 'mkpair'"""
len_x = int.from_bytes(xy[:2], 'little') len_x = int.from_bytes(xy[:2], "little")
x = xy[2 : len_x + 2] x = xy[2 : len_x + 2]
y = xy[len_x + 2 :] y = xy[len_x + 2 :]
return x, y return x, y
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Program to perform operations using ChaCha20 cipher on files", description="Program to perform operations using ChaCha20 cipher on files",
@ -121,7 +140,9 @@ def main():
subparsers = parser.add_subparsers(dest="operation", help="Operation to perform") subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
# Setup subcommand # Setup subcommand
setup_parser = subparsers.add_parser("setup", help="Setup diffie helman key pair for user") setup_parser = subparsers.add_parser(
"setup", help="Setup diffie helman key pair for user"
)
setup_parser.add_argument("user", help="User for which to setup the key pair") setup_parser.add_argument("user", help="User for which to setup the key pair")
# Encrypt subcommand # Encrypt subcommand
@ -148,5 +169,6 @@ def main():
file = args.file file = args.file
decrypt(user, file) decrypt(user, file)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

8
TPs/TP04/input Normal file
View file

@ -0,0 +1,8 @@
As armas e os barões assinalados,
Que da ocidental praia Lusitana,
Por mares nunca de antes navegados,
Passaram ainda além da Taprobana,
Em perigos e guerras esforçados,
Mais do que prometia a força humana,
E entre gente remota edificaram
Novo Reino, que tanto sublimaram;

23
TPs/TP05/ALICE.crt Normal file
View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDyzCCArOgAwIBAgIUCPhaX8nmoqG8snFrfNe+saiJ8CswDQYJKoZIhvcNAQEL
BQAwgYoxCzAJBgNVBAYTAlBUMQ4wDAYDVQQIDAVNaW5obzEOMAwGA1UEBwwFQnJh
Z2ExHjAcBgNVBAoMFVVuaXZlcnNpZGFkZSBkbyBNaW5obzENMAsGA1UECwwETVNH
UzEfMB0GA1UEAwwWRW50aWRhZGUgQ2VydGlmaWNhZG9yYTELMAkGA1UEQQwCRUMw
HhcNMjQwMzA4MjEzOTM4WhcNMjQwNjE2MjEzOTM4WjB9MQswCQYDVQQGEwJQVDEO
MAwGA1UECAwFTWluaG8xDjAMBgNVBAcMBUJyYWdhMR4wHAYDVQQKDBVVbml2ZXJz
aWRhZGUgZG8gTWluaG8xDTALBgNVBAsMBE1TR1MxDjAMBgNVBAMMBUFMSUNFMQ8w
DQYDVQRBDAZDTElFTlQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
dLkrGhqif05lmhTWKbRbWRtMpAqTlPs1uNTg4h0eXhvGGpWQBYiQ2lekreTtXB4U
vJ0EjDxcTQ68SodEvoLYlotPtnDlLwP5yAeoRy96lyFwbgWMjWQY/ILCY7CtLYmo
9ZA2ySL/6K1agDTE5Y+eRqO2WKu1kYL1O66zUs3dA+EsbWWGg6/sRvUWE+SuZV7O
OMXKtEszeqbeD0E10O7b+wBx5CvomZb4GYDQMiZkhcP7Gg4pLakgQ4EWvM6Jqmun
4DdMiutKxD5L2Q+MNhkYh8+1CsVg9z/MAd6OheSYyMKQD4M0MLm2EY35g+dHijL0
S+XhRGikr0xI9jSDmFMnAgMBAAGjNTAzMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/
BAQDAgbAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQA8
MHI0Ss76IPWq3fEODzLdfAQ86khHNZZ+KQEnOOb8h5UEVSciwiilISxTHltPmNPX
7YRvBvY3LFfURPQwV0UPL7FErORXMny8epd3W4/D/sL6HOL8n/5uq442jW8rPnaA
wFUcoLXBRxX+sTFXQisqGewpEzUpyf2MGJoneOcB3xQteQv7K0Sp3rop0LmlEV74
/ZP6cMjW4MjxLn71J6y3tJ5FP4rTpbUmefnD1F8YVsuNNb0kj1CUsx5YMIFp4PjQ
sruJXrAGTlqa8YuUwwjX7v6wvMv8Di95TIMgTm7bwonsjO7lmQsO261zGySfa0hF
jMUsOA7r+H44aIyWAeNb
-----END CERTIFICATE-----

30
TPs/TP05/ALICE.key Normal file
View file

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,5614AC7BE85EAABC3789637A31321B7F
HxiSg2g4b4agu05VuCQdKdiAvh7fQaBsPVYvvuGvGa3szsol9RBwdEviaOE9hIWz
vKIJghMVEz6C6fWwHoTv6qegTT0uMK6+Z7XN6Ma7GVABE9JDCc7AvAP8or1T0+NN
xh3iztDQvPu+EfLD0jaZDP2pmz5ymq/ibeHIf8hutrdF9dkeNuwtRhKJ9vLv+S5c
mJ4etKwDK1un2lt3wQpWSYQoqSKBz1B4tJT+NL6Nz0ZVW3nEcjNpUdLhBK+yBusM
s7LwiEoqG/1VxDx8gPweOVHDIeURjaOR91NOSdjwwpwOuYOWdhSkfEa/fGSVol9u
QMLEtNN1SRAWvakAmwhNrPNlSRRcFjn2nvO4AS1dRxZuwZEkYuiWeGONGFD6S4lK
rG1rvwYlI28p9+VKVDi7XsW+HUFJ1tG2qYC5BgAOav3S9HIFhjhvYn3sOoM/0vxt
cSr7r94v88UFhvKJZUSLHZn/yCiSX069yYEdY0grt8ZJRhRpN5G7o2T5ejQzGj8l
U8fyDr89DlPYEQn/2eMgxIPcSghxAQqYF5MHvnZ6/CjNMKgQFc8ymGiq/HXveTE9
91WtuH4NQZYsXanN6ZtUQm3a2h8lJD8x/H8LDQPFtwMfHMQP1WK8FlaiKiT7242Q
Gu98ZwEyRBY1qO9rVYX0nbsXMxLAMefdtZlAgKovl1S5xj5r6LcIvFDsVqN5K98h
IK2aoOG023wYJ4eNkjL4dBJiwZ5+RUg3sj4wkNzidcd0cucioj7mbTl4SWJilMI+
fSXzvJADEI4qyjZbPb3KNGqeX0tvF7bkRHwZk91b1ihvvWsK1Lc6j++aDzOraqxS
yVT5IuBy/kd/L+qps3UipneWuUA9NmqUgtIvp20W5R8G9aAfZFFaK0uVPbOYSLum
WNIfBZDZN0SnuiiVoLCaMkFRzK/5GL7UTKKr5JQxS4KW+1VDrdUc8NqzMBhmFwmX
fcGZNOj7bNkckooFIIEtUVL1r2/wX8t3hRY9M4S+MYuOR6Vr2yuajEJPodFD1Hom
BMQDrkicVUJ9yP6QL/UO7a+QNZ2wdaIjfG6zOZkf0RQ/JxJtCN5jaziV6LNE/SfX
UPeh/EdxVqU9hcdcZw6UsLCrVBpRJCwe5UhVHzrsShFW5DqdNBB0Blfxy/9X3IP4
tyOeLqRj7jgHrVgd11rccOiB7vyydUL8J+ilmMbFDlk8lfJYvDked7OaQ5ca3/q9
djkJfVBzGX4s7leJKg8286FBhceKgWhlyqrmXWe9WuBZNj8M94RvoZOCzrlw7WgF
SFf2kSNmh0yXFtsYlNO5skmJo0pgZc5qDQnkHlfXq+Hf89a62L+igsgiy5F1cShx
lCiFa/fkLUe3/+7kJg9XR35XqbLdFS0OLTMmLyghsL+p1Iab28xvajc2jyb7ftGe
CBDD0KT3FvEYSFiE8o0JVYgiWav5NJOML7kW0fq6/ZUm1j9oXxWBpZx8rlVs1jTK
16vkatrUENs9xaWmDsYvuvBmOnSFFzaCFcZ/mE6ixUUim7qKZWfGGwcTs6e1UmlW
twsdWhQEVdZF9mF3NdXqUGqWOHV5RJCHyx3sGzps+pb3DiIgp1Xu6Y8R1057sGKY
-----END RSA PRIVATE KEY-----

23
TPs/TP05/BOB.crt Normal file
View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDyTCCArGgAwIBAgIUX4OZN6EvuWMWUD7EWgYwomRa2JswDQYJKoZIhvcNAQEL
BQAwgYoxCzAJBgNVBAYTAlBUMQ4wDAYDVQQIDAVNaW5obzEOMAwGA1UEBwwFQnJh
Z2ExHjAcBgNVBAoMFVVuaXZlcnNpZGFkZSBkbyBNaW5obzENMAsGA1UECwwETVNH
UzEfMB0GA1UEAwwWRW50aWRhZGUgQ2VydGlmaWNhZG9yYTELMAkGA1UEQQwCRUMw
HhcNMjQwMzA4MjEzOTM3WhcNMjQwNjE2MjEzOTM3WjB7MQswCQYDVQQGEwJQVDEO
MAwGA1UECAwFTWluaG8xDjAMBgNVBAcMBUJyYWdhMR4wHAYDVQQKDBVVbml2ZXJz
aWRhZGUgZG8gTWluaG8xDTALBgNVBAsMBE1TR1MxDDAKBgNVBAMMA0JPQjEPMA0G
A1UEQQwGU0VSVkVSMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAljc+
6jnrgaXhkNqTTCfi4i55Poxmj2AM3xVxlZwron1oAFFx7Uw1RoqE+bKLk3chZPTu
I6Va4Ch9UF1lHl3YKf7+Z0I68JC/Z4mnZGnTYOZ2b9V5WqIyDWFcQlGhKfEQlPZg
BJYZNlrnmePTJjUkniG3+t3u9rSvm/5copv+/Bp5jb9lXZAHVoYNulVY9Th5aRQZ
8PB2bu+k3bsz/rGtF3TRpuMPu1oGoBUq6OVVWWZ6279DlKwycq+Yxl+0dd9dZD80
X+yTyzTd8pkWwRTEtQ3cy4z+V5NsBlw7JBVOPVNz0FtRrUctZmgvW/S87yrUDOQ5
eBXVsZwrZm6tmtHhVQIDAQABozUwMzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQE
AwIGwDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAay7j
May5ZrUh0O/XC/n/+NpxrxtUOWy+9ioGa/EJcuPRQJbyHTBkF7jhdY5WwgEMe2e/
qJvM9sDfx0EL6fc6S3Dku6LINvsFKGOp3ljYt5JZZGXFiNshCEXjJ5l061jWWenn
bAo2ef8OiO0LnfNqAZLqpRD0nI+AdEdqU/O5e792IMzPVNqDVxccYGvHron63WzB
/32gqBBby5gU0USTXffwCLlauv51wKAXCwkp7uGvU0adKp2vL9rHo5OY67N4FzaA
2a3/SMX5YMy0fvMB5Ao8mR2Y+jmQ8Lwb1xIMBx43NOc8exZtfPoHWH6WMKNyYx4i
ydtkrMURnOOdXcBpAQ==
-----END CERTIFICATE-----

30
TPs/TP05/BOB.key Normal file
View file

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,003D12E5CDCB087CCE0B3C7E89D2D651
lQirD96D/XjbbJOlZUzBQDqSmKeKICwrawMHddX6t34N414dcGuLaFPVDdvW5Aml
W6gI43ovOLJQzF+thtHRflANehPE7ObHM+oRJn7+yyzHOv7zvFlm+8GBqRSb6H8m
iVoupPPlFjcUfiy/6DNdRWU3iL2iIGFSwhbboZANrXMTBT+p7GGoaRTxdtwTd3hd
kj2U73ehg9TeGSH/ftXr0dOGKIBZLXeQlIpiPTy86yn93fZ28E3EmjgOCWJXblTa
Plv6xuLY/Y8vZUjjhoJto9IzeA5to4aI30gW90JkxaTnedk9ZL1skjMUKHCrl2Mi
loKU0AI7wJrVlEl7ea3xZ52U+5Qe/KmkZL8GLo5MsB9Fc0jbpRhbsLrrocRW8qgy
KYROLFb5l0s3N2WUN3zBmEO8vjm47mbZblGpPbHGpnVEvuqppNwBoWtUCiq9iptU
0BWWcQ68QUdl0YDp+Mutl3/28ONJo9UYtBFo55PLTNG8tp9qz0Bh/i2hqoCIFoqD
k0rVkGVaXU54IqQLZdF6yhWsKlUyehX8hy1SgukLzgpzwfHkW9DqlztKrmlcXPYl
bHCAaQhimwufRrqSD7nq2Pn0Mq2qtdp6rn+jS9n6Spls1OPNZx4ev5zxga3v1NfA
nIdXeevmP5VZlkIlZmkcoeMMPBKl1f4hzDL14kdA5kTBRbjasJQce4K6bqrueOxP
qpsTGE9KF64NcD/8a2MQhnnpv7TTIFKy1eLrdrojhHUPnDWlVGJ0ihGpvn2AjmJ9
YIFkxXhnegwbUYzabWFXk5fuX8Z2rXPXzpaKGfDTrEvpw0katlMZ/k9cfRJa0obb
W0cwUwkgv7kH8DDRpnU2arNLtkQMviZKhNkVudWh7dD9wQwFLu46yyrocz9qbCHF
vRA0IU5wwrHs0VxETEGm9qnfGJLHbSYaj57nybX2SI2tpwyqvdWEzCIuEvJ155Yc
8u4HijBHI2ysEnoUycDJhZSfW/nl+R892Ppf5t29YVIgdM0yUV2nVA2D+e/buDnR
B6ZKACBmNQIQIx9RsF3Q+EQipxUE82at48wWbTVl5iWbWJUbWK+Yoj9M1NDQZZjO
7ytGzg9WkmuG/6HxuKJJl+0/zdn3+9BvgBGBqjEXNHTaG7OSMrIiwOCFmWzFKguh
YLkh26+JuedrRmx1KoubR1q+gc+VDBD8owQaFz00O8virrqFC00zrC0Q0NtiEvCs
24qkFHsvlvEwOwVAS0xL/rORmZ2GoW7hTVYKth6iOfLjJo9Df/UxNrdqFmmjtI3J
G67ao4w93wiJnRwmgPyuvo0i5UKJqxkcwVkEzVuvYlLhgXj9IXp55atFNQ0h8NLr
aoaHPGX/B/Fj7qisIa3925yXHeLBRDtUFsp8koY/GmY2BPuIrc+utwuJykb02puW
+Z+FcWNeCJFxvGJgH1mVUGpcbbO4VwpuT2PoxBIeTca5ujfwJyPEBiXVgB8rfKxV
OjzrHdGIlz3aaLX1zGmiffyEQdWI1RHTk9VwF2K6Yxl+/ARiFqju89vzHXRX7B6s
1CqApD0x5dHmuyqywAX3KE7qyvgQKabL4K33OcLyr1G5i6V0dLKfbseQWzVImpK9
-----END RSA PRIVATE KEY-----

23
TPs/TP05/EC.crt Normal file
View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID3DCCAsSgAwIBAgIUTH2fOhbGuLcGi6Dy3NlUYspUr/IwDQYJKoZIhvcNAQEL
BQAwgYoxCzAJBgNVBAYTAlBUMQ4wDAYDVQQIDAVNaW5obzEOMAwGA1UEBwwFQnJh
Z2ExHjAcBgNVBAoMFVVuaXZlcnNpZGFkZSBkbyBNaW5obzENMAsGA1UECwwETVNH
UzEfMB0GA1UEAwwWRW50aWRhZGUgQ2VydGlmaWNhZG9yYTELMAkGA1UEQQwCRUMw
HhcNMjQwMzA4MjEzOTMxWhcNMjQwNjE2MjEzOTMxWjCBijELMAkGA1UEBhMCUFQx
DjAMBgNVBAgMBU1pbmhvMQ4wDAYDVQQHDAVCcmFnYTEeMBwGA1UECgwVVW5pdmVy
c2lkYWRlIGRvIE1pbmhvMQ0wCwYDVQQLDARNU0dTMR8wHQYDVQQDDBZFbnRpZGFk
ZSBDZXJ0aWZpY2Fkb3JhMQswCQYDVQRBDAJFQzCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKL4muNmXblWtpkGx7/Yfd9MnF++dwVlw835qnzCftMpVtAd
QvyTryrGGNElGQmUNhihnz9zAHQPMFaUt4M3Xx8Wfilts1jzjEpqA3dikWQNkwC8
ao8JA6v3n8rX0mWFA4Ggee6LpoiAu9YksAr8VYqSxH/cXg5SYybraxwW/GaFkfp9
brMM7X1rGNeTsWpPpHNUTuMDqdBsaTRw9LAfAg8bDB3JKaIuX78MxH2C2UrCuf0s
WyoEl6mJrDHXqxJ+mFntTLizHzEN3M8AZ/vrz/14avfcuih4i4LxFw/MlvTbWlLm
+gmgOBKmsx7JJ/O9VHg3AsBvG2XtmVc13XA3h4cCAwEAAaM4MDYwDwYDVR0TAQH/
BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ
KoZIhvcNAQELBQADggEBAJCvNfm9zA8Dnk+j5qfzrMTbSbKyNSUR4Ic02Ji+Ew1h
BsydcPMS62+yRH01I+AVLO1QX1dSJUoifQtHDbIOC2HkVaYq7mOyKPWVYkqRQT5u
cwON88wpXqKq50ngT+0+yQMGNdT5+ON2DsL/I70O9WJ/ZE8bNPs+uYkZISvui+FW
n0+iuuXIvB7wtdlVAB1RGvrkvi71UfXUPQXDX92rQj63oyrXuTtEDvsjWidMzPoN
7H5VkAY4t9LTWJhosuNvKZbeHKNiZdUUiz53zTACc6WTN8k7nUyW/m1SgsYwAt1m
AgfXmmTQqOeGFTkt5K3ElmCs3XD2ZMUnRf7w4jwb+ms=
-----END CERTIFICATE-----

BIN
TPs/TP05/Images/Q1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

56
TPs/TP05/Readme.md Normal file
View file

@ -0,0 +1,56 @@
# Respostas das Questões
## Q1
Para verificar que as chaves fornecidas constituem um par RSA valido, podemos extrair a chave publica do certificado e da chave privada (separadamente) e verificar se a chave publica é igual à chave publica extraída da chave privada.
![Verificação da chave publica](https://github.com/uminho-mei-es/2324-G05/blob/main/TPs/TP05/Images/Q1.png)
## Q2
Utilizando o comando `openssl x509 -text -noout -in xxx.cert` podemos visualizar o conteúdo do certificado. Correndo o comando para o certificado `ALICE.crt` obtemos o seguinte resultado:
```bash
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
08:f8:5a:5f:c9:e6:a2:a1:bc:b2:71:6b:7c:d7:be:b1:a8:89:f0:2b
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = PT, ST = Minho, L = Braga, O = Universidade do Minho, OU = MSGS, CN = Entidade Certificadora, pseudonym = EC
Validity
Not Before: Mar 8 21:39:38 2024 GMT
Not After : Jun 16 21:39:38 2024 GMT
Subject: C = PT, ST = Minho, L = Braga, O = Universidade do Minho, OU = MSGS, CN = ALICE, pseudonym = CLIENT
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
...
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Non Repudiation
X509v3 Extended Key Usage:
TLS Web Client Authentication
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
...
```
Neste certificado, os campos importantes incluem:
- **Serial Number**: Identificador único do certificado.
- **Validity**: Indica o período durante o qual o certificado é válido.
- **Subject**: Informações sobre a entidade a quem o certificado é emitido.
- **Subject Public Key Info**: Detalhes sobre a chave pública, incluindo o algoritmo e o tamanho.
- **X509v3 extensions**: Contém informações adicionais, como restrições básicas, uso de chaves e uso de chaves estendido.
- **Signature Algorithm**: Algoritmo usado para assinar o certificado.
- **Signature Value**: O valor de assinatura real que garante a integridade do certificado.
## Q3
Para provocar erros do tipo de validação do certificado, podemos alterar o certificado de várias maneiras, como:
- Alterar a data de validade para uma data anterior à data atual.
- Alterar o nome do emissor ou do sujeito.
- Alterar a chave pública ou privada.
Para provocar erros na validação da assinatura, podemos alterar a assinatura do certificado, o que invalidará a assinatura e resultará em um erro de validação.

165
TPs/TP05/sig_fich.py Normal file
View file

@ -0,0 +1,165 @@
import os
import sys
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.exceptions import InvalidSignature
import argparse
import datetime
def mkpair(x, y):
""" produz uma byte-string contendo o tuplo '(x,y)' ('x' e 'y' são byte-strings) """
len_x = len(x)
len_x_bytes = len_x.to_bytes(2, 'little')
return len_x_bytes + x + y
def unpair(xy):
""" extrai componentes de um par codificado com 'mkpair' """
len_x = int.from_bytes(xy[:2], 'little')
x = xy[2:len_x+2]
y = xy[len_x+2:]
return x, y
def cert_load(fname):
""" lê certificado de ficheiro """
with open(fname, "rb") as fcert:
cert = x509.load_pem_x509_certificate(fcert.read())
return cert
def cert_validtime(cert, now=None):
""" valida que 'now' se encontra no período
de validade do certificado. """
if now is None:
now = datetime.datetime.now(tz=datetime.timezone.utc)
if now < cert.not_valid_before_utc or now > cert.not_valid_after_utc:
raise x509.verification.VerificationError("Certificate is not valid at this time")
def cert_validsubject(cert, attrs=[]):
""" verifica atributos do campo 'subject'. 'attrs'
é uma lista de pares '(attr,value)' que condiciona
os valores de 'attr' a 'value'. """
print(cert.subject)
for attr in attrs:
if cert.subject.get_attributes_for_oid(attr[0])[0].value != attr[1]:
raise x509.verification.VerificationError("Certificate subject does not match expected value")
def cert_validexts(cert, policy=[]):
""" valida extensões do certificado. 'policy' é uma lista de pares '(ext,pred)' onde 'ext' é o OID de uma extensão e 'pred'
o predicado responsável por verificar o conteúdo dessa extensão. """
for check in policy:
ext = cert.extensions.get_extension_for_oid(check[0]).value
if not check[1](ext):
raise x509.verification.VerificationError("Certificate extensions does not match expected value")
def valida_cert(cert, ca_cert, user):
try:
ca_cert.public_key().verify(
cert.signature,
cert.tbs_certificate_bytes,
padding.PKCS1v15(),
cert.signature_hash_algorithm
)
except InvalidSignature:
raise x509.verification.VerificationError("Certificate signature is invalid")
cert_validtime(cert)
cert_validsubject(cert, [(NameOID.COMMON_NAME, user)])
"""
sign <user> <fich> -- em que assina o conteúdo de <fich> usando a chave privada armazenada em <user>.key.
Deve produzir o ficheiro <fich>.sig contendo o par composto pela assinatura e certificado do assinante;
"""
def sign(user, filename):
with open(user + ".key", "rb") as key_file:
private_key = serialization.load_pem_private_key(key_file.read(), password=b'1234', backend=default_backend())
user_cert = cert_load(user + ".crt")
with open(filename, "rb") as file:
data_to_sign = file.read()
signature = private_key.sign(data_to_sign,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256())
signature_and_cert = mkpair(signature, user_cert.public_bytes(serialization.Encoding.PEM))
with open(filename + ".sig", "wb") as sig_file:
sig_file.write(signature_and_cert)
"""
verify <fich>-- verifica a assinatura contida em <fich>.sig usando a
informação do signatário contida no certificado (também incluído em <fich>.sig).
Deve apresentar o status de validade da assinatura (Válida/Inválida) e,
no caso de ser válida, ainda os dados do signatário.
"""
def verify(filename, user):
with open(filename + ".sig", "rb") as sig_file:
signature_and_cert = sig_file.read()
signature, cert_bytes = unpair(signature_and_cert)
cert = x509.load_pem_x509_certificate(cert_bytes, default_backend())
ca_cert = cert_load("EC.crt")
try:
valida_cert(cert, ca_cert, user)
with open(filename, "rb") as file:
data_to_verify = file.read()
cert.public_key().verify(signature, data_to_verify,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256())
print("Valid signature")
print(cert.subject)
except x509.verification.VerificationError as e:
print("Invalid signature: " + str(e))
return
def main():
parser = argparse.ArgumentParser(description="Sign or verify files using X.509 certificates.")
parser.add_argument("command", choices=["sign", "verify"], help="Command to execute: 'sign' or 'verify'")
parser.add_argument("user", help="Username")
parser.add_argument("filename", help="File name")
parser.add_argument("--test", action="store_true", help="Perform testing (simulate errors)")
args = parser.parse_args()
match args.command:
case "sign" if os.path.exists(args.user + ".crt") and os.path.exists(args.user + ".key"):
sign(args.user, args.filename)
print("File signed successfully.")
case "sign":
print("User certificate or private key not found.")
case "verify" if os.path.exists(args.filename + ".sig"):
verify(args.filename, args.user)
case "verify":
print("Signature file not found.")
case _:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()