From cfcbe9ebebe4a44f5def4367f38e4933a664987c 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 --- ext/openssl/ossl_asn1.c | 389 +--------------------------------------- lib/openssl/asn1.rb | 297 +++++++++++++++++++++++++++++- 2 files changed, 297 insertions(+), 389 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 1676defd..dbccb3c1 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -185,68 +185,6 @@ static VALUE sym_IMPLICIT, sym_EXPLICIT; static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE; static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINDEFINITE_LENGTH, sivUNUSED_BITS; -/* - * Ruby to ASN1 converters - */ -static ASN1_BOOLEAN -obj_to_asn1bool(VALUE obj) -{ - if (NIL_P(obj)) - ossl_raise(rb_eTypeError, "Can't convert nil into Boolean"); - - return RTEST(obj) ? 0xff : 0x0; -} - -static ASN1_INTEGER* -obj_to_asn1int(VALUE obj) -{ - return num_to_asn1integer(obj, NULL); -} - -static ASN1_BIT_STRING* -obj_to_asn1bstr(VALUE obj, long unused_bits) -{ - ASN1_BIT_STRING *bstr; - - if (unused_bits < 0 || unused_bits > 7) - ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ - "the range 0 to 7"); - StringValue(obj); - if(!(bstr = ASN1_BIT_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj)); - bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */ - bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; - - return bstr; -} - -static ASN1_STRING* -obj_to_asn1str(VALUE obj) -{ - ASN1_STRING *str; - - StringValue(obj); - if(!(str = ASN1_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LENINT(obj)); - - return str; -} - -static ASN1_NULL* -obj_to_asn1null(VALUE obj) -{ - ASN1_NULL *null; - - if(!NIL_P(obj)) - ossl_raise(eASN1Error, "nil expected"); - if(!(null = ASN1_NULL_new())) - ossl_raise(eASN1Error, NULL); - - return null; -} - static ASN1_OBJECT* obj_to_asn1obj(VALUE obj) { @@ -260,50 +198,6 @@ obj_to_asn1obj(VALUE obj) return a1obj; } -static ASN1_UTCTIME * -obj_to_asn1utime(VALUE time) -{ - time_t sec; - ASN1_UTCTIME *t; - - int off_days; - - ossl_time_split(time, &sec, &off_days); - if (!(t = ASN1_UTCTIME_adj(NULL, sec, off_days, 0))) - ossl_raise(eASN1Error, NULL); - - return t; -} - -static ASN1_GENERALIZEDTIME * -obj_to_asn1gtime(VALUE time) -{ - time_t sec; - ASN1_GENERALIZEDTIME *t; - - int off_days; - - ossl_time_split(time, &sec, &off_days); - if (!(t = ASN1_GENERALIZEDTIME_adj(NULL, sec, off_days, 0))) - ossl_raise(eASN1Error, NULL); - - return t; -} - -static ASN1_STRING* -obj_to_asn1derstr(VALUE obj) -{ - ASN1_STRING *a1str; - VALUE str; - - str = ossl_to_der(obj); - if(!(a1str = ASN1_STRING_new())) - ossl_raise(eASN1Error, NULL); - ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LENINT(str)); - - return a1str; -} - /* * DER to Ruby converters */ @@ -495,100 +389,6 @@ enum {ossl_asn1_info_size = (sizeof(ossl_asn1_info)/sizeof(ossl_asn1_info[0]))}; static VALUE class_tag_map; -static int ossl_asn1_default_tag(VALUE obj); - -static ASN1_TYPE * -ossl_asn1_get_asn1type(VALUE obj) -{ - ASN1_TYPE *ret; - VALUE value, rflag; - void *ptr; - typedef void free_func_type(void *); - free_func_type *free_func; - int tag; - - tag = ossl_asn1_default_tag(obj); - value = ossl_asn1_get_value(obj); - switch(tag){ - case V_ASN1_BOOLEAN: - ptr = (void*)(VALUE)obj_to_asn1bool(value); - free_func = NULL; - break; - case V_ASN1_INTEGER: /* FALLTHROUGH */ - case V_ASN1_ENUMERATED: - ptr = obj_to_asn1int(value); - free_func = (free_func_type *)ASN1_INTEGER_free; - break; - case V_ASN1_BIT_STRING: - rflag = rb_attr_get(obj, sivUNUSED_BITS); - ptr = obj_to_asn1bstr(value, NUM2INT(rflag)); - free_func = (free_func_type *)ASN1_BIT_STRING_free; - break; - case V_ASN1_NULL: - ptr = obj_to_asn1null(value); - free_func = (free_func_type *)ASN1_NULL_free; - break; - case V_ASN1_OCTET_STRING: /* FALLTHROUGH */ - case V_ASN1_UTF8STRING: /* FALLTHROUGH */ - case V_ASN1_NUMERICSTRING: /* FALLTHROUGH */ - case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */ - case V_ASN1_T61STRING: /* FALLTHROUGH */ - case V_ASN1_VIDEOTEXSTRING: /* FALLTHROUGH */ - case V_ASN1_IA5STRING: /* FALLTHROUGH */ - case V_ASN1_GRAPHICSTRING: /* FALLTHROUGH */ - case V_ASN1_ISO64STRING: /* FALLTHROUGH */ - case V_ASN1_GENERALSTRING: /* FALLTHROUGH */ - case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */ - case V_ASN1_BMPSTRING: - ptr = obj_to_asn1str(value); - free_func = (free_func_type *)ASN1_STRING_free; - break; - case V_ASN1_OBJECT: - ptr = obj_to_asn1obj(value); - free_func = (free_func_type *)ASN1_OBJECT_free; - break; - case V_ASN1_UTCTIME: - ptr = obj_to_asn1utime(value); - free_func = (free_func_type *)ASN1_TIME_free; - break; - case V_ASN1_GENERALIZEDTIME: - ptr = obj_to_asn1gtime(value); - free_func = (free_func_type *)ASN1_TIME_free; - break; - case V_ASN1_SET: /* FALLTHROUGH */ - case V_ASN1_SEQUENCE: - ptr = obj_to_asn1derstr(obj); - free_func = (free_func_type *)ASN1_STRING_free; - break; - default: - ossl_raise(eASN1Error, "unsupported ASN.1 type"); - } - if(!(ret = OPENSSL_malloc(sizeof(ASN1_TYPE)))){ - if(free_func) free_func(ptr); - ossl_raise(eASN1Error, "ASN1_TYPE alloc failure"); - } - memset(ret, 0, sizeof(ASN1_TYPE)); - ASN1_TYPE_set(ret, tag, ptr); - - return ret; -} - -static int -ossl_asn1_default_tag(VALUE obj) -{ - VALUE tmp_class, tag; - - tmp_class = CLASS_OF(obj); - while (!NIL_P(tmp_class)) { - tag = rb_hash_lookup(class_tag_map, tmp_class); - if (tag != Qnil) - return NUM2INT(tag); - tmp_class = rb_class_superclass(tmp_class); - } - - return -1; -} - static int ossl_asn1_tag(VALUE obj) { @@ -601,24 +401,6 @@ ossl_asn1_tag(VALUE obj) return NUM2INT(tag); } -static int -ossl_asn1_tag_class(VALUE obj) -{ - VALUE s; - - s = ossl_asn1_get_tag_class(obj); - if (NIL_P(s) || s == sym_UNIVERSAL) - return V_ASN1_UNIVERSAL; - else if (s == sym_APPLICATION) - return V_ASN1_APPLICATION; - else if (s == sym_CONTEXT_SPECIFIC) - return V_ASN1_CONTEXT_SPECIFIC; - else if (s == sym_PRIVATE) - return V_ASN1_PRIVATE; - else - ossl_raise(eASN1Error, "invalid tag class"); -} - static VALUE ossl_asn1_class2sym(int tc) { @@ -632,79 +414,6 @@ ossl_asn1_class2sym(int tc) return sym_UNIVERSAL; } -static VALUE -to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) -{ - int encoding = constructed ? indef_len ? 2 : 1 : 0; - int tag_class = ossl_asn1_tag_class(self); - int tag_number = ossl_asn1_tag(self); - int default_tag_number = ossl_asn1_default_tag(self); - int body_length, total_length; - VALUE str; - unsigned char *p; - - body_length = RSTRING_LENINT(body); - if (ossl_asn1_get_tagging(self) == sym_EXPLICIT) { - int inner_length, e_encoding = indef_len ? 2 : 1; - - if (default_tag_number == -1) - ossl_raise(eASN1Error, "explicit tagging of unknown tag"); - - inner_length = ASN1_object_size(encoding, body_length, default_tag_number); - total_length = ASN1_object_size(e_encoding, inner_length, tag_number); - str = rb_str_new(NULL, total_length); - p = (unsigned char *)RSTRING_PTR(str); - /* Put explicit tag */ - ASN1_put_object(&p, e_encoding, inner_length, tag_number, tag_class); - /* Append inner object */ - ASN1_put_object(&p, encoding, body_length, default_tag_number, V_ASN1_UNIVERSAL); - memcpy(p, RSTRING_PTR(body), body_length); - p += body_length; - if (indef_len) { - ASN1_put_eoc(&p); /* For inner object */ - ASN1_put_eoc(&p); /* For wrapper object */ - } - } - else { - total_length = ASN1_object_size(encoding, body_length, tag_number); - str = rb_str_new(NULL, total_length); - p = (unsigned char *)RSTRING_PTR(str); - ASN1_put_object(&p, encoding, body_length, tag_number, tag_class); - memcpy(p, RSTRING_PTR(body), body_length); - p += body_length; - if (indef_len) - ASN1_put_eoc(&p); - } - assert(p - (unsigned char *)RSTRING_PTR(str) == total_length); - return str; -} - -static VALUE ossl_asn1prim_to_der(VALUE); -static VALUE ossl_asn1cons_to_der(VALUE); -/* - * call-seq: - * asn1.to_der => DER-encoded String - * - * Encodes this ASN1Data into a DER-encoded String value. The result is - * DER-encoded except for the possibility of indefinite length forms. - * Indefinite length forms are not allowed in strict DER, so strictly speaking - * the result of such an encoding would be a BER-encoding. - */ -static VALUE -ossl_asn1data_to_der(VALUE self) -{ - VALUE value = ossl_asn1_get_value(self); - - if (rb_obj_is_kind_of(value, rb_cArray)) - return ossl_asn1cons_to_der(self); - else { - if (RTEST(ossl_asn1_get_indefinite_length(self))) - ossl_raise(eASN1Error, "indefinite length form cannot be used " \ - "with primitive encoding"); - return ossl_asn1prim_to_der(self); - } -} - static VALUE int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag, VALUE tc, long *num_read) @@ -1012,96 +721,6 @@ ossl_asn1_decode_all(VALUE self, VALUE obj) return ary; } -static VALUE -ossl_asn1eoc_to_der(VALUE self) -{ - return rb_str_new("\0\0", 2); -} - -/* - * call-seq: - * asn1.to_der => DER-encoded String - * - * See ASN1Data#to_der for details. - */ -static VALUE -ossl_asn1prim_to_der(VALUE self) -{ - ASN1_TYPE *asn1; - long alllen, bodylen; - unsigned char *p0, *p1; - int j, tag, tc, state; - VALUE str; - - if (ossl_asn1_default_tag(self) == -1) { - str = ossl_asn1_get_value(self); - return to_der_internal(self, 0, 0, StringValue(str)); - } - - asn1 = ossl_asn1_get_asn1type(self); - alllen = i2d_ASN1_TYPE(asn1, NULL); - if (alllen < 0) { - ASN1_TYPE_free(asn1); - ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); - } - str = ossl_str_new(NULL, alllen, &state); - if (state) { - ASN1_TYPE_free(asn1); - rb_jump_tag(state); - } - p0 = p1 = (unsigned char *)RSTRING_PTR(str); - if (i2d_ASN1_TYPE(asn1, &p0) < 0) { - ASN1_TYPE_free(asn1); - ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); - } - ASN1_TYPE_free(asn1); - ossl_str_adjust(str, p0); - - /* Strip header since to_der_internal() wants only the payload */ - j = ASN1_get_object((const unsigned char **)&p1, &bodylen, &tag, &tc, alllen); - if (j & 0x80) - ossl_raise(eASN1Error, "ASN1_get_object"); /* should not happen */ - - return to_der_internal(self, 0, 0, rb_str_drop_bytes(str, alllen - bodylen)); -} - -/* - * call-seq: - * asn1.to_der => DER-encoded String - * - * See ASN1Data#to_der for details. - */ -static VALUE -ossl_asn1cons_to_der(VALUE self) -{ - VALUE ary, str; - long i; - int indef_len; - - indef_len = RTEST(ossl_asn1_get_indefinite_length(self)); - ary = rb_convert_type(ossl_asn1_get_value(self), T_ARRAY, "Array", "to_a"); - str = rb_str_new(NULL, 0); - for (i = 0; i < RARRAY_LEN(ary); i++) { - VALUE item = RARRAY_AREF(ary, i); - - if (indef_len && rb_obj_is_kind_of(item, cASN1EndOfContent)) { - if (i != RARRAY_LEN(ary) - 1) - ossl_raise(eASN1Error, "illegal EOC octets in value"); - - /* - * EOC is not really part of the content, but we required to add one - * at the end in the past. - */ - break; - } - - item = ossl_to_der_if_possible(item); - StringValue(item); - rb_str_append(str, item); - } - - return to_der_internal(self, 1, indef_len, str); -} /* * call-seq: @@ -1523,7 +1142,6 @@ Init_ossl_asn1(void) * puts int2.value # => 1 */ cASN1Data = rb_define_class_under(mASN1, "ASN1Data", rb_cObject); - rb_define_method(cASN1Data, "to_der", ossl_asn1data_to_der, 0); /* Document-class: OpenSSL::ASN1::Primitive * @@ -1590,7 +1208,7 @@ Init_ossl_asn1(void) * prim_zero_tagged_explicit = .new(value, 0, :EXPLICIT) */ cASN1Primitive = rb_define_class_under(mASN1, "Primitive", cASN1Data); - rb_define_method(cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0); + // rb_define_method(cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0); /* Document-class: OpenSSL::ASN1::Constructive * @@ -1620,7 +1238,6 @@ Init_ossl_asn1(void) * set = OpenSSL::ASN1::Set.new( [ int, str ] ) */ cASN1Constructive = rb_define_class_under(mASN1,"Constructive", cASN1Data); - rb_define_method(cASN1Constructive, "to_der", ossl_asn1cons_to_der, 0); #define OSSL_ASN1_DEFINE_CLASS(name, super) \ do{\ @@ -1670,7 +1287,9 @@ do{\ rb_define_alias(cASN1ObjectId, "long_name", "ln"); rb_define_method(cASN1ObjectId, "==", ossl_asn1obj_eq, 1); - rb_define_method(cASN1EndOfContent, "to_der", ossl_asn1eoc_to_der, 0); + // rb_define_method(cASN1ObjectId, "to_der", ossl_asn1prim_to_der, 0); + // rb_define_method(cASN1UTCTime, "to_der", ossl_asn1prim_to_der, 0); + // rb_define_method(cASN1GeneralizedTime, "to_der", ossl_asn1prim_to_der, 0); class_tag_map = rb_hash_new(); rb_hash_aset(class_tag_map, cASN1EndOfContent, INT2NUM(V_ASN1_EOC)); diff --git a/lib/openssl/asn1.rb b/lib/openssl/asn1.rb index 89fa28e1..85910616 100644 --- a/lib/openssl/asn1.rb +++ b/lib/openssl/asn1.rb @@ -71,6 +71,92 @@ def initialize(value, tag, tag_class) @tag_class = tag_class @indefinite_length = false end + + # + # call-seq: + # asn1.to_der => DER-encoded String + # + # Encodes this ASN1Data into a DER-encoded String value. The result is + # DER-encoded except for the possibility of indefinite length forms. + # Indefinite length forms are not allowed in strict DER, so strictly speaking + # the result of such an encoding would be a BER-encoding. + # + 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 + to_der_internal(der_value) + end + end + + def der_value + raise TypeError, "no implicit conversion of #{self.class} into String" unless @value.respond_to?(:to_str) + + @value.to_str.b + 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 + to_der_internal(der_value) + end + + def to_der_internal(body, constructed = false) + default_tag = ASN1.take_default_tag(self.class) + body.force_encoding(Encoding::BINARY) + body_len = body.size + + if @tagging == :EXPLICIT + raise ASN1Error, "explicit tagging of unknown tag" unless default_tag + + inner_obj = ASN1.put_object(constructed, @indefinite_length, body_len, default_tag, :UNIVERSAL) + + inner_len = body_len + inner_obj.size + + + # Put explicit tag + str = ASN1.put_object(true, @indefinite_length, inner_len, @tag, @tag_class) << inner_obj + # Append inner object + + + str << body + if @indefinite_length + str << "\x00\x00\x00\x00".b + end + else + str = ASN1.put_object(constructed, @indefinite_length, body_len, @tag, @tag_class) + str << body + if @indefinite_length + str << "\x00\x00".b + end + end + + str + end end module TaggedASN1Data @@ -127,6 +213,11 @@ class Primitive < ASN1Data undef_method :indefinite_length= undef_method :infinite_length= + + + def to_der + prim_to_der + end end class Constructive < ASN1Data @@ -150,11 +241,37 @@ def each(&blk) self end + + def to_der + cons_to_der + end + end + + class Null < Primitive + def der_value + "".b + end end - class Boolean < Primitive ; end - class Integer < Primitive ; end - class Enumerated < Primitive ; end + class Boolean < Primitive + def der_value + raise TypeError, "Can't convert nil into Boolean" if @value.nil? + + @value ? "\xff".b : "\x00".b + end + end + + class Integer < Primitive + def der_value + ASN1.put_integer(@value) + end + end + + class Enumerated < Primitive + def der_value + ASN1.put_integer(@value) + end + end class BitString < Primitive attr_accessor :unused_bits @@ -164,16 +281,176 @@ def initialize(*) @unused_bits = 0 end + + def der_value + if @unused_bits < 0 || @unused_bits > 7 + raise ASN1Error, "unused_bits for a bitstring value must be in " \ + "the range 0 to 7" + end + + return "\x00".b if @value.empty? + + @unused_bits.chr << super + end + end + + class OctetString < Primitive + end + + class UTF8String < Primitive + end + + class NumericString < Primitive + end + + class PrintableString < Primitive + end + + class T61String < Primitive + end + + class VideotexString < Primitive + end + + class IA5String < Primitive + end + + class GraphicString < Primitive + end + + class ISO64String < Primitive + end + + class GeneralString < Primitive + end + + class UniversalString < Primitive + end + + class BMPString < Primitive + end + + class ObjectId < Primitive + def der_value + value = oid.split(".").map(&:to_i) + + return (40 * value[0]).chr if value.length == 1 + + [value[0] * 40 + value[1], *value[2..]].pack("w*") + end + end + + class UTCTime < Primitive + FORMAT = "%y%m%d%H%M%SZ".b.freeze + + def der_value + value = if @value.is_a?(Time) + @value + else + Time.at(Integer(@value)) + end + + value.utc.strftime(FORMAT) + end + end + + class GeneralizedTime < Primitive + FORMAT = "%Y%m%d%H%M%SZ".b.freeze + def der_value + value = if @value.is_a?(Time) + @value + else + Time.at(Integer(@value)) + end + + value.utc.strftime(FORMAT) + end end class EndOfContent < ASN1Data def initialize super("", 0, :UNIVERSAL) end + + def to_der + "\x00\x00".b + end + end + + class Set < Constructive + end + + class Sequence < Constructive + + end + + module_function + + # 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 ? 0x20 : 0 + i |= (xclass & 0xc0) # PRIVATE + + if tag < 31 + str << (i | tag).chr + + else + str << [i | 0x1f, tag].pack("Cw") + end + + if constructed && indefinite_length + str << 0x80.chr + else + str << put_length(length) + end + str + end + + + def put_length(length) + if length < 0x80 + length.chr + else + data = integer_to_octets(length) + (data.size | 0x80).chr << data + end + end + + def put_integer(value) + raise TypeError, "Can't convert nil into OpenSSL::BN" if value.nil? + + value = value.to_bn + if value >= 0 + data = value.to_s(2) + data.prepend("\x00".b) if data.empty? || data.getbyte(0) >= 0x80 + else + value = (1.to_bn << (value.num_bits + 7) / 8 * 8) + value + data = value.to_s(2) + data.prepend("\xff".b) if data.empty? || data.getbyte(0) < 0x80 + end + + data + end + + def integer_to_octets(i) + 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 end # :nodoc: - def self.take_default_tag(klass) + def take_default_tag(klass) tag = CLASS_TAG_MAP[klass] return tag if tag @@ -184,5 +461,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 0x00 + when :APPLICATION then 0x40 + when :CONTEXT_SPECIFIC then 0x80 + when :PRIVATE then 0xc0 + else + raise ASN1Error, "invalid tag class" + end + end end end