From 9e54895af4ede5ebaa28590c76bcb1895cfae99f Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 2 Nov 2023 19:08:05 -0600 Subject: [PATCH 01/23] Add initial README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab5c55b..d2305f6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ -# gongor +# Gongor -Tool for producing identities \ No newline at end of file +Tool for producing identities + +Anagram of "Gorgon". From 2988a08109276bc56d46a9bf233fa06d30dc4b92 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 2 Nov 2023 21:18:57 -0600 Subject: [PATCH 02/23] Add initial package files. With this I can `pip install -e .`. It doesn't _do_ anything, but I can do it. --- LICENSE.txt | 15 +++++++++++ gongor/__init__.py | 0 gongor/gongor.egg-info/PKG-INFO | 29 +++++++++++++++++++++ gongor/gongor.egg-info/SOURCES.txt | 8 ++++++ gongor/gongor.egg-info/dependency_links.txt | 1 + gongor/gongor.egg-info/top_level.txt | 1 + pyproject.toml | 14 ++++++++++ setup.py | 5 ++++ 8 files changed, 73 insertions(+) create mode 100644 LICENSE.txt create mode 100644 gongor/__init__.py create mode 100644 gongor/gongor.egg-info/PKG-INFO create mode 100644 gongor/gongor.egg-info/SOURCES.txt create mode 100644 gongor/gongor.egg-info/dependency_links.txt create mode 100644 gongor/gongor.egg-info/top_level.txt create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8279d42 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,15 @@ +Gongor - A system for managing personal cryptographic identity +Copyright (C) 2023 Eli Ribble + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/gongor/__init__.py b/gongor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gongor/gongor.egg-info/PKG-INFO b/gongor/gongor.egg-info/PKG-INFO new file mode 100644 index 0000000..235e37e --- /dev/null +++ b/gongor/gongor.egg-info/PKG-INFO @@ -0,0 +1,29 @@ +Metadata-Version: 2.1 +Name: gongor +Version: 0.1 +Summary: A system to manage Aegi +License: Gongor - A system for managing personal cryptographic identity + Copyright (C) 2023 Eli Ribble + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE.txt + +# Gongor + +Project for managing an Aegis + +Anagram of "Gorgon". diff --git a/gongor/gongor.egg-info/SOURCES.txt b/gongor/gongor.egg-info/SOURCES.txt new file mode 100644 index 0000000..af9efe6 --- /dev/null +++ b/gongor/gongor.egg-info/SOURCES.txt @@ -0,0 +1,8 @@ +LICENSE.txt +README.md +pyproject.toml +setup.py +gongor/gongor.egg-info/PKG-INFO +gongor/gongor.egg-info/SOURCES.txt +gongor/gongor.egg-info/dependency_links.txt +gongor/gongor.egg-info/top_level.txt \ No newline at end of file diff --git a/gongor/gongor.egg-info/dependency_links.txt b/gongor/gongor.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gongor/gongor.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/gongor/gongor.egg-info/top_level.txt b/gongor/gongor.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gongor/gongor.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b1a197d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "gongor" +version = "0.1" +description = "A system to manage Aegi" +readme = "README.md" +requires-python = ">=3.7" +license = {file = "LICENSE.txt"} + +[build-system] +requires = ["setuptools >= 61.0.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["gongor"] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..11b7a5c --- /dev/null +++ b/setup.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +import setuptools + +if __name__ == "__main__": + setuptools.setup() From 370c2a9e05d4f04037068094b72b2a146eaac52b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 2 Nov 2023 21:26:40 -0600 Subject: [PATCH 03/23] Get a working entry-point done. It doesn't do anything, but we love water flowing through pipes. --- gongor/aegis.py | 3 +++ pyproject.toml | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 gongor/aegis.py diff --git a/gongor/aegis.py b/gongor/aegis.py new file mode 100644 index 0000000..0ffae14 --- /dev/null +++ b/gongor/aegis.py @@ -0,0 +1,3 @@ +def generate(): + print("Generating...") + print("Done.") diff --git a/pyproject.toml b/pyproject.toml index b1a197d..9190ea9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,12 @@ readme = "README.md" requires-python = ">=3.7" license = {file = "LICENSE.txt"} +[project.scripts] +aegis-generate = "gongor.aegis:generate" + [build-system] requires = ["setuptools >= 61.0.0"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] -where = ["gongor"] +include = ["gongor"] From deb5bd01bb5a1d3e34b108c52c34155c01f93400 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 2 Nov 2023 21:29:07 -0600 Subject: [PATCH 04/23] Remove files generated by pip installation. And start ignoring some files for git. --- .gitignore | 1 + gongor/gongor.egg-info/PKG-INFO | 29 --------------------- gongor/gongor.egg-info/SOURCES.txt | 8 ------ gongor/gongor.egg-info/dependency_links.txt | 1 - gongor/gongor.egg-info/top_level.txt | 1 - 5 files changed, 1 insertion(+), 39 deletions(-) create mode 100644 .gitignore delete mode 100644 gongor/gongor.egg-info/PKG-INFO delete mode 100644 gongor/gongor.egg-info/SOURCES.txt delete mode 100644 gongor/gongor.egg-info/dependency_links.txt delete mode 100644 gongor/gongor.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/gongor/gongor.egg-info/PKG-INFO b/gongor/gongor.egg-info/PKG-INFO deleted file mode 100644 index 235e37e..0000000 --- a/gongor/gongor.egg-info/PKG-INFO +++ /dev/null @@ -1,29 +0,0 @@ -Metadata-Version: 2.1 -Name: gongor -Version: 0.1 -Summary: A system to manage Aegi -License: Gongor - A system for managing personal cryptographic identity - Copyright (C) 2023 Eli Ribble - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Requires-Python: >=3.7 -Description-Content-Type: text/markdown -License-File: LICENSE.txt - -# Gongor - -Project for managing an Aegis - -Anagram of "Gorgon". diff --git a/gongor/gongor.egg-info/SOURCES.txt b/gongor/gongor.egg-info/SOURCES.txt deleted file mode 100644 index af9efe6..0000000 --- a/gongor/gongor.egg-info/SOURCES.txt +++ /dev/null @@ -1,8 +0,0 @@ -LICENSE.txt -README.md -pyproject.toml -setup.py -gongor/gongor.egg-info/PKG-INFO -gongor/gongor.egg-info/SOURCES.txt -gongor/gongor.egg-info/dependency_links.txt -gongor/gongor.egg-info/top_level.txt \ No newline at end of file diff --git a/gongor/gongor.egg-info/dependency_links.txt b/gongor/gongor.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/gongor/gongor.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/gongor/gongor.egg-info/top_level.txt b/gongor/gongor.egg-info/top_level.txt deleted file mode 100644 index 8b13789..0000000 --- a/gongor/gongor.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ - From 7a64ec773aafb9a10245cbe7a5b3c4928f284fe0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 3 Nov 2023 16:33:36 -0600 Subject: [PATCH 05/23] Initial steps to generate an aegis. --- README.md | 4 ++++ gongor/aegis.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2305f6..34f9ea0 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,7 @@ Tool for producing identities Anagram of "Gorgon". + +## aegis-generate + +This generates an initial ID, known as an 'aegis'. It uses [step-ca](https://smallstep.com/docs/step-cli/the-step-command/) defaults which puts the data files in `$HOME/.step`. diff --git a/gongor/aegis.py b/gongor/aegis.py index 0ffae14..3703f85 100644 --- a/gongor/aegis.py +++ b/gongor/aegis.py @@ -1,3 +1,13 @@ +import subprocess + def generate(): - print("Generating...") - print("Done.") + print("Please name this aegis. You can call it anything. Frequently people use their legal name.") + name = input("Name? ") + print("Generating aegis.") + subprocess.run([ + "step", "ca", "init", + "--pki", + "--deployment-type=standalone", + "--name", name, + ], + check=True,) From 47c40d0ca2ad84a71778aa578591909787d6c4ea Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 6 Nov 2023 10:23:57 -0700 Subject: [PATCH 06/23] Update aegis generate command to use openssl. I like step-ca and it's defaults, but as far as I can figure from the arguments, configuration, and documentation there is no way to tell it to give me a much longer-lived certificate. 10 years. That's it. It's a reasonable default for a server, but not for a human being. Instead, we'll do 200 years. Revisit this when the Transhumanists take over. --- gongor/aegis.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gongor/aegis.py b/gongor/aegis.py index 3703f85..c571c4d 100644 --- a/gongor/aegis.py +++ b/gongor/aegis.py @@ -1,13 +1,19 @@ import subprocess +MAX_HUMAN_AGE = 365 * 200 def generate(): print("Please name this aegis. You can call it anything. Frequently people use their legal name.") name = input("Name? ") print("Generating aegis.") subprocess.run([ - "step", "ca", "init", - "--pki", - "--deployment-type=standalone", - "--name", name, + "openssl", "req", # PKCS#10 certificate generation utility + "-new", # Generate a new certificate + "-newkey", "ec", # Generate a new private key using elliptic-curve (ECDSA or ECDH compatible) + "-pkeyopt", "ec_paramgen_curve:prime256v1", # Use the prime256v1 CE curve from NIST (P-256) + "-x509", # Create a self-signed certificate instead of a certificate request. + "-days", str(MAX_HUMAN_AGE), # Set the validity period to the expected max age of a human + "-subj", f"/CN={name}", # Add the common name for the persona tied to this aegis + "-out", "cert.pem", # Generate a self-signed certificate file with a .pem extension + "-keyout", "key.pem", # Generate an encrypted private key file with a .pem extension ], check=True,) From 7dcec92bf960a9bb0e870c54ab47749093f81944 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 6 Nov 2023 11:19:42 -0700 Subject: [PATCH 07/23] Add TODO related to aegis cert. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 34f9ea0..24051d3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ Tool for producing identities Anagram of "Gorgon". +## TODO + + * Fix up the aegis generation script to ensure that I either have, or don't need, the extensions from Step CA: + * X509v3 Key Usage: critical + * Certificate Sign, CRL Sign + * X509v3 Basic Constraints: critical + * CA:TRUE, pathlen:1 + ## aegis-generate This generates an initial ID, known as an 'aegis'. It uses [step-ca](https://smallstep.com/docs/step-cli/the-step-command/) defaults which puts the data files in `$HOME/.step`. From d01ff6d7de9ebd40c4f5bbd3af90e4282e76115e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 6 Nov 2023 11:20:40 -0700 Subject: [PATCH 08/23] Add basic functions to sign and validate a signature. This is blessedly simple and I'm assuming working with the very basic tests I've done. --- gongor/aegis.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 4 ++++ 2 files changed, 60 insertions(+) diff --git a/gongor/aegis.py b/gongor/aegis.py index c571c4d..ee8488c 100644 --- a/gongor/aegis.py +++ b/gongor/aegis.py @@ -1,5 +1,12 @@ +import argparse +from pathlib import Path import subprocess +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.serialization import pkcs12 +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding + MAX_HUMAN_AGE = 365 * 200 def generate(): print("Please name this aegis. You can call it anything. Frequently people use their legal name.") @@ -17,3 +24,52 @@ def generate(): "-keyout", "key.pem", # Generate an encrypted private key file with a .pem extension ], check=True,) + +def sign(): + "Sign some arbitrary data." + parser = argparse.ArgumentParser() + parser.add_argument("aegis_key_pem", type=Path, help="The file for the PEM-encoded aegis private key.") + parser.add_argument("message", type=Path, help="The file containing the message to sign.") + parser.add_argument("-p", "--password", help="The password to use to open the key file.") + args = parser.parse_args() + + with open(args.aegis_key_pem, "rb") as f: + private_key = serialization.load_pem_private_key( + data=f.read(), + password=args.password.encode("UTF-8"), + ) + with open(args.message, "rb") as f: + data = f.read() + signature = private_key.sign( + data, + ec.ECDSA(hashes.SHA256()), + ) + with open("signature.bin", "wb") as f: + f.write(signature) + print("Wrote signature to signature.bin") + +def validate(): + "Validate the signature of some arbitrary data." + parser = argparse.ArgumentParser() + parser.add_argument("aegis_cert_pem", type=Path, help="The file for the PEM-encoded aegis public certificate.") + parser.add_argument("message", type=Path, help="The file containing the message to validate.") + parser.add_argument("signature", type=Path, help="The file containing the signature to validate.") + args = parser.parse_args() + + with open(args.aegis_cert_pem, "rb") as f: + certificate = x509.load_pem_x509_certificate( + data=f.read(), + ) + with open(args.message, "rb") as f: + data = f.read() + with open(args.signature, "rb") as f: + signature = f.read() + + key = certificate.public_key() + key.verify( + signature=signature, + data=data, + signature_algorithm=ec.ECDSA(hashes.SHA256()), + ) + print("Signature is valid") + diff --git a/pyproject.toml b/pyproject.toml index 9190ea9..c3b3185 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,10 @@ license = {file = "LICENSE.txt"} [project.scripts] aegis-generate = "gongor.aegis:generate" +aegis-sign = "gongor.aegis:sign" +aegis-validate-signature = "gongor.aegis:validate" +aegis-box = "gongor.aegis:box" +aegis-unbox = "gongor.cipher:unbox" [build-system] requires = ["setuptools >= 61.0.0"] From 0ee61fed3474661dd2111b33c241d6465a7cd3c6 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 6 Nov 2023 11:52:48 -0700 Subject: [PATCH 09/23] Add extremely naive implementations of boxing and unboxing a message. I took the 'box' term from some other project. --- gongor/aegis.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/gongor/aegis.py b/gongor/aegis.py index ee8488c..091f9bd 100644 --- a/gongor/aegis.py +++ b/gongor/aegis.py @@ -1,13 +1,47 @@ import argparse +import os from pathlib import Path import subprocess from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.ciphers import aead from cryptography.hazmat.primitives.serialization import pkcs12 from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding MAX_HUMAN_AGE = 365 * 200 +def box(): + "Take a message, put it in a box, encrypt the contents using a public key." + parser = argparse.ArgumentParser() + parser.add_argument("aegis_cert_pem", type=Path, help="The file for the PEM-encoded aegis public cert.") + parser.add_argument("message", type=Path, help="The file containing the message to box.") + parser.add_argument("output", type=Path, help="The path to the file where the output will go.") + args = parser.parse_args() + + with open(args.aegis_cert_pem, "rb") as f: + certificate = x509.load_pem_x509_certificate( + data=f.read(), + ) + with open(args.message, "rb") as f: + data = f.read() + + aad = b"authenticated but unencrypted data" + key = aead.ChaCha20Poly1305.generate_key() + with open("encryption.key", "wb") as f: + f.write(key) + print("Wrote encryption key to 'encryption.key'") + chacha = aead.ChaCha20Poly1305(key) + nonce = os.urandom(12) + with open("nonce", "wb") as f: + f.write(nonce) + print("Wrote nonce to 'nonce'") + ct = chacha.encrypt(nonce, data, aad) + with open(args.output, "wb") as f: + f.write(ct) + print(f"Wrote encrypted message to '{args.output}'") + + + def generate(): print("Please name this aegis. You can call it anything. Frequently people use their legal name.") name = input("Name? ") @@ -73,3 +107,32 @@ def validate(): ) print("Signature is valid") +def unbox(): + "Take a message in a box, use a key, open the box, get the message." + parser = argparse.ArgumentParser() + parser.add_argument("aegis_key_pem", type=Path, help="The file for the PEM-encoded aegis key.") + parser.add_argument("ciphertext", type=Path, help="The file containing the boxed ciphertext.") + parser.add_argument("output", type=Path, help="The path to the file where the output will go.") + parser.add_argument("-p", "--password", help="The password to use to open the key file.") + args = parser.parse_args() + + with open(args.aegis_key_pem, "rb") as f: + private_key = serialization.load_pem_private_key( + data=f.read(), + password=args.password.encode("UTF-8"), + ) + with open(args.ciphertext, "rb") as f: + ciphertext = f.read() + aad = b"authenticated but unencrypted data" + + with open("encryption.key", "rb") as f: + key = f.read() + with open("nonce", "rb") as f: + nonce = f.read() + + chacha = aead.ChaCha20Poly1305(key) + message = chacha.decrypt(nonce, ciphertext, aad) + with open(args.output, "wb") as f: + f.write(message) + print(f"Wrote unboxed message to '{args.output}'") + diff --git a/pyproject.toml b/pyproject.toml index c3b3185..74c4918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ aegis-generate = "gongor.aegis:generate" aegis-sign = "gongor.aegis:sign" aegis-validate-signature = "gongor.aegis:validate" aegis-box = "gongor.aegis:box" -aegis-unbox = "gongor.cipher:unbox" +aegis-unbox = "gongor.aegis:unbox" [build-system] requires = ["setuptools >= 61.0.0"] From 65c5517935d5f209d0dd55d6c5e13053d76dcb26 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 7 Nov 2023 15:05:31 -0700 Subject: [PATCH 10/23] Add ecc hybrid encryption example. This comes from https://cryptobook.nakov.com/asymmetric-key-ciphers/ecc-encryption-decryption I'm hoping to use it to better understand the underlying primitives and operation so I can do something functionally similar but using the underlying elliptic curve primitives within my X.509 certificates. --- temp/ecc-hybrid-encryption-example.py | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 temp/ecc-hybrid-encryption-example.py diff --git a/temp/ecc-hybrid-encryption-example.py b/temp/ecc-hybrid-encryption-example.py new file mode 100644 index 0000000..a78b6cb --- /dev/null +++ b/temp/ecc-hybrid-encryption-example.py @@ -0,0 +1,53 @@ +from tinyec import registry +from Crypto.Cipher import AES +import hashlib, secrets, binascii + +def encrypt_AES_GCM(msg, secretKey): + aesCipher = AES.new(secretKey, AES.MODE_GCM) + ciphertext, authTag = aesCipher.encrypt_and_digest(msg) + return (ciphertext, aesCipher.nonce, authTag) + +def decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey): + aesCipher = AES.new(secretKey, AES.MODE_GCM, nonce) + plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag) + return plaintext + +def ecc_point_to_256_bit_key(point): + sha = hashlib.sha256(int.to_bytes(point.x, 32, 'big')) + sha.update(int.to_bytes(point.y, 32, 'big')) + return sha.digest() + +curve = registry.get_curve('brainpoolP256r1') + +def encrypt_ECC(msg, pubKey): + ciphertextPrivKey = secrets.randbelow(curve.field.n) + sharedECCKey = ciphertextPrivKey * pubKey + secretKey = ecc_point_to_256_bit_key(sharedECCKey) + ciphertext, nonce, authTag = encrypt_AES_GCM(msg, secretKey) + ciphertextPubKey = ciphertextPrivKey * curve.g + return (ciphertext, nonce, authTag, ciphertextPubKey) + +def decrypt_ECC(encryptedMsg, privKey): + (ciphertext, nonce, authTag, ciphertextPubKey) = encryptedMsg + sharedECCKey = privKey * ciphertextPubKey + secretKey = ecc_point_to_256_bit_key(sharedECCKey) + plaintext = decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey) + return plaintext + +msg = b'Text to be encrypted by ECC public key and ' \ + b'decrypted by its corresponding ECC private key' +print("original msg:", msg) +privKey = secrets.randbelow(curve.field.n) +pubKey = privKey * curve.g + +encryptedMsg = encrypt_ECC(msg, pubKey) +encryptedMsgObj = { + 'ciphertext': binascii.hexlify(encryptedMsg[0]), + 'nonce': binascii.hexlify(encryptedMsg[1]), + 'authTag': binascii.hexlify(encryptedMsg[2]), + 'ciphertextPubKey': hex(encryptedMsg[3].x) + hex(encryptedMsg[3].y % 2)[2:] +} +print("encrypted msg:", encryptedMsgObj) + +decryptedMsg = decrypt_ECC(encryptedMsg, privKey) +print("decrypted msg:", decryptedMsg) From 0f450672b19c2bd4c501ce1e3c9105919c6d9ba7 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 7 Nov 2023 15:07:54 -0700 Subject: [PATCH 11/23] Pull out a main function, remove global state. Makes it easier to see what inputs I need. --- temp/ecc-hybrid-encryption-example.py | 38 +++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/temp/ecc-hybrid-encryption-example.py b/temp/ecc-hybrid-encryption-example.py index a78b6cb..b444a1b 100644 --- a/temp/ecc-hybrid-encryption-example.py +++ b/temp/ecc-hybrid-encryption-example.py @@ -17,9 +17,8 @@ def ecc_point_to_256_bit_key(point): sha.update(int.to_bytes(point.y, 32, 'big')) return sha.digest() -curve = registry.get_curve('brainpoolP256r1') -def encrypt_ECC(msg, pubKey): +def encrypt_ECC(curve, msg, pubKey): ciphertextPrivKey = secrets.randbelow(curve.field.n) sharedECCKey = ciphertextPrivKey * pubKey secretKey = ecc_point_to_256_bit_key(sharedECCKey) @@ -34,20 +33,25 @@ def decrypt_ECC(encryptedMsg, privKey): plaintext = decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey) return plaintext -msg = b'Text to be encrypted by ECC public key and ' \ - b'decrypted by its corresponding ECC private key' -print("original msg:", msg) -privKey = secrets.randbelow(curve.field.n) -pubKey = privKey * curve.g +def main(): + curve = registry.get_curve('brainpoolP256r1') + msg = b'Text to be encrypted by ECC public key and ' \ + b'decrypted by its corresponding ECC private key' + print("original msg:", msg) + privKey = secrets.randbelow(curve.field.n) + pubKey = privKey * curve.g -encryptedMsg = encrypt_ECC(msg, pubKey) -encryptedMsgObj = { - 'ciphertext': binascii.hexlify(encryptedMsg[0]), - 'nonce': binascii.hexlify(encryptedMsg[1]), - 'authTag': binascii.hexlify(encryptedMsg[2]), - 'ciphertextPubKey': hex(encryptedMsg[3].x) + hex(encryptedMsg[3].y % 2)[2:] -} -print("encrypted msg:", encryptedMsgObj) + encryptedMsg = encrypt_ECC(curve, msg, pubKey) + encryptedMsgObj = { + 'ciphertext': binascii.hexlify(encryptedMsg[0]), + 'nonce': binascii.hexlify(encryptedMsg[1]), + 'authTag': binascii.hexlify(encryptedMsg[2]), + 'ciphertextPubKey': hex(encryptedMsg[3].x) + hex(encryptedMsg[3].y % 2)[2:] + } + print("encrypted msg:", encryptedMsgObj) -decryptedMsg = decrypt_ECC(encryptedMsg, privKey) -print("decrypted msg:", decryptedMsg) + decryptedMsg = decrypt_ECC(encryptedMsg, privKey) + print("decrypted msg:", decryptedMsg) + +if __name__ == "__main__": + main() From e1e66841963d7d740113770552b49c48fbf45f53 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 10 Nov 2023 15:51:13 -0700 Subject: [PATCH 12/23] Add WIP. Likely going nowhere. I've thrashed around a bit, I think I'll move to rust. --- gongor/aegis.py | 10 ++++++++++ gongor/cipher.py | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 gongor/cipher.py diff --git a/gongor/aegis.py b/gongor/aegis.py index 091f9bd..fd814f0 100644 --- a/gongor/aegis.py +++ b/gongor/aegis.py @@ -25,6 +25,7 @@ def box(): with open(args.message, "rb") as f: data = f.read() + shared_ecc_key, ciphered_public_key = _generate_encryption_key(certificate.public_key()) aad = b"authenticated but unencrypted data" key = aead.ChaCha20Poly1305.generate_key() with open("encryption.key", "wb") as f: @@ -41,6 +42,15 @@ def box(): print(f"Wrote encrypted message to '{args.output}'") +def ecc_calc_encryption_keys(pubKey): + ciphertextPrivKey = secrets.randbelow(curve.field.n) + ciphertextPubKey = ciphertextPrivKey * curve.g + sharedECCKey = pubKey * ciphertextPrivKey + return (sharedECCKey, ciphertextPubKey) + +def ecc_calc_decryption_key(privKey, ciphertextPubKey): + sharedECCKey = ciphertextPubKey * privKey + return sharedECCKey def generate(): print("Please name this aegis. You can call it anything. Frequently people use their legal name.") diff --git a/gongor/cipher.py b/gongor/cipher.py new file mode 100644 index 0000000..3353ab7 --- /dev/null +++ b/gongor/cipher.py @@ -0,0 +1,27 @@ +import argparse +from pathlib import Path + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization + +def box_message(): + parser = argparse.ArgumentParser() + parser.add_argument("recipient_certificate", type=Path, help="Path to the certificate of the recipient") + parser.add_argument("sender_key", type=Path, help="Path to the private key of the sender") + parser.add_argument("--sender-key-password", type="str", default=None, help="The password to the sender private key") + parser.add_argument("message", type=Path, help="Path to the message to box") + args = parser.parse_args() + + recipient_cert = _load_certificate(args.recipient_certificate) + sender_key = _load_key(args.sender_key) + + + + +def _load_certificate(path: Path) -> x509.Certificate: + with open(path, "rb") as f: + return x509.load_pem_x509_certificate(f.read()) + +def _load_key(path: Path, password: str) -> EllipticCurvePrivateKey: + with open(path, "rb") as f: + return serialization.load_pem_private_key(f.read(), password) From d7b7db3e881d730e621028198804d9156fb94b25 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 10 Nov 2023 15:53:20 -0700 Subject: [PATCH 13/23] Hello World in Rust. Yeah, I'm going to learn Rust today. --- gongor.rs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 gongor.rs diff --git a/gongor.rs b/gongor.rs new file mode 100644 index 0000000..47ad8c6 --- /dev/null +++ b/gongor.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello World!"); +} From 9145071a954eb45350f0b0c6ee2740e3ae071170 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 11 Nov 2023 15:11:23 -0700 Subject: [PATCH 14/23] Add basic Cargo setup. Via cargo init. --- .gitignore | 4 +++- Cargo.lock | 7 +++++++ Cargo.toml | 12 ++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml diff --git a/.gitignore b/.gitignore index c18dd8d..7a0ec7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -__pycache__/ +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f198875 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "gongor" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9f0b6ed --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gongor" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[[bin]] +name = "gongor" +path = "gongor.rs" From 080c248db0bc16f13c50dbcf3c8b32df35b38c33 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 11 Nov 2023 15:15:19 -0700 Subject: [PATCH 15/23] =?UTF-8?q?Add=20curve25519=E2=80=91dalek=20dependen?= =?UTF-8?q?cy.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We'll use it for signing and stuff. --- Cargo.lock | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 126 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index f198875..b610616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,131 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69037fe1b785e84986b4f2cbcf647381876a00671d25ceef715d7812dd7e1dd" + [[package]] name = "gongor" version = "0.1.0" +dependencies = [ + "curve25519-dalek", +] + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 9f0b6ed..7b1013d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +curve25519-dalek = "4.1.1" [[bin]] name = "gongor" From 0d3effd9d2a8624e59870bfc7b688c3b18cb1c23 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 29 Nov 2023 17:42:35 -0700 Subject: [PATCH 16/23] Write a simple file. --- gongor.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gongor.rs b/gongor.rs index 47ad8c6..a8c507f 100644 --- a/gongor.rs +++ b/gongor.rs @@ -1,3 +1,8 @@ -fn main() { - println!("Hello World!"); +use std::fs::File; +use std::io::prelude::*; + +fn main() -> std::io::Result<()>{ + let mut file = File::create("foo.txt")?; + file.write_all(b"Hello, world!")?; + Ok(()) } From d348df882a6f7b6f43c93c0a6f919e925b1942e2 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 29 Nov 2023 17:44:13 -0700 Subject: [PATCH 17/23] Read an argument from the commandline. --- gongor.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gongor.rs b/gongor.rs index a8c507f..d361db9 100644 --- a/gongor.rs +++ b/gongor.rs @@ -2,7 +2,9 @@ use std::fs::File; use std::io::prelude::*; fn main() -> std::io::Result<()>{ - let mut file = File::create("foo.txt")?; + let filename = std::env::args().nth(1).expect("no filename given"); + + let mut file = File::create(filename)?; file.write_all(b"Hello, world!")?; Ok(()) } From c25f3f2198cf191e1a40796c12e8eda574d5baf9 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 29 Nov 2023 17:54:10 -0700 Subject: [PATCH 18/23] Generate a key and write it out. --- Cargo.lock | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 + gongor.rs | 9 ++- 3 files changed, 234 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b610616..02633a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,33 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + [[package]] name = "cpufeatures" version = "0.2.11" @@ -17,6 +38,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "curve25519-dalek" version = "4.1.1" @@ -26,6 +57,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest", "fiat-crypto", "platforms", "rustc_version", @@ -45,16 +77,84 @@ dependencies = [ ] [[package]] -name = "fiat-crypto" -version = "0.2.3" +name = "der" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69037fe1b785e84986b4f2cbcf647381876a00671d25ceef715d7812dd7e1dd" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] name = "gongor" version = "0.1.0" dependencies = [ "curve25519-dalek", + "ed25519-dalek", + "rand", ] [[package]] @@ -63,6 +163,16 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "platforms" version = "3.2.0" @@ -70,10 +180,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" [[package]] -name = "proc-macro2" -version = "1.0.69" +name = "ppv-lite86" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -87,6 +203,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -102,6 +248,56 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "subtle" version = "2.5.0" @@ -119,6 +315,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -126,7 +328,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "zeroize" -version = "1.6.0" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 7b1013d..573dbe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" [dependencies] curve25519-dalek = "4.1.1" +ed25519-dalek = { version = "2.1.0", features = ["rand_core"] } +rand = "0.8.5" + [[bin]] name = "gongor" diff --git a/gongor.rs b/gongor.rs index d361db9..2e9c5ea 100644 --- a/gongor.rs +++ b/gongor.rs @@ -1,10 +1,17 @@ use std::fs::File; use std::io::prelude::*; +use rand::rngs::OsRng; +use ed25519_dalek::SigningKey; +//use ed25519_dalek::Signature; + fn main() -> std::io::Result<()>{ let filename = std::env::args().nth(1).expect("no filename given"); + let mut csprng = OsRng; + let signing_key: SigningKey = SigningKey::generate(&mut csprng); + let mut file = File::create(filename)?; - file.write_all(b"Hello, world!")?; + file.write_all(&signing_key.to_bytes())?; Ok(()) } From f08ddd24faf225d8bce45e9902db3a228bb2435b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 29 Nov 2023 18:06:38 -0700 Subject: [PATCH 19/23] Read the signing key from a file. --- gongor.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gongor.rs b/gongor.rs index 2e9c5ea..a52412e 100644 --- a/gongor.rs +++ b/gongor.rs @@ -8,10 +8,18 @@ use ed25519_dalek::SigningKey; fn main() -> std::io::Result<()>{ let filename = std::env::args().nth(1).expect("no filename given"); - let mut csprng = OsRng; - let signing_key: SigningKey = SigningKey::generate(&mut csprng); + //let mut csprng = OsRng; + // let signing_key: SigningKey = SigningKey::generate(&mut csprng); - let mut file = File::create(filename)?; - file.write_all(&signing_key.to_bytes())?; + // let mut file = File::create(filename)?; + // file.write_all(&signing_key.to_bytes())?; + + let mut f = File::open(&filename).expect("no file found"); + //let metadata = File::metadata(&f).expect("unable to read metadata"); + let mut buffer: [u8; 32] = [0; 32]; + f.read(&mut buffer).expect("buffer overflow"); + let signing_key: SigningKey = SigningKey::from_bytes(&buffer); + + println!("Using key {filename}"); Ok(()) } From a03899a588ad72dfbb57bb7f5aa530cfb9d3acb7 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 29 Nov 2023 18:28:35 -0700 Subject: [PATCH 20/23] Add command actions, sign a message --- gongor.rs | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/gongor.rs b/gongor.rs index a52412e..49bb796 100644 --- a/gongor.rs +++ b/gongor.rs @@ -1,25 +1,46 @@ +use std::io::BufReader; use std::fs::File; use std::io::prelude::*; use rand::rngs::OsRng; use ed25519_dalek::SigningKey; -//use ed25519_dalek::Signature; +use ed25519_dalek::Signature; +use ed25519_dalek::Signer; fn main() -> std::io::Result<()>{ - let filename = std::env::args().nth(1).expect("no filename given"); + let action = std::env::args().nth(1).expect("no command given"); + if action == "sign" { + let keyfilepath = std::env::args().nth(2).expect("no keyfilepath given"); + let messagefilepath = std::env::args().nth(3).expect("no message given"); - //let mut csprng = OsRng; - // let signing_key: SigningKey = SigningKey::generate(&mut csprng); + let mut keyfile = File::open(&keyfilepath).expect("no file found"); + let mut keybuffer: [u8; 32] = [0; 32]; + keyfile.read(&mut keybuffer).expect("buffer overflow"); + let signing_key: SigningKey = SigningKey::from_bytes(&keybuffer); - // let mut file = File::create(filename)?; - // file.write_all(&signing_key.to_bytes())?; + let messagefile= File::open(&messagefilepath).expect("no file found"); + let mut messagereader = BufReader::new(messagefile); + let mut messagebuf = Vec::new(); + messagereader.read_to_end(&mut messagebuf)?; - let mut f = File::open(&filename).expect("no file found"); - //let metadata = File::metadata(&f).expect("unable to read metadata"); - let mut buffer: [u8; 32] = [0; 32]; - f.read(&mut buffer).expect("buffer overflow"); - let signing_key: SigningKey = SigningKey::from_bytes(&buffer); - println!("Using key {filename}"); - Ok(()) + + println!("Using key {keyfilepath}"); + + let signature: Signature = signing_key.sign(&messagebuf); + println!("Signture: {signature}"); + return Ok(()) + } else if action == "create" { + let keyfilepath = std::env::args().nth(2).expect("no keyfilepath given"); + let mut csprng = OsRng; + let signing_key: SigningKey = SigningKey::generate(&mut csprng); + + println!("Writing new key to {keyfilepath}"); + let mut file = File::create(keyfilepath)?; + file.write_all(&signing_key.to_bytes())?; + return Ok(()); + } else { + println!("Unrecognized command {action}"); + return Ok(()); + } } From e72ef67049d7e83c9a080f3432967c28a9f41ca0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 6 Dec 2023 19:37:45 -0700 Subject: [PATCH 21/23] Add signature file output It's optional, whee! --- README.md | 5 +++++ gongor.rs | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24051d3..d858cfe 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ Tool for producing identities Anagram of "Gorgon". +## Actions + + * `cargo run -- create eli.key`: makes a new key. + * `cargo run -- sign eli.key message.txt [message.sig]`: signs a message, optionally writes the signature to a file. + ## TODO * Fix up the aegis generation script to ensure that I either have, or don't need, the extensions from Step CA: diff --git a/gongor.rs b/gongor.rs index 49bb796..0e34fdd 100644 --- a/gongor.rs +++ b/gongor.rs @@ -12,6 +12,8 @@ fn main() -> std::io::Result<()>{ if action == "sign" { let keyfilepath = std::env::args().nth(2).expect("no keyfilepath given"); let messagefilepath = std::env::args().nth(3).expect("no message given"); + let maybe_signature_filepath = std::env::args().nth(4); + let mut keyfile = File::open(&keyfilepath).expect("no file found"); let mut keybuffer: [u8; 32] = [0; 32]; @@ -28,7 +30,14 @@ fn main() -> std::io::Result<()>{ println!("Using key {keyfilepath}"); let signature: Signature = signing_key.sign(&messagebuf); - println!("Signture: {signature}"); + if maybe_signature_filepath.is_some() { + let filename = maybe_signature_filepath.expect("Not possible"); + let mut file = File::create(filename.clone())?; + println!("Writing signature to {filename}"); + file.write_all(&signature.to_bytes())?; + } else { + println!("Signture: {signature}"); + } return Ok(()) } else if action == "create" { let keyfilepath = std::env::args().nth(2).expect("no keyfilepath given"); From d296f40eae77ab6393c0b6499608e745098a2e8c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 6 Dec 2023 21:16:02 -0700 Subject: [PATCH 22/23] Add signature validation. I also realized I wasn't saving the public and private halves separately so I'm doing that now too. God, Rust is great and I'm learning it so slowly. --- README.md | 3 ++- gongor.rs | 50 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d858cfe..b2ae10c 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ Anagram of "Gorgon". ## Actions - * `cargo run -- create eli.key`: makes a new key. + * `cargo run -- create eli.key eli.publickey`: makes a new key. * `cargo run -- sign eli.key message.txt [message.sig]`: signs a message, optionally writes the signature to a file. + * `cargo run -- validate eli.publickey message.txt message.signed`: validates a signature. ## TODO diff --git a/gongor.rs b/gongor.rs index 0e34fdd..b26625f 100644 --- a/gongor.rs +++ b/gongor.rs @@ -6,6 +6,10 @@ use rand::rngs::OsRng; use ed25519_dalek::SigningKey; use ed25519_dalek::Signature; use ed25519_dalek::Signer; +use ed25519_dalek::Verifier; +use ed25519_dalek::VerifyingKey; +use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH}; + fn main() -> std::io::Result<()>{ let action = std::env::args().nth(1).expect("no command given"); @@ -16,7 +20,7 @@ fn main() -> std::io::Result<()>{ let mut keyfile = File::open(&keyfilepath).expect("no file found"); - let mut keybuffer: [u8; 32] = [0; 32]; + let mut keybuffer: [u8; 32] = [0; SECRET_KEY_LENGTH]; keyfile.read(&mut keybuffer).expect("buffer overflow"); let signing_key: SigningKey = SigningKey::from_bytes(&keybuffer); @@ -38,18 +42,50 @@ fn main() -> std::io::Result<()>{ } else { println!("Signture: {signature}"); } - return Ok(()) + } else if action == "validate" { + let public_key_filepath = std::env::args().nth(2).expect("no key filepath given"); + let message_filepath = std::env::args().nth(3).expect("no message filepath given"); + let signature_filepath = std::env::args().nth(4).expect("no signature filepath given"); + + let mut public_keyfile = File::open(&public_key_filepath).expect("no file found"); + let mut public_keybuffer: [u8; PUBLIC_KEY_LENGTH] = [0; PUBLIC_KEY_LENGTH]; + public_keyfile.read(&mut public_keybuffer).expect("buffer overflow"); + let public_key: VerifyingKey = VerifyingKey::from_bytes(&public_keybuffer).expect("Faled to make a key"); + + let messagefile= File::open(&message_filepath).expect("no file found"); + let mut messagereader = BufReader::new(messagefile); + let mut messagebuf = Vec::new(); + messagereader.read_to_end(&mut messagebuf)?; + + let signature_file = File::open(&signature_filepath).expect("no file found"); + let mut signature_reader = BufReader::new(signature_file); + let mut signature_buf: [u8; SIGNATURE_LENGTH] = [0; SIGNATURE_LENGTH]; + signature_reader.read(&mut signature_buf).expect("buffer overflow"); + let signature: Signature = Signature::try_from(&signature_buf[..]).expect("not a signature"); + + if public_key.verify(&messagebuf, &signature).is_ok() { + println!("Yep, that checks out."); + } else { + println!("Invalid signature."); + } } else if action == "create" { - let keyfilepath = std::env::args().nth(2).expect("no keyfilepath given"); + let signing_key_filepath = std::env::args().nth(2).expect("no signing key file path given"); + let verifying_key_filepath = std::env::args().nth(3).expect("no verifying key file path given"); + let mut csprng = OsRng; let signing_key: SigningKey = SigningKey::generate(&mut csprng); - println!("Writing new key to {keyfilepath}"); - let mut file = File::create(keyfilepath)?; + println!("Writing new private key to {signing_key_filepath}"); + let mut file = File::create(signing_key_filepath)?; file.write_all(&signing_key.to_bytes())?; - return Ok(()); + + println!("Writing new public key to {verifying_key_filepath}"); + let verifying_key: VerifyingKey = signing_key.verifying_key(); + file = File::create(verifying_key_filepath)?; + file.write_all(&verifying_key.to_bytes())?; + } else { println!("Unrecognized command {action}"); - return Ok(()); } + return Ok(()); } From 9b66d1ee98a03ef53abb4334874276b431ffbc1d Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 6 Dec 2023 23:09:40 -0700 Subject: [PATCH 23/23] Remove the old Python code. I'm liking Rust, it's working. --- gongor/__init__.py | 0 gongor/aegis.py | 148 --------------------------------------------- gongor/cipher.py | 27 --------- pyproject.toml | 21 ------- setup.py | 5 -- 5 files changed, 201 deletions(-) delete mode 100644 gongor/__init__.py delete mode 100644 gongor/aegis.py delete mode 100644 gongor/cipher.py delete mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/gongor/__init__.py b/gongor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/gongor/aegis.py b/gongor/aegis.py deleted file mode 100644 index fd814f0..0000000 --- a/gongor/aegis.py +++ /dev/null @@ -1,148 +0,0 @@ -import argparse -import os -from pathlib import Path -import subprocess - -from cryptography import x509 -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.ciphers import aead -from cryptography.hazmat.primitives.serialization import pkcs12 -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding - -MAX_HUMAN_AGE = 365 * 200 -def box(): - "Take a message, put it in a box, encrypt the contents using a public key." - parser = argparse.ArgumentParser() - parser.add_argument("aegis_cert_pem", type=Path, help="The file for the PEM-encoded aegis public cert.") - parser.add_argument("message", type=Path, help="The file containing the message to box.") - parser.add_argument("output", type=Path, help="The path to the file where the output will go.") - args = parser.parse_args() - - with open(args.aegis_cert_pem, "rb") as f: - certificate = x509.load_pem_x509_certificate( - data=f.read(), - ) - with open(args.message, "rb") as f: - data = f.read() - - shared_ecc_key, ciphered_public_key = _generate_encryption_key(certificate.public_key()) - aad = b"authenticated but unencrypted data" - key = aead.ChaCha20Poly1305.generate_key() - with open("encryption.key", "wb") as f: - f.write(key) - print("Wrote encryption key to 'encryption.key'") - chacha = aead.ChaCha20Poly1305(key) - nonce = os.urandom(12) - with open("nonce", "wb") as f: - f.write(nonce) - print("Wrote nonce to 'nonce'") - ct = chacha.encrypt(nonce, data, aad) - with open(args.output, "wb") as f: - f.write(ct) - print(f"Wrote encrypted message to '{args.output}'") - - -def ecc_calc_encryption_keys(pubKey): - ciphertextPrivKey = secrets.randbelow(curve.field.n) - ciphertextPubKey = ciphertextPrivKey * curve.g - sharedECCKey = pubKey * ciphertextPrivKey - return (sharedECCKey, ciphertextPubKey) - -def ecc_calc_decryption_key(privKey, ciphertextPubKey): - sharedECCKey = ciphertextPubKey * privKey - return sharedECCKey - -def generate(): - print("Please name this aegis. You can call it anything. Frequently people use their legal name.") - name = input("Name? ") - print("Generating aegis.") - subprocess.run([ - "openssl", "req", # PKCS#10 certificate generation utility - "-new", # Generate a new certificate - "-newkey", "ec", # Generate a new private key using elliptic-curve (ECDSA or ECDH compatible) - "-pkeyopt", "ec_paramgen_curve:prime256v1", # Use the prime256v1 CE curve from NIST (P-256) - "-x509", # Create a self-signed certificate instead of a certificate request. - "-days", str(MAX_HUMAN_AGE), # Set the validity period to the expected max age of a human - "-subj", f"/CN={name}", # Add the common name for the persona tied to this aegis - "-out", "cert.pem", # Generate a self-signed certificate file with a .pem extension - "-keyout", "key.pem", # Generate an encrypted private key file with a .pem extension - ], - check=True,) - -def sign(): - "Sign some arbitrary data." - parser = argparse.ArgumentParser() - parser.add_argument("aegis_key_pem", type=Path, help="The file for the PEM-encoded aegis private key.") - parser.add_argument("message", type=Path, help="The file containing the message to sign.") - parser.add_argument("-p", "--password", help="The password to use to open the key file.") - args = parser.parse_args() - - with open(args.aegis_key_pem, "rb") as f: - private_key = serialization.load_pem_private_key( - data=f.read(), - password=args.password.encode("UTF-8"), - ) - with open(args.message, "rb") as f: - data = f.read() - signature = private_key.sign( - data, - ec.ECDSA(hashes.SHA256()), - ) - with open("signature.bin", "wb") as f: - f.write(signature) - print("Wrote signature to signature.bin") - -def validate(): - "Validate the signature of some arbitrary data." - parser = argparse.ArgumentParser() - parser.add_argument("aegis_cert_pem", type=Path, help="The file for the PEM-encoded aegis public certificate.") - parser.add_argument("message", type=Path, help="The file containing the message to validate.") - parser.add_argument("signature", type=Path, help="The file containing the signature to validate.") - args = parser.parse_args() - - with open(args.aegis_cert_pem, "rb") as f: - certificate = x509.load_pem_x509_certificate( - data=f.read(), - ) - with open(args.message, "rb") as f: - data = f.read() - with open(args.signature, "rb") as f: - signature = f.read() - - key = certificate.public_key() - key.verify( - signature=signature, - data=data, - signature_algorithm=ec.ECDSA(hashes.SHA256()), - ) - print("Signature is valid") - -def unbox(): - "Take a message in a box, use a key, open the box, get the message." - parser = argparse.ArgumentParser() - parser.add_argument("aegis_key_pem", type=Path, help="The file for the PEM-encoded aegis key.") - parser.add_argument("ciphertext", type=Path, help="The file containing the boxed ciphertext.") - parser.add_argument("output", type=Path, help="The path to the file where the output will go.") - parser.add_argument("-p", "--password", help="The password to use to open the key file.") - args = parser.parse_args() - - with open(args.aegis_key_pem, "rb") as f: - private_key = serialization.load_pem_private_key( - data=f.read(), - password=args.password.encode("UTF-8"), - ) - with open(args.ciphertext, "rb") as f: - ciphertext = f.read() - aad = b"authenticated but unencrypted data" - - with open("encryption.key", "rb") as f: - key = f.read() - with open("nonce", "rb") as f: - nonce = f.read() - - chacha = aead.ChaCha20Poly1305(key) - message = chacha.decrypt(nonce, ciphertext, aad) - with open(args.output, "wb") as f: - f.write(message) - print(f"Wrote unboxed message to '{args.output}'") - diff --git a/gongor/cipher.py b/gongor/cipher.py deleted file mode 100644 index 3353ab7..0000000 --- a/gongor/cipher.py +++ /dev/null @@ -1,27 +0,0 @@ -import argparse -from pathlib import Path - -from cryptography import x509 -from cryptography.hazmat.primitives import serialization - -def box_message(): - parser = argparse.ArgumentParser() - parser.add_argument("recipient_certificate", type=Path, help="Path to the certificate of the recipient") - parser.add_argument("sender_key", type=Path, help="Path to the private key of the sender") - parser.add_argument("--sender-key-password", type="str", default=None, help="The password to the sender private key") - parser.add_argument("message", type=Path, help="Path to the message to box") - args = parser.parse_args() - - recipient_cert = _load_certificate(args.recipient_certificate) - sender_key = _load_key(args.sender_key) - - - - -def _load_certificate(path: Path) -> x509.Certificate: - with open(path, "rb") as f: - return x509.load_pem_x509_certificate(f.read()) - -def _load_key(path: Path, password: str) -> EllipticCurvePrivateKey: - with open(path, "rb") as f: - return serialization.load_pem_private_key(f.read(), password) diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 74c4918..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,21 +0,0 @@ -[project] -name = "gongor" -version = "0.1" -description = "A system to manage Aegi" -readme = "README.md" -requires-python = ">=3.7" -license = {file = "LICENSE.txt"} - -[project.scripts] -aegis-generate = "gongor.aegis:generate" -aegis-sign = "gongor.aegis:sign" -aegis-validate-signature = "gongor.aegis:validate" -aegis-box = "gongor.aegis:box" -aegis-unbox = "gongor.aegis:unbox" - -[build-system] -requires = ["setuptools >= 61.0.0"] -build-backend = "setuptools.build_meta" - -[tool.setuptools.packages.find] -include = ["gongor"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 11b7a5c..0000000 --- a/setup.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -import setuptools - -if __name__ == "__main__": - setuptools.setup()