branca-ruby 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +0 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +13 -0
- data/bin/console +15 -0
- data/bin/setup +9 -0
- data/branca-ruby.gemspec +34 -0
- data/lib/branca.rb +74 -0
- data/lib/branca/decoder.rb +12 -0
- data/lib/branca/exceptions.rb +17 -0
- data/lib/branca/version.rb +5 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed83738a4d8fa5af4ce11072867964581233d64b
|
4
|
+
data.tar.gz: 3bd9e2a9a4ec544199ff8fc4b412e5a8aee1c168
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57467de90627d3d71cf6c44678a73eff888ab63d8af956d3e8349c62f3370a4f4aefa5328df416ed99a579beae383ad1ca88a5136d57d0b5007513ea11f967bf
|
7
|
+
data.tar.gz: fad2dd6948928afea94699b963500585fe7d95ee2a59cb08dcd77cca3e30d1a5a85a4e19c8c5c8c58d6880c5e11b5e5c1a86cf46a3ca392df92f89b3907e522e
|
data/.gitignore
ADDED
data/.rspec
ADDED
File without changes
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 Thadeu Esteves Jr
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Branca Tokens for Ruby
|
2
|
+
|
3
|
+
Authenticated and encrypted API tokens using modern crypto.
|
4
|
+
|
5
|
+
[](LICENSE)
|
6
|
+
|
7
|
+
## What?
|
8
|
+
|
9
|
+
[Branca](https://github.com/thadeu/branca-ruby) is a secure easy to use token format which makes it hard to shoot yourself in the foot. It uses IETF XChaCha20-Poly1305 AEAD symmetric encryption to create encrypted and tamperproof tokens. Payload itself is an arbitrary sequence of bytes. You can use for example a JSON object, plain text string or even binary data serialized by [MessagePack](http://msgpack.org/) or [Protocol Buffers](https://developers.google.com/protocol-buffers/).
|
10
|
+
|
11
|
+
It is possible to use [Branca as an alternative to JWT](https://appelsiini.net/2017/branca-alternative-to-jwt/).
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile, Note that you also must have [libsodium](https://download.libsodium.org/doc/) installed.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'branca-ruby', '~> 1.0.0'
|
19
|
+
```
|
20
|
+
|
21
|
+
## Configure
|
22
|
+
|
23
|
+
You must be configure `secret_key` and `ttl` using this.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
Branca.configure do |config|
|
27
|
+
config.secret_key = 'supersecretkeyyoushouldnotcommit'.b
|
28
|
+
config.ttl = 86_400 # in seconds
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
The payload of the token can be anything, like a simple string.
|
35
|
+
|
36
|
+
### Encode
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Branca.encode('with string')
|
40
|
+
|
41
|
+
# 1y48BiLKOcB4N8xjazwFpas3DwOovXzu6vtbiUr4bDAGLaVyFjIN5Xwz5p3qvNYsi5kWjk7ilgnS
|
42
|
+
```
|
43
|
+
|
44
|
+
or JSON stringified
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Branca.encode(JSON.generate({ permissions: [] }))
|
48
|
+
|
49
|
+
# ATkzLjriA1ijbBcuZOJ1zMR0z5oVXDGDVjUWwrqJWszynAM4GLGiTwZnC6nUvtVIuavAVCMbwcsYqlYKejOI4
|
50
|
+
```
|
51
|
+
|
52
|
+
You can also pass `timestamp` to encode
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
Branca.encode('with string', Time.now.utc)
|
56
|
+
|
57
|
+
# 1y48BiV0jaalTYiARPdbm52IKgGEhfwq8DlP9ulKBx8LMLFrjNKe88vIGIUxsWzybIwBhmVvIam5
|
58
|
+
```
|
59
|
+
|
60
|
+
### Decode
|
61
|
+
|
62
|
+
If you branca token isnt expired. You will receive something like this
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
decode = Branca.decode('1y48BiV0jaalTYiARPdbm52IKgGEhfwq8DlP9ulKBx8LMLFrjNKe88vIGIUxsWzybIwBhmVvIam5')
|
66
|
+
|
67
|
+
# <Branca::Decoder:0x00007fde4e3e6398 @message="with string", @timestamp=2020-10-27 03:44:03 UTC>
|
68
|
+
|
69
|
+
decode.message
|
70
|
+
# "with string"
|
71
|
+
```
|
72
|
+
|
73
|
+
## Exceptions
|
74
|
+
|
75
|
+
Token is expired, will receive exception `Branca::ExpiredTokenError`
|
76
|
+
|
77
|
+
Invalid Version, will receive exception `Branca::VersionError`
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
10
|
+
t.options = ['--display-cop-names']
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: %i[spec rubocop]
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'branca'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/branca-ruby.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'branca/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'branca-ruby'
|
10
|
+
spec.version = Branca::VERSION
|
11
|
+
spec.authors = ['Thadeu Esteves']
|
12
|
+
spec.email = ['tadeuu@gmail.com']
|
13
|
+
spec.summary = 'Authenticated and encrypted API tokens using modern crypto'
|
14
|
+
spec.description = 'Authenticated and encrypted API tokens using modern crypto'
|
15
|
+
spec.homepage = 'https://github.com/thadeu/branca-ruby'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.required_ruby_version = '>= 2.3.0'
|
23
|
+
|
24
|
+
spec.bindir = 'exe'
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ['lib']
|
27
|
+
|
28
|
+
spec.add_dependency 'base_x', '~> 0.8.1'
|
29
|
+
spec.add_dependency 'rbnacl', '~> 7.0'
|
30
|
+
spec.add_development_dependency 'bundler', '>= 1.14'
|
31
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
32
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
33
|
+
spec.add_development_dependency 'rubocop', '~> 0.70'
|
34
|
+
end
|
data/lib/branca.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'base_x'
|
5
|
+
|
6
|
+
require 'branca/version'
|
7
|
+
require 'branca/exceptions'
|
8
|
+
require 'branca/decoder'
|
9
|
+
|
10
|
+
module Branca
|
11
|
+
VERSION = 0xBA
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :secret_key, :ttl
|
15
|
+
|
16
|
+
def encode(message, timestamp = Time.now.utc)
|
17
|
+
nonce = RbNaCl::Random.random_bytes(cipher.nonce_bytes)
|
18
|
+
|
19
|
+
header = [VERSION, timestamp.to_i].pack('C N') + nonce
|
20
|
+
ciphertext = cipher.encrypt(nonce, message, header)
|
21
|
+
raw_token = header + ciphertext
|
22
|
+
|
23
|
+
BaseX::Base62.encode(raw_token)
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode(token)
|
27
|
+
header, bytes = token_explode(token)
|
28
|
+
version, timestamp, nonce = header_explode(header)
|
29
|
+
|
30
|
+
raise VersionError unless version == VERSION
|
31
|
+
raise ExpiredTokenError if (timestamp + Branca.ttl) < Time.now.utc.to_i
|
32
|
+
|
33
|
+
message = cipher.decrypt(nonce, bytes.pack('C*'), header.pack('C*'))
|
34
|
+
Decoder.new(message, Time.at(timestamp).utc)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ttl
|
38
|
+
@ttl ||= ttl_default
|
39
|
+
end
|
40
|
+
|
41
|
+
def secret_key
|
42
|
+
@secret_key ||= RbNaCl::Random.random_bytes(32)
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure
|
46
|
+
yield self if block_given?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def cipher
|
52
|
+
@cipher ||= RbNaCl::AEAD::XChaCha20Poly1305IETF.new(Branca.secret_key&.b)
|
53
|
+
end
|
54
|
+
|
55
|
+
def token_explode(token)
|
56
|
+
bytes = BaseX::Base62.decode(token).unpack('C C4 C24 C*')
|
57
|
+
header = bytes.shift(1 + 4 + 24)
|
58
|
+
|
59
|
+
[header, bytes]
|
60
|
+
end
|
61
|
+
|
62
|
+
def header_explode(header)
|
63
|
+
version = header[0]
|
64
|
+
nonce = header[5..header.size].pack('C*')
|
65
|
+
timestamp = header[1..4].pack('C*').unpack('N')&.first
|
66
|
+
|
67
|
+
[version, timestamp, nonce]
|
68
|
+
end
|
69
|
+
|
70
|
+
def ttl_default
|
71
|
+
@ttl_default ||= 86_400
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Branca
|
4
|
+
class VersionError < StandardError; end
|
5
|
+
|
6
|
+
class DecodeError < StandardError
|
7
|
+
def to_s
|
8
|
+
"Can't decode token"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ExpiredTokenError < StandardError
|
13
|
+
def to_s
|
14
|
+
'Token is expired'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: branca-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thadeu Esteves
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: base_x
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rbnacl
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.70'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.70'
|
97
|
+
description: Authenticated and encrypted API tokens using modern crypto
|
98
|
+
email:
|
99
|
+
- tadeuu@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- branca-ruby.gemspec
|
114
|
+
- lib/branca.rb
|
115
|
+
- lib/branca/decoder.rb
|
116
|
+
- lib/branca/exceptions.rb
|
117
|
+
- lib/branca/version.rb
|
118
|
+
homepage: https://github.com/thadeu/branca-ruby
|
119
|
+
licenses:
|
120
|
+
- MIT
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 2.3.0
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.5.2.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: Authenticated and encrypted API tokens using modern crypto
|
142
|
+
test_files: []
|