mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8301553: Support Password-Based Cryptography in SunPKCS11
Co-authored-by: Francisco Ferrari Bihurriet <fferrari@redhat.com> Co-authored-by: Martin Balao <mbalao@openjdk.org> Reviewed-by: valeriep
This commit is contained in:
parent
0a4f9ad637
commit
4a75fd462c
30 changed files with 3013 additions and 452 deletions
355
src/java.base/share/classes/sun/security/util/PBEUtil.java
Normal file
355
src/java.base/share/classes/sun/security/util/PBEUtil.java
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Red Hat, Inc.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.security.util;
|
||||
|
||||
import java.security.AlgorithmParameters;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.InvalidParameterSpecException;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.PBEParameterSpec;
|
||||
|
||||
public final class PBEUtil {
|
||||
|
||||
/*
|
||||
* PBES2Params is an auxiliary class that represents the state needed for
|
||||
* PBES2 operations (iterations count, salt and IV) and its (re)
|
||||
* initialization logic. Users of this class are CipherSpi implementors that
|
||||
* support PBES2 cryptography (RFC #8018), such as PBES2Core (SunJCE) and
|
||||
* P11PBECipher (SunPKCS11).
|
||||
*
|
||||
* CipherSpi implementors must call ::getPBEKeySpec in every engine
|
||||
* initialization (CipherSpi::engineInit override) to reset the state and
|
||||
* get new values in a PBEKeySpec instance. These new values are taken
|
||||
* from parameters, defaults or generated randomly.
|
||||
*
|
||||
* After engine initialization, values in effect can be extracted with
|
||||
* ::getAlgorithmParameters (as AlgorithmParameters) or ::getIvSpec (as
|
||||
* IvParameterSpec).
|
||||
*/
|
||||
public static final class PBES2Params {
|
||||
private static final int DEFAULT_SALT_LENGTH = 20;
|
||||
private static final int DEFAULT_ITERATIONS = 4096;
|
||||
|
||||
private int iCount;
|
||||
private byte[] salt;
|
||||
private IvParameterSpec ivSpec;
|
||||
|
||||
/*
|
||||
* Initialize a PBES2Params instance. May generate random salt and
|
||||
* IV if not passed and the operation is encryption. If initialization
|
||||
* fails, values are reset. Used by PBES2Params and P11PBECipher
|
||||
* (SunPKCS11).
|
||||
*/
|
||||
public void initialize(int blkSize, int opmode, int iCount, byte[] salt,
|
||||
AlgorithmParameterSpec ivSpec, SecureRandom random)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
try {
|
||||
boolean doEncrypt = opmode == Cipher.ENCRYPT_MODE ||
|
||||
opmode == Cipher.WRAP_MODE;
|
||||
if (ivSpec instanceof IvParameterSpec iv) {
|
||||
this.ivSpec = iv;
|
||||
} else if (ivSpec == null && doEncrypt) {
|
||||
byte[] ivBytes = new byte[blkSize];
|
||||
random.nextBytes(ivBytes);
|
||||
this.ivSpec = new IvParameterSpec(ivBytes);
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException("Wrong " +
|
||||
"parameter type: IvParameterSpec " +
|
||||
(doEncrypt ? "or null " : "") + "expected");
|
||||
}
|
||||
this.iCount = iCount == 0 ? DEFAULT_ITERATIONS : iCount;
|
||||
if (salt == null) {
|
||||
if (doEncrypt) {
|
||||
salt = new byte[DEFAULT_SALT_LENGTH];
|
||||
random.nextBytes(salt);
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException("Salt " +
|
||||
"needed for decryption");
|
||||
}
|
||||
}
|
||||
this.salt = salt;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
this.ivSpec = null;
|
||||
this.iCount = 0;
|
||||
this.salt = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain an IvParameterSpec for Cipher services. This method returns
|
||||
* null when the state is not initialized. Used by PBES2Core (SunJCE)
|
||||
* and P11PBECipher (SunPKCS11).
|
||||
*/
|
||||
public IvParameterSpec getIvSpec() {
|
||||
return ivSpec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain AlgorithmParameters for Cipher services. This method will
|
||||
* initialize PBES2Params if needed, generating new values randomly or
|
||||
* assigning from defaults. If PBES2Params is initialized, existing
|
||||
* values will be returned. Used by PBES2Core (SunJCE) and
|
||||
* P11PBECipher (SunPKCS11).
|
||||
*/
|
||||
public AlgorithmParameters getAlgorithmParameters(int blkSize,
|
||||
String pbeAlgo, Provider algParamsProv, SecureRandom random) {
|
||||
AlgorithmParameters params;
|
||||
try {
|
||||
if (iCount == 0 && salt == null && ivSpec == null) {
|
||||
initialize(blkSize, Cipher.ENCRYPT_MODE, 0, null, null,
|
||||
random);
|
||||
}
|
||||
params = AlgorithmParameters.getInstance(pbeAlgo,
|
||||
algParamsProv);
|
||||
params.init(new PBEParameterSpec(salt, iCount, ivSpec));
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
// should never happen
|
||||
throw new RuntimeException("AlgorithmParameters for "
|
||||
+ pbeAlgo + " not configured");
|
||||
} catch (InvalidParameterSpecException ipse) {
|
||||
// should never happen
|
||||
throw new RuntimeException("PBEParameterSpec not supported");
|
||||
} catch (InvalidAlgorithmParameterException iape) {
|
||||
// should never happen
|
||||
throw new RuntimeException("Error initializing PBES2Params");
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize PBES2Params and obtain a PBEKeySpec for Cipher services.
|
||||
* Data from the key, parameters, defaults or random may be used for
|
||||
* initialization. Used by PBES2Core (SunJCE) and P11PBECipher
|
||||
* (SunPKCS11).
|
||||
*/
|
||||
public PBEKeySpec getPBEKeySpec(int blkSize, int keyLength, int opmode,
|
||||
Key key, AlgorithmParameterSpec params, SecureRandom random)
|
||||
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
if (key == null) {
|
||||
throw new InvalidKeyException("Null key");
|
||||
}
|
||||
byte[] passwdBytes;
|
||||
char[] passwdChars = null;
|
||||
if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) ||
|
||||
(passwdBytes = key.getEncoded()) == null) {
|
||||
throw new InvalidKeyException("Missing password");
|
||||
}
|
||||
try {
|
||||
int iCountInit;
|
||||
byte[] saltInit;
|
||||
AlgorithmParameterSpec ivSpecInit;
|
||||
// Extract from the supplied PBE params, if present
|
||||
if (params instanceof PBEParameterSpec pbeParams) {
|
||||
// salt should be non-null per PBEParameterSpec
|
||||
iCountInit = check(pbeParams.getIterationCount());
|
||||
saltInit = check(pbeParams.getSalt());
|
||||
ivSpecInit = pbeParams.getParameterSpec();
|
||||
} else if (params == null) {
|
||||
// Try extracting from the key if present. If unspecified,
|
||||
// PBEKey returns 0 and null respectively.
|
||||
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
|
||||
iCountInit = check(pbeKey.getIterationCount());
|
||||
saltInit = check(pbeKey.getSalt());
|
||||
} else {
|
||||
iCountInit = 0;
|
||||
saltInit = null;
|
||||
}
|
||||
ivSpecInit = null;
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Wrong parameter type: PBE expected");
|
||||
}
|
||||
initialize(blkSize, opmode, iCountInit, saltInit, ivSpecInit,
|
||||
random);
|
||||
passwdChars = new char[passwdBytes.length];
|
||||
for (int i = 0; i < passwdChars.length; i++) {
|
||||
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
|
||||
}
|
||||
return new PBEKeySpec(passwdChars, salt, iCount, keyLength);
|
||||
} finally {
|
||||
// password char[] was cloned in PBEKeySpec constructor,
|
||||
// so we can zero it out here
|
||||
if (passwdChars != null) Arrays.fill(passwdChars, '\0');
|
||||
if (passwdBytes != null) Arrays.fill(passwdBytes, (byte)0x00);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain an AlgorithmParameterSpec from an AlgorithmParameters
|
||||
* instance, for Cipher services. Used by PBES2Core (SunJCE) and
|
||||
* P11PBECipher (SunPKCS11).
|
||||
*/
|
||||
public static AlgorithmParameterSpec getParameterSpec(
|
||||
AlgorithmParameters params)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
AlgorithmParameterSpec pbeSpec = null;
|
||||
if (params != null) {
|
||||
try {
|
||||
pbeSpec = params.getParameterSpec(PBEParameterSpec.class);
|
||||
} catch (InvalidParameterSpecException ipse) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Wrong parameter type: PBE expected");
|
||||
}
|
||||
}
|
||||
return pbeSpec;
|
||||
}
|
||||
|
||||
private static byte[] check(byte[] salt)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
if (salt != null && salt.length < 8) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt must be at least 8 bytes long");
|
||||
}
|
||||
return salt;
|
||||
}
|
||||
|
||||
private static int check(int iCount)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
if (iCount < 0) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Iteration count must be a positive number");
|
||||
}
|
||||
return iCount;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain a PBEKeySpec for Mac services, after key and parameters
|
||||
* validation. Used by HmacPKCS12PBECore (SunJCE) and P11Mac (SunPKCS11).
|
||||
*/
|
||||
public static PBEKeySpec getPBAKeySpec(Key key,
|
||||
AlgorithmParameterSpec params)
|
||||
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
char[] passwdChars;
|
||||
byte[] salt = null;
|
||||
int iCount = 0;
|
||||
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
|
||||
passwdChars = pbeKey.getPassword();
|
||||
salt = pbeKey.getSalt(); // maybe null if unspecified
|
||||
iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified
|
||||
} else if (key instanceof SecretKey) {
|
||||
byte[] passwdBytes;
|
||||
if (!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3)) ||
|
||||
(passwdBytes = key.getEncoded()) == null) {
|
||||
throw new InvalidKeyException("Missing password");
|
||||
}
|
||||
passwdChars = new char[passwdBytes.length];
|
||||
for (int i = 0; i < passwdChars.length; i++) {
|
||||
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
|
||||
}
|
||||
Arrays.fill(passwdBytes, (byte)0x00);
|
||||
} else {
|
||||
throw new InvalidKeyException("SecretKey of PBE type required");
|
||||
}
|
||||
|
||||
try {
|
||||
if (params == null) {
|
||||
// should not auto-generate default values since current
|
||||
// javax.crypto.Mac api does not have any method for caller to
|
||||
// retrieve the generated defaults.
|
||||
if ((salt == null) || (iCount == 0)) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"PBEParameterSpec required for salt " +
|
||||
"and iteration count");
|
||||
}
|
||||
} else if (!(params instanceof PBEParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"PBEParameterSpec type required");
|
||||
} else {
|
||||
PBEParameterSpec pbeParams = (PBEParameterSpec) params;
|
||||
// make sure the parameter values are consistent
|
||||
if (salt != null) {
|
||||
if (!Arrays.equals(salt, pbeParams.getSalt())) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Inconsistent value of salt " +
|
||||
"between key and params");
|
||||
}
|
||||
} else {
|
||||
salt = pbeParams.getSalt();
|
||||
}
|
||||
if (iCount != 0) {
|
||||
if (iCount != pbeParams.getIterationCount()) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Different iteration count " +
|
||||
"between key and params");
|
||||
}
|
||||
} else {
|
||||
iCount = pbeParams.getIterationCount();
|
||||
}
|
||||
}
|
||||
// For security purpose, we need to enforce a minimum length
|
||||
// for salt; just require the minimum salt length to be 8-byte
|
||||
// which is what PKCS#5 recommends and openssl does.
|
||||
if (salt.length < 8) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt must be at least 8 bytes long");
|
||||
}
|
||||
if (iCount <= 0) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"IterationCount must be a positive number");
|
||||
}
|
||||
return new PBEKeySpec(passwdChars, salt, iCount);
|
||||
} finally {
|
||||
Arrays.fill(passwdChars, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the key implements the PBEKey interface. If params is an
|
||||
* instance of PBEParameterSpec, validate consistency with the key's
|
||||
* derivation data. Used by P11Mac and P11PBECipher (SunPKCS11).
|
||||
*/
|
||||
public static void checkKeyAndParams(Key key,
|
||||
AlgorithmParameterSpec params, String algorithm)
|
||||
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
if (key instanceof javax.crypto.interfaces.PBEKey pbeKey) {
|
||||
if (params instanceof PBEParameterSpec pbeParams) {
|
||||
if (pbeParams.getIterationCount() !=
|
||||
pbeKey.getIterationCount() ||
|
||||
!Arrays.equals(pbeParams.getSalt(), pbeKey.getSalt())) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt or iteration count parameters are " +
|
||||
"not consistent with PBE key");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new InvalidKeyException(
|
||||
"Cannot use a " + algorithm + " service with a key that " +
|
||||
"does not implement javax.crypto.interfaces.PBEKey");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue