krypt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +82 -0
  3. data/lib/krypt.rb +49 -0
  4. data/lib/krypt/asn1.rb +3 -0
  5. data/lib/krypt/asn1/common.rb +96 -0
  6. data/lib/krypt/asn1/template.rb +257 -0
  7. data/lib/krypt/codec.rb +57 -0
  8. data/lib/krypt/codec/base64.rb +140 -0
  9. data/lib/krypt/codec/base_codec.rb +36 -0
  10. data/lib/krypt/codec/hex.rb +122 -0
  11. data/lib/krypt/digest.rb +112 -0
  12. data/lib/krypt/hmac.rb +69 -0
  13. data/lib/krypt/pkcs5.rb +1 -0
  14. data/lib/krypt/pkcs5/pbkdf2.rb +41 -0
  15. data/lib/krypt/provider.rb +35 -0
  16. data/lib/krypt/x509.rb +3 -0
  17. data/lib/krypt/x509/certificate.rb +36 -0
  18. data/lib/krypt/x509/common.rb +41 -0
  19. data/lib/krypt/x509/crl.rb +33 -0
  20. data/lib/krypt_missing.rb +32 -0
  21. data/spec/krypt-core/MEMO.txt +85 -0
  22. data/spec/krypt-core/asn1/asn1_bit_string_spec.rb +475 -0
  23. data/spec/krypt-core/asn1/asn1_boolean_spec.rb +392 -0
  24. data/spec/krypt-core/asn1/asn1_constants_spec.rb +71 -0
  25. data/spec/krypt-core/asn1/asn1_data_spec.rb +1153 -0
  26. data/spec/krypt-core/asn1/asn1_end_of_contents_spec.rb +133 -0
  27. data/spec/krypt-core/asn1/asn1_enumerated_spec.rb +458 -0
  28. data/spec/krypt-core/asn1/asn1_generalized_time_spec.rb +492 -0
  29. data/spec/krypt-core/asn1/asn1_integer_spec.rb +557 -0
  30. data/spec/krypt-core/asn1/asn1_null_spec.rb +360 -0
  31. data/spec/krypt-core/asn1/asn1_object_id_spec.rb +495 -0
  32. data/spec/krypt-core/asn1/asn1_octet_string_spec.rb +456 -0
  33. data/spec/krypt-core/asn1/asn1_parser_spec.rb +503 -0
  34. data/spec/krypt-core/asn1/asn1_pem_spec.rb +282 -0
  35. data/spec/krypt-core/asn1/asn1_sequence_spec.rb +637 -0
  36. data/spec/krypt-core/asn1/asn1_set_spec.rb +795 -0
  37. data/spec/krypt-core/asn1/asn1_utc_time_spec.rb +495 -0
  38. data/spec/krypt-core/asn1/asn1_utf8_string_spec.rb +404 -0
  39. data/spec/krypt-core/asn1/resources.rb +53 -0
  40. data/spec/krypt-core/base64/base64_spec.rb +97 -0
  41. data/spec/krypt-core/digest/digest_spec.rb +707 -0
  42. data/spec/krypt-core/hex/hex_spec.rb +102 -0
  43. data/spec/krypt-core/pem/pem_decode_spec.rb +235 -0
  44. data/spec/krypt-core/resources.rb +1 -0
  45. data/spec/krypt-core/template/template_choice_parse_spec.rb +289 -0
  46. data/spec/krypt-core/template/template_dsl_spec.rb +351 -0
  47. data/spec/krypt-core/template/template_seq_of_parse_spec.rb +64 -0
  48. data/spec/krypt-core/template/template_seq_parse_spec.rb +1241 -0
  49. data/spec/krypt/codec/base64_decoder_spec.rb +94 -0
  50. data/spec/krypt/codec/base64_encoder_spec.rb +94 -0
  51. data/spec/krypt/codec/base64_mixed_spec.rb +16 -0
  52. data/spec/krypt/codec/hex_decoder_spec.rb +94 -0
  53. data/spec/krypt/codec/hex_encoder_spec.rb +94 -0
  54. data/spec/krypt/codec/hex_mixed_spec.rb +17 -0
  55. data/spec/krypt/codec/identity_shared.rb +119 -0
  56. data/spec/krypt/hmac/hmac_spec.rb +311 -0
  57. data/spec/krypt/pkcs5/pbkdf2_spec.rb +79 -0
  58. data/spec/krypt/provider/provider_spec.rb +83 -0
  59. data/spec/res/ca-bundle.crt +11758 -0
  60. data/spec/res/certificate.cer +0 -0
  61. data/spec/res/certificate.pem +20 -0
  62. data/spec/res/multiple_certs.pem +60 -0
  63. data/spec/resources.rb +66 -0
  64. data/test/helper.rb +8 -0
  65. data/test/res/certificate.cer +0 -0
  66. data/test/resources.rb +48 -0
  67. data/test/scratch.rb +28 -0
  68. data/test/test_krypt_asn1.rb +119 -0
  69. data/test/test_krypt_parser.rb +331 -0
  70. metadata +134 -0
@@ -0,0 +1,503 @@
1
+ # encoding: US-ASCII
2
+
3
+ require 'rspec'
4
+ require 'krypt'
5
+ require_relative '../resources'
6
+ require 'stringio'
7
+
8
+ def do_and_close(io)
9
+ yield io
10
+ io.close
11
+ end
12
+
13
+ describe Krypt::ASN1::Parser do
14
+
15
+ it "can be instantiated with default constructor" do
16
+ Krypt::ASN1::Parser.new.should be_an_instance_of Krypt::ASN1::Parser
17
+ end
18
+
19
+ it "takes no arguments in its constructor" do
20
+ lambda { Krypt::ASN1::Parser.new(Object.new) }.should raise_error(ArgumentError)
21
+ end
22
+
23
+ it "should be reusable for several IOs" do
24
+ parser = Krypt::ASN1::Parser.new
25
+ io = Resources.certificate_io
26
+ do_and_close(io) { |io| parser.next(io).should_not be_nil }
27
+ io = Resources.certificate_io
28
+ do_and_close(io) { |io| parser.next(io).should_not be_nil }
29
+ end
30
+
31
+ end
32
+
33
+ describe Krypt::ASN1::Parser, "#next" do
34
+
35
+ subject { Krypt::ASN1::Parser.new }
36
+
37
+ it "returns a Header when called on an IO representing an ASN.1" do
38
+ io = Resources.certificate_io
39
+ do_and_close(io) { |io| parse_next io }
40
+ parse_next(StringIO.new(Resources.certificate))
41
+ end
42
+
43
+ it "raises an ArgumentError when called on a String. A String does not
44
+ provide internal state that would be needed for parsing." do
45
+ lambda { parse_next(Resources.certificate) }.should raise_error(ArgumentError)
46
+ end
47
+
48
+ def parse_next(io)
49
+ subject.next(io).should be_an_instance_of Krypt::ASN1::Header
50
+ end
51
+
52
+ it "raises ArgumentError if anything other than an IO is passed. Rejection
53
+ is handled depending on the presence of a #read method." do
54
+ lambda { subject.next(:sym) }.should raise_error(ArgumentError)
55
+
56
+ c = Class.new do
57
+ def initialize
58
+ @io = Resources.certificate_io
59
+ end
60
+
61
+ def read(len=nil, buf=nil)
62
+ @io.read(len, buf)
63
+ end
64
+ end
65
+
66
+ parse_next(c.new)
67
+ end
68
+
69
+ it "does not close the underlying IO after reading it" do
70
+ io = Resources.certificate_io
71
+ subject.next(io).skip_value
72
+ subject.next(io).should be_nil
73
+ io.closed?.should be_false
74
+ io.close
75
+ io.closed?.should be_true
76
+ end
77
+
78
+ it "reads nested Headers when called subsequently for constructed values" do
79
+ num_headers = 0
80
+ io = Resources.certificate_io
81
+ while header = subject.next(io)
82
+ num_headers += 1
83
+ next if header.constructed?
84
+ header.skip_value #need to consume the values
85
+ end
86
+ num_headers.should > 1
87
+ io.close
88
+ end
89
+
90
+ it "also reads singular, non-constructed values" do
91
+ io = Resources.bytes_to_io( %w{02 01 01} )
92
+ header = subject.next(io)
93
+ header.tag.should == 2
94
+ header.size.should == 1
95
+ header.header_size.should == 2
96
+ header.tag_class.should == :UNIVERSAL
97
+ header.constructed?.should be_false
98
+ header.infinite?.should be_false
99
+ header.value.should == "\x01"
100
+ end
101
+
102
+ it "yields the original contents for the header and the value of the
103
+ initial sequence" do
104
+ cert = Resources.certificate
105
+ io = StringIO.new cert
106
+ header = subject.next io
107
+ value = header.value
108
+ (header.header_size + value.size).should == cert.size
109
+ ("" << header.bytes << value).should == cert
110
+ end
111
+
112
+ it "yields the original contents for the nested headers and their values" do
113
+ cert = Resources.certificate
114
+ io = StringIO.new cert
115
+ out = ""
116
+ while header = subject.next(io)
117
+ out << header.bytes
118
+ next if header.constructed?
119
+ val = header.value
120
+ out << val unless val == nil
121
+ end
122
+ out.should == cert
123
+ end
124
+
125
+ end
126
+
127
+ describe Krypt::ASN1::Header do
128
+
129
+ it "cannot be instantiated" do
130
+ lambda { Krypt::ASN1::Header.new }.should raise_error
131
+ end
132
+
133
+ end
134
+
135
+ describe Krypt::ASN1::Header, "#tag" do
136
+
137
+ subject { Krypt::ASN1::Parser.new }
138
+
139
+ it "yields the tag of an ASN1 value, both for simple and complex tags" do
140
+ simple = Resources.bytes_to_io( %w{05 00} )
141
+ header = subject.next(simple)
142
+ header.tag.should == 5
143
+ end
144
+
145
+ it "yields the tag for complex tags with a single octet" do
146
+ complex_single_octet = %w{df 2a 01}
147
+ complex_single_octet_v = Resources.bytes_to_io(Array.new(complex_single_octet) << '00')
148
+ header = subject.next(complex_single_octet_v)
149
+ header.tag.should == 42
150
+ end
151
+
152
+ it "yields the tag for complext tags with multiple octets" do
153
+ complex_two_octets = %w{5f 82 2c 01}
154
+ complex_two_octets_v = Resources.bytes_to_io(Array.new(complex_two_octets) << '00')
155
+ header = subject.next(complex_two_octets_v)
156
+ header.tag.should == 300
157
+ end
158
+
159
+ end
160
+
161
+ describe Krypt::ASN1::Header, "#tag_class" do
162
+
163
+ it "recognizes UNIVERSAL, CONTEXT_SPEICIFIC, APPLICATION and PRIVATE" do
164
+ subject = Krypt::ASN1::Parser.new
165
+ universal = Resources.bytes_to_io( %w{05 00} )
166
+ header = subject.next(universal)
167
+ header.tag_class.should == :UNIVERSAL
168
+ context_specific = Resources.bytes_to_io( %w{81 00} )
169
+ header = subject.next(context_specific)
170
+ header.tag_class.should == :CONTEXT_SPECIFIC
171
+ private_tc = Resources.bytes_to_io( %w{df 2a 01} )
172
+ header = subject.next(private_tc)
173
+ header.tag_class.should == :PRIVATE
174
+ application = Resources.bytes_to_io( %w{5f 82 2c 01} )
175
+ header = subject.next(application)
176
+ header.tag_class.should == :APPLICATION
177
+ end
178
+
179
+ end
180
+
181
+ describe Krypt::ASN1::Header, "#constructed?" do
182
+
183
+ subject { Krypt::ASN1::Parser.new }
184
+
185
+ it "returns false for primitive values" do
186
+ evaluate(%w{05 00}, false)
187
+ evaluate(%w{81 00}, false)
188
+ evaluate(%w{df 2a 01}, false)
189
+ evaluate(%w{5f 82 2c 01}, false)
190
+ end
191
+
192
+ it "returns true for constructed values" do
193
+ evaluate(%w{30 03 02 01 01}, true)
194
+ evaluate(%w{31 03 02 01 01}, true)
195
+ evaluate(%w{30 80 02 01 01 00 00}, true)
196
+ evaluate(%w{24 80 04 01 01 00 00}, true)
197
+ end
198
+
199
+ def evaluate(bytes, expectation)
200
+ io = Resources.bytes_to_io bytes
201
+ header = subject.next io
202
+ header.constructed?.should == expectation
203
+ end
204
+
205
+ end
206
+
207
+ describe Krypt::ASN1::Header, "#infinite?" do
208
+
209
+ subject { Krypt::ASN1::Parser.new }
210
+
211
+ it "returns false for definite length values" do
212
+ evaluate(%w{05 00}, false)
213
+ evaluate(%w{81 00}, false)
214
+ evaluate(%w{df 2a 01}, false)
215
+ evaluate(%w{5f 82 2c 01}, false)
216
+ evaluate(%w{30 03 02 01 01}, false)
217
+ evaluate(%w{31 03 02 01 01}, false)
218
+ end
219
+
220
+ it "returns true for infinite length values" do
221
+ evaluate(%w{30 80 02 01 01 00 00}, true)
222
+ evaluate(%w{31 80 02 01 01 00 00}, true)
223
+ evaluate(%w{24 80 04 01 01 00 00}, true)
224
+ end
225
+
226
+ it "raises an error for infinite length primitive values" do
227
+ lambda { evaluate(%w{04 80 04 01 01 00 00}, true) }.should raise_error(Krypt::ASN1::ParseError)
228
+ end
229
+
230
+ def evaluate(bytes, expectation)
231
+ io = Resources.bytes_to_io bytes
232
+ header = subject.next io
233
+ header.infinite?.should == expectation
234
+ end
235
+
236
+ end
237
+
238
+ describe Krypt::ASN1::Header, "#size" do
239
+
240
+ subject { Krypt::ASN1::Parser.new }
241
+
242
+ it "returns the size of the value for single octet lengths" do
243
+ simple = Resources.bytes_to_io( %w{02 01 01} )
244
+ header = subject.next(simple)
245
+ header.size.should == 1
246
+ end
247
+
248
+ it "returns the size of the value for multiple octet lengths" do
249
+ complex = Resources.bytes_to_io( %w{04 82 03 e8} << ('a' * 1000))
250
+ header = subject.next complex
251
+ header.size.should == 1000
252
+ end
253
+
254
+ it "returns 0 for missing values" do
255
+ null = %w{05 00}
256
+ eoc = %w{00 00}
257
+ subject.next(Resources.bytes_to_io(null)).size.should == 0
258
+ subject.next(Resources.bytes_to_io(eoc)).size.should == 0
259
+ end
260
+
261
+ it "returns 0 for infinite length values" do
262
+ inf = %w{30 80 02 01 01 00 00}
263
+ subject.next(Resources.bytes_to_io(inf)).size.should == 0
264
+ end
265
+
266
+ end
267
+
268
+ describe Krypt::ASN1::Header, "#header_size" do
269
+
270
+ subject { Krypt::ASN1::Parser.new }
271
+
272
+ it "returns the size of the sum of tag plus length encoding
273
+ for simple tags" do
274
+ simple = Resources.bytes_to_io( %w{05 00} )
275
+ header = subject.next(simple)
276
+ header.header_size.should == 2
277
+ end
278
+
279
+ it "returns the size of the sum of tag plus length encoding
280
+ for complex tags with a single octet" do
281
+ complex_single_octet = %w{df 2a 01}
282
+ complex_single_octet_v = Resources.bytes_to_io(Array.new(complex_single_octet) << '00')
283
+ header = subject.next(complex_single_octet_v)
284
+ header.header_size.should == 3
285
+ end
286
+
287
+ it "returns the size of the sum of tag plus length encoding
288
+ for complex tags with multiple octets" do
289
+ complex_two_octets = %w{5f 82 2c 01}
290
+ complex_two_octets_v = Resources.bytes_to_io(Array.new(complex_two_octets) << '00')
291
+ header = subject.next(complex_two_octets_v)
292
+ header.header_size.should == 4
293
+ end
294
+
295
+ it "returns the size of the header for multiple octet lengths" do
296
+ complex = Resources.bytes_to_io( %w{04 82 03 e8} << ('a' * 1000))
297
+ header = subject.next complex
298
+ header.header_size.should == 4
299
+ end
300
+
301
+ end
302
+
303
+ describe Krypt::ASN1::Header, "#skip_value" do
304
+
305
+ it "skips to the end of the file when asked to skip the value of a
306
+ starting constructed value" do
307
+ skip_value(StringIO.new(Resources.certificate))
308
+ skip_value Resources.certificate_io
309
+ end
310
+
311
+ def skip_value(io)
312
+ parser = Krypt::ASN1::Parser.new
313
+ header = parser.next(io)
314
+ header.skip_value
315
+ parser.next(io).should be_nil
316
+ end
317
+
318
+ end
319
+
320
+ describe Krypt::ASN1::Header, "#value" do
321
+
322
+ subject { Krypt::ASN1::Parser.new }
323
+
324
+ it "caches the value of a header once it was read" do
325
+ io = Resources.certificate_io
326
+ begin
327
+ header = subject.next(io)
328
+ header.value.should == header.value
329
+ ensure
330
+ io.close
331
+ end
332
+ end
333
+
334
+ it "returns nil for missing values" do
335
+ null = %w{05 00}
336
+ eoc = %w{00 00}
337
+ subject.next(Resources.bytes_to_io(null)).value.should be_nil
338
+ subject.next(Resources.bytes_to_io(eoc)).value.should be_nil
339
+ end
340
+
341
+ it "has Encoding::BINARY" do
342
+ io = Resources.certificate_io
343
+ begin
344
+ subject.next(io).value.encoding.should == Encoding::BINARY
345
+ ensure
346
+ io.close
347
+ end
348
+ end
349
+
350
+ end
351
+
352
+ describe Krypt::ASN1::Header, "#value_io" do
353
+
354
+ subject { Krypt::ASN1::Parser.new }
355
+
356
+ it "returns an IO that reads the entire value of a definite sequence" do
357
+ cert = Resources.certificate_io
358
+ header = subject.next cert
359
+ (header.bytes << header.value_io.read).should == Resources.certificate
360
+ cert.close
361
+ end
362
+
363
+ it "returns an IO whose content is in Encoding::BINARY" do
364
+ cert = Resources.certificate_io
365
+ header = subject.next cert
366
+ header.value_io.read.encoding.should == Encoding::BINARY
367
+ cert.close
368
+ end
369
+
370
+ it "reads the values excluding the headers for an infinite length primitive
371
+ value by default" do
372
+ io = Resources.bytes_to_io( %w{24 80 04 01 01 04 01 02 00 00} )
373
+ expected = [%w{01 02}.join('')].pack('H*')
374
+ header = subject.next io
375
+ header.value_io.read.should == expected
376
+ end
377
+
378
+ it "reads the values including the headers for an infinite length primitive
379
+ value when passed false as a parameter" do
380
+ io = Resources.bytes_to_io( %w{24 80 04 01 01 04 01 02 00 00} )
381
+ expected = [%w{04 01 01 04 01 02 00 00}.join('')].pack('H*')
382
+ header = subject.next io
383
+ header.value_io(false).read.should == expected
384
+ end
385
+
386
+ it "reads the values excluding the headers for an infinite length sequence
387
+ by default" do
388
+ io = Resources.bytes_to_io( %w{30 80 04 01 01 04 01 02 00 00} )
389
+ expected = [%w{01 02}.join('')].pack('H*')
390
+ header = subject.next io
391
+ header.value_io.read.should == expected
392
+ end
393
+
394
+ it "reads the values including the headers for an infinite length sequence
395
+ when passed false as a parameter" do
396
+ io = Resources.bytes_to_io( %w{30 80 04 01 01 04 01 02 00 00} )
397
+ expected = [%w{04 01 01 04 01 02 00 00}.join('')].pack('H*')
398
+ header = subject.next io
399
+ header.value_io(false).read.should == expected
400
+ end
401
+
402
+ it "caches an IO if requested for a header more than once" do
403
+ io = Resources.certificate_io
404
+ header = subject.next io
405
+ header.value_io
406
+ lambda { header.value_io }.should_not raise_error
407
+ io.close
408
+ end
409
+
410
+ it "raises an error if the value of a header is requested after requesting
411
+ an IO" do
412
+ io = Resources.certificate_io
413
+ header = subject.next io
414
+ header.value_io
415
+ lambda { header.value }.should raise_error(Krypt::ASN1::ParseError)
416
+ io.close
417
+ end
418
+
419
+ it "raises an error if an IO after reading the value of the header" do
420
+ io = Resources.certificate_io
421
+ header = subject.next io
422
+ header.value
423
+ lambda { header.value_io }.should raise_error(Krypt::ASN1::ParseError)
424
+ io.close
425
+ end
426
+
427
+ it "is stateful wrt to the amount of data already read" do
428
+ io = Resources.certificate_io
429
+ header = subject.next io
430
+ header.value_io.read.should_not == ""
431
+ header.value_io.read.should == ""
432
+ io.close
433
+ end
434
+
435
+ it "returns an 'empty' IO for missing values" do
436
+ null = %w{05 00}
437
+ eoc = %w{00 00}
438
+ subject.next(Resources.bytes_to_io(null)).value_io.read.should == ""
439
+ subject.next(Resources.bytes_to_io(eoc)).value_io.read.should == ""
440
+ end
441
+
442
+ end
443
+
444
+ describe Krypt::ASN1::Header, "#bytes" do
445
+
446
+ subject { Krypt::ASN1::Parser.new }
447
+
448
+ it "returns the encoding of a parsed header" do
449
+ complex_two_octets = %w{5f 82 2c 01}
450
+ complex_two_octets_v = Resources.bytes_to_io(Array.new(complex_two_octets) << '00')
451
+ header = subject.next(complex_two_octets_v)
452
+ header.bytes.should == Resources.bytes(complex_two_octets)
453
+ end
454
+
455
+ it "returns the encoding of a simple tag" do
456
+ raw = %w{05 00}
457
+ simple = Resources.bytes_to_io(raw)
458
+ header = subject.next(simple)
459
+ header.bytes.should == Resources.bytes(raw)
460
+ end
461
+
462
+ it "returns the encoding for complex tags with a single octet" do
463
+ complex_single_octet = %w{df 2a 01}
464
+ complex_single_octet_v = Resources.bytes_to_io(Array.new(complex_single_octet) << '00')
465
+ header = subject.next(complex_single_octet_v)
466
+ header.bytes.should == Resources.bytes(complex_single_octet)
467
+ end
468
+
469
+ it "returns the encoding for complex tags with multiple octets" do
470
+ complex_two_octets = %w{5f 82 2c 01}
471
+ complex_two_octets_v = Resources.bytes_to_io(Array.new(complex_two_octets) << '00')
472
+ header = subject.next(complex_two_octets_v)
473
+ header.bytes.should == Resources.bytes(complex_two_octets)
474
+ end
475
+
476
+ it "returns the encoding for multiple octet lengths" do
477
+ raw = %w{04 82 03 e8}
478
+ complex = Resources.bytes_to_io( Array.new(raw) << ('a' * 1000))
479
+ header = subject.next complex
480
+ header.bytes.should == Resources.bytes(raw)
481
+ end
482
+
483
+ it "has Encoding::BINARY" do
484
+ raw = %w{05 00}
485
+ io = Resources.bytes_to_io(raw)
486
+ subject.next(io).bytes.encoding.should == Encoding::BINARY
487
+ end
488
+
489
+ end
490
+
491
+ describe Krypt::ASN1::Header, "#encode_to" do
492
+
493
+ it "encodes the header to an IO" do
494
+ null = %w{05 00}
495
+ io = Resources.bytes_to_io(null)
496
+ header = Krypt::ASN1::Parser.new.next io
497
+ out = StringIO.new
498
+ header.encode_to(out)
499
+ out.string.should == header.bytes
500
+ end
501
+
502
+ end
503
+