Skip to content

Commit 09c3fbf

Browse files
ASN1: #to_der in pure ruby
1 parent c959729 commit 09c3fbf

File tree

1 file changed

+224
-1
lines changed

1 file changed

+224
-1
lines changed

lib/openssl/asn1.rb

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010

1111
module OpenSSL
1212
module ASN1
13+
INT_MAX = begin
14+
n_bytes = [42].pack('i').size
15+
n_bits = n_bytes * 16
16+
2 ** (n_bits - 2) - 1
17+
end
18+
19+
V_ASN1_UNIVERSAL= 0x00
20+
V_ASN1_APPLICATION = 0x40
21+
V_ASN1_CONTEXT_SPECIFIC = 0x80
22+
V_ASN1_PRIVATE = 0xc0
23+
V_ASN1_CONSTRUCTED = 0x20
24+
V_ASN1_PRIMITIVE_TAG = 0x1f
25+
26+
1327
class ASN1Data
1428
#
1529
# Carries the value of a ASN.1 type.
@@ -71,6 +85,101 @@ def initialize(value, tag, tag_class)
7185
@tag_class = tag_class
7286
@indefinite_length = false
7387
end
88+
89+
def to_der
90+
if @value.is_a?(Array)
91+
cons_to_der
92+
elsif @indefinite_length
93+
raise ASN1Error, "indefinite length form cannot be used " \
94+
"with primitive encoding"
95+
else
96+
prim_to_der
97+
end
98+
end
99+
100+
private
101+
102+
def cons_to_der
103+
ary = @value.to_a
104+
str = "".b
105+
106+
@value.each_with_index do |item, idx|
107+
if @indefinite_length && item.is_a?(EndOfContent)
108+
if idx != ary.size - 1
109+
raise ASN1Error, "illegal EOC octets in value"
110+
end
111+
112+
break
113+
end
114+
115+
item = item.to_der if item.respond_to?(:to_der)
116+
117+
str << item
118+
end
119+
120+
to_der_internal(str, true)
121+
end
122+
123+
def prim_to_der
124+
return to_der_internal(@value) unless ASN1.take_default_tag(self.class)
125+
126+
# TODO: how to translate this?
127+
asn1 = ossl_asn1_get_asn1type(self)
128+
alllen = i2d_ASN1_TYPE(asn1, NULL)
129+
130+
if (alllen < 0)
131+
ASN1_TYPE_free(asn1)
132+
raise ASN1Error, "i2d_ASN1_TYPE"
133+
end
134+
135+
str = String.new(capacity: alllen)
136+
137+
p0 = p1 = str;
138+
if (i2d_ASN1_TYPE(asn1, &p0) < 0)
139+
ASN1_TYPE_free(asn1);
140+
ossl_raise(eASN1Error, "i2d_ASN1_TYPE");
141+
end
142+
ASN1_TYPE_free(asn1);
143+
ossl_str_adjust(str, p0);
144+
145+
j = ASN1_get_object(p1, bodylen, tag, tc, alllen)
146+
if j & 0x80
147+
ossl_raise(eASN1Error, "ASN1_get_object"); # should not happen
148+
end
149+
150+
to_der_internal(str[(alllen - bodylen)..-1])
151+
end
152+
153+
def to_der_internal(body, constructed = false)
154+
default_tag = ASN1.take_default_tag(self.class)
155+
body_len = body.size
156+
157+
if @tagging == :EXPLICIT
158+
raise ASN1Error, "explicit tagging of unknown tag" unless default_tag
159+
160+
inner_len = ASN1.object_size(constructed && @indefinite_length, body_len, default_tag)
161+
total_len = ASN1.object_size(@indefinite_length, inner_len, @tag)
162+
163+
# Put explicit tag
164+
str = ASN1.put_object(constructed, @indefinite_length, inner_len, @tag, @tag_class) <<
165+
# Append inner object
166+
ASN1.put_object(constructed, @indefinite_length, body_len, default_tag, :UNIVERSAL)
167+
168+
str << body
169+
if @indefinite_length
170+
str << "\x00\x00\x00\x00"
171+
end
172+
else
173+
total_length = ASN1.object_size(constructed && @indefinite_length, body_len, @tag)
174+
str = ASN1.put_object(constructed, @indefinite_length, body_len, @tag, @tag_class)
175+
str << body
176+
if @indefinite_length
177+
str << "\x00\x00"
178+
end
179+
end
180+
181+
str
182+
end
74183
end
75184

76185
module TaggedASN1Data
@@ -172,8 +281,110 @@ def initialize
172281
end
173282
end
174283

284+
module_function
285+
286+
# ruby port of ASN1_object_size
287+
def object_size(indefinite_length, length, tag)
288+
ret = 1
289+
290+
return -1 if length < 0
291+
292+
if tag >= 31
293+
while tag > 0
294+
tag >>= 7
295+
ret += 1
296+
end
297+
end
298+
if indefinite_length
299+
ret += 3
300+
else
301+
ret += 1
302+
if length > 127
303+
tmplen = length
304+
while tmplen > 0
305+
tmplen >>= 8
306+
ret+=1
307+
end
308+
end
309+
end
310+
311+
return -1 if (ret >= INT_MAX - length)
312+
313+
314+
ret + length
315+
end
316+
317+
# ruby port of openssl ASN1_put_object
318+
def put_object(constructed, indefinite_length, length, tag, tag_class)
319+
str = "".b
320+
xclass = take_asn1_tag_class(tag_class)
321+
322+
i = constructed ? V_ASN1_CONSTRUCTED : 0
323+
i |= (xclass & V_ASN1_PRIVATE)
324+
325+
if tag < 31
326+
str << (i | (tag & V_ASN1_PRIMITIVE_TAG)).chr
327+
328+
else
329+
str << (i | V_ASN1_PRIMITIVE_TAG).chr
330+
331+
i = 0
332+
ttag = tag
333+
334+
while ttag > 0
335+
i += 1
336+
ttag >>= 7
337+
end
338+
339+
ttag = i
340+
341+
while i > 0
342+
i -= 1
343+
tag_str = tag & 0x7f
344+
if (i != (ttag - 1))
345+
tag_str |= 0x80
346+
end
347+
str.insert(1, tag_str.chr)
348+
tag >>= 7
349+
end
350+
end
351+
352+
if constructed && indefinite_length
353+
str << 0x80.chr
354+
else
355+
str << put_length(length)
356+
end
357+
str
358+
end
359+
360+
361+
def put_length(length)
362+
raise ASN1Error, "invalid length" if length < 0
363+
364+
if length < 0x80
365+
length.chr
366+
else
367+
i = length
368+
369+
if i >= 0
370+
done = 0
371+
else
372+
done = -1
373+
end
374+
375+
octets = "".b
376+
begin
377+
octets = (i & 0xff).chr << octets
378+
i = i >> 8
379+
end until i == done
380+
octets
381+
382+
(octets.size | 0x80).chr << octets
383+
end
384+
end
385+
175386
# :nodoc:
176-
def self.take_default_tag(klass)
387+
def take_default_tag(klass)
177388
tag = CLASS_TAG_MAP[klass]
178389

179390
return tag if tag
@@ -184,5 +395,17 @@ def self.take_default_tag(klass)
184395

185396
take_default_tag(sklass)
186397
end
398+
399+
# from ossl_asn1.c : ossl_asn1_tag_class
400+
def take_asn1_tag_class(tag_class)
401+
case tag_class
402+
when :UNIVERSAL, nil then V_ASN1_UNIVERSAL
403+
when :APPLICATION then V_ASN1_APPLICATION
404+
when :CONTEXT_SPECIFIC then V_ASN1_CONTEXT_SPECIFIC
405+
when :PRIVATE then V_ASN1_PRIVATE
406+
else
407+
raise ASN1Error, "invalid tag class"
408+
end
409+
end
187410
end
188411
end

0 commit comments

Comments
 (0)