1313import java .nio .file .Path ;
1414import java .nio .file .StandardOpenOption ;
1515import java .nio .file .attribute .FileAttribute ;
16- import java .security .Provider ;
16+ import java .security .NoSuchAlgorithmException ;
1717import java .security .SecureRandom ;
18- import java .security .SecureRandomSpi ;
1918import java .time .LocalDate ;
2019import java .time .temporal .TemporalQuery ;
2120import java .util .List ;
2928import de .tum .cit .ase .ares .api .aop .java .JavaAOPTestCase ;
3029import de .tum .cit .ase .ares .api .aop .java .JavaAOPTestCaseSettings ;
3130import de .tum .cit .ase .ares .api .aop .java .instrumentation .pointcut .JavaInstrumentationPointcutDefinitions ;
31+ import de .tum .cit .ase .ares .testutilities .FakeSecureRandomSeedingFixture ;
3232
3333import example .student .InstrumentationSecurityProbe ;
3434
@@ -484,7 +484,7 @@ void customSecureRandomSpiCannotForgeTheEntropyDeviceReadExemption() throws Exce
484484 // dispatch frame is not itself trusted - only genuine
485485 // sun.security.provider.* internal implementation frames are - so a
486486 // simulated entropy-device read from within it must still be denied.
487- assertThrows (SecurityException .class , () -> triggerFakeSecureRandomSeeding (
487+ assertThrows (SecurityException .class , () -> FakeSecureRandomSeedingFixture . triggerFakeSecureRandomSeeding (
488488 () -> InstrumentationSecurityProbe .checkEntropyDeviceReadDirectly ("/dev/urandom" )));
489489 } finally {
490490 resetSettings ();
@@ -493,20 +493,37 @@ void customSecureRandomSpiCannotForgeTheEntropyDeviceReadExemption() throws Exce
493493
494494 @ Test
495495 void genuineSecureRandomEntropySeedingIsPermittedByAnActivePolicy () throws Exception {
496- Assumptions .assumeTrue (Files .exists (Path .of ("/dev/urandom" )) || Files .exists (Path .of ("/dev/random" )),
497- "requires an OS entropy device (Linux/BSD)" );
496+ // A plain new SecureRandom().generateSeed(...) is not reliable here: the
497+ // JDK's own SeedGenerator/NativePRNG singletons open their entropy device at
498+ // most once per JVM and cache the stream for the rest of the process's
499+ // lifetime, so an unforced call could return a cached seed without ever
500+ // re-entering the woven FileInputStream constructor - meaning
501+ // assertDoesNotThrow could pass without exercising
502+ // isSecureRandomSeedingInProgress() at all. "NativePRNGBlocking" is a
503+ // distinct SecureRandomSpi (its own RandomIO instance, its own /dev/random
504+ // device) that nothing else in this codebase requests by name, which makes
505+ // it far less likely to already be warm from an earlier test in this fork -
506+ // the closest a black-box unit test can get to a genuinely reachable
507+ // JDK-internal seeding call.
508+ SecureRandom nativeBlockingSecureRandom ;
509+ try {
510+ nativeBlockingSecureRandom = SecureRandom .getInstance ("NativePRNGBlocking" );
511+ } catch (NoSuchAlgorithmException e ) {
512+ Assumptions .abort ("NativePRNGBlocking unavailable on this platform (" + e .getMessage () + ")" );
513+ return ;
514+ }
498515 try {
499516 resetSettings ();
500517 configureInstrumentationMode ();
501518 JavaAOPTestCase .setJavaAdviceSettingValue ("pathsAllowedToBeRead" , new String [0 ], "ARCH" , "INSTRUMENTATION" );
502519
503- // An unforced, genuinely fresh SecureRandom.generateSeed(...) call on the
504- // JDK's own default provider must still be permitted even though the active
505- // policy allows no read paths at all - proving the narrowed
506- // sun.security.provider.*-only trust still recognises real JDK seeding, not
507- // just the (now-denied) forged case above. Runs against the real
508- // java-agent-instrumented FileInputStream constructor, not a simulated call.
509- assertDoesNotThrow (() -> new SecureRandom () .generateSeed (8 ));
520+ // Must still be permitted even though the active policy allows no read
521+ // paths at all - proving the narrowed sun.security.provider.*-only trust
522+ // still recognises real JDK seeding, not just the (now-denied) forged case
523+ // above. Runs against the real java-agent-instrumented FileInputStream
524+ // constructor, not a simulated call.
525+ SecureRandom finalNativeBlockingSecureRandom = nativeBlockingSecureRandom ;
526+ assertDoesNotThrow (() -> finalNativeBlockingSecureRandom .generateSeed (8 ));
510527 } finally {
511528 resetSettings ();
512529 }
@@ -775,54 +792,5 @@ private static File createNonTempDirOutsideDefaultTempDir(String name) throws IO
775792 return dir .toFile ();
776793 }
777794
778- /**
779- * Registers a synthetic, student-authored-style {@link SecureRandomSpi} whose
780- * {@code engineGenerateSeed} runs the given probe, then calls
781- * {@link SecureRandom#generateSeed(int)} on it. This makes
782- * {@code java.security.SecureRandom.generateSeed(...)} a genuine caller frame
783- * on the real stack while the probe runs — the exact spoof the narrowed
784- * {@code sun.security.provider.*}-only trust set is designed to reject, since
785- * no genuine JDK seeding is actually taking place.
786- */
787- private static void triggerFakeSecureRandomSeeding (SeedingProbe probe ) throws Exception {
788- ProbingSecureRandomSpi .PROBE = probe ;
789- try {
790- Provider provider = new Provider ("ares-hotfix-test-secure-random-provider" , "1.0" ,
791- "Ares test fixture provider for exercising the SecureRandom-seeding stack detector" ) {
792- private static final long serialVersionUID = 1L ;
793- };
794- provider .put ("SecureRandom.AresProbe" , ProbingSecureRandomSpi .class .getName ());
795- SecureRandom .getInstance ("AresProbe" , provider ).generateSeed (1 );
796- } finally {
797- ProbingSecureRandomSpi .PROBE = null ;
798- }
799- }
800-
801- @ FunctionalInterface
802- private interface SeedingProbe {
803- void run ();
804- }
805-
806- public static final class ProbingSecureRandomSpi extends SecureRandomSpi {
807-
808- private static volatile SeedingProbe PROBE ;
809-
810- @ Override
811- protected byte [] engineGenerateSeed (int numBytes ) {
812- PROBE .run ();
813- return new byte [numBytes ];
814- }
815-
816- @ Override
817- protected void engineSetSeed (byte [] seed ) {
818- // Not exercised by these tests.
819- }
820-
821- @ Override
822- protected void engineNextBytes (byte [] bytes ) {
823- // Not exercised by these tests.
824- }
825- }
826-
827795 // </editor-fold>
828796}
0 commit comments