lemontree55 / rasn1 Goto Github PK
View Code? Open in Web Editor NEWRuby ASN.1 library
License: MIT License
Ruby ASN.1 library
License: MIT License
It would be cool to add support for outputting parsing information as the input stream is context. This functionality would have been very useful at the start when I was implementing the initial Kerberos specification
As an example - a library that does this is https://github.com/dmendel/bindata which is a declarative library for parsing binary structure which has a tracing feature:
require 'bindata'
class Polygon < BinData::Record
endian :little
uint8 :num_points, :value => lambda { points.length }
array :points, :initial_length => :num_points do
double :x
double :y
end
end
data =
"\x03\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00" \
"\x40\x00\x00\x00\x00\x00\x00\x08\x40\x00\x00\x00\x00\x00\x00\x10" \
"\x40\x00\x00\x00\x00\x00\x00\x14\x40\x00\x00\x00\x00\x00\x00\x18" \
"\x40".b
BinData::trace_reading($stdout) do
puts Polygon.read(data)
end
Outputs:
obj.num_points => 3
obj.points[0].x => 1.0
obj.points[0].y => 2.0
obj.points[1].x => 3.0
obj.points[1].y => 4.0
obj.points[2].x => 5.0
obj.points[2].y => 6.0
{:num_points=>3, :points=>[{:x=>1.0, :y=>2.0}, {:x=>3.0, :y=>4.0}, {:x=>5.0, :y=>6.0}]}
The API could be similar for RASN1, and it would be useful for users that are parsing complex structures. At the minute a parsing failure results in a crash, and I manually delete fields until I get things to parse again successfully
Hi there,
I was just looking at using RASN1 to parse some Kerberos models from the spec, and I just wanted to document an issue I ran into where the content had the same name as the sequence:
Int32 ::= INTEGER (-2147483648..2147483647)
-- signed values representable in 32 bits
Checksum ::= SEQUENCE {
cksumtype [0] Int32,
checksum [1] OCTET STRING
}
Initially I implemented this as:
require 'rasn1'
require 'openssl'
checksum_input = (
"\x30\x19\xa0\x03\x02\x01\x07\xa1\x12\x04\x10\x9e\xf0\x84\xd6\x81" \
"\xe5\x16\x02\x32\xb1\xc3\x4e\xad\x83\x1d\x43"
).b
class CheckSum < RASN1::Model
sequence :checksum,
content: [
integer(:cksumtype, explicit: 0, constructed: true),
octet_string(:checksum, explicit: 1, constructed: true)
]
end
pp RASN1.parse(checksum_input)
pp OpenSSL::ASN1.decode(checksum_input)
pp CheckSum.parse(checksum_input)
But it gives an unexpected runtime error:
/Users/user/.rvm/gems/ruby-3.0.2@foo/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:473:in `raise_id_error': checksum: Expected CONTEXT CONSTRUCTED 0x1 (0xa1) but get UNIVERSAL CONSTRUCTED SEQUENCE (RASN1::ASN1Error)
from /Users/user/.rvm/gems/ruby-3.0.2@foo/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:431:in `check_id'
from /Users/user/.rvm/gems/ruby-3.0.2@foo/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:204:in `parse!'
from /Users/user/.rvm/gems/ruby-3.0.2foo/gems/rasn1-0.10.0/lib/rasn1/model.rb:327:in `parse!'
from /Users/user/.rvm/gems/ruby-3.0.2@foo/gems/rasn1-0.10.0/lib/rasn1/model.rb:256:in `parse'
from foo.rb:102:in `<main>'
I realized I was able to get this working by renaming the checksum
octet_string in the content:
class CheckSum < RASN1::Model
sequence :checksum,
content: [
integer(:cksumtype, explicit: 0, constructed: true),
- octet_string(:checksum, explicit: 1, constructed: true)
+ octet_string(:foo, explicit: 1, constructed: true)
]
end
Which was then parsed as expected:
(CheckSum) checksum SEQUENCE:
cksumtype CONTEXT [0] EXPLICIT INTEGER: 7
foo CONTEXT [1] EXPLICIT OCTET STRING: "\x9E\xF0\x84\xD6\x81\xE5\x16\x022\xB1\xC3N\xAD\x83\x1DC"
I wasn't sure if this is user error or not, but thought it was worth sharing
Coming from #1 , it would be nice to have a top-level parsing method, like Rasn1.parse(der)
, which just parses like OpenSSL::ASN1.parse
.
The grammar-dependent variant could be done by passing the type as an optional parameter: Rasn1.parse(der, type: my_grammar_obj)
.
The object-dependent parse (my_grammar_obj.parse(der)
) could then reuse the previous suggestion.
In this way, one could maintain parity with openssl, while enhancing it.
In some scenario, an element in a sequence may have it own explicit tag.
For example, in Kerberos:
Ticket ::= [APPLICATION 1] SEQUENCE { <-------- Ticket with class: :application, explicit: 1
tkt-vno [0] INTEGER (5),
realm [1] Realm,
sname [2] PrincipalName,
enc-part [3] EncryptedData -- EncTicketPart
}
KDC-REP ::= SEQUENCE {
pvno [0] INTEGER (5),
msg-type [1] INTEGER (11 -- AS -- | 13 -- TGS --),
padata [2] SEQUENCE OF PA-DATA OPTIONAL
-- NOTE: not empty --,
crealm [3] Realm,
cname [4] PrincipalName,
ticket [5] Ticket, # <-------- Ticket also requires its own explicit 1, class: :application
enc-part [6] EncryptedData
-- EncASRepPart or EncTGSRepPart,
-- as appropriate
}
AS-REP ::= [APPLICATION 11] KDC-REP
Example asn dump:
Application 11 (1 elem)
SEQUENCE (6 elem)
[0] (1 elem)
INTEGER 5
[1] (1 elem)
INTEGER 11
[3] (1 elem)
GeneralString DEMO.LOCAL
[4] (1 elem)
SEQUENCE (2 elem)
[0] (1 elem)
INTEGER 1
[1] (1 elem)
SEQUENCE (1 elem)
GeneralString juan
[5] (1 elem) <--------------------------- The `ticket [5] Ticket,`
Application 1 (1 elem) <------ The `Ticket ::= [APPLICATION 1] SEQUENCE {`
SEQUENCE (4 elem)
[0] (1 elem)
INTEGER 5
[1] (1 elem)
GeneralString DEMO.LOCAL
[2] (1 elem)
SEQUENCE (2 elem)
[0] (1 elem)
INTEGER 1
[1] (1 elem)
SEQUENCE (2 elem)
GeneralString krbtgt
GeneralString DEMO.LOCAL
[3] (1 elem)
SEQUENCE (3 elem)
[0] (1 elem)
INTEGER 23
[1] (1 elem)
INTEGER 2
[2] (1 elem)
OCTET STRING (190 byte) 55E745C36FA22847AB9C8613EB1DA898EC671C1F15466BE04AF24D3DF7E4317A4F152…
[6] (1 elem)
SEQUENCE (3 elem)
[0] (1 elem)
INTEGER 23
[1] (1 elem)
INTEGER 1
[2] (1 elem)
OCTET STRING (248 byte) 8007783616C56763833BF842668AEBE9FEBA82821F3D154F5B70195CE202D6A58753B…
RASN1 models should support this in a way like this:
wrapper(model(:name, Model), explicit: 1, constructed: 2)
so that the Model can also have its own application tag/id if need be, and still be wrapped as with its own explicit: 5 in the parent sequence.
Exemple:
class ExplicitTaggedSeq < Model
sequence :seq, explicit: 0, class: :application,
content: [integer(:id), integer(:extern_id)]
end
exp = ExplicitTaggedSeq.parse("\x60\x08\x30\x06\x02\x01\x01\x02\x01\x10")
exp.root.value #=> "\x02\x01\x01\x02\x01\x10"
Here, exp.root.value
should be an array containing two RASN1::Types::Integer instances.
p exp
(ExplicitTaggedSeq) seq SEQUENCE: "\x02\x01\x01\x02\x01\x10"
but should be
p exp
(ExplicitTaggedSeq) seq SEQUENCE:
id INTEGER: 1
extern_id INTEGER: 16
Currently, value is a DateTime, but DateTime is deprecated since ruby 3.0 in favor of Time
Replication steps:
require 'rasn1'
# -- Encrypted part of ticket
# EncTicketPart ::= [APPLICATION 3] SEQUENCE {
# flags [0] TicketFlags,
# key [1] EncryptionKey,
# crealm [2] Realm,
# cname [3] PrincipalName,
# transited [4] TransitedEncoding,
# authtime [5] KerberosTime,
# starttime [6] KerberosTime OPTIONAL,
# endtime [7] KerberosTime,
# renew-till [8] KerberosTime OPTIONAL,
# caddr [9] HostAddresses OPTIONAL,
# authorization-data [10] AuthorizationData OPTIONAL
# }
class EncTicketPart < RASN1::Model
sequence :EncTicketPart,
class: :application,
explicit: 3,
content: [
bit_string(:flags, explicit: 0, constructed: true, bit_length: 32),
]
end
example = EncTicketPart.new(
flags: "P\xA0\x00\x00",
)
pp example.to_der
Stack:
ruby test_case.rb
/Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/bit_string.rb:54:in `value_to_der': : bit length is not set (RASN1::ASN1Error)
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:366:in `build'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:171:in `to_der'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:364:in `build'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:171:in `to_der'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/sequence.rb:73:in `map'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/sequence.rb:73:in `value_to_der'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:366:in `build'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:171:in `to_der'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:364:in `build'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/types/base.rb:171:in `to_der'
from /Users/user/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/rasn1-0.10.0/lib/rasn1/model.rb:312:in `to_der'
from test_case.rb:30:in `<main>'
Make RASN1::Types.define_type
no more define new types in RASN1::Types
module.
May be done one of these ways:
# First option
module MyModule
# use an option (`in` here) to specify in which module the new type will be defined
RASN1::Types.define_type('uint32', from: RASN1::Types::Integer, in: self) do |value|
(value >= 0) && (value < 2**32)
end
class MyClass < RASN1::Model
sequence :seq, content: [uint32(:uint32)]
end
end
# Second option
module MyModule
# automagically define the new type in current module. I am not sure this is possible
RASN1::Types.define_type('uint32', from: RASN1::Types::Integer) do |value|
(value >= 0) && (value < 2**32)
end
class MyClass < RASN1::Model
sequence :seq, content: [uint32(:uint32)]
end
end
First of all, cool that you're doing this gem! I made this gem called netsnmp which is dependent on the openssl gem ASN1 parser, and due to the many inconsistencies of jruby's equivalent parser, can't be really considered platform-independent. Having a pure-ruby parser can definitely help in the jruby space.
I started creating a wrapper around this gem, and I've found a few pitfalls regarding compatibility. The first one would be:
All types have a name as first argument. According to specs, the way to create an Integer is by doing Integer.new(:int)
. My question is, when is this argument not :int
?. I think that this could naturally be switched to Integer.new
. The same for all the other primitive types (at least).
The other thing is whether it is reasonable to expect API parity with openssl's ASN1 lib. I see that all Types respond to #to_der
, which is nice.
Question is whether the types name match, the parsing happens in the same way, and the types have the same base API. For instance, for instantiation base types:
OpenSSL::ASN1::OctetString.new(@value)
OpenSSL::ASN1::Integer.new(@value)
OpenSSL::ASN1::Boolean.new(@value)
OpenSSL::ASN1::Null.new(nil)
for application types:
# equivalent to the base type?
OpenSSL::ASN1::ASN1Data.new(@value, 0, :APPLICATION)
# this is how you'd build an integer of value 2
OpenSSL::ASN1::ASN1Data.new(2, 2, :UNIVERSAL)
for decoding:
OpenSSL::ASN1.decode(der)
for attributes:
i = OpenSSL::ASN1::Integer.new(2)
i.value #=> 2
i.tag #=> 2
i.tagging #=> nil
i.tag_class #=> :UNIVERSAL
i.to_der #=> "\x02\x01\x02"
That's at least for me use-cases. Some of the things I can get around to with monkey-patching, except for the first one maybe.
Hello! When I parse some ASN using this gem, my UTF8Strings has some kind of few strange bytes (4, or sometimes 2) in the beginning, for example:
ruby parses as:
(ReceiptAttribute) receipt_attribute SEQUENCE:
type INTEGER: 12
version INTEGER: 1
value ANY: "\x04\x1A\x16\x182021-03-12T16:54:12+0400"
where, for example python's ContentInfo parses this value as
"2021-03-12T16:54:12+0400"
So, the portion "\x04\x1A\x16\x18" - why it's there? Every attribute has this!
Did you ever see anything like that? Would appreciate any help
I get my data from PKSC7 container from Base64 data this way:
pkcs = OpenSSL::PKCS7.new(receipt)
pkcs.verify([], OpenSSL::X509::Store.new, nil, OpenSSL::PKCS7::NOVERIFY)
payload = Receipt.parse pkcs.data
Hello!
I have such a model, that in one of the attributes of the model there is a different nested model inside. Does it make any sense? :)
Is it somehow possible to parse it with this gem? I looked everywhere, doesn't seem like.
I'd be happy to make this functionality, if that's not too hard.
hsh = { one: 1, two: 2 }
int = Integer.new(name: 'int', explicit: 3, value: 1, enum: hsh)
int.to_der #=> RASN1::EnumeratedError
int.parse!("\x83\x03\x02\x01\x01".force_encoding('BINARY')) #=> RASN1::EnumeratedError
Hi there,
I was just looking at using RASN1 to parse some Kerberos models from the spec, and I wanted to ask if it's possible to represent the following Int32/UInt32 types cleanly:
Int32 ::= INTEGER (-2147483648..2147483647)
-- signed values representable in 32 bits
UInt32 ::= INTEGER (0..4294967295)
-- unsigned 32 bit values
EncryptedData ::= SEQUENCE {
etype [0] Int32 -- EncryptionType --,
kvno [1] UInt32 OPTIONAL,
cipher [2] OCTET STRING -- ciphertext
}
For now I'm using integer(...)
, but was interested to know if it's possible to be more rigorous here?
If it helps - this is the model I've converted:
require 'rasn1'
require 'openssl'
class EncryptedData < RASN1::Model
sequence :encrypted_data,
content: [
integer(:etype, explicit: 0, constructed: true),
integer(:kvno, explicit: 1, constructed: true, optional: true),
octet_string(:cipher, explicit: 2, constructed: true)
]
end
encrypted_data_input = (
"\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34\x60\xae\x53\xa5\x0b" +
"\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d\x45\x73\x7d\x0d\x8a" +
"\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22\x79\xc3\x8d\xd3\xaf" +
"\x9f\x5e\xb7\xb8\x9b\x57\xc5\xc9\xc5\xea\x90\x89\xc3\x63\x58"
).b
puts "RASN1:"
pp RASN1.parse(encrypted_data_input)
puts "OpenSSL:"
pp OpenSSL::ASN1.decode(encrypted_data_input)
puts "EncryptedData:"
pp EncryptedData.parse(encrypted_data_input)
Any insight would be appreciated, thanks!
The ldap specification supports the concept of filters, which are defined recursively:
Filter ::= CHOICE {
and [0] SET SIZE (1..MAX) OF filter Filter,
or [1] SET SIZE (1..MAX) OF filter Filter,
not [2] Filter,
equalityMatch [3] AttributeValueAssertion,
substrings [4] SubstringFilter,
greaterOrEqual [5] AttributeValueAssertion,
lessOrEqual [6] AttributeValueAssertion,
present [7] AttributeDescription,
approxMatch [8] AttributeValueAssertion,
extensibleMatch [9] MatchingRuleAssertion,
... }
With the following model everything works as expected:
class Filter < RASN1::Model
choice :filter,
content: [
# ... rest ommitted for simplicity...
octet_string(:present, implicit: 7)
]
end
input = "\x87\x0b\x6f\x62\x6a\x65\x63\x74\x63\x6c\x61\x73\x73".b
pp Filter.parse(input)
# Result:
# (Filter) filter CHOICE:
# present CONTEXT [7] IMPLICIT OCTET STRING: "objectclass"
However, putting in the not
filter causes an issue:
require 'rasn1'
class Filter < RASN1::Model
choice :filter,
content: [
# ... rest ommitted for simplicity...
wrapper(model(:not, Filter), implicit: 2),
# ... rest ommitted for simplicity...
octet_string(:present, implicit: 7)
]
end
input = "\x87\x0b\x6f\x62\x6a\x65\x63\x74\x63\x6c\x61\x73\x73".b
pp Filter.parse(input)
# error:
# /Users/user/Documents/code/rasn1/lib/rasn1/model.rb:491:in `generate_root': stack level too deep (SystemStackError)
I'm not sure if root_options
would be the best place for this, but here's the general scenario/use case:
require 'rasn1'
class Request < RASN1::Model
sequence :BaseRequest,
content: [
integer(:msg_type, explicit: 1, constructed: true),
octet_string(:msg, explicit: 2, constructed: true)
]
end
class FooRequest < Request
root_options name: :FooRequest, # <-- override name
class: :application,
explicit: 10
end
class BarRequest < Request
root_options name: :BarRequest, # <-- override name
class: :application,
explicit: 12
end
result = FooRequest.new(
msg_type: 2,
msg: 'foo_request'
)
puts result.to_h
Current output:
{:BaseRequest=>{:msg_type=>2, :msg=>"foo_request"}}
Desired output:
- {:BaseRequest=>{:msg_type=>2, :msg=>"foo_request"}}
+ {:FooRequest=>{:msg_type=>2, :msg=>"foo_request"}}
This edgecase/requirement came up as part of Kerberos - where the spec has a base KdcReq model, and specific AsReq/TgsReq models
It'd be great to support passing the explicit/constructed options to the model
dsl, without needing to set the options on the RASN1::Model directly, i.e.:
diff --git a/test_case.rb b/test_case.rb
index e0773e06e1..41cbe2803c 100644
--- a/test_case.rb
+++ b/test_case.rb
@@ -72,8 +72,6 @@ end
# }
class EncryptionKey < RASN1::Model
sequence :EncryptionKey,
- explicit: 1,
- constructed: true,
content: [
integer(:keytype, explicit: 0, constructed: true),
octet_string(:keyvalue, explicit: 1, constructed: true),
@@ -100,7 +98,7 @@ class EncTicketPart < RASN1::Model
content: [
bit_string(:flags, explicit: 0, constructed: true, bit_length: 32),
- model(:key, EncryptionKey)
+ model(:key, EncryptionKey, explicit: 1, constructed: true)
]
end
Scenario: The Kerberos ASN1 specification defines a reusable EncryptionKey
model which can be found in multiple places within the ASN1 specification, and I'd like to define the RASN1 model once:
EncKDCRepPart ::= SEQUENCE {
key [0] EncryptionKey,
# ....
}
EncTicketPart ::= [APPLICATION 3] SEQUENCE {
# ...
key [1] EncryptionKey,
# ...
}
-- Unencrypted authenticator
Authenticator ::= [APPLICATION 2] SEQUENCE {
# ...
subkey [6] EncryptionKey OPTIONAL,
}
EncAPRepPart ::= [APPLICATION 27] SEQUENCE {
# ...
subkey [2] EncryptionKey OPTIONAL,
This is the current test case:
require 'rasn1'
enc_ticket_part_input = (
"\x63\x82\x03\x2f\x30\x82\x03\x2b\xa0\x07\x03\x05\x00\x50\xa0\x00" \
"\x00\xa1\x1b\x30\x19\xa0\x03\x02\x01\x17\xa1\x12\x04\x10\x4b\x55" \
"\x4a\x51\x43\x73\x45\x52\x57\x55\x64\x49\x4b\x48\x6b\x78\xa2\x0a" \
"\x1b\x08\x44\x57\x2e\x4c\x4f\x43\x41\x4c\xa3\x17\x30\x15\xa0\x03" \
"\x02\x01\x01\xa1\x0e\x30\x0c\x1b\x0a\x66\x61\x6b\x65\x5f\x6d\x79" \
"\x73\x71\x6c\xa4\x0b\x30\x09\xa0\x03\x02\x01\x00\xa1\x02\x04\x00" \
"\xa5\x11\x18\x0f\x32\x30\x32\x32\x30\x36\x32\x30\x31\x30\x33\x39" \
"\x34\x31\x5a\xa6\x11\x18\x0f\x32\x30\x32\x32\x30\x36\x32\x30\x31" \
"\x30\x33\x39\x34\x31\x5a\xa7\x11\x18\x0f\x32\x30\x33\x32\x30\x36" \
"\x31\x37\x31\x30\x33\x39\x34\x31\x5a\xa8\x11\x18\x0f\x32\x30\x33" \
"\x32\x30\x36\x31\x37\x31\x30\x33\x39\x34\x31\x5a\xaa\x82\x02\x83" \
"\x30\x82\x02\x7f\x30\x82\x02\x7b\xa0\x03\x02\x01\x01\xa1\x82\x02" \
"\x72\x04\x82\x02\x6e\x30\x82\x02\x6a\x30\x82\x02\x66\xa0\x04\x02" \
"\x02\x00\x80\xa1\x82\x02\x5c\x04\x82\x02\x58\x04\x00\x00\x00\x00" \
"\x00\x00\x00\x01\x00\x00\x00\xbc\x01\x00\x00\x48\x00\x00\x00\x00" \
"\x00\x00\x00\x0a\x00\x00\x00\x1e\x00\x00\x00\x08\x02\x00\x00\x00" \
"\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00\x28\x02\x00\x00\x00" \
"\x00\x00\x00\x07\x00\x00\x00\x14\x00\x00\x00\x40\x02\x00\x00\x00" \
"\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc\xac\x01\x00\x00\xcc" \
"\xcc\xcc\xcc\x6c\x5c\x00\x00\x80\x94\x11\x0c\x92\x84\xd8\x01\xff" \
"\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff\xff\xff\xff\x7f\x80" \
"\x94\x11\x0c\x92\x84\xd8\x01\x00\x00\x00\x00\x00\x00\x00\x00\xff" \
"\xff\xff\xff\xff\xff\xff\x7f\x14\x00\x14\x00\x97\xdf\x00\x00\x00" \
"\x00\x00\x00\x60\xe2\x00\x00\x00\x00\x00\x00\x84\x9d\x00\x00\x00" \
"\x00\x00\x00\x8c\x3b\x00\x00\x00\x00\x00\x00\x94\xc2\x00\x00\x00" \
"\x00\x00\x00\x7e\xc9\x00\x00\xf4\x01\x00\x00\xf4\x01\x00\x00\x01" \
"\x02\x00\x00\x05\x00\x00\x00\x72\x34\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x83\x88\x00\x00\x10\x00\x10\x00\x9c\x94\x00\x00\xa2" \
"\xd7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x02\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a" \
"\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x66\x00\x61\x00\x6b" \
"\x00\x65\x00\x5f\x00\x6d\x00\x79\x00\x73\x00\x71\x00\x6c\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x01" \
"\x02\x00\x00\x07\x00\x00\x00\x00\x02\x00\x00\x07\x00\x00\x00\x08" \
"\x02\x00\x00\x07\x00\x00\x00\x06\x02\x00\x00\x07\x00\x00\x00\x07" \
"\x02\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x44" \
"\x00\x57\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00\x41\x00\x4c\x00\x05" \
"\x00\x00\x00\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x02" \
"\x87\x5a\x55\xfd\x66\x7b\xcc\xf7\xbc\xaf\x16\xf4\x01\x00\x00\x00" \
"\x00\x00\x00\x80\x94\x11\x0c\x92\x84\xd8\x01\x14\x00\x66\x00\x61" \
"\x00\x6b\x00\x65\x00\x5f\x00\x6d\x00\x79\x00\x73\x00\x71\x00\x6c" \
"\x00\x00\x00\x76\xff\xff\xff\xe2\x58\xd7\xf5\x06\x17\x64\x9c\x77" \
"\xc6\xff\x16\x13\x16\x5d\xe3\x00\x00\x00\x00\x76\xff\xff\xff\xaf" \
"\x64\xea\x1d\x9f\xa0\x63\xe9\xb1\x04\xd2\x53\xc4\x82\xce\x61\x00" \
"\x00\x00\x00"
).b
# EncryptionKey ::= SEQUENCE {
# keytype [0] Int32 -- actually encryption type --,
# keyvalue [1] OCTET STRING
# }
class EncryptionKey < RASN1::Model
sequence :EncryptionKey,
explicit: 1,
constructed: true,
content: [
integer(:keytype, explicit: 0, constructed: true),
octet_string(:keyvalue, explicit: 1, constructed: true),
]
end
# -- Encrypted part of ticket
# EncTicketPart ::= [APPLICATION 3] SEQUENCE {
# flags [0] TicketFlags,
# key [1] EncryptionKey,
# crealm [2] Realm,
# cname [3] PrincipalName,
# transited [4] TransitedEncoding,
# authtime [5] KerberosTime,
# starttime [6] KerberosTime OPTIONAL,
# endtime [7] KerberosTime,
# renew-till [8] KerberosTime OPTIONAL,
# caddr [9] HostAddresses OPTIONAL,
# authorization-data [10] AuthorizationData OPTIONAL
# }
class EncTicketPart < RASN1::Model
sequence :EncTicketPart,
class: :application,
explicit: 3,
content: [
bit_string(:flags, explicit: 0, constructed: true, bit_length: 32),
model(:key, EncryptionKey)
]
end
pp EncTicketPart.parse(enc_ticket_part_input)
# (EncTicketPart) EncTicketPart APPLICATION [3] EXPLICIT SEQUENCE:
# flags CONTEXT [0] EXPLICIT BIT STRING: "P\xA0\x00\x00" (bit length: 32)
# (EncryptionKey) EncryptionKey CONTEXT [1] EXPLICIT SEQUENCE:
# keytype CONTEXT [0] EXPLICIT INTEGER: 23
# keyvalue CONTEXT [1] EXPLICIT OCTET STRING: "KUJQCsERWUdIKHkx"
Test case 1 extracted from #16
require 'rasn1'
require 'base64'
RASN1::Types.define_type('Uint32', from: RASN1::Types::Integer) do |value|
(value >= 0) && (value < 2 ** 32)
end
RASN1::Types.define_type('Int32', from: RASN1::Types::Integer) do |value|
(value >= -2 ** 31) && (value < 2 ** 31)
end
RASN1::Types.define_type('KerberosMicroseconds', from: RASN1::Types::Integer)
RASN1::Types.define_type('KerberosTime', from: RASN1::Types::GeneralizedTime)
RASN1::Types.define_type('KerberosFlags', from: RASN1::Types::BitString)
RASN1::Types.define_type('KerberosTicketFlags', from: RASN1::Types::KerberosFlags)
class KerberosString < RASN1::Types::IA5String
# Important: Using GraphicString's id value of 27, instead of IA5String's ID of 22
ID = 27
# Get ASN.1 type
# @return [String]
def self.type
'KerberosString'
end
end
RASN1::Types.define_type('KerberosString', from: KerberosString)
RASN1::Types.define_type('KerberosRealm', from: RASN1::Types::KerberosString)
p RASN1::Model.singleton_methods
class PrincipalName < RASN1::Model
sequence :PrincipalName,
content: [
int32(:name_type, explicit: 0, constructed: true),
sequence_of(:name_string, RASN1::Types::KerberosString, explicit: 1, constructed: true)
]
end
class KrbError < RASN1::Model
sequence :KrbError,
class: :application,
explicit: 30,
content: [
integer(:pvno, explicit: 0, constructed: true),
integer(:msg_type, explicit: 1, constructed: true),
kerberos_time(:ctime, explicit: 2, constructed: true, optional: true),
kerberos_microseconds(:cusec, explicit: 3, constructed: true, optional: true),
kerberos_time(:stime, explicit: 4, constructed: true),
kerberos_microseconds(:susec, explicit: 5, constructed: true),
uint32(:error_code, explicit: 6, constructed: true),
kerberos_realm(:crealm, explicit: 7, constructed: true, optional: true),
# vvv should not be output vvvv - asn1 spec: `cname [8] PrincipalName OPTIONAL,`
wrapper(model(:cname, PrincipalName), explicit: 8, constructed: true, optional: true),
kerberos_realm(:realm, explicit: 9, constructed: true),
wrapper(model(:sname, PrincipalName), explicit: 10, constructed: true),
kerberos_string(:e_text, explicit: 11, constructed: true, optional: true),
octet_string(:e_data, explicit: 12, constructed: true, optional: true),
]
end
valid_data =
"\x7e\x81\x8d\x30\x81\x8a\xa0\x03\x02\x01\x05\xa1" \
"\x03\x02\x01\x1e\xa4\x11\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x34" \
"\x32\x32\x34\x35\x32\x32\x5a\xa5\x05\x02\x03\x07\x5a\x47\xa6\x03" \
"\x02\x01\x18\xa9\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41" \
"\x4c\xaa\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06" \
"\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43" \
"\x41\x4c\xac\x30\x04\x2e\x30\x2c\x30\x16\xa1\x03\x02\x01\x0b\xa2" \
"\x0f\x04\x0d\x30\x0b\x30\x09\xa0\x03\x02\x01\x17\xa1\x02\x04\x00" \
"\x30\x12\xa1\x03\x02\x01\x13\xa2\x0b\x04\x09\x30\x07\x30\x05\xa0" \
"\x03\x02\x01\x17".b
# expected = {
# KrbError: {
# pvno: 5,
# msg_type: 30,
# stime: DateTime.parse('2014-12-14T22:45:22+00:00'),
# susec: 481863,
# error_code: 24,
# cname: {
# name_type: nil, name_string: []
# },
# realm: 'DEMO.LOCAL',
# sname: {
# name_type: 1,
# name_string: ['krbtgt', 'DEMO.LOCAL']
# },
# e_data: "0,0\x16\xA1\x03\x02\x01\v\xA2\x0F\x04\r0\v0\t\xA0\x03\x02\x01\x17\xA1\x02\x04\x000\x12\xA1\x03\x02\x01\x13\xA2\v\x04\t0\a0\x05\xA0\x03\x02\x01\x17".b
# }
# }
krb_error = KrbError.parse(valid_data)
p krb_error
pp krb_error.to_h
$stderr.puts "input:"
$stderr.puts Base64.encode64(valid_data)
$stderr.puts "to_der:"
$stderr.puts Base64.encode64(krb_error.to_der)
if valid_data != krb_error.to_der
raise 'failure - input der should match to_der result'
else
puts 'success!'
end
Tests fail due to the asn1 output being different to the input:
Application 30 (1 elem)
- SEQUENCE (8 elem)
+ SEQUENCE (9 elem)
[0] (1 elem)
INTEGER 5
[1] (1 elem)
INTEGER 30
[4] (1 elem)
GeneralizedTime 2014-12-14 22:45:22 UTC
[5] (1 elem)
INTEGER 481863
[6] (1 elem)
INTEGER 24
+ [8] (1 elem)
+ SEQUENCE (2 elem)
+ [0] (1 elem)
+ INTEGER 0
+ [1] (1 elem)
+ SEQUENCE (0 elem)
[9] (1 elem)
GeneralString DEMO.LOCAL
[10] (1 elem)
SEQUENCE (2 elem)
[0] (1 elem)
INTEGER 1
[1] (1 elem)
SEQUENCE (2 elem)
GeneralString krbtgt
GeneralString DEMO.LOCAL
[12] (1 elem)
OCTET STRING (46 byte) 302C3016A10302010BA20F040D300B3009A003020117A10204003012A103020113A20B…
SEQUENCE (2 elem)
SEQUENCE (2 elem)
[1] (1 elem)
INTEGER 11
[2] (1 elem)
OCTET STRING (13 byte) 300B3009A003020117A1020400
SEQUENCE (1 elem)
SEQUENCE (2 elem)
[0] (1 elem)
INTEGER 23
[1] (1 elem)
OCTET STRING (0 byte)
SEQUENCE (2 elem)
[1] (1 elem)
INTEGER 19
[2] (1 elem)
OCTET STRING (9 byte) 30073005A003020117
SEQUENCE (1 elem)
SEQUENCE (1 elem)
[0] (1 elem)
INTEGER 23
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.