Skip to content

Commit

Permalink
add jwt-rs256 encryption scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
ezekg committed Sep 3, 2018
1 parent 46f6df8 commit 0d37d38
Show file tree
Hide file tree
Showing 13 changed files with 440 additions and 39 deletions.
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ gem 'stripe'
# Authorization
gem 'pundit'

# Cyrptography
# Cryptography
gem 'openssl'
gem 'jwt'

# Scopes and pagination
gem 'has_scope'
Expand Down
6 changes: 4 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ GEM
jsonapi-renderer (0.2.0)
jsonapi-serializable (0.3.0)
jsonapi-renderer (~> 0.2.0)
jwt (2.1.0)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
Expand All @@ -160,10 +161,10 @@ GEM
nio4r (1.2.1)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
openssl (2.1.1)
parallel (1.12.1)
parallel_tests (2.21.2)
parallel
openssl (2.1.1)
pg (0.19.0)
pg_search (2.1.2)
activerecord (>= 4.2)
Expand Down Expand Up @@ -337,11 +338,12 @@ DEPENDENCIES
has_scope
httparty
jsonapi-rails (= 0.3.1)
jwt
kaminari
listen (~> 3.0.5)
newrelic_rpm
parallel_tests
openssl
parallel_tests
pg
pg_search
premailer-rails
Expand Down
13 changes: 13 additions & 0 deletions app/models/license.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,19 @@ def encrypt_key

self.signature = Base64.strict_encode64 sig
self.key = Base64.strict_encode64 key
when "RSA_2048_JWT_RS256"
priv = OpenSSL::PKey::RSA.new account.private_key
begin
payload = JSON.parse key
jwt = JWT.encode payload, priv, 'RS256'

self.key = jwt
rescue JSON::GeneratorError,
JSON::ParserError
errors.add :key, :jwt_payload_invalid, message: "key is not a valid JWT payload (must be a valid JSON encoded string)"
rescue JWT::InvalidPayload => e
errors.add :key, :jwt_payload_invalid, message: "key is not a valid JWT payload (#{e.message})"
end
end

raise ActiveRecord::RecordInvalid if key.nil?
Expand Down
12 changes: 9 additions & 3 deletions app/models/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ class Policy < ApplicationRecord
include Pageable
include Searchable

ENCRYPTION_SCHEMES = %w[LEGACY RSA_2048_PKCS1_ENCRYPT RSA_2048_PKCS1_SIGN RSA_2048_PKCS1_PSS_SIGN].freeze
SIGNING_SCHEMES = %w[RSA_2048_PKCS1_SIGN RSA_2048_PKCS1_PSS_SIGN].freeze
ENCRYPTION_SCHEMES = %w[
LEGACY_ENCRYPT
RSA_2048_PKCS1_ENCRYPT
RSA_2048_PKCS1_SIGN
RSA_2048_PKCS1_PSS_SIGN
RSA_2048_JWT_RS256
].freeze

SEARCH_ATTRIBUTES = %i[id name metadata].freeze
SEARCH_RELATIONSHIPS = {
Expand All @@ -19,7 +25,7 @@ class Policy < ApplicationRecord
has_many :pool, class_name: "Key", dependent: :destroy

# Default to legacy encryption scheme so that we don't break backwards compat
before_validation -> { self.encryption_scheme = 'LEGACY' }, on: :create, if: -> { encrypted? && encryption_scheme.nil? }
before_validation -> { self.encryption_scheme = 'LEGACY_ENCRYPT' }, on: :create, if: -> { encrypted? && encryption_scheme.nil? }
before_create -> { self.protected = account.protected? }, if: -> { protected.nil? }

validates :account, presence: { message: "must exist" }
Expand Down Expand Up @@ -65,7 +71,7 @@ def signed?
end

def legacy_encrypted?
encrypted? && encryption_scheme == 'LEGACY'
encrypted? && encryption_scheme == 'LEGACY_ENCRYPT'
end

def protected?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ def change

# Mark all older policies with the legacy scheme
legacy_policies = Policy.where encrypted: true, encryption_scheme: nil
legacy_policies.update_all encryption_scheme: 'LEGACY'
legacy_policies.update_all encryption_scheme: 'LEGACY_ENCRYPT'
end
end
6 changes: 3 additions & 3 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "uuid-ossp"
enable_extension "pg_stat_statements"
enable_extension "btree_gin"
enable_extension "pg_stat_statements"
enable_extension "uuid-ossp"

create_table "accounts", id: :uuid, default: -> { "uuid_generate_v4()" }, force: :cascade do |t|
t.string "name"
Expand Down Expand Up @@ -81,8 +81,8 @@
t.datetime "last_expiring_soon_event_sent_at"
t.datetime "last_check_in_soon_event_sent_at"
t.integer "uses", default: 0
t.string "signature"
t.boolean "protected"
t.string "signature"
t.index "to_tsvector('simple'::regconfig, COALESCE((id)::text, ''::text))", name: "licenses_tsv_id_idx", using: :gin
t.index "to_tsvector('simple'::regconfig, COALESCE((key)::text, ''::text))", name: "licenses_tsv_key_idx", using: :gin
t.index "to_tsvector('simple'::regconfig, COALESCE((metadata)::text, ''::text))", name: "licenses_tsv_metadata_idx", using: :gin
Expand Down
63 changes: 62 additions & 1 deletion features/api/v1/licenses/actions/validations.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1715,7 +1715,7 @@ Feature: License validation actions
And the first "policy" has the following attributes:
"""
{
"encryptionScheme": "LEGACY",
"encryptionScheme": "LEGACY_ENCRYPT",
"encrypted": true
}
"""
Expand Down Expand Up @@ -1787,6 +1787,23 @@ Feature: License validation actions
And sidekiq should have 1 "webhook" job
And sidekiq should have 1 "metric" job

Scenario: An admin validates an encrypted license using RSA_2048_JWT_RS256
Given I am an admin of account "test1"
And the current account is "test1"
And the current account has 1 "webhook-endpoint"
And the current account has 1 "policy"
And the current account has 1 encrypted "license" using "RSA_2048_JWT_RS256"
And I use an authentication token
When I send a POST request to "/accounts/test1/licenses/$0/actions/validate"
Then the response status should be "200"
And the JSON response should contain a "license"
And the JSON response should contain meta with the following:
"""
{ "valid": true, "detail": "is valid", "constant": "VALID" }
"""
And sidekiq should have 1 "webhook" job
And sidekiq should have 1 "metric" job

# Key validation
Scenario: Key validation endpoint should be inaccessible when account is disabled
Given the account "test1" is canceled
Expand Down Expand Up @@ -2075,6 +2092,50 @@ Feature: License validation actions
And sidekiq should have 0 "webhook" jobs
And sidekiq should have 0 "metric" jobs

Scenario: Anonymous validates an encrypted license using RSA_2048_JWT_RS256 by key
Given the current account is "test1"
And the current account has 1 "webhook-endpoint"
And the current account has 1 encrypted "license" using "RSA_2048_JWT_RS256"
When I send a POST request to "/accounts/test1/licenses/actions/validate-key" with the following:
"""
{
"meta": {
"key": "$licenses[0].key",
"encrypted": false
}
}
"""
Then the response status should be "200"
And the JSON response should contain a "license"
And the JSON response should contain meta with the following:
"""
{ "valid": true, "detail": "is valid", "constant": "VALID" }
"""
And sidekiq should have 1 "webhook" job
And sidekiq should have 1 "metric" job

Scenario: Anonymous validates an encrypted license using RSA_2048_JWT_RS256 by key using the legacy encrypted flag
Given the current account is "test1"
And the current account has 1 "webhook-endpoint"
And the current account has 1 encrypted "license" using "RSA_2048_JWT_RS256"
When I send a POST request to "/accounts/test1/licenses/actions/validate-key" with the following:
"""
{
"meta": {
"key": "$licenses[0].key",
"encrypted": true
}
}
"""
Then the response status should be "200"
And the JSON response should not contain a "license"
And the JSON response should contain meta with the following:
"""
{ "valid": false, "detail": "does not exist", "constant": "NOT_FOUND" }
"""
And sidekiq should have 0 "webhook" jobs
And sidekiq should have 0 "metric" jobs

Scenario: Anonymous validates a valid license by key from a pool
Given the current account is "test1"
And the current account has 1 "webhook-endpoint"
Expand Down
Loading

0 comments on commit 0d37d38

Please sign in to comment.