Reputation: 463
Still a little confused with ASN1. I am parsing a RFC3161 timestamp response with ruby openssl, so I know the specification. I am pretty sure the structure is getting loaded correctly but I'm confused on how to find the pieces/keys that I want. I understand there are sequences (ordered) and sets (unordered/unique).
I see we can call asn1_object.value[0].value[0]
, but this is clumsy and I was hoping if I know the keys or headers, I can treat it like JSON or YAML and just call asn1_object['TSTInfo']['serialNumber']
. Does it not work this way?
Are we supposed to use the properties of the sequence which are expected to be in order and know their position already since we also have specifications and lengths? This is strange to me, but maybe that's because I haven't used a whole lot of TLV formats before. Unfortunately, openssl has minimal documentation, just how to load the object with OpenSSL::ASN1.decode(der)
and access a value by index. There is also traverse
method, but this does not yield header names or anything I would think to use in this case.
#<OpenSSL::ASN1::Sequence:0x000055fdee1a2c68
@indefinite_length=false,
@tag=16,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=
[#<OpenSSL::ASN1::ObjectId:0x000055fdee1a2ec0
@indefinite_length=false,
@tag=6,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value="1.2.840.113549.1.9.52">,
#<OpenSSL::ASN1::Set:0x000055fdee1a2c90
@indefinite_length=false,
@tag=17,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=
[#<OpenSSL::ASN1::Sequence:0x000055fdee1a2cb8
@indefinite_length=false,
@tag=16,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=
[#<OpenSSL::ASN1::Sequence:0x000055fdee1a2da8
@indefinite_length=false,
@tag=16,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=
@value=
[#<OpenSSL::ASN1::ObjectId:0x000055fdee1a3190
@indefinite_length=false,
@tag=6,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value="signingTime">,
#<OpenSSL::ASN1::Set:0x000055fdee1a30f0
@indefinite_length=false,
@tag=17,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=
[#<OpenSSL::ASN1::UTCTime:0x000055fdee1a3118
@indefinite_length=false,
@tag=23,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=2018-12-19 01:49:08 UTC>]>]>
Upvotes: 0
Views: 1141
Reputation: 1299
Let me clarify something here; I just spotted a contradiction.
The OP said that he knows the specification
but also mentioned that the signingTime value is two below the declaration
is strange
.
This leads me to conclude that OP means he knows that the format is due to the specification but doesn't actually know the specification itself.
Let read some of them together so you'll know-how.
Let answer the question of why does the time value is there?
The signing time was mentioned in RFC5652 section 11.3; from there, we'll know that it is a type of attribute (since it is under section 11). The structure of the attribute type is also mentioned in the same document in section 5.3
Attribute ::= SEQUENCE {
attrType OBJECT IDENTIFIER,
attrValues SET OF AttributeValue }
So it is basically a sequence of an object identifier followed by a set.
Section 11.3 also specified that the set must contain exactly 1 member.
This is exactly what are we seeing here:
[
#<OpenSSL::ASN1::ObjectId:0x000055fdee1a3190
@indefinite_length=false,
@tag=6,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value="signingTime">,
#<OpenSSL::ASN1::Set:0x000055fdee1a30f0
@indefinite_length=false,
@tag=17,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=[
#<OpenSSL::ASN1::UTCTime:0x000055fdee1a3118
@indefinite_length=false,
@tag=23,
@tag_class=:UNIVERSAL,
@tagging=nil,
@value=2018-12-19 01:49:08 UTC>
]>
]
As mentioned already, OpenSSL::ASN1.decode
decoded der into just structured ruby objects and did not takes into account the meaning of each field. So you need to read the specification and work out the fields yourself. It is tedious but not impossibly hard by any means.
Since the traverse function provided by the official library is pretty much useless, I end up implementing it myself. It is still just a working prototype, though.
class TraverseASN1
class StopIteration < ::StandardError
def initialize(result = nil)
super(nil)
@result = result
end
attr_reader :result
end
def call(
decoded,
_depth: 0,
_sequence: nil,
_parents: [],
&traversal_block
)
traversal_block.call(
decoded,
depth: _depth,
sequence: _sequence,
parents: _parents
)
value = decoded.value
return unless value.is_a?(::Array)
value.each_with_index do |sub_token, sequence|
call(
sub_token,
_depth: (_depth + 1),
_sequence: sequence,
_parents: [*_parents, decoded],
&traversal_block
)
end
end
end
And this is how I use it:
target_element = begin
TraverseASN1.new.call(asn1) do |current, depth:, parents:, **_unused_args|
next unless depth == 2
next unless current.is_a?(::OpenSSL::ASN1::ObjectId)
next unless current.value == 'pkcs7-signedData'
raise TraverseASN1::StopIteration.new(parents.last)
end
rescue TraverseASN1::StopIteration => e
e.result
end
Once you know the format, it is fairly trivial to starts hacking things.
Upvotes: 0
Reputation: 8414
Openssl.ASN1.decode(der) in the example you linked to is exploiting the cannonical nature of DER to reconstruct a structure from the wire data, and isn't using the ASN.1 schema at all. Thus whilst it can deduce what shape, composition and content of that structure from what it's reading from the wire data (especially if it was encoded with explicit tagging, which also gives data type), it has no idea what the field names would have been in the original schema (these are not in the wire format data).
If you look at the original ASN.1 schema for an RFC3161 timestamp, you can work out what fields are where.
Using ASN.1 with C/C++, Java and C# one generally uses an ASN.1 compiler to generate source code. This source code defines classes that contain fields with field names taken from the schema. Apologies, I've no idea if this is possible in Ruby's Openssl.ASN1
Implicit / Explicit.
The example you provided a link for takes a minor liberty with the implicit tagging example. It interprets the value as an integer; strictly speaking it should be interpretted as more ASN.1 to be decoded. It just so happens that those bytes are an integer.
Upvotes: 0