@@ -2,6 +2,8 @@ const assert = require("assert");
22const rewire = require ( "rewire" ) ;
33const Crypto = rewire ( "../lib/mcapi/crypto/jwe-crypto" ) ;
44const utils = require ( "../lib/mcapi/utils/utils" ) ;
5+ const c = require ( "../lib/mcapi/utils/constants" ) ;
6+ const nodeCrypto = require ( "crypto" ) ;
57
68const testConfig = require ( "./mock/jwe-config" ) ;
79
@@ -257,6 +259,120 @@ describe("JWE Crypto", () => {
257259
258260 } ) ;
259261
262+ describe ( "verifyCbcHmac()" , ( ) => {
263+ let encodedHeaderB64Url ;
264+ let ciphertext ;
265+ let secretKey ;
266+ let iv ;
267+ let fullTag ;
268+ let authTag ;
269+
270+ before ( ( ) => {
271+ const headerJson = JSON . stringify ( { alg : "RSA-OAEP-256" , enc : "A128CBC-HS256" } ) ;
272+ encodedHeaderB64Url = Buffer . from ( headerJson , c . UTF8 )
273+ . toString ( c . BASE64 )
274+ . replace ( / \+ / g, "-" )
275+ . replace ( / \/ / g, "_" )
276+ . replace ( / = / g, "" ) ;
277+
278+ iv = nodeCrypto . randomBytes ( 16 ) ;
279+ ciphertext = nodeCrypto . randomBytes ( 32 ) ;
280+
281+ const macKey = nodeCrypto . randomBytes ( 16 ) ;
282+ const encKey = nodeCrypto . randomBytes ( 16 ) ;
283+ secretKey = Buffer . concat ( [ macKey , encKey ] ) ;
284+
285+ const aad = Buffer . from ( encodedHeaderB64Url , c . ASCII ) ;
286+ const al = Buffer . alloc ( 8 ) ;
287+ const aadBits = aad . length * 8 ;
288+ al . writeUInt32BE ( Math . floor ( aadBits / Math . pow ( 2 , 32 ) ) , 0 ) ;
289+ al . writeUInt32BE ( aadBits >>> 0 , 4 ) ;
290+
291+ const hmac = nodeCrypto . createHmac ( "sha256" , macKey ) ;
292+ hmac . update ( aad ) ;
293+ hmac . update ( iv ) ;
294+ hmac . update ( ciphertext ) ;
295+ hmac . update ( al ) ;
296+ fullTag = hmac . digest ( ) ;
297+ authTag = fullTag . slice ( 0 , 16 ) ;
298+ } ) ;
299+
300+ it ( "should NOT throw when HMAC tag is valid" , ( ) => {
301+ const verifyCbcHmac = Crypto . __get__ ( "verifyCbcHmac" ) ;
302+
303+ assert . doesNotThrow ( ( ) => {
304+ verifyCbcHmac ( encodedHeaderB64Url , iv , ciphertext , authTag , secretKey ) ;
305+ } ) ;
306+ } ) ;
307+
308+ it ( "should throw when HMAC tag is invalid" , ( ) => {
309+ const verifyCbcHmac = Crypto . __get__ ( "verifyCbcHmac" ) ;
310+
311+ const tamperedTag = Buffer . from ( authTag ) ;
312+ tamperedTag [ 0 ] ^= 0xff ;
313+
314+ assert . throws ( ( ) => {
315+ verifyCbcHmac ( encodedHeaderB64Url , iv , ciphertext , tamperedTag , secretKey ) ;
316+ } , / A u t h e n t i c a t i o n t a g v e r i f i c a t i o n f a i l e d / ) ;
317+ } ) ;
318+ } ) ;
319+
320+ describe ( "HMAC verification toggle (A128CBC-HS256)" , ( ) => {
321+ let CryptoRewired ;
322+ let verifySpy ;
323+ let token ;
324+ before ( ( ) => {
325+ CryptoRewired = rewire ( "../lib/mcapi/crypto/jwe-crypto" ) ;
326+ verifySpy = { called : false } ;
327+ CryptoRewired . __set__ ( "verifyCbcHmac" , ( ) => { verifySpy . called = true ; } ) ;
328+
329+ CryptoRewired . __set__ ( "nodeCrypto" , {
330+ constants : { RSA_PKCS1_OAEP_PADDING : 4 } ,
331+ privateDecrypt : ( ) => {
332+ return Buffer . alloc ( 32 , 1 ) ;
333+ } ,
334+ createDecipheriv : ( ) => ( {
335+ setAAD : ( ) => { } ,
336+ setAuthTag : ( ) => { } ,
337+ update : ( ) => "" ,
338+ final : ( ) => "test"
339+ } )
340+ } ) ;
341+
342+ const header = Buffer . from ( JSON . stringify ( { alg : "RSA-OAEP-256" , enc : "A128CBC-HS256" } ) , c . UTF8 )
343+ . toString ( c . BASE64 )
344+ . replace ( / \+ / g, "-" )
345+ . replace ( / \/ / g, "_" )
346+ . replace ( / = / g, "" ) ;
347+ const ek = Buffer . from ( "ek" ) . toString ( c . BASE64 ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = / g, "" ) ;
348+ const iv = Buffer . from ( "1234567890123456" ) . toString ( c . BASE64 ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = / g, "" ) ;
349+ const ct = Buffer . from ( "ciphertext" ) . toString ( c . BASE64 ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = / g, "" ) ;
350+ const tag = Buffer . alloc ( 16 ) . toString ( c . BASE64 ) . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = / g, "" ) ;
351+ token = `${ header } .${ ek } .${ iv } .${ ct } .${ tag } ` ;
352+ } ) ;
353+
354+ it ( "does NOT call verifyCbcHmac by default" , ( ) => {
355+ const cfg = JSON . parse ( JSON . stringify ( testConfig ) ) ;
356+
357+ const crypto = new CryptoRewired ( cfg ) ;
358+ crypto . decryptData ( token ) ;
359+
360+ assert . strictEqual ( verifySpy . called , false , "verifyCbcHmac should not be called by default" ) ;
361+ } ) ;
362+
363+ it ( "calls verifyCbcHmac when config.enableHmacVerification is true" , ( ) => {
364+ const cfg = JSON . parse ( JSON . stringify ( testConfig ) ) ;
365+ cfg . enableHmacVerification = true ;
366+
367+ const crypto = new CryptoRewired ( cfg ) ;
368+ crypto . decryptData ( token ) ;
369+
370+ assert . strictEqual ( verifySpy . called , true , "verifyCbcHmac should be called when enabled" ) ;
371+ } ) ;
372+ } ) ;
373+
374+
375+
260376 describe ( "#readPublicCertificate" , ( ) => {
261377 it ( "not valid key" , ( ) => {
262378 const readPublicCertificate = Crypto . __get__ ( "readPublicCertificate" ) ;
0 commit comments