88import java .io .IOException ;
99import java .io .InputStream ;
1010import java .io .OutputStream ;
11+ import java .io .ByteArrayInputStream ;
12+ import java .io .ByteArrayOutputStream ;
1113import java .security .DigestException ;
1214import java .security .InvalidAlgorithmParameterException ;
1315import java .security .InvalidKeyException ;
2224import org .mozilla .jss .asn1 .OCTET_STRING ;
2325import org .mozilla .jss .asn1 .SEQUENCE ;
2426import 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 ;
2530import org .mozilla .jss .crypto .CryptoToken ;
2631import org .mozilla .jss .crypto .DigestAlgorithm ;
2732import org .mozilla .jss .crypto .HMACAlgorithm ;
3237import org .mozilla .jss .crypto .PBEKeyGenParams ;
3338import org .mozilla .jss .crypto .SymmetricKey ;
3439import org .mozilla .jss .crypto .TokenException ;
40+ import org .mozilla .jss .crypto .PBEAlgorithm ;
41+ import org .mozilla .jss .pkix .primitive .PBMAC1Params ;
3542import org .mozilla .jss .pkcs7 .DigestInfo ;
3643import org .mozilla .jss .pkix .primitive .AlgorithmIdentifier ;
44+ import org .mozilla .jss .pkix .primitive .PBKDF2Params ;
3745import org .mozilla .jss .util .Password ;
46+ import org .mozilla .jss .util .UTF8Converter ;
3847
3948public 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 ///////////////////////////////////////////////////////////////////////
0 commit comments