Skip to content

Commit dd44ecb

Browse files
committed
added support for PBEKey storage, relates to #2164
1 parent 2aa0926 commit dd44ecb

5 files changed

Lines changed: 496 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package org.bouncycastle.asn1.bc;
2+
3+
import org.bouncycastle.asn1.ASN1EncodableVector;
4+
import org.bouncycastle.asn1.ASN1Integer;
5+
import org.bouncycastle.asn1.ASN1Object;
6+
import org.bouncycastle.asn1.ASN1OctetString;
7+
import org.bouncycastle.asn1.ASN1Primitive;
8+
import org.bouncycastle.asn1.ASN1Sequence;
9+
import org.bouncycastle.asn1.ASN1TaggedObject;
10+
import org.bouncycastle.asn1.ASN1UTF8String;
11+
import org.bouncycastle.asn1.DEROctetString;
12+
import org.bouncycastle.asn1.DERSequence;
13+
import org.bouncycastle.asn1.DERTaggedObject;
14+
import org.bouncycastle.asn1.DERUTF8String;
15+
import org.bouncycastle.util.Arrays;
16+
import org.bouncycastle.util.BigIntegers;
17+
18+
/**
19+
* Carrier for the contents of a {@link javax.crypto.interfaces.PBEKey} stored
20+
* in a BCFKS keystore.
21+
* <pre>
22+
* PbkdKeyData ::= SEQUENCE {
23+
* keyAlgorithm UTF8String,
24+
* password OCTET STRING,
25+
* salt [0] IMPLICIT OCTET STRING OPTIONAL,
26+
* iterationCount [1] IMPLICIT INTEGER OPTIONAL,
27+
* encoded [2] IMPLICIT OCTET STRING OPTIONAL
28+
* }
29+
* </pre>
30+
*/
31+
public class PbkdKeyData
32+
extends ASN1Object
33+
{
34+
private final ASN1UTF8String keyAlgorithm;
35+
private final ASN1OctetString password;
36+
private final ASN1OctetString salt;
37+
private final ASN1Integer iterationCount;
38+
private final ASN1OctetString encoded;
39+
40+
public PbkdKeyData(String keyAlgorithm, byte[] password, byte[] salt, int iterationCount, byte[] encoded)
41+
{
42+
this.keyAlgorithm = new DERUTF8String(keyAlgorithm);
43+
this.password = new DEROctetString(Arrays.clone(password));
44+
this.salt = (salt != null) ? new DEROctetString(Arrays.clone(salt)) : null;
45+
this.iterationCount = (iterationCount > 0) ? new ASN1Integer(iterationCount) : null;
46+
this.encoded = (encoded != null) ? new DEROctetString(Arrays.clone(encoded)) : null;
47+
}
48+
49+
private PbkdKeyData(ASN1Sequence seq)
50+
{
51+
this.keyAlgorithm = ASN1UTF8String.getInstance(seq.getObjectAt(0));
52+
this.password = ASN1OctetString.getInstance(seq.getObjectAt(1));
53+
54+
ASN1OctetString salt = null;
55+
ASN1Integer iterationCount = null;
56+
ASN1OctetString encoded = null;
57+
58+
for (int i = 2; i != seq.size(); i++)
59+
{
60+
ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(seq.getObjectAt(i));
61+
62+
switch (tagged.getTagNo())
63+
{
64+
case 0:
65+
salt = ASN1OctetString.getInstance(tagged, false);
66+
break;
67+
case 1:
68+
iterationCount = ASN1Integer.getInstance(tagged, false);
69+
break;
70+
case 2:
71+
encoded = ASN1OctetString.getInstance(tagged, false);
72+
break;
73+
default:
74+
throw new IllegalArgumentException("unknown tag in PbkdKeyData: " + tagged.getTagNo());
75+
}
76+
}
77+
78+
this.salt = salt;
79+
this.iterationCount = iterationCount;
80+
this.encoded = encoded;
81+
}
82+
83+
public static PbkdKeyData getInstance(Object o)
84+
{
85+
if (o instanceof PbkdKeyData)
86+
{
87+
return (PbkdKeyData)o;
88+
}
89+
else if (o != null)
90+
{
91+
return new PbkdKeyData(ASN1Sequence.getInstance(o));
92+
}
93+
94+
return null;
95+
}
96+
97+
public String getKeyAlgorithm()
98+
{
99+
return keyAlgorithm.getString();
100+
}
101+
102+
public byte[] getPassword()
103+
{
104+
return Arrays.clone(password.getOctets());
105+
}
106+
107+
public byte[] getSalt()
108+
{
109+
return (salt != null) ? Arrays.clone(salt.getOctets()) : null;
110+
}
111+
112+
public int getIterationCount()
113+
{
114+
return (iterationCount != null) ? BigIntegers.intValueExact(iterationCount.getValue()) : 0;
115+
}
116+
117+
public byte[] getKeyEncoding()
118+
{
119+
return (encoded != null) ? Arrays.clone(encoded.getOctets()) : null;
120+
}
121+
122+
public ASN1Primitive toASN1Primitive()
123+
{
124+
ASN1EncodableVector v = new ASN1EncodableVector(5);
125+
v.add(keyAlgorithm);
126+
v.add(password);
127+
if (salt != null)
128+
{
129+
v.add(new DERTaggedObject(false, 0, salt));
130+
}
131+
if (iterationCount != null)
132+
{
133+
v.add(new DERTaggedObject(false, 1, iterationCount));
134+
}
135+
if (encoded != null)
136+
{
137+
v.add(new DERTaggedObject(false, 2, encoded));
138+
}
139+
return new DERSequence(v);
140+
}
141+
}

‎docs/releasenotes.html‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ <h3>2.1.2 Defects Fixed</h3>
2929
<h3>2.2.3 Additional Features and Functionality</h3>
3030
<ul>
3131
<li>The system/security property "org.bouncycastle.argon2.max_memory_exp" can now be used to set the maximum exponent for the memory setting in Argon2. Default value is (and max possible) is 30.</li>
32+
<li>BCFKS keystore now supports storing and retrieving javax.crypto.interfaces.PBEKey entries, so passwords and other PBE-based secrets no longer need to be stored as HMAC keys (issue #2164).</li>
3233
</ul>
3334

3435
<a id="r1rv84"><h3>2.2.1 Version</h3></a>

‎prov/src/main/java/org/bouncycastle/jcajce/provider/keystore/bcfks/BcFKSKeyStoreSpi.java‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import javax.crypto.NoSuchPaddingException;
4444
import javax.crypto.SecretKey;
4545
import javax.crypto.SecretKeyFactory;
46+
import javax.crypto.interfaces.PBEKey;
4647
import javax.crypto.spec.SecretKeySpec;
4748

4849
import org.bouncycastle.asn1.ASN1Encodable;
@@ -58,6 +59,7 @@
5859
import org.bouncycastle.asn1.bc.ObjectStore;
5960
import org.bouncycastle.asn1.bc.ObjectStoreData;
6061
import org.bouncycastle.asn1.bc.ObjectStoreIntegrityCheck;
62+
import org.bouncycastle.asn1.bc.PbkdKeyData;
6163
import org.bouncycastle.asn1.bc.PbkdMacIntegrityCheck;
6264
import org.bouncycastle.asn1.bc.SecretKeyData;
6365
import org.bouncycastle.asn1.bc.SignatureCheck;
@@ -165,6 +167,7 @@ private static String getPublicKeyAlg(ASN1ObjectIdentifier oid)
165167
private final static BigInteger SECRET_KEY = BigInteger.valueOf(2);
166168
private final static BigInteger PROTECTED_PRIVATE_KEY = BigInteger.valueOf(3);
167169
private final static BigInteger PROTECTED_SECRET_KEY = BigInteger.valueOf(4);
170+
private final static BigInteger PBKDF_KEY = BigInteger.valueOf(5);
168171

169172
private final JcaJceHelper helper;
170173
private final Map<String, ObjectData> entries = new HashMap<String, ObjectData>();
@@ -236,6 +239,26 @@ else if (ent.getType().equals(SECRET_KEY) || ent.getType().equals(PROTECTED_SECR
236239
throw new UnrecoverableKeyException("BCFKS KeyStore unable to recover secret key (" + alias + "): " + e.getMessage());
237240
}
238241
}
242+
else if (ent.getType().equals(PBKDF_KEY))
243+
{
244+
EncryptedSecretKeyData encKeyData = EncryptedSecretKeyData.getInstance(ent.getData());
245+
246+
try
247+
{
248+
PbkdKeyData keyData = PbkdKeyData.getInstance(decryptData("SECRET_KEY_ENCRYPTION", encKeyData.getKeyEncryptionAlgorithm(), password, encKeyData.getEncryptedKeyData()));
249+
250+
return new RecoveredPBEKey(
251+
keyData.getKeyAlgorithm(),
252+
bytesToChars(keyData.getPassword()),
253+
keyData.getSalt(),
254+
keyData.getIterationCount(),
255+
keyData.getKeyEncoding());
256+
}
257+
catch (Exception e)
258+
{
259+
throw new UnrecoverableKeyException("BCFKS KeyStore unable to recover PBE key (" + alias + "): " + e.getMessage());
260+
}
261+
}
239262
else
240263
{
241264
throw new UnrecoverableKeyException("BCFKS KeyStore unable to recover secret key (" + alias + "): type not recognized");
@@ -405,6 +428,56 @@ public void engineSetKeyEntry(String alias, Key key, char[] password, Certificat
405428
throw new ExtKeyStoreException("BCFKS KeyStore exception storing private key: " + e.toString(), e);
406429
}
407430
}
431+
else if (key instanceof PBEKey)
432+
{
433+
if (chain != null)
434+
{
435+
throw new KeyStoreException("BCFKS KeyStore cannot store certificate chain with PBE key.");
436+
}
437+
438+
try
439+
{
440+
PBEKey pbeKey = (PBEKey)key;
441+
PbkdKeyData pbeData = new PbkdKeyData(
442+
pbeKey.getAlgorithm(),
443+
charsToBytes(pbeKey.getPassword()),
444+
pbeKey.getSalt(),
445+
pbeKey.getIterationCount(),
446+
pbeKey.getEncoded());
447+
448+
KeyDerivationFunc pbkdAlgId = generatePkbdAlgorithmIdentifier(PKCSObjectIdentifiers.id_PBKDF2, 256 / 8);
449+
byte[] keyBytes = generateKey(pbkdAlgId, "SECRET_KEY_ENCRYPTION", ((password != null) ? password : new char[0]), 32);
450+
451+
EncryptedSecretKeyData keyData;
452+
if (storeEncryptionAlgorithm.equals(NISTObjectIdentifiers.id_aes256_CCM))
453+
{
454+
Cipher c = createCipher("AES/CCM/NoPadding", keyBytes);
455+
456+
byte[] encryptedKey = c.doFinal(pbeData.getEncoded());
457+
458+
AlgorithmParameters algParams = c.getParameters();
459+
460+
PBES2Parameters pbeParams = new PBES2Parameters(pbkdAlgId, new EncryptionScheme(NISTObjectIdentifiers.id_aes256_CCM, CCMParameters.getInstance(algParams.getEncoded())));
461+
462+
keyData = new EncryptedSecretKeyData(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, pbeParams), encryptedKey);
463+
}
464+
else
465+
{
466+
Cipher c = createCipher("AESKWP", keyBytes);
467+
468+
byte[] encryptedKey = c.doFinal(pbeData.getEncoded());
469+
470+
PBES2Parameters pbeParams = new PBES2Parameters(pbkdAlgId, new EncryptionScheme(NISTObjectIdentifiers.id_aes256_wrap_pad));
471+
472+
keyData = new EncryptedSecretKeyData(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, pbeParams), encryptedKey);
473+
}
474+
entries.put(alias, new ObjectData(PBKDF_KEY, alias, creationDate, lastEditDate, keyData.getEncoded(), null));
475+
}
476+
catch (Exception e)
477+
{
478+
throw new ExtKeyStoreException("BCFKS KeyStore exception storing PBE key: " + e.toString(), e);
479+
}
480+
}
408481
else if (key instanceof SecretKey)
409482
{
410483
if (chain != null)
@@ -495,6 +568,35 @@ private Cipher createCipher(String algorithm, byte[] keyBytes)
495568
return c;
496569
}
497570

571+
private static byte[] charsToBytes(char[] chars)
572+
{
573+
if (chars == null)
574+
{
575+
return new byte[0];
576+
}
577+
byte[] bytes = new byte[chars.length * 2];
578+
for (int i = 0; i != chars.length; i++)
579+
{
580+
bytes[2 * i] = (byte)(chars[i] >>> 8);
581+
bytes[2 * i + 1] = (byte)chars[i];
582+
}
583+
return bytes;
584+
}
585+
586+
private static char[] bytesToChars(byte[] bytes)
587+
{
588+
if (bytes == null || bytes.length == 0)
589+
{
590+
return new char[0];
591+
}
592+
char[] chars = new char[bytes.length / 2];
593+
for (int i = 0; i != chars.length; i++)
594+
{
595+
chars[i] = (char)(((bytes[2 * i] & 0xff) << 8) | (bytes[2 * i + 1] & 0xff));
596+
}
597+
return chars;
598+
}
599+
498600
private SecureRandom getDefaultSecureRandom()
499601
{
500602
return CryptoServicesRegistrar.getSecureRandom();
@@ -1672,4 +1774,53 @@ public Throwable getCause()
16721774
return cause;
16731775
}
16741776
}
1777+
1778+
private static class RecoveredPBEKey
1779+
implements PBEKey
1780+
{
1781+
private final String algorithm;
1782+
private final char[] password;
1783+
private final byte[] salt;
1784+
private final int iterationCount;
1785+
private final byte[] encoded;
1786+
1787+
RecoveredPBEKey(String algorithm, char[] password, byte[] salt, int iterationCount, byte[] encoded)
1788+
{
1789+
this.algorithm = algorithm;
1790+
this.password = password;
1791+
this.salt = (salt != null) ? (byte[])salt.clone() : null;
1792+
this.iterationCount = iterationCount;
1793+
this.encoded = (encoded != null) ? (byte[])encoded.clone() : null;
1794+
}
1795+
1796+
public String getAlgorithm()
1797+
{
1798+
return algorithm;
1799+
}
1800+
1801+
public String getFormat()
1802+
{
1803+
return (encoded != null) ? "RAW" : null;
1804+
}
1805+
1806+
public byte[] getEncoded()
1807+
{
1808+
return (encoded != null) ? (byte[])encoded.clone() : null;
1809+
}
1810+
1811+
public char[] getPassword()
1812+
{
1813+
return (char[])password.clone();
1814+
}
1815+
1816+
public byte[] getSalt()
1817+
{
1818+
return (salt != null) ? (byte[])salt.clone() : null;
1819+
}
1820+
1821+
public int getIterationCount()
1822+
{
1823+
return iterationCount;
1824+
}
1825+
}
16751826
}

0 commit comments

Comments
 (0)