fernet 1.4 → 1.5

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.
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .rbenv-version
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "1.9.3"
5
+ - "1.9.2"
6
+ - rbx-19mode
7
+ - "1.8.7"
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+
11
+ script: bundle exec rspec -b spec
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ platforms :jruby do
4
+ gem 'jruby-openssl', '~> 0.7.7'
5
+ end
6
+
3
7
  # Specify your gem's dependencies in fernet.gemspec
4
8
  gemspec
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # Fernet
2
2
 
3
- Fernet allows you to easily generate and verify HMAC based authentication
4
- tokens for issuing API requests between remote servers.
3
+ [![Build Status](https://secure.travis-ci.org/hgmnz/fernet.png)](http://travis-ci.org/hgmnz/fernet)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/hgmnz/fernet)
5
+
6
+ Fernet allows you to easily generate and verify **HMAC based authentication
7
+ tokens** for issuing API requests between remote servers. It also **encrypts**
8
+ data by default, so it can be used to transmit secure messages over the wire.
5
9
 
6
10
  ![Fernet](http://f.cl.ly/items/2d0P3d26271O3p2v253u/photo.JPG)
7
11
 
@@ -55,8 +59,8 @@ Otherwise, `verified` will be false, and you should deny the request with an
55
59
  HTTP 401, for example.
56
60
 
57
61
  The `Fernet.verify` method can be awkward if extracting the plain text data is
58
- required. For this case, a `verifier` can be requested that makes more
59
- pleasent:
62
+ required. For this case, a `verifier` can be requested that makes that
63
+ use case more pleasent:
60
64
 
61
65
  ```ruby
62
66
  verifier = Fernet.verifier(secret, token)
@@ -15,7 +15,5 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Fernet::VERSION
17
17
 
18
- gem.add_dependency "yajl-ruby"
19
-
20
18
  gem.add_development_dependency "rspec"
21
19
  end
@@ -3,6 +3,7 @@ require 'fernet/generator'
3
3
  require 'fernet/verifier'
4
4
  require 'fernet/secret'
5
5
  require 'fernet/configuration'
6
+ require 'fernet/okjson'
6
7
 
7
8
  if RUBY_VERSION == '1.8.7'
8
9
  require 'shim/base64'
@@ -1,5 +1,4 @@
1
1
  require 'base64'
2
- require 'yajl'
3
2
  require 'openssl'
4
3
  require 'date'
5
4
 
@@ -16,13 +15,13 @@ module Fernet
16
15
 
17
16
  def generate
18
17
  yield self if block_given?
19
- data.merge!(:issued_at => DateTime.now)
18
+ data.merge!(:issued_at => DateTime.now.to_s)
20
19
 
21
20
  if encrypt?
22
21
  iv = encrypt_data!
23
22
  @payload = "#{base64(data)}|#{base64(iv)}"
24
23
  else
25
- @payload = base64(Yajl::Encoder.encode(data))
24
+ @payload = base64(Fernet::OkJson.encode(stringify_hash_keys(data)))
26
25
  end
27
26
 
28
27
  mac = OpenSSL::HMAC.hexdigest('sha256', payload, signing_key)
@@ -47,7 +46,7 @@ module Fernet
47
46
  iv = cipher.random_iv
48
47
  cipher.iv = iv
49
48
  cipher.key = encryption_key
50
- @data = cipher.update(Yajl::Encoder.encode(data)) + cipher.final
49
+ @data = cipher.update(Fernet::OkJson.encode(stringify_hash_keys(data))) + cipher.final
51
50
  iv
52
51
  end
53
52
 
@@ -67,5 +66,11 @@ module Fernet
67
66
  @encrypt
68
67
  end
69
68
 
69
+ def stringify_hash_keys(hash)
70
+ hash.inject({}) do |result, (k, v)|
71
+ result[k.to_s] = v
72
+ result
73
+ end
74
+ end
70
75
  end
71
76
  end
@@ -0,0 +1,596 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2011, 2012 Keith Rarick
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ # See https://github.com/kr/okjson for updates.
24
+
25
+ require 'stringio'
26
+
27
+ # Some parts adapted from
28
+ # http://golang.org/src/pkg/json/decode.go and
29
+ # http://golang.org/src/pkg/utf8/utf8.go
30
+ module Fernet::OkJson
31
+ extend self
32
+
33
+
34
+ # Decodes a json document in string s and
35
+ # returns the corresponding ruby value.
36
+ # String s must be valid UTF-8. If you have
37
+ # a string in some other encoding, convert
38
+ # it first.
39
+ #
40
+ # String values in the resulting structure
41
+ # will be UTF-8.
42
+ def decode(s)
43
+ ts = lex(s)
44
+ v, ts = textparse(ts)
45
+ if ts.length > 0
46
+ raise Error, 'trailing garbage'
47
+ end
48
+ v
49
+ end
50
+
51
+
52
+ # Parses a "json text" in the sense of RFC 4627.
53
+ # Returns the parsed value and any trailing tokens.
54
+ # Note: this is almost the same as valparse,
55
+ # except that it does not accept atomic values.
56
+ def textparse(ts)
57
+ if ts.length < 0
58
+ raise Error, 'empty'
59
+ end
60
+
61
+ typ, _, val = ts[0]
62
+ case typ
63
+ when '{' then objparse(ts)
64
+ when '[' then arrparse(ts)
65
+ else
66
+ raise Error, "unexpected #{val.inspect}"
67
+ end
68
+ end
69
+
70
+
71
+ # Parses a "value" in the sense of RFC 4627.
72
+ # Returns the parsed value and any trailing tokens.
73
+ def valparse(ts)
74
+ if ts.length < 0
75
+ raise Error, 'empty'
76
+ end
77
+
78
+ typ, _, val = ts[0]
79
+ case typ
80
+ when '{' then objparse(ts)
81
+ when '[' then arrparse(ts)
82
+ when :val,:str then [val, ts[1..-1]]
83
+ else
84
+ raise Error, "unexpected #{val.inspect}"
85
+ end
86
+ end
87
+
88
+
89
+ # Parses an "object" in the sense of RFC 4627.
90
+ # Returns the parsed value and any trailing tokens.
91
+ def objparse(ts)
92
+ ts = eat('{', ts)
93
+ obj = {}
94
+
95
+ if ts[0][0] == '}'
96
+ return obj, ts[1..-1]
97
+ end
98
+
99
+ k, v, ts = pairparse(ts)
100
+ obj[k] = v
101
+
102
+ if ts[0][0] == '}'
103
+ return obj, ts[1..-1]
104
+ end
105
+
106
+ loop do
107
+ ts = eat(',', ts)
108
+
109
+ k, v, ts = pairparse(ts)
110
+ obj[k] = v
111
+
112
+ if ts[0][0] == '}'
113
+ return obj, ts[1..-1]
114
+ end
115
+ end
116
+ end
117
+
118
+
119
+ # Parses a "member" in the sense of RFC 4627.
120
+ # Returns the parsed values and any trailing tokens.
121
+ def pairparse(ts)
122
+ (typ, _, k), ts = ts[0], ts[1..-1]
123
+ if typ != :str
124
+ raise Error, "unexpected #{k.inspect}"
125
+ end
126
+ ts = eat(':', ts)
127
+ v, ts = valparse(ts)
128
+ [k, v, ts]
129
+ end
130
+
131
+
132
+ # Parses an "array" in the sense of RFC 4627.
133
+ # Returns the parsed value and any trailing tokens.
134
+ def arrparse(ts)
135
+ ts = eat('[', ts)
136
+ arr = []
137
+
138
+ if ts[0][0] == ']'
139
+ return arr, ts[1..-1]
140
+ end
141
+
142
+ v, ts = valparse(ts)
143
+ arr << v
144
+
145
+ if ts[0][0] == ']'
146
+ return arr, ts[1..-1]
147
+ end
148
+
149
+ loop do
150
+ ts = eat(',', ts)
151
+
152
+ v, ts = valparse(ts)
153
+ arr << v
154
+
155
+ if ts[0][0] == ']'
156
+ return arr, ts[1..-1]
157
+ end
158
+ end
159
+ end
160
+
161
+
162
+ def eat(typ, ts)
163
+ if ts[0][0] != typ
164
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
165
+ end
166
+ ts[1..-1]
167
+ end
168
+
169
+
170
+ # Scans s and returns a list of json tokens,
171
+ # excluding white space (as defined in RFC 4627).
172
+ def lex(s)
173
+ ts = []
174
+ while s.length > 0
175
+ typ, lexeme, val = tok(s)
176
+ if typ == nil
177
+ raise Error, "invalid character at #{s[0,10].inspect}"
178
+ end
179
+ if typ != :space
180
+ ts << [typ, lexeme, val]
181
+ end
182
+ s = s[lexeme.length..-1]
183
+ end
184
+ ts
185
+ end
186
+
187
+
188
+ # Scans the first token in s and
189
+ # returns a 3-element list, or nil
190
+ # if s does not begin with a valid token.
191
+ #
192
+ # The first list element is one of
193
+ # '{', '}', ':', ',', '[', ']',
194
+ # :val, :str, and :space.
195
+ #
196
+ # The second element is the lexeme.
197
+ #
198
+ # The third element is the value of the
199
+ # token for :val and :str, otherwise
200
+ # it is the lexeme.
201
+ def tok(s)
202
+ case s[0]
203
+ when ?{ then ['{', s[0,1], s[0,1]]
204
+ when ?} then ['}', s[0,1], s[0,1]]
205
+ when ?: then [':', s[0,1], s[0,1]]
206
+ when ?, then [',', s[0,1], s[0,1]]
207
+ when ?[ then ['[', s[0,1], s[0,1]]
208
+ when ?] then [']', s[0,1], s[0,1]]
209
+ when ?n then nulltok(s)
210
+ when ?t then truetok(s)
211
+ when ?f then falsetok(s)
212
+ when ?" then strtok(s)
213
+ when Spc then [:space, s[0,1], s[0,1]]
214
+ when ?\t then [:space, s[0,1], s[0,1]]
215
+ when ?\n then [:space, s[0,1], s[0,1]]
216
+ when ?\r then [:space, s[0,1], s[0,1]]
217
+ else numtok(s)
218
+ end
219
+ end
220
+
221
+
222
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
223
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
224
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
225
+
226
+
227
+ def numtok(s)
228
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
229
+ if m && m.begin(0) == 0
230
+ if m[3] && !m[2]
231
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
232
+ elsif m[2]
233
+ [:val, m[0], Float(m[0])]
234
+ else
235
+ [:val, m[0], Integer(m[0])]
236
+ end
237
+ else
238
+ []
239
+ end
240
+ end
241
+
242
+
243
+ def strtok(s)
244
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
245
+ if ! m
246
+ raise Error, "invalid string literal at #{abbrev(s)}"
247
+ end
248
+ [:str, m[0], unquote(m[0])]
249
+ end
250
+
251
+
252
+ def abbrev(s)
253
+ t = s[0,10]
254
+ p = t['`']
255
+ t = t[0,p] if p
256
+ t = t + '...' if t.length < s.length
257
+ '`' + t + '`'
258
+ end
259
+
260
+
261
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
262
+ # The rules are different than for Ruby, so we cannot use eval.
263
+ # Unquote will raise an error if q contains control characters.
264
+ def unquote(q)
265
+ q = q[1...-1]
266
+ a = q.dup # allocate a big enough string
267
+ rubydoesenc = false
268
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
269
+ if a.class.method_defined?(:force_encoding)
270
+ a.force_encoding('UTF-8')
271
+ rubydoesenc = true
272
+ end
273
+ r, w = 0, 0
274
+ while r < q.length
275
+ c = q[r]
276
+ case true
277
+ when c == ?\\
278
+ r += 1
279
+ if r >= q.length
280
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
281
+ end
282
+
283
+ case q[r]
284
+ when ?",?\\,?/,?'
285
+ a[w] = q[r]
286
+ r += 1
287
+ w += 1
288
+ when ?b,?f,?n,?r,?t
289
+ a[w] = Unesc[q[r]]
290
+ r += 1
291
+ w += 1
292
+ when ?u
293
+ r += 1
294
+ uchar = begin
295
+ hexdec4(q[r,4])
296
+ rescue RuntimeError => e
297
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
298
+ end
299
+ r += 4
300
+ if surrogate? uchar
301
+ if q.length >= r+6
302
+ uchar1 = hexdec4(q[r+2,4])
303
+ uchar = subst(uchar, uchar1)
304
+ if uchar != Ucharerr
305
+ # A valid pair; consume.
306
+ r += 6
307
+ end
308
+ end
309
+ end
310
+ if rubydoesenc
311
+ a[w] = '' << uchar
312
+ w += 1
313
+ else
314
+ w += ucharenc(a, w, uchar)
315
+ end
316
+ else
317
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
318
+ end
319
+ when c == ?", c < Spc
320
+ raise Error, "invalid character in string literal \"#{q}\""
321
+ else
322
+ # Copy anything else byte-for-byte.
323
+ # Valid UTF-8 will remain valid UTF-8.
324
+ # Invalid UTF-8 will remain invalid UTF-8.
325
+ # In ruby >= 1.9, c is a codepoint, not a byte,
326
+ # in which case this is still what we want.
327
+ a[w] = c
328
+ r += 1
329
+ w += 1
330
+ end
331
+ end
332
+ a[0,w]
333
+ end
334
+
335
+
336
+ # Encodes unicode character u as UTF-8
337
+ # bytes in string a at position i.
338
+ # Returns the number of bytes written.
339
+ def ucharenc(a, i, u)
340
+ case true
341
+ when u <= Uchar1max
342
+ a[i] = (u & 0xff).chr
343
+ 1
344
+ when u <= Uchar2max
345
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
346
+ a[i+1] = (Utagx | (u&Umaskx)).chr
347
+ 2
348
+ when u <= Uchar3max
349
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
350
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
351
+ a[i+2] = (Utagx | (u&Umaskx)).chr
352
+ 3
353
+ else
354
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
355
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
356
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
357
+ a[i+3] = (Utagx | (u&Umaskx)).chr
358
+ 4
359
+ end
360
+ end
361
+
362
+
363
+ def hexdec4(s)
364
+ if s.length != 4
365
+ raise Error, 'short'
366
+ end
367
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
368
+ end
369
+
370
+
371
+ def subst(u1, u2)
372
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
373
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
374
+ end
375
+ return Ucharerr
376
+ end
377
+
378
+
379
+ def surrogate?(u)
380
+ Usurr1 <= u && u < Usurr3
381
+ end
382
+
383
+
384
+ def nibble(c)
385
+ case true
386
+ when ?0 <= c && c <= ?9 then c.ord - ?0.ord
387
+ when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
388
+ when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
389
+ else
390
+ raise Error, "invalid hex code #{c}"
391
+ end
392
+ end
393
+
394
+
395
+ # Encodes x into a json text. It may contain only
396
+ # Array, Hash, String, Numeric, true, false, nil.
397
+ # (Note, this list excludes Symbol.)
398
+ # X itself must be an Array or a Hash.
399
+ # No other value can be encoded, and an error will
400
+ # be raised if x contains any other value, such as
401
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
402
+ # is not a String.
403
+ # Strings contained in x must be valid UTF-8.
404
+ def encode(x)
405
+ case x
406
+ when Hash then objenc(x)
407
+ when Array then arrenc(x)
408
+ else
409
+ raise Error, 'root value must be an Array or a Hash'
410
+ end
411
+ end
412
+
413
+
414
+ def valenc(x)
415
+ case x
416
+ when Hash then objenc(x)
417
+ when Array then arrenc(x)
418
+ when String then strenc(x)
419
+ when Numeric then numenc(x)
420
+ when true then "true"
421
+ when false then "false"
422
+ when nil then "null"
423
+ else
424
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
425
+ end
426
+ end
427
+
428
+
429
+ def objenc(x)
430
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
431
+ end
432
+
433
+
434
+ def arrenc(a)
435
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
436
+ end
437
+
438
+
439
+ def keyenc(k)
440
+ case k
441
+ when String then strenc(k)
442
+ else
443
+ raise Error, "Hash key is not a string: #{k.inspect}"
444
+ end
445
+ end
446
+
447
+
448
+ def strenc(s)
449
+ t = StringIO.new
450
+ t.putc(?")
451
+ r = 0
452
+
453
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
454
+ rubydoesenc = s.class.method_defined?(:encoding)
455
+
456
+ while r < s.length
457
+ case s[r]
458
+ when ?" then t.print('\\"')
459
+ when ?\\ then t.print('\\\\')
460
+ when ?\b then t.print('\\b')
461
+ when ?\f then t.print('\\f')
462
+ when ?\n then t.print('\\n')
463
+ when ?\r then t.print('\\r')
464
+ when ?\t then t.print('\\t')
465
+ else
466
+ c = s[r]
467
+ case true
468
+ when rubydoesenc
469
+ begin
470
+ c.ord # will raise an error if c is invalid UTF-8
471
+ t.write(c)
472
+ rescue
473
+ t.write(Ustrerr)
474
+ end
475
+ when Spc <= c && c <= ?~
476
+ t.putc(c)
477
+ else
478
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
479
+ r += n - 1 # r is incremented below
480
+ end
481
+ end
482
+ r += 1
483
+ end
484
+ t.putc(?")
485
+ t.string
486
+ end
487
+
488
+
489
+ def numenc(x)
490
+ if ((x.nan? || x.infinite?) rescue false)
491
+ raise Error, "Numeric cannot be represented: #{x}"
492
+ end
493
+ "#{x}"
494
+ end
495
+
496
+
497
+ # Copies the valid UTF-8 bytes of a single character
498
+ # from string s at position i to I/O object t, and
499
+ # returns the number of bytes copied.
500
+ # If no valid UTF-8 char exists at position i,
501
+ # ucharcopy writes Ustrerr and returns 1.
502
+ def ucharcopy(t, s, i)
503
+ n = s.length - i
504
+ raise Utf8Error if n < 1
505
+
506
+ c0 = s[i].ord
507
+
508
+ # 1-byte, 7-bit sequence?
509
+ if c0 < Utagx
510
+ t.putc(c0)
511
+ return 1
512
+ end
513
+
514
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
515
+
516
+ raise Utf8Error if n < 2 # need continuation byte
517
+ c1 = s[i+1].ord
518
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
519
+
520
+ # 2-byte, 11-bit sequence?
521
+ if c0 < Utag3
522
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
523
+ t.putc(c0)
524
+ t.putc(c1)
525
+ return 2
526
+ end
527
+
528
+ # need second continuation byte
529
+ raise Utf8Error if n < 3
530
+
531
+ c2 = s[i+2].ord
532
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
533
+
534
+ # 3-byte, 16-bit sequence?
535
+ if c0 < Utag4
536
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
537
+ raise Utf8Error if u <= Uchar2max
538
+ t.putc(c0)
539
+ t.putc(c1)
540
+ t.putc(c2)
541
+ return 3
542
+ end
543
+
544
+ # need third continuation byte
545
+ raise Utf8Error if n < 4
546
+ c3 = s[i+3].ord
547
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
548
+
549
+ # 4-byte, 21-bit sequence?
550
+ if c0 < Utag5
551
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
552
+ raise Utf8Error if u <= Uchar3max
553
+ t.putc(c0)
554
+ t.putc(c1)
555
+ t.putc(c2)
556
+ t.putc(c3)
557
+ return 4
558
+ end
559
+
560
+ raise Utf8Error
561
+ rescue Utf8Error
562
+ t.write(Ustrerr)
563
+ return 1
564
+ end
565
+
566
+
567
+ class Utf8Error < ::StandardError
568
+ end
569
+
570
+
571
+ class Error < ::StandardError
572
+ end
573
+
574
+
575
+ Utagx = 0x80 # 1000 0000
576
+ Utag2 = 0xc0 # 1100 0000
577
+ Utag3 = 0xe0 # 1110 0000
578
+ Utag4 = 0xf0 # 1111 0000
579
+ Utag5 = 0xF8 # 1111 1000
580
+ Umaskx = 0x3f # 0011 1111
581
+ Umask2 = 0x1f # 0001 1111
582
+ Umask3 = 0x0f # 0000 1111
583
+ Umask4 = 0x07 # 0000 0111
584
+ Uchar1max = (1<<7) - 1
585
+ Uchar2max = (1<<11) - 1
586
+ Uchar3max = (1<<16) - 1
587
+ Ucharerr = 0xFFFD # unicode "replacement char"
588
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
589
+ Usurrself = 0x10000
590
+ Usurr1 = 0xd800
591
+ Usurr2 = 0xdc00
592
+ Usurr3 = 0xe000
593
+
594
+ Spc = ' '[0]
595
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
596
+ end
@@ -1,5 +1,4 @@
1
1
  require 'base64'
2
- require 'yajl'
3
2
  require 'openssl'
4
3
  require 'date'
5
4
 
@@ -44,19 +43,20 @@ module Fernet
44
43
  parts = @token.split('|')
45
44
  if decrypt?
46
45
  encrypted_data, iv, @received_signature = *parts
47
- @data = Yajl::Parser.parse(decrypt!(encrypted_data, Base64.urlsafe_decode64(iv)))
46
+ @data = Fernet::OkJson.decode(decrypt!(encrypted_data, Base64.urlsafe_decode64(iv)))
48
47
  signing_blob = "#{encrypted_data}|#{iv}"
49
48
  else
50
49
  encoded_data, @received_signature = *parts
51
50
  signing_blob = encoded_data
52
- @data = Yajl::Parser.parse(Base64.urlsafe_decode64(encoded_data))
51
+ @data = Fernet::OkJson.decode(Base64.urlsafe_decode64(encoded_data))
53
52
  end
54
53
  @regenerated_mac = OpenSSL::HMAC.hexdigest('sha256', signing_blob, signing_key)
55
54
  end
56
55
 
57
56
  def token_recent_enough?
58
57
  if enforce_ttl?
59
- DateTime.parse(data['issued_at']) > (now - ttl)
58
+ good_till = DateTime.parse(data['issued_at']) + (ttl.to_f / 24 / 60 / 60)
59
+ good_till > now
60
60
  else
61
61
  true
62
62
  end
@@ -1,3 +1,3 @@
1
1
  module Fernet
2
- VERSION = "1.4"
2
+ VERSION = "1.5"
3
3
  end
@@ -16,9 +16,11 @@ describe Fernet do
16
16
  generator.data = token_data
17
17
  end
18
18
 
19
- Fernet.verify(secret, token) do |verifier|
20
- verifier.data['email'] == 'harold@heroku.com'
21
- end.should be_true
19
+ expect(
20
+ Fernet.verify(secret, token) do |verifier|
21
+ verifier.data['email'] == 'harold@heroku.com'
22
+ end
23
+ ).to be_true
22
24
  end
23
25
 
24
26
  it 'fails with a bad secret' do
@@ -26,19 +28,23 @@ describe Fernet do
26
28
  generator.data = token_data
27
29
  end
28
30
 
29
- Fernet.verify(bad_secret, token) do |verifier|
30
- verifier.data['email'] == 'harold@heroku.com'
31
- end.should be_false
31
+ expect(
32
+ Fernet.verify(bad_secret, token) do |verifier|
33
+ verifier.data['email'] == 'harold@heroku.com'
34
+ end
35
+ ).to be_false
32
36
  end
33
37
 
34
38
  it 'fails with a bad custom verification' do
35
39
  token = Fernet.generate(secret) do |generator|
36
- generator.data = token_data
40
+ generator.data = { :email => 'harold@heroku.com' }
37
41
  end
38
42
 
39
- Fernet.verify(bad_secret, token) do |verifier|
40
- verifier.data['email'] == 'harold@gmail.com'
41
- end.should be_false
43
+ expect(
44
+ Fernet.verify(secret, token) do |verifier|
45
+ verifier.data['email'] == 'lol@heroku.com'
46
+ end
47
+ ).to be_false
42
48
  end
43
49
 
44
50
  it 'fails if the token is too old' do
@@ -46,9 +52,18 @@ describe Fernet do
46
52
  generator.data = token_data
47
53
  end
48
54
 
49
- Fernet.verify(bad_secret, token) do |verifier|
50
- verifier.ttl = 0
51
- end.should be_false
55
+ expect(
56
+ Fernet.verify(secret, token) do |verifier|
57
+ verifier.ttl = 1
58
+
59
+ def verifier.now
60
+ now = DateTime.now
61
+ DateTime.new(now.year, now.month, now.day, now.hour,
62
+ now.min, now.sec + 2, now.offset)
63
+ end
64
+ true
65
+ end
66
+ ).to be_false
52
67
  end
53
68
 
54
69
  it 'verifies without a custom verification' do
@@ -56,7 +71,7 @@ describe Fernet do
56
71
  generator.data = token_data
57
72
  end
58
73
 
59
- Fernet.verify(secret, token).should be_true
74
+ expect(Fernet.verify(secret, token)).to be_true
60
75
  end
61
76
 
62
77
  it 'can ignore TTL enforcement' do
@@ -64,13 +79,15 @@ describe Fernet do
64
79
  generator.data = token_data
65
80
  end
66
81
 
67
- Fernet.verify(secret, token) do |verifier|
68
- def verifier.now
69
- Time.now + 99999999999
82
+ expect(
83
+ Fernet.verify(secret, token) do |verifier|
84
+ def verifier.now
85
+ Time.now + 99999999999
86
+ end
87
+ verifier.enforce_ttl = false
88
+ true
70
89
  end
71
- verifier.enforce_ttl = false
72
- true
73
- end.should be_true
90
+ ).to be_true
74
91
  end
75
92
 
76
93
  it 'can ignore TTL enforcement via global config' do
@@ -82,18 +99,20 @@ describe Fernet do
82
99
  generator.data = token_data
83
100
  end
84
101
 
85
- Fernet.verify(secret, token) do |verifier|
86
- def verifier.now
87
- Time.now + 99999999999
102
+ expect(
103
+ Fernet.verify(secret, token) do |verifier|
104
+ def verifier.now
105
+ Time.now + 99999999999
106
+ end
107
+ true
88
108
  end
89
- true
90
- end.should be_true
109
+ ).to be_true
91
110
  end
92
111
 
93
112
  it 'generates without custom data' do
94
113
  token = Fernet.generate(secret)
95
114
 
96
- Fernet.verify(secret, token).should be_true
115
+ expect(Fernet.verify(secret, token)).to be_true
97
116
  end
98
117
 
99
118
  it 'can encrypt the payload' do
@@ -101,11 +120,10 @@ describe Fernet do
101
120
  generator.data['password'] = 'password1'
102
121
  end
103
122
 
104
- payload = Base64.decode64(token)
105
- payload.should_not match /password1/
123
+ expect(Base64.decode64(token)).not_to match /password1/
106
124
 
107
125
  Fernet.verify(secret, token) do |verifier|
108
- verifier.data['password'].should == 'password1'
126
+ expect(verifier.data['password']).to eq('password1')
109
127
  end
110
128
  end
111
129
 
@@ -114,11 +132,10 @@ describe Fernet do
114
132
  generator.data['password'] = 'password1'
115
133
  end
116
134
 
117
- payload = Base64.decode64(token)
118
- payload.should match /password1/
135
+ expect(Base64.decode64(token)).to match /password1/
119
136
 
120
137
  Fernet.verify(secret, token, false) do |verifier|
121
- verifier.data['password'].should == 'password1'
138
+ expect(verifier.data['password']).to eq('password1')
122
139
  end
123
140
  end
124
141
 
@@ -128,11 +145,10 @@ describe Fernet do
128
145
  generator.data['password'] = 'password1'
129
146
  end
130
147
 
131
- payload = Base64.decode64(token)
132
- payload.should match /password1/
148
+ expect(Base64.decode64(token)).to match /password1/
133
149
 
134
150
  Fernet.verify(secret, token) do |verifier|
135
- verifier.data['password'].should == 'password1'
151
+ expect(verifier.data['password']).to eq('password1')
136
152
  end
137
153
  end
138
154
 
@@ -142,7 +158,7 @@ describe Fernet do
142
158
  end
143
159
 
144
160
  verifier = Fernet.verifier(secret, token)
145
- verifier.should be_valid
146
- verifier.data['password'].should == 'password1'
161
+ expect(verifier.valid?).to be_true
162
+ expect(verifier.data['password']).to eq('password1')
147
163
  end
148
164
  end
@@ -14,4 +14,8 @@ RSpec.configure do |config|
14
14
  # the seed, which is printed after each run.
15
15
  # --seed 1234
16
16
  config.order = 'random'
17
+
18
+ config.expect_with :rspec do |c|
19
+ c.syntax = :expect
20
+ end
17
21
  end
metadata CHANGED
@@ -1,38 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fernet
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.4'
5
- prerelease:
4
+ prerelease:
5
+ version: '1.5'
6
6
  platform: ruby
7
7
  authors:
8
8
  - Harold Giménez
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-15 00:00:00.000000000 Z
12
+ date: 2012-11-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: yajl-ruby
16
- requirement: &70124570227380 !ruby/object:Gem::Requirement
17
- none: false
15
+ name: rspec
16
+ version_requirements: !ruby/object:Gem::Requirement
18
17
  requirements:
19
18
  - - ! '>='
20
19
  - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: *70124570227380
25
- - !ruby/object:Gem::Dependency
26
- name: rspec
27
- requirement: &70124570226960 !ruby/object:Gem::Requirement
20
+ version: !binary |-
21
+ MA==
28
22
  none: false
23
+ requirement: !ruby/object:Gem::Requirement
29
24
  requirements:
30
25
  - - ! '>='
31
26
  - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :development
27
+ version: !binary |-
28
+ MA==
29
+ none: false
34
30
  prerelease: false
35
- version_requirements: *70124570226960
31
+ type: :development
36
32
  description: Delicious HMAC Digest(if) authentication and encryption
37
33
  email:
38
34
  - harold.gimenez@gmail.com
@@ -42,6 +38,7 @@ extra_rdoc_files: []
42
38
  files:
43
39
  - .gitignore
44
40
  - .rspec
41
+ - .travis.yml
45
42
  - Gemfile
46
43
  - LICENSE
47
44
  - README.md
@@ -50,6 +47,7 @@ files:
50
47
  - lib/fernet.rb
51
48
  - lib/fernet/configuration.rb
52
49
  - lib/fernet/generator.rb
50
+ - lib/fernet/okjson.rb
53
51
  - lib/fernet/secret.rb
54
52
  - lib/fernet/verifier.rb
55
53
  - lib/fernet/version.rb
@@ -58,26 +56,28 @@ files:
58
56
  - spec/spec_helper.rb
59
57
  homepage: ''
60
58
  licenses: []
61
- post_install_message:
59
+ post_install_message:
62
60
  rdoc_options: []
63
61
  require_paths:
64
62
  - lib
65
63
  required_ruby_version: !ruby/object:Gem::Requirement
66
- none: false
67
64
  requirements:
68
65
  - - ! '>='
69
66
  - !ruby/object:Gem::Version
70
- version: '0'
71
- required_rubygems_version: !ruby/object:Gem::Requirement
67
+ version: !binary |-
68
+ MA==
72
69
  none: false
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
71
  requirements:
74
72
  - - ! '>='
75
73
  - !ruby/object:Gem::Version
76
- version: '0'
74
+ version: !binary |-
75
+ MA==
76
+ none: false
77
77
  requirements: []
78
- rubyforge_project:
79
- rubygems_version: 1.8.10
80
- signing_key:
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.24
80
+ signing_key:
81
81
  specification_version: 3
82
82
  summary: Easily generate and verify AES encrypted HMAC based authentication tokens
83
83
  test_files: