Skip to content

Commit

Permalink
Upgrade padding scheme to PSS
Browse files Browse the repository at this point in the history
  • Loading branch information
chrislattman committed Feb 10, 2025
1 parent 7d10b78 commit 4ed0f9f
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 15 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ openssl rsa -pubout -inform PEM -in keypair.pem -outform DER -out public_key.der
```

> Note: C# offers a cryptography interface, but it's not fully cross-platform, as it requires either using the Cryptography API: Next Generation (CNG) library (bcrypt.dll) on Windows, or OpenSSL for non-Windows platforms.
> Windows CNG stores cryptographic public/private key pairs as blobs, which aren't compatible with OpenSSL-generated key pairs. This is something to consider when importing private keys or exporting public keys.
27 changes: 18 additions & 9 deletions TLS.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

Expand Down Expand Up @@ -49,18 +51,23 @@ public static void main(String[] args) throws Exception {
* In the absence of a certificate authority, the server's RSA public
* key is assumed to be known and trusted by both parties.
*
* Nonetheless, the server sends its encoded RSA public key used to sign
* its encoded ECDH public key, the encoded ECDH public key itself, the
* SHA-256 hash of the encoded ECDH public key, and the RSA signature of
* the hash to the client.
* Nonetheless, the server sends: its encoded ECDH public key (120 bytes
* long), the SHA-256 hash of the encoded ECDH public key (32 bytes
* long), the RSA signature of the hash (256 bytes long), and its
* encoded RSA public key (used to verify the signature) to the client
* (294 bytes long). This example chooses to sign with PSS instead of
* PKCS#1 v1.5 for better security.
*
* Normally, the RSA public key is part of an X.509 certificate, which
* itself is part of a chain of signed certificates, ultimately ending
* with a root certificate installed on the computer.
*/
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] keyHash = sha256.digest(encodedServerEcdhPublicKey);
Signature rsaSign = Signature.getInstance("NONEwithRSA");
Signature rsaSign = Signature.getInstance("RSASSA-PSS");
PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256",
"MGF1", MGF1ParameterSpec.SHA256, 222, 1);
rsaSign.setParameter(pssSpec);
rsaSign.initSign(rsaPrivateKey);
rsaSign.update(keyHash);
byte[] signature = rsaSign.sign();
Expand Down Expand Up @@ -88,9 +95,10 @@ public static void main(String[] args) throws Exception {
* or shared secret) using its ECDH private key and the server's ECDH
* public key, and hashes that master secret with SHA-256 to generate a
* 256-bit AES secret key. It then sends its encoded ECDH public key to
* the server.
* the server (also 120 bytes long).
*/
X509EncodedKeySpec decodedServerEcdhPublicKeySpec = new X509EncodedKeySpec(encodedServerEcdhPublicKey);
X509EncodedKeySpec decodedServerEcdhPublicKeySpec = new X509EncodedKeySpec(
encodedServerEcdhPublicKey);
PublicKey decodedServerEcdhPublicKey = KeyFactory
.getInstance("EC")
.generatePublic(decodedServerEcdhPublicKeySpec);
Expand All @@ -108,9 +116,10 @@ public static void main(String[] args) throws Exception {
* The server decodes the client's ECDH public key and generates the
* master secret using its ECDH private key and the client's ECDH public
* key. Then it generates the AES secret key the same way the client
* does.
* does. Both master secrets should be 48 bytes long.
*/
X509EncodedKeySpec decodedClientEcdhPublicKeySpec = new X509EncodedKeySpec(encodedClientEcdhPublicKey);
X509EncodedKeySpec decodedClientEcdhPublicKeySpec = new X509EncodedKeySpec(
encodedClientEcdhPublicKey);
PublicKey decodedClientEcdhPublicKey = KeyFactory
.getInstance("EC")
.generatePublic(decodedClientEcdhPublicKeySpec);
Expand Down
5 changes: 3 additions & 2 deletions go/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func main() {
encodedServerEcdhPublicKey, _ := x509.MarshalPKIXPublicKey(serverEcdhPrivateKey.PublicKey())

keyHash := sha256.Sum256(encodedServerEcdhPublicKey)
signature, _ := rsa.SignPKCS1v15(nil, rsaPrivateKey, crypto.SHA256, keyHash[:])
opts := rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthAuto}
signature, _ := rsa.SignPSS(rng, rsaPrivateKey, crypto.SHA256, keyHash[:], &opts)

var decodedRsaPublicKey *rsa.PublicKey
key, _ = x509.ParsePKIXPublicKey(encodedRsaPublicKey)
Expand All @@ -42,7 +43,7 @@ func main() {
default:
log.Fatalln("Error parsing encodedRsaPublicKey")
}
err := rsa.VerifyPKCS1v15(decodedRsaPublicKey, crypto.SHA256, keyHash[:], signature)
err := rsa.VerifyPSS(decodedRsaPublicKey, crypto.SHA256, keyHash[:], signature, &opts)
if err != nil {
log.Fatalln("RSA signature wasn't verified.")
}
Expand Down
10 changes: 8 additions & 2 deletions tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ int main(void)
*decoded_server_ecdh_public_key = NULL, *client_ecdh_params = NULL,
*client_ecdh_keypair = NULL, *decoded_client_ecdh_public_key = NULL;
EVP_PKEY_CTX *server_ecdh_param_context, *server_ecdh_key_context,
*rsa_private_key_ctx = NULL, *encoded_rsa_public_key_ctx = NULL,
*client_ecdh_param_context, *client_ecdh_key_context,
*client_master_secret_context, *server_master_secret_context;
EVP_MD_CTX *sha256_context, *sign_context, *verify_context;
Expand Down Expand Up @@ -68,7 +69,9 @@ int main(void)
key_hash = malloc(key_hash_len);
EVP_DigestFinal_ex(sha256_context, key_hash, &key_hash_len);
sign_context = EVP_MD_CTX_new();
EVP_DigestSignInit(sign_context, NULL, EVP_sha256(), NULL, rsa_private_key);
EVP_DigestSignInit(sign_context, &rsa_private_key_ctx, EVP_sha256(), NULL, rsa_private_key);
EVP_PKEY_CTX_set_rsa_padding(rsa_private_key_ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_pss_saltlen(rsa_private_key_ctx, -2);
EVP_DigestSignUpdate(sign_context, key_hash, key_hash_len);
EVP_DigestSignFinal(sign_context, NULL, &signature_len);
signature = malloc(signature_len);
Expand All @@ -77,7 +80,10 @@ int main(void)
const_ptr = encoded_rsa_public_key;
d2i_PUBKEY(&decoded_rsa_public_key, &const_ptr, encoded_rsa_public_key_len);
verify_context = EVP_MD_CTX_new();
EVP_DigestVerifyInit(verify_context, NULL, EVP_sha256(), NULL, decoded_rsa_public_key);
EVP_DigestVerifyInit(verify_context, &encoded_rsa_public_key_ctx, EVP_sha256(),
NULL, decoded_rsa_public_key);
EVP_PKEY_CTX_set_rsa_padding(encoded_rsa_public_key_ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_pss_saltlen(encoded_rsa_public_key_ctx, -2);
EVP_DigestVerifyUpdate(verify_context, key_hash, key_hash_len);
verified = EVP_DigestVerifyFinal(verify_context, (const unsigned char *) signature, signature_len);
if (!verified) {
Expand Down
4 changes: 4 additions & 0 deletions tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ const encodedServerEcdhPublicKey = Buffer.concat(
let sha256 = crypto.createHash("sha256");
sha256.update(encodedServerEcdhPublicKey);
const keyHash = sha256.digest();
rsaPrivateKey.padding = crypto.constants.RSA_PKCS1_PSS_PADDING;
rsaPrivateKey.saltLength = crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN;
const signature = crypto.sign(null, keyHash, rsaPrivateKey);

const decodedRsaPublicKey = crypto.createPublicKey({
key: encodedRsaPublicKey,
format: "der",
type: "spki",
});
decodedRsaPublicKey.padding = crypto.constants.RSA_PKCS1_PSS_PADDING;
decodedRsaPublicKey.saltLength = crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN;
if (!crypto.verify(null, keyHash, decodedRsaPublicKey, signature)) {
throw new Error("RSA signature wasn't verified.");
}
Expand Down
4 changes: 2 additions & 2 deletions tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ fn main() {

// 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,
rsa_private_key.sign(&signature::RSA_PSS_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,
::UnparsedPublicKey::new(&signature::RSA_PSS_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(|_| {
Expand Down

0 comments on commit 4ed0f9f

Please sign in to comment.