Skip to content

Commit c64063a

Browse files
committed
X509::CRL verification using (only) BC APIs
1 parent 93ab617 commit c64063a

File tree

1 file changed

+188
-58
lines changed

1 file changed

+188
-58
lines changed

src/main/java/org/jruby/ext/openssl/X509CRL.java

Lines changed: 188 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@
4444
import java.security.cert.CRLException;
4545
import java.security.cert.CertificateFactory;
4646
import java.security.cert.X509CRLEntry;
47+
import java.security.interfaces.DSAParams;
48+
import java.security.interfaces.DSAPublicKey;
49+
import java.security.interfaces.RSAPublicKey;
4750
import java.util.Arrays;
51+
import java.util.Collection;
4852
import java.util.Comparator;
4953

5054
import org.bouncycastle.asn1.ASN1Encodable;
@@ -56,11 +60,26 @@
5660
import org.bouncycastle.asn1.DERSequence;
5761
import org.bouncycastle.asn1.DLSequence;
5862
import org.bouncycastle.asn1.x500.X500Name;
63+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
64+
import org.bouncycastle.asn1.x509.CertificateList;
65+
import org.bouncycastle.asn1.x509.Extension;
5966
import org.bouncycastle.asn1.x509.Extensions;
67+
import org.bouncycastle.cert.CertException;
6068
import org.bouncycastle.cert.X509CRLHolder;
6169
import org.bouncycastle.cert.X509v2CRLBuilder;
70+
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
71+
import org.bouncycastle.crypto.params.DSAParameters;
72+
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
73+
import org.bouncycastle.crypto.params.RSAKeyParameters;
6274
import org.bouncycastle.jce.provider.X509CRLObject;
6375
import org.bouncycastle.operator.ContentSigner;
76+
import org.bouncycastle.operator.ContentVerifier;
77+
import org.bouncycastle.operator.ContentVerifierProvider;
78+
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
79+
import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
80+
import org.bouncycastle.operator.OperatorException;
81+
import org.bouncycastle.operator.bc.BcDSAContentVerifierProviderBuilder;
82+
import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
6483
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
6584

6685
import org.joda.time.DateTime;
@@ -87,7 +106,6 @@
87106

88107
import static org.jruby.ext.openssl.OpenSSL.*;
89108
import static org.jruby.ext.openssl.X509._X509;
90-
import static org.jruby.ext.openssl.X509Extension._Extension;
91109
import static org.jruby.ext.openssl.X509Extension.newExtension;
92110
import static org.jruby.ext.openssl.StringHelper.appendGMTDateTime;
93111
import static org.jruby.ext.openssl.StringHelper.appendLowerHexValue;
@@ -122,7 +140,7 @@ public static void createX509CRL(final Ruby runtime, final RubyModule _X509) {
122140

123141
private boolean changed = true;
124142

125-
private java.security.cert.X509CRL crl;
143+
private java.security.cert.X509CRL crl = null;
126144
private transient X509CRLHolder crlHolder;
127145
private transient ASN1Primitive crlValue;
128146

@@ -150,12 +168,16 @@ java.security.cert.X509CRL getCRL() {
150168
catch (IOException ex) {
151169
throw newCRLError(getRuntime(), ex);
152170
}
171+
catch (GeneralSecurityException ex) {
172+
throw newCRLError(getRuntime(), ex);
173+
}
153174
}
154175

155-
X509CRLHolder getCRLHolder() {
176+
private X509CRLHolder getCRLHolder(boolean allowNull) {
156177
if ( crlHolder != null ) return crlHolder;
157178
try {
158179
if ( crl == null ) {
180+
if ( allowNull ) return null;
159181
throw new IllegalStateException("no crl");
160182
}
161183
return crlHolder = new X509CRLHolder(crl.getEncoded());
@@ -177,17 +199,20 @@ private byte[] getSignature() {
177199
return getCRL().getSignature();
178200
}
179201

180-
private java.security.cert.X509CRL generateCRL(final byte[] bytes,
181-
final int offset, final int length) {
182-
try {
183-
CertificateFactory factory = SecurityHelper.getCertificateFactory("X.509");
184-
return (java.security.cert.X509CRL) factory.generateCRL(
185-
new ByteArrayInputStream(bytes, offset, length)
186-
);
187-
}
188-
catch (GeneralSecurityException e) {
189-
throw newCRLError(getRuntime(), e.getMessage());
190-
}
202+
private static boolean avoidJavaSecurity = false;
203+
204+
private static java.security.cert.X509CRL generateCRL(
205+
final byte[] bytes, final int offset, final int length)
206+
throws GeneralSecurityException {
207+
CertificateFactory factory = SecurityHelper.getCertificateFactory("X.509");
208+
return (java.security.cert.X509CRL) factory.generateCRL(
209+
new ByteArrayInputStream(bytes, offset, length)
210+
);
211+
}
212+
213+
private static X509CRLHolder parseCRLHolder(
214+
final byte[] bytes, final int offset, final int length) throws IOException {
215+
return new X509CRLHolder(new ByteArrayInputStream(bytes, offset, length));
191216
}
192217

193218
@JRubyMethod(name = "initialize", rest = true, visibility = Visibility.PRIVATE)
@@ -199,9 +224,25 @@ public IRubyObject initialize(final ThreadContext context,
199224

200225
if ( Arity.checkArgumentCount(runtime, args, 0, 1) == 0 ) return this;
201226

202-
final ByteList bytes = args[0].asString().getByteList();
203-
final int offset = bytes.getBegin(); final int length = bytes.getRealSize();
204-
this.crl = generateCRL(bytes.unsafeBytes(), offset, length);
227+
final ByteList strList = args[0].asString().getByteList();
228+
final byte[] bytes = strList.unsafeBytes();
229+
final int offset = strList.getBegin(); final int length = strList.getRealSize();
230+
try {
231+
if ( avoidJavaSecurity ) {
232+
this.crlHolder = parseCRLHolder(bytes, offset, length);
233+
}
234+
else {
235+
this.crl = generateCRL(bytes, offset, length);
236+
}
237+
}
238+
catch (IOException e) {
239+
debugStackTrace(runtime, e);
240+
throw newCRLError(runtime, e);
241+
}
242+
catch (GeneralSecurityException e) {
243+
debugStackTrace(runtime, e);
244+
throw newCRLError(runtime, e);
245+
}
205246

206247
set_last_update( context, RubyTime.newTime(runtime, crl.getThisUpdate().getTime()) );
207248
set_next_update( context, RubyTime.newTime(runtime, crl.getNextUpdate().getTime()) );
@@ -210,22 +251,7 @@ public IRubyObject initialize(final ThreadContext context,
210251
final int version = crl.getVersion();
211252
this.version = runtime.newFixnum( version > 0 ? version - 1 : 2 );
212253

213-
214-
final RubyClass _Extension = _Extension(runtime);
215-
216-
final Set<String> criticalExtOIDs = crl.getCriticalExtensionOIDs();
217-
if ( criticalExtOIDs != null ) {
218-
for ( final String extOID : criticalExtOIDs ) {
219-
addExtension(context, _Extension, extOID, true);
220-
}
221-
}
222-
223-
final Set<String> nonCriticalExtOIDs = crl.getNonCriticalExtensionOIDs();
224-
if ( nonCriticalExtOIDs != null ) {
225-
for ( final String extOID : nonCriticalExtOIDs ) {
226-
addExtension(context, _Extension, extOID, false);
227-
}
228-
}
254+
extractExtensions(context);
229255

230256
Set<? extends X509CRLEntry> revokedCRLs = crl.getRevokedCertificates();
231257
if ( revokedCRLs != null && ! revokedCRLs.isEmpty() ) {
@@ -245,8 +271,48 @@ public int compare(X509CRLEntry o1, X509CRLEntry o2) {
245271
return this;
246272
}
247273

274+
private void extractExtensions(final ThreadContext context) {
275+
if ( crlHolder != null ) extractExtensions(context, crlHolder);
276+
else extractExtensionsCRL(context, getCRL());
277+
}
278+
279+
@SuppressWarnings("unchecked")
280+
private void extractExtensions(final ThreadContext context, final X509CRLHolder crl) {
281+
if ( ! crlHolder.hasExtensions() ) return;
282+
for ( ASN1ObjectIdentifier oid : (Collection<ASN1ObjectIdentifier>) crl.getExtensionOIDs() ) {
283+
addExtension(context, oid, crl);
284+
}
285+
}
286+
248287
private void addExtension(final ThreadContext context,
249-
final RubyClass _Extension, final String extOID, final boolean critical) {
288+
final ASN1ObjectIdentifier extOID, final X509CRLHolder crl) {
289+
final Extension ext = crl.getExtension(extOID);
290+
final IRubyObject extension = newExtension(context.runtime, extOID, ext);
291+
this.extensions.append(extension);
292+
}
293+
294+
private void extractExtensionsCRL(final ThreadContext context,
295+
final java.security.cert.X509Extension crl) {
296+
//final RubyClass _Extension = _Extension(context.runtime);
297+
298+
final Set<String> criticalExtOIDs = crl.getCriticalExtensionOIDs();
299+
if ( criticalExtOIDs != null ) {
300+
for ( final String extOID : criticalExtOIDs ) {
301+
addExtensionCRL(context, extOID, crl, true);
302+
}
303+
}
304+
305+
final Set<String> nonCriticalExtOIDs = crl.getNonCriticalExtensionOIDs();
306+
if ( nonCriticalExtOIDs != null ) {
307+
for ( final String extOID : nonCriticalExtOIDs ) {
308+
addExtensionCRL(context, extOID, crl, false);
309+
}
310+
}
311+
}
312+
313+
private void addExtensionCRL(final ThreadContext context,
314+
final String extOID, final java.security.cert.X509Extension crl,
315+
final boolean critical) {
250316
try {
251317
final IRubyObject extension = newExtension(context, extOID, crl, critical);
252318
if ( extension != null ) this.extensions.append(extension);
@@ -394,20 +460,24 @@ private RubyString signature_algorithm(final Ruby runtime) {
394460
}
395461

396462
private String getSignatureAlgorithm(final Ruby runtime, final String def) {
397-
if ( getCRL() == null ) return def;
463+
final X509CRLHolder crlHolder = getCRLHolder(true);
464+
if ( crlHolder == null ) return def;
398465

466+
ASN1ObjectIdentifier algId =
467+
crlHolder.toASN1Structure().getSignatureAlgorithm().getAlgorithm();
468+
//ASN1ObjectIdentifier algId = ASN1.toObjectID( getCRL().getSigAlgOID(), true );
399469
String algName;
400-
ASN1ObjectIdentifier algId = ASN1.toObjectID( getCRL().getSigAlgOID(), true );
401470
if ( algId != null ) {
402471
algName = ASN1.o2a(runtime, algId, true);
403472
}
404-
else {
405-
algName = getCRL().getSigAlgName();
406-
algId = ASN1.toObjectID( algName, true );
407-
if ( algId != null ) {
408-
algName = ASN1.o2a(runtime, algId, true);
409-
}
410-
}
473+
else algName = null;
474+
//else {
475+
// algName = getCRL().getSigAlgName();
476+
// algId = ASN1.toObjectID( algName, true );
477+
// if ( algId != null ) {
478+
// algName = ASN1.o2a(runtime, algId, true);
479+
// }
480+
//}
411481
return algName == null ? def : algName;
412482
}
413483

@@ -542,6 +612,12 @@ public IRubyObject sign(final ThreadContext context, final IRubyObject key, IRub
542612

543613
final PrivateKey privateKey = ((PKey) key).getPrivateKey();
544614
try {
615+
if ( avoidJavaSecurity ) {
616+
// NOT IMPLEMENTED
617+
}
618+
else {
619+
//crl = generator.generate(((PKey) key).getPrivateKey());
620+
}
545621
/*
546622
AlgorithmIdentifier keyAldID = new AlgorithmIdentifier(new ASN1ObjectIdentifier(keyAlg));
547623
AlgorithmIdentifier digAldID = new AlgorithmIdentifier(new ASN1ObjectIdentifier(digAlg));
@@ -569,9 +645,9 @@ public IRubyObject sign(final ThreadContext context, final IRubyObject key, IRub
569645
debugStackTrace(e); throw newCRLError(runtime, e.getMessage());
570646
}
571647

572-
final ASN1Primitive crlValue = getCRLValue(runtime);
648+
final ASN1Primitive crlVal = getCRLValue(runtime);
573649

574-
ASN1Sequence v1 = (ASN1Sequence) ( ((ASN1Sequence) crlValue).getObjectAt(0) );
650+
ASN1Sequence v1 = (ASN1Sequence) ( ((ASN1Sequence) crlVal).getObjectAt(0) );
575651
final ASN1EncodableVector build1 = new ASN1EncodableVector();
576652
int copyIndex = 0;
577653
if ( v1.getObjectAt(0) instanceof ASN1Integer ) copyIndex++;
@@ -581,8 +657,8 @@ public IRubyObject sign(final ThreadContext context, final IRubyObject key, IRub
581657
}
582658
final ASN1EncodableVector build2 = new ASN1EncodableVector();
583659
build2.add( new DLSequence(build1) );
584-
build2.add( ((ASN1Sequence) crlValue).getObjectAt(1) );
585-
build2.add( ((ASN1Sequence) crlValue).getObjectAt(2) );
660+
build2.add( ((ASN1Sequence) crlVal).getObjectAt(1) );
661+
build2.add( ((ASN1Sequence) crlVal).getObjectAt(2) );
586662

587663
this.crlValue = new DLSequence(build2);
588664
changed = false;
@@ -595,8 +671,7 @@ private String getSignatureAlgorithm(final Ruby runtime, final PKey key, final D
595671
final String digAlg = digest.getShortAlgorithm();
596672

597673
if ( "DSA".equalsIgnoreCase(keyAlg) ) {
598-
if ( ( "MD5".equalsIgnoreCase( digAlg ) ) ||
599-
( "SHA1".equals( digest.name().toString() ) ) ) {
674+
if ( ( "MD5".equalsIgnoreCase( digAlg ) ) ) {
600675
throw newCRLError(runtime, "unsupported key / digest algorithm ("+ key +" / "+ digAlg +")");
601676
}
602677
}
@@ -609,6 +684,10 @@ else if ( "RSA".equalsIgnoreCase(keyAlg) ) {
609684
return digAlg + "WITH" + keyAlg;
610685
}
611686

687+
private boolean isDSA(final PKey key) {
688+
return "DSA".equalsIgnoreCase( key.getAlgorithm() );
689+
}
690+
612691
private ASN1Primitive getCRLValue(final Ruby runtime) {
613692
if ( this.crlValue != null ) return this.crlValue;
614693
return this.crlValue = readCRL( runtime );
@@ -627,25 +706,76 @@ public IRubyObject verify(final ThreadContext context, final IRubyObject key) {
627706
if ( changed ) return context.runtime.getFalse();
628707
final PublicKey publicKey = ((PKey) key).getPublicKey();
629708
try {
630-
boolean valid = SecurityHelper.verify(getCRL(), publicKey, true);
709+
// NOTE: with BC 1.49 this seems to need BC provider installed ;(
710+
// java.security.NoSuchProviderException: no such provider: BC
711+
// at sun.security.jca.GetInstance.getService(GetInstance.java:83)
712+
// at sun.security.jca.GetInstance.getInstance(GetInstance.java:206)
713+
// at java.security.Signature.getInstance(Signature.java:355)
714+
// at org.bouncycastle.jcajce.provider.asymmetric.x509.X509CRLObject.verify(Unknown Source)
715+
// at org.bouncycastle.jcajce.provider.asymmetric.x509.X509CRLObject.verify(Unknown Source)
716+
// at org.jruby.ext.openssl.SecurityHelper.verify(SecurityHelper.java:564)
717+
// at org.jruby.ext.openssl.X509CRL.verify(X509CRL.java:717)
718+
//boolean valid = SecurityHelper.verify(getCRL(), publicKey, true);
719+
720+
final DigestAlgorithmIdentifierFinder digestAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
721+
final ContentVerifierProvider verifierProvider;
722+
if ( isDSA( (PKey) key ) ) {
723+
BigInteger y = ((DSAPublicKey) publicKey).getY();
724+
DSAParams params = ((DSAPublicKey) publicKey).getParams();
725+
DSAParameters parameters = new DSAParameters(params.getP(), params.getQ(), params.getG());
726+
AsymmetricKeyParameter dsaKey = new DSAPublicKeyParameters(y, parameters);
727+
verifierProvider = new BcDSAContentVerifierProviderBuilder(digestAlgFinder).build(dsaKey);
728+
}
729+
else {
730+
BigInteger mod = ((RSAPublicKey) publicKey).getModulus();
731+
BigInteger exp = ((RSAPublicKey) publicKey).getPublicExponent();
732+
AsymmetricKeyParameter rsaKey = new RSAKeyParameters(false, mod, exp);
733+
verifierProvider = new BcRSAContentVerifierProviderBuilder(digestAlgFinder).build(rsaKey);
734+
}
735+
//final X509CRLHolder crl = getCRLHolder();
736+
//final AlgorithmIdentifier algId = crl.toASN1Structure().getSignatureAlgorithm();
737+
boolean valid = getCRLHolder(false).isSignatureValid( verifierProvider );
631738
return context.runtime.newBoolean(valid);
632739
}
633-
catch (CRLException e) {
740+
catch (OperatorException e) {
634741
debug("CRL#verify() failed:", e);
635742
return context.runtime.getFalse();
636743
}
637-
catch (InvalidKeyException e) {
744+
catch (CertException e) {
638745
debug("CRL#verify() failed:", e);
639746
return context.runtime.getFalse();
640747
}
641-
catch (SignatureException e) {
642-
debug("CRL#verify() failed:", e);
643-
return context.runtime.getFalse();
748+
// catch (SignatureException e) {
749+
// debug("CRL#verify() failed:", e);
750+
// return context.runtime.getFalse();
751+
// }
752+
// catch (NoSuchAlgorithmException e) {
753+
// return context.runtime.getFalse();
754+
// }
755+
}
756+
757+
/*
758+
private static boolean verify(final CertificateList crl, final PublicKey publicKey)
759+
throws CRLException, InvalidKeyException, SignatureException, NoSuchAlgorithmException {
760+
761+
final AlgorithmIdentifier tbsSignatureId = crl.getTBSCertList().getSignature();
762+
if ( ! crl.getSignatureAlgorithm().equals( tbsSignatureId ) ) {
763+
if ( true ) return false;
764+
//throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
644765
}
645-
catch (NoSuchAlgorithmException e) {
646-
return context.runtime.getFalse();
766+
767+
final String sigAlgName = X509SignatureUtil.getSignatureName(crl.getSignatureAlgorithm());
768+
final Signature signature = SecurityHelper.getSignature(sigAlgName, securityProvider);
769+
770+
signature.initVerify(publicKey);
771+
signature.update(crl.getTBSCertList());
772+
773+
if ( ! signature.verify( crl.getSignature() ) ) {
774+
if ( true ) return false;
775+
//throw new SignatureException("CRL does not verify with supplied public key.");
647776
}
648-
}
777+
return true;
778+
} */
649779

650780
private static RubyClass _CRLError(final Ruby runtime) {
651781
return _X509(runtime).getClass("CRLError");

0 commit comments

Comments
 (0)