darrrr 0.0.3 → 0.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.
- checksums.yaml +4 -4
- data/README.md +12 -5
- data/lib/darrrr.rb +144 -2
- data/lib/darrrr/account_provider.rb +150 -0
- data/lib/darrrr/constants.rb +17 -0
- data/lib/darrrr/crypto_helper.rb +55 -0
- data/lib/darrrr/cryptors/default/default_encryptor.rb +68 -0
- data/lib/darrrr/cryptors/default/encrypted_data.rb +90 -0
- data/lib/darrrr/cryptors/default/encrypted_data_io.rb +10 -0
- data/lib/darrrr/provider.rb +157 -0
- data/lib/darrrr/recovery_provider.rb +129 -0
- data/lib/darrrr/recovery_token.rb +117 -0
- data/lib/darrrr/serialization/recovery_token_reader.rb +20 -0
- data/lib/darrrr/serialization/recovery_token_writer.rb +20 -0
- metadata +17 -18
- data/lib/github/delegated_account_recovery.rb +0 -178
- data/lib/github/delegated_account_recovery/account_provider.rb +0 -140
- data/lib/github/delegated_account_recovery/constants.rb +0 -19
- data/lib/github/delegated_account_recovery/crypto_helper.rb +0 -57
- data/lib/github/delegated_account_recovery/cryptors/default/default_encryptor.rb +0 -66
- data/lib/github/delegated_account_recovery/cryptors/default/encrypted_data.rb +0 -92
- data/lib/github/delegated_account_recovery/cryptors/default/encrypted_data_io.rb +0 -12
- data/lib/github/delegated_account_recovery/provider.rb +0 -125
- data/lib/github/delegated_account_recovery/recovery_provider.rb +0 -118
- data/lib/github/delegated_account_recovery/recovery_token.rb +0 -113
- data/lib/github/delegated_account_recovery/serialization/recovery_token_reader.rb +0 -22
- data/lib/github/delegated_account_recovery/serialization/recovery_token_writer.rb +0 -22
- data/lib/github/delegated_account_recovery/version.rb +0 -5
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Darrrr
|
4
|
+
class RecoveryTokenReader < BinData::Record
|
5
|
+
uint8 :version
|
6
|
+
uint8 :token_type
|
7
|
+
array :token_id, :type => :uint8, :read_until => lambda { index + 1 == Darrrr::TOKEN_ID_BYTE_LENGTH }
|
8
|
+
uint8 :options
|
9
|
+
uint16be :issuer_length
|
10
|
+
string :issuer, :read_length => :issuer_length
|
11
|
+
uint16be :audience_length
|
12
|
+
string :audience, :read_length => :audience_length
|
13
|
+
uint16be :issued_time_length
|
14
|
+
string :issued_time, :read_length => :issued_time_length
|
15
|
+
uint16be :data_length
|
16
|
+
string :data, :read_length => :data_length
|
17
|
+
uint16be :binding_data_length
|
18
|
+
string :binding_data, :read_length => :binding_data_length
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Darrrr
|
4
|
+
class RecoveryTokenWriter < BinData::Record
|
5
|
+
uint8 :version
|
6
|
+
uint8 :token_type
|
7
|
+
array :token_id, :type => :uint8, :initial_length => Darrrr::TOKEN_ID_BYTE_LENGTH
|
8
|
+
uint8 :options
|
9
|
+
uint16be :issuer_length, :value => lambda { issuer.length }
|
10
|
+
string :issuer
|
11
|
+
uint16be :audience_length, :value => lambda { audience.length }
|
12
|
+
string :audience
|
13
|
+
uint16be :issued_time_length, :value => lambda { issued_time.length }
|
14
|
+
string :issued_time
|
15
|
+
uint16be :data_length, :value => lambda { data.length }
|
16
|
+
string :data
|
17
|
+
uint16be :binding_data_length, :value => lambda { binding_data.length }
|
18
|
+
string :binding_data
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: darrrr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Matatall
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -90,21 +90,20 @@ files:
|
|
90
90
|
- README.md
|
91
91
|
- Rakefile
|
92
92
|
- lib/darrrr.rb
|
93
|
-
- lib/
|
94
|
-
- lib/
|
95
|
-
- lib/
|
96
|
-
- lib/
|
97
|
-
- lib/
|
98
|
-
- lib/
|
99
|
-
- lib/
|
100
|
-
- lib/
|
101
|
-
- lib/
|
102
|
-
- lib/
|
103
|
-
- lib/
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
licenses: []
|
93
|
+
- lib/darrrr/account_provider.rb
|
94
|
+
- lib/darrrr/constants.rb
|
95
|
+
- lib/darrrr/crypto_helper.rb
|
96
|
+
- lib/darrrr/cryptors/default/default_encryptor.rb
|
97
|
+
- lib/darrrr/cryptors/default/encrypted_data.rb
|
98
|
+
- lib/darrrr/cryptors/default/encrypted_data_io.rb
|
99
|
+
- lib/darrrr/provider.rb
|
100
|
+
- lib/darrrr/recovery_provider.rb
|
101
|
+
- lib/darrrr/recovery_token.rb
|
102
|
+
- lib/darrrr/serialization/recovery_token_reader.rb
|
103
|
+
- lib/darrrr/serialization/recovery_token_writer.rb
|
104
|
+
homepage: http://github.com/github/darrrr
|
105
|
+
licenses:
|
106
|
+
- MIT
|
108
107
|
metadata: {}
|
109
108
|
post_install_message:
|
110
109
|
rdoc_options: []
|
@@ -122,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
121
|
version: '0'
|
123
122
|
requirements: []
|
124
123
|
rubyforge_project:
|
125
|
-
rubygems_version: 2.
|
124
|
+
rubygems_version: 2.6.10
|
126
125
|
signing_key:
|
127
126
|
specification_version: 4
|
128
127
|
summary: Client library for the Delegated Recovery spec
|
@@ -1,178 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bindata"
|
4
|
-
require "openssl"
|
5
|
-
|
6
|
-
require_relative "delegated_account_recovery/constants"
|
7
|
-
require_relative "delegated_account_recovery/crypto_helper"
|
8
|
-
require_relative "delegated_account_recovery/recovery_token"
|
9
|
-
require_relative "delegated_account_recovery/provider"
|
10
|
-
require_relative "delegated_account_recovery/account_provider"
|
11
|
-
require_relative "delegated_account_recovery/recovery_provider"
|
12
|
-
require_relative "delegated_account_recovery/serialization/recovery_token_writer"
|
13
|
-
require_relative "delegated_account_recovery/serialization/recovery_token_reader"
|
14
|
-
require_relative "delegated_account_recovery/cryptors/default/default_encryptor"
|
15
|
-
require_relative "delegated_account_recovery/cryptors/default/encrypted_data"
|
16
|
-
require_relative "delegated_account_recovery/cryptors/default/encrypted_data_io"
|
17
|
-
|
18
|
-
module GitHub
|
19
|
-
module DelegatedAccountRecovery
|
20
|
-
# Represents a binary serialization error
|
21
|
-
class RecoveryTokenSerializationError < StandardError; end
|
22
|
-
|
23
|
-
# Represents invalid data within a valid token
|
24
|
-
# (e.g. wrong `version` number, invalid token `type`)
|
25
|
-
class TokenFormatError < StandardError; end
|
26
|
-
|
27
|
-
# Represents all crypto errors
|
28
|
-
# (e.g. invalid keys, invalid signature, decrypt failures)
|
29
|
-
class CryptoError < StandardError; end
|
30
|
-
|
31
|
-
# Represents providers supplying invalid configurations
|
32
|
-
# (e.g. non-https URLs, missing required fields, http errors)
|
33
|
-
class ProviderConfigError < StandardError; end
|
34
|
-
|
35
|
-
# Represents an invalid countersigned recovery token.
|
36
|
-
# (e.g. invalid signature, invalid nested token, unregistered provider, stale tokens)
|
37
|
-
class CountersignedTokenError < StandardError
|
38
|
-
attr_reader :key
|
39
|
-
def initialize(message, key)
|
40
|
-
super(message)
|
41
|
-
@key = key
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Represents an invalid recovery token.
|
46
|
-
# (e.g. invalid signature, unregistered provider, stale tokens)
|
47
|
-
class RecoveryTokenError < StandardError; end
|
48
|
-
|
49
|
-
# Represents a call to to `recovery_provider` or `account_provider` that
|
50
|
-
# has not been registered.
|
51
|
-
class UnknownProviderError < ArgumentError; end
|
52
|
-
|
53
|
-
include Constants
|
54
|
-
|
55
|
-
class << self
|
56
|
-
REQUIRED_CRYPTO_OPS = [:sign, :verify, :encrypt, :decrypt].freeze
|
57
|
-
# recovery provider data is only loaded (and cached) upon use.
|
58
|
-
attr_accessor :recovery_providers, :account_providers, :cache, :allow_unsafe_urls,
|
59
|
-
:privacy_policy, :icon_152px, :authority
|
60
|
-
|
61
|
-
# Find and load remote recovery provider configuration data.
|
62
|
-
#
|
63
|
-
# provider_origin: the origin that contains the config data in a well-known
|
64
|
-
# location.
|
65
|
-
def recovery_provider(provider_origin)
|
66
|
-
unless self.recovery_providers
|
67
|
-
raise "No recovery providers configured"
|
68
|
-
end
|
69
|
-
if self.recovery_providers.include?(provider_origin)
|
70
|
-
RecoveryProvider.new(provider_origin).load
|
71
|
-
else
|
72
|
-
raise UnknownProviderError, "Unknown recovery provider: #{provider_origin}"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Permit an origin to act as a recovery provider.
|
77
|
-
#
|
78
|
-
# provider_origin: the origin to permit
|
79
|
-
def register_recovery_provider(provider_origin)
|
80
|
-
self.recovery_providers ||= []
|
81
|
-
self.recovery_providers << provider_origin
|
82
|
-
end
|
83
|
-
|
84
|
-
# Find and load remote account provider configuration data.
|
85
|
-
#
|
86
|
-
# provider_origin: the origin that contains the config data in a well-known
|
87
|
-
# location.
|
88
|
-
def account_provider(provider_origin)
|
89
|
-
unless self.account_providers
|
90
|
-
raise "No account providers configured"
|
91
|
-
end
|
92
|
-
if self.account_providers.include?(provider_origin)
|
93
|
-
AccountProvider.new(provider_origin).load
|
94
|
-
else
|
95
|
-
raise UnknownProviderError, "Unknown account provider: #{provider_origin}"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Permit an origin to act as an account provider.
|
100
|
-
#
|
101
|
-
# account_origin: the origin to permit
|
102
|
-
def register_account_provider(account_origin)
|
103
|
-
self.account_providers ||= []
|
104
|
-
self.account_providers << account_origin
|
105
|
-
end
|
106
|
-
|
107
|
-
# Provide a reference to the account provider configuration for this web app
|
108
|
-
def this_account_provider
|
109
|
-
AccountProvider.this
|
110
|
-
end
|
111
|
-
|
112
|
-
# Provide a reference to the recovery provider configuration for this web app
|
113
|
-
def this_recovery_provider
|
114
|
-
RecoveryProvider.this
|
115
|
-
end
|
116
|
-
|
117
|
-
# Returns a hash of all configuration values, recovery and account provider.
|
118
|
-
def account_and_recovery_provider_config
|
119
|
-
provider_data = Darrrr.this_account_provider.try(:to_h) || {}
|
120
|
-
|
121
|
-
if Darrrr.this_recovery_provider
|
122
|
-
provider_data.merge!(recovery_provider_config) do |key, lhs, rhs|
|
123
|
-
unless lhs == rhs
|
124
|
-
raise ArgumentError, "inconsistent config value detected #{key}: #{lhs} != #{rhs}"
|
125
|
-
end
|
126
|
-
|
127
|
-
lhs
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
provider_data
|
132
|
-
end
|
133
|
-
|
134
|
-
# returns the account provider information in hash form
|
135
|
-
def account_provider_config
|
136
|
-
this_account_provider.to_h
|
137
|
-
end
|
138
|
-
|
139
|
-
# returns the account provider information in hash form
|
140
|
-
def recovery_provider_config
|
141
|
-
this_recovery_provider.to_h
|
142
|
-
end
|
143
|
-
|
144
|
-
def with_encryptor(encryptor)
|
145
|
-
raise ArgumentError, "A block must be supplied" unless block_given?
|
146
|
-
unless valid_encryptor?(encryptor)
|
147
|
-
raise ArgumentError, "custom encryption class must respond to all of #{REQUIRED_CRYPTO_OPS}"
|
148
|
-
end
|
149
|
-
|
150
|
-
Thread.current[:darrrr_encryptor] = encryptor
|
151
|
-
yield
|
152
|
-
ensure
|
153
|
-
Thread.current[:darrrr_encryptor] = nil
|
154
|
-
end
|
155
|
-
|
156
|
-
# Returns the crypto API to be used. A thread local instance overrides the
|
157
|
-
# globally configured value which overrides the default encryptor.
|
158
|
-
def encryptor
|
159
|
-
Thread.current[:darrrr_encryptor] || @encryptor || DefaultEncryptor
|
160
|
-
end
|
161
|
-
|
162
|
-
# Overrides the global `encryptor` API to use
|
163
|
-
#
|
164
|
-
# encryptor: a class/module that responds to all +REQUIRED_CRYPTO_OPS+.
|
165
|
-
def custom_encryptor=(encryptor)
|
166
|
-
if valid_encryptor?(encryptor)
|
167
|
-
@encryptor = encryptor
|
168
|
-
else
|
169
|
-
raise ArgumentError, "custom encryption class must respond to all of #{REQUIRED_CRYPTO_OPS}"
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
private def valid_encryptor?(encryptor)
|
174
|
-
REQUIRED_CRYPTO_OPS.all? {|m| encryptor.respond_to?(m)}
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
@@ -1,140 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GitHub
|
4
|
-
module DelegatedAccountRecovery
|
5
|
-
class AccountProvider
|
6
|
-
include CryptoHelper
|
7
|
-
include Provider
|
8
|
-
|
9
|
-
# Only applicable when acting as a recovery provider
|
10
|
-
PRIVATE_FIELDS = [:symmetric_key, :signing_private_key]
|
11
|
-
|
12
|
-
FIELDS = [:tokensign_pubkeys_secp256r1].freeze
|
13
|
-
URL_FIELDS = [:issuer, :save_token_return, :recover_account_return,
|
14
|
-
:privacy_policy, :icon_152px].freeze
|
15
|
-
|
16
|
-
# These are the fields required by the spec
|
17
|
-
REQUIRED_FIELDS = FIELDS + URL_FIELDS
|
18
|
-
|
19
|
-
attr_accessor(*REQUIRED_FIELDS)
|
20
|
-
attr_accessor(*PRIVATE_FIELDS)
|
21
|
-
|
22
|
-
alias :origin :issuer
|
23
|
-
|
24
|
-
# The CryptoHelper defines an `unseal` method that requires us to
|
25
|
-
# define a `unseal_keys` method that will return the set of keys that
|
26
|
-
# are valid when verifying the signature on a sealed key.
|
27
|
-
def unseal_keys
|
28
|
-
tokensign_pubkeys_secp256r1
|
29
|
-
end
|
30
|
-
|
31
|
-
# Used to serve content at /.well-known/delegated-account-recovery/configuration
|
32
|
-
def to_h
|
33
|
-
{
|
34
|
-
"issuer" => self.issuer,
|
35
|
-
"tokensign-pubkeys-secp256r1" => self.tokensign_pubkeys_secp256r1.dup,
|
36
|
-
"save-token-return" => self.save_token_return,
|
37
|
-
"recover-account-return" => self.recover_account_return,
|
38
|
-
"privacy-policy" => self.privacy_policy,
|
39
|
-
"icon-152px" => self.icon_152px
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
|
-
# Generates a binary token with an encrypted arbitrary data payload.
|
44
|
-
#
|
45
|
-
# data: value to encrypt in the token
|
46
|
-
# provider: the recovery provider/audience of the token
|
47
|
-
# binding data: binding data value retrieved from recovery provider to
|
48
|
-
# provide some assurance the same browser was used.
|
49
|
-
def generate_recovery_token(data:, audience:)
|
50
|
-
RecoveryToken.build(issuer: self, audience: audience, type: RECOVERY_TOKEN_TYPE).tap do |token|
|
51
|
-
token.data = Darrrr.encryptor.encrypt(data)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Parses a countersigned_token and returns the nested recovery token
|
56
|
-
# WITHOUT verifying any signatures. This should only be used if no user
|
57
|
-
# context can be identified or if we're extracting issuer information.
|
58
|
-
def dangerous_unverified_recovery_token(countersigned_token)
|
59
|
-
parsed_countersigned_token = RecoveryToken.parse(Base64.strict_decode64(countersigned_token))
|
60
|
-
RecoveryToken.parse(parsed_countersigned_token.data)
|
61
|
-
end
|
62
|
-
|
63
|
-
# Validates the countersigned recovery token by verifying the signature
|
64
|
-
# of the countersigned token, parsing out the origin recovery token,
|
65
|
-
# verifying the signature on the recovery token, and finally decrypting
|
66
|
-
# the data in the origin recovery token.
|
67
|
-
#
|
68
|
-
# countersigned_token: our original recovery token wrapped in recovery
|
69
|
-
# token instance that is signed by the recovery provider.
|
70
|
-
#
|
71
|
-
# returns a verified recovery token or raises
|
72
|
-
# an error if the token fails validation.
|
73
|
-
def validate_countersigned_recovery_token!(countersigned_token)
|
74
|
-
# 5. Validate the the issuer field is present in the token,
|
75
|
-
# and that it matches the audience field in the original countersigned token.
|
76
|
-
begin
|
77
|
-
recovery_provider = RecoveryToken.recovery_provider_issuer(Base64.strict_decode64(countersigned_token))
|
78
|
-
rescue RecoveryTokenSerializationError => e
|
79
|
-
raise CountersignedTokenError.new("Countersigned token is invalid: " + e.message, :countersigned_token_parse_error)
|
80
|
-
rescue UnknownProviderError => e
|
81
|
-
raise CountersignedTokenError.new(e.message, :recovery_token_invalid_issuer)
|
82
|
-
end
|
83
|
-
|
84
|
-
# 1. Parse the countersigned-token.
|
85
|
-
# 2. Validate that the version field is 0.
|
86
|
-
# 7. Retrieve the current Recovery Provider configuration as described in Section 2.
|
87
|
-
# 8. Validate that the counter-signed token signature validates with a current element of the countersign-pubkeys-secp256r1 array.
|
88
|
-
begin
|
89
|
-
parsed_countersigned_token = recovery_provider.unseal(Base64.strict_decode64(countersigned_token))
|
90
|
-
rescue TokenFormatError => e
|
91
|
-
raise CountersignedTokenError.new(e.message, :countersigned_invalid_token_version)
|
92
|
-
rescue CryptoError
|
93
|
-
raise CountersignedTokenError.new("Countersigned token has an invalid signature", :countersigned_invalid_signature)
|
94
|
-
end
|
95
|
-
|
96
|
-
# 3. De-serialize the original recovery token from the data field.
|
97
|
-
# 4. Validate the signature on the original recovery token.
|
98
|
-
begin
|
99
|
-
recovery_token = self.unseal(parsed_countersigned_token.data)
|
100
|
-
rescue RecoveryTokenSerializationError => e
|
101
|
-
raise CountersignedTokenError.new("Nested recovery token is invalid: " + e.message, :recovery_token_token_parse_error)
|
102
|
-
rescue TokenFormatError => e
|
103
|
-
raise CountersignedTokenError.new("Nested recovery token format error: #{e.message}", :recovery_token_invalid_token_type)
|
104
|
-
rescue CryptoError
|
105
|
-
raise CountersignedTokenError.new("Nested recovery token has an invalid signature", :recovery_token_invalid_signature)
|
106
|
-
end
|
107
|
-
|
108
|
-
# 5. Validate the the issuer field is present in the countersigned-token,
|
109
|
-
# and that it matches the audience field in the original token.
|
110
|
-
|
111
|
-
countersigned_token_issuer = parsed_countersigned_token.issuer
|
112
|
-
if countersigned_token_issuer.blank? || countersigned_token_issuer != recovery_token.audience || recovery_provider.origin != countersigned_token_issuer
|
113
|
-
raise CountersignedTokenError.new("Validate the the issuer field is present in the countersigned-token, and that it matches the audience field in the original token", :recovery_token_invalid_issuer)
|
114
|
-
end
|
115
|
-
|
116
|
-
# 6. Validate the token binding for the countersigned token, if present.
|
117
|
-
# (the token binding for the inner token is not relevant)
|
118
|
-
# TODO not required, to be implemented later
|
119
|
-
|
120
|
-
# 9. Decrypt the data field from the original recovery token and parse its information, if present.
|
121
|
-
begin
|
122
|
-
recovery_token.decode
|
123
|
-
rescue CryptoError
|
124
|
-
raise CountersignedTokenError.new("Recovery token data could not be decrypted", :indecipherable_opaque_data)
|
125
|
-
end
|
126
|
-
|
127
|
-
# 10. Apply any additional processing which provider-specific data in the opaque data portion may indicate is necessary.
|
128
|
-
begin
|
129
|
-
if DateTime.parse(parsed_countersigned_token.issued_time).utc < (Time.now - CLOCK_SKEW).utc
|
130
|
-
raise CountersignedTokenError.new("Countersigned recovery token issued at time is too far in the past", :stale_token)
|
131
|
-
end
|
132
|
-
rescue ArgumentError
|
133
|
-
raise CountersignedTokenError.new("Invalid countersigned token issued time", :invalid_issued_time)
|
134
|
-
end
|
135
|
-
|
136
|
-
recovery_token
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GitHub
|
4
|
-
module DelegatedAccountRecovery
|
5
|
-
module Constants
|
6
|
-
PROTOCOL_VERSION = 0
|
7
|
-
PRIME_256_V1 = "prime256v1" # AKA secp256r1
|
8
|
-
GROUP = OpenSSL::PKey::EC::Group.new(PRIME_256_V1)
|
9
|
-
DIGEST = OpenSSL::Digest::SHA256
|
10
|
-
TOKEN_ID_BYTE_LENGTH = 16
|
11
|
-
RECOVERY_TOKEN_TYPE = 0
|
12
|
-
COUNTERSIGNED_RECOVERY_TOKEN_TYPE = 1
|
13
|
-
WELL_KNOWN_CONFIG_PATH = ".well-known/delegated-account-recovery/configuration"
|
14
|
-
CLOCK_SKEW = 5 * 60
|
15
|
-
end
|
16
|
-
|
17
|
-
include Constants
|
18
|
-
end
|
19
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GitHub
|
4
|
-
module DelegatedAccountRecovery
|
5
|
-
module CryptoHelper
|
6
|
-
include Constants
|
7
|
-
# Signs the provided token and joins the data with the signature.
|
8
|
-
#
|
9
|
-
# token: a RecoveryToken instance
|
10
|
-
#
|
11
|
-
# returns a base64 value for the binary token string and the signature
|
12
|
-
# of the token.
|
13
|
-
def seal(token)
|
14
|
-
raise RuntimeError, "signing private key must be set" unless self.signing_private_key
|
15
|
-
binary_token = token.to_binary_s
|
16
|
-
signature = Darrrr.encryptor.sign(binary_token, self.signing_private_key)
|
17
|
-
Base64.strict_encode64([binary_token, signature].join)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Splits the payload by the token size, treats the remaining portion as
|
21
|
-
# the signature of the payload, and verifies the signature is valid for
|
22
|
-
# the given payload.
|
23
|
-
#
|
24
|
-
# token_and_signature: binary string consisting of [token_binary_str, signature].join
|
25
|
-
# keys - An array of public keys to use for signature verification.
|
26
|
-
#
|
27
|
-
# returns a RecoveryToken if the payload has been verified and
|
28
|
-
# deserializes correctly. Raises exceptions if any crypto fails.
|
29
|
-
# Raises an error if the token's version field is not valid.
|
30
|
-
def unseal(token_and_signature)
|
31
|
-
token = RecoveryToken.parse(token_and_signature)
|
32
|
-
|
33
|
-
unless token.version.to_i == PROTOCOL_VERSION
|
34
|
-
raise TokenFormatError, "Version field must be #{PROTOCOL_VERSION}"
|
35
|
-
end
|
36
|
-
|
37
|
-
token_data, signature = partition_signed_token(token_and_signature, token)
|
38
|
-
self.unseal_keys.each do |key|
|
39
|
-
return token if Darrrr.encryptor.verify(token_data, signature, key)
|
40
|
-
end
|
41
|
-
raise CryptoError, "Recovery token signature was invalid"
|
42
|
-
end
|
43
|
-
|
44
|
-
# Split the binary token into the token data and the signature over the
|
45
|
-
# data.
|
46
|
-
#
|
47
|
-
# token_and_signature: binary serialization of the token and signature for the token
|
48
|
-
# recovery_token: a RecoveryToken object parsed from token_and_signature
|
49
|
-
#
|
50
|
-
# returns a two element array of [token, signature]
|
51
|
-
private def partition_signed_token(token_and_signature, recovery_token)
|
52
|
-
token_length = recovery_token.num_bytes
|
53
|
-
[token_and_signature[0...token_length], token_and_signature[token_length..-1]]
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|