Medium severity5.9NVD Advisory· Published May 14, 2024· Updated Apr 15, 2026
CVE-2024-30171
CVE-2024-30171
Description
An issue was discovered in Bouncy Castle Java TLS API and JSSE Provider before 1.78. Timing-based leakage may occur in RSA based handshakes because of exception processing.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.bouncycastle:bctls-fipsMaven | < 1.0.19 | 1.0.19 |
org.bouncycastle:bcprov-jdk18onMaven | < 1.78 | 1.78 |
org.bouncycastle:bcprov-jdk15onMaven | < 1.78 | 1.78 |
org.bouncycastle:bcprov-jdk15to18Maven | < 1.78 | 1.78 |
org.bouncycastle:bcprov-jdk14Maven | < 1.78 | 1.78 |
org.bouncycastle:bctls-jdk18onMaven | < 1.78 | 1.78 |
org.bouncycastle:bctls-jdk14Maven | < 1.78 | 1.78 |
org.bouncycastle:bctls-jdk15to18Maven | < 1.78 | 1.78 |
BouncyCastleNuGet | >= 0 | — |
BouncyCastle.CryptographyNuGet | < 2.3.1 | 2.3.1 |
Patches
3c984b8bfd854Improve TLS RSA PreMasterSecret decryption
2 files changed · +23 −20
crypto/src/crypto/tls/TlsRsaKeyExchange.cs+20 −18 modified@@ -11,11 +11,13 @@ namespace Org.BouncyCastle.Crypto.Tls { public static class TlsRsaKeyExchange { - public static byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret, RsaKeyParameters privateKey, + public const int PreMasterSecretLength = 48; + + public static byte[] DecryptPreMasterSecret(byte[] buf, int off, int len, RsaKeyParameters privateKey, int protocolVersion, SecureRandom secureRandom) { - if (Arrays.IsNullOrEmpty(encryptedPreMasterSecret)) - throw new ArgumentException("cannot be null or empty", nameof(encryptedPreMasterSecret)); + if (buf == null || len < 1 || len > GetInputLimit(privateKey) || off < 0 || off > buf.Length - len) + throw new ArgumentException("input not a valid EncryptedPreMasterSecret"); if (!privateKey.IsPrivate) throw new ArgumentException("must be an RSA private key", nameof(privateKey)); @@ -31,24 +33,24 @@ public static byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret, Rsa secureRandom = CryptoServicesRegistrar.GetSecureRandom(secureRandom); /* - * Generate 48 random bytes we can use as a Pre-Master-Secret if the decrypted value is invalid. + * Generate random bytes we can use as a Pre-Master-Secret if the decrypted value is invalid. */ - byte[] result = new byte[48]; + byte[] result = new byte[PreMasterSecretLength]; secureRandom.NextBytes(result); try { - BigInteger input = ConvertInput(modulus, encryptedPreMasterSecret); + BigInteger input = ConvertInput(modulus, buf, off, len); byte[] encoding = RsaBlinded(privateKey, input, secureRandom); int pkcs1Length = (bitLength - 1) / 8; - int plainTextOffset = encoding.Length - 48; + int plainTextOffset = encoding.Length - PreMasterSecretLength; - int badEncodingMask = CheckPkcs1Encoding2(encoding, pkcs1Length, 48); + int badEncodingMask = CheckPkcs1Encoding2(encoding, pkcs1Length, PreMasterSecretLength); int badVersionMask = -(Pack.BE_To_UInt16(encoding, plainTextOffset) ^ protocolVersion) >> 31; int fallbackMask = badEncodingMask | badVersionMask; - for (int i = 0; i < 48; ++i) + for (int i = 0; i < PreMasterSecretLength; ++i) { result[i] = (byte)((result[i] & fallbackMask) | (encoding[plainTextOffset + i] & ~fallbackMask)); } @@ -69,6 +71,11 @@ public static byte[] DecryptPreMasterSecret(byte[] encryptedPreMasterSecret, Rsa return result; } + public static int GetInputLimit(RsaKeyParameters privateKey) + { + return (privateKey.Modulus.BitLength + 7) / 8; + } + private static int CAddTo(int len, int cond, byte[] x, byte[] z) { Debug.Assert(cond == 0 || cond == -1); @@ -116,16 +123,11 @@ private static int CheckPkcs1Encoding2(byte[] buf, int pkcs1Length, int plaintex return errorSign >> 31; } - private static BigInteger ConvertInput(BigInteger modulus, byte[] input) + private static BigInteger ConvertInput(BigInteger modulus, byte[] buf, int off, int len) { - int inputLimit = (modulus.BitLength + 7) / 8; - - if (input.Length <= inputLimit) - { - BigInteger result = new BigInteger(1, input); - if (result.CompareTo(modulus) < 0) - return result; - } + BigInteger result = BigIntegers.FromUnsignedByteArray(buf, off, len); + if (result.CompareTo(modulus) < 0) + return result; throw new DataLengthException("input too large for RSA cipher."); }
crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs+3 −2 modified@@ -45,21 +45,22 @@ public BcDefaultTlsCredentialedDecryptor(BcTlsCrypto crypto, Certificate certifi public virtual TlsSecret Decrypt(TlsCryptoParameters cryptoParams, byte[] ciphertext) { - // TODO Keep only the decryption itself here - move error handling outside return SafeDecryptPreMasterSecret(cryptoParams, (RsaKeyParameters)m_privateKey, ciphertext); } /* * TODO[tls-ops] Probably need to make RSA encryption/decryption into TlsCrypto functions so that users can * implement "generic" encryption credentials externally */ + // TODO[api] Just inline this into Decrypt protected virtual TlsSecret SafeDecryptPreMasterSecret(TlsCryptoParameters cryptoParams, RsaKeyParameters rsaServerPrivateKey, byte[] encryptedPreMasterSecret) { ProtocolVersion expectedVersion = cryptoParams.RsaPreMasterSecretVersion; byte[] preMasterSecret = Org.BouncyCastle.Crypto.Tls.TlsRsaKeyExchange.DecryptPreMasterSecret( - encryptedPreMasterSecret, rsaServerPrivateKey, expectedVersion.FullVersion, m_crypto.SecureRandom); + encryptedPreMasterSecret, 0, encryptedPreMasterSecret.Length, rsaServerPrivateKey, + expectedVersion.FullVersion, m_crypto.SecureRandom); return m_crypto.CreateSecret(preMasterSecret); }
d7d5e735abd6added new RSA mode for better TLS unwrap operation relates to github #1528
3 files changed · +107 −35
prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java+41 −3 modified@@ -31,9 +31,13 @@ import org.bouncycastle.crypto.encodings.OAEPEncoding; import org.bouncycastle.crypto.engines.RSABlindedEngine; import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; +import org.bouncycastle.crypto.tls.TlsRsaKeyExchange; import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi; import org.bouncycastle.jcajce.provider.util.BadBlockException; import org.bouncycastle.jcajce.provider.util.DigestFactory; +import org.bouncycastle.jcajce.spec.TLSRSAPremasterSecretParameterSpec; import org.bouncycastle.jcajce.util.BCJcaJceHelper; import org.bouncycastle.jcajce.util.JcaJceHelper; import org.bouncycastle.util.Strings; @@ -49,6 +53,8 @@ public class CipherSpi private boolean publicKeyOnly = false; private boolean privateKeyOnly = false; private ErasableOutputStream bOut = new ErasableOutputStream(); + private TLSRSAPremasterSecretParameterSpec tlsRsaSpec = null; + private CipherParameters param = null; public CipherSpi( AsymmetricBlockCipher engine) @@ -262,9 +268,12 @@ protected void engineInit( SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { - CipherParameters param; - if (params == null || params instanceof OAEPParameterSpec) + this.tlsRsaSpec = null; + + if (params == null + || params instanceof OAEPParameterSpec + || params instanceof TLSRSAPremasterSecretParameterSpec) { if (key instanceof RSAPublicKey) { @@ -291,7 +300,7 @@ else if (key instanceof RSAPrivateKey) throw new InvalidKeyException("unknown key type passed to RSA"); } - if (params != null) + if (params instanceof OAEPParameterSpec) { OAEPParameterSpec spec = (OAEPParameterSpec)params; @@ -324,6 +333,14 @@ else if (key instanceof RSAPrivateKey) cipher = new OAEPEncoding(new RSABlindedEngine(), digest, mgfDigest, ((PSource.PSpecified)spec.getPSource()).getValue()); } + else if (params instanceof TLSRSAPremasterSecretParameterSpec) + { + if (!(param instanceof RSAPrivateCrtKeyParameters)) + { + throw new InvalidKeyException("RSA private key required for TLS decryption"); + } + this.tlsRsaSpec = (TLSRSAPremasterSecretParameterSpec)params; + } } else { @@ -403,6 +420,11 @@ protected byte[] engineUpdate( int inputOffset, int inputLen) { + if (tlsRsaSpec != null) + { + throw new IllegalStateException("RSA cipher initialized for TLS only"); + } + bOut.write(input, inputOffset, inputLen); if (cipher instanceof RSABlindedEngine) @@ -430,6 +452,11 @@ protected int engineUpdate( byte[] output, int outputOffset) { + if (tlsRsaSpec != null) + { + throw new IllegalStateException("RSA cipher initialized for TLS only"); + } + bOut.write(input, inputOffset, inputLen); if (cipher instanceof RSABlindedEngine) @@ -456,6 +483,12 @@ protected byte[] engineDoFinal( int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if (tlsRsaSpec != null) + { + ParametersWithRandom pWithR = (ParametersWithRandom)param; + return TlsRsaKeyExchange.decryptPreMasterSecret(input, (RSAKeyParameters)pWithR.getParameters(), tlsRsaSpec.getProtocolVersion(), pWithR.getRandom()); + } + if (input != null) { bOut.write(input, inputOffset, inputLen); @@ -487,6 +520,11 @@ protected int engineDoFinal( int outputOffset) throws IllegalBlockSizeException, BadPaddingException, ShortBufferException { + if (tlsRsaSpec != null) + { + throw new IllegalStateException("RSA cipher initialized for TLS only"); + } + if (outputOffset + engineGetOutputSize(inputLen) > output.length) { throw new ShortBufferException("output buffer too short for input.");
prov/src/main/java/org/bouncycastle/jcajce/spec/TLSRSAPremasterSecretParameterSpec.java+19 −0 added@@ -0,0 +1,19 @@ +package org.bouncycastle.jcajce.spec; + +import java.security.spec.AlgorithmParameterSpec; + +public class TLSRSAPremasterSecretParameterSpec + implements AlgorithmParameterSpec +{ + private final int protocolVersion; + + public TLSRSAPremasterSecretParameterSpec(int protocolVersion) + { + this.protocolVersion = protocolVersion; + } + + public int getProtocolVersion() + { + return protocolVersion; + } +}
tls/src/main/java/org/bouncycastle/tls/crypto/impl/jcajce/JceDefaultTlsCredentialedDecryptor.java+47 −32 modified@@ -7,6 +7,7 @@ import javax.crypto.Cipher; +import org.bouncycastle.jcajce.spec.TLSRSAPremasterSecretParameterSpec; import org.bouncycastle.tls.Certificate; import org.bouncycastle.tls.ProtocolVersion; import org.bouncycastle.tls.TlsCredentialedDecryptor; @@ -81,49 +82,63 @@ protected TlsSecret safeDecryptPreMasterSecret(TlsCryptoParameters cryptoParams, * RFC 5246 7.4.7.1. */ ProtocolVersion expectedVersion = cryptoParams.getRSAPreMasterSecretVersion(); + byte[] M; - /* - * Generate 48 random bytes we can use as a Pre-Master-Secret, if the PKCS1 padding check should fail. - */ - byte[] fallback = new byte[48]; - secureRandom.nextBytes(fallback); - - byte[] M = Arrays.clone(fallback); try { Cipher c = crypto.createRSAEncryptionCipher(); - c.init(Cipher.DECRYPT_MODE, rsaServerPrivateKey, secureRandom); - byte[] m = c.doFinal(encryptedPreMasterSecret); - if (m != null && m.length == 48) - { - M = m; - } + + c.init(Cipher.DECRYPT_MODE, rsaServerPrivateKey, new TLSRSAPremasterSecretParameterSpec(expectedVersion.getFullVersion()), secureRandom); + M = c.doFinal(encryptedPreMasterSecret); } - catch (Exception e) + catch (Exception ex) { + // Fallback + /* - * A TLS server MUST NOT generate an alert if processing an RSA-encrypted premaster secret message - * fails, or the version number is not as expected. Instead, it MUST continue the handshake with a - * randomly generated premaster secret. + * Generate 48 random bytes we can use as a Pre-Master-Secret, if the PKCS1 padding check should fail. */ - } + byte[] fallback = new byte[48]; + secureRandom.nextBytes(fallback); - /* - * Compare the version number in the decrypted Pre-Master-Secret with the legacy_version field from - * the ClientHello. If they don't match, continue the handshake with the randomly generated 'fallback' - * value. - * - * NOTE: The comparison and replacement must be constant-time. - */ - int mask = (expectedVersion.getMajorVersion() ^ (M[0] & 0xFF)) - | (expectedVersion.getMinorVersion() ^ (M[1] & 0xFF)); + M = Arrays.clone(fallback); + try + { + Cipher c = crypto.createRSAEncryptionCipher(); + + c.init(Cipher.DECRYPT_MODE, rsaServerPrivateKey, secureRandom); + byte[] m = c.doFinal(encryptedPreMasterSecret); + if (m != null && m.length == 48) + { + M = m; + } + } + catch (Exception e) + { + /* + * A TLS server MUST NOT generate an alert if processing an RSA-encrypted premaster secret message + * fails, or the version number is not as expected. Instead, it MUST continue the handshake with a + * randomly generated premaster secret. + */ + } + + /* + * Compare the version number in the decrypted Pre-Master-Secret with the legacy_version field from + * the ClientHello. If they don't match, continue the handshake with the randomly generated 'fallback' + * value. + * + * NOTE: The comparison and replacement must be constant-time. + */ + int mask = (expectedVersion.getMajorVersion() ^ (M[0] & 0xFF)) + | (expectedVersion.getMinorVersion() ^ (M[1] & 0xFF)); - // 'mask' will be all 1s if the versions matched, or else all 0s. - mask = (mask - 1) >> 31; + // 'mask' will be all 1s if the versions matched, or else all 0s. + mask = (mask - 1) >> 31; - for (int i = 0; i < 48; i++) - { - M[i] = (byte)((M[i] & mask) | (fallback[i] & ~mask)); + for (int i = 0; i < 48; i++) + { + M[i] = (byte)((M[i] & mask) | (fallback[i] & ~mask)); + } } return crypto.createSecret(M);
e0569dcb1deaImprovements to PKCS1Encoding
3 files changed · +402 −133
core/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java+115 −119 modified@@ -233,52 +233,86 @@ private byte[] encodeBlock( } /** - * Checks if the argument is a correctly PKCS#1.5 encoded Plaintext - * for encryption. - * - * @param encoded The Plaintext. - * @param pLen Expected length of the plaintext. - * @return Either 0, if the encoding is correct, or -1, if it is incorrect. + * Check the argument is a valid encoding with type 1. Returns the plaintext length if valid, or -1 if invalid. */ - private static int checkPkcs1Encoding(byte[] encoded, int pLen) + private static int checkPkcs1Encoding1(byte[] buf) { - int correct = 0; - /* - * Check if the first two bytes are 0 2 - */ - correct |= (encoded[0] ^ 2); + int foundZeroMask = 0; + int lastPadPos = 0; - /* - * Now the padding check, check for no 0 byte in the padding - */ - int plen = encoded.length - ( - pLen /* Length of the PMS */ - + 1 /* Final 0-byte before PMS */ - ); + // The first byte should be 0x01 + int badPadSign = -((buf[0] & 0xFF) ^ 0x01); - for (int i = 1; i < plen; i++) + // There must be a zero terminator for the padding somewhere + for (int i = 1; i < buf.length; ++i) { - int tmp = encoded[i]; - tmp |= tmp >> 1; - tmp |= tmp >> 2; - tmp |= tmp >> 4; - correct |= (tmp & 1) - 1; + int padByte = buf[i] & 0xFF; + int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31; + int is0xFFMask = ((padByte ^ 0xFF) - 1) >> 31; + lastPadPos ^= i & ~foundZeroMask & is0x00Mask; + foundZeroMask |= is0x00Mask; + badPadSign |= ~(foundZeroMask | is0xFFMask); } - /* - * Make sure the padding ends with a 0 byte. - */ - correct |= encoded[encoded.length - (pLen + 1)]; + // The header should be at least 10 bytes + badPadSign |= lastPadPos - 9; - /* - * Return 0 or 1, depending on the result. - */ - correct |= correct >> 1; - correct |= correct >> 2; - correct |= correct >> 4; - return ~((correct & 1) - 1); + int plaintextLength = buf.length - 1 - lastPadPos; + return plaintextLength | badPadSign >> 31; } + /** + * Check the argument is a valid encoding with type 2. Returns the plaintext length if valid, or -1 if invalid. + */ + private static int checkPkcs1Encoding2(byte[] buf) + { + int foundZeroMask = 0; + int lastPadPos = 0; + + // The first byte should be 0x02 + int badPadSign = -((buf[0] & 0xFF) ^ 0x02); + + // There must be a zero terminator for the padding somewhere + for (int i = 1; i < buf.length; ++i) + { + int padByte = buf[i] & 0xFF; + int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31; + lastPadPos ^= i & ~foundZeroMask & is0x00Mask; + foundZeroMask |= is0x00Mask; + } + + // The header should be at least 10 bytes + badPadSign |= lastPadPos - 9; + + int plaintextLength = buf.length - 1 - lastPadPos; + return plaintextLength | badPadSign >> 31; + } + + /** + * Check the argument is a valid encoding with type 2 of a plaintext with the given length. Returns 0 if + * valid, or -1 if invalid. + */ + private static int checkPkcs1Encoding2(byte[] buf, int plaintextLength) + { + // The first byte should be 0x02 + int badPadSign = -((buf[0] & 0xFF) ^ 0x02); + + int lastPadPos = buf.length - 1 - plaintextLength; + + // The header should be at least 10 bytes + badPadSign |= lastPadPos - 9; + + // All pad bytes before the last one should be non-zero + for (int i = 1; i < lastPadPos; ++i) + { + badPadSign |= (buf[i] & 0xFF) - 1; + } + + // Last pad byte should be zero + badPadSign |= -(buf[lastPadPos] & 0xFF); + + return badPadSign >> 31; + } /** * Decode PKCS#1.5 encoding, and return a random value if the padding is not correct. @@ -298,132 +332,94 @@ private byte[] decodeBlockOrRandom(byte[] in, int inOff, int inLen) throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing"); } - byte[] block = engine.processBlock(in, inOff, inLen); - byte[] random; - if (this.fallback == null) + int plaintextLength = this.pLen; + + byte[] random = fallback; + if (fallback == null) { - random = new byte[this.pLen]; + random = new byte[plaintextLength]; this.random.nextBytes(random); } - else + + int badPadMask = 0; + int strictBlockSize = engine.getOutputBlockSize(); + byte[] block = engine.processBlock(in, inOff, inLen); + + byte[] data = block; + if (block.length != strictBlockSize) { - random = fallback; + if (useStrictLength || block.length < strictBlockSize) + { + data = blockBuffer; + } } - byte[] data = (useStrictLength & (block.length != engine.getOutputBlockSize())) ? blockBuffer : block; + badPadMask |= checkPkcs1Encoding2(data, plaintextLength); - /* - * Check the padding. - */ - int correct = PKCS1Encoding.checkPkcs1Encoding(data, this.pLen); - /* * Now, to a constant time constant memory copy of the decrypted value * or the random value, depending on the validity of the padding. */ - byte[] result = new byte[this.pLen]; - for (int i = 0; i < this.pLen; i++) + int dataOff = data.length - plaintextLength; + byte[] result = new byte[plaintextLength]; + for (int i = 0; i < plaintextLength; ++i) { - result[i] = (byte)((data[i + (data.length - pLen)] & (~correct)) | (random[i] & correct)); + result[i] = (byte)((data[dataOff + i] & ~badPadMask) | (random[i] & badPadMask)); } - Arrays.fill(data, (byte)0); + Arrays.fill(block, (byte)0); + Arrays.fill(blockBuffer, 0, Math.max(0, blockBuffer.length - block.length), (byte)0); return result; } /** * @throws InvalidCipherTextException if the decrypted block is not in PKCS1 format. */ - private byte[] decodeBlock( - byte[] in, - int inOff, - int inLen) + private byte[] decodeBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException { /* * If the length of the expected plaintext is known, we use a constant-time decryption. * If the decryption fails, we return a random value. */ - if (this.pLen != -1) + if (forPrivateKey && this.pLen != -1) { return this.decodeBlockOrRandom(in, inOff, inLen); } + int strictBlockSize = engine.getOutputBlockSize(); byte[] block = engine.processBlock(in, inOff, inLen); - boolean incorrectLength = (useStrictLength & (block.length != engine.getOutputBlockSize())); - - byte[] data; - if (block.length < getOutputBlockSize()) - { - data = blockBuffer; - } - else - { - data = block; - } - - byte type = data[0]; - - boolean badType; - if (forPrivateKey) - { - badType = (type != 2); - } - else - { - badType = (type != 1); - } - - // - // find and extract the message block. - // - int start = findStart(type, data); - - start++; // data should start at the next byte - if (badType | start < HEADER_LENGTH) - { - Arrays.fill(data, (byte)0); - throw new InvalidCipherTextException("block incorrect"); - } + boolean incorrectLength = useStrictLength & (block.length != strictBlockSize); - // if we get this far, it's likely to be a genuine encoding error - if (incorrectLength) + byte[] data = block; + if (block.length < strictBlockSize) { - Arrays.fill(data, (byte)0); - throw new InvalidCipherTextException("block incorrect size"); + data = blockBuffer; } - byte[] result = new byte[data.length - start]; - - System.arraycopy(data, start, result, 0, result.length); - - return result; - } + int plaintextLength = forPrivateKey ? checkPkcs1Encoding2(data) : checkPkcs1Encoding1(data); - private int findStart(byte type, byte[] block) - throws InvalidCipherTextException - { - int start = -1; - boolean padErr = false; - - for (int i = 1; i != block.length; i++) + try { - byte pad = block[i]; - - if (pad == 0 & start < 0) + if (plaintextLength < 0) { - start = i; + throw new InvalidCipherTextException("block incorrect"); + } + if (incorrectLength) + { + throw new InvalidCipherTextException("block incorrect size"); } - padErr |= (type == 1 & start < 0 & pad != (byte)0xff); - } - if (padErr) + byte[] result = new byte[plaintextLength]; + System.arraycopy(data, data.length - plaintextLength, result, 0, plaintextLength); + return result; + } + finally { - return -1; + Arrays.fill(block, (byte)0); + Arrays.fill(blockBuffer, 0, Math.max(0, blockBuffer.length - block.length), (byte)0); } - - return start; } }
prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/CipherSpi.java+24 −14 modified@@ -29,7 +29,6 @@ import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.encodings.ISO9796d1Encoding; import org.bouncycastle.crypto.encodings.OAEPEncoding; -import org.bouncycastle.crypto.encodings.PKCS1Encoding; import org.bouncycastle.crypto.engines.RSABlindedEngine; import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.jcajce.provider.asymmetric.util.BaseCipherSpi; @@ -200,7 +199,7 @@ protected void engineSetPadding( } else if (pad.equals("PKCS1PADDING")) { - cipher = new PKCS1Encoding(new RSABlindedEngine()); + cipher = new CustomPKCS1Encoding(new RSABlindedEngine()); } else if (pad.equals("ISO9796-1PADDING")) { @@ -531,15 +530,26 @@ private byte[] getOutput() { try { - return cipher.processBlock(bOut.getBuf(), 0, bOut.size()); - } - catch (InvalidCipherTextException e) - { - throw new BadBlockException("unable to decrypt block", e); - } - catch (ArrayIndexOutOfBoundsException e) - { - throw new BadBlockException("unable to decrypt block", e); + byte[] output; + try + { + output = cipher.processBlock(bOut.getBuf(), 0, bOut.size()); + } + catch (InvalidCipherTextException e) + { + throw new BadBlockException("unable to decrypt block", e); + } + catch (ArrayIndexOutOfBoundsException e) + { + throw new BadBlockException("unable to decrypt block", e); + } + + if (output == null) + { + throw new BadBlockException("unable to decrypt block", null); + } + + return output; } finally { @@ -565,7 +575,7 @@ static public class PKCS1v1_5Padding { public PKCS1v1_5Padding() { - super(new PKCS1Encoding(new RSABlindedEngine())); + super(new CustomPKCS1Encoding(new RSABlindedEngine())); } } @@ -574,7 +584,7 @@ static public class PKCS1v1_5Padding_PrivateOnly { public PKCS1v1_5Padding_PrivateOnly() { - super(false, true, new PKCS1Encoding(new RSABlindedEngine())); + super(false, true, new CustomPKCS1Encoding(new RSABlindedEngine())); } } @@ -583,7 +593,7 @@ static public class PKCS1v1_5Padding_PublicOnly { public PKCS1v1_5Padding_PublicOnly() { - super(true, false, new PKCS1Encoding(new RSABlindedEngine())); + super(true, false, new CustomPKCS1Encoding(new RSABlindedEngine())); } }
prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/CustomPkcs1Encoding.java+263 −0 added@@ -0,0 +1,263 @@ +package org.bouncycastle.jcajce.provider.asymmetric.rsa; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Properties; + +/** + * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this + * depends on your application - see PKCS1 Version 2 for details. + */ +class CustomPKCS1Encoding + implements AsymmetricBlockCipher +{ + private static final int HEADER_LENGTH = 10; + + private SecureRandom random; + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private boolean forPrivateKey; + private boolean useStrictLength; + private byte[] blockBuffer; + + /** + * Basic constructor. + * + * @param cipher + */ + CustomPKCS1Encoding(AsymmetricBlockCipher cipher) + { + this.engine = cipher; + this.useStrictLength = useStrict(); + } + + // + // for J2ME compatibility + // + private boolean useStrict() + { + if (Properties.isOverrideSetTo(PKCS1Encoding.NOT_STRICT_LENGTH_ENABLED_PROPERTY, true)) + { + return false; + } + + return !Properties.isOverrideSetTo(PKCS1Encoding.STRICT_LENGTH_ENABLED_PROPERTY, false); + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init(boolean forEncryption, CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + kParam = (AsymmetricKeyParameter)param; + if (!kParam.isPrivate() && forEncryption) + { + this.random = CryptoServicesRegistrar.getSecureRandom(); + } + } + + engine.init(forEncryption, param); + + this.forPrivateKey = kParam.isPrivate(); + this.forEncryption = forEncryption; + this.blockBuffer = new byte[engine.getOutputBlockSize()]; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - HEADER_LENGTH; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - HEADER_LENGTH; + } + } + + public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException + { + if (inLen > getInputBlockSize()) + { + throw new IllegalArgumentException("input data too large"); + } + + byte[] block = new byte[engine.getInputBlockSize()]; + + if (forPrivateKey) + { + block[0] = 0x01; // type code 1 + + for (int i = 1; i != block.length - inLen - 1; i++) + { + block[i] = (byte)0xFF; + } + } + else + { + random.nextBytes(block); // random fill + + block[0] = 0x02; // type code 2 + + // + // a zero byte marks the end of the padding, so all + // the pad bytes must be non-zero. + // + for (int i = 1; i != block.length - inLen - 1; i++) + { + while (block[i] == 0) + { + block[i] = (byte)random.nextInt(); + } + } + } + + block[block.length - inLen - 1] = 0x00; // mark the end of the padding + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + return engine.processBlock(block, 0, block.length); + } + + /** + * Check the argument is a valid encoding with type 1. Returns the plaintext length if valid, or -1 if invalid. + */ + private static int checkPkcs1Encoding1(byte[] buf) + { + int foundZeroMask = 0; + int lastPadPos = 0; + + // The first byte should be 0x01 + int badPadSign = -((buf[0] & 0xFF) ^ 0x01); + + // There must be a zero terminator for the padding somewhere + for (int i = 1; i < buf.length; ++i) + { + int padByte = buf[i] & 0xFF; + int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31; + int is0xFFMask = ((padByte ^ 0xFF) - 1) >> 31; + lastPadPos ^= i & ~foundZeroMask & is0x00Mask; + foundZeroMask |= is0x00Mask; + badPadSign |= ~(foundZeroMask | is0xFFMask); + } + + // The header should be at least 10 bytes + badPadSign |= lastPadPos - 9; + + int plaintextLength = buf.length - 1 - lastPadPos; + return plaintextLength | badPadSign >> 31; + } + + /** + * Check the argument is a valid encoding with type 2. Returns the plaintext length if valid, or -1 if invalid. + */ + private static int checkPkcs1Encoding2(byte[] buf) + { + int foundZeroMask = 0; + int lastPadPos = 0; + + // The first byte should be 0x02 + int badPadSign = -((buf[0] & 0xFF) ^ 0x02); + + // There must be a zero terminator for the padding somewhere + for (int i = 1; i < buf.length; ++i) + { + int padByte = buf[i] & 0xFF; + int is0x00Mask = ((padByte ^ 0x00) - 1) >> 31; + lastPadPos ^= i & ~foundZeroMask & is0x00Mask; + foundZeroMask |= is0x00Mask; + } + + // The header should be at least 10 bytes + badPadSign |= lastPadPos - 9; + + int plaintextLength = buf.length - 1 - lastPadPos; + return plaintextLength | badPadSign >> 31; + } + + /** + * @throws InvalidCipherTextException if the decrypted block is not in PKCS1 format. + */ + private byte[] decodeBlock(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException + { + int strictBlockSize = engine.getOutputBlockSize(); + byte[] block = engine.processBlock(in, inOff, inLen); + + boolean incorrectLength = useStrictLength & (block.length != strictBlockSize); + + byte[] data = block; + if (block.length < strictBlockSize) + { + data = blockBuffer; + } + + int plaintextLength = forPrivateKey ? checkPkcs1Encoding2(data) : checkPkcs1Encoding1(data); + + try + { + if (plaintextLength < 0 | incorrectLength) + { + // Special behaviour to avoid throw/catch/throw in CipherSpi + return null; + } + + byte[] result = new byte[plaintextLength]; + System.arraycopy(data, data.length - plaintextLength, result, 0, plaintextLength); + return result; + } + finally + { + Arrays.fill(block, (byte)0); + Arrays.fill(blockBuffer, 0, Math.max(0, blockBuffer.length - block.length), (byte)0); + } + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-v435-xc8x-wvr9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-30171ghsaADVISORY
- github.com/bcgit/bc-csharp/commit/c984b8bfd8544dfc55dba91a02cbbbb9c580c217ghsaWEB
- github.com/bcgit/bc-java/commit/d7d5e735abd64bf0f413f54fd9e495fc02400fb0ghsaWEB
- github.com/bcgit/bc-java/commit/e0569dcb1dea9d421d84fc4c5c5688fe101afa2dghsaWEB
- security.netapp.com/advisory/ntap-20240614-0008ghsaWEB
- www.bouncycastle.org/latest_releases.htmlnvdWEB
- security.netapp.com/advisory/ntap-20240614-0008/nvd
News mentions
0No linked articles in our index yet.