@@ -365,16 +365,50 @@ private static byte[] calculateSignature(byte[] data, byte[] secret, Config.Inte
365365 return Arrays .copyOfRange (data , data .length - kGMACPayloadLength , data .length );
366366 }
367367
368+ private static class InternalSystemMetadataAssertionBinder implements AssertionBinder {
369+ private final byte [] payloadKey ;
370+
371+ public InternalSystemMetadataAssertionBinder (byte [] payloadKey ) {
372+ this .payloadKey = payloadKey ;
373+ }
374+
375+ @ Override
376+ public Manifest .Assertion bind (Manifest manifest , byte [] aggregateHash ) throws SDK .AssertionException {
377+ try {
378+ AssertionConfig config = AssertionConfig .getSystemMetadataAssertionConfig (TDF_SPEC_VERSION );
379+
380+ Manifest .Assertion assertion = new Manifest .Assertion ();
381+ assertion .id = config .id ;
382+ assertion .type = config .type .toString ();
383+ assertion .scope = config .scope .toString ();
384+ assertion .statement = config .statement ;
385+ assertion .appliesToState = config .appliesToState .toString ();
386+
387+ String assertionHashAsHex = assertion .hash ();
388+ byte [] assertionHash = Hex .decodeHex (assertionHashAsHex );
389+
390+ byte [] completeHash = AssertionUtils .computeAssertionSignature (aggregateHash , assertionHash );
391+ String encodedHash = Base64 .getEncoder ().encodeToString (completeHash );
392+
393+ Manifest .Assertion .HashValues hashValues = new Manifest .Assertion .HashValues (
394+ assertionHashAsHex ,
395+ encodedHash );
396+
397+ AssertionConfig .AssertionKey signingKey = new AssertionConfig .AssertionKey (AssertionConfig .AssertionKeyAlg .HS256 , payloadKey );
398+
399+ assertion .sign (hashValues , signingKey );
400+
401+ return assertion ;
402+ } catch (IOException | org .apache .commons .codec .DecoderException | com .nimbusds .jose .KeyLengthException e ) {
403+ throw new SDK .AssertionException ("failed to bind system metadata assertion" , e .getMessage ());
404+ }
405+ }
406+ }
407+
368408 TDFObject createTDF (InputStream payload , OutputStream outputStream , Config .TDFConfig tdfConfig ) throws SDKException , IOException {
369409 Planner planner = new Planner (tdfConfig , services , Autoconfigure ::createGranter );
370410 Map <String , List <KASInfo >> splits = planner .getSplits ();
371411
372- // Add System Metadata Assertion if configured
373- if (tdfConfig .systemMetadataAssertion ) {
374- AssertionConfig systemAssertion = AssertionConfig .getSystemMetadataAssertionConfig (TDF_SPEC_VERSION );
375- tdfConfig .assertionConfigList .add (systemAssertion );
376- }
377-
378412 TDFObject tdfObject = new TDFObject ();
379413 tdfObject .prepareManifest (tdfConfig , splits );
380414
@@ -458,47 +492,25 @@ TDFObject createTDF(InputStream payload, OutputStream outputStream, Config.TDFCo
458492 tdfObject .manifest .payload .url = TDFWriter .TDF_PAYLOAD_FILE_NAME ;
459493 tdfObject .manifest .payload .isEncrypted = true ;
460494
461- List <Manifest .Assertion > signedAssertions = new ArrayList <>(tdfConfig .assertionConfigList .size ());
462-
463- for (var assertionConfig : tdfConfig .assertionConfigList ) {
464- var assertion = new Manifest .Assertion ();
465- assertion .id = assertionConfig .id ;
466- assertion .type = assertionConfig .type .toString ();
467- assertion .scope = assertionConfig .scope .toString ();
468- assertion .statement = assertionConfig .statement ;
469- assertion .appliesToState = assertionConfig .appliesToState .toString ();
495+ List <Manifest .Assertion > signedAssertions = new ArrayList <>();
470496
471- var assertionHashAsHex = assertion .hash ();
472- byte [] assertionHash ;
473- if (tdfConfig .hexEncodeRootAndSegmentHashes ) {
474- assertionHash = assertionHashAsHex .getBytes (StandardCharsets .UTF_8 );
475- } else {
476- try {
477- assertionHash = Hex .decodeHex (assertionHashAsHex );
478- } catch (DecoderException e ) {
479- throw new SDKException ("error decoding assertion hash" , e );
480- }
497+ if (tdfConfig .systemMetadataAssertion ) {
498+ try {
499+ InternalSystemMetadataAssertionBinder binder = new InternalSystemMetadataAssertionBinder (tdfObject .payloadKey );
500+ Manifest .Assertion assertion = binder .bind (tdfObject .manifest , aggregateHash .toByteArray ());
501+ signedAssertions .add (assertion );
502+ } catch (SDK .AssertionException e ) {
503+ throw new SDKException ("error binding system metadata assertion" , e );
481504 }
482- byte [] completeHash = new byte [aggregateHash .size () + assertionHash .length ];
483- System .arraycopy (aggregateHash .toByteArray (), 0 , completeHash , 0 , aggregateHash .size ());
484- System .arraycopy (assertionHash , 0 , completeHash , aggregateHash .size (), assertionHash .length );
485-
486- var encodedHash = Base64 .getEncoder ().encodeToString (completeHash );
505+ }
487506
488- var assertionSigningKey = new AssertionConfig .AssertionKey (AssertionConfig .AssertionKeyAlg .HS256 ,
489- tdfObject .aesGcm .getKey ());
490- if (assertionConfig .signingKey != null && assertionConfig .signingKey .isDefined ()) {
491- assertionSigningKey = assertionConfig .signingKey ;
492- }
493- var hashValues = new Manifest .Assertion .HashValues (
494- assertionHashAsHex ,
495- encodedHash );
507+ for (var binder : tdfConfig .binders ) {
496508 try {
497- assertion .sign (hashValues , assertionSigningKey );
498- } catch (KeyLengthException e ) {
499- throw new SDKException ("error signing assertion hash" , e );
509+ var assertion = binder .bind (tdfObject .manifest , aggregateHash .toByteArray ());
510+ signedAssertions .add (assertion );
511+ } catch (SDK .AssertionException e ) {
512+ throw new SDKException ("error binding assertion" , e );
500513 }
501- signedAssertions .add (assertion );
502514 }
503515
504516 tdfObject .manifest .assertions = signedAssertions ;
@@ -682,48 +694,86 @@ Reader loadTDF(SeekableByteChannel tdf, Config.TDFReaderConfig tdfReaderConfig)
682694 break ;
683695 }
684696
685- // Set default to HS256
686- var assertionKey = new AssertionConfig .AssertionKey (AssertionConfig .AssertionKeyAlg .HS256 , payloadKey );
687- Config .AssertionVerificationKeys assertionVerificationKeys = tdfReaderConfig .assertionVerificationKeys ;
688- if (!assertionVerificationKeys .isEmpty ()) {
689- var keyForAssertion = assertionVerificationKeys .getKey (assertion .id );
690- if (keyForAssertion != null ) {
691- assertionKey = keyForAssertion ;
692- }
693- }
697+ AssertionValidator validator = tdfReaderConfig .validators .get (assertion .type );
694698
695- Manifest .Assertion .HashValues hashValues ;
696- try {
697- hashValues = assertion .verify (assertionKey );
698- } catch (ParseException | JOSEException e ) {
699- throw new SDKException ("error validating assertion hash" , e );
700- }
701- var hashOfAssertionAsHex = assertion .hash ();
699+ if (validator != null ) {
700+ try {
701+ validator .verify (assertion , tdfReader , aggregateHash .toByteArray ());
702+ validator .validate (assertion , tdfReader );
703+ } catch (SDK .AssertionException e ) {
704+ if (tdfReaderConfig .verificationMode == VerificationMode .STRICT || tdfReaderConfig .verificationMode == VerificationMode .FAIL_FAST ) {
705+ throw new SDKException ("assertion validation failed in " + tdfReaderConfig .verificationMode + " mode" , e );
706+ }
707+ // In permissive mode, we log the error and continue
708+ logger .warn ("Assertion validation failed for assertion id {}" , assertion .id , e );
709+ }
710+ } else {
711+ if (tdfReaderConfig .verificationMode == VerificationMode .STRICT ) {
712+ throw new SDKException ("No validator found for assertion id " + assertion .id + " in strict mode" );
713+ }
714+ // Permissive and FailFast mode, we attempt DEK fallback
715+ logger .warn ("No validator for assertion {}, attempting DEK fallback" , assertion .id );
702716
703- if (! Objects . equals ( hashOfAssertionAsHex , hashValues . getAssertionHash ())) {
704- throw new SDK . AssertionException ( "assertion hash mismatch" , assertion . id );
705- }
717+ // Fallback to DEK-based verification
718+ // Set default to HS256
719+ var assertionKey = getAssertionKey ( tdfReaderConfig , assertion , payloadKey );
706720
707- byte [] hashOfAssertion ;
708- if (isLegacyTdf ) {
709- hashOfAssertion = hashOfAssertionAsHex .getBytes (StandardCharsets .UTF_8 );
710- } else {
721+ Manifest .Assertion .HashValues hashValues ;
711722 try {
712- hashOfAssertion = Hex .decodeHex (hashOfAssertionAsHex );
713- } catch (DecoderException e ) {
714- throw new SDKException ("error decoding assertion hash" , e );
723+ hashValues = assertion .verify (assertionKey );
724+ } catch (ParseException | JOSEException e ) {
725+ if (tdfReaderConfig .verificationMode == VerificationMode .FAIL_FAST ) {
726+ throw new SDKException ("error validating assertion hash" , e );
727+ }
728+ logger .warn ("Error validating assertion hash for assertion id {}" , assertion .id , e );
729+ continue ; // permissive
730+ }
731+ var hashOfAssertionAsHex = assertion .hash ();
732+
733+ if (!Objects .equals (hashOfAssertionAsHex , hashValues .getAssertionHash ())) {
734+ if (tdfReaderConfig .verificationMode == VerificationMode .FAIL_FAST ) {
735+ throw new SDK .AssertionException ("assertion hash mismatch" , assertion .id );
736+ }
737+ logger .warn ("Assertion hash mismatch for assertion id {}" , assertion .id );
738+ continue ; // permissive
715739 }
716- }
717- var signature = new byte [aggregateHashByteArrayBytes .length + hashOfAssertion .length ];
718- System .arraycopy (aggregateHashByteArrayBytes , 0 , signature , 0 , aggregateHashByteArrayBytes .length );
719- System .arraycopy (hashOfAssertion , 0 , signature , aggregateHashByteArrayBytes .length , hashOfAssertion .length );
720- var encodeSignature = Base64 .getEncoder ().encodeToString (signature );
721740
722- if (!Objects .equals (encodeSignature , hashValues .getSignature ())) {
723- throw new SDK .AssertionException ("failed integrity check on assertion signature" , assertion .id );
741+ byte [] hashOfAssertion ;
742+ if (isLegacyTdf ) {
743+ hashOfAssertion = hashOfAssertionAsHex .getBytes (StandardCharsets .UTF_8 );
744+ } else {
745+ try {
746+ hashOfAssertion = Hex .decodeHex (hashOfAssertionAsHex );
747+ } catch (DecoderException e ) {
748+ throw new SDKException ("error decoding assertion hash" , e );
749+ }
750+ }
751+ var signature = new byte [aggregateHashByteArrayBytes .length + hashOfAssertion .length ];
752+ System .arraycopy (aggregateHashByteArrayBytes , 0 , signature , 0 , aggregateHashByteArrayBytes .length );
753+ System .arraycopy (hashOfAssertion , 0 , signature , aggregateHashByteArrayBytes .length , hashOfAssertion .length );
754+ var encodeSignature = Base64 .getEncoder ().encodeToString (signature );
755+
756+ if (!Objects .equals (encodeSignature , hashValues .getSignature ())) {
757+ if (tdfReaderConfig .verificationMode == VerificationMode .FAIL_FAST ) {
758+ throw new SDK .AssertionException ("failed integrity check on assertion signature" , assertion .id );
759+ }
760+ logger .warn ("Failed integrity check on assertion signature for assertion id {}" , assertion .id );
761+ }
724762 }
725763 }
726764
727765 return new Reader (tdfReader , manifest , payloadKey , unencryptedMetadata );
728766 }
767+
768+ private static AssertionConfig .AssertionKey getAssertionKey (Config .TDFReaderConfig tdfReaderConfig , Manifest .Assertion assertion , byte [] payloadKey ) {
769+ var assertionKey = new AssertionConfig .AssertionKey (AssertionConfig .AssertionKeyAlg .HS256 , payloadKey );
770+ Config .AssertionVerificationKeys assertionVerificationKeys = tdfReaderConfig .assertionVerificationKeys ;
771+ if (!assertionVerificationKeys .isEmpty ()) {
772+ var keyForAssertion = assertionVerificationKeys .getKey (assertion .id );
773+ if (keyForAssertion != null ) {
774+ assertionKey = keyForAssertion ;
775+ }
776+ }
777+ return assertionKey ;
778+ }
729779}
0 commit comments