# HG changeset patch # User gromero # Date 1522418354 18000 # Fri Mar 30 08:59:14 2018 -0500 # Node ID 333d5f922f9d4cd0fae120e9771d5ac720a3cb81 # Parent cc58f1fa04389303cdde72200b89fc168be0a91c PPC64: Add support for HW random number generator Add support to use a hardware random number generator through new 'darn' instruction introduced with POWER9 processor. That change introduces a new JCA provider (called HWTRNG) with the proper methods to be intrinsified and that are used, in the end, by generateSeed() and nextBytes() methods in SecureRandom class when the HWTPRNG provider is selected. It also paves the way to use a HW TRNG for other architectures that support it. diff --git a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp --- a/src/hotspot/cpu/ppc/assembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp @@ -522,6 +522,9 @@ XXLXOR_OPCODE = (60u << OPCODE_SHIFT | 154u << 3), XXLEQV_OPCODE = (60u << OPCODE_SHIFT | 186u << 3), + // Deliver A Random Number (introduced with POWER9) + DARN_OPCODE = (31u << OPCODE_SHIFT | 755 << 1), + // Vector Permute and Formatting VPKPX_OPCODE = (4u << OPCODE_SHIFT | 782u ), VPKSHSS_OPCODE = (4u << OPCODE_SHIFT | 398u ), @@ -2178,6 +2181,9 @@ inline void mtfprwa( FloatRegister d, Register a); inline void mffprd( Register a, FloatRegister d); + // Deliver A Random Number (introduced with POWER9) + inline void darn( Register d); + // AES (introduced with Power 8) inline void vcipher( VectorRegister d, VectorRegister a, VectorRegister b); inline void vcipherlast( VectorRegister d, VectorRegister a, VectorRegister b); diff --git a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp --- a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp @@ -959,6 +959,9 @@ inline void Assembler::tresume_() { emit_int32( TSR_OPCODE | /*L=1*/ 1u << (31-10) | rc(1)); } inline void Assembler::tcheck(int f) { emit_int32( TCHECK_OPCODE | bf(f)); } +// Deliver A Random Number (introduced with POWER9) +inline void Assembler::darn(Register d) { emit_int32( DARN_OPCODE | rt(d) | 1u << 17); } + // ra0 version inline void Assembler::lwzx( Register d, Register s2) { emit_int32( LWZX_OPCODE | rt(d) | rb(s2));} inline void Assembler::lwz( Register d, int si16 ) { emit_int32( LWZ_OPCODE | rt(d) | d1(si16));} diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -2655,6 +2655,9 @@ } } +void LIR_Assembler::rng(LIR_Opr dst) { + __ darn(dst->as_register_lo()); +} void LIR_Assembler::set_24bit_FPU() { Unimplemented(); diff --git a/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp --- a/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp @@ -1377,6 +1377,11 @@ } } +void LIRGenerator::do_Random(Intrinsic* x) { + LIR_Opr result_reg = rlock_result(x); + __ rng(result_reg); +} + void LIRGenerator::do_vectorizedMismatch(Intrinsic* x) { fatal("vectorizedMismatch intrinsic is not implemented on this platform"); } diff --git a/src/hotspot/cpu/ppc/globals_ppc.hpp b/src/hotspot/cpu/ppc/globals_ppc.hpp --- a/src/hotspot/cpu/ppc/globals_ppc.hpp +++ b/src/hotspot/cpu/ppc/globals_ppc.hpp @@ -205,5 +205,8 @@ \ experimental(bool, UseRTMXendForLockBusy, true, \ "Use RTM Xend instead of Xabort when lock busy") \ + \ + experimental(bool, UseRANDOMIntrinsics, false, \ + "Use hardware TRNG") \ #endif // CPU_PPC_VM_GLOBALS_PPC_HPP diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -14579,6 +14579,15 @@ ins_pipe(pipe_class_default); %} +instruct randL(iRegLdst dst) %{ + match(Set dst (RandL)); + format %{ "DARN $dst\t# long" %} + ins_encode %{ + __ darn($dst$$Register); + %} + ins_pipe(pipe_class_default); +%} + //----------PEEPHOLE RULES----------------------------------------------------- // These must follow all instruction definitions as they use the names // defined in the instructions definitions. diff --git a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp --- a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp @@ -2039,6 +2039,29 @@ return NULL; } +// POWER9: darn (Deliver a Random Number). +/** + * Method entry for static method: + * long sun.security.provider.HWTRNG.randomLong(void) + * + * This method is used by generateSeed() and nextBytes() from SecureRandom + * class when the JCA 'HWTRNG' provider is selected. + */ +address TemplateInterpreterGenerator::generate_HWTRNG_randomLong_entry() { + address _pc; + + if (VM_Version::has_darn()) { + _pc = __ pc(); + + __ darn(R3_RET); // Return a random number taken from HW TRNG + __ blr(); + + return _pc; + } else { + return NULL; + } +} + // ============================================================================= // Exceptions diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -131,7 +131,7 @@ // Create and print feature-string. char buf[(num_features+1) * 16]; // Max 16 chars per feature. jio_snprintf(buf, sizeof(buf), - "ppc64%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + "ppc64%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", (has_fsqrt() ? " fsqrt" : ""), (has_isel() ? " isel" : ""), (has_lxarxeh() ? " lxarxeh" : ""), @@ -149,7 +149,8 @@ (has_ldbrx() ? " ldbrx" : ""), (has_stdbrx() ? " stdbrx" : ""), (has_vshasig() ? " sha" : ""), - (has_tm() ? " rtm" : "") + (has_tm() ? " rtm" : ""), + (has_darn() ? " darn" : "") // Make sure number of %s matches num_features! ); _features_string = os::strdup(buf); @@ -663,6 +664,7 @@ a->ldbrx(R7, R3_ARG1, R4_ARG2); // code[14] -> ldbrx a->stdbrx(R7, R3_ARG1, R4_ARG2); // code[15] -> stdbrx a->vshasigmaw(VR0, VR1, 1, 0xF); // code[16] -> vshasig + a->darn(R7); // code[17] -> darn a->blr(); // Emit function to set one cache line to zero. Emit function descriptor and get pointer to it. @@ -714,6 +716,7 @@ if (code[feature_cntr++]) features |= ldbrx_m; if (code[feature_cntr++]) features |= stdbrx_m; if (code[feature_cntr++]) features |= vshasig_m; + if (code[feature_cntr++]) features |= darn_m; // Print the detection code. if (PrintAssembly) { diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp --- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp @@ -50,6 +50,7 @@ stdbrx, vshasig, rtm, + darn, num_features // last entry to count features }; enum Feature_Flag_Set { @@ -72,6 +73,7 @@ stdbrx_m = (1 << stdbrx ), vshasig_m = (1 << vshasig), rtm_m = (1 << rtm ), + darn_m = (1 << darn ), all_features_m = (unsigned long)-1 }; @@ -108,6 +110,7 @@ static bool has_ldbrx() { return (_features & ldbrx_m) != 0; } static bool has_stdbrx() { return (_features & stdbrx_m) != 0; } static bool has_vshasig() { return (_features & vshasig_m) != 0; } + static bool has_darn() { return (_features & darn_m) != 0; } static bool has_mtfprd() { return has_vpmsumb(); } // alias for P8 // OS feature support static bool has_tm() { return (_features & rtm_m) != 0; } diff --git a/src/hotspot/share/c1/c1_Compiler.cpp b/src/hotspot/share/c1/c1_Compiler.cpp --- a/src/hotspot/share/c1/c1_Compiler.cpp +++ b/src/hotspot/share/c1/c1_Compiler.cpp @@ -215,6 +215,7 @@ case vmIntrinsics::_updateCRC32: case vmIntrinsics::_updateBytesCRC32: case vmIntrinsics::_updateByteBufferCRC32: + case vmIntrinsics::_randomLong: #if defined(SPARC) || defined(S390) || defined(PPC64) || defined(AARCH64) case vmIntrinsics::_updateBytesCRC32C: case vmIntrinsics::_updateDirectByteBufferCRC32C: diff --git a/src/hotspot/share/c1/c1_LIR.cpp b/src/hotspot/share/c1/c1_LIR.cpp --- a/src/hotspot/share/c1/c1_LIR.cpp +++ b/src/hotspot/share/c1/c1_LIR.cpp @@ -446,6 +446,7 @@ case lir_std_entry: // may have result, info always invalid case lir_osr_entry: // may have result, info always invalid case lir_get_thread: // may have result, info always invalid + case lir_random: // { assert(op->as_Op0() != NULL, "must be"); if (op->_info != NULL) do_info(op->_info); @@ -1676,6 +1677,7 @@ case lir_monaddr: s = "mon_addr"; break; case lir_pack64: s = "pack64"; break; case lir_unpack64: s = "unpack64"; break; + case lir_random: s = "random"; break; // LIR_Op2 case lir_cmp: s = "cmp"; break; case lir_cmp_l2i: s = "cmp_l2i"; break; diff --git a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp --- a/src/hotspot/share/c1/c1_LIR.hpp +++ b/src/hotspot/share/c1/c1_LIR.hpp @@ -901,6 +901,7 @@ , lir_membar_storeload , lir_get_thread , lir_on_spin_wait + , lir_random , end_op0 , begin_op1 , lir_fxch @@ -2054,6 +2055,7 @@ } void get_thread(LIR_Opr result) { append(new LIR_Op0(lir_get_thread, result)); } + void rng(LIR_Opr result) { append(new LIR_Op0(lir_random, result)); } void word_align() { append(new LIR_Op0(lir_word_align)); } void membar() { append(new LIR_Op0(lir_membar)); } void membar_acquire() { append(new LIR_Op0(lir_membar_acquire)); } diff --git a/src/hotspot/share/c1/c1_LIRAssembler.cpp b/src/hotspot/share/c1/c1_LIRAssembler.cpp --- a/src/hotspot/share/c1/c1_LIRAssembler.cpp +++ b/src/hotspot/share/c1/c1_LIRAssembler.cpp @@ -681,6 +681,10 @@ get_thread(op->result_opr()); break; + case lir_random: + rng(op->result_opr()); + break; + case lir_on_spin_wait: on_spin_wait(); break; diff --git a/src/hotspot/share/c1/c1_LIRAssembler.hpp b/src/hotspot/share/c1/c1_LIRAssembler.hpp --- a/src/hotspot/share/c1/c1_LIRAssembler.hpp +++ b/src/hotspot/share/c1/c1_LIRAssembler.hpp @@ -253,6 +253,7 @@ void membar_storeload(); void on_spin_wait(); void get_thread(LIR_Opr result); + void rng(LIR_Opr result); void verify_oop_map(CodeEmitInfo* info); diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -3024,6 +3024,8 @@ case vmIntrinsics::_fmaD: do_FmaIntrinsic(x); break; case vmIntrinsics::_fmaF: do_FmaIntrinsic(x); break; + case vmIntrinsics::_randomLong: do_Random(x); break; + // java.nio.Buffer.checkIndex case vmIntrinsics::_checkIndex: do_NIOCheckIndex(x); break; diff --git a/src/hotspot/share/c1/c1_LIRGenerator.hpp b/src/hotspot/share/c1/c1_LIRGenerator.hpp --- a/src/hotspot/share/c1/c1_LIRGenerator.hpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.hpp @@ -255,6 +255,7 @@ void do_getClass(Intrinsic* x); void do_currentThread(Intrinsic* x); void do_FmaIntrinsic(Intrinsic* x); + void do_Random(Intrinsic* x); void do_MathIntrinsic(Intrinsic* x); void do_LibmIntrinsic(Intrinsic* x); void do_ArrayCopy(Intrinsic* x); diff --git a/src/hotspot/share/classfile/vmSymbols.cpp b/src/hotspot/share/classfile/vmSymbols.cpp --- a/src/hotspot/share/classfile/vmSymbols.cpp +++ b/src/hotspot/share/classfile/vmSymbols.cpp @@ -589,6 +589,8 @@ case vmIntrinsics::_updateByteBufferCRC32: if (!UseCRC32Intrinsics) return true; break; + case vmIntrinsics::_randomLong: + if (!UseRANDOMIntrinsics) return true; case vmIntrinsics::_getObject: case vmIntrinsics::_getBoolean: case vmIntrinsics::_getByte: diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -746,6 +746,11 @@ do_signature(int2_int_signature, "(II)I") \ do_signature(long2_long_signature, "(JJ)J") \ \ + /* HW True Random Number Generator intrinsics */ \ + do_class(sun_security_provider_HWTRNG, "sun/security/provider/HWTRNG") \ + do_intrinsic(_randomLong, sun_security_provider_HWTRNG, randomLong_name, void_long_signature, F_S) \ + do_name(randomLong_name, "randomLong") \ + \ /* here are the math names, all together: */ \ do_name(abs_name,"abs") do_name(sin_name,"sin") do_name(cos_name,"cos") \ do_name(tan_name,"tan") do_name(atan2_name,"atan2") do_name(sqrt_name,"sqrt") \ @@ -1533,7 +1538,7 @@ #undef VM_INTRINSIC_ENUM ID_LIMIT, - LAST_COMPILER_INLINE = _getAndSetObject, + LAST_COMPILER_INLINE = _randomLong, FIRST_MH_SIG_POLY = _invokeGeneric, FIRST_MH_STATIC = _linkToVirtual, LAST_MH_SIG_POLY = _linkToInterface, diff --git a/src/hotspot/share/interpreter/abstractInterpreter.cpp b/src/hotspot/share/interpreter/abstractInterpreter.cpp --- a/src/hotspot/share/interpreter/abstractInterpreter.cpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.cpp @@ -136,6 +136,8 @@ case vmIntrinsics::_floatToRawIntBits: return java_lang_Float_floatToRawIntBits; case vmIntrinsics::_longBitsToDouble: return java_lang_Double_longBitsToDouble; case vmIntrinsics::_doubleToRawLongBits: return java_lang_Double_doubleToRawLongBits; + // For generateSeed() and nextBytes() from HWTRNG JCA provider. + case vmIntrinsics::_randomLong: return sun_security_provider_HWTRNG_randomLong; default: break; } #endif // CC_INTERP diff --git a/src/hotspot/share/interpreter/abstractInterpreter.hpp b/src/hotspot/share/interpreter/abstractInterpreter.hpp --- a/src/hotspot/share/interpreter/abstractInterpreter.hpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.hpp @@ -89,6 +89,7 @@ java_lang_Float_floatToRawIntBits, // implementation of java.lang.Float.floatToRawIntBits() java_lang_Double_longBitsToDouble, // implementation of java.lang.Double.longBitsToDouble() java_lang_Double_doubleToRawLongBits, // implementation of java.lang.Double.doubleToRawLongBits() + sun_security_provider_HWTRNG_randomLong, // implementation of sun.security.provider.HWTRNG.randomLong() used by generateSeed() and nextBytes() number_of_method_entries, invalid = -1 }; diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp @@ -227,6 +227,8 @@ method_entry(java_lang_Double_longBitsToDouble); method_entry(java_lang_Double_doubleToRawLongBits); + method_entry(sun_security_provider_HWTRNG_randomLong) + #undef method_entry // Bytecodes @@ -441,6 +443,8 @@ : // fall thru case Interpreter::java_util_zip_CRC32C_updateDirectByteBuffer : entry_point = generate_CRC32C_updateBytes_entry(kind); break; + case Interpreter::sun_security_provider_HWTRNG_randomLong + : entry_point = generate_HWTRNG_randomLong_entry(); break; #ifdef IA32 // On x86_32 platforms, a special entry is generated for the following four methods. // On other platforms the normal entry is used to enter these methods. diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp @@ -86,6 +86,9 @@ // entry point generator address generate_method_entry(AbstractInterpreter::MethodKind kind); + // random number + address generate_HWTRNG_randomLong_entry(void); + address generate_normal_entry(bool synchronized); address generate_native_entry(bool synchronized); address generate_abstract_entry(void); diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -422,6 +422,9 @@ case vmIntrinsics::_onSpinWait: if (!Matcher::match_rule_supported(Op_OnSpinWait)) return false; break; + case vmIntrinsics::_randomLong: + if (!Matcher::match_rule_supported(Op_RandL)) return false; + break; case vmIntrinsics::_fmaD: if (!UseFMA || !Matcher::match_rule_supported(Op_FmaD)) return false; break; diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -379,3 +379,4 @@ macro(ExtractL) macro(ExtractF) macro(ExtractD) +macro(RandL) diff --git a/src/hotspot/share/opto/intrinsicnode.hpp b/src/hotspot/share/opto/intrinsicnode.hpp --- a/src/hotspot/share/opto/intrinsicnode.hpp +++ b/src/hotspot/share/opto/intrinsicnode.hpp @@ -180,4 +180,14 @@ virtual const Type* Value(PhaseGVN* phase) const; }; + +//------------------------------RandL----------------------------------------- +class RandLNode : public Node { +public: + RandLNode(Node* c) : Node(c) {} + virtual int Opcode() const; + const Type* bottom_type() const { return TypeLong::LONG; } + virtual uint ideal_reg() const { return Op_RegL; } +}; + #endif // SHARE_VM_OPTO_INTRINSICNODE_HPP diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -239,6 +239,7 @@ bool inline_math_negateExactL(); bool inline_math_subtractExactI(bool is_decrement); bool inline_math_subtractExactL(bool is_decrement); + bool inline_randomLong(vmIntrinsics::ID id); bool inline_min_max(vmIntrinsics::ID id); bool inline_notify(vmIntrinsics::ID id); Node* generate_min_max(vmIntrinsics::ID id, Node* x, Node* y); @@ -522,6 +523,7 @@ switch (intrinsic_id()) { + case vmIntrinsics::_randomLong: return inline_randomLong(intrinsic_id()); case vmIntrinsics::_hashCode: return inline_native_hashcode(intrinsic()->is_virtual(), !is_static); case vmIntrinsics::_identityHashCode: return inline_native_hashcode(/*!virtual*/ false, is_static); case vmIntrinsics::_getClass: return inline_native_getClass(); @@ -3785,6 +3787,21 @@ } +bool LibraryCallKit::inline_randomLong(vmIntrinsics::ID id) { + Node* n = NULL; + + switch (id) { + case vmIntrinsics::_randomLong: + if (!Matcher::match_rule_supported(Op_RandL)) + return false; + n = new RandLNode(control()); + break; + default: fatal_unexpected_iid(id); + } + set_result(_gvn.transform(n)); + return true; +} + /** * Build special case code for calls to hashCode on an object. This call may * be virtual (invokevirtual) or bound (invokespecial). For each case we generate diff --git a/src/java.base/share/classes/sun/security/provider/SunEntries.java b/src/java.base/share/classes/sun/security/provider/SunEntries.java --- a/src/java.base/share/classes/sun/security/provider/SunEntries.java +++ b/src/java.base/share/classes/sun/security/provider/SunEntries.java @@ -129,6 +129,10 @@ map.put("SecureRandom.NativePRNGNonBlocking ThreadSafe", "true"); } + // Provider for using TRNG. + map.put("SecureRandom.HWTRNG", "sun.security.provider.HWTRNG"); + map.put("SecureRandom.HWTRNG ThreadSafe", "true"); + /* * Signature engines */ diff --git a/src/java.base/unix/classes/sun/security/provider/HWTRNG.java b/src/java.base/unix/classes/sun/security/provider/HWTRNG.java new file mode 100644 --- /dev/null +++ b/src/java.base/unix/classes/sun/security/provider/HWTRNG.java @@ -0,0 +1,668 @@ +package sun.security.provider; + +import java.io.*; +import java.nio.*; +import java.net.*; +import java.security.*; +import java.util.Arrays; + +import sun.security.util.Debug; + +import jdk.internal.HotSpotIntrinsicCandidate; + +/** + * Hardware True Random Number Generator provider. + * @since 11 + * @author Gustavo Romero + */ +public final class HWTRNG extends SecureRandomSpi { + + private static final long serialVersionUID = -7529628636369209955L; + + private static final Debug debug = Debug.getInstance("provider"); + + // name of the pure random file (also used for setSeed()) + private static final String NAME_RANDOM = "/dev/random"; + // name of the pseudo random file + private static final String NAME_URANDOM = "/dev/urandom"; + + // which kind of RandomIO object are we creating? + private enum Variant { + MIXED, BLOCKING, NONBLOCKING + } + + // singleton instance or null if not available + private static final RandomIO INSTANCE = initIO(Variant.MIXED); + + /** + * Get the System egd source (if defined). We only allow "file:" + * URLs for now. If there is a egd value, parse it. + * + * @return the URL or null if not available. + */ + private static URL getEgdUrl() { + // This will return "" if nothing was set. + String egdSource = SunEntries.getSeedSource(); + URL egdUrl; + + if (egdSource.length() != 0) { + if (debug != null) { + debug.println("NativePRNG egdUrl: " + egdSource); + } + try { + egdUrl = new URL(egdSource); + if (!egdUrl.getProtocol().equalsIgnoreCase("file")) { + return null; + } + } catch (MalformedURLException e) { + return null; + } + } else { + egdUrl = null; + } + + return egdUrl; + } + + /** + * Create a RandomIO object for all I/O of this Variant type. + */ + private static RandomIO initIO(final Variant v) { + return AccessController.doPrivileged( + new PrivilegedAction<>() { + @Override + public RandomIO run() { + + File seedFile; + File nextFile; + + switch(v) { + case MIXED: + URL egdUrl; + File egdFile = null; + + if ((egdUrl = getEgdUrl()) != null) { + try { + egdFile = SunEntries.getDeviceFile(egdUrl); + } catch (IOException e) { + // Swallow, seedFile is still null + } + } + + // Try egd first. + if ((egdFile != null) && egdFile.canRead()) { + seedFile = egdFile; + } else { + // fall back to /dev/random. + seedFile = new File(NAME_RANDOM); + } + nextFile = new File(NAME_URANDOM); + break; + + case BLOCKING: + seedFile = new File(NAME_RANDOM); + nextFile = new File(NAME_RANDOM); + break; + + case NONBLOCKING: + seedFile = new File(NAME_URANDOM); + nextFile = new File(NAME_URANDOM); + break; + + default: + // Shouldn't happen! + return null; + } + + if (debug != null) { + debug.println("NativePRNG." + v + + " seedFile: " + seedFile + + " nextFile: " + nextFile); + } + + if (!seedFile.canRead() || !nextFile.canRead()) { + if (debug != null) { + debug.println("NativePRNG." + v + + " Couldn't read Files."); + } + return null; + } + + try { + return new RandomIO(seedFile, nextFile); + } catch (Exception e) { + return null; + } + } + }); + } + + // return whether the HWTPRNG is available + static boolean isAvailable() { + return INSTANCE != null; + } + + // constructor, called by the JCA framework + public HWTRNG() { + super(); + if (INSTANCE == null) { + throw new AssertionError("HWTRNG not available"); + } + } + + /** + * Helpers for using HW RNG: + * byte[] getRandomSequence0(int) + * byte[] getRandomSequence1(byte[]) + * long validRandomLong(void) + * long randomLong(void) + */ + + // Obtain a random number sequence using hardware instruction given a + // 'numBytes' of requested bytes and return the seed sequence or -1L if + // the HW RNG fails. + static public byte[] getRandomSequence0(int numBytes) { + + int n = numBytes / (Long.BYTES); // how many random longs? + int r = numBytes % (Long.BYTES); // need an additional long? + + long random_number; + ByteBuffer buffer; + + // allocate buffer + buffer = ByteBuffer.allocate(n * Long.BYTES + (r != 0 ? Long.BYTES : 0)); + + // copy a long a time + int i; + for (i = 0; i < n; i++) + if ((random_number = validRandomLong()) == -1L) + return null; // after 10 attempts no valid random number + else + buffer.putLong(random_number); + + // copy a single long for remaining bytes, if any + if (r != 0) + if ((random_number = validRandomLong()) == -1L) + return null; // after 10 attemps no valid random number + else + buffer.putLong(random_number); + + // extract just the bytes we need from buffer + byte[] b = Arrays.copyOfRange(buffer.array(), 0, n * Long.BYTES + r); + + return b; + } + + // Obtain a random sequence using hardware instruction into 'bytes' byte + // array and return a pointer to that array or a null pointer if it fails. + static public byte[] getRandomSequence1(byte[] bytes) { + + int n = bytes.length / (Long.BYTES); // how many random longs? + int r = bytes.length % (Long.BYTES); // need an additional long? + + long random_number; + + // allocate temporary buffer + ByteBuffer buffer = ByteBuffer.allocate(n * Long.BYTES + (r != 0 ? Long.BYTES : 0)); + + // copy a long a time + int i; + for (i = 0; i < n; i++) + if ((random_number = validRandomLong()) == -1L) + return null; // after 10 attempts no valid random number + else + buffer.putLong(random_number); + + // copy a single long for remaining bytes, if any + if (r != 0) + if ((random_number = validRandomLong()) == -1L) + return null; // after 10 attempts no valid random number + else + buffer.putLong(random_number); + + buffer.rewind(); + buffer.get(bytes); + + return bytes; + } + + // We do the best to try to obtain a valid random (long) number by means + // of a HW instruction. If it's not possible to obtain a valid + // random number it passes the error ahead so callers can decide what + // to do by themselves. + // For instance, the Power ISA v3.0 recommends 10 attempts. However + // it's worth to note that hardware will fail in very rare occasions + // usually just associated to a HW malfunction and not due to lack of + // entropy. Also the HW TRNG has usually a couple of internal checkers + // to ensure the random number returned is NIST SP800-90A/B and + // SP800-90C compliant before making the random number available in + // HW random number pool. It's possible to move that check to the + // intrinsic code but on profiling validRandomLong() it did not look + // of concern, specially for JITed code. + static public long validRandomLong() { + int attempts; + long r; + + for (attempts = 10; attempts > 0; attempts--) { + r = randomLong(); // get a random number + if (r != 0xFFFFFFFF_FFFFFFFFL) + return r; // valid random number + } + + return 0xFFFFFFFF_FFFFFFFFL; // error + } + + // This method is intended to be intrinsified. When not intrinsified + // it returns an error value. For Power that value is exactly the same + // as returned by the hardware when it failes to generated a valid random + // random number. + // When this method is intrinsified and there is no problem with the HW + // RNG it returns a random number with a minimum of 0.5 bits of entropy + // per bit. + @HotSpotIntrinsicCandidate + static public long randomLong() { + /* + * Power: + * Accordingly to the ISA v3.0 0xFFFFFFFF_FFFFFFFF indicates an + * error condition for 'darn' instruction, i.e. the random + * number generator failed to generate a random number and + * so it's necessary to use a software random number + * generation method if the failure repeats after ten + * attempts. That condition is also returned if this method + * has no intrinsic available, otherwise it will be substituted + * by its intrisified form which does not return the error value + * when the RNG is sane. + * + * Intel: + * On CF=0 (i.e. 'rdrand' returned an invalid random number) the + * intrinsic can return -1L to inform about an invalid random + * number. + */ + return 0xFFFFFFFF_FFFFFFFFL; // return error condition by default + //return 0xDEADBEEF_DEADBEEFL; // for debug purposes. Uncommenting it + // will make generateSeed() and + // nextBytes() return a sequence of + // 0xdeadbeefdeadbeef when it's not + // intrinsified since it's treated as + // valid number and so it won't be + // routed to the SW fallback. + + } + + // set the seed + @Override + protected void engineSetSeed(byte[] seed) { + INSTANCE.implSetSeed(seed); + } + + // get pseudo random bytes + @Override + protected void engineNextBytes(byte[] bytes) { + INSTANCE.implNextBytes(bytes); + } + + // get true random bytes + @Override + protected byte[] engineGenerateSeed(int numBytes) { + return INSTANCE.implGenerateSeed(numBytes); + } + + /** + * A NativePRNG-like class that uses /dev/random for both + * seed and random material. + * + * Note that it does not respect the egd properties, since we have + * no way of knowing what those qualities are. + * + * This is very similar to the outer NativePRNG class, minimizing any + * breakage to the serialization of the existing implementation. + * + * @since 1.8 + */ + public static final class Blocking extends SecureRandomSpi { + private static final long serialVersionUID = -6396183145759983347L; + + private static final RandomIO INSTANCE = initIO(Variant.BLOCKING); + + // return whether this is available + static boolean isAvailable() { + return INSTANCE != null; + } + + // constructor, called by the JCA framework + public Blocking() { + super(); + if (INSTANCE == null) { + throw new AssertionError("NativePRNG$Blocking not available"); + } + } + + // set the seed + @Override + protected void engineSetSeed(byte[] seed) { + INSTANCE.implSetSeed(seed); + } + + // get pseudo random bytes + @Override + protected void engineNextBytes(byte[] bytes) { + INSTANCE.implNextBytes(bytes); + } + + // get true random bytes + @Override + protected byte[] engineGenerateSeed(int numBytes) { + return INSTANCE.implGenerateSeed(numBytes); + } + } + + /** + * A NativePRNG-like class that uses /dev/urandom for both + * seed and random material. + * + * Note that it does not respect the egd properties, since we have + * no way of knowing what those qualities are. + * + * This is very similar to the outer NativePRNG class, minimizing any + * breakage to the serialization of the existing implementation. + * + * @since 1.8 + */ + public static final class NonBlocking extends SecureRandomSpi { + private static final long serialVersionUID = -1102062982994105487L; + + private static final RandomIO INSTANCE = initIO(Variant.NONBLOCKING); + + // return whether this is available + static boolean isAvailable() { + return INSTANCE != null; + } + + // constructor, called by the JCA framework + public NonBlocking() { + super(); + if (INSTANCE == null) { + throw new AssertionError( + "NativePRNG$NonBlocking not available"); + } + } + + // set the seed + @Override + protected void engineSetSeed(byte[] seed) { + INSTANCE.implSetSeed(seed); + } + + // get pseudo random bytes + @Override + protected void engineNextBytes(byte[] bytes) { + INSTANCE.implNextBytes(bytes); + } + + // get true random bytes + @Override + protected byte[] engineGenerateSeed(int numBytes) { + return INSTANCE.implGenerateSeed(numBytes); + } + } + + /** + * Nested class doing the actual work. Singleton, see INSTANCE above. + */ + private static class RandomIO { + + // we buffer data we read from the "next" file for efficiency, + // but we limit the lifetime to avoid using stale bits + // lifetime in ms, currently 100 ms (0.1 s) + private static final long MAX_BUFFER_TIME = 100; + + // size of the "next" buffer + private static final int MAX_BUFFER_SIZE = 65536; + private static final int MIN_BUFFER_SIZE = 32; + private int bufferSize = 256; + + // Holder for the seedFile. Used if we ever add seed material. + File seedFile; + + // In/OutputStream for "seed" and "next" + private final InputStream seedIn, nextIn; + private OutputStream seedOut; + + // flag indicating if we have tried to open seedOut yet + private boolean seedOutInitialized; + + // SHA1PRNG instance for mixing + // initialized lazily on demand to avoid problems during startup + private volatile sun.security.provider.SecureRandom mixRandom; + + // buffer for next bits + private byte[] nextBuffer; + + // number of bytes left in nextBuffer + private int buffered; + + // time we read the data into the nextBuffer + private long lastRead; + + // Count for the number of buffer size changes requests + // Positive value in increase size, negative to lower it. + private int change_buffer = 0; + + // Request limit to trigger an increase in nextBuffer size + private static final int REQ_LIMIT_INC = 1000; + + // Request limit to trigger a decrease in nextBuffer size + private static final int REQ_LIMIT_DEC = -100; + + // mutex lock for nextBytes() + private final Object LOCK_GET_BYTES = new Object(); + + // mutex lock for generateSeed() + private final Object LOCK_GET_SEED = new Object(); + + // mutex lock for setSeed() + private final Object LOCK_SET_SEED = new Object(); + + // constructor, called only once from initIO() + private RandomIO(File seedFile, File nextFile) throws IOException { + this.seedFile = seedFile; + seedIn = FileInputStreamPool.getInputStream(seedFile); + nextIn = FileInputStreamPool.getInputStream(nextFile); + nextBuffer = new byte[bufferSize]; + } + + // get the SHA1PRNG for mixing + // initialize if not yet created + private sun.security.provider.SecureRandom getMixRandom() { + sun.security.provider.SecureRandom r = mixRandom; + if (r == null) { + synchronized (LOCK_GET_BYTES) { + r = mixRandom; + if (r == null) { + r = new sun.security.provider.SecureRandom(); + try { + byte[] b = new byte[20]; + readFully(nextIn, b); + r.engineSetSeed(b); + } catch (IOException e) { + throw new ProviderException("init failed", e); + } + mixRandom = r; + } + } + } + return r; + } + + // read data.length bytes from in + // These are not normal files, so we need to loop the read. + // just keep trying as long as we are making progress + private static void readFully(InputStream in, byte[] data) + throws IOException { + int len = data.length; + int ofs = 0; + while (len > 0) { + int k = in.read(data, ofs, len); + if (k <= 0) { + throw new EOFException("File(s) closed?"); + } + ofs += k; + len -= k; + } + if (len > 0) { + throw new IOException("Could not read from file(s)"); + } + } + + // get true random bytes, just read from "seed" + private byte[] implGenerateSeed(int numBytes) { + synchronized (LOCK_GET_SEED) { + try { + byte[] b; + b = getRandomSequence0(numBytes); + + // Failed to get random sequence using + // hardware, fallback to NativePRNG + if (b == null) { + b = new byte[numBytes]; + readFully(seedIn, b); + } + + return b; + } catch (IOException e) { + throw new ProviderException("generateSeed() failed", e); + } + } + } + + // supply random bytes to the OS + // write to "seed" if possible + // always add the seed to our mixing random + private void implSetSeed(byte[] seed) { + synchronized (LOCK_SET_SEED) { + if (seedOutInitialized == false) { + seedOutInitialized = true; + seedOut = AccessController.doPrivileged( + new PrivilegedAction<>() { + @Override + public OutputStream run() { + try { + return new FileOutputStream(seedFile, true); + } catch (Exception e) { + return null; + } + } + }); + } + if (seedOut != null) { + try { + seedOut.write(seed); + } catch (IOException e) { + // Ignored. On Mac OS X, /dev/urandom can be opened + // for write, but actual write is not permitted. + } + } + getMixRandom().engineSetSeed(seed); + } + } + + // ensure that there is at least one valid byte in the buffer + // if not, read new bytes + private void ensureBufferValid() throws IOException { + long time = System.currentTimeMillis(); + int new_buffer_size = 0; + + // Check if buffer has bytes available that are not too old + if (buffered > 0) { + if (time - lastRead < MAX_BUFFER_TIME) { + return; + } else { + // byte is old, so subtract from counter to shrink buffer + change_buffer--; + } + } else { + // No bytes available, so add to count to increase buffer + change_buffer++; + } + + // If counter has it a limit, increase or decrease size + if (change_buffer > REQ_LIMIT_INC) { + new_buffer_size = nextBuffer.length * 2; + } else if (change_buffer < REQ_LIMIT_DEC) { + new_buffer_size = nextBuffer.length / 2; + } + + // If buffer size is to be changed, replace nextBuffer. + if (new_buffer_size > 0) { + if (new_buffer_size <= MAX_BUFFER_SIZE && + new_buffer_size >= MIN_BUFFER_SIZE) { + nextBuffer = new byte[new_buffer_size]; + if (debug != null) { + debug.println("Buffer size changed to " + + new_buffer_size); + } + } else { + if (debug != null) { + debug.println("Buffer reached limit: " + + nextBuffer.length); + } + } + change_buffer = 0; + } + + // Load fresh random bytes into nextBuffer + lastRead = time; + readFully(nextIn, nextBuffer); + buffered = nextBuffer.length; + } + + // try to get random bytes using a HW TRNG, if that fails + // get pseudo random bytes + // read from "next" and XOR with bytes generated by the + // mixing SHA1PRNG + private void implNextBytes(byte[] data) { + + synchronized (LOCK_GET_BYTES) { + if (getRandomSequence1(data) != null) + return; + } + + // Failed to get random sequence using + // hardware, fallback to NativePRNG + try { + getMixRandom().engineNextBytes(data); + int data_len = data.length; + int ofs = 0; + int len; + int buf_pos; + int localofs; + byte[] localBuffer; + + while (data_len > 0) { + synchronized (LOCK_GET_BYTES) { + ensureBufferValid(); + buf_pos = nextBuffer.length - buffered; + if (data_len > buffered) { + len = buffered; + buffered = 0; + } else { + len = data_len; + buffered -= len; + } + localBuffer = Arrays.copyOfRange(nextBuffer, buf_pos, + buf_pos + len); + } + localofs = 0; + while (len > localofs) { + data[ofs] ^= localBuffer[localofs]; + ofs++; + localofs++; + } + data_len -= len; + } + } catch (IOException e){ + throw new ProviderException("nextBytes() failed", e); + } + } + } +} diff --git a/src/java.base/unix/classes/sun/security/provider/NativePRNG.java b/src/java.base/unix/classes/sun/security/provider/NativePRNG.java --- a/src/java.base/unix/classes/sun/security/provider/NativePRNG.java +++ b/src/java.base/unix/classes/sun/security/provider/NativePRNG.java @@ -566,5 +566,5 @@ throw new ProviderException("nextBytes() failed", e); } } - } + } }