Skip to content

Commit

Permalink
ASN1: #to_der in pure ruby
Browse files Browse the repository at this point in the history
  • Loading branch information
HoneyryderChuck committed Jul 12, 2024
1 parent c959729 commit 09c3fbf
Showing 1 changed file with 224 additions and 1 deletion.
225 changes: 224 additions & 1 deletion lib/openssl/asn1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@

module OpenSSL
module ASN1
INT_MAX = begin
n_bytes = [42].pack('i').size
n_bits = n_bytes * 16
2 ** (n_bits - 2) - 1
end

V_ASN1_UNIVERSAL= 0x00
V_ASN1_APPLICATION = 0x40
V_ASN1_CONTEXT_SPECIFIC = 0x80
V_ASN1_PRIVATE = 0xc0
V_ASN1_CONSTRUCTED = 0x20
V_ASN1_PRIMITIVE_TAG = 0x1f


class ASN1Data
#
# Carries the value of a ASN.1 type.
Expand Down Expand Up @@ -71,6 +85,101 @@ def initialize(value, tag, tag_class)
@tag_class = tag_class
@indefinite_length = false
end

def to_der
if @value.is_a?(Array)
cons_to_der
elsif @indefinite_length
raise ASN1Error, "indefinite length form cannot be used " \
"with primitive encoding"
else
prim_to_der
end
end

private

def cons_to_der
ary = @value.to_a
str = "".b

@value.each_with_index do |item, idx|
if @indefinite_length && item.is_a?(EndOfContent)
if idx != ary.size - 1
raise ASN1Error, "illegal EOC octets in value"
end

break
end

item = item.to_der if item.respond_to?(:to_der)

str << item
end

to_der_internal(str, true)
end

def prim_to_der
return to_der_internal(@value) unless ASN1.take_default_tag(self.class)

# TODO: how to translate this?
asn1 = ossl_asn1_get_asn1type(self)
alllen = i2d_ASN1_TYPE(asn1, NULL)

if (alllen < 0)
ASN1_TYPE_free(asn1)
raise ASN1Error, "i2d_ASN1_TYPE"
end

str = String.new(capacity: alllen)

p0 = p1 = str;
if (i2d_ASN1_TYPE(asn1, &p0) < 0)
ASN1_TYPE_free(asn1);
ossl_raise(eASN1Error, "i2d_ASN1_TYPE");
end
ASN1_TYPE_free(asn1);
ossl_str_adjust(str, p0);

j = ASN1_get_object(p1, bodylen, tag, tc, alllen)
if j & 0x80
ossl_raise(eASN1Error, "ASN1_get_object"); # should not happen
end

to_der_internal(str[(alllen - bodylen)..-1])
end

def to_der_internal(body, constructed = false)
default_tag = ASN1.take_default_tag(self.class)
body_len = body.size

if @tagging == :EXPLICIT
raise ASN1Error, "explicit tagging of unknown tag" unless default_tag

inner_len = ASN1.object_size(constructed && @indefinite_length, body_len, default_tag)
total_len = ASN1.object_size(@indefinite_length, inner_len, @tag)

# Put explicit tag
str = ASN1.put_object(constructed, @indefinite_length, inner_len, @tag, @tag_class) <<
# Append inner object
ASN1.put_object(constructed, @indefinite_length, body_len, default_tag, :UNIVERSAL)

str << body
if @indefinite_length
str << "\x00\x00\x00\x00"
end
else
total_length = ASN1.object_size(constructed && @indefinite_length, body_len, @tag)
str = ASN1.put_object(constructed, @indefinite_length, body_len, @tag, @tag_class)
str << body
if @indefinite_length
str << "\x00\x00"
end
end

str
end
end

module TaggedASN1Data
Expand Down Expand Up @@ -172,8 +281,110 @@ def initialize
end
end

module_function

# ruby port of ASN1_object_size
def object_size(indefinite_length, length, tag)
ret = 1

return -1 if length < 0

if tag >= 31
while tag > 0
tag >>= 7
ret += 1
end
end
if indefinite_length
ret += 3
else
ret += 1
if length > 127
tmplen = length
while tmplen > 0
tmplen >>= 8
ret+=1
end
end
end

return -1 if (ret >= INT_MAX - length)


ret + length
end

# ruby port of openssl ASN1_put_object
def put_object(constructed, indefinite_length, length, tag, tag_class)
str = "".b
xclass = take_asn1_tag_class(tag_class)

i = constructed ? V_ASN1_CONSTRUCTED : 0
i |= (xclass & V_ASN1_PRIVATE)

if tag < 31
str << (i | (tag & V_ASN1_PRIMITIVE_TAG)).chr

else
str << (i | V_ASN1_PRIMITIVE_TAG).chr

i = 0
ttag = tag

while ttag > 0
i += 1
ttag >>= 7
end

ttag = i

while i > 0
i -= 1
tag_str = tag & 0x7f
if (i != (ttag - 1))
tag_str |= 0x80
end
str.insert(1, tag_str.chr)
tag >>= 7
end
end

if constructed && indefinite_length
str << 0x80.chr
else
str << put_length(length)
end
str
end


def put_length(length)
raise ASN1Error, "invalid length" if length < 0

if length < 0x80
length.chr
else
i = length

if i >= 0
done = 0
else
done = -1
end

octets = "".b
begin
octets = (i & 0xff).chr << octets
i = i >> 8
end until i == done
octets

(octets.size | 0x80).chr << octets
end
end

# :nodoc:
def self.take_default_tag(klass)
def take_default_tag(klass)
tag = CLASS_TAG_MAP[klass]

return tag if tag
Expand All @@ -184,5 +395,17 @@ def self.take_default_tag(klass)

take_default_tag(sklass)
end

# from ossl_asn1.c : ossl_asn1_tag_class
def take_asn1_tag_class(tag_class)
case tag_class
when :UNIVERSAL, nil then V_ASN1_UNIVERSAL
when :APPLICATION then V_ASN1_APPLICATION
when :CONTEXT_SPECIFIC then V_ASN1_CONTEXT_SPECIFIC
when :PRIVATE then V_ASN1_PRIVATE
else
raise ASN1Error, "invalid tag class"
end
end
end
end

0 comments on commit 09c3fbf

Please sign in to comment.