Skip to content

Commit eee9cd3

Browse files
committed
[compat] improve ASN.1 tagging behavior
now passing OpenSSL imported tests
1 parent bd3d3fd commit eee9cd3

File tree

2 files changed

+91
-54
lines changed

2 files changed

+91
-54
lines changed

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

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,17 @@ byte[] toDER(final ThreadContext context) throws IOException {
18411841
return toDERInternal(context, false, false, string);
18421842
}
18431843

1844+
// Special behavior: Encoding universal types with non-default 'tag'
1845+
// attribute and nil tagging method - replace the tag byte with the custom tag.
1846+
if ( !isTagged() && isUniversal(context) ) {
1847+
final IRubyObject defTag = defaultTag();
1848+
if ( !defTag.isNil() && getTag(context) != RubyNumeric.fix2int(defTag) ) {
1849+
final byte[] encoded = toASN1Primitive(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
1850+
encoded[0] = (byte) getTag(context);
1851+
return encoded;
1852+
}
1853+
}
1854+
18441855
return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
18451856
}
18461857

@@ -2033,11 +2044,18 @@ ASN1Encodable toASN1(final ThreadContext context) {
20332044
if ( isInfiniteLength() ) return super.toASN1(context);
20342045

20352046
if ( isSequence() ) {
2036-
return new DERSequence( toASN1EncodableVector(context) );
2047+
final ASN1Encodable seq = new DERSequence( toASN1EncodableVector(context) );
2048+
if ( isTagged() ) {
2049+
return new DERTaggedObject(isExplicitTagging(), getTagClass(context), getTag(context), seq);
2050+
}
2051+
return seq;
20372052
}
20382053
if ( isSet() ) {
2039-
return new DLSet( toASN1EncodableVector(context) ); // return new BERSet(values);
2040-
//return ASN1Set.getInstance(toASN1TaggedObject(context), isExplicitTagging());
2054+
final ASN1Encodable set = new DLSet( toASN1EncodableVector(context) );
2055+
if ( isTagged() ) {
2056+
return new DERTaggedObject(isExplicitTagging(), getTagClass(context), getTag(context), set);
2057+
}
2058+
return set;
20412059
}
20422060
switch ( getTag(context) ) { // "raw" Constructive ?!?
20432061
case OCTET_STRING:
@@ -2069,10 +2087,10 @@ byte[] toDER(final ThreadContext context) throws IOException {
20692087

20702088
if ( isIndefiniteLength ) {
20712089
if ( isSequence() || tagNo == SEQUENCE ) {
2072-
return sequenceToDER(context);
2090+
return applyIndefiniteTagging(context, sequenceToDER(context));
20732091
}
20742092
if ( isSet() || tagNo == SET) {
2075-
return setToDER(context);
2093+
return applyIndefiniteTagging(context, setToDER(context));
20762094
}
20772095
// "raw" Constructive
20782096
switch ( getTag(context) ) {
@@ -2094,6 +2112,18 @@ byte[] toDER(final ThreadContext context) throws IOException {
20942112
return toDERInternal(context, true, isIndefiniteLength, valueAsArray(context));
20952113
}
20962114

2115+
// Special behavior: Encoding universal types with non-default 'tag'
2116+
// attribute and nil tagging method - replace the tag byte with the custom tag,
2117+
// preserving the CONSTRUCTED bit.
2118+
if ( !isTagged() && isUniversal(context) ) {
2119+
final IRubyObject defTag = defaultTag();
2120+
if ( !defTag.isNil() && tagNo != RubyNumeric.fix2int(defTag) ) {
2121+
final byte[] encoded = toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
2122+
encoded[0] = (byte) (BERTags.CONSTRUCTED | tagNo);
2123+
return encoded;
2124+
}
2125+
}
2126+
20972127
return super.toDER(context);
20982128
}
20992129

@@ -2140,6 +2170,28 @@ private byte[] setToDER(final ThreadContext context) throws IOException {
21402170
return new BERSet(values).toASN1Primitive().getEncoded();
21412171
}
21422172

2173+
// Applies EXPLICIT or IMPLICIT tagging to an already-encoded indefinite-length
2174+
// Sequence or Set byte array. For IMPLICIT, replaces the tag byte in-place.
2175+
// For EXPLICIT, wraps the inner bytes with an outer tag + indefinite-length header
2176+
// and appends the required outer EOC (0x00 0x00).
2177+
private byte[] applyIndefiniteTagging(final ThreadContext context, final byte[] innerBytes) throws IOException {
2178+
if ( !isTagged() ) return innerBytes;
2179+
final int tag = getTag(context);
2180+
final int tagClass = getTagClass(context);
2181+
if ( isImplicitTagging() ) {
2182+
innerBytes[0] = (byte) (tagClass | BERTags.CONSTRUCTED | tag);
2183+
return innerBytes;
2184+
} else { // EXPLICIT
2185+
final ByteArrayOutputStream out = new ByteArrayOutputStream(innerBytes.length + 4);
2186+
writeDERIdentifier(tag, tagClass | BERTags.CONSTRUCTED, out);
2187+
out.write(0x80); // indefinite length
2188+
out.write(innerBytes);
2189+
out.write(0x00); // outer EOC
2190+
out.write(0x00);
2191+
return out.toByteArray();
2192+
}
2193+
}
2194+
21432195
private ASN1EncodableVector toASN1EncodableVector(final ThreadContext context) {
21442196
final ASN1EncodableVector vec = new ASN1EncodableVector();
21452197
final IRubyObject value = value(context);

src/test/ruby/test_asn1.rb

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -620,61 +620,46 @@ def test_prim_implicit_tagging
620620
encode_test B(%w{ 80 01 01 }), int
621621
int2 = OpenSSL::ASN1::Integer.new(1, 1, :IMPLICIT, :APPLICATION)
622622
encode_test B(%w{ 41 01 01 }), int2
623-
#decoded = OpenSSL::ASN1.decode(int2.to_der)
624-
# <:APPLICATION> expected but was <:UNIVERSAL>
625-
#assert_equal :APPLICATION, decoded.tag_class
626-
# <1> expected but was <2>
627-
#assert_equal 1, decoded.tag
628-
# <"\x01"> expected but was <#<OpenSSL::BN 1>>
629-
#assert_equal B(%w{ 01 }), decoded.value
630-
631-
# Special behavior: Encoding universal types with non-default 'tag'
632-
# attribute and nil tagging method.
633-
#int3 = OpenSSL::ASN1::Integer.new(1, 1)
634-
# <"\x01\x01\x01"> expected but was <"\x02\x01\x01">
635-
#encode_test B(%w{ 01 01 01 }), int3
623+
decoded = OpenSSL::ASN1.decode(int2.to_der)
624+
assert_equal :APPLICATION, decoded.tag_class
625+
assert_equal 1, decoded.tag
626+
assert_equal B(%w{ 01 }), decoded.value
627+
628+
# Special behavior: Encoding universal types with non-default 'tag' attribute and nil tagging method.
629+
int3 = OpenSSL::ASN1::Integer.new(1, 1)
630+
encode_test B(%w{ 01 01 01 }), int3
636631
end
637632

638633
def test_cons_explicit_tagging
639-
#content = [ OpenSSL::ASN1::PrintableString.new('abc') ]
640-
#seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT)
641-
# TODO: Import Issue
642-
# RuntimeError: No message available
643-
#encode_test B(%w{ A2 07 30 05 13 03 61 62 63 }), seq
644-
#seq2 = OpenSSL::ASN1::Sequence.new(content, 3, :EXPLICIT, :APPLICATION)
645-
# RuntimeError: No message available
646-
#encode_test B(%w{ 63 07 30 05 13 03 61 62 63 }), seq2
647-
648-
#content3 = [ OpenSSL::ASN1::PrintableString.new('abc'),
649-
# OpenSSL::ASN1::EndOfContent.new() ]
650-
#seq3 = OpenSSL::ASN1::Sequence.new(content3, 2, :EXPLICIT)
651-
#seq3.indefinite_length = true
652-
# RuntimeError: No message available
653-
#encode_test B(%w{ A2 80 30 80 13 03 61 62 63 00 00 00 00 }), seq3
634+
content = [ OpenSSL::ASN1::PrintableString.new('abc') ]
635+
seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT)
636+
encode_test B(%w{ A2 07 30 05 13 03 61 62 63 }), seq
637+
seq2 = OpenSSL::ASN1::Sequence.new(content, 3, :EXPLICIT, :APPLICATION)
638+
encode_test B(%w{ 63 07 30 05 13 03 61 62 63 }), seq2
639+
640+
content3 = [ OpenSSL::ASN1::PrintableString.new('abc'),
641+
OpenSSL::ASN1::EndOfContent.new() ]
642+
seq3 = OpenSSL::ASN1::Sequence.new(content3, 2, :EXPLICIT)
643+
seq3.indefinite_length = true
644+
encode_test B(%w{ A2 80 30 80 13 03 61 62 63 00 00 00 00 }), seq3
654645
end
655646

656647
def test_cons_implicit_tagging
657-
#content = [ OpenSSL::ASN1::Null.new(nil) ]
658-
#seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT)
659-
# TODO: Import Issue
660-
# <"\xA1\x02\x05\x00"> expected but was <"0\x02\x05\x00">
661-
#encode_test B(%w{ A1 02 05 00 }), seq
662-
#seq2 = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT, :APPLICATION)
663-
# <"a\x02\x05\x00"> expected but was <"0\x02\x05\x00">
664-
#encode_test B(%w{ 61 02 05 00 }), seq2
665-
666-
#content3 = [ OpenSSL::ASN1::Null.new(nil),
667-
# OpenSSL::ASN1::EndOfContent.new() ]
668-
#seq3 = OpenSSL::ASN1::Sequence.new(content3, 1, :IMPLICIT)
669-
#seq3.indefinite_length = true
670-
# <"\xA1\x80\x05\x00\x00\x00"> expected but was <"0\x80\x05\x00\x00\x00">
671-
#encode_test B(%w{ A1 80 05 00 00 00 }), seq3
672-
673-
# Special behavior: Encoding universal types with non-default 'tag'
674-
# attribute and nil tagging method.
675-
#seq4 = OpenSSL::ASN1::Sequence.new([], 1)
676-
# <"!\x00"> expected but was <"0\x00">
677-
#encode_test B(%w{ 21 00 }), seq4
648+
content = [ OpenSSL::ASN1::Null.new(nil) ]
649+
seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT)
650+
encode_test B(%w{ A1 02 05 00 }), seq
651+
seq2 = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT, :APPLICATION)
652+
encode_test B(%w{ 61 02 05 00 }), seq2
653+
654+
content3 = [ OpenSSL::ASN1::Null.new(nil),
655+
OpenSSL::ASN1::EndOfContent.new() ]
656+
seq3 = OpenSSL::ASN1::Sequence.new(content3, 1, :IMPLICIT)
657+
seq3.indefinite_length = true
658+
encode_test B(%w{ A1 80 05 00 00 00 }), seq3
659+
660+
# Special behavior: Encoding universal types with non-default 'tag' attribute and nil tagging method.
661+
seq4 = OpenSSL::ASN1::Sequence.new([], 1)
662+
encode_test B(%w{ 21 00 }), seq4
678663
end
679664

680665
def test_octet_string_constructed_tagging

0 commit comments

Comments
 (0)