Skip to content

Improving completeness of ASN1 encoding/decoding #335

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
inlining getConstructiveTag logic, expand on the rest of the informat…
…ion in the tag segment

the tag segment also contains info about whether the payload is for a constructed DER, and whether it's indefinite length; this info was buried in the method, with no easy way to piggyback on, so it was easier to inline the logic (only used here anyway), and propagate the rest of the information, which allows setting the indefinite_length ivar for ASN1Data objects

it also raises exceptions where it couldn't (or shouldn't?)
  • Loading branch information
HoneyryderChuck committed Jun 25, 2025
commit 610a3559b982f987deb775ee65541776d6000a45
210 changes: 93 additions & 117 deletions src/main/java/org/jruby/ext/openssl/ASN1.java
Original file line number Diff line number Diff line change
Expand Up @@ -1166,19 +1166,60 @@ private BytesInputStream(final ByteList bytes) {

private static IRubyObject decodeImpl(final ThreadContext context, final RubyModule ASN1, final BytesInputStream in)
throws IOException, IllegalArgumentException {
final byte[] asn1 = in.bytes();
int offset = in.offset();
final int tag = asn1[offset] & 0xFF;

if ( ( tag & BERTags.CONSTRUCTED ) == 0 ) {
return decodeObject(context, ASN1, readObject(in));
}

// NOTE: need to handle OpenSSL::ASN1::Constructive wrapping by hand :
final Integer tag = getConstructiveTag(in.bytes(), in.offset());
IRubyObject decoded = decodeObject(context, ASN1, readObject( in ));
if ( tag != null ) { // OpenSSL::ASN1::Constructive.new( arg ) :
if ( tag.intValue() == SEQUENCE ) {
//type = "Sequence"; // got a OpenSSL::ASN1::Sequence already :
return Constructive.setInfiniteLength(context, decoded);
int tagNo = tag & 0x1f;
if (tagNo == 0x1f)
{
tagNo = 0;
int b = asn1[ ++offset ];

// X.690-0207 8.1.2.4.2
// "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
if ((b & 0x7f) == 0) // Note: -1 will pass
{
throw new IOException("corrupted stream - invalid high tag number found");
}

while ((b >= 0) && ((b & 0x80) != 0))
{
tagNo |= (b & 0x7f);
tagNo <<= 7;
b = asn1[ ++offset ];
}

if (b < 0)
{
throw new IOException("EOF found inside tag value.");
}
if ( tag.intValue() == SET ) {
//type = "Set"; // got a OpenSSL::ASN1::Set already :
return Constructive.setInfiniteLength(context, decoded);

tagNo |= (b & 0x7f);
}
final int length = asn1[ ++offset ] & 0xFF;
final boolean isIndefiniteLength = length == 0x80;

IRubyObject decoded = decodeObject(context, ASN1, readObject(in));
final boolean isUniversal = ((ASN1Data) decoded).isUniversal(context);

if (isIndefiniteLength) {
if (tagNo == BERTags.SEQUENCE || tagNo == BERTags.SET) {
return ASN1Data.setInfiniteLength(context, decoded);
} else if (isUniversal) {
decoded = Constructive.newInfiniteLength(context, context.runtime.newArray(decoded), tagNo);
} else {
if (decoded instanceof ASN1Data) {
return ASN1Data.setInfiniteLength(context, decoded);
} else {
decoded = ASN1Data.newInfiniteLength(context, context.runtime.newArray(decoded), tagNo, ((ASN1Data) decoded).tagClass());
}
}
return Constructive.newInfiniteConstructive(context, "Constructive", context.runtime.newArray(decoded), tag);
}
return decoded;
}
Expand Down Expand Up @@ -1235,89 +1276,6 @@ private static org.bouncycastle.asn1.ASN1Primitive readObject(final InputStream
return new ASN1InputStream(bytes).readObject();
}

// NOTE: BC's ASNInputStream internals "reinvented" a bit :
private static Integer getConstructiveTag(final byte[] asn1, int offset) {
final int tag = asn1[ offset ] & 0xFF;
if ( ( tag & BERTags.CONSTRUCTED ) != 0 ) { // isConstructed
//
// calculate tag number
//
// readTagNumber(asn1, ++offset, tag) :
int tagNo = tag & 0x1f;
//
// with tagged object tag number is bottom 5 bits, or stored at the start of the content
//
if (tagNo == 0x1f)
{
tagNo = 0;

int b = asn1[ ++offset ]; //s.read();

// X.690-0207 8.1.2.4.2
// "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
if ((b & 0x7f) == 0) // Note: -1 will pass
{
return null; //throw new IOException("corrupted stream - invalid high tag number found");
}

while ((b >= 0) && ((b & 0x80) != 0))
{
tagNo |= (b & 0x7f);
tagNo <<= 7;
b = asn1[ ++offset ]; //s.read();
}

if (b < 0)
{
return null; //throw new EOFException("EOF found inside tag value.");
}

tagNo |= (b & 0x7f);
}

//
// calculate length
//
final int length = asn1[ ++offset ] & 0xFF;

if ( length == 0x80 ) {
// return -1; // indefinite-length encoding
}
else {
return null;
}

if ((tag & BERTags.APPLICATION) != 0) {
//return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
}

if ((tag & BERTags.TAGGED) != 0) {
//return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
}

//System.out.println(" tagNo = 0x" + Integer.toHexString(tagNo));
// TODO There are other tags that may be constructed (e.g. BIT_STRING)
switch (tagNo) {
case BERTags.SEQUENCE :
//return new BERSequenceParser(sp).getLoadedObject();
return Integer.valueOf( SEQUENCE ); //return "Sequence";
case BERTags.SET :
//return new BERSetParser(sp).getLoadedObject();
return Integer.valueOf( SET ); //return "Set";
case BERTags.OCTET_STRING :
return Integer.valueOf( OCTET_STRING );
//return new BEROctetStringParser(sp).getLoadedObject();
case BERTags.EXTERNAL :
//return new DERExternalParser(sp).getLoadedObject();
default:
return Integer.valueOf( 0 ); //return "Constructive";
//throw new IOException("unknown BER object encountered");
}
}

return null;
}

public static class ASN1Data extends RubyObject {
private static final long serialVersionUID = 6117598347932209839L;

Expand All @@ -1340,9 +1298,38 @@ public IRubyObject initialize(final ThreadContext context,
this.callMethod(context, "tag=", tag);
this.callMethod(context, "value=", value);
this.callMethod(context, "tag_class=", tag_class);
this.setInstanceVariable("@indefinite_length", context.runtime.getFalse());
return this;
}

static ASN1Data newInfiniteLength(final ThreadContext context,
final IRubyObject value, final int defaultTag, final IRubyObject tagClass) {
final Ruby runtime = context.runtime;

final RubyClass klass = _ASN1(runtime).getClass("ASN1Data");
final ASN1Data self = new Constructive(runtime, klass);

ASN1Data.newInfiniteLengthImpl(context, self, value, defaultTag, tagClass);
return self;
}

static void newInfiniteLengthImpl(final ThreadContext context, final ASN1Data self, final IRubyObject value, final int defaultTag, final IRubyObject tagClass) {
self.setInstanceVariable("@tag", context.runtime.newFixnum(defaultTag));
self.setInstanceVariable("@value", value);
self.setInstanceVariable("@tag_class", tagClass);
self.setInstanceVariable("@tagging", context.nil);

setInfiniteLength(context, self);
}

static ASN1Data setInfiniteLength(final ThreadContext context, final IRubyObject constructive) {
final ASN1Data instance = ((ASN1Data) constructive);
final IRubyObject value = instance.value(context);
value.callMethod(context, "<<", EndOfContent.newInstance(context));
instance.setInstanceVariable("@indefinite_length", context.runtime.getTrue());
return instance;
}

private void checkTag(final Ruby runtime, final IRubyObject tag, final IRubyObject tagClass) {
if ( ! (tagClass instanceof RubySymbol) ) {
throw newASN1Error(runtime, "invalid tag class");
Expand All @@ -1357,13 +1344,17 @@ boolean isEOC() {
}

boolean isUniversal(final ThreadContext context) {
return "ASN1Data".equals(getClassBaseName()) && getTagClass(context) == 0;
return getTagClass(context) == BERTags.UNIVERSAL;
}

IRubyObject tagging() {
return getInstanceVariable("@tagging");
}

IRubyObject tagClass() {
return getInstanceVariable("@tag_class");
}

boolean isExplicitTagging() { return ! isImplicitTagging(); }

boolean isImplicitTagging() { return true; }
Expand Down Expand Up @@ -1459,7 +1450,9 @@ public IRubyObject to_der(final ThreadContext context) {
byte[] toDER(final ThreadContext context) throws IOException {
if ( isEOC() ) return new byte[] { 0x00, 0x00 };

if (isUniversal(context)) {
final boolean isIndefiniteLength = getInstanceVariable("@indefinite_length").isTrue();

if ("ASN1Data".equals(getClassBaseName()) && isUniversal(context)) {
// handstitch conversion
final java.io.ByteArrayOutputStream out = new ByteArrayOutputStream();
final IRubyObject value = callMethod(context, "value");
Expand Down Expand Up @@ -1686,11 +1679,6 @@ boolean isEOC() {
return false;
}

@Override
boolean isUniversal(final ThreadContext context) {
return false;
}

private boolean isNull() {
return "Null".equals(getMetaClass().getRealClass().getBaseName());
}
Expand Down Expand Up @@ -1857,27 +1845,15 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a
return this;
}

static Constructive newInfiniteConstructive(final ThreadContext context,
final String type, final IRubyObject value, final int defaultTag) {
static Constructive newInfiniteLength(final ThreadContext context,
final IRubyObject value, final int defaultTag) {
final Ruby runtime = context.runtime;

final RubyClass klass = _ASN1(context.runtime).getClass(type);
final RubyClass klass = _ASN1(context.runtime).getClass("Constructive");
final Constructive self = new Constructive(runtime, klass);

self.setInstanceVariable("@tag", runtime.newFixnum(defaultTag));
self.setInstanceVariable("@value", value);
self.setInstanceVariable("@tag_class", runtime.newSymbol("UNIVERSAL"));
self.setInstanceVariable("@tagging", context.nil);

return setInfiniteLength(context, self);
}

static Constructive setInfiniteLength(final ThreadContext context, final IRubyObject constructive) {
final Constructive instance = ((Constructive) constructive);
final IRubyObject value = instance.value(context);
value.callMethod(context, "<<", EndOfContent.newInstance(context));
instance.setInstanceVariable("@indefinite_length", context.runtime.getTrue());
return instance;
ASN1Data.newInfiniteLengthImpl(context, self, value, defaultTag, runtime.newSymbol("UNIVERSAL"));
return self;
}

private boolean rawConstructive() {
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy