如何在Ruby中解析ASN1

时间:2018-12-18 20:13:57

标签: ruby serialization openssl tlv asn1

仍然与ASN1混淆。我正在使用ruby openssl解析RFC3161时间戳响应,因此我知道该规范。我很确定结构可以正确加载,但是我对如何找到想要的片段/键感到困惑。我知道有序列(有序)和集合(无序/唯一)。

我看到我们可以调用asn1_object.value[0].value[0],但这很笨拙,我希望如果我知道键或标头,就可以将其视为JSON或YAML并只需调用 {{ 1}}。 这种方式行不通吗?

我们是否应该使用序列的属性,因为我们也有规范和长度,所以它们应该顺序排列并知道它们的位置?这对我来说很奇怪,但这也许是因为我之前没有使用过很多TLV格式。不幸的是,openssl的文档很少,只是如何使用asn1_object['TSTInfo']['serialNumber']加载对象并按索引访问值。还有OpenSSL::ASN1.decode(der)方法,但这不会产生标头名称或在这种情况下我想使用的任何名称。

traverse

2 个答案:

答案 0 :(得分:0)

在您链接到的示例中,

Openssl.ASN1.decode(der)正在利用DER的规范性质从线路数据中重建结构,并且根本不使用ASN.1模式。因此,虽然它可以根据从导线数据中读取的内容推断出该结构的形状,组成和内容(特别是如果使用显式标记进行编码,并且还给出了数据类型),但是却不知道字段名称是什么在原始模式中(这些不在有线格式数据中)。

如果您查看RFC3161时间戳的原始ASN.1模式,则可以确定哪些字段在哪里。

将ASN.1与C / C ++,Java和C#一起使用通常使用ASN.1编译器生成源代码。此源代码定义了包含具有字段名称的类的类,这些字段名称取自架构。抱歉,我不知道这在Ruby的Openssl.ASN1中是否可行

隐式/显式。

您提供的链接示例在隐式标记示例中略有自由。它将值解释为整数;严格来说,应将其解释为要解码的更多ASN.1。碰巧这些字节是整数。

答案 1 :(得分:0)

让我在这里澄清一些事情;我刚刚发现了一个矛盾。 OP 说他 knows the specification 但也提到 the signingTime value is two below the declarationstrange
这使我得出结论,OP 意味着他知道格式是由规范引起的,但实际上并不知道规范本身。

让我们一起阅读其中的一些内容,以便您掌握诀窍。
让我们回答一下为什么会有时间价值?

签署时间在RFC5652 section 11.3中提到;从那里,我们将知道它是一种属性(因为它在第 11 节下)。属性类型的结构在section 5.3

的同一个文档中也有提到 <块引用>

属性::=序列{
attrType 对象标识符,
attrValues 属性值集 }

所以它基本上是一个对象标识符后跟一个集合的序列。
Section 11.3 还指定该集合必须恰好包含 1 个成员。

这正是我们在这里看到的:

[
  #<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>
    ]>
]

如前所述,OpenSSL::ASN1.decode 将 der 解码为结构化的 ruby​​ 对象,并没有考虑每个字段的含义。所以你需要阅读规范并自己计算字段。这很乏味,但无论如何也不是不可能的。

由于官方库提供的遍历函数几乎没用,最后还是自己实现了。不过,它仍然只是一个工作原型。

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

这就是我使用它的方式:

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

一旦你知道了格式,就可以开始破解了。