diff --git a/.gitignore b/.gitignore index d6b645e..8787382 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,7 @@ test/TestSerialize.cakedata test/TestDynamicArraySerialize.cakedata test/TestDictionarySerialize.cakedata test/TestDictionarySerializeStrDict.cakedata -test/MultiReads.cakedata \ No newline at end of file +test/MultiReads.cakedata + +test/cryptography-cli* +PublicAndSecretKeys.bin \ No newline at end of file diff --git a/src/Cryptography.cake b/src/Cryptography.cake index 12fea77..5d97f63 100644 --- a/src/Cryptography.cake +++ b/src/Cryptography.cake @@ -1,14 +1,16 @@ ;; Cryptography.cake: Cryptographic functions for signing, encryption, etc. -(add-c-search-directory-module "cakelisp_cache/LibSodiumInstallDir/include") -(c-import "sodium.h") +(add-cakelisp-search-directory "Dependencies/cakelisp/runtime") +(import "CHelpers.cake" "BuildTools.cake" "Dependencies.cake") + +(export-and-evaluate + (add-c-search-directory-module "cakelisp_cache/LibSodiumInstallDir/include") + (c-import "sodium.h")) (defun cryptography-initialize (&return bool) (return (>= (sodium_init) 0))) (comptime-cond ('auto-test - (add-cakelisp-search-directory "Dependencies/cakelisp/runtime") - (import "CHelpers.cake") (c-import "") (defun test--cryptography (&return int) (unless (cryptography-initialize) @@ -49,7 +51,7 @@ (defun-comptime build-libsodium (manager (& ModuleManager) module (* Module) &return bool) (var libsodium-archive (* (const char)) - (comptime-cond ('Windows "Dependencies/libsodium/onig_s.lib") + (comptime-cond ('Windows "Dependencies/libsodium/libsodium.lib") ('Unix "cakelisp_cache/LibSodiumInstallDir/lib/libsodium.a"))) ;; Already built? ;; We could enhance this by checking for modifications, but that's pretty rare diff --git a/src/CryptographyCLI.cake b/src/CryptographyCLI.cake new file mode 100644 index 0000000..8dfd838 --- /dev/null +++ b/src/CryptographyCLI.cake @@ -0,0 +1,235 @@ +(add-cakelisp-search-directory "Dependencies/cakelisp/runtime") +(import "CHelpers.cake" "FileUtilities.cake" + "Cryptography.cake") + +(c-import "") + +(var s-keys-filename (* (const char)) "PublicAndSecretKeys.bin") + +;; +;; Command registration +;; + +(defmacro defcommand (command-name symbol arguments array &rest body any) + + (get-or-create-comptime-var command-table (<> (in std vector) (* (const Token)))) + (call-on-ptr push_back command-table command-name) + + (tokenize-push output + (defun (token-splice command-name) (token-splice arguments) + (token-splice-rest body tokens))) + (return true)) + +(defun-comptime create-command-lookup-table (environment (& EvaluatorEnvironment) &return bool) + (get-or-create-comptime-var command-table-already-created bool false) + (when (deref command-table-already-created) + (return true)) + (set (deref command-table-already-created) true) + + (get-or-create-comptime-var command-table (<> (in std vector) (* (const Token)))) + + (var command-data (* (<> std::vector Token)) (new (<> std::vector Token))) + (call-on push_back (field environment comptimeTokens) command-data) + + (for-in command-name (* (const Token)) (deref command-table) + (var command-name-string Token (deref command-name)) + (set (field command-name-string type) TokenType_String) + + (tokenize-push (deref command-data) + (array (token-splice-addr command-name-string) + (token-splice command-name)))) + + (var command-table-tokens (* (<> std::vector Token)) (new (<> std::vector Token))) + (call-on push_back (field environment comptimeTokens) command-table-tokens) + (tokenize-push (deref command-table-tokens) + (var command-table ([] command-metadata) + (array (token-splice-array (deref command-data))))) + + (return (ClearAndEvaluateAtSplicePoint environment "command-lookup-table" command-table-tokens))) + +(add-compile-time-hook post-references-resolved + create-command-lookup-table) + +(def-function-signature command-function (num-arguments int arguments ([] (* (const char))) &return int)) + +(defstruct-local command-metadata + name (* (const char)) + command command-function) + +(splice-point command-lookup-table) + +;; +;; Commands +;; + +(defun-local print-keys (public-key (* (unsigned char)) + secret-key (* (unsigned char))) + (fprintf stderr "Public key:\n") + (each-in-range crypto_sign_PUBLICKEYBYTES i + (fprintf stderr "%02x " (at i public-key))) + (fprintf stderr "\nSecret key:\n") + (each-in-range crypto_sign_SECRETKEYBYTES i + (fprintf stderr "%02x " (at i secret-key))) + (fprintf stderr "\n")) + +(defcommand generate-keys (num-arguments int arguments ([] (* (const char))) &return int) + (fprintf stderr "Generating public and secret keys\n") + (var public-key ([] crypto_sign_PUBLICKEYBYTES (unsigned char))) + (var secret-key ([] crypto_sign_SECRETKEYBYTES (unsigned char))) + (crypto_sign_keypair public-key secret-key) + (print-keys public-key secret-key) + + (var out-file (* FILE) (fopen s-keys-filename "wb")) + (unless out-file + (fprintf stderr "Failed to open key file '%s'.\n" s-keys-filename) + (return 1)) + (fwrite public-key (sizeof public-key) 1 out-file) + (fwrite secret-key (sizeof secret-key) 1 out-file) + (fclose out-file) + (fprintf stderr "Keys saved to %s\n" s-keys-filename) + (return 0)) + +(defun-local load-keys-from-file (filename (* (const char)) + public-key-out (* (unsigned char)) + secret-key-out (* (unsigned char)) + &return bool) + (var in-file (* FILE) (fopen filename "rb")) + (unless in-file + (fprintf stderr "Failed to open key file '%s'. You can generate new keys with the generate-keys command.\n" + s-keys-filename) + (return false)) + (fread public-key-out crypto_sign_PUBLICKEYBYTES 1 in-file) + (fread secret-key-out crypto_sign_SECRETKEYBYTES 1 in-file) + (fclose in-file) + (return true)) + +(defcommand list-keys (num-arguments int arguments ([] (* (const char))) &return int) + (var public-key ([] crypto_sign_PUBLICKEYBYTES (unsigned char))) + (var secret-key ([] crypto_sign_SECRETKEYBYTES (unsigned char))) + (unless (load-keys-from-file s-keys-filename public-key secret-key) + (return 1)) + (print-keys public-key secret-key) + (return 0)) + +(defcommand create-signed-file (num-arguments int arguments ([] (* (const char))) &return int) + (unless (= 2 num-arguments) + (fprintf stderr "Command create-signed-file expects two arguments: [input file] [output file]\n") + (return 1)) + + (var input-filename (* (const char)) (at 0 arguments)) + (var output-filename (* (const char)) (at 1 arguments)) + + (var public-key ([] crypto_sign_PUBLICKEYBYTES (unsigned char))) + (var secret-key ([] crypto_sign_SECRETKEYBYTES (unsigned char))) + (unless (load-keys-from-file s-keys-filename public-key secret-key) + (fprintf stderr "Generate keys with generate-keys first before trying to create a signed file.\n") + (return 1)) + (fprintf stderr "Signing file %s with the following keys:\n" input-filename) + (print-keys public-key secret-key) + + (var input-file (* FILE) (fopen input-filename "rb")) + (unless input-file + (fprintf stderr "error: Failed to open input file %s.\n" input-filename) + (return 1)) + (var input-file-size size_t) + (var-cast-to file-contents (* (unsigned char)) + (read-file-into-memory-ex input-file 0 (addr input-file-size))) + (fclose input-file) + + (var-cast-to signed-contents (* (unsigned char)) (malloc (+ input-file-size crypto_sign_BYTES))) + (var signed-contents-length (unsigned (long long))) + (crypto_sign signed-contents (addr signed-contents-length) + file-contents input-file-size secret-key) + + (var output-file (* FILE) (fopen output-filename "wb")) + (unless output-file + (fprintf stderr "error: Failed to open output file %s.\n" output-filename) + (free signed-contents) + (free file-contents) + (return 1)) + (fwrite signed-contents signed-contents-length 1 output-file) + (fclose output-file) + + (fprintf stderr "The file %s has been signed and written to %s.\n" + input-filename output-filename) + + (free signed-contents) + (free file-contents) + (return 0)) + +(defcommand verify-signed-file (num-arguments int arguments ([] (* (const char))) &return int) + (unless (= 1 num-arguments) + (fprintf stderr "Command create-signed-file expects one argument: [input file]\n") + (return 1)) + + (var input-filename (* (const char)) (at 0 arguments)) + + (var public-key ([] crypto_sign_PUBLICKEYBYTES (unsigned char))) + (var secret-key ([] crypto_sign_SECRETKEYBYTES (unsigned char))) + (unless (load-keys-from-file s-keys-filename public-key secret-key) + (fprintf stderr "There were no keys found to check against.\n") + (return 1)) + (fprintf stderr "Checking signed file %s against the following keys:\n" input-filename) + (print-keys public-key secret-key) + + (var input-file (* FILE) (fopen input-filename "rb")) + (unless input-file + (fprintf stderr "error: Failed to open input file %s.\n" input-filename) + (return 1)) + (var input-file-size size_t) + (var-cast-to file-contents (* (unsigned char)) + (read-file-into-memory-ex input-file 0 (addr input-file-size))) + (fclose input-file) + + ;; This is a little too big but we don't care about those extra few signature bytes + (var-cast-to unsigned-message (* (unsigned char)) (malloc input-file-size)) + (var unsigned-message-length (unsigned (long long))) + (unless (= 0 (crypto_sign_open unsigned-message (addr unsigned-message-length) + file-contents input-file-size public-key)) + (fprintf stderr "\n[FAIL] This file's signature does NOT match the key.\n") + (free unsigned-message) + (free file-contents) + (return 1)) + + (fprintf stderr "\n[PASS] This file's signature matches the key.\n") + + (free unsigned-message) + (free file-contents) + (return 0)) + +;; +;; CLI +;; + +(defun-local print-help () + (fprintf stderr #"#Usage: +cryptography-cli [command] [arguments] + +Available commands: +#"#) + (each-in-array command-table i + (fprintf stderr " %s\n" + (field (at i command-table) name)))) + +(defun main (num-arguments int arguments ([] (* (const char))) &return int) + (unless (> num-arguments 1) + (fprintf stderr "Expected command.\n") + (print-help) + (return 1)) + + (var result int 1) + (var found bool false) + (each-in-array command-table i + (when (= 0 (strcmp (field (at i command-table) name) (at 1 arguments))) + (set result (call (field (at i command-table) command) + ;; Trim process name and command name + (- num-arguments 2) (+ arguments 2))) + (set found true) + (break))) + (unless found + (fprintf stderr "The command '%s' was not recognized.\n" (at 1 arguments)) + (print-help) + (return 1)) + (return result)) + +(set-cakelisp-option executable-output "cryptography-cli") diff --git a/test/BuildStandaloneExecutables.sh b/test/BuildStandaloneExecutables.sh new file mode 100755 index 0000000..52db79f --- /dev/null +++ b/test/BuildStandaloneExecutables.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +CAKELISP_DIR=Dependencies/cakelisp + +# Link Cakelisp if necessary +[ ! -d "$CAKELISP_DIR" ] && git clone git@github.com:makuto/cakelisp.git Dependencies/cakelisp + +# Build Cakelisp itself +echo "\n\nCakelisp\n\n" +cd $CAKELISP_DIR +./Build.sh || exit $? + +cd ../.. + +CAKELISP=./Dependencies/cakelisp/bin/cakelisp + +$CAKELISP src/Config_Linux.cake ../src/CryptographyCLI.cake --verbose-processes || exit $?