Skip to content

Commit d763849

Browse files
committed
Fix: Add support to PBMAC1 in pkcs 12 [JSS]
Add PBMAC1 (RFC 9579) support for PKCS#12 files Implement modern password-based MAC algorithm as defined in RFC 9579. Supports HMAC-SHA256/384/512 with PBKDF2 key derivation. Maintains full backward compatibility with legacy PKCS#12 v1.0 MAC. Includes comprehensive test suite and NSS interoperability validation. Coding assitant aided. Address review bot issues.
1 parent bd994f9 commit d763849

8 files changed

Lines changed: 941 additions & 7 deletions

File tree

base/src/main/java/org/mozilla/jss/netscape/security/pkcs/PKCS12Util.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,21 @@
6767
import org.mozilla.jss.pkix.primitive.Attribute;
6868
import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo;
6969
import org.mozilla.jss.util.Password;
70+
import org.mozilla.jss.crypto.DigestAlgorithm;
7071
import org.slf4j.Logger;
7172
import org.slf4j.LoggerFactory;
7273

7374
public class PKCS12Util {
7475

7576
private static Logger logger = LoggerFactory.getLogger(PKCS12Util.class);
7677

78+
79+
//Differentiate between newer PBMAC1 and older algs
80+
public static enum MacType {
81+
LEGACY,
82+
PBMAC1
83+
}
84+
7785
public final static String NO_ENCRYPTION = "none";
7886

7987
public final static List<PBEAlgorithm> SUPPORTED_CERT_ENCRYPTIONS = Arrays.asList(new PBEAlgorithm[] {
@@ -97,6 +105,26 @@ public class PKCS12Util {
97105
PBEAlgorithm keyEncryption = DEFAULT_KEY_ENCRYPTION;
98106
boolean trustFlagsEnabled = true;
99107

108+
// MAC configuration (separate from encryption)
109+
private MacType macType = MacType.LEGACY; // default for backward compatibility
110+
private DigestAlgorithm macDigest = DigestAlgorithm.SHA256; // default digest
111+
112+
public void setMacType(MacType type) {
113+
this.macType = type;
114+
}
115+
116+
public MacType getMacType() {
117+
return macType;
118+
}
119+
120+
public void setMacDigest(DigestAlgorithm digest) {
121+
this.macDigest = digest;
122+
}
123+
124+
public DigestAlgorithm getMacDigest() {
125+
return macDigest;
126+
}
127+
100128
public PKCS12Util() throws Exception {
101129
random = SecureRandom.getInstance("pkcs11prng", "Mozilla-JSS");
102130
}
@@ -603,6 +631,10 @@ public PFX generatePFX(PKCS12 pkcs12, Password password) throws Exception {
603631

604632
byte[] salt = new byte[16];
605633
random.nextBytes(salt);
634+
635+
pfx.setMacType(macType);
636+
pfx.setMacDigest(macDigest);
637+
606638
pfx.computeMacData(password, salt, 100000);
607639

608640
return pfx;

base/src/main/java/org/mozilla/jss/pkcs12/MacData.java

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.io.IOException;
99
import java.io.InputStream;
1010
import java.io.OutputStream;
11+
import java.io.ByteArrayInputStream;
12+
import java.io.ByteArrayOutputStream;
1113
import java.security.DigestException;
1214
import java.security.InvalidAlgorithmParameterException;
1315
import java.security.InvalidKeyException;
@@ -22,6 +24,8 @@
2224
import org.mozilla.jss.asn1.OCTET_STRING;
2325
import org.mozilla.jss.asn1.SEQUENCE;
2426
import org.mozilla.jss.asn1.Tag;
27+
import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
28+
import org.mozilla.jss.asn1.ANY;
2529
import org.mozilla.jss.crypto.CryptoToken;
2630
import org.mozilla.jss.crypto.DigestAlgorithm;
2731
import org.mozilla.jss.crypto.HMACAlgorithm;
@@ -32,8 +36,11 @@
3236
import org.mozilla.jss.crypto.PBEKeyGenParams;
3337
import org.mozilla.jss.crypto.SymmetricKey;
3438
import org.mozilla.jss.crypto.TokenException;
39+
import org.mozilla.jss.crypto.PBEAlgorithm;
40+
import org.mozilla.jss.pkix.primitive.PBMAC1Params;
3541
import org.mozilla.jss.pkcs7.DigestInfo;
3642
import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
43+
import org.mozilla.jss.pkix.primitive.PBKDF2Params;
3744
import org.mozilla.jss.util.Password;
3845

3946
public class MacData implements ASN1Value {
@@ -137,13 +144,30 @@ public MacData( Password password, byte[] macSalt,
137144
rand.nextBytes(macSalt);
138145
}
139146

140-
PBEKeyGenParams params = new PBEKeyGenParams(password, macSalt, iterations);
147+
// Handle null algID - default to SHA1 for backward compatibility
141148

142149
try {
143-
// generate key from password and salt
144-
if(algID == null) {
150+
if (algID == null) {
145151
algID = new AlgorithmIdentifier(DigestAlgorithm.SHA1.toOID());
146152
}
153+
154+
// Check if this is PBMAC1 - route to new implementation
155+
if (algID.getOID().equals(PBEAlgorithm.PBE_PKCS5_PBMAC1.toOID())) {
156+
computePBMAC1(token, password, toBeMACed, algID);
157+
return; // Early return - skip legacy code below
158+
}
159+
} catch (NoSuchAlgorithmException e) {
160+
throw new TokenException("Algorithm OID error: " + e.getMessage(), e);
161+
} catch (TokenException | CharConversionException e) {
162+
throw e;
163+
} catch (Exception e) {
164+
throw new TokenException("Failed to compute PBMAC1: " + e.getMessage(), e);
165+
}
166+
167+
PBEKeyGenParams params = new PBEKeyGenParams(password, macSalt, iterations);
168+
169+
try {
170+
// generate key from password and salt
147171
KeyGenerator kg = null;
148172
JSSMessageDigest digest = null;
149173
if(DigestAlgorithm.SHA1.toOID().equals(algID.getOID())){
@@ -196,6 +220,116 @@ public MacData( Password password, byte[] macSalt,
196220
}
197221
}
198222

223+
private void computePBMAC1(CryptoToken token, Password password,
224+
byte[] data, AlgorithmIdentifier algID)
225+
throws Exception
226+
{
227+
// Parse PBMAC1 parameters to extract KDF and MAC algorithms
228+
229+
PBMAC1Params pbmac1Params;
230+
ASN1Value params = algID.getParameters();
231+
232+
if (params instanceof PBMAC1Params) {
233+
// Already decoded (create/write path)
234+
pbmac1Params = (PBMAC1Params) params;
235+
} else if (params instanceof ANY) {
236+
// Needs decoding (read from file path)
237+
pbmac1Params = (PBMAC1Params) ((ANY) params).decodeWith(PBMAC1Params.getTemplate());
238+
} else {
239+
throw new Exception("Unexpected PBMAC1 parameter type: " + params.getClass().getName());
240+
}
241+
242+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
243+
244+
algID.encode(bos);
245+
byte[] pbmac1AlgIDBytes = bos.toByteArray();
246+
247+
AlgorithmIdentifier kdfAlg = pbmac1Params.getKeyDerivationFunc();
248+
AlgorithmIdentifier macAlg = pbmac1Params.getMessageAuthScheme();
249+
250+
ASN1Value kdfParams = kdfAlg.getParameters();
251+
252+
//Extract PBKDF2 parameters
253+
PBKDF2Params pbkdf2Params;
254+
255+
if (kdfParams instanceof SEQUENCE) {
256+
// Need to decode SEQUENCE bytes to PBKDF2Params
257+
ByteArrayOutputStream bosForDecode = new ByteArrayOutputStream();
258+
kdfParams.encode(bosForDecode);
259+
pbkdf2Params = (PBKDF2Params) PBKDF2Params.getTemplate().decode(
260+
new ByteArrayInputStream(bosForDecode.toByteArray()));
261+
262+
} else if (kdfParams instanceof ANY) {
263+
// Create template that knows PBKDF2 structure
264+
pbkdf2Params = (PBKDF2Params) ((ANY) kdfParams).decodeWith(PBKDF2Params.getTemplate());
265+
} else {
266+
throw new Exception("Unexpected PBKDF2 parameter type: " + kdfParams.getClass().getName());
267+
}
268+
269+
byte[] kdfSalt = pbkdf2Params.getSalt();
270+
int kdfIterations = pbkdf2Params.getIterations();
271+
272+
// Get HMAC OID from MAC AlgorithmIdentifier
273+
OBJECT_IDENTIFIER macOID = macAlg.getOID();
274+
275+
HMACAlgorithm hmacAlgorithm;
276+
if (macOID.equals(HMACAlgorithm.SHA256.toOID())) {
277+
hmacAlgorithm = HMACAlgorithm.SHA256;
278+
} else if (macOID.equals(HMACAlgorithm.SHA384.toOID())) {
279+
hmacAlgorithm = HMACAlgorithm.SHA384;
280+
} else if (macOID.equals(HMACAlgorithm.SHA512.toOID())) {
281+
hmacAlgorithm = HMACAlgorithm.SHA512;
282+
} else {
283+
throw new NoSuchAlgorithmException("Unsupported HMAC algorithm for PBMAC1: " + macOID.toString());
284+
}
285+
286+
// Call native JSS code to perform PBKDF2 + HMAC
287+
// This uses certified NSS crypto (PK11_PBEKeyGen + PK11_DigestOp)
288+
// The OID will be mapped to the appropriate NSS HMAC mechanism
289+
290+
291+
char[] passwordChars = password.getCharCopy();
292+
byte[] passwordBytes = null;
293+
294+
try {
295+
passwordBytes = Password.charToByte(passwordChars);
296+
byte[] macValue = nativeComputePBMAC1(
297+
token,
298+
passwordBytes,
299+
data,
300+
pbmac1AlgIDBytes,
301+
hmacAlgorithm
302+
);
303+
304+
this.mac = new DigestInfo(algID, new OCTET_STRING(macValue));
305+
this.macSalt = new OCTET_STRING(kdfSalt);
306+
this.macIterationCount = new INTEGER(1);
307+
} finally {
308+
if (passwordBytes != null) {
309+
Password.wipeBytes(passwordBytes);
310+
}
311+
Password.wipeChars(passwordChars);
312+
}
313+
}
314+
315+
/**
316+
* Native method to compute PBMAC1 MAC using NSS.
317+
*
318+
* @param password Password bytes
319+
* @param salt PBKDF2 salt
320+
* @param iterations PBKDF2 iteration count
321+
* @param data Data to MAC
322+
* @param hmacOID HMAC algorithm OID (e.g., hmacWithSHA256)
323+
* @return HMAC value
324+
*/
325+
private native byte[] nativeComputePBMAC1(
326+
CryptoToken token,
327+
byte[] password,
328+
byte[] data,
329+
byte[] pbmac1AlgID,
330+
HMACAlgorithm hmacAlgorithm
331+
) throws Exception;
332+
199333
///////////////////////////////////////////////////////////////////////
200334
// DER encoding
201335
///////////////////////////////////////////////////////////////////////

base/src/main/java/org/mozilla/jss/pkcs12/PFX.java

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,23 @@
2828
import org.mozilla.jss.asn1.SEQUENCE;
2929
import org.mozilla.jss.asn1.SET;
3030
import org.mozilla.jss.asn1.Tag;
31+
import org.mozilla.jss.asn1.NULL;
32+
import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
3133
import org.mozilla.jss.crypto.JSSSecureRandom;
3234
import org.mozilla.jss.crypto.PBEAlgorithm;
3335
import org.mozilla.jss.crypto.DigestAlgorithm;
3436
import org.mozilla.jss.crypto.TokenException;
37+
import org.mozilla.jss.crypto.HMACAlgorithm;
3538
import org.mozilla.jss.pkcs7.ContentInfo;
3639
import org.mozilla.jss.pkcs7.DigestInfo;
3740
import org.mozilla.jss.pkix.cert.Certificate;
3841
import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
3942
import org.mozilla.jss.pkix.primitive.Attribute;
4043
import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo;
4144
import org.mozilla.jss.pkix.primitive.PrivateKeyInfo;
45+
import org.mozilla.jss.pkix.primitive.PBMAC1Params;
4246
import org.mozilla.jss.util.Password;
47+
import org.mozilla.jss.netscape.security.pkcs.PKCS12Util.MacType;
4348

4449
/**
4550
* The top level ASN.1 structure for a PKCS #12 blob.
@@ -94,12 +99,23 @@ public class PFX implements ASN1Value {
9499
// currently we are on version 3 of the standard
95100
private static final INTEGER VERSION = new INTEGER(3);
96101

102+
// MAC configuration
103+
private MacType macType = MacType.LEGACY; // default
104+
private DigestAlgorithm macDigest = DigestAlgorithm.SHA256; // default
105+
97106
/**
98107
* The default number of iterations to use when generating the MAC.
99108
* Currently, it is 1.
100109
*/
101110
public static final int DEFAULT_ITERATIONS = 1;
102111

112+
public void setMacType(MacType type) {
113+
this.macType = type;
114+
}
115+
116+
public void setMacDigest(DigestAlgorithm digest) {
117+
this.macDigest = digest;
118+
}
103119

104120
public INTEGER getVersion() {
105121
return version;
@@ -153,6 +169,7 @@ public boolean verifyAuthSafes(Password password, StringBuffer reason)
153169

154170
// create a new MacData based on the encoded Auth Safes
155171
DigestInfo macDataMac = macData.getMac();
172+
156173
MacData testMac = new MacData(password,
157174
macData.getMacSalt().toByteArray(),
158175
macData.getMacIterationCount().intValue(),
@@ -219,12 +236,81 @@ public void computeMacData(Password password,
219236
TokenException, CharConversionException
220237
{
221238

222-
//Make this alg the default mac alg.
223-
AlgorithmIdentifier algID = new AlgorithmIdentifier(DigestAlgorithm.SHA256.toOID());
224-
macData = new MacData( password, salt, iterationCount,
225-
ASN1Util.encode(authSafes), algID );
239+
AlgorithmIdentifier algID;
240+
241+
if (salt == null) {
242+
CryptoManager cm = CryptoManager.getInstance();
243+
JSSSecureRandom rand = cm.createPseudoRandomNumberGenerator();
244+
salt = new byte[20]; // SALT_LENGTH from MacData
245+
rand.nextBytes(salt);
246+
}
247+
248+
if (macType == MacType.PBMAC1) {
249+
// Create PBMAC1 AlgorithmIdentifier with PBKDF2 parameters
250+
algID = createPBMAC1AlgorithmID(salt, iterationCount, macDigest);
251+
} else {
252+
// Legacy: Use configured digest
253+
algID = new AlgorithmIdentifier(macDigest.toOID());
254+
}
255+
256+
macData = new MacData(password, salt, iterationCount,
257+
ASN1Util.encode(authSafes), algID);
226258
}
227259

260+
private AlgorithmIdentifier createPBMAC1AlgorithmID(
261+
byte[] salt, int iterationCount, DigestAlgorithm digest)
262+
throws NoSuchAlgorithmException
263+
{
264+
// Determine HMAC OID and key length from digest algorithm
265+
// Use existing HMACAlgorithm constants instead of hardcoded OIDs
266+
267+
int keyLength = digest.getOutputSize();
268+
// Get the corresponding HMAC algorithm
269+
HMACAlgorithm hmacAlg;
270+
271+
if (digest.equals(DigestAlgorithm.SHA256)) {
272+
hmacAlg = HMACAlgorithm.SHA256;
273+
} else if (digest.equals(DigestAlgorithm.SHA384)) {
274+
hmacAlg = HMACAlgorithm.SHA384;
275+
} else if (digest.equals(DigestAlgorithm.SHA512)) {
276+
hmacAlg = HMACAlgorithm.SHA512;
277+
} else {
278+
throw new NoSuchAlgorithmException(
279+
"Unsupported PBMAC1 digest: " + digest);
280+
}
281+
282+
OBJECT_IDENTIFIER hmacOID = hmacAlg.toOID();
283+
284+
// Construct PBKDF2 parameters
285+
// PBKDF2-params ::= SEQUENCE {
286+
// salt OCTET STRING,
287+
// iterationCount INTEGER,
288+
// keyLength INTEGER OPTIONAL,
289+
// prf AlgorithmIdentifier DEFAULT hmacWithSHA1
290+
// }
291+
SEQUENCE pbkdf2Params = new SEQUENCE();
292+
pbkdf2Params.addElement(new OCTET_STRING(salt));
293+
pbkdf2Params.addElement(new INTEGER(iterationCount));
294+
pbkdf2Params.addElement(new INTEGER(keyLength));
295+
296+
// PRF algorithm for PBKDF2 (same HMAC algorithm)
297+
AlgorithmIdentifier prfAlg = new AlgorithmIdentifier(hmacOID);
298+
pbkdf2Params.addElement(prfAlg);
299+
300+
// Construct PBKDF2 AlgorithmIdentifier
301+
AlgorithmIdentifier kdfAlg = new AlgorithmIdentifier(
302+
PBEAlgorithm.PBE_PKCS5_PBKDF2.toOID(), pbkdf2Params);
303+
304+
// Construct HMAC AlgorithmIdentifier for MAC scheme
305+
AlgorithmIdentifier macAlg = new AlgorithmIdentifier(hmacOID);
306+
307+
// Construct PBMAC1 parameters using dedicated ASN.1 type
308+
PBMAC1Params pbmac1Params = new PBMAC1Params(kdfAlg, macAlg);
309+
310+
// Construct final PBMAC1 AlgorithmIdentifier
311+
return new AlgorithmIdentifier(
312+
PBEAlgorithm.PBE_PKCS5_PBMAC1.toOID(), pbmac1Params);
313+
}
228314

229315
///////////////////////////////////////////////////////////////////////
230316
// DER encoding

0 commit comments

Comments
 (0)