/* * The contents of this file are subject to the Common 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/cpl-v10.html * * 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 APARTICULAR 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. * * Copyright (C) 2017 Donovan Lampa <donovan.lampa@gmail.com> * Copyright (C) 2009-2017 The JRuby Team * * 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. * * * JRuby-OpenSSL includes software by The Legion of the Bouncy Castle Inc. * Please, visit (http://bouncycastle.org/license.html) for licensing details. */ package org.jruby.ext.openssl; import static org.jruby.ext.openssl.OCSP._OCSP; import static org.jruby.ext.openssl.OCSP.newOCSPError; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1GeneralizedTime; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.ocsp.CertID; import org.bouncycastle.asn1.ocsp.RevokedInfo; import org.bouncycastle.asn1.ocsp.SingleResponse; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extensions; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyModule; 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.Visibility; import org.jruby.runtime.builtin.IRubyObject; /* * An OpenSSL::OCSP::SingleResponse represents an OCSP SingleResponse structure, * which contains the basic information of the status of the certificate. * * @author lampad */ public class OCSPSingleResponse extends RubyObject { private static final long serialVersionUID = 7947277768033100227L; private static ObjectAllocator SINGLERESPONSE_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new OCSPSingleResponse(runtime, klass); } }; public static void createSingleResponse(final Ruby runtime, final RubyModule _OCSP) { RubyClass _request = _OCSP.defineClassUnder("SingleResponse", runtime.getObject(), SINGLERESPONSE_ALLOCATOR); _request.defineAnnotatedMethods(OCSPSingleResponse.class); } public OCSPSingleResponse(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); } public OCSPSingleResponse(Ruby runtime) { this(runtime, (RubyClass) _OCSP(runtime).getConstantAt("SingleResponse")); } private SingleResponse bcSingleResponse; @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, IRubyObject derStr) { Ruby runtime = context.getRuntime(); RubyString rubyDerStr = (RubyString) derStr; try { bcSingleResponse = SingleResponse.getInstance(DERTaggedObject.fromByteArray(rubyDerStr.getBytes())); } catch (IOException e) { throw newOCSPError(runtime, e); } return this; } @JRubyMethod(name = "cert_status") public IRubyObject cert_status() { return RubyFixnum.newFixnum(getRuntime(), bcSingleResponse.getCertStatus().getTagNo()); } @JRubyMethod(name = "certid") public IRubyObject certid(ThreadContext context) { Ruby runtime = context.runtime; CertID bcCertId = bcSingleResponse.getCertID(); OCSPCertificateId rubyCertId = new OCSPCertificateId(runtime); try { rubyCertId.initialize(context, RubyString.newString(runtime, bcCertId.getEncoded())); } catch (IOException e) { throw newOCSPError(runtime, e); } return rubyCertId; } @JRubyMethod(name = "check_validity", rest = true) public IRubyObject check_validity(IRubyObject[] args) { Ruby runtime = getRuntime(); int nsec, maxsec; Date thisUpdate, nextUpdate; if ( Arity.checkArgumentCount(runtime, args, 0, 2) == 0 ) { nsec = 0; maxsec = -1; } else if ( Arity.checkArgumentCount(runtime, args, 0, 2) == 1 ) { RubyFixnum rNsec = (RubyFixnum) args[0]; nsec = (int)rNsec.getLongValue(); maxsec = -1; } else { RubyFixnum rNsec = (RubyFixnum) args[0]; RubyFixnum rMaxsec = (RubyFixnum) args[1]; nsec = (int)rNsec.getLongValue(); maxsec = (int)rMaxsec.getLongValue(); } try { ASN1GeneralizedTime bcThisUpdate = bcSingleResponse.getThisUpdate(); if (bcThisUpdate == null) { thisUpdate = null; } else { thisUpdate = bcThisUpdate.getDate(); } ASN1GeneralizedTime bcNextUpdate = bcSingleResponse.getNextUpdate(); if (bcNextUpdate == null) { nextUpdate = null; } else { nextUpdate = bcNextUpdate.getDate(); } } catch (ParseException e) { throw newOCSPError(runtime, e); } return RubyBoolean.newBoolean(runtime, checkValidityImpl(thisUpdate, nextUpdate, nsec, maxsec)); } @JRubyMethod(name = "extensions") public IRubyObject extensions() { Ruby runtime = getRuntime(); Extensions exts = bcSingleResponse.getSingleExtensions(); if (exts == null) return RubyArray.newEmptyArray(runtime); ASN1ObjectIdentifier[] extOIDs = exts.getExtensionOIDs(); RubyArray retExts = runtime.newArray(extOIDs.length); for (ASN1ObjectIdentifier extOID : extOIDs) { Extension ext = exts.getExtension(extOID); ASN1Encodable extAsn1 = ext.getParsedValue(); X509Extension retExt = X509Extension.newExtension(runtime, extOID, extAsn1, ext.isCritical()); retExts.append(retExt); } return retExts; } @JRubyMethod(name = "next_update") public IRubyObject next_update() { Ruby runtime = getRuntime(); if (bcSingleResponse.getNextUpdate() == null) return runtime.getNil(); Date nextUpdate; try { nextUpdate = bcSingleResponse.getNextUpdate().getDate(); } catch (ParseException e) { throw newOCSPError(runtime, e); } if (nextUpdate == null) { return runtime.getNil(); } return RubyTime.newTime(runtime, nextUpdate.getTime()); } @JRubyMethod(name = "this_update") public IRubyObject this_update() { Ruby runtime = getRuntime(); if (bcSingleResponse.getThisUpdate() == null) return runtime.getNil(); Date thisUpdate; try { thisUpdate = bcSingleResponse.getThisUpdate().getDate(); } catch (ParseException e) { throw newOCSPError(runtime, e); } return RubyTime.newTime(runtime, thisUpdate.getTime()); } @JRubyMethod(name = "revocation_reason") public IRubyObject revocation_reason() { Ruby runtime = getRuntime(); RubyFixnum revoked = (RubyFixnum) _OCSP(runtime).getConstant("V_CERTSTATUS_REVOKED"); if (bcSingleResponse.getCertStatus().getTagNo() == (int) revoked.getLongValue()) { try { RevokedInfo revokedInfo = RevokedInfo.getInstance( DERTaggedObject.fromByteArray(bcSingleResponse.getCertStatus().getStatus().toASN1Primitive().getEncoded()) ); return RubyFixnum.newFixnum(runtime, revokedInfo.getRevocationReason().getValue().intValue()); } catch (IOException e) { throw newOCSPError(runtime, e); } } return runtime.getNil(); } @JRubyMethod(name = "revocation_time") public IRubyObject revocation_time() { Ruby runtime = getRuntime(); RubyFixnum revoked = (RubyFixnum) _OCSP(runtime).getConstant("V_CERTSTATUS_REVOKED"); if (bcSingleResponse.getCertStatus().getTagNo() == (int)revoked.getLongValue()) { try { RevokedInfo revokedInfo = RevokedInfo.getInstance( DERTaggedObject.fromByteArray(bcSingleResponse.getCertStatus().getStatus().toASN1Primitive().getEncoded()) ); return RubyTime.newTime(runtime, revokedInfo.getRevocationTime().getDate().getTime()); } catch (Exception e) { throw newOCSPError(runtime, e); } } return runtime.getNil(); } @JRubyMethod(name = "to_der") public IRubyObject to_der() { Ruby runtime = getRuntime(); try { return RubyString.newString(runtime, bcSingleResponse.getEncoded()); } catch (IOException e) { throw newOCSPError(runtime, e); } } public SingleResponse getBCSingleResp() { return bcSingleResponse; } // see OCSP_check_validity in ocsp_cl.c private boolean checkValidityImpl(Date thisUpdate, Date nextUpdate, int nsec, int maxsec) { boolean ret = true; Date currentTime = new Date(); Date tempTime = new Date(); tempTime.setTime(currentTime.getTime() + (nsec*1000)); if (thisUpdate.compareTo(tempTime) > 0) { ret = false; } if (maxsec >= 0) { tempTime.setTime(currentTime.getTime() - (maxsec*1000)); if (thisUpdate.compareTo(tempTime) < 0) { ret = false; } } if (nextUpdate == null) { return ret; } tempTime.setTime(currentTime.getTime() - (nsec*1000)); if (nextUpdate.compareTo(tempTime) < 0) { ret = false; } if (nextUpdate.compareTo(thisUpdate) < 0) { ret = false; } return ret; } }