/* * 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 java.io.IOException; import java.math.BigInteger; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ocsp.CertID; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.ocsp.CertificateID; import org.bouncycastle.operator.DigestCalculator; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; import org.jruby.Ruby; import org.jruby.RubyBignum; import org.jruby.RubyClass; import org.jruby.RubyFixnum; 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.Visibility; import org.jruby.runtime.builtin.IRubyObject; import static org.jruby.ext.openssl.OCSP._OCSP; import static org.jruby.ext.openssl.Digest._Digest; import static org.jruby.ext.openssl.OCSP.newOCSPError; /** * An OpenSSL::OCSP::CertificateId identifies a certificate to the * CA so that a status check can be performed. * * @author lampad */ public class OCSPCertificateId extends RubyObject { private static final long serialVersionUID = 6324454052172773918L; private static ObjectAllocator CERTIFICATEID_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new OCSPCertificateId(runtime, klass); } }; public static void createCertificateId(final Ruby runtime, final RubyModule _OCSP) { RubyClass _certificateId = _OCSP.defineClassUnder("CertificateId", runtime.getObject(), CERTIFICATEID_ALLOCATOR); _certificateId.defineAnnotatedMethods(OCSPCertificateId.class); } private CertID bcCertId; private X509Cert originalIssuer; public OCSPCertificateId(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); } public OCSPCertificateId(Ruby runtime) { this(runtime, (RubyClass) _OCSP(runtime).getConstantAt("CertificateId")); } @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, IRubyObject subject, IRubyObject issuer, IRubyObject digest) { if (digest == null || digest.isNil()) { return initialize(context, subject, issuer); } X509Cert subjectCert = (X509Cert) subject; originalIssuer = (X509Cert) issuer; BigInteger serial = subjectCert.getSerial(); return initializeImpl(context, serial, originalIssuer, digest); } @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, IRubyObject subject, IRubyObject issuer) { Ruby runtime = context.getRuntime(); X509Cert subjectCert = (X509Cert) subject; originalIssuer = (X509Cert) issuer; BigInteger serial = subjectCert.getSerial(); Digest digestInstance = new Digest(runtime, _Digest(runtime)); IRubyObject digest = digestInstance.initialize(context, new IRubyObject[] { RubyString.newString(runtime, "SHA1") }); return initializeImpl(context, serial, originalIssuer, digest); } @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, IRubyObject der) { Ruby runtime = context.getRuntime(); RubyString derStr = StringHelper.readPossibleDERInput(context, der); try { return initializeImpl(derStr.getBytes()); } catch (IOException e) { throw newOCSPError(runtime, e); } } private IRubyObject initializeImpl(final ThreadContext context, BigInteger serial, IRubyObject issuerCert, IRubyObject digest) { Ruby runtime = context.getRuntime(); Digest rubyDigest = (Digest) digest; ASN1ObjectIdentifier oid = ASN1.sym2Oid(runtime, rubyDigest.getName().toLowerCase()); AlgorithmIdentifier bcAlgId = new AlgorithmIdentifier(oid); BcDigestCalculatorProvider calculatorProvider = new BcDigestCalculatorProvider(); DigestCalculator calc; try { calc = calculatorProvider.get(bcAlgId); } catch (OperatorCreationException e) { throw newOCSPError(runtime, e); } X509Cert rubyCert = (X509Cert) issuerCert; try { this.bcCertId = new CertificateID(calc, new X509CertificateHolder(rubyCert.getAuxCert().getEncoded()), serial).toASN1Primitive(); } catch (Exception e) { throw newOCSPError(runtime, e); } return this; } private IRubyObject initializeImpl(byte[] derByteStream) throws IOException { this.bcCertId = CertID.getInstance(derByteStream); return this; } @JRubyMethod(name = "serial") public IRubyObject serial() { return RubyBignum.newBignum(getRuntime(), bcCertId.getSerialNumber().getValue()); } @JRubyMethod(name = "issuer_name_hash") public IRubyObject issuer_name_hash() { Ruby runtime = getRuntime(); String oidSym = ASN1.oid2Sym(runtime, getBCCertificateID().getHashAlgOID()); RubyString digestName = RubyString.newString(runtime, oidSym); // For whatever reason, the MRI Ruby tests appear to suggest that they compute the hexdigest hash // of the issuer name over the original name instead of the hash computed in the created CertID. // I'm not sure how it's supposed to work with a passed in DER string since presumably the hash // is already computed and can't be reversed to get to the original name and thus we just compute // a hash of a hash if we don't have the original issuer around. if (originalIssuer == null) { try { return Digest.hexdigest(runtime.getCurrentContext(), this, digestName, RubyString.newString(runtime, bcCertId.getIssuerNameHash().getEncoded("DER"))); } catch (IOException e) { throw newOCSPError(runtime, e); } } else { return Digest.hexdigest(runtime.getCurrentContext(), this, digestName, originalIssuer.getSubject().to_der(runtime.getCurrentContext())); } } // For whatever reason, the MRI Ruby tests appear to suggest that they compute the hexdigest hash // of the issuer key over the original key instead of the hash computed in the created CertID. // I'm not sure how it's supposed to work with a passed in DER string since presumably the hash // is already computed and can't be reversed to get to the original key, so we just compute // a hash of a hash if we don't have the original issuer around. @JRubyMethod(name = "issuer_key_hash") public IRubyObject issuer_key_hash() { Ruby runtime = getRuntime(); String oidSym = ASN1.oid2Sym(runtime, getBCCertificateID().getHashAlgOID()); RubyString digestName = RubyString.newString(runtime, oidSym); if (originalIssuer == null) { try { return Digest.hexdigest(runtime.getCurrentContext(), this, RubyString.newString(runtime, oidSym), RubyString.newString(runtime, bcCertId.getIssuerKeyHash().getEncoded("DER"))); } catch (IOException e) { throw newOCSPError(runtime, e); } } else { PKey key = (PKey)originalIssuer.public_key(runtime.getCurrentContext()); return Digest.hexdigest(runtime.getCurrentContext(), this, digestName, key.to_der()); } } @JRubyMethod(name = "hash_algorithm") public IRubyObject hash_algorithm() { Ruby runtime = getRuntime(); ASN1ObjectIdentifier oid = bcCertId.getHashAlgorithm().getAlgorithm(); Integer nid = ASN1.oid2nid(runtime, oid); String ln = ASN1.nid2ln(runtime, nid); return RubyString.newString(runtime, ln); } @JRubyMethod(name = "cmp") public IRubyObject cmp(IRubyObject other) { Ruby runtime = getRuntime(); RubyFixnum ret = (RubyFixnum) this.cmp_issuer(other); if (!ret.eql(RubyFixnum.zero(runtime))) return ret; OCSPCertificateId that = (OCSPCertificateId) other; return RubyFixnum.newFixnum( runtime, this.getCertID().getSerialNumber().getValue().compareTo( that.getCertID().getSerialNumber().getValue() ) ); } @JRubyMethod(name = "cmp_issuer") public IRubyObject cmp_issuer(IRubyObject other) { Ruby runtime = getRuntime(); if ( equals(other) ) { return RubyFixnum.zero(runtime); } if (other instanceof OCSPCertificateId) { OCSPCertificateId that = (OCSPCertificateId) other; CertID thisCert = this.getCertID(); CertID thatCert = that.getCertID(); int ret = thisCert.getHashAlgorithm().getAlgorithm().toString().compareTo( thatCert.getHashAlgorithm().getAlgorithm().toString()); if (ret != 0) return RubyFixnum.newFixnum(runtime, ret); ret = thisCert.getIssuerNameHash().toString().compareTo( thatCert.getIssuerNameHash().toString()); if (ret != 0) return RubyFixnum.newFixnum(runtime, ret); return RubyFixnum.newFixnum(runtime, thisCert.getIssuerKeyHash().toString().compareTo( thatCert.getIssuerKeyHash().toString())); } else { return runtime.getCurrentContext().nil; } } @JRubyMethod(name = "to_der") public IRubyObject to_der() { Ruby runtime = getRuntime(); try { return StringHelper.newString(runtime, bcCertId.getEncoded(ASN1Encoding.DER)); } catch (IOException e) { throw newOCSPError(runtime, e); } } @Override @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject initialize_copy(IRubyObject obj) { if ( this == obj ) return this; checkFrozen(); this.bcCertId = ((OCSPCertificateId)obj).getCertID(); return this; } @Override public boolean equals(Object other) { if ( this == other ) return true; if ( other instanceof OCSPCertificateId ) { OCSPCertificateId that = (OCSPCertificateId) other; return this.getCertID().equals(that.getCertID()); } else { return false; } } public CertID getCertID() { return bcCertId; } public CertificateID getBCCertificateID() { if (bcCertId == null) return null; return new CertificateID(bcCertId); } }