credify 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -0
- data/lib/credify.rb +31 -1
- data/lib/credify/encryption.rb +10 -23
- data/lib/credify/signing.rb +127 -12
- data/lib/credify/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22b70b4ef4e0b030632a418609e96ff5f84002c57c2ab03f0b8dfa96c2269079
|
4
|
+
data.tar.gz: 27e4c5183a0ddd41131a00ba8ef76ceab4a335c13047a8285ad4c11f3855cd82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd20625b4eaecd91a65127d140d5fb746076ae15b064dac3a27d88f5802049f14129543ef810a166838ba71352ade6cb58b051abb4a6e6b17565d8cc0cd03d60
|
7
|
+
data.tar.gz: 3bfc34738682cab00664f8264e0a9f19118964b7ce4b218e47c14a507f4152711178425caf7235a9a5d869bda59badfd2fbb4f2b6ff6c5cf17795fb095fa79f3
|
data/README.md
CHANGED
@@ -40,6 +40,27 @@ def existing_key_is_used
|
|
40
40
|
valid = signing.verify "message", signature
|
41
41
|
puts valid
|
42
42
|
end
|
43
|
+
|
44
|
+
def generate_approval_token
|
45
|
+
signing = Signing.new
|
46
|
+
signing.generate_key_pair
|
47
|
+
token = signing.generate_approval_token 'client_id', 'entity_id', ['openid', 'email', 'phone'], 'offer-code'
|
48
|
+
puts token
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_request_token
|
52
|
+
signing = Signing.new
|
53
|
+
signing.generate_key_pair
|
54
|
+
token = signing.generate_request_token 'client_id', 'encryption_public_key', ['openid', 'email', 'phone'], 'offer-code'
|
55
|
+
puts token
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_claim_token
|
59
|
+
signing = Signing.new
|
60
|
+
signing.generate_key_pair
|
61
|
+
result = signing.generate_claim_token 'provider_id', 'entity_id', 'credify-score', { score: 100 }
|
62
|
+
puts result
|
63
|
+
end
|
43
64
|
```
|
44
65
|
|
45
66
|
### Encryption
|
@@ -54,6 +75,8 @@ def new_key_is_generated
|
|
54
75
|
encryption.generate_key_pair
|
55
76
|
cipher_text = encryption.encrypt "secret message"
|
56
77
|
plain_text = encryption.decrypt cipher_text
|
78
|
+
pem = encryption.export_private_key
|
79
|
+
puts pem
|
57
80
|
end
|
58
81
|
|
59
82
|
def existing_key_is_used
|
data/lib/credify.rb
CHANGED
@@ -1,6 +1,36 @@
|
|
1
1
|
require "credify/version"
|
2
|
+
require 'base64'
|
3
|
+
require 'securerandom'
|
2
4
|
|
3
5
|
module Credify
|
4
6
|
class Error < StandardError; end
|
5
|
-
|
7
|
+
|
8
|
+
class Helpers
|
9
|
+
def self.sha256(message)
|
10
|
+
base64 = Digest::SHA256.base64digest(message)
|
11
|
+
Helpers.short_urlsafe_encode64(Base64.decode64(base64))
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# short_urlsafe_encode64
|
16
|
+
# @param [Binary] - str
|
17
|
+
# @return [String] - Base64 URL encoded string without padding
|
18
|
+
def self.short_urlsafe_encode64(bytes)
|
19
|
+
Base64.urlsafe_encode64(bytes).delete('=')
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# short_urlsafe_decode64
|
24
|
+
# @return [Binary]
|
25
|
+
def self.short_urlsafe_decode64(str)
|
26
|
+
Base64.urlsafe_decode64(str + '=' * (-1 * str.size & 3))
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.generate_commitment(bytes = 32)
|
30
|
+
random_bytes = SecureRandom.random_bytes(bytes)
|
31
|
+
short_urlsafe_encode64(random_bytes)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
6
36
|
end
|
data/lib/credify/encryption.rb
CHANGED
@@ -2,6 +2,7 @@ require 'openssl'
|
|
2
2
|
require 'openssl/oaep'
|
3
3
|
require "base64"
|
4
4
|
require 'openssl_pkcs8_pure'
|
5
|
+
require 'credify'
|
5
6
|
|
6
7
|
class Encryption
|
7
8
|
attr_reader :private_key, :public_key
|
@@ -43,7 +44,7 @@ class Encryption
|
|
43
44
|
# @param [String] payload - Base64 URL encoded string
|
44
45
|
# @return [Boolean]
|
45
46
|
def import_private_key_base64_url(payload)
|
46
|
-
bytes = short_urlsafe_decode64(payload)
|
47
|
+
bytes = Credify::Helpers.short_urlsafe_decode64(payload)
|
47
48
|
base64 = Base64.encode64(bytes)
|
48
49
|
formatted = base64.scan(/.{1,64}/).join("\n")
|
49
50
|
pem = add_box('PRIVATE KEY', formatted)
|
@@ -55,7 +56,7 @@ class Encryption
|
|
55
56
|
# @param [String] payload - Base64 URL encoded string
|
56
57
|
# @return [Boolean]
|
57
58
|
def import_public_key_base64_url(payload)
|
58
|
-
bytes = short_urlsafe_decode64(payload)
|
59
|
+
bytes = Credify::Helpers.short_urlsafe_decode64(payload)
|
59
60
|
base64 = Base64.encode64(bytes)
|
60
61
|
formatted = base64.scan(/.{1,64}/).join("\n")
|
61
62
|
pem = add_box('PUBLIC KEY', formatted)
|
@@ -73,7 +74,7 @@ class Encryption
|
|
73
74
|
label = ''
|
74
75
|
md = OpenSSL::Digest::SHA256
|
75
76
|
cipher_text = @public_key.public_encrypt_oaep(message, label, md)
|
76
|
-
short_urlsafe_encode64(cipher_text)
|
77
|
+
Credify::Helpers.short_urlsafe_encode64(cipher_text)
|
77
78
|
end
|
78
79
|
|
79
80
|
#
|
@@ -86,7 +87,7 @@ class Encryption
|
|
86
87
|
end
|
87
88
|
label = ''
|
88
89
|
md = OpenSSL::Digest::SHA256
|
89
|
-
raw_cipher = short_urlsafe_decode64(cipher)
|
90
|
+
raw_cipher = Credify::Helpers.short_urlsafe_decode64(cipher)
|
90
91
|
raw_text = @private_key.private_decrypt_oaep(raw_cipher, label, md)
|
91
92
|
raw_text
|
92
93
|
end
|
@@ -94,7 +95,7 @@ class Encryption
|
|
94
95
|
#
|
95
96
|
# export_private_key
|
96
97
|
# @param [Boolean] in_base64_url
|
97
|
-
# @return [Signing] - PCKS8 PEM or Base64 URL encoded string
|
98
|
+
# @return [Signing | String] - PCKS8 PEM or Base64 URL encoded string
|
98
99
|
def export_private_key(in_base64_url = false)
|
99
100
|
if @private_key.nil?
|
100
101
|
raise Exception.new 'Please pass private key'
|
@@ -103,7 +104,7 @@ class Encryption
|
|
103
104
|
|
104
105
|
if in_base64_url
|
105
106
|
formatted = remove_box('PRIVATE KEY', pem)
|
106
|
-
short_urlsafe_encode64(Base64.decode64(formatted))
|
107
|
+
Credify::Helpers.short_urlsafe_encode64(Base64.decode64(formatted))
|
107
108
|
else
|
108
109
|
pem
|
109
110
|
end
|
@@ -112,7 +113,7 @@ class Encryption
|
|
112
113
|
#
|
113
114
|
# export_public_key
|
114
115
|
# @param [Boolean] in_base64_url
|
115
|
-
# @return [Signing] - PCKS8 PEM or Base64 URL encoded string
|
116
|
+
# @return [Signing | String] - PCKS8 PEM or Base64 URL encoded string
|
116
117
|
def export_public_key(in_base64_url = false)
|
117
118
|
if @public_key.nil?
|
118
119
|
raise Exception.new 'Please pass public key'
|
@@ -122,7 +123,7 @@ class Encryption
|
|
122
123
|
|
123
124
|
if in_base64_url
|
124
125
|
formatted = remove_box('PUBLIC KEY', pem)
|
125
|
-
short_urlsafe_encode64(Base64.decode64(formatted))
|
126
|
+
Credify::Helpers.short_urlsafe_encode64(Base64.decode64(formatted))
|
126
127
|
else
|
127
128
|
pem
|
128
129
|
end
|
@@ -131,7 +132,7 @@ class Encryption
|
|
131
132
|
|
132
133
|
protected
|
133
134
|
|
134
|
-
|
135
|
+
#
|
135
136
|
# remove_box
|
136
137
|
# @param [String] tag - Either 'PUBLIC KEY' or 'PRIVATE KEY'
|
137
138
|
# @param [String] pem - String value loaded from a PEM file
|
@@ -152,18 +153,4 @@ class Encryption
|
|
152
153
|
"-----BEGIN #{tag}-----\n" << payload << "\n-----END #{tag}-----"
|
153
154
|
end
|
154
155
|
|
155
|
-
#
|
156
|
-
# short_urlsafe_encode64
|
157
|
-
# @param [Binary] - str
|
158
|
-
# @return [String] - Base64 URL encoded string without padding
|
159
|
-
def short_urlsafe_encode64(bytes)
|
160
|
-
Base64.urlsafe_encode64(bytes).delete('=')
|
161
|
-
end
|
162
|
-
|
163
|
-
#
|
164
|
-
# short_urlsafe_decode64
|
165
|
-
# @return [Binary]
|
166
|
-
def short_urlsafe_decode64(str)
|
167
|
-
Base64.urlsafe_decode64(str + '=' * (-1 * str.size & 3))
|
168
|
-
end
|
169
156
|
end
|
data/lib/credify/signing.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "ed25519"
|
2
2
|
require "base64"
|
3
|
+
require 'json'
|
3
4
|
require 'openssl_pkcs8_pure'
|
5
|
+
require 'credify'
|
4
6
|
|
5
7
|
class Signing
|
6
8
|
attr_reader :signing_key
|
@@ -31,7 +33,7 @@ class Signing
|
|
31
33
|
raise Exception.new 'Please pass signing key'
|
32
34
|
end
|
33
35
|
signature = @signing_key.sign(message)
|
34
|
-
short_urlsafe_encode64(signature)
|
36
|
+
Credify::Helpers.short_urlsafe_encode64(signature)
|
35
37
|
end
|
36
38
|
|
37
39
|
#
|
@@ -43,7 +45,7 @@ class Signing
|
|
43
45
|
if @signing_key.nil?
|
44
46
|
raise Exception.new 'Please pass signing key'
|
45
47
|
end
|
46
|
-
raw_sign = short_urlsafe_decode64(signature)
|
48
|
+
raw_sign = Credify::Helpers.short_urlsafe_decode64(signature)
|
47
49
|
@signing_key.verify_key.verify raw_sign, message
|
48
50
|
end
|
49
51
|
|
@@ -57,21 +59,134 @@ class Signing
|
|
57
59
|
Base64.encode64(@signing_key.seed)
|
58
60
|
end
|
59
61
|
|
60
|
-
|
62
|
+
#
|
63
|
+
# generate_jwt
|
64
|
+
# @param [Hash] payload
|
65
|
+
# @return [String]
|
66
|
+
def generate_jwt(payload)
|
67
|
+
if payload.empty?
|
68
|
+
raise Exception.new 'Invalid payload'
|
69
|
+
end
|
70
|
+
header = {
|
71
|
+
alg: 'EdDSA',
|
72
|
+
typ: 'JWT'
|
73
|
+
}
|
74
|
+
message = compose_message(header, payload)
|
75
|
+
signature = sign(message)
|
76
|
+
message << '.' << signature
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# parse_jwt
|
81
|
+
# @param [String] jwt
|
82
|
+
# @return [Hash] - { header: '', payload: {}, signature: '' }
|
83
|
+
def parse_jwt(jwt)
|
84
|
+
components = jwt.split('.')
|
85
|
+
unless components.length == 3
|
86
|
+
raise Exception 'JST is invalid'
|
87
|
+
end
|
88
|
+
|
89
|
+
header = JSON.parse(Credify::Helpers.short_urlsafe_decode64(components[0]))
|
90
|
+
payload = JSON.parse(Credify::Helpers.short_urlsafe_decode64(components[1]))
|
91
|
+
{ header: header, payload: payload, signature: components[2] }
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# verify_jwt
|
96
|
+
# @param [Hash] jwt - { header: '', payload: {}, signature: '' }
|
97
|
+
# @return [Boolean]
|
98
|
+
def verify_jwt(jwt)
|
99
|
+
message = compose_message(jwt[:header], jwt[:payload])
|
100
|
+
verify(jwt[:signature], message)
|
101
|
+
end
|
61
102
|
|
62
103
|
#
|
63
|
-
#
|
64
|
-
# @param [
|
65
|
-
# @
|
66
|
-
|
67
|
-
|
104
|
+
# generate_approval_token
|
105
|
+
# @param [String] client_id
|
106
|
+
# @param [String] entity_id
|
107
|
+
# @param [String[]] approved_scopes
|
108
|
+
# @param [String | nil] offer_code
|
109
|
+
# @return [String]
|
110
|
+
def generate_approval_token(client_id, entity_id, approved_scopes, offer_code = nil)
|
111
|
+
# minus 60 just in case this timestamp could collide one in the server side.
|
112
|
+
now = Time.now.to_i - 60
|
113
|
+
payload = {
|
114
|
+
client_id: client_id,
|
115
|
+
iat: now,
|
116
|
+
iss: entity_id,
|
117
|
+
scopes: approved_scopes.join(' ')
|
118
|
+
}
|
119
|
+
unless offer_code.nil?
|
120
|
+
payload[:offer_code] = offer_code
|
121
|
+
end
|
122
|
+
generate_jwt(payload)
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# generate_claim_token
|
127
|
+
# @param [String] provider_id
|
128
|
+
# @param [String] entity_id
|
129
|
+
# @param [String] scope_name
|
130
|
+
# @param [Hash] claim
|
131
|
+
# @return [Hash]
|
132
|
+
def generate_claim_token(provider_id, entity_id, scope_name, claim)
|
133
|
+
# minus 60 just in case this timestamp could collide one in the server side.
|
134
|
+
now = Time.now.to_i - 60
|
135
|
+
commitment = Credify::Helpers.generate_commitment
|
136
|
+
data = claim[:"#{scope_name}:commitment"] = commitment
|
137
|
+
scope_hash = Credify::Helpers.sha256(data)
|
138
|
+
puts scope_hash
|
139
|
+
payload = {
|
140
|
+
iat: now,
|
141
|
+
iss: provider_id,
|
142
|
+
user_id: entity_id,
|
143
|
+
scope_name: scope_name,
|
144
|
+
scope_hash: scope_hash
|
145
|
+
}
|
146
|
+
token = generate_jwt(payload)
|
147
|
+
{ token: token, commitment: commitment }
|
68
148
|
end
|
69
149
|
|
70
150
|
#
|
71
|
-
#
|
72
|
-
# @
|
73
|
-
|
74
|
-
|
151
|
+
# generate_request_token
|
152
|
+
# @param [String] client_id
|
153
|
+
# @param [String] encryption_public_key - Encryption public key in Base64 URL
|
154
|
+
# @param [String[]] scopes
|
155
|
+
# @param [String | nil] offer_code
|
156
|
+
# @return [String]
|
157
|
+
def generate_request_token(client_id, encryption_public_key, scopes, offer_code = nil)
|
158
|
+
unless scopes.include?('openid')
|
159
|
+
raise Exception 'scopes need to contain openid'
|
160
|
+
end
|
161
|
+
# minus 60 just in case this timestamp could collide one in the server side.
|
162
|
+
now = Time.now.to_i - 60
|
163
|
+
payload = {
|
164
|
+
iat: now,
|
165
|
+
iss: client_id,
|
166
|
+
encryption_public_key: encryption_public_key,
|
167
|
+
scopes: scopes.join(' ')
|
168
|
+
}
|
169
|
+
unless offer_code.nil?
|
170
|
+
payload[:offer_code] = offer_code
|
171
|
+
end
|
172
|
+
generate_jwt(payload)
|
173
|
+
end
|
174
|
+
|
175
|
+
protected
|
176
|
+
|
177
|
+
|
178
|
+
# compose_message
|
179
|
+
# @param [Hash] header
|
180
|
+
# @param [Hash] payload
|
181
|
+
# @return [String]
|
182
|
+
def compose_message(header, payload)
|
183
|
+
encoded_header = header.to_json
|
184
|
+
h = Credify::Helpers.short_urlsafe_encode64(encoded_header)
|
185
|
+
|
186
|
+
encoded_payload = payload.to_json
|
187
|
+
p = Credify::Helpers.short_urlsafe_encode64(encoded_payload)
|
188
|
+
|
189
|
+
h << '.' << p
|
75
190
|
end
|
76
191
|
|
77
192
|
end
|
data/lib/credify/version.rb
CHANGED