Skip to content

Commit 91f6f92

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.
1 parent bd994f9 commit 91f6f92

8 files changed

Lines changed: 932 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: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.mozilla.jss.asn1.OCTET_STRING;
2323
import org.mozilla.jss.asn1.SEQUENCE;
2424
import org.mozilla.jss.asn1.Tag;
25+
import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
26+
import org.mozilla.jss.asn1.ANY;
2527
import org.mozilla.jss.crypto.CryptoToken;
2628
import org.mozilla.jss.crypto.DigestAlgorithm;
2729
import org.mozilla.jss.crypto.HMACAlgorithm;
@@ -32,6 +34,8 @@
3234
import org.mozilla.jss.crypto.PBEKeyGenParams;
3335
import org.mozilla.jss.crypto.SymmetricKey;
3436
import org.mozilla.jss.crypto.TokenException;
37+
import org.mozilla.jss.crypto.PBEAlgorithm;
38+
import org.mozilla.jss.pkix.primitive.PBMAC1Params;
3539
import org.mozilla.jss.pkcs7.DigestInfo;
3640
import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
3741
import org.mozilla.jss.util.Password;
@@ -137,13 +141,28 @@ public MacData( Password password, byte[] macSalt,
137141
rand.nextBytes(macSalt);
138142
}
139143

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

142146
try {
143-
// generate key from password and salt
144-
if(algID == null) {
147+
if (algID == null) {
145148
algID = new AlgorithmIdentifier(DigestAlgorithm.SHA1.toOID());
146149
}
150+
151+
// Check if this is PBMAC1 - route to new implementation
152+
if (algID.getOID().equals(PBEAlgorithm.PBE_PKCS5_PBMAC1.toOID())) {
153+
computePBMAC1(token,password, macSalt, iterations, toBeMACed, algID);
154+
return; // Early return - skip legacy code below
155+
}
156+
} catch (NoSuchAlgorithmException e) {
157+
throw new RuntimeException("Algorithm OID error: " + e.getMessage(), e);
158+
} catch (Exception e) {
159+
throw new RuntimeException("Failed to compute PBMAC1: " + e.getMessage(), e);
160+
}
161+
162+
PBEKeyGenParams params = new PBEKeyGenParams(password, macSalt, iterations);
163+
164+
try {
165+
// generate key from password and salt
147166
KeyGenerator kg = null;
148167
JSSMessageDigest digest = null;
149168
if(DigestAlgorithm.SHA1.toOID().equals(algID.getOID())){
@@ -196,6 +215,113 @@ public MacData( Password password, byte[] macSalt,
196215
}
197216
}
198217

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

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;
@@ -117,6 +133,13 @@ public MacData getMacData() {
117133
return macData;
118134
}
119135

136+
// Native method declaration
137+
private native AlgorithmIdentifier nativeCreatePBMAC1AlgorithmID(
138+
byte[] salt,
139+
int iterationCount,
140+
OBJECT_IDENTIFIER hmacOID
141+
) throws Exception;
142+
120143
private void setEncodedAuthSafes(byte[] encodedAuthSafes) {
121144
this.encodedAuthSafes = encodedAuthSafes;
122145
}
@@ -219,12 +242,75 @@ public void computeMacData(Password password,
219242
TokenException, CharConversionException
220243
{
221244

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 );
245+
AlgorithmIdentifier algID;
246+
247+
if (macType == MacType.PBMAC1) {
248+
// Create PBMAC1 AlgorithmIdentifier with PBKDF2 parameters
249+
algID = createPBMAC1AlgorithmID(salt, iterationCount, macDigest);
250+
} else {
251+
// Legacy: Use digest algorithm directly for HMAC
252+
algID = new AlgorithmIdentifier(macDigest.toOID());
253+
}
254+
255+
macData = new MacData(password, salt, iterationCount,
256+
ASN1Util.encode(authSafes), algID);
226257
}
227258

259+
private AlgorithmIdentifier createPBMAC1AlgorithmID(
260+
byte[] salt, int iterationCount, DigestAlgorithm digest)
261+
throws NoSuchAlgorithmException
262+
{
263+
// Determine HMAC OID and key length from digest algorithm
264+
// Use existing HMACAlgorithm constants instead of hardcoded OIDs
265+
266+
int keyLength = digest.getOutputSize();
267+
// Get the corresponding HMAC algorithm
268+
HMACAlgorithm hmacAlg;
269+
270+
if (digest.equals(DigestAlgorithm.SHA256)) {
271+
hmacAlg = HMACAlgorithm.SHA256;
272+
} else if (digest.equals(DigestAlgorithm.SHA384)) {
273+
hmacAlg = HMACAlgorithm.SHA384;
274+
} else if (digest.equals(DigestAlgorithm.SHA512)) {
275+
hmacAlg = HMACAlgorithm.SHA512;
276+
} else {
277+
// Default to SHA256
278+
hmacAlg = HMACAlgorithm.SHA256;
279+
keyLength = 32;
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, new NULL());
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, new NULL());
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)