/*
* 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;
}
}