From 09c3fbf953401486ca1f921e5b7001800fe39570 Mon Sep 17 00:00:00 2001 From: HoneyryderChuck Date: Thu, 11 Jul 2024 15:02:03 +0100 Subject: [PATCH] ASN1: #to_der in pure ruby --- lib/openssl/asn1.rb | 225 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 1 deletion(-) diff --git a/lib/openssl/asn1.rb b/lib/openssl/asn1.rb index 89fa28e1f..2c64053ec 100644 --- a/lib/openssl/asn1.rb +++ b/lib/openssl/asn1.rb @@ -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. @@ -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 @@ -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 @@ -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