fernet 0.1 → 1.0
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/README.md +11 -3
- data/lib/fernet.rb +5 -4
- data/lib/fernet/generator.rb +42 -5
- data/lib/fernet/secret.rb +20 -0
- data/lib/fernet/verifier.rb +35 -6
- data/lib/fernet/version.rb +1 -1
- data/spec/fernet_spec.rb +32 -4
- metadata +5 -4
data/README.md
CHANGED
@@ -55,16 +55,24 @@ Otherwise, `verified` will be false, and you should deny the request with an
|
|
55
55
|
HTTP 401, for example.
|
56
56
|
|
57
57
|
The specs
|
58
|
-
([spec/fernet_spec.rb](https://github.com/
|
58
|
+
([spec/fernet_spec.rb](https://github.com/hgmnz/fernet/blob/master/spec/fernet_spec.rb))
|
59
59
|
have more usage examples.
|
60
60
|
|
61
|
+
### Generating a secret
|
62
|
+
|
63
|
+
Generating appropriate secrets is beyond the scope of `Fernet`, but you should
|
64
|
+
generate it using `/dev/random` in a *nix. To generate a base64-encoded 256 bit
|
65
|
+
(32 byte) random sequence, try:
|
66
|
+
|
67
|
+
dd if=/dev/urandom bs=32 count=1 2>/dev/null | openssl base64
|
68
|
+
|
61
69
|
### Attribution
|
62
70
|
|
63
71
|
This library was largely made possible by [Mr. Tom
|
64
72
|
Maher](http://twitter.com/#tmaher), who clearly articulated the mechanics
|
65
73
|
behind this process, and further found ways to make it
|
66
|
-
[more](https://github.com/
|
67
|
-
[secure](https://github.com/
|
74
|
+
[more](https://github.com/hgmnz/fernet/commit/2bf0b4a66b49ef3fc92ef50708a2c8b401950fc2)
|
75
|
+
[secure](https://github.com/hgmnz/fernet/commit/051161d0afb0b41480734d84bc824bdbc7f9c563).
|
68
76
|
|
69
77
|
## License
|
70
78
|
|
data/lib/fernet.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'fernet/version'
|
2
2
|
require 'fernet/generator'
|
3
3
|
require 'fernet/verifier'
|
4
|
+
require 'fernet/secret'
|
4
5
|
|
5
6
|
module Fernet
|
6
|
-
def self.generate(secret, &block)
|
7
|
-
Generator.new(secret).generate(&block)
|
7
|
+
def self.generate(secret, encrypt = true, &block)
|
8
|
+
Generator.new(secret, encrypt).generate(&block)
|
8
9
|
end
|
9
10
|
|
10
|
-
def self.verify(secret, token, &block)
|
11
|
-
Verifier.new(secret).verify_token(token, &block)
|
11
|
+
def self.verify(secret, token, decrypt = true, &block)
|
12
|
+
Verifier.new(secret, decrypt).verify_token(token, &block)
|
12
13
|
end
|
13
14
|
end
|
data/lib/fernet/generator.rb
CHANGED
@@ -5,18 +5,28 @@ require 'date'
|
|
5
5
|
|
6
6
|
module Fernet
|
7
7
|
class Generator
|
8
|
-
attr_accessor :data
|
8
|
+
attr_accessor :data, :payload
|
9
9
|
|
10
|
-
def initialize(secret)
|
11
|
-
@secret
|
10
|
+
def initialize(secret, encrypt)
|
11
|
+
@secret = Secret.new(secret, encrypt)
|
12
|
+
@encrypt = encrypt
|
13
|
+
@payload = ''
|
14
|
+
@data = {}
|
12
15
|
end
|
13
16
|
|
14
17
|
def generate
|
15
18
|
yield self if block_given?
|
16
19
|
data.merge!(issued_at: DateTime.now)
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
if encrypt?
|
22
|
+
iv = encrypt_data!
|
23
|
+
@payload = "#{base64(data)}|#{base64(iv)}"
|
24
|
+
else
|
25
|
+
@payload = base64(JSON.dump(data))
|
26
|
+
end
|
27
|
+
|
28
|
+
mac = OpenSSL::HMAC.hexdigest('sha256', payload, signing_key)
|
29
|
+
"#{payload}|#{mac}"
|
20
30
|
end
|
21
31
|
|
22
32
|
def inspect
|
@@ -30,5 +40,32 @@ module Fernet
|
|
30
40
|
|
31
41
|
private
|
32
42
|
attr_reader :secret
|
43
|
+
|
44
|
+
def encrypt_data!
|
45
|
+
cipher = OpenSSL::Cipher.new('AES-128-CBC')
|
46
|
+
cipher.encrypt
|
47
|
+
iv = cipher.random_iv
|
48
|
+
cipher.iv = iv
|
49
|
+
cipher.key = encryption_key
|
50
|
+
@data = cipher.update(JSON.dump(data)) + cipher.final
|
51
|
+
iv
|
52
|
+
end
|
53
|
+
|
54
|
+
def base64(chars)
|
55
|
+
Base64.urlsafe_encode64(chars)
|
56
|
+
end
|
57
|
+
|
58
|
+
def encryption_key
|
59
|
+
@secret.encryption_key
|
60
|
+
end
|
61
|
+
|
62
|
+
def signing_key
|
63
|
+
@secret.signing_key
|
64
|
+
end
|
65
|
+
|
66
|
+
def encrypt?
|
67
|
+
@encrypt
|
68
|
+
end
|
69
|
+
|
33
70
|
end
|
34
71
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Fernet
|
2
|
+
class Secret
|
3
|
+
def initialize(secret, encrypt)
|
4
|
+
@secret = secret
|
5
|
+
@encrypt = encrypt
|
6
|
+
end
|
7
|
+
|
8
|
+
def encryption_key
|
9
|
+
@secret.byteslice(@secret.bytesize/2, @secret.bytesize)
|
10
|
+
end
|
11
|
+
|
12
|
+
def signing_key
|
13
|
+
if @encrypt
|
14
|
+
@secret.byteslice(0, @secret.bytesize/2)
|
15
|
+
else
|
16
|
+
@secret
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/fernet/verifier.rb
CHANGED
@@ -5,12 +5,12 @@ require 'date'
|
|
5
5
|
|
6
6
|
module Fernet
|
7
7
|
class Verifier
|
8
|
-
|
9
8
|
attr_reader :token, :data
|
10
9
|
attr_writer :seconds_valid
|
11
10
|
|
12
|
-
def initialize(secret)
|
13
|
-
@secret
|
11
|
+
def initialize(secret, decrypt)
|
12
|
+
@secret = Secret.new(secret, decrypt)
|
13
|
+
@decrypt = decrypt
|
14
14
|
end
|
15
15
|
|
16
16
|
def verify_token(token)
|
@@ -35,9 +35,17 @@ module Fernet
|
|
35
35
|
attr_reader :secret
|
36
36
|
|
37
37
|
def deconstruct
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
parts = @token.split('|')
|
39
|
+
if decrypt?
|
40
|
+
encrypted_data, iv, @received_signature = *parts
|
41
|
+
@data = JSON.parse(decrypt!(encrypted_data, Base64.urlsafe_decode64(iv)))
|
42
|
+
signing_blob = "#{encrypted_data}|#{iv}"
|
43
|
+
else
|
44
|
+
encoded_data, @received_signature = *parts
|
45
|
+
signing_blob = encoded_data
|
46
|
+
@data = JSON.parse(Base64.urlsafe_decode64(encoded_data))
|
47
|
+
end
|
48
|
+
@regenerated_mac = OpenSSL::HMAC.hexdigest('sha256', signing_blob, signing_key)
|
41
49
|
end
|
42
50
|
|
43
51
|
def token_recent_enough?
|
@@ -51,5 +59,26 @@ module Fernet
|
|
51
59
|
accum |= byte ^ regenerated_bytes.shift
|
52
60
|
end.zero?
|
53
61
|
end
|
62
|
+
|
63
|
+
def decrypt!(encrypted_data, iv)
|
64
|
+
decipher = OpenSSL::Cipher.new('AES-128-CBC')
|
65
|
+
decipher.decrypt
|
66
|
+
decipher.iv = iv
|
67
|
+
decipher.key = encryption_key
|
68
|
+
decipher.update(Base64.urlsafe_decode64(encrypted_data)) + decipher.final
|
69
|
+
end
|
70
|
+
|
71
|
+
def encryption_key
|
72
|
+
@secret.encryption_key
|
73
|
+
end
|
74
|
+
|
75
|
+
def signing_key
|
76
|
+
@secret.signing_key
|
77
|
+
end
|
78
|
+
|
79
|
+
def decrypt?
|
80
|
+
@decrypt
|
81
|
+
end
|
82
|
+
|
54
83
|
end
|
55
84
|
end
|
data/lib/fernet/version.rb
CHANGED
data/spec/fernet_spec.rb
CHANGED
@@ -6,7 +6,8 @@ describe Fernet do
|
|
6
6
|
{ email: 'harold@heroku.com', id: '123', arbitrary: 'data' }
|
7
7
|
end
|
8
8
|
|
9
|
-
let(:secret) { '
|
9
|
+
let(:secret) { 'JrdICDH6x3M7duQeM8dJEMK4Y5TkBIsYDw1lPy35RiY=' }
|
10
|
+
let(:bad_secret) { 'jrdICDH6x3M7duQeM8dJEMK4Y5TkBIsYDw1lPy35RiY=' }
|
10
11
|
|
11
12
|
it 'can verify tokens it generates' do
|
12
13
|
token = Fernet.generate(secret) do |generator|
|
@@ -23,7 +24,7 @@ describe Fernet do
|
|
23
24
|
generator.data = token_data
|
24
25
|
end
|
25
26
|
|
26
|
-
Fernet.verify(
|
27
|
+
Fernet.verify(bad_secret, token) do |verifier|
|
27
28
|
verifier.data['email'] == 'harold@heroku.com'
|
28
29
|
end.should be_false
|
29
30
|
end
|
@@ -33,7 +34,7 @@ describe Fernet do
|
|
33
34
|
generator.data = token_data
|
34
35
|
end
|
35
36
|
|
36
|
-
Fernet.verify(
|
37
|
+
Fernet.verify(bad_secret, token) do |verifier|
|
37
38
|
verifier.data['email'] == 'harold@gmail.com'
|
38
39
|
end.should be_false
|
39
40
|
end
|
@@ -43,7 +44,7 @@ describe Fernet do
|
|
43
44
|
generator.data = token_data
|
44
45
|
end
|
45
46
|
|
46
|
-
Fernet.verify(
|
47
|
+
Fernet.verify(bad_secret, token) do |verifier|
|
47
48
|
verifier.seconds_valid = 0
|
48
49
|
end.should be_false
|
49
50
|
end
|
@@ -61,4 +62,31 @@ describe Fernet do
|
|
61
62
|
|
62
63
|
Fernet.verify(secret, token).should be_true
|
63
64
|
end
|
65
|
+
|
66
|
+
it 'can encrypt the payload' do
|
67
|
+
token = Fernet.generate(secret, true) do |generator|
|
68
|
+
generator.data['password'] = 'password1'
|
69
|
+
end
|
70
|
+
|
71
|
+
payload = Base64.decode64(token)
|
72
|
+
payload.should_not match /password1/
|
73
|
+
|
74
|
+
Fernet.verify(secret, token) do |verifier|
|
75
|
+
verifier.data['password'].should == 'password1'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'does not encrypt when asked nicely' do
|
80
|
+
token = Fernet.generate(secret, false) do |generator|
|
81
|
+
generator.data['password'] = 'password1'
|
82
|
+
end
|
83
|
+
|
84
|
+
payload = Base64.decode64(token)
|
85
|
+
payload.should match /password1/
|
86
|
+
|
87
|
+
Fernet.verify(secret, token, false) do |verifier|
|
88
|
+
verifier.data['password'].should == 'password1'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
64
92
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fernet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0
|
4
|
+
version: '1.0'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-08-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70135148211780 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70135148211780
|
25
25
|
description: Delicious HMAC Digest(if) authentication
|
26
26
|
email:
|
27
27
|
- harold.gimenez@gmail.com
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- fernet.gemspec
|
39
39
|
- lib/fernet.rb
|
40
40
|
- lib/fernet/generator.rb
|
41
|
+
- lib/fernet/secret.rb
|
41
42
|
- lib/fernet/verifier.rb
|
42
43
|
- lib/fernet/version.rb
|
43
44
|
- spec/fernet_spec.rb
|