8209129: Further improvements to cipher buffer management
Reviewed-by: weijun, igerasim
This commit is contained in:
parent
58e2f2d41c
commit
50ec35819d
src/java.base/share/classes
com/sun/crypto/provider
HmacPKCS12PBESHA1.javaKeyProtector.javaPBEKey.javaPBES1Core.javaPBES2Core.javaPBKDF2KeyImpl.javaPBMAC1Core.javaPKCS12PBECipherCore.java
sun/security/provider
test/jdk/com/sun/crypto/provider/Cipher/PBE
@ -73,62 +73,69 @@ public final class HmacPKCS12PBESHA1 extends HmacCore {
|
||||
salt = pbeKey.getSalt(); // maybe null if unspecified
|
||||
iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified
|
||||
} else if (key instanceof SecretKey) {
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
if ((passwdBytes == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
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");
|
||||
}
|
||||
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)) {
|
||||
|
||||
byte[] derivedKey;
|
||||
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 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");
|
||||
}
|
||||
("PBEParameterSpec type required");
|
||||
} else {
|
||||
salt = pbeParams.getSalt();
|
||||
}
|
||||
if (iCount != 0) {
|
||||
if (iCount != pbeParams.getIterationCount()) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Different iteration count between key and params");
|
||||
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();
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
derivedKey = PKCS12PBECipherCore.derive(passwdChars, salt,
|
||||
iCount, engineGetMacLength(), PKCS12PBECipherCore.MAC_KEY);
|
||||
} finally {
|
||||
Arrays.fill(passwdChars, '\0');
|
||||
}
|
||||
// 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");
|
||||
}
|
||||
byte[] derivedKey = PKCS12PBECipherCore.derive(passwdChars, salt,
|
||||
iCount, engineGetMacLength(), PKCS12PBECipherCore.MAC_KEY);
|
||||
SecretKey cipherKey = new SecretKeySpec(derivedKey, "HmacSHA1");
|
||||
super.engineInit(cipherKey, null);
|
||||
}
|
||||
|
@ -310,14 +310,14 @@ final class KeyProtector {
|
||||
Cipher cipher;
|
||||
try {
|
||||
sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
|
||||
pbeKeySpec.clearPassword();
|
||||
pbeKeySpec.clearPassword();
|
||||
|
||||
// seal key
|
||||
PBEWithMD5AndTripleDESCipher cipherSpi;
|
||||
cipherSpi = new PBEWithMD5AndTripleDESCipher();
|
||||
cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
|
||||
"PBEWithMD5AndTripleDES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
|
||||
// seal key
|
||||
PBEWithMD5AndTripleDESCipher cipherSpi;
|
||||
cipherSpi = new PBEWithMD5AndTripleDESCipher();
|
||||
cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
|
||||
"PBEWithMD5AndTripleDES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
|
||||
} finally {
|
||||
if (sKey != null) sKey.destroy();
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ final class PBEKey implements SecretKey {
|
||||
this.key = new byte[passwd.length];
|
||||
for (int i=0; i<passwd.length; i++)
|
||||
this.key[i] = (byte) (passwd[i] & 0x7f);
|
||||
Arrays.fill(passwd, ' ');
|
||||
Arrays.fill(passwd, '\0');
|
||||
type = keytype;
|
||||
|
||||
// Use the cleaner to zero the key when no longer referenced
|
||||
|
@ -27,6 +27,7 @@ package com.sun.crypto.provider;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.*;
|
||||
|
||||
@ -213,35 +214,43 @@ final class PBES1Core {
|
||||
throw new InvalidAlgorithmParameterException("Parameters "
|
||||
+ "missing");
|
||||
}
|
||||
if ((key == null) ||
|
||||
(key.getEncoded() == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
throw new InvalidKeyException("Missing password");
|
||||
if (key == null) {
|
||||
throw new InvalidKeyException("Null key");
|
||||
}
|
||||
|
||||
if (params == null) {
|
||||
// create random salt and use default iteration count
|
||||
salt = new byte[8];
|
||||
random.nextBytes(salt);
|
||||
} else {
|
||||
if (!(params instanceof PBEParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Wrong parameter type: PBE expected");
|
||||
byte[] derivedKey;
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
try {
|
||||
if ((passwdBytes == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
throw new InvalidKeyException("Missing password");
|
||||
}
|
||||
salt = ((PBEParameterSpec) params).getSalt();
|
||||
// salt must be 8 bytes long (by definition)
|
||||
if (salt.length != 8) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Salt must be 8 bytes long");
|
||||
}
|
||||
iCount = ((PBEParameterSpec) params).getIterationCount();
|
||||
if (iCount <= 0) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("IterationCount must be a positive number");
|
||||
}
|
||||
}
|
||||
|
||||
byte[] derivedKey = deriveCipherKey(key);
|
||||
if (params == null) {
|
||||
// create random salt and use default iteration count
|
||||
salt = new byte[8];
|
||||
random.nextBytes(salt);
|
||||
} else {
|
||||
if (!(params instanceof PBEParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Wrong parameter type: PBE expected");
|
||||
}
|
||||
salt = ((PBEParameterSpec) params).getSalt();
|
||||
// salt must be 8 bytes long (by definition)
|
||||
if (salt.length != 8) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Salt must be 8 bytes long");
|
||||
}
|
||||
iCount = ((PBEParameterSpec) params).getIterationCount();
|
||||
if (iCount <= 0) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("IterationCount must be a positive number");
|
||||
}
|
||||
}
|
||||
derivedKey = deriveCipherKey(passwdBytes);
|
||||
} finally {
|
||||
if (passwdBytes != null) Arrays.fill(passwdBytes, (byte) 0x00);
|
||||
}
|
||||
// use all but the last 8 bytes as the key value
|
||||
SecretKeySpec cipherKey = new SecretKeySpec(derivedKey, 0,
|
||||
derivedKey.length-8, algo);
|
||||
@ -253,16 +262,14 @@ final class PBES1Core {
|
||||
cipher.init(opmode, cipherKey, ivSpec, random);
|
||||
}
|
||||
|
||||
private byte[] deriveCipherKey(Key key) {
|
||||
private byte[] deriveCipherKey(byte[] passwdBytes) {
|
||||
|
||||
byte[] result = null;
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
|
||||
if (algo.equals("DES")) {
|
||||
// P || S (password concatenated with salt)
|
||||
byte[] concat = new byte[Math.addExact(passwdBytes.length, salt.length)];
|
||||
System.arraycopy(passwdBytes, 0, concat, 0, passwdBytes.length);
|
||||
java.util.Arrays.fill(passwdBytes, (byte)0x00);
|
||||
System.arraycopy(salt, 0, concat, passwdBytes.length, salt.length);
|
||||
|
||||
// digest P || S with c iterations
|
||||
@ -271,7 +278,7 @@ final class PBES1Core {
|
||||
md.update(toBeHashed);
|
||||
toBeHashed = md.digest(); // this resets the digest
|
||||
}
|
||||
java.util.Arrays.fill(concat, (byte)0x00);
|
||||
Arrays.fill(concat, (byte)0x00);
|
||||
result = toBeHashed;
|
||||
} else if (algo.equals("DESede")) {
|
||||
// if the 2 salt halves are the same, invert one of them
|
||||
@ -294,8 +301,6 @@ final class PBES1Core {
|
||||
// Concatenate the output from each digest round with the
|
||||
// password, and use the result as the input to the next digest
|
||||
// operation.
|
||||
byte[] kBytes = null;
|
||||
IvParameterSpec iv = null;
|
||||
byte[] toBeHashed = null;
|
||||
result = new byte[DESedeKeySpec.DES_EDE_KEY_LEN +
|
||||
DESConstants.DES_BLOCK_SIZE];
|
||||
@ -306,12 +311,14 @@ final class PBES1Core {
|
||||
for (int j=0; j < iCount; j++) {
|
||||
md.update(toBeHashed);
|
||||
md.update(passwdBytes);
|
||||
toBeHashed = md.digest(); // this resets the digest
|
||||
toBeHashed = md.digest();
|
||||
}
|
||||
System.arraycopy(toBeHashed, 0, result, i*16,
|
||||
toBeHashed.length);
|
||||
}
|
||||
}
|
||||
// clear data used in message
|
||||
md.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -478,9 +485,9 @@ final class PBES1Core {
|
||||
byte[] wrap(Key key)
|
||||
throws IllegalBlockSizeException, InvalidKeyException {
|
||||
byte[] result = null;
|
||||
|
||||
byte[] encodedKey = null;
|
||||
try {
|
||||
byte[] encodedKey = key.getEncoded();
|
||||
encodedKey = key.getEncoded();
|
||||
if ((encodedKey == null) || (encodedKey.length == 0)) {
|
||||
throw new InvalidKeyException("Cannot get an encoding of " +
|
||||
"the key to be wrapped");
|
||||
@ -489,6 +496,8 @@ final class PBES1Core {
|
||||
result = doFinal(encodedKey, 0, encodedKey.length);
|
||||
} catch (BadPaddingException e) {
|
||||
// Should never happen
|
||||
} finally {
|
||||
if (encodedKey != null) Arrays.fill(encodedKey, (byte)0x00);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -27,6 +27,7 @@ package com.sun.crypto.provider;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.*;
|
||||
|
||||
@ -173,101 +174,105 @@ abstract class PBES2Core extends CipherSpi {
|
||||
SecureRandom random)
|
||||
throws InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
|
||||
if ((key == null) ||
|
||||
(key.getEncoded() == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
throw new InvalidKeyException("Missing password");
|
||||
if (key == null) {
|
||||
throw new InvalidKeyException("Null key");
|
||||
}
|
||||
|
||||
// TBD: consolidate the salt, ic and IV parameter checks below
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
char[] passwdChars = null;
|
||||
PBEKeySpec pbeSpec;
|
||||
try {
|
||||
if ((passwdBytes == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
throw new InvalidKeyException("Missing password");
|
||||
}
|
||||
|
||||
// Extract salt and iteration count from the key, if present
|
||||
if (key instanceof javax.crypto.interfaces.PBEKey) {
|
||||
salt = ((javax.crypto.interfaces.PBEKey)key).getSalt();
|
||||
if (salt != null && salt.length < 8) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt must be at least 8 bytes long");
|
||||
}
|
||||
iCount = ((javax.crypto.interfaces.PBEKey)key).getIterationCount();
|
||||
if (iCount == 0) {
|
||||
iCount = DEFAULT_COUNT;
|
||||
} else if (iCount < 0) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Iteration count must be a positive number");
|
||||
}
|
||||
}
|
||||
// TBD: consolidate the salt, ic and IV parameter checks below
|
||||
|
||||
// Extract salt, iteration count and IV from the params, if present
|
||||
if (params == null) {
|
||||
if (salt == null) {
|
||||
// generate random salt and use default iteration count
|
||||
salt = new byte[DEFAULT_SALT_LENGTH];
|
||||
random.nextBytes(salt);
|
||||
iCount = DEFAULT_COUNT;
|
||||
// Extract salt and iteration count from the key, if present
|
||||
if (key instanceof javax.crypto.interfaces.PBEKey) {
|
||||
salt = ((javax.crypto.interfaces.PBEKey)key).getSalt();
|
||||
if (salt != null && salt.length < 8) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt must be at least 8 bytes long");
|
||||
}
|
||||
iCount = ((javax.crypto.interfaces.PBEKey)key).getIterationCount();
|
||||
if (iCount == 0) {
|
||||
iCount = DEFAULT_COUNT;
|
||||
} else if (iCount < 0) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Iteration count must be a positive number");
|
||||
}
|
||||
}
|
||||
if ((opmode == Cipher.ENCRYPT_MODE) ||
|
||||
|
||||
// Extract salt, iteration count and IV from the params, if present
|
||||
if (params == null) {
|
||||
if (salt == null) {
|
||||
// generate random salt and use default iteration count
|
||||
salt = new byte[DEFAULT_SALT_LENGTH];
|
||||
random.nextBytes(salt);
|
||||
iCount = DEFAULT_COUNT;
|
||||
}
|
||||
if ((opmode == Cipher.ENCRYPT_MODE) ||
|
||||
(opmode == Cipher.WRAP_MODE)) {
|
||||
// generate random IV
|
||||
byte[] ivBytes = new byte[blkSize];
|
||||
random.nextBytes(ivBytes);
|
||||
ivSpec = new IvParameterSpec(ivBytes);
|
||||
}
|
||||
} else {
|
||||
if (!(params instanceof PBEParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Wrong parameter type: PBE expected");
|
||||
}
|
||||
// salt and iteration count from the params take precedence
|
||||
byte[] specSalt = ((PBEParameterSpec) params).getSalt();
|
||||
if (specSalt != null && specSalt.length < 8) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt must be at least 8 bytes long");
|
||||
}
|
||||
salt = specSalt;
|
||||
int specICount = ((PBEParameterSpec) params).getIterationCount();
|
||||
if (specICount == 0) {
|
||||
specICount = DEFAULT_COUNT;
|
||||
} else if (specICount < 0) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Iteration count must be a positive number");
|
||||
}
|
||||
iCount = specICount;
|
||||
// generate random IV
|
||||
byte[] ivBytes = new byte[blkSize];
|
||||
random.nextBytes(ivBytes);
|
||||
ivSpec = new IvParameterSpec(ivBytes);
|
||||
}
|
||||
} else {
|
||||
if (!(params instanceof PBEParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Wrong parameter type: PBE expected");
|
||||
}
|
||||
// salt and iteration count from the params take precedence
|
||||
byte[] specSalt = ((PBEParameterSpec) params).getSalt();
|
||||
if (specSalt != null && specSalt.length < 8) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Salt must be at least 8 bytes long");
|
||||
}
|
||||
salt = specSalt;
|
||||
int specICount = ((PBEParameterSpec) params).getIterationCount();
|
||||
if (specICount == 0) {
|
||||
specICount = DEFAULT_COUNT;
|
||||
} else if (specICount < 0) {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Iteration count must be a positive number");
|
||||
}
|
||||
iCount = specICount;
|
||||
|
||||
AlgorithmParameterSpec specParams =
|
||||
((PBEParameterSpec) params).getParameterSpec();
|
||||
if (specParams != null) {
|
||||
if (specParams instanceof IvParameterSpec) {
|
||||
ivSpec = (IvParameterSpec)specParams;
|
||||
AlgorithmParameterSpec specParams =
|
||||
((PBEParameterSpec) params).getParameterSpec();
|
||||
if (specParams != null) {
|
||||
if (specParams instanceof IvParameterSpec) {
|
||||
ivSpec = (IvParameterSpec)specParams;
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Wrong parameter type: IV expected");
|
||||
}
|
||||
} else if ((opmode == Cipher.ENCRYPT_MODE) ||
|
||||
(opmode == Cipher.WRAP_MODE)) {
|
||||
// generate random IV
|
||||
byte[] ivBytes = new byte[blkSize];
|
||||
random.nextBytes(ivBytes);
|
||||
ivSpec = new IvParameterSpec(ivBytes);
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Wrong parameter type: IV expected");
|
||||
"Missing parameter type: IV expected");
|
||||
}
|
||||
} else if ((opmode == Cipher.ENCRYPT_MODE) ||
|
||||
(opmode == Cipher.WRAP_MODE)) {
|
||||
// generate random IV
|
||||
byte[] ivBytes = new byte[blkSize];
|
||||
random.nextBytes(ivBytes);
|
||||
ivSpec = new IvParameterSpec(ivBytes);
|
||||
} else {
|
||||
throw new InvalidAlgorithmParameterException(
|
||||
"Missing parameter type: IV expected");
|
||||
}
|
||||
}
|
||||
|
||||
SecretKeySpec cipherKey = null;
|
||||
byte[] derivedKey = null;
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
char[] passwdChars = new char[passwdBytes.length];
|
||||
passwdChars = new char[passwdBytes.length];
|
||||
for (int i = 0; i < passwdChars.length; i++)
|
||||
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
|
||||
|
||||
for (int i=0; i<passwdChars.length; i++)
|
||||
passwdChars[i] = (char) (passwdBytes[i] & 0x7f);
|
||||
|
||||
PBEKeySpec pbeSpec =
|
||||
new PBEKeySpec(passwdChars, salt, iCount, keyLength);
|
||||
pbeSpec = new PBEKeySpec(passwdChars, salt, iCount, keyLength);
|
||||
// password char[] was cloned in PBEKeySpec constructor,
|
||||
// so we can zero it out here
|
||||
java.util.Arrays.fill(passwdChars, ' ');
|
||||
java.util.Arrays.fill(passwdBytes, (byte)0x00);
|
||||
} finally {
|
||||
if (passwdChars != null) Arrays.fill(passwdChars, '\0');
|
||||
if (passwdBytes != null) Arrays.fill(passwdBytes, (byte)0x00);
|
||||
}
|
||||
|
||||
SecretKey s = null;
|
||||
|
||||
@ -280,8 +285,8 @@ abstract class PBES2Core extends CipherSpi {
|
||||
ike.initCause(ikse);
|
||||
throw ike;
|
||||
}
|
||||
derivedKey = s.getEncoded();
|
||||
cipherKey = new SecretKeySpec(derivedKey, cipherAlgo);
|
||||
byte[] derivedKey = s.getEncoded();
|
||||
SecretKeySpec cipherKey = new SecretKeySpec(derivedKey, cipherAlgo);
|
||||
|
||||
// initialize the underlying cipher
|
||||
cipher.init(opmode, cipherKey, ivSpec, random);
|
||||
|
@ -93,46 +93,50 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
|
||||
}
|
||||
// Convert the password from char[] to byte[]
|
||||
byte[] passwdBytes = getPasswordBytes(this.passwd);
|
||||
// remove local copy
|
||||
if (passwd != null) Arrays.fill(passwd, '\0');
|
||||
|
||||
this.salt = keySpec.getSalt();
|
||||
if (salt == null) {
|
||||
throw new InvalidKeySpecException("Salt not found");
|
||||
}
|
||||
this.iterCount = keySpec.getIterationCount();
|
||||
if (iterCount == 0) {
|
||||
throw new InvalidKeySpecException("Iteration count not found");
|
||||
} else if (iterCount < 0) {
|
||||
throw new InvalidKeySpecException("Iteration count is negative");
|
||||
}
|
||||
int keyLength = keySpec.getKeyLength();
|
||||
if (keyLength == 0) {
|
||||
throw new InvalidKeySpecException("Key length not found");
|
||||
} else if (keyLength < 0) {
|
||||
throw new InvalidKeySpecException("Key length is negative");
|
||||
}
|
||||
try {
|
||||
this.salt = keySpec.getSalt();
|
||||
if (salt == null) {
|
||||
throw new InvalidKeySpecException("Salt not found");
|
||||
}
|
||||
this.iterCount = keySpec.getIterationCount();
|
||||
if (iterCount == 0) {
|
||||
throw new InvalidKeySpecException("Iteration count not found");
|
||||
} else if (iterCount < 0) {
|
||||
throw new InvalidKeySpecException("Iteration count is negative");
|
||||
}
|
||||
int keyLength = keySpec.getKeyLength();
|
||||
if (keyLength == 0) {
|
||||
throw new InvalidKeySpecException("Key length not found");
|
||||
} else if (keyLength < 0) {
|
||||
throw new InvalidKeySpecException("Key length is negative");
|
||||
}
|
||||
this.prf = Mac.getInstance(prfAlgo);
|
||||
// SunPKCS11 requires a non-empty PBE password
|
||||
if (passwdBytes.length == 0 &&
|
||||
this.prf.getProvider().getName().startsWith("SunPKCS11")) {
|
||||
this.prf.getProvider().getName().startsWith("SunPKCS11")) {
|
||||
this.prf = Mac.getInstance(prfAlgo, SunJCE.getInstance());
|
||||
}
|
||||
this.key = deriveKey(prf, passwdBytes, salt, iterCount, keyLength);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
// not gonna happen; re-throw just in case
|
||||
InvalidKeySpecException ike = new InvalidKeySpecException();
|
||||
ike.initCause(nsae);
|
||||
throw ike;
|
||||
}
|
||||
this.key = deriveKey(prf, passwdBytes, salt, iterCount, keyLength);
|
||||
} finally {
|
||||
Arrays.fill(passwdBytes, (byte) 0x00);
|
||||
|
||||
// Use the cleaner to zero the key when no longer referenced
|
||||
final byte[] k = this.key;
|
||||
final char[] p = this.passwd;
|
||||
CleanerFactory.cleaner().register(this,
|
||||
() -> {
|
||||
java.util.Arrays.fill(k, (byte)0x00);
|
||||
java.util.Arrays.fill(p, '0');
|
||||
});
|
||||
// Use the cleaner to zero the key when no longer referenced
|
||||
final byte[] k = this.key;
|
||||
final char[] p = this.passwd;
|
||||
CleanerFactory.cleaner().register(this,
|
||||
() -> {
|
||||
Arrays.fill(k, (byte) 0x00);
|
||||
Arrays.fill(p, '\0');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] deriveKey(final Mac prf, final byte[] password,
|
||||
@ -266,8 +270,8 @@ final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
|
||||
if (!(that.getFormat().equalsIgnoreCase("RAW")))
|
||||
return false;
|
||||
byte[] thatEncoded = that.getEncoded();
|
||||
boolean ret = MessageDigest.isEqual(key, that.getEncoded());
|
||||
java.util.Arrays.fill(thatEncoded, (byte)0x00);
|
||||
boolean ret = MessageDigest.isEqual(key, thatEncoded);
|
||||
Arrays.fill(thatEncoded, (byte)0x00);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -108,72 +108,76 @@ abstract class PBMAC1Core extends HmacCore {
|
||||
salt = pbeKey.getSalt(); // maybe null if unspecified
|
||||
iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified
|
||||
} else if (key instanceof SecretKey) {
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
if ((passwdBytes == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
PBEKeySpec pbeSpec =
|
||||
new PBEKeySpec(passwdChars, salt, iCount, blockLength);
|
||||
PBEKeySpec pbeSpec;
|
||||
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");
|
||||
}
|
||||
|
||||
pbeSpec = new PBEKeySpec(passwdChars, salt, iCount, blockLength);
|
||||
// password char[] was cloned in PBEKeySpec constructor,
|
||||
// so we can zero it out here
|
||||
java.util.Arrays.fill(passwdChars, ' ');
|
||||
} finally {
|
||||
Arrays.fill(passwdChars, '\0');
|
||||
}
|
||||
|
||||
SecretKey s = null;
|
||||
SecretKey s;
|
||||
PBKDF2Core kdf = getKDFImpl(kdfAlgo);
|
||||
try {
|
||||
s = kdf.engineGenerateSecret(pbeSpec);
|
||||
|
||||
} catch (InvalidKeySpecException ikse) {
|
||||
InvalidKeyException ike =
|
||||
new InvalidKeyException("Cannot construct PBE key");
|
||||
|
@ -104,6 +104,7 @@ final class PKCS12PBECipherCore {
|
||||
Arrays.fill(D, (byte)type);
|
||||
concat(salt, I, 0, s);
|
||||
concat(passwd, I, s, p);
|
||||
Arrays.fill(passwd, (byte) 0x00);
|
||||
|
||||
byte[] Ai;
|
||||
byte[] B = new byte[v];
|
||||
@ -268,87 +269,92 @@ final class PKCS12PBECipherCore {
|
||||
salt = pbeKey.getSalt(); // maybe null if unspecified
|
||||
iCount = pbeKey.getIterationCount(); // maybe 0 if unspecified
|
||||
} else if (key instanceof SecretKey) {
|
||||
byte[] passwdBytes = key.getEncoded();
|
||||
if ((passwdBytes == null) ||
|
||||
!(key.getAlgorithm().regionMatches(true, 0, "PBE", 0, 3))) {
|
||||
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");
|
||||
}
|
||||
|
||||
if (((opmode == Cipher.DECRYPT_MODE) ||
|
||||
(opmode == Cipher.UNWRAP_MODE)) &&
|
||||
((params == null) && ((salt == null) || (iCount == 0)))) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Parameters missing");
|
||||
}
|
||||
try {
|
||||
if (((opmode == Cipher.DECRYPT_MODE) ||
|
||||
(opmode == Cipher.UNWRAP_MODE)) &&
|
||||
((params == null) && ((salt == null) || (iCount == 0)))) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Parameters missing");
|
||||
}
|
||||
|
||||
if (params == null) {
|
||||
// generate default for salt and iteration count if necessary
|
||||
if (salt == null) {
|
||||
salt = new byte[DEFAULT_SALT_LENGTH];
|
||||
if (random != null) {
|
||||
random.nextBytes(salt);
|
||||
if (params == null) {
|
||||
// generate default for salt and iteration count if necessary
|
||||
if (salt == null) {
|
||||
salt = new byte[DEFAULT_SALT_LENGTH];
|
||||
if (random != null) {
|
||||
random.nextBytes(salt);
|
||||
} else {
|
||||
SunJCE.getRandom().nextBytes(salt);
|
||||
}
|
||||
}
|
||||
if (iCount == 0) iCount = DEFAULT_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 {
|
||||
SunJCE.getRandom().nextBytes(salt);
|
||||
salt = pbeParams.getSalt();
|
||||
}
|
||||
if (iCount != 0) {
|
||||
if (iCount != pbeParams.getIterationCount()) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Different iteration count between key and params");
|
||||
}
|
||||
} else {
|
||||
iCount = pbeParams.getIterationCount();
|
||||
}
|
||||
}
|
||||
if (iCount == 0) iCount = DEFAULT_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");
|
||||
}
|
||||
// salt is recommended to be ideally as long as the output
|
||||
// of the hash function. However, it may be too strict to
|
||||
// force this; so instead, we'll 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");
|
||||
}
|
||||
byte[] derivedKey = derive(passwdChars, salt, iCount,
|
||||
keySize, CIPHER_KEY);
|
||||
SecretKey cipherKey = new SecretKeySpec(derivedKey, algo);
|
||||
|
||||
if (cipherImpl != null && cipherImpl instanceof ARCFOURCipher) {
|
||||
((ARCFOURCipher)cipherImpl).engineInit(opmode, cipherKey, random);
|
||||
|
||||
} else {
|
||||
salt = pbeParams.getSalt();
|
||||
byte[] derivedIv = derive(passwdChars, salt, iCount, 8,
|
||||
CIPHER_IV);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(derivedIv, 0, 8);
|
||||
|
||||
// initialize the underlying cipher
|
||||
cipher.init(opmode, cipherKey, ivSpec, random);
|
||||
}
|
||||
if (iCount != 0) {
|
||||
if (iCount != pbeParams.getIterationCount()) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("Different iteration count between key and params");
|
||||
}
|
||||
} else {
|
||||
iCount = pbeParams.getIterationCount();
|
||||
}
|
||||
}
|
||||
// salt is recommended to be ideally as long as the output
|
||||
// of the hash function. However, it may be too strict to
|
||||
// force this; so instead, we'll 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");
|
||||
}
|
||||
byte[] derivedKey = derive(passwdChars, salt, iCount,
|
||||
keySize, CIPHER_KEY);
|
||||
SecretKey cipherKey = new SecretKeySpec(derivedKey, algo);
|
||||
|
||||
if (cipherImpl != null && cipherImpl instanceof ARCFOURCipher) {
|
||||
((ARCFOURCipher)cipherImpl).engineInit(opmode, cipherKey, random);
|
||||
|
||||
} else {
|
||||
byte[] derivedIv = derive(passwdChars, salt, iCount, 8,
|
||||
CIPHER_IV);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(derivedIv, 0, 8);
|
||||
|
||||
// initialize the underlying cipher
|
||||
cipher.init(opmode, cipherKey, ivSpec, random);
|
||||
} finally {
|
||||
Arrays.fill(passwdChars, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ package sun.security.provider;
|
||||
import java.security.MessageDigestSpi;
|
||||
import java.security.DigestException;
|
||||
import java.security.ProviderException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.internal.HotSpotIntrinsicCandidate;
|
||||
@ -178,6 +179,7 @@ abstract class DigestBase extends MessageDigestSpi implements Cloneable {
|
||||
implReset();
|
||||
bufOfs = 0;
|
||||
bytesProcessed = 0;
|
||||
Arrays.fill(buffer, (byte) 0x00);
|
||||
}
|
||||
|
||||
// return the digest. See JCA doc.
|
||||
|
@ -26,6 +26,7 @@
|
||||
package sun.security.provider;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static sun.security.provider.ByteArrayAccess.*;
|
||||
import static sun.security.util.SecurityConstants.PROVIDER_VER;
|
||||
@ -92,7 +93,7 @@ public final class MD4 extends DigestBase {
|
||||
super("MD4", 16, 64);
|
||||
state = new int[4];
|
||||
x = new int[16];
|
||||
implReset();
|
||||
resetHashes();
|
||||
}
|
||||
|
||||
// clone this object
|
||||
@ -108,6 +109,12 @@ public final class MD4 extends DigestBase {
|
||||
*/
|
||||
void implReset() {
|
||||
// Load magic initialization constants.
|
||||
resetHashes();
|
||||
// clear out old data
|
||||
Arrays.fill(x, 0);
|
||||
}
|
||||
|
||||
private void resetHashes() {
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xefcdab89;
|
||||
state[2] = 0x98badcfe;
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package sun.security.provider;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static sun.security.provider.ByteArrayAccess.*;
|
||||
|
||||
/**
|
||||
@ -66,7 +68,7 @@ public final class MD5 extends DigestBase {
|
||||
super("MD5", 16, 64);
|
||||
state = new int[4];
|
||||
x = new int[16];
|
||||
implReset();
|
||||
resetHashes();
|
||||
}
|
||||
|
||||
// clone this object
|
||||
@ -82,6 +84,12 @@ public final class MD5 extends DigestBase {
|
||||
*/
|
||||
void implReset() {
|
||||
// Load magic initialization constants.
|
||||
resetHashes();
|
||||
// clear out old data
|
||||
Arrays.fill(x, 0);
|
||||
}
|
||||
|
||||
private void resetHashes() {
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xefcdab89;
|
||||
state[2] = 0x98badcfe;
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package sun.security.provider;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static sun.security.provider.ByteArrayAccess.*;
|
||||
@ -62,7 +63,7 @@ public final class SHA extends DigestBase {
|
||||
super("SHA-1", 20, 64);
|
||||
state = new int[5];
|
||||
W = new int[80];
|
||||
implReset();
|
||||
resetHashes();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -79,6 +80,13 @@ public final class SHA extends DigestBase {
|
||||
* Resets the buffers and hash value to start a new hash.
|
||||
*/
|
||||
void implReset() {
|
||||
// Load magic initialization constants.
|
||||
resetHashes();
|
||||
// clear out old data
|
||||
Arrays.fill(W, 0);
|
||||
}
|
||||
|
||||
private void resetHashes() {
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xefcdab89;
|
||||
state[2] = 0x98badcfe;
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package sun.security.provider;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.internal.HotSpotIntrinsicCandidate;
|
||||
@ -83,13 +84,18 @@ abstract class SHA2 extends DigestBase {
|
||||
this.initialHashes = initialHashes;
|
||||
state = new int[8];
|
||||
W = new int[64];
|
||||
implReset();
|
||||
resetHashes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffers and hash value to start a new hash.
|
||||
*/
|
||||
void implReset() {
|
||||
resetHashes();
|
||||
Arrays.fill(W, 0);
|
||||
}
|
||||
|
||||
private void resetHashes() {
|
||||
System.arraycopy(initialHashes, 0, state, 0, state.length);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package sun.security.provider;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.internal.HotSpotIntrinsicCandidate;
|
||||
@ -98,10 +99,15 @@ abstract class SHA5 extends DigestBase {
|
||||
this.initialHashes = initialHashes;
|
||||
state = new long[8];
|
||||
W = new long[80];
|
||||
implReset();
|
||||
resetHashes();
|
||||
}
|
||||
|
||||
final void implReset() {
|
||||
resetHashes();
|
||||
Arrays.fill(W, 0L);
|
||||
}
|
||||
|
||||
private void resetHashes() {
|
||||
System.arraycopy(initialHashes, 0, state, 0, state.length);
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ class MyPBEKey implements PBEKey {
|
||||
this.salt = salt;
|
||||
this.iCount = iCount;
|
||||
}
|
||||
public char[] getPassword() { return passwd; }
|
||||
public char[] getPassword() { return passwd.clone(); }
|
||||
public byte[] getSalt() { return salt; }
|
||||
public int getIterationCount() { return iCount; }
|
||||
public String getAlgorithm() { return "PBE"; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user