diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 502be1019..dc947dcb5 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -680,30 +680,6 @@ to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) } 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, @@ -1012,11 +988,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: @@ -1065,44 +1036,6 @@ ossl_asn1prim_to_der(VALUE self) 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: * OpenSSL::ASN1::ObjectId.register(object_id, short_name, long_name) @@ -1523,7 +1456,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 +1522,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 +1552,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 +1601,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 89fa28e1f..9f67337ea 100644 --- a/lib/openssl/asn1.rb +++ b/lib/openssl/asn1.rb @@ -10,6 +10,41 @@ module OpenSSL module ASN1 + 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 + + V_ASN1_INTEGER = 2 + V_ASN1_BIT_STRING = 3 + V_ASN1_OCTET_STRING = 4 + V_ASN1_NULL = 5 + V_ASN1_OBJECT = 6 + V_ASN1_OBJECT_DESCRIPTOR = 7 + V_ASN1_EXTERNAL = 8 + V_ASN1_REAL = 9 + V_ASN1_ENUMERATED = 10 + V_ASN1_UTF8STRING = 12 + V_ASN1_SEQUENCE = 16 + V_ASN1_SET = 17 + V_ASN1_NUMERICSTRING = 18 + V_ASN1_PRINTABLESTRING = 19 + V_ASN1_T61STRING = 20 + V_ASN1_TELETEXSTRING = 20 + V_ASN1_VIDEOTEXSTRING = 21 + V_ASN1_IA5STRING = 22 + V_ASN1_UTCTIME = 23 + V_ASN1_GENERALIZEDTIME = 24 + V_ASN1_GRAPHICSTRING = 25 + V_ASN1_ISO64STRING = 26 + V_ASN1_VISIBLESTRING = 26 + V_ASN1_GENERALSTRING = 27 + V_ASN1_UNIVERSALSTRING = 28 + V_ASN1_BMPSTRING = 30 + + class ASN1Data # # Carries the value of a ASN.1 type. @@ -71,6 +106,91 @@ 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) + + String(@value) + 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_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" + end + else + 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 @@ -127,6 +247,11 @@ class Primitive < ASN1Data undef_method :indefinite_length= undef_method :infinite_length= + + + def to_der + prim_to_der + end end class Constructive < ASN1Data @@ -150,13 +275,43 @@ def each(&blk) self end + + def to_der + cons_to_der + end end - class Boolean < Primitive ; end - class Integer < Primitive ; end - class Enumerated < Primitive ; end + class Null < Primitive + def to_der + raise ASN1Error, "nil expected" unless @value.nil? + + "\x05\x00" + end + 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 + ASN1_STRING_FLAG_BITS_LEFT = 0x08 + attr_accessor :unused_bits def initialize(*) @@ -164,16 +319,157 @@ 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 << String(@value) + 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 + end + + class GeneralizedTime < Primitive 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 ? 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, 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 Boolean" if value.nil? + + value = value.to_i + + if value > 0 && value < 0x80 + data = value.chr + else + data = ASN1.integer_to_octets(value) + if value > 0 && data[0].ord > 0x7f + data = "\000".b << data + elsif value < 0 && data[0].ord < 0x80 + data = "\377".b << data + end + 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 +480,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