/***** 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.security.cert.CRLException; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.List; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.RubyTime; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; 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.x509store.X509AuxCertificate; import org.jruby.ext.openssl.x509store.StoreContext; import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; import static org.jruby.ext.openssl.OpenSSL.warn; import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.X509CRL._CRL; import static org.jruby.ext.openssl.X509Cert._Certificate; import static org.jruby.ext.openssl.x509store.X509Utils.verifyCertificateErrorString; /** * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a> */ public class X509StoreContext extends RubyObject { private static final long serialVersionUID = -4165247923898746888L; private static ObjectAllocator X509STORECTX_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new X509StoreContext(runtime, klass); } }; public static void createX509StoreContext(final Ruby runtime, final RubyModule X509) { RubyClass StoreContext = X509.defineClassUnder("StoreContext", runtime.getObject(), X509STORECTX_ALLOCATOR); StoreContext.defineAnnotatedMethods(X509StoreContext.class); StoreContext.undefineMethod("dup"); } private static RubyClass _StoreContext(final Ruby runtime) { return _X509(runtime).getClass("StoreContext"); } private StoreContext storeContext; public X509StoreContext(Ruby runtime, RubyClass type) { super(runtime, type); } // constructor for creating callback parameter object of verify_cb private X509StoreContext(Ruby runtime, RubyClass type, StoreContext storeContext) { super(runtime, type); this.storeContext = storeContext; } static X509StoreContext newStoreContext(final Ruby runtime, final StoreContext storeContext) { return new X509StoreContext(runtime, _StoreContext(runtime), storeContext); } static X509StoreContext newStoreContext(final ThreadContext context, final X509Store store, final IRubyObject cert, final IRubyObject chain) { final Ruby runtime = context.runtime; X509StoreContext instance = new X509StoreContext(runtime, _StoreContext(runtime)); instance.initialize(context, new IRubyObject[] { store, cert, chain } ); return instance; } @JRubyMethod(name = "initialize", rest = true, visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { X509Store store; IRubyObject cert, chain; cert = chain = context.nil; store = (X509Store) args[0]; if ( Arity.checkArgumentCount(context.runtime, args, 1, 3) > 1 ) { cert = args[1]; if ( args.length > 2) chain = args[2]; } final X509AuxCertificate _cert; if (cert.isNil()) { _cert = null; } else { if ( ! (cert instanceof X509Cert) ) { throw context.runtime.newTypeError(cert, "OpenSSL::X509::Certificate"); } _cert = ((X509Cert) cert).getAuxCert(); } final List<X509AuxCertificate> _chain; if ( ! chain.isNil() ) { @SuppressWarnings("unchecked") final RubyArray certs = (RubyArray) chain; _chain = new ArrayList<X509AuxCertificate>( certs.size() ); for (int i = 0; i < certs.size(); i++) { // NOTE: if we use the normal java syntax for iterating over this // RubyArray, the `toJava` method of the X509Cert class will be // implicitly called, and that will return the BC certificate object // rather than the JRuby one. X509Cert c = (X509Cert) certs.eltOk(i); _chain.add(c.getAuxCert()); } } else { _chain = new ArrayList<X509AuxCertificate>(4); } this.storeContext = new StoreContext(store.getStore()); if ( storeContext.init(_cert, _chain) != 1 ) { throw newStoreError(context.runtime, null); } IRubyObject time = store.getInstanceVariables().getInstanceVariable("@time"); if ( ! time.isNil() ) set_time(time); this.setInstanceVariable("@verify_callback", store.verify_callback()); this.setInstanceVariable("@cert", cert); return this; } @JRubyMethod public IRubyObject verify(final ThreadContext context) { final Ruby runtime = context.runtime; storeContext.setExtraData(1, getInstanceVariable("@verify_callback")); try { final int result = storeContext.verifyCertificate(); return result != 0 ? runtime.getTrue() : runtime.getFalse(); } catch (Exception e) { debugStackTrace(runtime, e); // TODO: define suitable exception for jopenssl and catch it. throw newStoreError(runtime, e.getMessage()); } } @JRubyMethod public IRubyObject chain(final ThreadContext context) { final Ruby runtime = context.runtime; final List<X509AuxCertificate> chain = storeContext.getChain(); if ( chain == null ) return runtime.getNil(); final RubyArray result = runtime.newArray(chain.size()); final RubyClass _Certificate = _Certificate(runtime); try { for (X509AuxCertificate x509 : chain) { RubyString encoded = StringHelper.newString(runtime, x509.getEncoded()); result.append( _Certificate.callMethod( context, "new", encoded ) ); } } catch (CertificateEncodingException e) { throw newStoreError(runtime, e.getMessage()); } return result; } @JRubyMethod public IRubyObject error(final ThreadContext context) { return context.runtime.newFixnum( storeContext.getError() ); } @JRubyMethod(name="error=") public IRubyObject set_error(final IRubyObject error) { storeContext.setError( RubyNumeric.fix2int(error) ); return error; } @JRubyMethod public IRubyObject error_string(final ThreadContext context) { final int error = storeContext.getError(); return context.runtime.newString( verifyCertificateErrorString(error) ); } @JRubyMethod public IRubyObject error_depth(final ThreadContext context) { final int depth = storeContext.getErrorDepth(); return context.runtime.newFixnum( depth ); } @JRubyMethod public IRubyObject current_cert(final ThreadContext context) { final X509AuxCertificate x509 = storeContext.getCurrentCertificate(); try { return X509Cert.wrap(context, x509.getEncoded()); } catch (CertificateEncodingException e) { throw newStoreError(context.runtime, e.getMessage()); } } @JRubyMethod public IRubyObject current_crl(final ThreadContext context) { final Ruby runtime = context.runtime; final RubyClass _CRL = _CRL(runtime); try { final java.security.cert.X509CRL crl = storeContext.getCurrentCRL(); return _CRL.callMethod(context, "new", StringHelper.newString(runtime, crl.getEncoded())); } catch (CRLException e) { throw newStoreError(runtime, e.getMessage()); } } @JRubyMethod public IRubyObject cleanup(final ThreadContext context) { try { storeContext.cleanup(); } catch (RuntimeException e) { throw e; } catch (Exception e) { debugStackTrace(context.runtime, e); throw newStoreError(context.runtime, e.getMessage()); } return context.runtime.getNil(); } @JRubyMethod(name = "flags=") public IRubyObject set_flags(final ThreadContext context, final IRubyObject arg) { storeContext.setFlags(RubyFixnum.fix2long((RubyFixnum)arg)); return arg; } @JRubyMethod(name = "purpose=") public IRubyObject set_purpose(final ThreadContext context, final IRubyObject arg) { storeContext.setPurpose(RubyFixnum.fix2int((RubyFixnum)arg)); return arg; } @JRubyMethod(name = "trust=") public IRubyObject set_trust(final ThreadContext context, final IRubyObject arg) { storeContext.setTrust(RubyFixnum.fix2int((RubyFixnum)arg)); return arg; } @JRubyMethod(name = "time=") public IRubyObject set_time(IRubyObject arg) { storeContext.setTime( 0, ( (RubyTime) arg ).getJavaDate() ); return arg; } private static RaiseException newStoreError(Ruby runtime, String message) { return Utils.newError(runtime, _X509(runtime).getClass("StoreError"), message); } }// X509StoreContext