class RASN1::Types::Base
@abstract This is base class for all ASN.1 types.
Subclasses SHOULD define: * an ID constant defining ASN.1 BER/DER identification number, * a private method {#value_to_der} converting its {#value} to DER, * a private method {#der_to_value} converting DER into {#value}.
Define an optional value¶ ↑
An optional value may be defined using :optional
key from {#initialize}:
Integer.new(:int, optional: true)
An optional value implies:
-
while parsing, if decoded ID is not optional expected ID, no {ASN1Error} is raised, and parser tries next field,
-
while encoding, if {#value} is
nil
, this value is not encoded.
Define a default value¶ ↑
A default value may be defined using :default
key from {#initialize}:
Integer.new(:int, default: 0)
A default value implies:
-
while parsing, if decoded ID is not expected one, no {ASN1Error} is raised and parser sets default value to this ID. Then parser tries next field,
-
while encoding, if {#value} is equal to default value, this value is not encoded.
Define a tagged value¶ ↑
ASN.1 permits to define tagged values. By example:
-- context specific tag CType ::= [0] EXPLICIT INTEGER -- application specific tag AType ::= [APPLICATION 1] EXPLICIT INTEGER -- private tag PType ::= [PRIVATE 2] EXPLICIT INTEGER
These types may be defined as:
ctype = RASN1::Types::Integer.new(explicit: 0) # with explicit, default #asn1_class is :context atype = RASN1::Types::Integer.new(explicit: 1, class: :application) ptype = RASN1::Types::Integer.new(explicit: 2, class: :private)
Sometimes, an EXPLICIT type should be CONSTRUCTED. To do that, use :constructed
option:
ptype = RASN1::Types::Integer.new(explicit: 2, class: :private, constructed: true)
Implicit tagged values may also be defined:
ctype_implicit = RASN1::Types::Integer.new(implicit: 0)
@author Sylvain Daubert
Constants
- CLASSES
Allowed ASN.1 classes
- CLASS_MASK
Binary mask to get class @private
- INDEFINITE_LENGTH
Length value for indefinite length
- MULTI_OCTETS_ID
@private first octet identifier for multi-octets identifier
Attributes
@return [Symbol]
@return [Object,nil] default value, if defined
@return [String,nil]
@return [Object]
Public Class Methods
Get ASN.1 type used to encode this one @return [String]
# File lib/rasn1/types/base.rb, line 85 def self.encoded_type type end
@overload initialize(options={})
@param [Hash] options @option options [Symbol] :class ASN.1 class. Default value is +:universal+. If +:explicit+ or +:implicit:+ is defined, default value is +:context+. @option options [::Boolean] :optional define this tag as optional. Default is +false+ @option options [Object] :default default value (ASN.1 DEFAULT) @option options [Object] :value value to set @option options [::Integer] :implicit define an IMPLICIT tagged type @option options [::Integer] :explicit define an EXPLICIT tagged type @option options [::Boolean] :constructed if +true+, set type as constructed. May only be used when +:explicit+ is defined, else it is discarded. @option options [::String] :name name for this node
@overload initialize(value, options={})
@param [Object] value value to set for this ASN.1 object @param [Hash] options @option options [Symbol] :class ASN.1 class. Default value is +:universal+. If +:explicit+ or +:implicit:+ is defined, default value is +:context+. @option options [::Boolean] :optional define this value as optional. Default is +false+ @option options [Object] :default default value (ASN.1 DEFAULT) @option options [::Integer] :implicit define an IMPLICIT tagged type @option options [::Integer] :explicit define an EXPLICIT tagged type @option options [::Boolean] :constructed if +true+, set type as constructed. May only be used when +:explicit+ is defined, else it is discarded. @option options [::String] :name name for this node
# File lib/rasn1/types/base.rb, line 126 def initialize(value_or_options={}, options={}) @constructed = nil if value_or_options.is_a? Hash set_options value_or_options else set_options options @value = value_or_options end end
Parse a DER or BER string @param [String] der_or_ber string to parse @param [Hash] options @option options [Boolean] :ber if true
, parse a BER string, else a DER one @note More options are supported. See {Base#initialize}.
# File lib/rasn1/types/base.rb, line 94 def self.parse(der_or_ber, options={}) obj = self.new(options) obj.parse!(der_or_ber, ber: options[:ber]) obj end
Get ASN.1 type @return [String]
# File lib/rasn1/types/base.rb, line 77 def self.type return @type if defined? @type @type = self.to_s.gsub(/.*::/, '').gsub(/([a-z0-9])([A-Z])/, '\1 \2').upcase end
Public Instance Methods
Objects are equal if they have same class AND same DER @param [Base] other @return [Boolean]
# File lib/rasn1/types/base.rb, line 250 def ==(other) (other.class == self.class) && (other.to_der == self.to_der) end
@return [::Boolean] true
if this is a constructed type
# File lib/rasn1/types/base.rb, line 189 def constructed? (self.class < Constructed) || !!@constructed end
Say if a tagged type is explicit @return [::Boolean,nil] return nil
if not tagged, return true
if explicit, else +false+
# File lib/rasn1/types/base.rb, line 165 def explicit? !defined?(@tag) ? nil : @tag == :explicit end
Get identifier value @return [Integer]
# File lib/rasn1/types/base.rb, line 201 def id id_value end
Say if a tagged type is implicit @return [::Boolean,nil] return nil
if not tagged, return true
if implicit, else +false+
# File lib/rasn1/types/base.rb, line 172 def implicit? !defined?(@tag) ? nil : @tag == :implicit end
Used by #dup
and #clone
. Deep copy @value and @default.
# File lib/rasn1/types/base.rb, line 137 def initialize_copy(_other) @value = @value.dup @default = @default.dup end
@param [Integer] level @return [String]
# File lib/rasn1/types/base.rb, line 239 def inspect(level=0) str = common_inspect(level) str << " #{value.inspect}" str << ' OPTIONAL' if optional? str << " DEFAULT #{@default}" unless @default.nil? str end
@return [::Boolean]
# File lib/rasn1/types/base.rb, line 152 def optional? @optional end
@abstract This method SHOULD be partly implemented by subclasses to parse
data. Subclasses SHOULD respond to +#der_to_value+.
Parse a DER string. This method updates object. @param [String] der DER string @param [Boolean] ber if true
, accept BER encoding @return [Integer] total number of parsed bytes @raise [ASN1Error] error on parsing
# File lib/rasn1/types/base.rb, line 212 def parse!(der, ber: false) return 0 unless check_id(der) id_size = Types.decode_identifier_octets(der).last total_length, data = get_data(der[id_size..-1], ber) total_length += id_size if explicit? # Delegate to #explicit type to generate sub-value type = explicit_type type.value = @value type.parse!(data) @value = type.value else der_to_value(data, ber: ber) end total_length end
@return [::Boolean] true
if this is a primitive type
# File lib/rasn1/types/base.rb, line 184 def primitive? (self.class < Primitive) && !@constructed end
Say if this type is tagged or not @return [::Boolean]
# File lib/rasn1/types/base.rb, line 158 def tagged? !@tag.nil? end
@abstract This method SHOULD be partly implemented by subclasses, which
SHOULD respond to +#value_to_der+.
@return [String] DER-formated string
# File lib/rasn1/types/base.rb, line 179 def to_der build end
Get ASN.1 type @return [String]
# File lib/rasn1/types/base.rb, line 195 def type self.class.type end
Get value or default value
# File lib/rasn1/types/base.rb, line 143 def value if @value.nil? @default else @value end end
Give size in octets of encoded value @return [Integer]
# File lib/rasn1/types/base.rb, line 233 def value_size value_to_der.size end
Private Instance Methods
# File lib/rasn1/types/base.rb, line 482 def bin2hex(str) str.unpack1('H*') end
# File lib/rasn1/types/base.rb, line 341 def build if can_build? if explicit? v = explicit_type v.value = @value encoded_value = v.to_der else encoded_value = value_to_der end encode_identifier_octets << encode_size(encoded_value.size) << encoded_value else '' end end
# File lib/rasn1/types/base.rb, line 336 def can_build? !(!@default.nil? && (@value.nil? || (@value == @default))) && !(optional? && @value.nil?) end
# File lib/rasn1/types/base.rb, line 403 def check_id(der) expected_id = encode_identifier_octets real_id = der[0, expected_id.size] if real_id != expected_id if optional? @value = nil elsif !@default.nil? @value = @default else raise_id_error(der) end false else true end end
# File lib/rasn1/types/base.rb, line 458 def class_from_numeric_id(id) CLASSES.key(id & CLASS_MASK) end
# File lib/rasn1/types/base.rb, line 266 def common_inspect(level) lvl = level >= 0 ? level : 0 str = ' ' * lvl str << "#{@name} " unless @name.nil? str << "#{type}:" end
# File lib/rasn1/types/base.rb, line 471 def der2name(der) return 'no ID' if der.nil? || der.empty? asn1_class, pc, id, id_size = Types.decode_identifier_octets(der) name = +"#{asn1_class.to_s.upcase} #{pc.to_s.upcase}" type = Types.constants.map { |c| Types.const_get(c) } .select { |klass| klass < Primitive || klass < Constructed } .find { |klass| klass::ID == id } name << " #{type.nil? ? '0x%X (0x%s)' % [id, bin2hex(der[0...id_size])] : type.encoded_type}" end
# File lib/rasn1/types/base.rb, line 282 def der_to_value(der, ber: false) @value = der end
# File lib/rasn1/types/base.rb, line 362 def encode_identifier_octets id2octets.pack('C*') end
# File lib/rasn1/types/base.rb, line 388 def encode_size(size) if size >= INDEFINITE_LENGTH bytes = [] while size > 255 bytes.unshift(size & 0xff) size >>= 8 end bytes.unshift(size) bytes.unshift(INDEFINITE_LENGTH | bytes.size) bytes.pack('C*') else [size].pack('C') end end
# File lib/rasn1/types/base.rb, line 448 def explicit_type self.class.new end
# File lib/rasn1/types/base.rb, line 420 def get_data(der, ber) length = der[0, 1].unpack1('C') length_length = 0 if length == INDEFINITE_LENGTH if primitive? raise ASN1Error, "malformed #{type}: indefinite length " \ 'forbidden for primitive types' elsif ber raise NotImplementedError, 'indefinite length not supported' else raise ASN1Error, 'indefinite length forbidden in DER encoding' end elsif length < INDEFINITE_LENGTH data = der[1, length] else length_length = length & 0x7f length = der[1, length_length].unpack('C*') .reduce(0) { |len, b| (len << 8) | b } data = der[1 + length_length, length] end total_length = 1 + length total_length += length_length if length_length.positive? [total_length, data] end
# File lib/rasn1/types/base.rb, line 366 def id2octets first_octet = CLASSES[asn1_class] | pc_bit if id < MULTI_OCTETS_ID [first_octet | id] else [first_octet | MULTI_OCTETS_ID] + unsigned_to_chained_octets(id) end end
# File lib/rasn1/types/base.rb, line 356 def id_value return @id_value if defined? @id_value self.class::ID end
# File lib/rasn1/types/base.rb, line 256 def pc_bit if @constructed.nil? self.class::ASN1_PC elsif @constructed # true Constructed::ASN1_PC else # false Primitive::ASN1_PC end end
# File lib/rasn1/types/base.rb, line 452 def raise_id_error(der) msg = name.nil? ? +'' : +"#{name}: " msg << "Expected #{self2name} but get #{der2name(der)}" raise ASN1Error, msg end
# File lib/rasn1/types/base.rb, line 462 def self2name name = +"#{asn1_class.to_s.upcase} #{constructed? ? 'CONSTRUCTED' : 'PRIMITIVE'}" if implicit? || explicit? name << ' 0x%X (0x%s)' % [id, bin2hex(encode_identifier_octets)] else name << ' ' << self.class.type end end
# File lib/rasn1/types/base.rb, line 296 def set_class(asn1_class) case asn1_class when nil @asn1_class = :universal when Symbol raise ClassError unless CLASSES.key? asn1_class @asn1_class = asn1_class else raise ClassError end end
# File lib/rasn1/types/base.rb, line 313 def set_default(default) @default = default end
# File lib/rasn1/types/base.rb, line 309 def set_optional(optional) @optional = !!optional end
# File lib/rasn1/types/base.rb, line 286 def set_options(options) set_class options[:class] set_optional options[:optional] set_default options[:default] set_tag options @value = options[:value] @name = options[:name] @options = options end
handle undocumented option :tag_value
, used internally by {RASN1.parse} to parse non-universal class tags.
# File lib/rasn1/types/base.rb, line 319 def set_tag(options) if options[:explicit] @tag = :explicit @id_value = options[:explicit] @constructed = options[:constructed] elsif options[:implicit] @tag = :implicit @id_value = options[:implicit] @constructed = options[:constructed] elsif options[:tag_value] @id_value = options[:tag_value] @constructed = options[:constructed] end @asn1_class = :context if defined?(@tag) && (@asn1_class == :universal) end
Encode an unsigned integer on multiple octets. Value is encoded on bit 6-0 of each octet, bit 7(MSB) indicates wether further octets follow.
# File lib/rasn1/types/base.rb, line 378 def unsigned_to_chained_octets(value) ary = [] while value.positive? ary.unshift(value & 0x7f | 0x80) value >>= 7 end ary[-1] &= 0x7f ary end
# File lib/rasn1/types/base.rb, line 273 def value_to_der case @value when Base @value.to_der else @value.to_s end end