Skip to content

Commit fae30be

Browse files
committed
Implement PBMAC1 MAC computation for PKCS#12: Claude Sonnet 4.5 assisted.
1 parent aab36ef commit fae30be

5 files changed

Lines changed: 458 additions & 9 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,10 @@ public PFX generatePFX(PKCS12 pkcs12, Password password) throws Exception {
648648

649649
byte[] salt = new byte[16];
650650
random.nextBytes(salt);
651+
652+
pfx.setMacType(macType);
653+
pfx.setMacDigest(macDigest);
654+
651655
pfx.computeMacData(password, salt, 100000);
652656

653657
return pfx;

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

Lines changed: 154 additions & 4 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,9 @@
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;
29+
import org.mozilla.jss.asn1.ASN1Util;
2530
import org.mozilla.jss.crypto.CryptoToken;
2631
import org.mozilla.jss.crypto.DigestAlgorithm;
2732
import org.mozilla.jss.crypto.HMACAlgorithm;
@@ -32,9 +37,13 @@
3237
import org.mozilla.jss.crypto.PBEKeyGenParams;
3338
import org.mozilla.jss.crypto.SymmetricKey;
3439
import org.mozilla.jss.crypto.TokenException;
40+
import org.mozilla.jss.crypto.PBEAlgorithm;
41+
import org.mozilla.jss.pkix.primitive.PBMAC1Params;
3542
import org.mozilla.jss.pkcs7.DigestInfo;
3643
import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
44+
import org.mozilla.jss.pkix.primitive.PBKDF2Params;
3745
import org.mozilla.jss.util.Password;
46+
import org.mozilla.jss.util.UTF8Converter;
3847

3948
public class MacData implements ASN1Value {
4049

@@ -45,7 +54,7 @@ public class MacData implements ASN1Value {
4554
private static final int DEFAULT_ITERATIONS = 1;
4655

4756
// 20 is the length of SHA-1 hash output
48-
private static final int SALT_LENGTH = 20;
57+
public static final int SALT_LENGTH = 20;
4958

5059
public DigestInfo getMac() {
5160
return mac;
@@ -137,13 +146,30 @@ public MacData( Password password, byte[] macSalt,
137146
rand.nextBytes(macSalt);
138147
}
139148

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

142151
try {
143-
// generate key from password and salt
144-
if(algID == null) {
152+
if (algID == null) {
145153
algID = new AlgorithmIdentifier(DigestAlgorithm.SHA1.toOID());
146154
}
155+
156+
// Check if this is PBMAC1 - route to new implementation
157+
if (algID.getOID().equals(PBEAlgorithm.PBE_PKCS5_PBMAC1.toOID())) {
158+
computePBMAC1(token, password, toBeMACed, algID);
159+
return; // Early return - skip classic code below
160+
}
161+
} catch (NoSuchAlgorithmException e) {
162+
throw new TokenException("Algorithm OID error: " + e.getMessage(), e);
163+
} catch (TokenException | CharConversionException e) {
164+
throw e;
165+
} catch (Exception e) {
166+
throw new TokenException("Failed to compute PBMAC1: " + e.getMessage(), e);
167+
}
168+
169+
PBEKeyGenParams params = new PBEKeyGenParams(password, macSalt, iterations);
170+
171+
try {
172+
// generate key from password and salt
147173
KeyGenerator kg = null;
148174
JSSMessageDigest digest = null;
149175
if(DigestAlgorithm.SHA1.toOID().equals(algID.getOID())){
@@ -196,6 +222,130 @@ public MacData( Password password, byte[] macSalt,
196222
}
197223
}
198224

225+
/**
226+
* Computes a PBMAC1 MAC per RFC 9579 using NSS native crypto.
227+
*
228+
* @param token The crypto token for key derivation and HMAC
229+
* @param password The password for PBKDF2 key derivation
230+
* @param data The data to authenticate
231+
* @param algID The PBMAC1 AlgorithmIdentifier containing KDF and MAC params
232+
* @throws Exception if MAC computation fails
233+
*/
234+
private void computePBMAC1(CryptoToken token, Password password,
235+
byte[] data, AlgorithmIdentifier algID)
236+
throws Exception
237+
{
238+
// Parse PBMAC1 parameters to extract KDF and MAC algorithms
239+
240+
PBMAC1Params pbmac1Params;
241+
ASN1Value params = algID.getParameters();
242+
243+
if (params == null) {
244+
throw new InvalidBERException("Missing PBMAC1 parameters");
245+
}
246+
247+
if (params instanceof PBMAC1Params) {
248+
// Already decoded (create/write path)
249+
pbmac1Params = (PBMAC1Params) params;
250+
} else if (params instanceof ANY) {
251+
// Needs decoding (read from file path)
252+
pbmac1Params = (PBMAC1Params) ((ANY) params).decodeWith(PBMAC1Params.getTemplate());
253+
} else {
254+
throw new InvalidBERException("Unexpected PBMAC1 parameter type: " + params.getClass().getName());
255+
}
256+
257+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
258+
259+
algID.encode(bos);
260+
byte[] pbmac1AlgIDBytes = bos.toByteArray();
261+
262+
AlgorithmIdentifier kdfAlg = pbmac1Params.getKeyDerivationFunc();
263+
AlgorithmIdentifier macAlg = pbmac1Params.getMessageAuthScheme();
264+
265+
ASN1Value kdfParams = kdfAlg.getParameters();
266+
if (kdfParams == null) {
267+
throw new InvalidBERException("Missing PBKDF2 parameters");
268+
}
269+
270+
//Extract PBKDF2 parameters
271+
PBKDF2Params pbkdf2Params;
272+
273+
if (kdfParams instanceof PBKDF2Params) {
274+
// Already decoded (create/write path using typed constructor)
275+
pbkdf2Params = (PBKDF2Params) kdfParams;
276+
} else if (kdfParams instanceof SEQUENCE) {
277+
pbkdf2Params = (PBKDF2Params) ASN1Util.decode(
278+
PBKDF2Params.getTemplate(), ASN1Util.encode(kdfParams));
279+
} else if (kdfParams instanceof ANY) {
280+
pbkdf2Params = (PBKDF2Params) ((ANY) kdfParams).decodeWith(PBKDF2Params.getTemplate());
281+
} else {
282+
throw new InvalidBERException("Unexpected PBKDF2 parameter type: " + kdfParams.getClass().getName());
283+
}
284+
285+
byte[] kdfSalt = pbkdf2Params.getSalt();
286+
if(kdfSalt == null) {
287+
throw new InvalidBERException("PBKFD2 otherSource salt not supported");
288+
}
289+
int kdfIterations = pbkdf2Params.getIterations();
290+
291+
// Get HMAC OID from MAC AlgorithmIdentifier
292+
OBJECT_IDENTIFIER macOID = macAlg.getOID();
293+
294+
HMACAlgorithm hmacAlgorithm = HMACAlgorithm.fromOID(macOID);
295+
296+
// Call native JSS code to perform PBKDF2 + HMAC
297+
// This uses certified NSS crypto (PK11_PBEKeyGen + PK11_DigestOp)
298+
// The OID will be mapped to the appropriate NSS HMAC mechanism
299+
300+
301+
char[] passwordChars = password.getCharCopy();
302+
byte[] passwordBytes = null;
303+
304+
try {
305+
passwordBytes = UTF8Converter.UnicodeToUTF8(passwordChars);
306+
byte[] macValue = nativeComputePBMAC1(
307+
token,
308+
passwordBytes,
309+
data,
310+
pbmac1AlgIDBytes,
311+
hmacAlgorithm
312+
);
313+
314+
this.mac = new DigestInfo(algID, new OCTET_STRING(macValue));
315+
// PBMAC1: outer macSalt comes from the PBKDF2 params within
316+
// the AlgorithmIdentifier, not from the constructor argument,
317+
// since the salt is bound to the KDF computation.
318+
this.macSalt = new OCTET_STRING(kdfSalt);
319+
// PBMAC1: outer macIterationCount is always 1 (default) because
320+
// the actual iteration count is inside the PBKDF2 params within
321+
// the AlgorithmIdentifier, not at the MacData level.
322+
this.macIterationCount = new INTEGER(1);
323+
} finally {
324+
if (passwordBytes != null) {
325+
Password.wipeBytes(passwordBytes);
326+
}
327+
Password.wipeChars(passwordChars);
328+
}
329+
}
330+
331+
/**
332+
* Native method to compute PBMAC1 MAC using NSS.
333+
*
334+
* @param password Password bytes
335+
* @param salt PBKDF2 salt
336+
* @param iterations PBKDF2 iteration count
337+
* @param data Data to MAC
338+
* @param hmacOID HMAC algorithm OID (e.g., hmacWithSHA256)
339+
* @return HMAC value
340+
*/
341+
private native byte[] nativeComputePBMAC1(
342+
CryptoToken token,
343+
byte[] password,
344+
byte[] data,
345+
byte[] pbmac1AlgID,
346+
HMACAlgorithm hmacAlgorithm
347+
) throws Exception;
348+
199349
///////////////////////////////////////////////////////////////////////
200350
// DER encoding
201351
///////////////////////////////////////////////////////////////////////

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

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,24 @@
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;
46+
import org.mozilla.jss.pkix.primitive.PBKDF2Params;
4247
import org.mozilla.jss.util.Password;
43-
48+
import org.mozilla.jss.pkcs12.MacType;
4449
/**
4550
* The top level ASN.1 structure for a PKCS #12 blob.
4651
*
@@ -94,12 +99,41 @@ 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.CLASSIC; // 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+
/**
113+
* Sets the MAC algorithm type for this PFX.
114+
*
115+
* @param type The MAC type (CLASSIC or PBMAC1)
116+
* @throws IllegalArgumentException if type is null
117+
*/
118+
public void setMacType(MacType type) {
119+
if(type == null) {
120+
throw new IllegalArgumentException("MacType must not be null");
121+
}
122+
this.macType = type;
123+
}
124+
125+
/**
126+
* Sets the digest algorithm for MAC computation.
127+
*
128+
* @param digest The digest algorithm (e.g., SHA256, SHA384, SHA512)
129+
* @throws IllegalArgumentException if digest is null
130+
*/
131+
public void setMacDigest(DigestAlgorithm digest) {
132+
if(digest == null) {
133+
throw new IllegalArgumentException("Digest must not be null");
134+
}
135+
this.macDigest = digest;
136+
}
103137

104138
public INTEGER getVersion() {
105139
return version;
@@ -153,6 +187,7 @@ public boolean verifyAuthSafes(Password password, StringBuffer reason)
153187

154188
// create a new MacData based on the encoded Auth Safes
155189
DigestInfo macDataMac = macData.getMac();
190+
156191
MacData testMac = new MacData(password,
157192
macData.getMacSalt().toByteArray(),
158193
macData.getMacIterationCount().intValue(),
@@ -219,10 +254,87 @@ public void computeMacData(Password password,
219254
TokenException, CharConversionException
220255
{
221256

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 );
257+
AlgorithmIdentifier algID;
258+
259+
if (salt == null) {
260+
CryptoManager cm = CryptoManager.getInstance();
261+
JSSSecureRandom rand = cm.createPseudoRandomNumberGenerator();
262+
salt = new byte[MacData.SALT_LENGTH]; // SALT_LENGTH from MacData
263+
rand.nextBytes(salt);
264+
}
265+
266+
if (macType == MacType.PBMAC1) {
267+
// Create PBMAC1 AlgorithmIdentifier with PBKDF2 parameters
268+
algID = createPBMAC1AlgorithmID(salt, iterationCount, macDigest);
269+
} else {
270+
// Legacy: Use configured digest
271+
algID = new AlgorithmIdentifier(macDigest.toOID());
272+
}
273+
274+
macData = new MacData(password, salt, iterationCount,
275+
ASN1Util.encode(authSafes), algID);
276+
}
277+
278+
/**
279+
* Creates a PBMAC1 AlgorithmIdentifier with PBKDF2 parameters per RFC 9579.
280+
*
281+
* @param salt The salt for PBKDF2 key derivation
282+
* @param iterationCount The PBKDF2 iteration count
283+
* @param digest The digest algorithm for HMAC
284+
* @return The PBMAC1 AlgorithmIdentifier
285+
* @throws NoSuchAlgorithmException if the digest is unsupported
286+
*/
287+
private AlgorithmIdentifier createPBMAC1AlgorithmID(
288+
byte[] salt, int iterationCount, DigestAlgorithm digest)
289+
throws NoSuchAlgorithmException
290+
{
291+
// Determine HMAC OID and key length from digest algorithm
292+
// Use existing HMACAlgorithm constants instead of hardcoded OIDs
293+
294+
int keyLength = digest.getOutputSize();
295+
// Get the corresponding HMAC algorithm
296+
HMACAlgorithm hmacAlg = getHMACForDigest(digest);
297+
298+
OBJECT_IDENTIFIER hmacOID = hmacAlg.toOID();
299+
300+
// PRF algorithm for PBKDF2 (same HMAC algorithm)
301+
AlgorithmIdentifier prfAlg = new AlgorithmIdentifier(hmacOID, new NULL());
302+
PBKDF2Params pbkdf2Params = new PBKDF2Params(salt, null, iterationCount, keyLength, prfAlg);
303+
304+
// Construct PBKDF2 AlgorithmIdentifier
305+
AlgorithmIdentifier kdfAlg = new AlgorithmIdentifier(
306+
PBEAlgorithm.PBE_PKCS5_PBKDF2.toOID(), pbkdf2Params);
307+
308+
// Construct HMAC AlgorithmIdentifier for MAC scheme
309+
AlgorithmIdentifier macAlg = new AlgorithmIdentifier(hmacOID, new NULL());
310+
311+
// Construct PBMAC1 parameters using dedicated ASN.1 type
312+
PBMAC1Params pbmac1Params = new PBMAC1Params(kdfAlg, macAlg);
313+
314+
// Construct final PBMAC1 AlgorithmIdentifier
315+
return new AlgorithmIdentifier(
316+
PBEAlgorithm.PBE_PKCS5_PBMAC1.toOID(), pbmac1Params);
317+
}
318+
319+
/**
320+
* Maps a digest algorithm to the corresponding HMAC algorithm.
321+
*
322+
* @param digest The digest algorithm
323+
* @return The corresponding HMACAlgorithm
324+
* @throws NoSuchAlgorithmException if no HMAC exists for the digest
325+
*/
326+
private static HMACAlgorithm getHMACForDigest(DigestAlgorithm digest)
327+
throws NoSuchAlgorithmException {
328+
if (digest.equals(DigestAlgorithm.SHA256)) {
329+
return HMACAlgorithm.SHA256;
330+
} else if (digest.equals(DigestAlgorithm.SHA384)) {
331+
return HMACAlgorithm.SHA384;
332+
} else if (digest.equals(DigestAlgorithm.SHA512)) {
333+
return HMACAlgorithm.SHA512;
334+
} else {
335+
throw new NoSuchAlgorithmException(
336+
"Unsupported PBMAC1 digest: " + digest);
337+
}
226338
}
227339

228340

lib/jss.map

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ JSS_5.10.0 {
538538
global:
539539
Java_org_mozilla_jss_pkcs11_PK11KeyPairGenerator_generateMLKEMKeyPair;
540540
Java_org_mozilla_jss_pkcs11_PK11KeyPairGenerator_generateMLKEMKeyPairWithOpFlags;
541+
Java_org_mozilla_jss_pkcs12_MacData_nativeComputePBMAC1;
541542
local:
542543
*;
543544
};

0 commit comments

Comments
 (0)