diff --git a/.gitignore b/.gitignore index 5ad0b4c..6b34c4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store tls +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..083f446 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,152 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "test-crypto" +version = "0.1.0" +dependencies = [ + "hex", + "ring", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dce418c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test-crypto" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "tls" +path = "tls.rs" + +[dependencies] +hex = "0.4.3" +ring = "0.17.8" diff --git a/Makefile b/Makefile index 54c7835..ac62fba 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,10 @@ c: gcc -Wall -Wextra -Werror -pedantic -std=c99 -o tls tls.c -lcrypto ./tls +rust: + cargo run --bin tls + clean: - rm tls + rm -rf tls target -.PHONY: java python nodejs go c clean +.PHONY: java python nodejs go c rust clean diff --git a/TLS.java b/TLS.java index 36ee908..59a3aa9 100644 --- a/TLS.java +++ b/TLS.java @@ -95,13 +95,13 @@ public static void main(String[] args) throws Exception { .getInstance("EC") .generatePublic(decodedServerEcdhPublicKeySpec); KeyPair clientEcdhKeyPair = keyPairGen.generateKeyPair(); - byte[] encodedClientEcdhPublicKey = clientEcdhKeyPair.getPublic().getEncoded(); // 91 bytes + byte[] encodedClientEcdhPublicKey = clientEcdhKeyPair.getPublic().getEncoded(); KeyAgreement agreement = KeyAgreement.getInstance("ECDH"); agreement.init(clientEcdhKeyPair.getPrivate()); agreement.doPhase(decodedServerEcdhPublicKey, true); - byte[] clientMasterSecret = agreement.generateSecret(); // 32 bytes + byte[] clientMasterSecret = agreement.generateSecret(); sha256.reset(); - byte[] aesKeyBytes = sha256.digest(clientMasterSecret); // 32 bytes + byte[] aesKeyBytes = sha256.digest(clientMasterSecret); SecretKeySpec aesKey = new SecretKeySpec(aesKeyBytes, "AES"); /* diff --git a/tls.c b/tls.c index 454cc9e..22a0c1b 100644 --- a/tls.c +++ b/tls.c @@ -12,8 +12,8 @@ int main(void) FILE *fp; unsigned char *encoded_rsa_public_key, *encoded_rsa_private_key, *encoded_server_ecdh_public_key, *ptr, *key_hash, *signature, - *encoded_client_ecdh_public_key, *client_shared_secret, - *server_shared_secret, *aes_key, iv[12], *ciphertext, tag[16], *decrypted; + *encoded_client_ecdh_public_key, *client_master_secret, + *server_master_secret, *aes_key, iv[12], *ciphertext, tag[16], *decrypted; int encoded_rsa_public_key_len, encoded_rsa_private_key_len, encoded_server_ecdh_public_key_len, verified, encoded_client_ecdh_public_key_len, len, ciphertext_len, decrypted_len; @@ -24,10 +24,10 @@ int main(void) *client_ecdh_keypair = NULL, *decoded_client_ecdh_public_key = NULL; EVP_PKEY_CTX *server_ecdh_param_context, *server_ecdh_key_context, *client_ecdh_param_context, *client_ecdh_key_context, - *client_shared_secret_context, *server_shared_secret_context; + *client_master_secret_context, *server_master_secret_context; EVP_MD_CTX *sha256_context, *sign_context, *verify_context; unsigned int key_hash_len, aes_key_len; - size_t signature_len, client_shared_secret_len, server_shared_secret_len; + size_t signature_len, client_master_secret_len, server_master_secret_len; const char *plaintext = "Hello world!", *aad = "authenticated but unencrypted data"; EVP_CIPHER_CTX *aes_gcm_encrypt_context, *aes_gcm_decrypt_context; @@ -100,30 +100,30 @@ int main(void) encoded_client_ecdh_public_key = malloc(encoded_client_ecdh_public_key_len); ptr = encoded_client_ecdh_public_key; i2d_PUBKEY(client_ecdh_keypair, &ptr); - client_shared_secret_context = EVP_PKEY_CTX_new(client_ecdh_keypair, NULL); - EVP_PKEY_derive_init(client_shared_secret_context); - EVP_PKEY_derive_set_peer(client_shared_secret_context, decoded_server_ecdh_public_key); - EVP_PKEY_derive(client_shared_secret_context, NULL, &client_shared_secret_len); - client_shared_secret = malloc(client_shared_secret_len); - EVP_PKEY_derive(client_shared_secret_context, client_shared_secret, &client_shared_secret_len); + client_master_secret_context = EVP_PKEY_CTX_new(client_ecdh_keypair, NULL); + EVP_PKEY_derive_init(client_master_secret_context); + EVP_PKEY_derive_set_peer(client_master_secret_context, decoded_server_ecdh_public_key); + EVP_PKEY_derive(client_master_secret_context, NULL, &client_master_secret_len); + client_master_secret = malloc(client_master_secret_len); + EVP_PKEY_derive(client_master_secret_context, client_master_secret, &client_master_secret_len); EVP_MD_CTX_free(sha256_context); sha256_context = EVP_MD_CTX_new(); EVP_DigestInit_ex(sha256_context, EVP_sha256(), NULL); - EVP_DigestUpdate(sha256_context, client_shared_secret, client_shared_secret_len); + EVP_DigestUpdate(sha256_context, client_master_secret, client_master_secret_len); aes_key_len = EVP_MD_get_size(EVP_sha256()); aes_key = malloc(aes_key_len); EVP_DigestFinal_ex(sha256_context, aes_key, &aes_key_len); const_ptr = encoded_client_ecdh_public_key; d2i_PUBKEY(&decoded_client_ecdh_public_key, &const_ptr, encoded_client_ecdh_public_key_len); - server_shared_secret_context = EVP_PKEY_CTX_new(server_ecdh_keypair, NULL); - EVP_PKEY_derive_init(server_shared_secret_context); - EVP_PKEY_derive_set_peer(server_shared_secret_context, decoded_client_ecdh_public_key); - EVP_PKEY_derive(server_shared_secret_context, NULL, &server_shared_secret_len); - server_shared_secret = malloc(server_shared_secret_len); - EVP_PKEY_derive(server_shared_secret_context, server_shared_secret, &server_shared_secret_len); - if (client_shared_secret_len != server_shared_secret_len || - memcmp(client_shared_secret, server_shared_secret, client_shared_secret_len) != 0) { + server_master_secret_context = EVP_PKEY_CTX_new(server_ecdh_keypair, NULL); + EVP_PKEY_derive_init(server_master_secret_context); + EVP_PKEY_derive_set_peer(server_master_secret_context, decoded_client_ecdh_public_key); + EVP_PKEY_derive(server_master_secret_context, NULL, &server_master_secret_len); + server_master_secret = malloc(server_master_secret_len); + EVP_PKEY_derive(server_master_secret_context, server_master_secret, &server_master_secret_len); + if (client_master_secret_len != server_master_secret_len || + memcmp(client_master_secret, server_master_secret, client_master_secret_len) != 0) { printf("Master secrets don't match.\n"); exit(1); } @@ -159,13 +159,13 @@ int main(void) EVP_CIPHER_CTX_free(aes_gcm_decrypt_context); free(ciphertext); EVP_CIPHER_CTX_free(aes_gcm_encrypt_context); - free(server_shared_secret); - EVP_PKEY_CTX_free(server_shared_secret_context); + free(server_master_secret); + EVP_PKEY_CTX_free(server_master_secret_context); EVP_PKEY_free(decoded_client_ecdh_public_key); free(aes_key); EVP_MD_CTX_free(sha256_context); - free(client_shared_secret); - EVP_PKEY_CTX_free(client_shared_secret_context); + free(client_master_secret); + EVP_PKEY_CTX_free(client_master_secret_context); free(encoded_client_ecdh_public_key); EVP_PKEY_free(client_ecdh_keypair); EVP_PKEY_CTX_free(client_ecdh_key_context); diff --git a/tls.rs b/tls.rs new file mode 100644 index 0000000..8ac7f86 --- /dev/null +++ b/tls.rs @@ -0,0 +1,89 @@ +use std::fs; + +use ring::{aead, agreement, digest, rand::{self, SecureRandom}, rsa, signature}; + +fn main() { + let encoded_rsa_public_key = fs::read("public_key.der").unwrap(); + let encoded_rsa_private_key = fs::read("private_key.der").unwrap(); + let rsa_private_key = rsa::KeyPair::from_pkcs8(encoded_rsa_private_key.as_slice()).unwrap(); + + let rng = rand::SystemRandom::new(); + let server_ecdh_private_key = agreement + ::EphemeralPrivateKey::generate(&agreement::ECDH_P384, &rng).unwrap(); + let server_ecdh_public_key = server_ecdh_private_key.compute_public_key().unwrap(); + let server_ecdh_public_key_raw = server_ecdh_public_key.as_ref(); + // hack to encode secp384r1 public key in DER SPKI format manually since + // ring doesn't support it yet :( + let mut encoded_server_ecdh_public_key = hex::decode( + "3076301006072a8648ce3d020106052b81040022036200").unwrap(); + encoded_server_ecdh_public_key.extend_from_slice(server_ecdh_public_key_raw); + + // SHA-256 hash of server's ECDH public key is computed by sign() + let mut signature = vec![0; rsa_private_key.public().modulus_len()]; + rsa_private_key.sign(&signature::RSA_PKCS1_SHA256, &rng, + encoded_server_ecdh_public_key.as_slice(), &mut signature).unwrap(); + + let decoded_rsa_public_key = signature + ::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, + &encoded_rsa_public_key.as_slice()[24..]); // removing the encoding manually + decoded_rsa_public_key.verify(encoded_server_ecdh_public_key.as_slice(), + signature.as_slice()).unwrap_or_else(|_| { + panic!("RSA signature wasn't verified."); + }); + + // client should import ring::constant_time and use + // constant_time::verify_slices_are_equal() to compare server's hash with + // its own hash of the server's encoded ECDH public key to avoid timing + // attacks + let decoded_server_ecdh_public_key = agreement + ::UnparsedPublicKey::new(&agreement::ECDH_P384, + &encoded_server_ecdh_public_key.as_slice()[23..]); // removing the encoding manually + let client_ecdh_private_key = agreement + ::EphemeralPrivateKey::generate(&agreement::ECDH_P384, &rng).unwrap(); + let client_ecdh_public_key = client_ecdh_private_key.compute_public_key().unwrap(); + let client_ecdh_public_key_raw = client_ecdh_public_key.as_ref(); + // manual public key encoding again + let mut encoded_client_ecdh_public_key = hex::decode( + "3076301006072a8648ce3d020106052b81040022036200").unwrap(); + encoded_client_ecdh_public_key.extend_from_slice(client_ecdh_public_key_raw); + let aes_key = agreement::agree_ephemeral( + client_ecdh_private_key, &decoded_server_ecdh_public_key, + |client_master_secret| { + // perform SHA-256 hash of the master secret in here + return digest::digest(&digest::SHA256, client_master_secret).as_ref().to_vec(); + } + ).unwrap(); + + let decoded_client_ecdh_public_key = agreement + ::UnparsedPublicKey::new(&agreement::ECDH_P384, + &encoded_client_ecdh_public_key.as_slice()[23..]); // removing the encoding manually + let server_aes_key = agreement::agree_ephemeral( + server_ecdh_private_key, &decoded_client_ecdh_public_key, + |server_master_secret| { + // perform SHA-256 hash of the master secret in here + return digest::digest(&digest::SHA256, server_master_secret).as_ref().to_vec(); + } + ).unwrap(); + if aes_key != server_aes_key { + panic!("Master secrets don't match."); + } + + let plaintext = "Hello world!"; + let mut ciphertext = plaintext.as_bytes().to_vec(); + let aad_bytes = b"authenticated but unencrypted data"; + let mut iv_bytes = [08; 12]; + rng.fill(&mut iv_bytes).unwrap(); + let iv = aead::Nonce::assume_unique_for_key(iv_bytes); + let aad = aead::Aad::from(aad_bytes); + let unbound_key = aead::UnboundKey::new( + &aead::AES_256_GCM, aes_key.as_slice()).unwrap(); + let aes = aead::LessSafeKey::new(unbound_key); + aes.seal_in_place_append_tag(iv, aad, &mut ciphertext).unwrap(); + + let iv_copy = aead::Nonce::assume_unique_for_key(iv_bytes); + let decrypted = aes.open_in_place(iv_copy, aad, &mut ciphertext).unwrap(); + let recovered = std::str::from_utf8(decrypted).unwrap(); + if plaintext != recovered { + panic!("Plaintexts don't match."); + } +}