Class: Krypt::ASN1::ASN1Data

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
ext/krypt/core/krypt_asn1.c,
ext/krypt/core/krypt_asn1.c

Overview

The top-level class representing any ASN.1 object. When parsed by ASN1.decode, tagged values are always represented by an instance of ASN1Data.

The role of ASN1Data for parsing tagged values

When encoding an ASN.1 type it is inherently clear what original type (e.g. INTEGER, OCTET STRING etc.) this value has, regardless of its tagging. But opposed to the time an ASN.1 type is to be encoded, when parsing them it is not possible to deduce the “real type” of tagged values. This is why tagged values are generally parsed into ASN1Data instances, but with a different outcome for implicit and explicit tagging.

Example of a parsed implicitly tagged value

An implicitly 1-tagged INTEGER value will be parsed as an ASN1Data with

  • tag equal to 1

  • tag_class equal to :CONTEXT_SPECIFIC

  • value equal to a String that carries the raw encoding of the INTEGER.

This implies that a subsequent decoding step is required to completely decode implicitly tagged values.

Example of a parsed explicitly tagged value

An explicitly 1-tagged INTEGER value will be parsed as an ASN1Data with

  • tag equal to 1

  • tag_class equal to :CONTEXT_SPECIFIC

  • value equal to an Array with one single element, an instance of Krypt::ASN1::Integer, i.e. the inner element is the non-tagged primitive value, and the tagging is represented in the outer ASN1Data

Example - Decoding an implicitly tagged INTEGER

int = Krypt::ASN1::Integer.new(1, 0, :CONTEXT_SPECIFIC) # implicit 0-tagged
seq = Krypt::ASN1::Sequence.new( [int] )
der = seq.to_der
asn1 = Krypt::ASN1.decode(der)
# pp asn1 => #<Krypt::ASN1::Sequence:0x87326e0
#              @infinite_length=false,
#              @tag=16,
#              @tag_class=:UNIVERSAL>
# pp asn1.value => [#<Krypt::ASN1::ASN1Data:0x87326f4
#                   @infinite_length=false,
#                   @tag=0,
#                   @tag_class=:CONTEXT_SPECIFIC>]
# pp asn1.value[0].value => "\x01"
raw_int = asn1.value[0]
# manually rewrite tag and tag class to make it an UNIVERSAL value
raw_int.tag = OpenSSL::ASN1::INTEGER
raw_int.tag_class = :UNIVERSAL
int2 = Krypt::ASN1.decode(raw_int)
puts int2.value # => 1

Example - Decoding an explicitly tagged INTEGER

int = Krypt::ASN1::Integer.new(1)
data = Krypt::ASN1Data.new([int], 0, :CONTEXT_SPECIFIC) # explicit 0-tagged
seq = Krypt::ASN1::Sequence.new( [data] )
der = seq.to_der
asn1 = Krypt::ASN1.decode(der)
# pp asn1 => #<Krypt::ASN1::Sequence:0x87326e0
#              @infinite_length=false,
#              @tag=16,
#              @tag_class=:UNIVERSAL>
# pp asn1.value => [#<Krypt::ASN1::ASN1Data:0x87326f4
#                   @infinite_length=false,
#                   @tag=0,
#                   @tag_class=:CONTEXT_SPECIFIC>]
# pp asn1.value[0].value => [#<Krypt::ASN1::Integer:0x85bf308
#                            @infinite_length=false,
#                            @tag=2,
#                            @tag_class=:UNIVERSAL>]
int2 = asn1.value[0].value[0]
puts int2.value # => 1

Direct Known Subclasses

Constructive, Primitive

Instance Method Summary collapse

Constructor Details

#new(value, tag, tag_class) ⇒ ASN1Data

  • value: the value to be associated. See Primitive for the mappings

between ASN.1 types and Ruby types.

  • tag: a Number representing this value’s tag.

  • tag_class: a Symbol representing one of the four valid tag classes

:UNIVERSAL, :CONTEXT_SPECIFIC, :APPLICATION or :PRIVATE.

Creates an ASN1Data from scratch.



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'ext/krypt/core/krypt_asn1.c', line 359

static VALUE
krypt_asn1_data_initialize(VALUE self, VALUE value, VALUE vtag, VALUE vtag_class)
{
    ID stag_class;
    int tag, tag_class, is_constructed;
    krypt_asn1_data *data;

    int_validate_tag_and_class(vtag, vtag_class);
    tag = NUM2INT(vtag);
    stag_class = SYM2ID(vtag_class);
    if (stag_class == sKrypt_TC_EXPLICIT)
	rb_raise(eKryptASN1Error, "Explicit tagging is only supported for explicit UNIVERSAL sub classes of ASN1Data");
    if (stag_class == sKrypt_TC_UNIVERSAL && tag > 30)
	rb_raise(eKryptASN1Error, "Tag too large for UNIVERSAL tag class");
    if ((tag_class = krypt_asn1_tag_class_for_id(stag_class)) == KRYPT_ERR)
        rb_raise(eKryptASN1Error, "Unknown tag class");
    is_constructed = rb_respond_to(value, sKrypt_ID_EACH);
    
    int_asn1_data_initialize(self, tag, tag_class, is_constructed, 0);

    int_asn1_data_get(self, data);
    data->update_cb = int_asn1_data_update_cb;

    int_asn1_data_set_tag(self, vtag);
    int_asn1_data_set_tag_class(self, vtag_class);
    int_asn1_data_set_infinite_length(self, Qfalse);
    int_asn1_data_set_value(self, value);

    int_asn1_data_set_modified(data, 1); /* newly created is modified by default */

    return self;
}

Instance Method Details

#<=>(b) ⇒ -1 | 0 | +1

ASN1Data includes the Comparable module.

<=> compares two instances of ASN1Data by comparing the bytes of their encoding. The order applied is SET order, i.e. a < b iff tag of a < tag of b. If tags are equal, SET OF order is applied, a lexicographical byte order. Element order is decided based on the first byte where two elements differ, the lower byte indicates the lower element.

If two elements differ in length, but are equal up to the last byte of the smaller element, the smaller element is the lower one.

Example

Given the following SET of values

[
  Krypt::ASN1::OctetString.new("a"),
  Krypt::ASN1::Null.new,
  Krypt::ASN1::Boolean.new(true),
  Krypt::ASN1::Integer.new(1)
]

the implied SET order is

[
  Krypt::ASN1::Boolean.new(true),
  Krypt::ASN1::Integer.new(1)
  Krypt::ASN1::OctetString.new("a"),
  Krypt::ASN1::Null.new,
]

Given the following byte representations of OCTET STRINGS,

[ "\x04\x06\aaabaa", "\x04\x01b", "\x04\x06aaabba", "\x04\x04aaab" ]

the SET OF order implied is

[ "\x04\x01b", "\x04\x04aaab", "\x04\x06aaabaa", "\x04\x06aaabba" ]

Returns:

  • (-1 | 0 | +1)


1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
# File 'ext/krypt/core/krypt_asn1.c', line 1096

static VALUE
krypt_asn1_data_cmp(VALUE a, VALUE b)
{
    VALUE vs1, vs2;
    int result;

    vs1 = krypt_asn1_data_to_der(a);
    if (!rb_respond_to(b, sKrypt_ID_TO_DER)) return Qnil;
    vs2 = krypt_to_der(b);

    if(krypt_asn1_cmp_set_of((uint8_t *) RSTRING_PTR(vs1), (size_t) RSTRING_LEN(vs1),
	                     (uint8_t *) RSTRING_PTR(vs2), (size_t) RSTRING_LEN(vs2), &result) == KRYPT_ERR) {
	krypt_error_raise(eKryptASN1Error, "Error while comparing values");
    }
    return INT2NUM(result);
}

#encode_to(io) ⇒ self

  • io: an IO-like object supporting IO#write

Encodes this ASN1Data into a DER-encoded String value by writing the contents to an IO-like object. Newly created ASN1Data are DER-encoded except for the possibility of infinite length encodings. If a value with BER encoding was parsed and is not modified, the BER encoding will be preserved when encoding it again.

Returns:

  • (self)


967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
# File 'ext/krypt/core/krypt_asn1.c', line 967

static VALUE
krypt_asn1_data_encode_to(VALUE self, VALUE io)
{
    binyo_outstream *out;
    krypt_asn1_data *data;
    int result;

    int_asn1_data_get(self, data);

    out = binyo_outstream_new_value(io);
    result = int_asn1_encode_to(out, data, self);
    binyo_outstream_free(out);
    if (result == KRYPT_ERR)
	krypt_error_raise(eKryptASN1Error, "Error while encoding value");
    return self;
}

#infinite_lengthBoolean

Returns either true or false, depending on whether the value is to be or was encoded using infinite length. See ASN1Data#infinite_length= for details.

Returns:

  • (Boolean)


748
749
750
751
752
# File 'ext/krypt/core/krypt_asn1.c', line 748

static VALUE
krypt_asn1_data_get_inf_length(VALUE self)
{
    return int_asn1_data_get_infinite_length(self);
}

#infinite_length=(bool) ⇒ Boolean

  • bool: either true or false, depending on whether the value shall be

encoded using infinite length encoding or not

Set a Boolean indicating whether the encoding shall be infinite length or not. In DER, every value has a finite length associated with it. But in scenarios where large amounts of data need to be transferred, it might be desirable to have some kind of streaming support available. For example, huge OCTET STRINGs are preferably sent in smaller-sized chunks, each at a time. This is possible in BER by setting the length bytes of an encoding to zero and thus indicating that the following value will be sent in chunks. Infinite length encodings are always constructed. The end of such a stream of chunks is indicated by sending a EndOfContents value. SETs and SEQUENCEs may use an infinite length encoding, but also primitive types such as e.g. OCTET STRINGS or BIT STRINGS may leverage this functionality (cf. ITU-T X.690).

Returns:

  • (Boolean)


776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
# File 'ext/krypt/core/krypt_asn1.c', line 776

static VALUE
krypt_asn1_data_set_inf_length(VALUE self, VALUE inf_length)
{
    krypt_asn1_data *data;
    krypt_asn1_header *header;
    int new_inf;

    int_asn1_data_get(self, data);

    header = data->object->header;
    new_inf = RTEST(inf_length) ? 1 : 0;
    if (header->is_infinite == new_inf)
	return inf_length;

    header->is_infinite = new_inf;
    int_invalidate_length(header);
    
    int_asn1_data_set_modified(data, 1);
    int_asn1_data_set_infinite_length(self, new_inf ? Qtrue : Qfalse);

    return inf_length;
}

#tagObject

#tag=(number) ⇒ Number

  • number: a Number representing the tag number of this ASN1Data.

Must not be nil.

Returns:

  • (Number)


632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'ext/krypt/core/krypt_asn1.c', line 632

static VALUE
krypt_asn1_data_set_tag(VALUE self, VALUE tag)
{
    krypt_asn1_data *data;
    krypt_asn1_header *header;
    int new_tag;

    int_asn1_data_get(self, data);

    header = data->object->header;
    new_tag = NUM2INT(tag);
    if (header->tag == new_tag)
	return tag;

    header->tag = new_tag;
    int_invalidate_tag(header);
    if (data->update_cb)
	data->update_cb(data);

    int_asn1_data_set_modified(data, 1);
    int_asn1_data_set_tag(self, tag);

    return tag;
}

#tag_classSymbol

Returns a Symbol representing the tag class of this ASN1Data. Never nil. See ASN1Data for possible values.

Returns:

  • (Symbol)


664
665
666
667
668
# File 'ext/krypt/core/krypt_asn1.c', line 664

static VALUE
krypt_asn1_data_get_tag_class(VALUE self)
{
    return int_asn1_data_get_tag_class(self);
}

#tag_class=(sym) ⇒ Symbol

  • sym: A Symbol representing the tag class of this ASN1Data.

Must not be nil. See ASN1Data for possible values.

Returns:

  • (Symbol)


704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
# File 'ext/krypt/core/krypt_asn1.c', line 704

static VALUE
krypt_asn1_data_set_tag_class(VALUE self, VALUE tag_class)
{
    krypt_asn1_data *data;
    krypt_asn1_header *header;
    int new_tag_class;
    ID new_tc, old_tc;

    int_asn1_data_get(self, data);

    new_tc = SYM2ID(tag_class);
    old_tc = SYM2ID(int_asn1_data_get_tag_class(self));
    if (new_tc == old_tc)
	return tag_class;
    if (new_tc == sKrypt_TC_EXPLICIT && data->default_tag == -1)
	rb_raise(eKryptASN1Error, "Cannot explicitly tag value with unknown default tag");

    header = data->object->header;
    if ((new_tag_class = krypt_asn1_tag_class_for_id(new_tc)) == KRYPT_ERR)
        rb_raise(eKryptASN1Error, "Cannot set tag class");

    header->tag_class = new_tag_class;
    int_invalidate_tag(header);

    if (data->update_cb)
	data->update_cb(data);

    if (int_asn1_handle_explicit_tagging(self, data, new_tc) == KRYPT_ERR)
	rb_raise(eKryptASN1Error, "Tagging explicitly failed");

    int_asn1_data_set_modified(data, 1);
    int_asn1_data_set_tag_class(self, tag_class);

    return tag_class;
}

#to_derDER-/BER-encoded String

Encodes this ASN1Data into a DER-encoded String value. Newly created ASN1Data are DER-encoded except for the possibility of infinite length encodings. If a value with BER encoding was parsed and is not modified, the BER encoding will be preserved when encoding it again.

Returns:

  • (DER-/BER-encoded String)


1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
# File 'ext/krypt/core/krypt_asn1.c', line 1038

static VALUE
krypt_asn1_data_to_der(VALUE self)
{
    krypt_asn1_data *data;
    krypt_asn1_object *object;

    int_asn1_data_get(self, data);
    object = data->object;

    if (object->bytes && object->header->tag_bytes && object->header->length_bytes)
	return int_asn1_data_to_der_cached(data->object);
    else
	return int_asn1_data_to_der_non_cached(data, self);
}

#valueObject

Obtain the value of an ASN1Data. Please see Constructive and Primitive docs for the mappings between ASN.1 data types and Ruby classes.



841
842
843
844
845
846
847
# File 'ext/krypt/core/krypt_asn1.c', line 841

static VALUE
krypt_asn1_data_get_value(VALUE self)
{
    if (int_asn1_decode_value(self) == KRYPT_ERR)
	krypt_error_raise(eKryptASN1Error, "Error while decoding value");
    return int_asn1_data_get_value(self);
}

#value=(value) ⇒ Object

Set the value of an ASN1Data. Please see Constructive and Primitive docs for the mappings between ASN.1 data types and Ruby classes.



857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
# File 'ext/krypt/core/krypt_asn1.c', line 857

static VALUE
krypt_asn1_data_set_value(VALUE self, VALUE value)
{
    krypt_asn1_data *data;
    krypt_asn1_object *object;
    int is_constructed;

    int_asn1_data_get(self, data);
    int_asn1_data_set_value(self, value);

    /* Free data that is now stale */
    object = data->object;
    int_invalidate_value(object);    
    is_constructed = rb_respond_to(value, sKrypt_ID_EACH);
    if (object->header->is_constructed != is_constructed) {
	object->header->is_constructed = is_constructed;
	int_invalidate_tag(object->header);
	data->codec = int_codec_for(data->object);
    }

    int_asn1_data_set_modified(data, 1);

    return value;
}