/***** 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.util.ArrayList; import java.util.List; import java.io.IOException; import java.io.StringWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.PublicKey; import java.security.PrivateKey; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.pkcs.Attribute; import org.bouncycastle.asn1.x500.X500Name; import org.jruby.Ruby; import org.jruby.RubyArray; 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.ext.openssl.x509store.PEMInputOutput; import org.jruby.runtime.Arity; 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.impl.PKCS10Request; import static org.jruby.ext.openssl.OpenSSL.*; import static org.jruby.ext.openssl.PKey._PKey; import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.PKey.supportedSignatureAlgorithm; /** * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a> */ public class X509Request extends RubyObject { private static final long serialVersionUID = -2886532636278901502L; private static ObjectAllocator REQUEST_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new X509Request(runtime, klass); } }; public static void createRequest(final Ruby runtime, final RubyModule _X509) { RubyClass _Request = _X509.defineClassUnder("Request", runtime.getObject(), REQUEST_ALLOCATOR); RubyClass _OpenSSLError = runtime.getModule("OpenSSL").getClass("OpenSSLError"); _X509.defineClassUnder("RequestError", _OpenSSLError, _OpenSSLError.getAllocator()); _Request.defineAnnotatedMethods(X509Request.class); } static RubyClass _RequestError(final Ruby runtime) { return (RubyClass) _X509(runtime).getConstantAt("RequestError"); } private IRubyObject subject; private PKey public_key; private IRubyObject version; private final List<X509Attribute> attributes; private transient PKCS10Request request; public X509Request(Ruby runtime, RubyClass type) { super(runtime, type); attributes = new ArrayList<X509Attribute>(4); } @JRubyMethod(name = "initialize", 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, 1) == 0 ) return this; try { request = new PKCS10Request( StringHelper.readX509PEM(context, args[0]) ); } catch (RuntimeException e) { debugStackTrace(runtime, e); throw newRequestError(runtime, "invalid certificate request data", e); } final String algorithm; final byte[] encoded; try { final PublicKey pkey = request.generatePublicKey(); algorithm = pkey.getAlgorithm(); encoded = pkey.getEncoded(); } catch (IOException e) { throw newRequestError(runtime, e); } catch (GeneralSecurityException e) { throw newRequestError(runtime, e); } final RubyString enc = RubyString.newString(runtime, encoded); if ( "RSA".equalsIgnoreCase(algorithm) ) { this.public_key = newPKeyImplInstance(context, "RSA", enc); } else if ( "DSA".equalsIgnoreCase(algorithm) ) { this.public_key = newPKeyImplInstance(context, "DSA", enc); } else { throw runtime.newNotImplementedError("public key algorithm: " + algorithm); } this.subject = newName( context, request.getSubject() ); final Attribute[] attrs = request.getAttributes(); try { // final RubyModule _ASN1 = _ASN1(runtime); if ( attrs != null ) { for ( final Attribute attr : attrs ) { final ASN1ObjectIdentifier type = attr.getAttrType(); final ASN1Set values = attr.getAttrValues(); attributes.add( newAttribute( context, type, values ) ); } } } catch (IOException e) { throw newRequestError(runtime, e); } return this; } private static PKey newPKeyImplInstance(final ThreadContext context, final String className, final RubyString encoded) { // OpenSSL::PKey::RSA.new(encoded) return (PKey) _PKey(context.runtime).getClass(className).callMethod(context, "new", encoded); } private static X509Attribute newAttribute(final ThreadContext context, final ASN1ObjectIdentifier type, final ASN1Set values) throws IOException { return X509Attribute.newAttribute(context.runtime, type, values); } private static IRubyObject newName(final ThreadContext context, X500Name name) { if ( name == null ) return context.nil; return X509Name.newName(context.runtime, name); } @Override @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject initialize_copy(IRubyObject obj) { final Ruby runtime = getRuntime(); warn(runtime.getCurrentContext(), "WARNING: unimplemented method called: request#initialize_copy"); if ( this == obj ) return this; checkFrozen(); // subject = public_key = null; return this; } private PKCS10Request getRequest() { if ( request != null ) return request; PublicKey publicKey = null; if ( public_key != null && ! public_key.isNil() ) { publicKey = public_key.getPublicKey(); } X500Name subjectName = subject != null ? getX500Name(subject) : null; final ThreadContext context = getRuntime().getCurrentContext(); return request = new PKCS10Request( subjectName, publicKey, newAttributesImpl(context) ); } private static X500Name getX500Name(final IRubyObject name) { if ( name.isNil() ) return null; return ((X509Name) name).getX500Name(); } @JRubyMethod(name = { "to_pem", "to_s" }) public RubyString to_pem() { StringWriter writer = new StringWriter(); try { PEMInputOutput.writeX509Request(writer, getRequest()); return getRuntime().newString( writer.toString() ); } catch (IOException e) { throw Utils.newIOError(getRuntime(), e); } } @JRubyMethod public RubyString to_der() { final ASN1Sequence seq = getRequest().toASN1Structure(); try { return StringHelper.newString(getRuntime(), seq.getEncoded(ASN1Encoding.DER)); } catch (IOException ex) { throw getRuntime().newIOErrorFromException(ex); } } @JRubyMethod public IRubyObject to_text(final ThreadContext context) { warn(context, "WARNING: unimplemented method called: X509::Request#to_text"); return context.runtime.getNil(); } @JRubyMethod public IRubyObject version() { final PKCS10Request request = getRequest(); // (false) if ( request != null ) { BigInteger certVersion = request.getVersion(); if ( certVersion != null ) { return getRuntime().newFixnum( certVersion.intValue() ); } } return version == null ? getRuntime().newFixnum(0) : version; } @JRubyMethod(name="version=") public IRubyObject set_version(final ThreadContext context, IRubyObject version) { warn(context, "X509::Request#version= has no effect on certification request"); return this.version = version; } @JRubyMethod public IRubyObject subject() { return subject == null ? subject = getRuntime().getNil() : subject; } @JRubyMethod(name="subject=") public IRubyObject set_subject(final IRubyObject val) { if ( val != this.subject ) { if (request != null) { request.setSubject( getX500Name(val) ); } this.subject = val; } return val; } @JRubyMethod public IRubyObject signature_algorithm(final ThreadContext context) { warn(context, "WARNING: unimplemented method called: request#signature_algorithm"); return context.runtime.getNil(); } @JRubyMethod public IRubyObject public_key() { return public_key == null ? getRuntime().getNil() : public_key; } @JRubyMethod(name="public_key=") public IRubyObject set_public_key(final IRubyObject pkey) { if ( pkey != this.public_key ) { if (request != null) { request.setPublicKey( ((PKey) pkey).getPublicKey() ); } this.public_key = (PKey) pkey; } return pkey; } @JRubyMethod public IRubyObject sign(final ThreadContext context, final IRubyObject key, final IRubyObject digest) { // PublicKey publicKey = public_key.getPublicKey(); PrivateKey privateKey = ((PKey) key).getPrivateKey(); final Ruby runtime = context.runtime; supportedSignatureAlgorithm(runtime, _RequestError(runtime), public_key, (Digest) digest); final String digAlg = ((Digest) digest).getShortAlgorithm(); try { request = null; getRequest().sign( privateKey, digAlg ); } catch (GeneralSecurityException e) { debugStackTrace(runtime, e); throw newRequestError(runtime, e); } //catch (IOException e) { // debugStackTrace(runtime, e); // throw newRequestError(runtime, e); //} return this; } private List<Attribute> newAttributesImpl(final ThreadContext context) { ArrayList<Attribute> attrs = new ArrayList<Attribute>(attributes.size()); for ( X509Attribute attribute : attributes ) { attrs.add( newAttributeImpl(context, attribute) ); } return attrs; } private Attribute newAttributeImpl(final ThreadContext context, final X509Attribute attribute) { return Attribute.getInstance( attribute.toASN1( context ) ); } @JRubyMethod public IRubyObject verify(final ThreadContext context, IRubyObject key) { final Ruby runtime = context.runtime; final PublicKey publicKey; try { publicKey = ( (PKey) key.callMethod(context, "public_key") ).getPublicKey(); return runtime.newBoolean( getRequest().verify(publicKey) ); } catch (InvalidKeyException e) { debug(runtime, "X509::Request.verify invalid key", e); throw newRequestError(runtime, "invalid key supplied", e); } //catch (IOException e) { // debug(runtime, "X509::Request.verify failed", e); // return runtime.getFalse(); //} //catch (RuntimeException e) { // debug(runtime, "X509::Request.verify failed", e); // return runtime.getFalse(); //} } @JRubyMethod public IRubyObject attributes() { @SuppressWarnings("unchecked") List<IRubyObject> attributes = (List) this.attributes; return getRuntime().newArray(attributes); } @JRubyMethod(name="attributes=") public IRubyObject set_attributes(final ThreadContext context,final IRubyObject attributes) { this.attributes.clear(); final RubyArray attrs = (RubyArray) attributes; for ( int i = 0; i < attrs.size(); i++ ) { this.attributes.add( (X509Attribute) attrs.entry(i) ); } if (request != null) { request.setAttributes( newAttributesImpl(context) ); } return attributes; } @JRubyMethod public IRubyObject add_attribute(final ThreadContext context,final IRubyObject attribute) { attributes.add( (X509Attribute) attribute ); if (request != null) { request.addAttribute( newAttributeImpl( context, (X509Attribute) attribute ) ); } return attribute; } private static RaiseException newRequestError(Ruby runtime, Exception e) { return Utils.newError(runtime, _X509(runtime).getClass("RequestError"), e); } private static RaiseException newRequestError(Ruby runtime, String message) { return Utils.newError(runtime, _X509(runtime).getClass("RequestError"), message); } private static RaiseException newRequestError(Ruby runtime, String message, Exception cause) { return Utils.newError(runtime, _X509(runtime).getClass("RequestError"), message, cause); } }// X509Request