diff --git a/scripts/infer.sh b/scripts/infer.sh index 660e61be..b6eda664 100755 --- a/scripts/infer.sh +++ b/scripts/infer.sh @@ -143,6 +143,7 @@ infer --fail-on-issue run -- javac \ src/main/java/com/wolfssl/provider/jce/WolfCryptMessageDigestSha512.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptMessageDigestSha3.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptMessageDigestShake.java \ + src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptPBEKey.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java \ src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java \ diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java index 923270f4..4d32e918 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java @@ -174,6 +174,7 @@ private static final class DefaultIvRandom { /* RSA-OAEP parameters */ private int oaepHashType = 0; private int oaepMgf = 0; + private OAEPParameterSpec oaepSpec = null; /* for debug logging */ private String algString; @@ -374,6 +375,8 @@ private WolfCryptCipher(CipherType type, CipherMode mode, PaddingType pad, private void initOaepParams() { this.oaepHashType = WolfCrypt.WC_HASH_TYPE_SHA256; this.oaepMgf = Rsa.WC_MGF1SHA1; + this.oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", + MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); } /** @@ -384,6 +387,8 @@ private void initOaepParams() { private void initOaepParamsSha1() { this.oaepHashType = WolfCrypt.WC_HASH_TYPE_SHA; this.oaepMgf = Rsa.WC_MGF1SHA1; + this.oaepSpec = new OAEPParameterSpec("SHA-1", "MGF1", + MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); } /** @@ -509,6 +514,9 @@ private void setOaepParams(OAEPParameterSpec spec) } } + /* Store spec for AlgorithmParameters retrieval */ + this.oaepSpec = spec; + /* Set OAEP hash type */ this.oaepHashType = hashNameToWolfCryptType(spec.getDigestAlgorithm()); @@ -1090,6 +1098,13 @@ else if (this.cipherType == CipherType.WC_DES3) { break; } + if (params == null && this.oaepSpec != null) { + params = AlgorithmParameters.getInstance("OAEP"); + if (params != null) { + params.init(this.oaepSpec); + } + } + } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { /* Return null if parameter creation fails */ diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java index 28590829..5f6299a4 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptECKeyFactory.java @@ -320,9 +320,19 @@ private PrivateKey generatePrivateFromECSpec(ECPrivateKeySpec keySpec) "ECParameterSpec cannot be null"); } - /* Validate ECParameterSpec is supported by wolfCrypt, - * throws WolfCryptException if invalid */ - WolfCryptECParameterSpec.validateParameters(keySpec.getParams()); + /* Validate ECParameterSpec is supported by wolfCrypt. + * validateParameters throws IllegalArgumentException (unchecked) + * when the curve is not recognized; wrap it as + * InvalidKeySpecException so callers see a + * GeneralSecurityException. */ + try { + WolfCryptECParameterSpec.validateParameters( + keySpec.getParams()); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Unsupported curve parameters: " + + e.getMessage(), e); + } /* Get curve name from ECParameterSpec */ try { @@ -577,9 +587,19 @@ private PublicKey generatePublicFromECSpec(ECPublicKeySpec keySpec) "ECParameterSpec cannot be null"); } - /* Validate ECParameterSpec is supported by wolfCrypt, - * throws WolfCryptException if invalid */ - WolfCryptECParameterSpec.validateParameters(keySpec.getParams()); + /* Validate ECParameterSpec is supported by wolfCrypt. + * validateParameters throws IllegalArgumentException (unchecked) + * when the curve is not recognized; wrap it as + * InvalidKeySpecException so callers see a + * GeneralSecurityException. */ + try { + WolfCryptECParameterSpec.validateParameters( + keySpec.getParams()); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException( + "Unsupported curve parameters: " + + e.getMessage(), e); + } /* Get curve name from ECParameterSpec */ try { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java index 3e7779e0..270567ca 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptKeyAgreement.java @@ -71,6 +71,8 @@ enum EngineState { private Ecc ecPrivate = null; private int primeLen = 0; + private byte[] dhParamP = null; + private byte[] dhParamG = null; private int curveSize = 0; private String curveName = null; @@ -128,12 +130,36 @@ protected Key engineDoPhase(Key key, boolean lastPhase) "Key must be of type DHPublicKey"); } - pubKey = ((DHPublicKey)key).getY().toByteArray(); + DHPublicKey dhPubKey = (DHPublicKey)key; + + pubKey = dhPubKey.getY().toByteArray(); if (pubKey == null) { throw new InvalidKeyException( "Failed to get DH public key from Key object"); } + if (this.dhParamP == null || this.dhParamG == null) { + throw new InvalidKeyException( + "DH private key not initialized with parameters"); + } + + if (dhPubKey.getParams() == null) { + throw new InvalidKeyException( + "DH public key has no parameters"); + } + + BigInteger privP = new BigInteger(this.dhParamP); + BigInteger privG = new BigInteger(this.dhParamG); + BigInteger pubPBig = dhPubKey.getParams().getP(); + BigInteger pubGBig = dhPubKey.getParams().getG(); + + if (!privP.equals(pubPBig) || !privG.equals(pubGBig)) { + throw new InvalidKeyException( + "DH public key parameters do not match " + + "private key parameters, cannot generate " + + "shared secret"); + } + /* Validate peer public key (SP 800-56A) before use */ try { this.dh.checkPublicKey(pubKey); @@ -168,6 +194,25 @@ protected Key engineDoPhase(Key key, boolean lastPhase) "ECC public key failed validation", e); } + /* Verify the public key curve matches the private key curve. + * A curve mismatch (e.g. secp224r1 public against secp256r1 + * private) is caught here so the caller sees + * InvalidKeyException + * rather than a WolfCryptException from makeSharedSecret. */ + try { + int pubCurveId = this.ecPublic.getCurveId(); + int privCurveId = this.ecPrivate.getCurveId(); + if (pubCurveId != privCurveId) { + throw new InvalidKeyException( + "EC public key curve does not match private key " + + "curve (public curveId=" + pubCurveId + + ", private curveId=" + privCurveId + ")"); + } + } catch (WolfCryptException e) { + throw new InvalidKeyException( + "Failed to validate EC key curve compatibility", e); + } + break; }; @@ -328,7 +373,13 @@ protected int engineGenerateSecret(byte[] sharedSecret, int offset) case WC_ECDH: - tmp = this.ecPrivate.makeSharedSecret(this.ecPublic); + try { + tmp = this.ecPrivate.makeSharedSecret(this.ecPublic); + } catch (WolfCryptException e) { + throw new IllegalStateException( + "Native ECDH shared secret generation failed: " + + e.getMessage(), e); + } if (tmp == null) { throw new RuntimeException("Error when creating " + "ECDH shared secret"); @@ -505,6 +556,9 @@ private void wcInitDHParams(Key key, AlgorithmParameterSpec params) this.dh.setParams(paramP, paramG); + this.dhParamP = paramP; + this.dhParamG = paramG; + primeLen = paramP.length; /* prime may have leading zero */ @@ -603,11 +657,22 @@ private void wcInitECDHParams(Key key, AlgorithmParameterSpec params) "ECC curve is null, please check algorithm parameters"); } + /* Release and recreate native structs to support re-initialization. + * JCE requires KeyAgreement.init() to be callable multiple times. */ + if (this.ecPrivate != null) { + this.ecPrivate.releaseNativeStruct(); + } + this.ecPrivate = new Ecc(); + if (this.ecPublic != null) { + this.ecPublic.releaseNativeStruct(); + } + this.ecPublic = new Ecc(); + privKeyBytes = ecKey.getS().toByteArray(); try { this.ecPrivate.importPrivateOnCurve(privKeyBytes, - null, this.curveName); + null, this.curveName); } finally { zeroArray(privKeyBytes); } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java new file mode 100644 index 00000000..41948c30 --- /dev/null +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptOaepParameters.java @@ -0,0 +1,154 @@ +/* WolfCryptOaepParameters.java + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jce; + +import java.io.IOException; +import java.security.AlgorithmParametersSpi; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.MGF1ParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +/** + * wolfCrypt JCE AlgorithmParametersSpi implementation for RSA-OAEP parameters + */ +public class WolfCryptOaepParameters extends AlgorithmParametersSpi { + + private OAEPParameterSpec oaepSpec; + + public WolfCryptOaepParameters() { + this.oaepSpec = OAEPParameterSpec.DEFAULT; + } + + @Override + protected void engineInit(AlgorithmParameterSpec paramSpec) + throws InvalidParameterSpecException { + + if (!(paramSpec instanceof OAEPParameterSpec)) { + throw new InvalidParameterSpecException( + "Only OAEPParameterSpec supported"); + } + + OAEPParameterSpec spec = (OAEPParameterSpec) paramSpec; + + if (!"MGF1".equals(spec.getMGFAlgorithm())) { + throw new InvalidParameterSpecException( + "Only MGF1 supported for OAEP, got: " + + spec.getMGFAlgorithm()); + } + + AlgorithmParameterSpec mgfParams = spec.getMGFParameters(); + if (!(mgfParams instanceof MGF1ParameterSpec)) { + throw new InvalidParameterSpecException( + "MGF parameters must be MGF1ParameterSpec"); + } + + String mgfDigest = ((MGF1ParameterSpec) mgfParams).getDigestAlgorithm(); + if (!isDigestSupported(mgfDigest)) { + throw new InvalidParameterSpecException( + "Unsupported MGF digest: " + mgfDigest); + } + + PSource pSource = spec.getPSource(); + if (pSource != null && pSource instanceof PSource.PSpecified) { + byte[] label = ((PSource.PSpecified) pSource).getValue(); + if (label != null && label.length > 0) { + throw new InvalidParameterSpecException( + "OAEP label (PSource) must be empty"); + } + } + + this.oaepSpec = spec; + } + + @Override + protected void engineInit(byte[] params) throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + protected void engineInit(byte[] params, String format) + throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + @SuppressWarnings("unchecked") + protected T engineGetParameterSpec( + Class paramSpec) throws InvalidParameterSpecException { + + if (this.oaepSpec == null) { + throw new InvalidParameterSpecException( + "OAEP parameters not initialized"); + } + + if (paramSpec == null) { + throw new InvalidParameterSpecException( + "Parameter spec class cannot be null"); + } + + if (paramSpec == OAEPParameterSpec.class || + paramSpec == AlgorithmParameterSpec.class) { + return (T) this.oaepSpec; + } + + throw new InvalidParameterSpecException( + "Unsupported parameter spec: " + paramSpec.getName()); + } + + @Override + protected byte[] engineGetEncoded() throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + protected byte[] engineGetEncoded(String format) throws IOException { + throw new IOException("Encoded OAEP parameters not supported"); + } + + @Override + protected String engineToString() { + if (this.oaepSpec == null) { + return "WolfCryptOaepParameters[uninitialized]"; + } + + return "WolfCryptOaepParameters[" + + "digest=" + this.oaepSpec.getDigestAlgorithm() + + ", mgf=" + this.oaepSpec.getMGFAlgorithm() + "]"; + } + + private boolean isDigestSupported(String digestAlg) { + switch (digestAlg.toUpperCase()) { + case "SHA-1": + case "SHA-224": + case "SHA-256": + case "SHA-384": + case "SHA-512": + case "SHA-512/224": + case "SHA-512/256": + return true; + default: + return false; + } + } +} diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java index 7f973e43..10800a35 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java @@ -859,6 +859,12 @@ private void registerServices() { put("Alg.Alias.Cipher.RSA/ECB/OAEPWithSHA1AndMGF1Padding", "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); } + + /* OAEP AlgorithmParameters */ + if (FeatureDetect.RsaOaepEnabled()) { + put("AlgorithmParameters.OAEP", + "com.wolfssl.provider.jce.WolfCryptOaepParameters"); + } } /* KeyAgreement */ diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java index 2017c7b8..7ec9574b 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java @@ -32,6 +32,7 @@ import java.security.spec.ECParameterSpec; import java.math.BigInteger; +import java.util.Arrays; import java.security.InvalidKeyException; import java.security.SignatureException; @@ -318,12 +319,69 @@ private void wolfCryptInitPrivateKey(PrivateKey key, byte[] encodedKey) switch (this.keyType) { - case WC_RSA: - + case WC_RSA: { /* import private PKCS#8 */ this.rsa.decodePrivateKeyPKCS8(encodedKey); + /* Defensive consistency check (n == p*q) via raw CRT export. + * Some wolfSSL builds do not compile in raw key export + * (wc_RsaExportKey -> "Feature not compiled in"); the key has + * already imported cleanly above, so skip the check on those + * builds rather than fail the operation. A genuine mismatch + * still throws InvalidKeyException below. */ + BigInteger biN = null, biP = null, biQ = null; + boolean validated = false; + int keySize = this.rsa.getEncryptSize(); + byte[] n = new byte[keySize]; + byte[] e = new byte[keySize]; + byte[] d = new byte[keySize]; + byte[] p = new byte[keySize]; + byte[] q = new byte[keySize]; + byte[] dP = new byte[keySize]; + byte[] dQ = new byte[keySize]; + byte[] u = new byte[keySize]; + long[] nSz = new long[]{keySize}; + long[] eSz = new long[]{keySize}; + long[] dSz = new long[]{keySize}; + long[] pSz = new long[]{keySize}; + long[] qSz = new long[]{keySize}; + long[] dPSz = new long[]{keySize}; + long[] dQSz = new long[]{keySize}; + long[] uSz = new long[]{keySize}; + byte[] pCopy = null; + byte[] qCopy = null; + + try { + this.rsa.exportRawPrivateKey(n, nSz, e, eSz, d, dSz, + p, pSz, q, qSz, dP, dPSz, dQ, dQSz, u, uSz); + pCopy = Arrays.copyOf(p, (int)pSz[0]); + qCopy = Arrays.copyOf(q, (int)qSz[0]); + biN = new BigInteger(1, Arrays.copyOf(n, (int)nSz[0])); + biP = new BigInteger(1, pCopy); + biQ = new BigInteger(1, qCopy); + validated = true; + } catch (WolfCryptException wce) { + /* raw key export not compiled in; skip consistency check */ + } finally { + /* Zero CRT components regardless of outcome. biP/biQ + * are immutable and cannot be wiped. */ + zeroArray(d); + zeroArray(p); + zeroArray(q); + zeroArray(pCopy); + zeroArray(qCopy); + zeroArray(dP); + zeroArray(dQ); + zeroArray(u); + } + + if (validated && !biN.equals(biP.multiply(biQ))) { + throw new InvalidKeyException( + "RSA private key modulus n does not equal p * q"); + } + break; + } case WC_ECDSA: diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherRSAOAEPTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherRSAOAEPTest.java index 48488505..003ccd78 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherRSAOAEPTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherRSAOAEPTest.java @@ -1827,5 +1827,35 @@ public void testOAEPSHA1KeyTransportForSAMLDecryption() assertArrayEquals("Decrypted SAML assertion must match original", samlAssertion, decryptedAssertion); } + + /* + * getParameters() must report the MGF1 hash actually used. SunJCE + * decrypts with the reported parameters, so a mismatch between the + * reported and applied MGF1 digest fails the decrypt. + */ + @Test + public void testOAEPGetParametersMatchesInterop() throws Exception { + + Assume.assumeTrue(oaepAvailable); + Assume.assumeNotNull(rsaPair); + Assume.assumeNotNull(interopProvider); + + byte[] plaintext = "OAEP parameters interop".getBytes(); + + Cipher encCipher = Cipher.getInstance(OAEP_ALGO, jceProvider); + encCipher.init(Cipher.ENCRYPT_MODE, rsaPair.getPublic()); + byte[] ciphertext = encCipher.doFinal(plaintext); + + OAEPParameterSpec reported = encCipher.getParameters() + .getParameterSpec(OAEPParameterSpec.class); + assertEquals("SHA-256", reported.getDigestAlgorithm()); + assertEquals("SHA-1", ((MGF1ParameterSpec) + reported.getMGFParameters()).getDigestAlgorithm()); + + Cipher interopDecrypt = Cipher.getInstance(OAEP_ALGO, interopProvider); + interopDecrypt.init(Cipher.DECRYPT_MODE, rsaPair.getPrivate(), + reported); + assertArrayEquals(plaintext, interopDecrypt.doFinal(ciphertext)); + } }