/* * The MIT License * * Copyright (c) 2014 Karol Bucek LTD. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jruby.ext.openssl; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.Map; import org.jruby.CompatVersion; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.anno.JRubyModule; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.util.SafePropertyAccessor; /** * OpenSSL (methods as well as an entry point) * * @author kares */ @JRubyModule(name = "OpenSSL") public final class OpenSSL { public static void load(final Ruby runtime) { createOpenSSL(runtime); } public static boolean isProviderAvailable() { return SecurityHelper.isProviderAvailable("BC"); } public static void createOpenSSL(final Ruby runtime) { SecurityHelper.setRegisterProvider( SafePropertyAccessor.getBoolean("jruby.openssl.provider.register") ); final RubyModule _OpenSSL = runtime.getOrCreateModule("OpenSSL"); RubyClass _StandardError = runtime.getClass("StandardError"); _OpenSSL.defineClassUnder("OpenSSLError", _StandardError, _StandardError.getAllocator()); _OpenSSL.defineAnnotatedMethods(OpenSSL.class); // set OpenSSL debug internal flag early on so it can print traces even while loading extension setDebug(_OpenSSL, runtime.newBoolean( SafePropertyAccessor.getBoolean("jruby.openssl.debug") ) ); final String warn = SafePropertyAccessor.getProperty("jruby.openssl.warn"); if ( warn != null ) OpenSSL.warn = Boolean.parseBoolean(warn); PKey.createPKey(runtime, _OpenSSL); BN.createBN(runtime, _OpenSSL); Digest.createDigest(runtime, _OpenSSL); Cipher.createCipher(runtime, _OpenSSL); Random.createRandom(runtime, _OpenSSL); HMAC.createHMAC(runtime, _OpenSSL); Config.createConfig(runtime, _OpenSSL); ASN1.createASN1(runtime, _OpenSSL); X509.createX509(runtime, _OpenSSL); NetscapeSPKI.createNetscapeSPKI(runtime, _OpenSSL); SSL.createSSL(runtime, _OpenSSL); PKCS7.createPKCS7(runtime, _OpenSSL); PKCS5.createPKCS5(runtime, _OpenSSL); OCSP.createOCSP(runtime, _OpenSSL); runtime.getLoadService().require("jopenssl/version"); // MRI 1.8.7 : // OpenSSL::VERSION: "1.0.0" // OpenSSL::OPENSSL_VERSION: "OpenSSL 1.0.1c 10 May 2012" // OpenSSL::OPENSSL_VERSION_NUMBER: 268439615 // MRI 1.9.3 / 2.2.3 : // OpenSSL::VERSION: "1.1.0" // OpenSSL::OPENSSL_VERSION: "OpenSSL 1.0.1f 6 Jan 2014" // OpenSSL::OPENSSL_VERSION_NUMBER: 268439663 // OpenSSL::OPENSSL_LIBRARY_VERSION: ""OpenSSL 1.0.2d 9 Jul 2015" // OpenSSL::FIPS: false final byte[] version = { '1','.','1','.','0' }; final boolean ruby18 = runtime.getInstanceConfig().getCompatVersion() == CompatVersion.RUBY1_8; if ( ruby18 ) version[2] = '0'; // 1.0.0 compatible on 1.8 _OpenSSL.setConstant("VERSION", StringHelper.newString(runtime, version)); final RubyModule _Jopenssl = runtime.getModule("Jopenssl"); final RubyModule _Version = (RubyModule) _Jopenssl.getConstantAt("Version"); final RubyString jVERSION = _Version.getConstantAt("VERSION").asString(); final byte[] JRuby_OpenSSL_ = { 'J','R','u','b','y','-','O','p','e','n','S','S','L',' ' }; final int OPENSSL_VERSION_NUMBER = 999999999; // NOTE: smt more useful? ByteList OPENSSL_VERSION = new ByteList( jVERSION.getByteList().length() + JRuby_OpenSSL_.length ); OPENSSL_VERSION.setEncoding( jVERSION.getEncoding() ); OPENSSL_VERSION.append( JRuby_OpenSSL_ ); OPENSSL_VERSION.append( jVERSION.getByteList() ); final RubyString VERSION; _OpenSSL.setConstant("OPENSSL_VERSION", VERSION = runtime.newString(OPENSSL_VERSION)); _OpenSSL.setConstant("OPENSSL_VERSION_NUMBER", runtime.newFixnum(OPENSSL_VERSION_NUMBER)); if ( ! ruby18 ) { // MRI 2.3 tests do: /\AOpenSSL +0\./ !~ OpenSSL::OPENSSL_LIBRARY_VERSION _OpenSSL.setConstant("OPENSSL_LIBRARY_VERSION", VERSION); _OpenSSL.setConstant("OPENSSL_FIPS", runtime.getFalse()); } } static RubyClass _OpenSSLError(final Ruby runtime) { return runtime.getModule("OpenSSL").getClass("OpenSSLError"); } // OpenSSL module methods : @JRubyMethod(name = "errors", meta = true) public static IRubyObject errors(IRubyObject self) { final Ruby runtime = self.getRuntime(); RubyArray result = runtime.newArray(); for (Map.Entry<Integer, String> e : X509.getErrors().entrySet()) { result.add( runtime.newString( e.getValue() ) ); } return result; } @JRubyMethod(name = "debug", meta = true) public static IRubyObject getDebug(IRubyObject self) { return (IRubyObject) getDebug((RubyModule) self); } private static Object getDebug(RubyModule self) { return self.getInternalVariable("debug"); } @JRubyMethod(name = "debug=", meta = true) public static IRubyObject setDebug(IRubyObject self, IRubyObject debug) { ((RubyModule) self).setInternalVariable("debug", debug); OpenSSL.debug = debug.isTrue(); return debug; } @JRubyMethod(name = "Digest", meta = true) public static IRubyObject Digest(final IRubyObject self, final IRubyObject name) { // OpenSSL::Digest("MD5") -> OpenSSL::Digest::MD5 final Ruby runtime = self.getRuntime(); final RubyClass Digest = runtime.getModule("OpenSSL").getClass("Digest"); return Digest.getConstantAt( name.asString().toString() ); } // API "stubs" in JRuby-OpenSSL : @JRubyMethod(meta = true) public static IRubyObject deprecated_warning_flag(final IRubyObject self) { return self.getRuntime().getNil(); // no-op in JRuby-OpenSSL } @JRubyMethod(meta = true, rest = true) // check_func(func, header) public static IRubyObject check_func(final IRubyObject self, final IRubyObject[] args) { return self.getRuntime().getNil(); // no-op in JRuby-OpenSSL } // Added in 2.0; not masked because it does nothing anyway (there's no reader in MRI) @JRubyMethod(name = "fips_mode=", meta = true) public static IRubyObject set_fips_mode(ThreadContext context, IRubyObject self, IRubyObject value) { if ( value.isTrue() ) { warn(context, "WARNING: FIPS mode not supported on JRuby-OpenSSL"); } return value; } // internal (package-level) helpers : /** * PRIMARILY MEANT FOR TESTING ONLY, USAGE IS DISCOURAGED! * @see org.jruby.ext.openssl.util.CryptoSecurity */ @JRubyMethod(name = "_disable_security_restrictions!", visibility = Visibility.PRIVATE, meta = true) public static IRubyObject _disable_security_restrictions(ThreadContext context, IRubyObject self) { Boolean unrestrict = org.jruby.ext.openssl.util.CryptoSecurity.unrestrictSecurity(); Boolean allPerm = org.jruby.ext.openssl.util.CryptoSecurity.setAllPermissionPolicy(); if ( unrestrict == null || allPerm == null ) return context.nil; return context.runtime.newBoolean( unrestrict && allPerm ); } private static boolean debug; // on by default, warnings can be disabled using -Djruby.openssl.warn=false private static boolean warn = true; static boolean isDebug() { return debug; } public static void debugStackTrace(final Throwable e) { if ( isDebug() ) e.printStackTrace(System.out); } public static void debug(final String msg) { if ( isDebug() ) System.out.println(msg); } public static void debug(final String msg, final Throwable e) { if ( isDebug() ) System.out.println(msg + ' ' + e); } static boolean isDebug(final Ruby runtime) { final RubyModule OpenSSL = runtime.getModule("OpenSSL"); if ( OpenSSL == null ) return debug; // debug early on return getDebug( OpenSSL ) == runtime.getTrue(); } static void debugStackTrace(final Ruby runtime, final Throwable e) { if ( isDebug(runtime) ) e.printStackTrace(runtime.getOut()); } public static void debug(final Ruby runtime, final CharSequence msg) { if ( isDebug(runtime) ) runtime.getOut().println(msg.toString()); } public static void debug(final Ruby runtime, final CharSequence msg, final Throwable e) { if ( isDebug(runtime) ) runtime.getOut().println(msg.toString() + ' ' + e); } static void warn(final ThreadContext context, final CharSequence msg) { warn(context, RubyString.newString(context.runtime, msg)); } static void warn(final ThreadContext context, final RubyString msg) { warn(context, (IRubyObject) msg); } static void warn(final ThreadContext context, final IRubyObject msg) { if ( warn ) context.runtime.getModule("OpenSSL").callMethod(context, "warn", msg); } public static String javaVersion(final String def) { final String javaVersionProperty = SafePropertyAccessor.getProperty("java.version", def); if ( javaVersionProperty == "0" ) return "1.7.0"; // Android return javaVersionProperty; } static boolean javaVersion7(final boolean atLeast) { final int gt = "1.7".compareTo( javaVersion("0.0").substring(0, 3) ); return atLeast ? gt <= 0 : gt == 0; } static boolean javaVersion8(final boolean atLeast) { final int gt = "1.8".compareTo( javaVersion("0.0").substring(0, 3) ); return atLeast ? gt <= 0 : gt == 0; } static boolean javaVersion9(final boolean atLeast) { final int gt = "9".compareTo( javaVersion("0").substring(0, 1) ); return atLeast ? gt <= 0 : gt == 0; } private static String javaName(final String def) { // Sun Java 6 or Oracle Java 7/8 // "Java HotSpot(TM) Server VM" or "Java HotSpot(TM) 64-Bit Server VM" // OpenJDK : // "OpenJDK 64-Bit Server VM" return SafePropertyAccessor.getProperty("java.vm.name", def); } public static boolean javaHotSpot() { return javaName("").contains("HotSpot(TM)"); } public static boolean javaOpenJDK() { return javaName("").contains("OpenJDK"); } // shared secure-random : private static boolean tryContextSecureRandom = true; static SecureRandom getSecureRandom(final Ruby runtime) { return getSecureRandom(runtime, false); } static SecureRandom getSecureRandom(final Ruby runtime, final boolean nullByDefault) { if ( tryContextSecureRandom ) { SecureRandom random = getSecureRandomFrom(runtime.getCurrentContext()); if ( random != null ) return random; } return nullByDefault ? null : new SecureRandom(); } static SecureRandom getSecureRandomFrom(final ThreadContext context) { if ( tryContextSecureRandom ) { try { SecureRandom random = context.secureRandom; if (random == null) { // public SecureRandom getSecureRandom() on 9K random = (SecureRandom) context.getClass().getMethod("getSecureRandom").invoke(context); } return random; } catch (Throwable ex) { tryContextSecureRandom = false; debug(context.runtime, "JRuby-OpenSSL failed to retrieve secure random from thread-context", ex); } } return null; } // internals static IRubyObject to_der_if_possible(final ThreadContext context, IRubyObject obj) { if ( ! obj.respondsTo("to_der")) return obj; return obj.callMethod(context, "to_der"); } // static String bcExceptionMessage(NoSuchProviderException ex) { return "You need to configure JVM/classpath to enable BouncyCastle Security Provider: " + ex; } static String bcExceptionMessage(NoClassDefFoundError ex) { return "You need to configure JVM/classpath to enable BouncyCastle Security Provider: " + ex; } }