slosilo 2.0.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b2f977e53f906393dcac0918f5fb5b2b2b41563b
4
- data.tar.gz: 967b35f4ed8f91c24dfab09245ff0cb798b585ed
2
+ SHA256:
3
+ metadata.gz: 81d8f2c013b6f88d01bd7dd4c44b39c7cf0ffaa9385afca3e7ea826dae0855c8
4
+ data.tar.gz: acd627ef6ca45a404ce08c7bc680c8db7ea1ebdbe10d45b248ae09e59e804a02
5
5
  SHA512:
6
- metadata.gz: fd31b09380e3f9411aca6a7ee9244d727e0962954da29c18adffa68b81b58265607205b5a519de8d211da07f412600788c1b5579fc0a604e06328e5bb86e62a8
7
- data.tar.gz: 3454255c91a0a62c59bedf5b425e5b5a9fdb1ba29283d85ae0e5be7ca9b5cd2b1fa44b4fa280d65d37441d802e634309a928373848ad4cf199c9e5ac58d074cd
6
+ metadata.gz: '098a049570888513d10bf23edacfb8c566f605a08a9e918a2a06c15839d406b14696edaf76c49b84820d3c0fc1147b0f7f754c74fdad680a98f919d134cabcc7'
7
+ data.tar.gz: 6cef96001fcc2932c23e106c732d9e7722afd2f6a8af11e99a023a49aa005bdc8ff9f3fa4ce278644b753acfe0e50953e14cf86114e78877368b95107a1126c0
data/.dockerignore ADDED
@@ -0,0 +1,2 @@
1
+ /Gemfile.lock
2
+ /spec/reports
@@ -0,0 +1,10 @@
1
+ * @cyberark/conjur-core-team @conjurinc/conjur-core-team @conjurdemos/conjur-core-team
2
+
3
+ # Changes to .trivyignore require Security Architect approval
4
+ .trivyignore @cyberark/security-architects @conjurinc/security-architects @conjurdemos/security-architects
5
+
6
+ # Changes to .codeclimate.yml require Quality Architect approval
7
+ .codeclimate.yml @cyberark/quality-architects @conjurinc/quality-architects @conjurdemos/quality-architects
8
+
9
+ # Changes to SECURITY.md require Security Architect approval
10
+ SECURITY.md @cyberark/security-architects @conjurinc/security-architects @conjurdemos/security-architects
data/.gitleaks.toml ADDED
@@ -0,0 +1,221 @@
1
+ title = "Secretless Broker gitleaks config"
2
+
3
+ # This is the config file for gitleaks. You can configure gitleaks what to search for and what to whitelist.
4
+ # If GITLEAKS_CONFIG environment variable
5
+ # is set, gitleaks will load configurations from that path. If option --config-path is set, gitleaks will load
6
+ # configurations from that path. Gitleaks does not whitelist anything by default.
7
+ # - https://www.ndss-symposium.org/wp-content/uploads/2019/02/ndss2019_04B-3_Meli_paper.pdf
8
+ # - https://github.com/dxa4481/truffleHogRegexes/blob/master/truffleHogRegexes/regexes.json
9
+ [[rules]]
10
+ description = "AWS Client ID"
11
+ regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
12
+ tags = ["key", "AWS"]
13
+
14
+ [[rules]]
15
+ description = "AWS Secret Key"
16
+ regex = '''(?i)aws(.{0,20})?(?-i)['\"][0-9a-zA-Z\/+]{40}['\"]'''
17
+ tags = ["key", "AWS"]
18
+
19
+ [[rules]]
20
+ description = "AWS MWS key"
21
+ regex = '''amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'''
22
+ tags = ["key", "AWS", "MWS"]
23
+
24
+ [[rules]]
25
+ description = "PKCS8"
26
+ regex = '''-----BEGIN PRIVATE KEY-----'''
27
+ tags = ["key", "PKCS8"]
28
+
29
+ [[rules]]
30
+ description = "RSA"
31
+ regex = '''-----BEGIN RSA PRIVATE KEY-----'''
32
+ tags = ["key", "RSA"]
33
+
34
+ [[rules]]
35
+ description = "SSH"
36
+ regex = '''-----BEGIN OPENSSH PRIVATE KEY-----'''
37
+ tags = ["key", "SSH"]
38
+
39
+ [[rules]]
40
+ description = "PGP"
41
+ regex = '''-----BEGIN PGP PRIVATE KEY BLOCK-----'''
42
+ tags = ["key", "PGP"]
43
+
44
+ [[rules]]
45
+ description = "Facebook Secret Key"
46
+ regex = '''(?i)(facebook|fb)(.{0,20})?(?-i)['\"][0-9a-f]{32}['\"]'''
47
+ tags = ["key", "Facebook"]
48
+
49
+ [[rules]]
50
+ description = "Facebook Client ID"
51
+ regex = '''(?i)(facebook|fb)(.{0,20})?['\"][0-9]{13,17}['\"]'''
52
+ tags = ["key", "Facebook"]
53
+
54
+ [[rules]]
55
+ description = "Facebook access token"
56
+ regex = '''EAACEdEose0cBA[0-9A-Za-z]+'''
57
+ tags = ["key", "Facebook"]
58
+
59
+ [[rules]]
60
+ description = "Twitter Secret Key"
61
+ regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{35,44}['\"]'''
62
+ tags = ["key", "Twitter"]
63
+
64
+ [[rules]]
65
+ description = "Twitter Client ID"
66
+ regex = '''(?i)twitter(.{0,20})?['\"][0-9a-z]{18,25}['\"]'''
67
+ tags = ["client", "Twitter"]
68
+
69
+ [[rules]]
70
+ description = "Github"
71
+ regex = '''(?i)github(.{0,20})?(?-i)['\"][0-9a-zA-Z]{35,40}['\"]'''
72
+ tags = ["key", "Github"]
73
+
74
+ [[rules]]
75
+ description = "LinkedIn Client ID"
76
+ regex = '''(?i)linkedin(.{0,20})?(?-i)['\"][0-9a-z]{12}['\"]'''
77
+ tags = ["client", "Twitter"]
78
+
79
+ [[rules]]
80
+ description = "LinkedIn Secret Key"
81
+ regex = '''(?i)linkedin(.{0,20})?['\"][0-9a-z]{16}['\"]'''
82
+ tags = ["secret", "Twitter"]
83
+
84
+ [[rules]]
85
+ description = "Slack"
86
+ regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?'''
87
+ tags = ["key", "Slack"]
88
+
89
+ [[rules]]
90
+ description = "EC"
91
+ regex = '''-----BEGIN EC PRIVATE KEY-----'''
92
+ tags = ["key", "EC"]
93
+
94
+ [[rules]]
95
+ description = "Generic API key"
96
+ regex = '''(?i)(api_key|apikey)(.{0,20})?['|"][0-9a-zA-Z]{32,45}['|"]'''
97
+ tags = ["key", "API", "generic"]
98
+
99
+ [[rules]]
100
+ description = "Generic Secret"
101
+ regex = '''(?i)secret(.{0,20})?['|"][0-9a-zA-Z]{32,45}['|"]'''
102
+ tags = ["key", "Secret", "generic"]
103
+
104
+ [[rules]]
105
+ description = "Google API key"
106
+ regex = '''AIza[0-9A-Za-z\\-_]{35}'''
107
+ tags = ["key", "Google"]
108
+
109
+ [[rules]]
110
+ description = "Google Cloud Platform API key"
111
+ regex = '''(?i)(google|gcp|youtube|drive|yt)(.{0,20})?['\"][AIza[0-9a-z\\-_]{35}]['\"]'''
112
+ tags = ["key", "Google", "GCP"]
113
+
114
+ [[rules]]
115
+ description = "Google OAuth"
116
+ regex = '''(?i)(google|gcp|auth)(.{0,20})?['"][0-9]+-[0-9a-z_]{32}\.apps\.googleusercontent\.com['"]'''
117
+ tags = ["key", "Google", "OAuth"]
118
+
119
+ [[rules]]
120
+ description = "Google OAuth access token"
121
+ regex = '''ya29\.[0-9A-Za-z\-_]+'''
122
+ tags = ["key", "Google", "OAuth"]
123
+
124
+ [[rules]]
125
+ description = "Heroku API key"
126
+ regex = '''(?i)heroku(.{0,20})?['"][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"]'''
127
+ tags = ["key", "Heroku"]
128
+
129
+ [[rules]]
130
+ description = "MailChimp API key"
131
+ regex = '''(?i)(mailchimp|mc)(.{0,20})?['"][0-9a-f]{32}-us[0-9]{1,2}['"]'''
132
+ tags = ["key", "Mailchimp"]
133
+
134
+ [[rules]]
135
+ description = "Mailgun API key"
136
+ regex = '''(?i)(mailgun|mg)(.{0,20})?['"][0-9a-z]{32}['"]'''
137
+ tags = ["key", "Mailgun"]
138
+
139
+ [[rules]]
140
+ description = "Password in URL"
141
+ regex = '''[a-zA-Z]{3,10}:\/\/[^\/\s:@]{3,20}:[^\/\s:@]{3,20}@.{1,100}\/?.?'''
142
+ tags = ["key", "URL", "generic"]
143
+
144
+ [[rules]]
145
+ description = "PayPal Braintree access token"
146
+ regex = '''access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}'''
147
+ tags = ["key", "Paypal"]
148
+
149
+ [[rules]]
150
+ description = "Picatic API key"
151
+ regex = '''sk_live_[0-9a-z]{32}'''
152
+ tags = ["key", "Picatic"]
153
+
154
+ [[rules]]
155
+ description = "Slack Webhook"
156
+ regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}'''
157
+ tags = ["key", "slack"]
158
+
159
+ [[rules]]
160
+ description = "Stripe API key"
161
+ regex = '''(?i)stripe(.{0,20})?['\"][sk|rk]_live_[0-9a-zA-Z]{24}'''
162
+ tags = ["key", "Stripe"]
163
+
164
+ [[rules]]
165
+ description = "Square access token"
166
+ regex = '''sq0atp-[0-9A-Za-z\-_]{22}'''
167
+ tags = ["key", "square"]
168
+
169
+ [[rules]]
170
+ description = "Square OAuth secret"
171
+ regex = '''sq0csp-[0-9A-Za-z\\-_]{43}'''
172
+ tags = ["key", "square"]
173
+
174
+ [[rules]]
175
+ description = "Twilio API key"
176
+ regex = '''(?i)twilio(.{0,20})?['\"][0-9a-f]{32}['\"]'''
177
+ tags = ["key", "twilio"]
178
+
179
+ [whitelist]
180
+ files = [
181
+ "(.*?)(jpg|gif|doc|pdf|bin)$",
182
+ ".gitleaks.toml"
183
+ ]
184
+ regexes = [
185
+ ]
186
+ commits = [
187
+ "3a496cef2d737f69038630f3c884a159f783bd06", # old commit to add test data
188
+ "047e58e40c87f9d19d68c21a533b706616ab1ef2", # old commit to add test data
189
+ "5345e49e7d63589fc637c2b0c7156bf97e9c72b8", # old commit to add test data
190
+ "9c31229cedceedd75e06c381fe7218571a03c26d" # old commit to add test data
191
+ ]
192
+
193
+ # Additional Examples
194
+
195
+ # [[rules]]
196
+ # description = "Generic Key"
197
+ # regex = '''(?i)key(.{0,6})?(:|=|=>|:=)'''
198
+ # entropies = [
199
+ # "4.1-4.3",
200
+ # "5.5-6.3",
201
+ # ]
202
+ # entropyROI = "line"
203
+ # filetypes = [".go", ".py", ".c"]
204
+ # tags = ["key"]
205
+ # severity = "8"
206
+ #
207
+ #
208
+ # [[rules]]
209
+ # description = "Generic Key"
210
+ # regex = '''(?i)key(.{0,6})?(:|=|=>|:=)'''
211
+ # entropies = ["4.1-4.3"]
212
+ # filetypes = [".gee"]
213
+ # entropyROI = "line"
214
+ # tags = ["key"]
215
+ # severity = "medium"
216
+
217
+ # [[rules]]
218
+ # description = "Any pem file"
219
+ # filetypes = [".key"]
220
+ # tags = ["pem"]
221
+ # severity = "high"
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
+ # v3.0.0
2
+
3
+ * Transition to Ruby 3. Consuming projects based on Ruby 2 shall use slosilo V2.X.X.
4
+
5
+ # v2.2.2
6
+
7
+ * Add rake task `slosilo:recalculate_fingerprints` which rehashes the fingerprints in the keystore.
8
+ **Note**: After migrating the slosilo keystore, run the above rake task to ensure the fingerprints are correctly hashed.
9
+
10
+ # v2.2.1
11
+
12
+ * Use SHA256 algorithm instead of MD5 for public key fingerprints.
13
+
14
+ # v2.1.1
15
+
16
+ * Add support for JWT-formatted tokens, with arbitrary expiration.
17
+
1
18
  # v2.0.1
2
19
 
3
20
  * Fixes a bug that occurs when signing tokens containing Unicode data
4
-
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,16 @@
1
+ # Contributing
2
+
3
+ For general contribution and community guidelines, please see the [community repo](https://github.com/cyberark/community).
4
+
5
+ ## Contributing Workflow
6
+
7
+ 1. [Fork the project](https://help.github.com/en/github/getting-started-with-github/fork-a-repo)
8
+ 2. [Clone your fork](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository)
9
+ 3. Make local changes to your fork by editing files
10
+ 3. [Commit your changes](https://help.github.com/en/github/managing-files-in-a-repository/adding-a-file-to-a-repository-using-the-command-line)
11
+ 4. [Push your local changes to the remote server](https://help.github.com/en/github/using-git/pushing-commits-to-a-remote-repository)
12
+ 5. [Create new Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
13
+
14
+ From here your pull request will be reviewed and once you've responded to all
15
+ feedback it will be merged into the project. Congratulations, you're a
16
+ contributor!
data/Jenkinsfile ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env groovy
2
+
3
+ pipeline {
4
+ agent { label 'executor-v2' }
5
+
6
+ options {
7
+ timestamps()
8
+ buildDiscarder(logRotator(daysToKeepStr: '30'))
9
+ }
10
+
11
+ stages {
12
+ stage('Test') {
13
+ parallel {
14
+ stage('Run tests on EE') {
15
+ agent { label 'executor-v2-rhel-ee' }
16
+ steps {
17
+ sh './test.sh'
18
+ }
19
+ post { always {
20
+ stash name: 'eeTestResults', includes: 'spec/reports/*.xml', allowEmpty:true
21
+ }}
22
+ }
23
+
24
+ stage('Run tests') {
25
+ steps {
26
+ sh './test.sh'
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ stage('Publish to RubyGems') {
33
+ agent { label 'executor-v2' }
34
+ when {
35
+ allOf {
36
+ branch 'master'
37
+ expression {
38
+ boolean publish = false
39
+
40
+ try {
41
+ timeout(time: 5, unit: 'MINUTES') {
42
+ input(message: 'Publish to RubyGems?')
43
+ publish = true
44
+ }
45
+ } catch (final ignore) {
46
+ publish = false
47
+ }
48
+
49
+ return publish
50
+ }
51
+ }
52
+ }
53
+
54
+ steps {
55
+ checkout scm
56
+ sh './publish-rubygem.sh'
57
+ deleteDir()
58
+ }
59
+ }
60
+ }
61
+
62
+ post {
63
+ always {
64
+ dir('ee-results'){
65
+ unstash 'eeTestResults'
66
+ }
67
+ junit 'spec/reports/*.xml, ee-results/spec/reports/*.xml'
68
+ cobertura coberturaReportFile: 'spec/coverage/coverage.xml'
69
+ sh 'cp spec/coverage/coverage.xml cobertura.xml'
70
+ ccCoverage("cobertura", "github.com/cyberark/slosilo")
71
+
72
+ cleanupAndNotify(currentBuild.currentResult)
73
+ }
74
+ }
75
+ }
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Conjur Inc
1
+ Copyright (c) 2020 CyberArk Software Ltd. All rights reserved.
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -19,6 +19,9 @@ And then execute:
19
19
 
20
20
  ## Compatibility
21
21
 
22
+ Version 3.0 introduced full transition to Ruby 3.
23
+ Consumers who use slosilo in Ruby 2 projects, shall use slosilo V2.X.X.
24
+
22
25
  Version 2.0 introduced new symmetric encryption scheme using AES-256-GCM
23
26
  for authenticated encryption. It allows you to provide AAD on all symmetric
24
27
  encryption primitives. It's also **NOT COMPATIBLE** with CBC used in version <2.
@@ -144,8 +147,6 @@ Slosilo.adapter = Slosilo::Adapters::SequelAdapter.new
144
147
 
145
148
  ## Contributing
146
149
 
147
- 1. Fork it
148
- 2. Create your feature branch (`git checkout -b my-new-feature`)
149
- 3. Commit your changes (`git commit -am 'Added some feature'`)
150
- 4. Push to the branch (`git push origin my-new-feature`)
151
- 5. Create new Pull Request
150
+ We welcome contributions of all kinds to this repository. For instructions on
151
+ how to get started and descriptions of our development workflows, please see our
152
+ [contributing guide](CONTRIBUTING.md).
data/SECURITY.md ADDED
@@ -0,0 +1,42 @@
1
+ # Security Policies and Procedures
2
+
3
+ This document outlines security procedures and general policies for the CyberArk Conjur
4
+ suite of tools and products.
5
+
6
+ * [Reporting a Bug](#reporting-a-bug)
7
+ * [Disclosure Policy](#disclosure-policy)
8
+ * [Comments on this Policy](#comments-on-this-policy)
9
+
10
+ ## Reporting a Bug
11
+
12
+ The CyberArk Conjur team and community take all security bugs in the Conjur suite seriously.
13
+ Thank you for improving the security of the Conjur suite. We appreciate your efforts and
14
+ responsible disclosure and will make every effort to acknowledge your
15
+ contributions.
16
+
17
+ Report security bugs by emailing the lead maintainers at security@conjur.org.
18
+
19
+ The maintainers will acknowledge your email within 2 business days. Subsequently, we will
20
+ send a more detailed response within 2 business days of our acknowledgement indicating
21
+ the next steps in handling your report. After the initial reply to your report, the security
22
+ team will endeavor to keep you informed of the progress towards a fix and full
23
+ announcement, and may ask for additional information or guidance.
24
+
25
+ Report security bugs in third-party modules to the person or team maintaining
26
+ the module.
27
+
28
+ ## Disclosure Policy
29
+
30
+ When the security team receives a security bug report, they will assign it to a
31
+ primary handler. This person will coordinate the fix and release process,
32
+ involving the following steps:
33
+
34
+ * Confirm the problem and determine the affected versions.
35
+ * Audit code to find any potential similar problems.
36
+ * Prepare fixes for all releases still under maintenance. These fixes will be
37
+ released as fast as possible.
38
+
39
+ ## Comments on this Policy
40
+
41
+ If you have suggestions on how this process could be improved please submit a
42
+ pull request.
@@ -49,6 +49,17 @@ module Slosilo
49
49
  end
50
50
  end
51
51
 
52
+ def recalculate_fingerprints
53
+ # Use a transaction to ensure that all fingerprints are updated together. If any update fails,
54
+ # we want to rollback all updates.
55
+ model.db.transaction do
56
+ model.each do |m|
57
+ m.update fingerprint: Slosilo::Key.new(m.key).fingerprint
58
+ end
59
+ end
60
+ end
61
+
62
+
52
63
  def migrate!
53
64
  unless fingerprint_in_db?
54
65
  model.db.transaction do
@@ -59,9 +70,7 @@ module Slosilo
59
70
  # reload the schema
60
71
  model.set_dataset model.dataset
61
72
 
62
- model.each do |m|
63
- m.update fingerprint: Slosilo::Key.new(m.key).fingerprint
64
- end
73
+ recalculate_fingerprints
65
74
 
66
75
  model.db.alter_table :slosilo_keystore do
67
76
  set_column_not_null :fingerprint
@@ -26,7 +26,10 @@ module Slosilo
26
26
  aad = options[:aad]
27
27
  # note nil.to_s is "", which is exactly the right thing
28
28
  auth_data = aad.respond_to?(:to_proc) ? aad.to_proc : proc{ |_| aad.to_s }
29
- raise ":aad proc must take one argument" unless auth_data.arity.abs == 1 # take abs to allow *args arity, -1
29
+
30
+ # In ruby 3 .arity for #proc returns both 1 and 2, depends on internal #proc
31
+ # This method is also being called with aad which is string, in such case the arity is 1
32
+ raise ":aad proc must take two arguments" unless (auth_data.arity.abs == 2 || auth_data.arity.abs == 1)
30
33
 
31
34
  # push a module onto the inheritance hierarchy
32
35
  # this allows calling super in classes
@@ -8,5 +8,8 @@ module Slosilo
8
8
  super
9
9
  end
10
10
  end
11
+
12
+ class TokenValidationError < Error
13
+ end
11
14
  end
12
15
  end
@@ -0,0 +1,122 @@
1
+ require 'json'
2
+
3
+ module Slosilo
4
+ # A JWT-formatted Slosilo token.
5
+ # @note This is not intended to be a general-purpose JWT implementation.
6
+ class JWT
7
+ # Create a new unsigned token with the given claims.
8
+ # @param claims [#to_h] claims to embed in this token.
9
+ def initialize claims = {}
10
+ @claims = JSONHash[claims]
11
+ end
12
+
13
+ # Parse a token in compact representation
14
+ def self.parse_compact raw
15
+ load *raw.split('.', 3).map(&Base64.method(:urlsafe_decode64))
16
+ end
17
+
18
+ # Parse a token in JSON representation.
19
+ # @note only single signature is currently supported.
20
+ def self.parse_json raw
21
+ raw = JSON.load raw unless raw.respond_to? :to_h
22
+ parts = raw.to_h.values_at(*%w(protected payload signature))
23
+ fail ArgumentError, "input not a complete JWT" unless parts.all?
24
+ load *parts.map(&Base64.method(:urlsafe_decode64))
25
+ end
26
+
27
+ # Add a signature.
28
+ # @note currently only a single signature is handled;
29
+ # the token will be frozen after this operation.
30
+ def add_signature header, &sign
31
+ @claims = canonicalize_claims.freeze
32
+ @header = JSONHash[header].freeze
33
+ @signature = sign[string_to_sign].freeze
34
+ freeze
35
+ end
36
+
37
+ def string_to_sign
38
+ [header, claims].map(&method(:encode)).join '.'
39
+ end
40
+
41
+ # Returns the JSON serialization of this JWT.
42
+ def to_json *a
43
+ {
44
+ protected: encode(header),
45
+ payload: encode(claims),
46
+ signature: encode(signature)
47
+ }.to_json *a
48
+ end
49
+
50
+ # Returns the compact serialization of this JWT.
51
+ def to_s
52
+ [header, claims, signature].map(&method(:encode)).join('.')
53
+ end
54
+
55
+ attr_accessor :claims, :header, :signature
56
+
57
+ private
58
+
59
+ # Create a JWT token object from existing header, payload, and signature strings.
60
+ # @param header [#to_s] URLbase64-encoded representation of the protected header
61
+ # @param payload [#to_s] URLbase64-encoded representation of the token payload
62
+ # @param signature [#to_s] URLbase64-encoded representation of the signature
63
+ def self.load header, payload, signature
64
+ self.new(JSONHash.load payload).tap do |token|
65
+ token.header = JSONHash.load header
66
+ token.signature = signature.to_s.freeze
67
+ token.freeze
68
+ end
69
+ end
70
+
71
+ def canonicalize_claims
72
+ claims[:iat] = Time.now unless claims.include? :iat
73
+ claims[:iat] = claims[:iat].to_time.to_i
74
+ claims[:exp] = claims[:exp].to_time.to_i if claims.include? :exp
75
+ JSONHash[claims.to_a]
76
+ end
77
+
78
+ # Convenience method to make the above code clearer.
79
+ # Converts to string and urlbase64-encodes.
80
+ def encode s
81
+ Base64.urlsafe_encode64 s.to_s
82
+ end
83
+
84
+ # a hash with a possibly frozen JSON stringification
85
+ class JSONHash < Hash
86
+ def to_s
87
+ @repr || to_json
88
+ end
89
+
90
+ def freeze
91
+ @repr = to_json.freeze
92
+ super
93
+ end
94
+
95
+ def self.load raw
96
+ self[JSON.load raw.to_s].tap do |h|
97
+ h.send :repr=, raw
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def repr= raw
104
+ @repr = raw.freeze
105
+ freeze
106
+ end
107
+ end
108
+ end
109
+
110
+ # Try to convert by detecting token representation and parsing
111
+ def self.JWT raw
112
+ if raw.is_a? JWT
113
+ raw
114
+ elsif raw.respond_to?(:to_h) || raw =~ /\A\s*\{/
115
+ JWT.parse_json raw
116
+ else
117
+ JWT.parse_compact raw
118
+ end
119
+ rescue
120
+ raise ArgumentError, "invalid value for JWT(): #{raw.inspect}"
121
+ end
122
+ end