/*
* 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);
}
}