/***** 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>
* Copyright (C) 2007 Wiliam N Dortch <bill.dortch@gmail.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.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.DSAKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import org.jruby.Ruby;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.openssl.impl.CipherSpec;
import org.jruby.ext.openssl.x509store.PEMInputOutput;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.util.ByteList;
import static org.jruby.ext.openssl.OpenSSL.*;
import static org.jruby.ext.openssl.impl.PKey.readDSAPrivateKey;
import static org.jruby.ext.openssl.impl.PKey.readDSAPublicKey;
import static org.jruby.ext.openssl.impl.PKey.toDerDSAKey;
import static org.jruby.ext.openssl.PKey._PKey;
/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
*/
public class PKeyDSA extends PKey {
private static final long serialVersionUID = 6351851846414049890L;
private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public PKeyDSA allocate(Ruby runtime, RubyClass klass) { return new PKeyDSA(runtime, klass); }
};
public static void createPKeyDSA(final Ruby runtime, final RubyModule PKey, final RubyClass PKeyPKey) {
RubyClass DSA = PKey.defineClassUnder("DSA", PKeyPKey, ALLOCATOR);
RubyClass PKeyError = PKey.getClass("PKeyError");
PKey.defineClassUnder("DSAError", PKeyError, PKeyError.getAllocator());
DSA.defineAnnotatedMethods(PKeyDSA.class);
}
static RubyClass _DSA(final Ruby runtime) {
return _PKey(runtime).getClass("DSA");
}
public PKeyDSA(Ruby runtime, RubyClass type) {
super(runtime, type);
}
public PKeyDSA(Ruby runtime, RubyClass type, DSAPrivateKey privKey, DSAPublicKey pubKey) {
super(runtime, type);
this.privateKey = privKey;
this.publicKey = pubKey;
}
PKeyDSA(Ruby runtime, DSAPublicKey pubKey) {
this(runtime, _DSA(runtime), null, pubKey);
}
private volatile DSAPublicKey publicKey;
private volatile transient DSAPrivateKey privateKey;
// specValues holds individual DSAPublicKeySpec components. this allows
// a public key to be constructed incrementally, as required by the
// current implementation of Net::SSH.
// (see net-ssh-1.1.2/lib/net/ssh/transport/ossl/buffer.rb #read_keyblob)
private transient volatile BigInteger dsa_x;
private transient volatile BigInteger dsa_y;
private transient volatile BigInteger dsa_p;
private transient volatile BigInteger dsa_q;
private transient volatile BigInteger dsa_g;
@Override
public IRubyObject initialize_copy(final IRubyObject original) {
if (this == original) return this;
checkFrozen();
final PKeyDSA that = (PKeyDSA) original;
this.publicKey = that.publicKey;
this.privateKey = that.privateKey;
this.dsa_x = that.dsa_x;
this.dsa_y = that.dsa_y;
this.dsa_p = that.dsa_p;
this.dsa_q = that.dsa_q;
this.dsa_g = that.dsa_g;
return this;
}
@Override
public PublicKey getPublicKey() { return publicKey; }
@Override
public PrivateKey getPrivateKey() { return privateKey; }
@Override
public String getAlgorithm() { return "DSA"; }
@JRubyMethod(name = "generate", meta = true)
public static IRubyObject generate(IRubyObject self, IRubyObject arg) {
final Ruby runtime = self.getRuntime();
final int keySize = RubyNumeric.fix2int(arg);
return dsaGenerate(runtime, new PKeyDSA(runtime, (RubyClass) self), keySize);
}
/*
* c: dsa_generate
*/
private static PKeyDSA dsaGenerate(final Ruby runtime,
PKeyDSA dsa, int keySize) throws RaiseException {
try {
KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("DSA");
gen.initialize(keySize, getSecureRandom(runtime));
KeyPair pair = gen.generateKeyPair();
dsa.privateKey = (DSAPrivateKey) pair.getPrivate();
dsa.publicKey = (DSAPublicKey) pair.getPublic();
return dsa;
}
catch (NoSuchAlgorithmException e) {
throw newDSAError(runtime, e.getMessage());
}
catch (RuntimeException e) {
throw newDSAError(runtime, e.getMessage(), e);
}
}
static PKeyDSA newInstance(final Ruby runtime, final PublicKey publicKey) {
//if ( publicKey instanceof DSAPublicKey ) {
return new PKeyDSA(runtime, (DSAPublicKey) publicKey);
//}
}
@JRubyMethod(rest = true, visibility = Visibility.PRIVATE)
public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) {
final Ruby runtime = context.runtime;
if ( Arity.checkArgumentCount(runtime, args, 0, 2) == 0 ) {
this.privateKey = null; this.publicKey = null; return this;
}
IRubyObject arg = args[0]; IRubyObject pass = null;
if ( args.length > 1 ) pass = args[1];
if ( arg instanceof RubyFixnum ) {
int keySize = RubyNumeric.fix2int((RubyFixnum) arg);
return dsaGenerate(context.runtime, this, keySize);
}
final char[] passwd = password(pass);
final RubyString str = readInitArg(context, arg);
final String strJava = str.toString();
Object key = null;
final KeyFactory dsaFactory;
try {
dsaFactory = SecurityHelper.getKeyFactory("DSA");
}
catch (NoSuchAlgorithmException e) {
throw runtime.newRuntimeError("unsupported key algorithm (DSA)");
}
catch (RuntimeException e) {
throw runtime.newRuntimeError("unsupported key algorithm (DSA) " + e);
}
// TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this?
boolean noClassDef = false;
if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPrivateKey
try {
key = readPrivateKey(strJava, passwd);
}
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
catch (PEMInputOutput.PasswordRequiredException retry) {
if ( ttySTDIN(context) ) {
try { key = readPrivateKey(strJava, passwordPrompt(context)); }
catch (Exception e) { debugStackTrace(runtime, e); }
}
}
catch (Exception e) { debugStackTrace(runtime, e); }
}
if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPublicKey
try {
key = PEMInputOutput.readDSAPublicKey(new StringReader(strJava), passwd);
}
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
catch (Exception e) { debugStackTrace(runtime, e); }
}
if ( key == null && ! noClassDef ) { // PEM_read_bio_DSA_PUBKEY
try {
key = PEMInputOutput.readDSAPubKey(new StringReader(strJava));
}
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
catch (Exception e) { debugStackTrace(runtime, e); }
}
if ( key == null && ! noClassDef ) { // d2i_DSAPrivateKey_bio
try {
key = readDSAPrivateKey(dsaFactory, str.getBytes());
}
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
catch (InvalidKeySpecException e) { debug(runtime, "PKeyDSA could not read private key", e); }
catch (IOException e) { debug(runtime, "PKeyDSA could not read private key", e); }
catch (RuntimeException e) {
if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyDSA could not read private key", e);
else debugStackTrace(runtime, e);
}
}
if ( key == null && ! noClassDef ) { // d2i_DSA_PUBKEY_bio
try {
key = readDSAPublicKey(dsaFactory, str.getBytes());
}
catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); }
catch (InvalidKeySpecException e) { debug(runtime, "PKeyDSA could not read public key", e); }
catch (IOException e) { debug(runtime, "PKeyDSA could not read public key", e); }
catch (RuntimeException e) {
if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyDSA could not read public key", e);
else debugStackTrace(runtime, e);
}
}
if ( key == null ) key = tryPKCS8EncodedKey(runtime, dsaFactory, str.getBytes());
if ( key == null ) key = tryX509EncodedKey(runtime, dsaFactory, str.getBytes());
if ( key == null ) throw newDSAError(runtime, "Neither PUB key nor PRIV key:");
if ( key instanceof KeyPair ) {
final PublicKey pubKey = ((KeyPair) key).getPublic();
final PrivateKey privKey = ((KeyPair) key).getPrivate();
if ( ! ( privKey instanceof DSAPrivateKey ) ) {
if ( privKey == null ) {
throw newDSAError(runtime, "Neither PUB key nor PRIV key: (private key is null)");
}
throw newDSAError(runtime, "Neither PUB key nor PRIV key: (invalid key type " + privKey.getClass().getName() + ")");
}
this.privateKey = (DSAPrivateKey) privKey;
this.publicKey = (DSAPublicKey) pubKey;
}
else if ( key instanceof DSAPrivateKey ) {
this.privateKey = (DSAPrivateKey) key;
}
else if ( key instanceof DSAPublicKey ) {
this.publicKey = (DSAPublicKey) key; this.privateKey = null;
}
else {
throw newDSAError(runtime, "Neither PUB key nor PRIV key: " + key.getClass().getName());
}
return this;
}
//private static Object readPrivateKey(final RubyString str, final char[] passwd)
// throws PEMInputOutput.PasswordRequiredException, IOException {
// return PEMInputOutput.readDSAPrivateKey(new StringReader(str.toString()), passwd);
//}
@JRubyMethod(name = "public?")
public RubyBoolean public_p() {
return publicKey != null ? getRuntime().getTrue() : getRuntime().getFalse();
}
@JRubyMethod(name = "private?")
public RubyBoolean private_p() {
return privateKey != null ? getRuntime().getTrue() : getRuntime().getFalse();
}
@Override
@JRubyMethod(name = "to_der")
public RubyString to_der() {
final byte[] bytes;
try {
bytes = toDerDSAKey(publicKey, privateKey);
}
catch (NoClassDefFoundError e) {
throw newDSAError(getRuntime(), bcExceptionMessage(e));
}
catch (IOException e) {
throw newDSAError(getRuntime(), e.getMessage(), e);
}
return StringHelper.newString(getRuntime(), bytes);
}
@JRubyMethod
public RubyString to_text() {
StringBuilder result = new StringBuilder();
if (privateKey != null) {
int len = privateKey.getParams().getP().bitLength();
result.append("Private-Key: (").append(len).append(" bit)").append("\n");
result.append("priv:");
addSplittedAndFormatted(result, privateKey.getX());
}
result.append("pub:");
addSplittedAndFormatted(result, publicKey.getY());
result.append("P:");
addSplittedAndFormatted(result, publicKey.getParams().getP());
result.append("Q:");
addSplittedAndFormatted(result, publicKey.getParams().getQ());
result.append("G:");
addSplittedAndFormatted(result, publicKey.getParams().getG());
return RubyString.newString(getRuntime(), result);
}
@JRubyMethod
public PKeyDSA public_key() {
return new PKeyDSA(getRuntime(), this.publicKey);
}
@Override
@JRubyMethod(name = { "to_pem", "to_s" }, alias = "export", rest = true)
public RubyString to_pem(final IRubyObject[] args) {
Arity.checkArgumentCount(getRuntime(), args, 0, 2);
CipherSpec spec = null; char[] passwd = null;
if ( args.length > 0 ) {
spec = cipherSpec( args[0] );
if ( args.length > 1 ) passwd = password(args[1]);
}
try {
final StringWriter writer = new StringWriter();
if ( privateKey != null ) {
PEMInputOutput.writeDSAPrivateKey(writer, privateKey, spec, passwd);
}
else {
PEMInputOutput.writeDSAPublicKey(writer, publicKey);
}
return RubyString.newString(getRuntime(), writer.getBuffer());
}
catch (NoClassDefFoundError ncdfe) {
throw newDSAError(getRuntime(), bcExceptionMessage(ncdfe));
}
catch (IOException e) {
throw newDSAError(getRuntime(), e.getMessage(), e);
}
}
@JRubyMethod // ossl_dsa_sign
public IRubyObject syssign(IRubyObject data) {
final Ruby runtime = getRuntime();
DSAPrivateKey privateKey;
if ((privateKey = this.privateKey) == null) {
throw newDSAError(runtime, "Private DSA key needed!");
}
try {
ByteList sign = sign("NONEwithDSA", privateKey, data.convertToString().getByteList()); // DSS1
return RubyString.newString(runtime, sign);
}
catch (GeneralSecurityException ex) {
throw newDSAError(runtime, ex.getMessage());
}
}
@JRubyMethod // ossl_dsa_verify
public IRubyObject sysverify(IRubyObject data, IRubyObject sign) {
final Ruby runtime = getRuntime();
ByteList sigBytes = convertToString(runtime, sign, "OpenSSL::PKey::DSAError", "invalid signature").getByteList();
ByteList dataBytes = convertToString(runtime, data, "OpenSSL::PKey::DSAError", "invalid data").getByteList();
try {
return runtime.newBoolean( verify("NONEwithDSA", getPublicKey(), dataBytes, sigBytes) );
}
catch (NoSuchAlgorithmException e) {
throw newDSAError(runtime, e.getMessage());
}
catch (SignatureException e) {
throw newDSAError(runtime, "invalid signature");
}
catch (InvalidKeyException e) {
throw newDSAError(runtime, "invalid key");
}
}
private DSAKey getDsaKey() {
DSAKey result;
return (result = publicKey) != null ? result : privateKey;
}
private IRubyObject toBN(BigInteger value) {
return value == null ? getRuntime().getNil() : BN.newBN(getRuntime(), value);
}
private synchronized BigInteger getP() {
DSAKey key = getDsaKey();
if (key != null) {
return key.getParams().getP();
}
return dsa_p;
}
@JRubyMethod(name = "p")
public IRubyObject get_p() {
return toBN(getP());
}
@JRubyMethod(name = "p=")
public synchronized IRubyObject set_p(IRubyObject p) {
return setKeySpecComponent(SPEC_P, p);
}
private synchronized BigInteger getQ() {
DSAKey key = getDsaKey();
if (key != null) {
return key.getParams().getQ();
}
return dsa_q;
}
@JRubyMethod(name = "q")
public IRubyObject get_q() {
return toBN(getQ());
}
@JRubyMethod(name = "q=")
public synchronized IRubyObject set_q(IRubyObject q) {
return setKeySpecComponent(SPEC_Q, q);
}
private synchronized BigInteger getG() {
DSAKey key = getDsaKey();
if (key != null) {
return key.getParams().getG();
}
return dsa_g;
}
@JRubyMethod(name = "g")
public IRubyObject get_g() {
return toBN(getG());
}
@JRubyMethod(name = "g=")
public synchronized IRubyObject set_g(IRubyObject g) {
return setKeySpecComponent(SPEC_G, g);
}
@JRubyMethod(name = "priv_key")
public synchronized IRubyObject get_priv_key() {
DSAPrivateKey key;
if ((key = this.privateKey) != null) {
return toBN(key.getX());
}
return toBN(dsa_x);
}
@JRubyMethod(name = "priv_key=")
public synchronized IRubyObject set_priv_key(IRubyObject priv_key) {
return setKeySpecComponent(SPEC_X, priv_key);
}
@JRubyMethod(name = "pub_key")
public synchronized IRubyObject get_pub_key() {
DSAPublicKey key;
if ( ( key = this.publicKey ) != null ) {
return toBN(key.getY());
}
return toBN(dsa_y);
}
@JRubyMethod(name = "pub_key=")
public synchronized IRubyObject set_pub_key(IRubyObject pub_key) {
return setKeySpecComponent(SPEC_Y, pub_key);
}
private IRubyObject setKeySpecComponent(final int index, final IRubyObject value) {
final BigInteger val = BN.getBigInteger(value);
switch (index) {
case SPEC_X: this.dsa_x = val; break;
case SPEC_Y: this.dsa_y = val; break;
case SPEC_P: this.dsa_p = val; break;
case SPEC_Q: this.dsa_q = val; break;
case SPEC_G: this.dsa_g = val; break;
}
// Don't access the dsa_p, dsa_q and dsa_g fields directly. They may
// have already been consumed and cleared.
BigInteger _dsa_p = getP();
BigInteger _dsa_q = getQ();
BigInteger _dsa_g = getG();
if ( dsa_x != null && _dsa_p != null && _dsa_q != null && _dsa_g != null ) {
// we now have all private key components. create the key :
DSAPrivateKeySpec spec = new DSAPrivateKeySpec(dsa_x, _dsa_p, _dsa_q, _dsa_g);
try {
this.privateKey = (DSAPrivateKey) SecurityHelper.getKeyFactory("DSA").generatePrivate(spec);
}
catch (InvalidKeySpecException e) {
throw newDSAError(getRuntime(), "invalid keyspec", e);
}
catch (NoSuchAlgorithmException e) {
throw newDSAError(getRuntime(), "unsupported key algorithm (DSA)", e);
}
// clear out the specValues
this.dsa_x = this.dsa_p = this.dsa_q = this.dsa_g = null;
}
if ( dsa_y != null && _dsa_p != null && _dsa_q != null && _dsa_g != null ) {
// we now have all public key components. create the key :
DSAPublicKeySpec spec = new DSAPublicKeySpec(dsa_y, _dsa_p, _dsa_q, _dsa_g);
try {
this.publicKey = (DSAPublicKey) SecurityHelper.getKeyFactory("DSA").generatePublic(spec);
}
catch (InvalidKeySpecException e) {
throw newDSAError(getRuntime(), "invalid keyspec", e);
}
catch (NoSuchAlgorithmException e) {
throw newDSAError(getRuntime(), "unsupported key algorithm (DSA)", e);
}
// clear out the specValues
this.dsa_y = this.dsa_p = this.dsa_q = this.dsa_g = null;
}
return value;
}
private static final int SPEC_X = 0;
private static final int SPEC_Y = 1;
private static final int SPEC_P = 2;
private static final int SPEC_Q = 3;
private static final int SPEC_G = 4;
public static RaiseException newDSAError(Ruby runtime, String message) {
return Utils.newError(runtime, _PKey(runtime).getClass("DSAError"), message);
}
static RaiseException newDSAError(Ruby runtime, String message, Exception cause) {
return Utils.newError(runtime, _PKey(runtime).getClass("DSAError"), message, cause);
}
}// PKeyDSA