Merge pull request #258 from cconlon/fenrirAug14

Fixes for error mapping, DH init, OCSP reason code, and documentation
pull/252/merge
Ruby Martin 2026-08-18 16:46:55 -05:00 committed by GitHub
commit 646b2b7419
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 282 additions and 65 deletions

View File

@ -1845,6 +1845,10 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPSS_1CheckPadding(
}
}
if (ret < 0) {
throwWolfCryptExceptionFromError(env, ret);
}
if (ret == 0) {
XMEMSET(pssData, 0, pssDataSz);
@ -1858,6 +1862,11 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPSS_1CheckPadding(
ret = wc_RsaPSS_CheckPadding_ex(digest, digestSz, pssData,
pssDataSz, (enum wc_HashType)hashType, saltLen, 0);
}
/* Throw OutOfMemoryError on memory error, other codes map to false */
if (ret == MEMORY_E) {
throwWolfCryptExceptionFromError(env, ret);
}
}
if (ret == 0) {
result = JNI_TRUE;

View File

@ -44,9 +44,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfObject_init
* com.wolfssl.wolfcrypt.Fips.runAllCast_fips() method. runAllCast_fips()
* includes a synchronized check that only runs the CASTs once as long
* as they were successful. Fips.runAllCast_fips() is called at both
* the JNI-only level (WolfObject.init()), and the JCE level
* (WolfCryptProvider constructor). Both of these runAllCast_fips()
* at JNI/JCE levels are called before this wolfCrypt_Init() below. */
* the JNI-only level (WolfObject static initializer), and the JCE level
* (WolfCryptProvider constructor). Both of these calls happen after
* this wolfCrypt_Init() below has returned. */
return (jint)wolfCrypt_Init();
}

View File

@ -470,37 +470,35 @@ public class WolfCryptKeyAgreement extends KeyAgreementSpi {
"AlgorithmParameterSpec is not of type DHParameterSpec");
}
/* reject spec that conflicts with the key's own parameters */
if (dhKey.getParams() != null &&
(!dhKey.getParams().getP().equals(
((DHParameterSpec)params).getP()) ||
!dhKey.getParams().getG().equals(
((DHParameterSpec)params).getG()))) {
throw new InvalidAlgorithmParameterException(
"DHParameterSpec does not match key parameters");
}
paramP = ((DHParameterSpec)params).getP().toByteArray();
paramG = ((DHParameterSpec)params).getG().toByteArray();
if (paramP != null && paramG != null) {
this.dh.setParams(paramP, paramG);
primeLen = paramP.length;
/* prime may have leading zero */
if (paramP[0] == 0x00) {
primeLen--;
}
return;
} else {
if (paramP == null || paramG == null) {
throw new InvalidParameterException(
"AlgorithmParameterSpec does not include required " +
"DH parameters (P,G)");
}
}
else {
/* try to import params from key */
paramP = dhKey.getParams().getP().toByteArray();
paramG = dhKey.getParams().getG().toByteArray();
/* try to import params from key */
paramP = dhKey.getParams().getP().toByteArray();
paramG = dhKey.getParams().getG().toByteArray();
if (paramP == null || paramG == null) {
throw new InvalidKeyException(
"Key must include DH parameters when not called " +
"with explicit AlgorithmParameterSpec");
if (paramP == null || paramG == null) {
throw new InvalidKeyException(
"Key must include DH parameters when not called " +
"with explicit AlgorithmParameterSpec");
}
}
this.dh.setParams(paramP, paramG);

View File

@ -76,11 +76,10 @@ import com.wolfssl.wolfcrypt.WolfCryptException;
* 1. Certificate policies, and the related setters/getters. As such,
* validation will not return PolicyNode in CertPathBuilderResult
*
* Revocation checking is supported via:
* - CRL: If PKIXParameters.isRevocationEnabled() is true and appropriate
* CRLs have been loaded into CertStore Set
* - OCSP: via getRevocationChecker() which returns a
* WolfCryptPKIXRevocationChecker supporting OCSP and options
* 2. CRL and OCSP revocation checking during path building, configured
* CertPathCheckers are not executed by engineBuild(). To check
* revocation, validate the returned CertPath with CertPathValidator
* and PKIXParameters configured for revocation.
*/
public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
@ -1205,14 +1204,13 @@ public class WolfCryptPKIXCertPathBuilder extends CertPathBuilderSpi {
}
/**
* Returns a CertPathChecker that this implementation uses to check
* the revocation status of certificates.
* Returns a WolfCryptPKIXRevocationChecker supporting OCSP and CRL
* checking.
*
* This implementation returns a WolfCryptPKIXRevocationChecker that
* supports both OCSP and CRL checking.
* Path building does not execute this checker. To check revocation, add
* it to PKIXParameters and validate the built path with CertPathValidator.
*
* @return a CertPathChecker object that this implementation uses to
* check the revocation status of certificates.
* @return a WolfCryptPKIXRevocationChecker for use with CertPathValidator
*/
@Override
public CertPathChecker engineGetRevocationChecker() {

View File

@ -1020,11 +1020,13 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi {
* a. CertPath.getType() is "X.509"
* b. CertPath.getEncoding() contains "PkiPath"
* 3. If wolfCrypt FIPS, verify params.getSigProvider() is wolfJCE
* 4. Sanitize Certificate objects in CertPath chain
* 4. Load TrustAnchors into WolfSSLCertManager, done before checker
* initialization since OCSP needs anchors to verify responses
* 5. Initialize any registered CertPathCheckers
* 6. Sanitize Certificate objects in CertPath chain
* a. Check target certificate constraints meet target cert
* b. Check cert policies are not used (not supported)
* 5. Call any registered CertPathCheckers
* 6. Load TrustAnchors into WolfSSLCertManager
* c. Call registered CertPathCheckers on each certificate
* 7. Enable CRL if requested, load CRLs from getCertStores()
* 8. Verify X.509 certificate chain
* 9. Find top-most TrustAnchor for return object

View File

@ -330,16 +330,7 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker {
"Failed to encode certificate", e);
} catch (WolfCryptException e) {
/* A definitive OCSP "revoked" status must always be a hard fail,
* with BasicReason.REVOKED. Other codes SOFT_FAIL may suppress. */
if (e.getCode() == WolfCryptError.OCSP_CERT_REVOKED.getCode()) {
throw new CertPathValidatorException(
"Certificate revoked (OCSP): " + e.getMessage(), e,
null, -1, BasicReason.REVOKED);
}
throw new CertPathValidatorException(
"OCSP check failed: " + e.getMessage(), e,
null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
throw mapOcspException("OCSP check failed", e);
}
}
@ -503,12 +494,34 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker {
"Failed to encode certificate", e);
} catch (WolfCryptException e) {
throw new CertPathValidatorException(
"OCSP response check failed: " + e.getMessage(), e,
null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
throw mapOcspException("OCSP response check failed", e);
}
}
/**
* Map a native OCSP exception to CertPathValidatorException. An OCSP
* "revoked" status must always be a hard fail with BasicReason.REVOKED.
* Other codes map to UNDETERMINED_REVOCATION_STATUS, which SOFT_FAIL may
* suppress.
*
* @param msgPrefix message prefix for non-revoked failures
* @param e native exception from OCSP check
*
* @return CertPathValidatorException for caller to throw
*/
private CertPathValidatorException mapOcspException(String msgPrefix,
WolfCryptException e) {
if (e.getCode() == WolfCryptError.OCSP_CERT_REVOKED.getCode()) {
return new CertPathValidatorException(
"Certificate revoked (OCSP): " + e.getMessage(), e,
null, -1, BasicReason.REVOKED);
}
return new CertPathValidatorException(
msgPrefix + ": " + e.getMessage(), e,
null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
}
/**
* Handle exception based on SOFT_FAIL option.
*

View File

@ -1249,7 +1249,6 @@ public class WolfCryptSignature extends SignatureSpi {
/* Validate spec type and MGF1 inner digest */
AlgorithmParameterSpec mgfParams = pss.getMGFParameters();
if (mgfParams != null) {
/* Different spec type silently falls back to the default digest */
if (!(mgfParams instanceof MGF1ParameterSpec)) {
throw new InvalidAlgorithmParameterException(
"MGF1 parameters must be of type MGF1ParameterSpec, got " +

View File

@ -2544,8 +2544,8 @@ public class WolfSSLKeyStore extends KeyStoreSpi {
* Load the KeyStore from the provided InputStream.
*
* @param stream InputStream from which to load KeyStore
* @param password password used to check KeyStore integrity, must not
* be null
* @param password password used to check KeyStore integrity, may be
* null or empty to skip the whole KeyStore HMAC integrity check
*
* @throws IOException on I/O problem or issue with the
* KeyStore data format

View File

@ -217,10 +217,10 @@ public class AesCcm extends NativeStruct {
* @param authIn additional data to be authenticated but not encrypted,
* can be null if no additional data desired or available.
*
* @return encrypted cipertext buffer
* @return encrypted ciphertext buffer
*
* @throws WolfCryptException if native operation fails
* @throws IllegalStateException if key has already been set, if object
* @throws IllegalStateException if key has not been set, if object
* fails to initialize, or if releaseNativeStruct() has been
* called and object has been released.
*/
@ -251,7 +251,7 @@ public class AesCcm extends NativeStruct {
* @return decrypted plaintext buffer
*
* @throws WolfCryptException if native operation fails
* @throws IllegalStateException if key has already been set, if object
* @throws IllegalStateException if key has not been set, if object
* fails to initialize, or if releaseNativeStruct() has been
* called and object has been released.
*/

View File

@ -336,13 +336,21 @@ public class AesCmac extends NativeStruct {
*
* @return true if verification succeeds, false otherwise
*
* @throws WolfCryptException if native operation fails
* @throws WolfCryptException if the native operation fails for any
* reason other than MAC comparison mismatch
*/
public static synchronized boolean verify(byte[] mac, byte[] data,
byte[] key) throws WolfCryptException {
int ret = wc_AesCmacVerify(mac, mac.length, data, data.length,
key, key.length);
key, key.length);
/* Native returns MAC_CMP_FAILED_E for a MAC mismatch, wolfSSL
* versions before 5.9.2 return 1 */
if ((ret != 0) && (ret != 1) &&
(ret != WolfCryptError.MAC_CMP_FAILED_E.getCode())) {
throw new WolfCryptException(ret);
}
return (ret == 0);
}

View File

@ -218,10 +218,10 @@ public class AesGcm extends NativeStruct {
* @param authIn additional data to be authenticated but not encrypted,
* can be null if no additional data desired or available.
*
* @return encrypted cipertext buffer
* @return encrypted ciphertext buffer
*
* @throws WolfCryptException if native operation fails
* @throws IllegalStateException if key has already been set, if object
* @throws IllegalStateException if key has not been set, if object
* fails to initialize, or if releaseNativeStruct() has been
* called and object has been released.
*/
@ -252,7 +252,7 @@ public class AesGcm extends NativeStruct {
* @return decrypted plaintext buffer
*
* @throws WolfCryptException if native operation fails
* @throws IllegalStateException if key has already been set, if object
* @throws IllegalStateException if key has not been set, if object
* fails to initialize, or if releaseNativeStruct() has been
* called and object has been released.
*/

View File

@ -952,9 +952,12 @@ public class Rsa extends NativeStruct {
* @param mgf mask generation function (WC_MGF1SHA256 for MGF1 with SHA-256)
* @param saltLen salt length in bytes, or special value
*
* @return true if padding is valid, false otherwise
* @return true if padding is valid, false otherwise, including for
* hashType, mgf, or saltLen values rejected during decode
*
* @throws WolfCryptException if native operation fails
* @throws WolfCryptException on invalid arguments detected before
* signature decode
* @throws OutOfMemoryError on memory allocation failure
* @throws IllegalStateException if object fails to initialize, or if
* releaseNativeStruct() has been called and object has been
* released.

View File

@ -405,6 +405,50 @@ public class WolfCryptKeyAgreementTest {
assertArrayEquals(secretA, secretB);
}
@Test
public void testDHKeyAgreementWithExplicitParams()
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidKeyException, InvalidAlgorithmParameterException {
/* skip test if DH is not compiled in native wolfSSL */
if (!FeatureDetect.DhEnabled()) {
return;
}
byte[][] pg = Dh.getNamedDhParams(Dh.WC_FFDHE_2048);
BigInteger p = new BigInteger(1, pg[0]);
BigInteger g = new BigInteger(1, pg[1]);
DHParameterSpec dhParams = new DHParameterSpec(p, g);
KeyPairGenerator keyGen =
KeyPairGenerator.getInstance("DH", "wolfJCE");
keyGen.initialize(dhParams);
KeyPair aPair = keyGen.generateKeyPair();
KeyPair bPair = keyGen.generateKeyPair();
/* init() with explicit DHParameterSpec must import private key */
KeyAgreement aKeyAgree = KeyAgreement.getInstance("DH", "wolfJCE");
KeyAgreement bKeyAgree = KeyAgreement.getInstance("DH", "wolfJCE");
aKeyAgree.init(aPair.getPrivate(), dhParams);
bKeyAgree.init(bPair.getPrivate(), dhParams);
aKeyAgree.doPhase(bPair.getPublic(), true);
bKeyAgree.doPhase(aPair.getPublic(), true);
byte secretA[] = aKeyAgree.generateSecret();
byte secretB[] = bKeyAgree.generateSecret();
assertTrue(secretA.length > 0);
assertArrayEquals(secretA, secretB);
/* spec that conflicts with key params must be rejected */
try {
aKeyAgree.init(aPair.getPrivate(),
new DHParameterSpec(p, g.add(BigInteger.ONE)));
fail("init() should reject mismatched DHParameterSpec");
} catch (InvalidAlgorithmParameterException e) {
/* expected */
}
}
/* Minimal DHPublicKey holding a caller-chosen Y, for feeding malicious
* peer public key values into engineDoPhase() during testing. */
private static DHPublicKey makeDHPublicKey(final BigInteger y,

View File

@ -55,6 +55,7 @@ import com.wolfssl.wolfcrypt.WolfSSLCertManager;
import com.wolfssl.wolfcrypt.WolfCryptException;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
import com.wolfssl.wolfcrypt.test.Util;
import com.wolfssl.provider.jce.WolfCryptProvider;
import com.wolfssl.provider.jce.WolfCryptPKIXRevocationChecker;
@ -873,6 +874,89 @@ public class WolfCryptPKIXRevocationCheckerTest {
cm.free();
}
/* DER encoded OCSP response for server-cert.der, status successful, signed
* by ca-cert.pem, generated with openssl ocsp. Contains no nextUpdate so
* it never expires. Only needs to pass native response status parsing,
* response verification itself is mocked below. */
private static final String OCSP_RESPONSE_HEX =
"308202330a0100a082022c3082022806092b0601050507300101048202193082" +
"02153081fea18197308194310b30090603550406130255533110300e06035504" +
"080c074d6f6e74616e613110300e06035504070c07426f7a656d616e3111300f" +
"060355040a0c08536177746f6f746831133011060355040b0c0a436f6e73756c" +
"74696e673118301606035504030c0f7777772e776f6c6673736c2e636f6d311f" +
"301d06092a864886f70d0109011610696e666f40776f6c6673736c2e636f6d18" +
"0f32303236303831343232353035335a3051304f303a300906052b0e03021a05" +
"000414ff66218a6ec5866184259abad65539fb25512cdd0414278e671174c326" +
"1d3fed3363b3a4d81d30e5e8d50201018000180f323032363038313432323530" +
"35335a300d06092a864886f70d01010b0500038201010076d97a9999d345fc6f" +
"071b127d806865c588e0bea677757ec370396aea7020ef0db922251f8ba6bb45" +
"8abd4aeb310d9ff024b733a483ed8c8b7cfd116e003b5db9ab5d69f640973c93" +
"856562fef0d4eeef3115877c3867bcfbe5910b47fad0e5c9dfa1d487bc6048be" +
"7a0900a7505d9a2a59210921c359a95ebc94f12d2b8def4489a26a39253be4a7" +
"6084f4f42dfc46d800536ebbff8bc85111408e221b6d5e8462588994d4ca338b" +
"62d22bf0496dc53677f91892a6036040216851124f3abdb781d6c96a0358b0ad" +
"a97563122e9c1d8fd99d2cd38b78373f0ad4c841dc11727fdbeac44efd9a75da" +
"060e70d1c02774b5de05d3bd9a9fced3c73d65d3cc750d";
/* CertManager that simulates a definitive OCSP "revoked" status for a
* pre-loaded response by throwing WolfCryptException(OCSP_CERT_REVOKED)
* from CertManagerCheckOCSPResponse, like native wolfSSL does for a
* revoked certificate. */
private static class RevokedOcspResponseCertManager
extends WolfSSLCertManager {
@Override
public synchronized void CertManagerCheckOCSPResponse(
byte[] response, int responseSz, byte[] cert, int certSz) {
throw new WolfCryptException(
WolfCryptError.OCSP_CERT_REVOKED.getCode());
}
}
@Test
public void testPreloadedOcspRevokedUsesRevokedReason() throws Exception {
if (!WolfCrypt.OcspEnabled()) {
/* Skip test if OCSP not compiled in */
return;
}
CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider);
WolfCryptPKIXRevocationChecker checker =
(WolfCryptPKIXRevocationChecker)cpv.getRevocationChecker();
FileInputStream fis = new FileInputStream(caCertDer);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(fis);
fis.close();
fis = new FileInputStream(serverCertDer);
X509Certificate serverCert =
(X509Certificate)cf.generateCertificate(fis);
fis.close();
/* Preload OCSP response so check() takes the stapled path */
Map<X509Certificate, byte[]> responses =
new HashMap<X509Certificate, byte[]>();
responses.put(serverCert, Util.h2b(OCSP_RESPONSE_HEX));
checker.setOcspResponses(responses);
WolfSSLCertManager cm = new RevokedOcspResponseCertManager();
cm.CertManagerLoadCA(caCert);
checker.setCertManager(cm);
checker.init(false);
try {
checker.check(serverCert, null);
fail("A revoked pre-loaded OCSP status must fail validation");
} catch (CertPathValidatorException e) {
assertEquals("Revoked cert must fail with BasicReason.REVOKED",
BasicReason.REVOKED, e.getReason());
}
cm.free();
}
@Test
public void testRevocationCheckerCheckWithCertChain() throws Exception {

View File

@ -45,6 +45,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import com.wolfssl.wolfcrypt.Aes;
import com.wolfssl.wolfcrypt.Fips;
import com.wolfssl.wolfcrypt.AesCmac;
import com.wolfssl.wolfcrypt.FeatureDetect;
import com.wolfssl.wolfcrypt.NativeStruct;
import com.wolfssl.wolfcrypt.WolfCryptError;
import com.wolfssl.wolfcrypt.WolfCryptException;
@ -220,6 +221,33 @@ public class AesCmacTest {
}
}
@Test
public void aesCmacVerifyInvalidArgsShouldThrow() {
if (!FeatureDetect.AesCmacEnabled()) {
/* skip test if AES-CMAC is not compiled in native wolfCrypt */
return;
}
byte[] mac = new byte[16];
byte[] data = new byte[16];
/* zero length key is an operational error, not a MAC mismatch */
try {
AesCmac.verify(mac, data, new byte[0]);
fail("verify() should have thrown for zero length key");
} catch (WolfCryptException e) {
/* expected */
}
/* invalid key length is an operational error, not a MAC mismatch */
try {
AesCmac.verify(mac, data, new byte[5]);
fail("verify() should have thrown for invalid key length");
} catch (WolfCryptException e) {
/* expected */
}
}
@Test
public void aesCmacAlgorithmInfoShouldWork() {
try {

View File

@ -1509,6 +1509,37 @@ public class RsaTest {
WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 32);
assertTrue("RSA-PSS check padding failed", verified);
/* Corrupted signatures must report false, not throw. PSS decode
* of garbage fails with position dependent error codes, exercise
* first, middle and last bytes */
int[] flipPos = new int[] { 0, signature.length / 2,
signature.length - 1 };
for (int pos : flipPos) {
byte[] badSig = signature.clone();
badSig[pos] ^= (byte)0x80;
verified = key.rsaPssCheckPadding(badSig, digest,
WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 32);
assertFalse("corrupted signature passed padding check, " +
"flipped byte " + pos, verified);
}
/* Mismatched digest must report false, not throw */
byte[] wrongDigest = digest.clone();
wrongDigest[0] ^= 1;
verified = key.rsaPssCheckPadding(signature, wrongDigest,
WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 32);
assertFalse("padding check passed with wrong digest", verified);
/* Operational errors must throw, not report false */
try {
key.rsaPssCheckPadding(signature, new byte[0],
WolfCrypt.WC_HASH_TYPE_SHA256, Rsa.WC_MGF1SHA256, 32);
fail("rsaPssCheckPadding should have thrown for empty " +
"digest");
} catch (WolfCryptException e) {
/* expected */
}
} catch (Exception e) {
fail("RSA-PSS check padding test failed: " + e.getMessage());
}