Uh oh!
There was an error while loading. Please reload this page.
This repository was archived by the owner on Aug 1, 2023. It is now read-only.
- Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathblockstack.rb
More file actions
Latest commit
137 lines (109 loc) · 4.41 KB
/
Copy pathblockstack.rb
File metadata and controls
137 lines (109 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
require'blockstack/version'
require'blockstack/user'
require'bitcoin'
require'faraday'
require'jwtb'
moduleBlockstack
classInvalidAuthResponse < StandardError;end
USER_AGENT="blockstack-ruby #{VERSION}"
ALGORITHM='ES256K'
REQUIRED_CLAIMS=%w(issiatjtiexpusernameprofilepublic_keys)
DEFAULT_LEEWAY=30# seconds
DEFAULT_VALID_WITHIN=30# seconds
DEFAULT_API='https://core.blockstack.org'
defself.api=(api)
@api=api || DEFAULT_API
end
defself.api
@api
end
defself.leeway=(leeway)
@leeway=leeway || DEFAULT_LEEWAY
end
defself.leeway
@leeway
end
defself.valid_within=(valid_within)
@valid_within=valid_within || DEFAULT_VALID_WITHIN
end
defself.valid_within
@valid_within
end
# decode & verify token without checking signature so we can extract
# public keys
defself.verify_without_signature(auth_token)
public_key=nil
verify=false
decoded_tokens=JWTB.decodeauth_token,public_key,verify,algorithm: ALGORITHM
decoded_tokens[0]
end
# decode & verify signature
defself.verify_with_signature(auth_token,public_keys)
compressed_hex_public_key=public_keys[0]
bignum=OpenSSL::BN.new(compressed_hex_public_key,16)
group=OpenSSL::PKey::EC::Group.new'secp256k1'
public_key=OpenSSL::PKey::EC::Point.new(group,bignum)
ecdsa_key=OpenSSL::PKey::EC.new'secp256k1'
ecdsa_key.public_key=public_key
verify=true
decoded_tokens=JWTB.decodeauth_token,ecdsa_key,verify,algorithm: ALGORITHM,exp_leeway: leeway
decoded_tokens[0]
end
defself.verify_auth_response(auth_token)
decoded_token=verify_without_signature(auth_token)
REQUIRED_CLAIMS.eachdo |field|
failInvalidAuthResponse.new("Missing required '#{field}' claim.")unlessdecoded_token.key?(field.to_s)
end
failInvalidAuthResponse.new("Missing required 'iat' claim.")unlessdecoded_token['iat']
failInvalidAuthResponse.new("'iat' timestamp claim is skewed too far from present.")if(Time.now.to_i - decoded_token['iat']).abs > valid_within
public_keys=decoded_token['public_keys']
failInvalidAuthResponse.new('Invalid public_keys array: only 1 key is supported')unlesspublic_keys.length == 1
decoded_token=verify_with_signature(auth_token,public_keys)
failInvalidAuthResponse.new("Public keys don't match issuer address")unlessself.public_keys_match_issuer?(decoded_token)
failInvalidAuthResponse.new("Public keys don't match owner of claimed username")unlessself.public_keys_match_username?(decoded_token)
returndecoded_token
rescueJWTB::VerificationError
raiseInvalidAuthResponse.new('Signature on JWT is invalid')
rescueJWTB::DecodeError
raiseInvalidAuthResponse.new('Unable to decode JWT')
rescueRuntimeError=>error
raiseInvalidAuthResponse.new(error.message)
end
defself.get_did_type(decentralized_id)
did_parts=decentralized_id.split(':')
fail'Decentralized IDs must have 3 parts'ifdid_parts.length != 3
fail'Decentralized IDs must start with "did"'ifdid_parts[0].downcase != 'did'
did_parts[1].downcase
end
defself.get_address_from_did(decentralized_id)
did_type=get_did_type(decentralized_id)
returnnilifdid_type != 'btc-addr'
decentralized_id.split(':')[2]
end
defself.public_keys_match_issuer?(decoded_token)
public_keys=decoded_token['public_keys']
address_from_issuer=get_address_from_did(decoded_token['iss'])
fail'Multiple public keys are not supported'unlesspublic_keys.count == 1
address_from_public_keys=Bitcoin.pubkey_to_address(public_keys.first)
address_from_issuer == address_from_public_keys
end
defself.public_keys_match_username?(decoded_token)
username=decoded_token['username']
returntrueifusername.nil?
response=Faraday.get"#{api}/v1/names/#{username}"
json=JSON.parseresponse.body
fail"Issuer claimed username that doesn't exist"ifresponse.status == 404
fail"Unable to verify issuer's claimed username"ifresponse.status != 200
name_owning_address=json['address']
address_from_issuer=get_address_from_diddecoded_token['iss']
name_owning_address == address_from_issuer
end
defself.faraday
connection=Faraday.new
connection.headers[:user_agent]=USER_AGENT
connection
end
@leeway=DEFAULT_LEEWAY
@valid_within=DEFAULT_VALID_WITHIN
@api=DEFAULT_API
end