/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.Visibility;
import org.jruby.ext.openssl.x509store.PEMInputOutput;
import static org.jruby.ext.openssl.OpenSSL.*;
import org.jruby.ext.openssl.impl.CipherSpec;
import org.jruby.util.ByteList;
/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
*/
public abstract class PKey extends RubyObject {
private static final long serialVersionUID = 6114668087816965720L;
public static void createPKey(final Ruby runtime, final RubyModule OpenSSL) {
final RubyModule PKey = OpenSSL.defineModuleUnder("PKey");
PKey.defineAnnotatedMethods(PKeyModule.class);
// PKey is abstract
RubyClass PKeyPKey = PKey.defineClassUnder("PKey", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
RubyClass OpenSSLError = OpenSSL.getClass("OpenSSLError");
PKey.defineClassUnder("PKeyError", OpenSSLError, OpenSSLError.getAllocator());
PKeyPKey.defineAnnotatedMethods(PKey.class);
PKeyRSA.createPKeyRSA(runtime, PKey, PKeyPKey);
PKeyDSA.createPKeyDSA(runtime, PKey, PKeyPKey);
PKeyDH.createPKeyDH(runtime, PKey, PKeyPKey);
PKeyEC.createPKeyEC(runtime, PKey, PKeyPKey);
}
public static RaiseException newPKeyError(Ruby runtime, String message) {
return Utils.newError(runtime, _PKey(runtime).getClass("PKeyError"), message);
}
static RubyModule _PKey(final Ruby runtime) {
return (RubyModule) runtime.getModule("OpenSSL").getConstantAt("PKey");
}
public static class PKeyModule {
@JRubyMethod(name = "read", meta = true, required = 1, optional = 1)
public static IRubyObject read(final ThreadContext context, IRubyObject recv, IRubyObject[] args) {
final Ruby runtime = context.runtime;
final IRubyObject data; final char[] pass;
switch (args.length) {
case 1:
data = args[0];
pass = null;
break;
default:
data = args[0];
pass = args[1].isNil() ? null : args[1].toString().toCharArray();
}
final byte[] input = StringHelper.readX509PEM(context, data);
KeyPair key = null;
// d2i_PrivateKey_bio
try {
key =org.jruby.ext.openssl.impl.PKey.readPrivateKey(input);
} catch (IOException ioe) {
// ignore
} catch (GeneralSecurityException gse) {
// ignore
}
// PEM_read_bio_PrivateKey
if (key == null) {
try {
key = PEMInputOutput.readPrivateKey(new InputStreamReader(new ByteArrayInputStream(input)), pass);
} catch (IOException ioe) {
// ignore
}
}
if (key != null) {
final String alg = getAlgorithm(key);
if ( "RSA".equals(alg) ) {
return new PKeyRSA(runtime, _PKey(runtime).getClass("RSA"),
(RSAPrivateCrtKey) key.getPrivate(), (RSAPublicKey) key.getPublic()
);
}
if ( "DSA".equals(alg) ) {
return new PKeyDSA(runtime, _PKey(runtime).getClass("DSA"),
(DSAPrivateKey) key.getPrivate(), (DSAPublicKey) key.getPublic()
);
}
if ( "ECDSA".equals(alg) ) {
return new PKeyEC(runtime, _PKey(runtime).getClass("EC"),
(PrivateKey) key.getPrivate(), (PublicKey) key.getPublic()
);
}
}
PublicKey pubKey = null;
// d2i_PUBKEY_bio
try {
pubKey = org.jruby.ext.openssl.impl.PKey.readPublicKey(input);
} catch (IOException ioe) {
// ignore
} catch (GeneralSecurityException gse) {
// ignore
}
// PEM_read_bio_PUBKEY
if (pubKey == null) {
try {
pubKey = PEMInputOutput.readPubKey(new InputStreamReader(new ByteArrayInputStream(input)));
} catch (IOException ioe) {
// ignore
}
}
if (pubKey != null) {
if ( "RSA".equals(pubKey.getAlgorithm()) ) {
return new PKeyRSA(runtime, (RSAPublicKey) pubKey);
}
if ( "DSA".equals(pubKey.getAlgorithm()) ) {
return new PKeyDSA(runtime, (DSAPublicKey) pubKey);
}
if ( "ECDSA".equals(pubKey.getAlgorithm()) ) {
return new PKeyEC(runtime, pubKey);
}
}
throw runtime.newArgumentError("Could not parse PKey");
}
private static String getAlgorithm(final KeyPair key) {
if ( key.getPrivate() != null ) return key.getPrivate().getAlgorithm();
if ( key.getPublic() != null ) return key.getPublic().getAlgorithm();
return null;
}
}
public PKey(Ruby runtime, RubyClass type) {
super(runtime,type);
}
@Override
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize(ThreadContext context) {
return this;
}
public abstract PublicKey getPublicKey() ;
public abstract PrivateKey getPrivateKey() ;
public String getAlgorithm() { return "NONE"; }
public boolean isPrivateKey() { return getPrivateKey() != null; }
public abstract RubyString to_der() ;
public abstract RubyString to_pem(final IRubyObject[] args) ;
@Deprecated
public RubyString export(final IRubyObject[] args) {
return to_pem(args);
}
@JRubyMethod(name = "sign")
public IRubyObject sign(IRubyObject digest, IRubyObject data) {
final Ruby runtime = getRuntime();
if ( ! isPrivateKey() ) throw runtime.newArgumentError("Private key is needed.");
String digAlg = (digest instanceof Digest) ? ((Digest) digest).getShortAlgorithm() : digest.asJavaString();
try {
ByteList sign = sign(digAlg + "WITH" + getAlgorithm(), getPrivateKey(), data.convertToString().getByteList());
return RubyString.newString(runtime, sign);
}
catch (GeneralSecurityException ex) {
throw newPKeyError(runtime, ex.getMessage());
}
}
static ByteList sign(final String signAlg, final PrivateKey privateKey, final ByteList data)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = SecurityHelper.getSignature(signAlg);
signature.initSign( privateKey );
signature.update( data.getUnsafeBytes(), data.getBegin(), data.getRealSize() );
return new ByteList(signature.sign(), false);
}
@JRubyMethod(name = "verify")
public IRubyObject verify(IRubyObject digest, IRubyObject sign, IRubyObject data) {
final Ruby runtime = getRuntime();
ByteList sigBytes = convertToString(runtime, sign, "OpenSSL::PKey::PKeyError", "invalid signature").getByteList();
ByteList dataBytes = convertToString(runtime, data, "OpenSSL::PKey::PKeyError", "invalid data").getByteList();
String digAlg = (digest instanceof Digest) ? ((Digest) digest).getShortAlgorithm() : digest.asJavaString();
final String algorithm = digAlg + "WITH" + getAlgorithm();
try {
return runtime.newBoolean( verify(algorithm, getPublicKey(), dataBytes, sigBytes) );
}
catch (NoSuchAlgorithmException e) {
throw newPKeyError(runtime, "unsupported algorithm: " + algorithm);
}
catch (SignatureException e) {
throw newPKeyError(runtime, "invalid signature");
}
catch (InvalidKeyException e) {
throw newPKeyError(runtime, "invalid key");
}
}
static RubyString convertToString(final Ruby runtime, final IRubyObject str, final String errorType, final CharSequence errorMsg) {
try {
return str.convertToString();
}
catch (RaiseException ex) { // to_str conversion failed
throw Utils.newError(runtime, (RubyClass) runtime.getClassFromPath(errorType), errorMsg == null ? null : errorMsg.toString());
}
}
static boolean verify(final String signAlg, final PublicKey publicKey, final ByteList data, final ByteList sign)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = SecurityHelper.getSignature(signAlg);
signature.initVerify(publicKey);
signature.update(data.getUnsafeBytes(), data.getBegin(), data.getRealSize());
return signature.verify(sign.getUnsafeBytes(), sign.getBegin(), sign.getRealSize());
}
static SecureRandom getSecureRandom(final Ruby runtime) {
return OpenSSL.getSecureRandom(runtime);
}
// shared Helpers for PKeyRSA / PKEyDSA :
protected PrivateKey tryPKCS8EncodedKey(final Ruby runtime, final KeyFactory keyFactory, final byte[] encodedKey) {
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
catch (InvalidKeySpecException e) {
if ( isDebug(runtime) ) {
debug(runtime, getClass().getSimpleName() + " could not generate (PKCS8) private key", e);
}
}
catch (RuntimeException e) {
if ( isKeyGenerationFailure(e) ) {
if( isDebug(runtime) ) {
debug(runtime, getClass().getSimpleName() + " could not generate (PKCS8) private key", e);
}
}
else debugStackTrace(runtime, e);
}
return null;
}
protected static boolean isKeyGenerationFailure(final RuntimeException e) {
// NOTE handle "common-failure" more gently (no need for stack trace) :
// java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer
// at org.bouncycastle.asn1.pkcs.PrivateKeyInfo.<init>(Unknown Source)
// at org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(Unknown Source)
// at org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi.engineGeneratePrivate(Unknown Source)
// at org.bouncycastle.jcajce.provider.asymmetric.dsa.KeyFactorySpi.engineGeneratePrivate(Unknown Source)
// at java.security.KeyFactory.generatePrivate(KeyFactory.java:366)
if ( e instanceof ClassCastException ) {
// RSA :
final String msg = e.getMessage();
if ( msg != null && msg.contains("DLSequence cannot be cast to") ) {
return true;
}
}
return false;
}
protected PublicKey tryX509EncodedKey(final Ruby runtime, final KeyFactory keyFactory, final byte[] encodedKey) {
try {
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
catch (InvalidKeySpecException e) {
if ( isDebug(runtime) ) {
debug(runtime, getClass().getSimpleName() + " could not generate (X509) public key", e);
}
}
catch (RuntimeException e) {
if ( isKeyGenerationFailure(e) ) { // NOTE: not (yet) detected with X.509
if( isDebug(runtime) ) {
debug(runtime, getClass().getSimpleName() + " could not generate (X509) public key", e);
}
}
else debugStackTrace(runtime, e);
}
return null;
}
protected static void addSplittedAndFormatted(StringBuilder result, BigInteger value) {
String v = value.toString(16);
if ((v.length() % 2) != 0) {
v = "0" + v;
}
String sep = "";
for (int i = 0; i < v.length(); i += 2) {
result.append(sep);
if ((i % 30) == 0) {
result.append("\n ");
}
result.append(v.substring(i, i + 2));
sep = ":";
}
result.append("\n");
}
protected static CipherSpec cipherSpec(final IRubyObject cipher) {
if ( cipher != null && ! cipher.isNil() ) {
final Cipher c = (Cipher) cipher;
return new CipherSpec(c.getCipherInstance(), c.getName(), c.getKeyLength() * 8);
}
return null;
}
protected static char[] password(final IRubyObject pass) {
if ( pass != null && ! pass.isNil() ) {
return pass.toString().toCharArray();
}
return null;
}
protected static char[] passwordPrompt(final ThreadContext context) {
return passwordPrompt(context, "Enter PEM pass phrase:");
}
protected static char[] passwordPrompt(final ThreadContext context, final String prompt) {
final RubyModule Kernel = context.runtime.getKernel();
// NOTE: just a fast and simple print && gets - hopefully better than nothing!
Kernel.callMethod("print", context.runtime.newString(prompt));
final RubyString gets = Kernel.callMethod(context, "gets").convertToString();
gets.chomp_bang(context);
return gets.toString().toCharArray();
}
protected static boolean ttySTDIN(final ThreadContext context) {
final IRubyObject stdin = context.runtime.getGlobalVariables().get("$stdin");
if ( stdin == null || stdin.isNil() ) return false;
try {
final IRubyObject tty = stdin.callMethod(context, "tty?");
return ! tty.isNil() && ! ( tty == context.runtime.getFalse() );
}
catch (RaiseException ex) { return false; }
}
static Object readPrivateKey(final String str, final char[] passwd)
throws PEMInputOutput.PasswordRequiredException, IOException {
return PEMInputOutput.readPrivateKey(new StringReader(str), passwd);
}
static Object readPrivateKey(final RubyString str, final char[] passwd)
throws PEMInputOutput.PasswordRequiredException, IOException {
return readPrivateKey(str.toString(), passwd);
}
protected static RubyString readInitArg(final ThreadContext context, IRubyObject arg) {
return StringHelper.readPossibleDERInput(context, arg);
}
static void supportedSignatureAlgorithm(final Ruby runtime, final RubyClass errorClass,
final PKey key, final Digest digest) {
// Have to obey some artificial constraints of the OpenSSL implementation. Stupid.
final String keyAlg = key.getAlgorithm();
final String digAlg = digest.getShortAlgorithm();
if ( ( "DSA".equalsIgnoreCase(keyAlg) && "MD5".equalsIgnoreCase(digAlg)) ||
( "RSA".equalsIgnoreCase(keyAlg) && "DSS1".equals( digest.name().toString() ) ) ) {
throw Utils.newError(runtime, errorClass, "unsupported key / digest algorithm ( "+ keyAlg +" / "+ digAlg +" )");
}
}
static void supportedSignatureAlgorithm(final Ruby runtime, final PKey key, final Digest digest) {
supportedSignatureAlgorithm(runtime, _OpenSSLError(runtime), key, digest);
}
}// PKey