keybase-core 0.1.2

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.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +19 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +49 -0
  7. data/LICENSE +23 -0
  8. data/README.md +137 -0
  9. data/Rakefile +20 -0
  10. data/keybase.gemspec +25 -0
  11. data/lib/keybase.rb +49 -0
  12. data/lib/keybase/crypto.rb +20 -0
  13. data/lib/keybase/error.rb +37 -0
  14. data/lib/keybase/models/dump.rb +33 -0
  15. data/lib/keybase/models/user.rb +168 -0
  16. data/lib/keybase/request/base.rb +32 -0
  17. data/lib/keybase/request/dump/all.rb +10 -0
  18. data/lib/keybase/request/dump/latest.rb +10 -0
  19. data/lib/keybase/request/key/add.rb +13 -0
  20. data/lib/keybase/request/key/revoke.rb +14 -0
  21. data/lib/keybase/request/root/get_salt_and_login_session.rb +12 -0
  22. data/lib/keybase/request/root/login.rb +14 -0
  23. data/lib/keybase/request/sig/post_auth.rb +13 -0
  24. data/lib/keybase/request/user/lookup.rb +10 -0
  25. data/lib/keybase/response.rb +14 -0
  26. data/lib/keybase/token_store.rb +32 -0
  27. data/test/all.rb +6 -0
  28. data/test/fixtures/example_dump.json +9 -0
  29. data/test/fixtures/example_user.json +50 -0
  30. data/test/integration/dumps_test.rb +23 -0
  31. data/test/integration/keys_test.rb +26 -0
  32. data/test/integration/login_users_test.rb +18 -0
  33. data/test/integration/lookup_users_test.rb +42 -0
  34. data/test/integration_test_helper.rb +6 -0
  35. data/test/test_helper.rb +17 -0
  36. data/test/unit/lib/keybase/crypto_test.rb +45 -0
  37. data/test/unit/lib/keybase/error_test.rb +32 -0
  38. data/test/unit/lib/keybase/models/dump_test.rb +31 -0
  39. data/test/unit/lib/keybase/models/user_test.rb +136 -0
  40. data/test/unit/lib/keybase/requests/base_test.rb +60 -0
  41. data/test/unit/lib/keybase/requests/dump/all_test.rb +17 -0
  42. data/test/unit/lib/keybase/requests/dump/latest_test.rb +17 -0
  43. data/test/unit/lib/keybase/requests/key/add_test.rb +19 -0
  44. data/test/unit/lib/keybase/requests/key/revoke_test.rb +19 -0
  45. data/test/unit/lib/keybase/requests/root/get_salt_test.rb +23 -0
  46. data/test/unit/lib/keybase/requests/root/login_test.rb +19 -0
  47. data/test/unit/lib/keybase/requests/sig/post_auth_test.rb +19 -0
  48. data/test/unit/lib/keybase/requests/user/lookup_test.rb +17 -0
  49. data/test/unit/lib/keybase/response_test.rb +23 -0
  50. data/test/unit/lib/keybase/token_store_test.rb +25 -0
  51. data/test/unit/lib/keybase_test.rb +25 -0
  52. data/test/vcr_cassettes/dumps.yml +49418 -0
  53. data/test/vcr_cassettes/keys.yml +349 -0
  54. data/test/vcr_cassettes/user_login.yml +176 -0
  55. data/test/vcr_cassettes/user_lookup_foo.yml +89 -0
  56. data/test/vcr_cassettes/user_lookup_invalid.yml +52 -0
  57. data/test/vcr_cassettes/user_lookup_missing.yml +52 -0
  58. data/test/vcr_cassettes/user_lookup_not_found.yml +55 -0
  59. metadata +200 -0
@@ -0,0 +1,168 @@
1
+ module Keybase
2
+ # A Keybase user containing all attributes you have permission to see
3
+ class User
4
+
5
+ attr_reader :id, :basics, :invitation_stats, :profile, :emails,
6
+ :public_keys, :private_keys
7
+
8
+ def initialize(params)
9
+ @id = params['id']
10
+ @invitation_stats = OpenStruct.new(params['invitation_stats'])
11
+
12
+ set_basics(params['basics']) if params['basics']
13
+ set_profile(params['profile']) if params['profile']
14
+ set_emails(params['emails']) if params['emails']
15
+ set_public_keys(params['public_keys']) if params['public_keys']
16
+ set_private_keys(params['private_keys']) if params['private_keys']
17
+ end
18
+
19
+ # Lookup a user on Keybase
20
+ #
21
+ # @param [String] username the username of the user you are searching for
22
+ # @raise [Keybase::UserNotFoundError] if the user is not found
23
+ # @raise [Keybase::InputError] if the username is empty or invalid
24
+ # @return [Keybase::Model::User] the user, if they exist
25
+ def self.lookup(username)
26
+ new(Request::User.lookup(username))
27
+ end
28
+
29
+ # Login to Keybase
30
+ #
31
+ # @param [String] email_or_username the email or username of the account
32
+ # @param [String] passphrase the passphrase for the account
33
+ # @raise [Keybase::UserNotFoundError] if the user is not found
34
+ # @raise [Keybase::InputError] if the submitted parameters are empty or invalid
35
+ # @raise [Keybase::BadPasswordError] if the submitted passphrase is incorrect
36
+ # @return [Keybase::Model::User] the user, if login is successful
37
+ def self.login(email_or_username, passphrase)
38
+ salt, login_session = Request::Root.get_salt_and_login_session(email_or_username)
39
+ pwh = Crypto.scrypt(passphrase, salt)
40
+ hmac_pwh = Crypto.hmac_sha512(pwh, login_session)
41
+ response = Request::Root.login(email_or_username, hmac_pwh, login_session)
42
+ return new(response['me'])
43
+ end
44
+
45
+ # Post a self-signed authentication certificate to Keybase
46
+ #
47
+ # This requires login first.
48
+ #
49
+ # The payload of the signature should take the form of other keybase signatures,
50
+ # like self-signing keys, or proving ownership of remote accounts.
51
+ #
52
+ # An example looks like:
53
+ #
54
+ # {
55
+ # "body": {
56
+ # "key": {
57
+ # "fingerprint": "da99a6ebeca98b14d944cb6e1ca9bfeab344f0fc",
58
+ # "host": "keybase.io",
59
+ # "key_id": "1ca9bfeab344f0fc",
60
+ # "uid": "15a9e2826313eaf005291a1ae00c3f00",
61
+ # "username": "taco422107"
62
+ # },
63
+ # "nonce": null,
64
+ # "type": "auth",
65
+ # "version": 1
66
+ # },
67
+ # "ctime": 1386537779,
68
+ # "expire_in": 86400,
69
+ # "tag": "signature"
70
+ # }
71
+ #
72
+ # The client can provide an optional nonce to randomize the signatures. The server
73
+ # will check the signature for validatity, and on success, will return an auth_token,
74
+ # which is the SHA-256 hash of the full signature body, from the "---- BEGIN" all the
75
+ # way through to the ---- END PGP MESSAGE ----.
76
+ #
77
+ # @param [String] sig the whole certificate contents
78
+ # @raise [Keybase::InputError] if the certificate is empty or invalid
79
+ # @raise [Keybase::BadSessionError] if the session is not valid
80
+ # @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
81
+ # @return [String] the authentication token
82
+ def post_auth(sig)
83
+ Request::Sig.post_auth(basics.username, sig)
84
+ end
85
+
86
+ # Add a new public key to Keybase
87
+ #
88
+ # This requires login first.
89
+ #
90
+ # @param [String] key the public key
91
+ # @raise [Keybase::InputError] if the key is empty or invalid
92
+ # @raise [Keybase::BadSessionError] if the session is not valid
93
+ # @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
94
+ # @return [String] The Key ID for the uploaded key
95
+ def add_public_key(key)
96
+ Request::Key.add(public_key: key)
97
+ end
98
+
99
+ # Add a new private key to Keybase
100
+ #
101
+ # This requires login first.
102
+ #
103
+ # @param [String] key the private key
104
+ # @raise [Keybase::InputError] if the key is empty or invalid
105
+ # @raise [Keybase::BadSessionError] if the session is not valid
106
+ # @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
107
+ # @return [String] The Key ID for the uploaded key
108
+ def add_private_key(key)
109
+ Request::Key.add(private_key: key)
110
+ end
111
+
112
+ # Revoke a key from Keybase
113
+ #
114
+ # This requires login first.
115
+ #
116
+ # Currently the key is simply deleted - full revokation is due in later
117
+ # revisions of the API.
118
+ #
119
+ # @param [String] kid the key id to be revoked
120
+ # @raise [Keybase::InputError] if the key id is empty or invalid
121
+ # @raise [Keybase::BadSessionError] if the session is not valid
122
+ # @raise [Keybase::CSRFVerificationError] if the CSRF token is not valid
123
+ # @return [Boolean] success
124
+ def revoke_key(kid)
125
+ Request::Key.revoke(kid)
126
+ end
127
+
128
+ private
129
+
130
+ def set_basics(params)
131
+ @basics = OpenStruct.new(params.merge(created_at: nil, updated_at: nil))
132
+ @basics.created_at = Time.at(@basics.ctime).to_datetime if @basics.ctime
133
+ @basics.updated_at = Time.at(@basics.mtime).to_datetime if @basics.mtime
134
+ end
135
+
136
+ def set_profile(params)
137
+ @profile = OpenStruct.new(params.merge(updated_at: nil))
138
+ @profile.updated_at = Time.at(@profile.mtime).to_datetime if @profile.mtime
139
+ end
140
+
141
+ def set_emails(params)
142
+ @emails = OpenStruct.new(params)
143
+ params.each do |k,v|
144
+ v.merge!('is_verified?' => (v['is_verified'] == 1 ? true : false))
145
+ @emails.send("#{k}=".to_sym, OpenStruct.new(v))
146
+ end
147
+ end
148
+
149
+ def set_public_keys(params)
150
+ @public_keys = OpenStruct.new(params)
151
+ params.each do |k,v|
152
+ v.merge!('created_at' => Time.at(v['ctime']).to_datetime)
153
+ v.merge!('updated_at' => Time.at(v['mtime']).to_datetime)
154
+ @public_keys.send("#{k}=".to_sym, OpenStruct.new(v))
155
+ end
156
+ end
157
+
158
+ def set_private_keys(params)
159
+ @private_keys = OpenStruct.new(params)
160
+ params.each do |k,v|
161
+ v.merge!('created_at' => Time.at(v['ctime']).to_datetime)
162
+ v.merge!('updated_at' => Time.at(v['mtime']).to_datetime)
163
+ @private_keys.send("#{k}=".to_sym, OpenStruct.new(v))
164
+ end
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,32 @@
1
+ module Keybase
2
+ # @private
3
+ module Request
4
+ API_BASE_URL = 'https://keybase.io'
5
+ class Base
6
+
7
+ def self.get(url, params={})
8
+ Keybase::Response.new(conn.get(url, params)).body
9
+ end
10
+
11
+ def self.post(url, params={})
12
+ response = Keybase::Response.new(conn.post(url, params))
13
+ TokenStore.cookie = response.cookie if response.cookie
14
+ response.body
15
+ end
16
+
17
+ private
18
+
19
+ def self.conn
20
+ Faraday.new(:url => API_BASE_URL) do |faraday|
21
+ faraday.path_prefix = "/_/api/1.0"
22
+ faraday.request :url_encoded
23
+ faraday.headers['Cookie'] = TokenStore.cookie if TokenStore.cookie
24
+ faraday.headers['X-CSRF-Token'] = TokenStore.csrf if TokenStore.csrf
25
+ # faraday.response :logger
26
+ faraday.adapter Faraday.default_adapter
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ # GET dump/all.json
2
+ module Keybase
3
+ module Request
4
+ class Dump < Base
5
+ def self.all
6
+ get('dump/all.json', {})['dumps']
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # GET dump/latest.json
2
+ module Keybase
3
+ module Request
4
+ class Dump < Base
5
+ def self.latest
6
+ get('dump/latest.json', {})['dump']
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # POST /key/add.json
2
+ # public_key: "-----BEGIN PGP PUBLIC..."
3
+ # private_key: "hKRib2R5gqRwcml2gqRkY..."
4
+ # is_primary: true
5
+ module Keybase
6
+ module Request
7
+ class Key < Base
8
+ def self.add(params)
9
+ post('key/add.json', params.merge(is_primary: true))['kid']
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # POST /key/revoke.json
2
+ # revocation_type: 0
3
+ # kid: "a140c70404a13370f7..."
4
+ module Keybase
5
+ module Request
6
+ class Key < Base
7
+ def self.revoke(kid)
8
+ post('key/revoke.json', revocation_type: 0,
9
+ csrf_token: TokenStore.csrf, kid: kid)
10
+ true
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # GET /getsalt.json?email_or_username=EMAIL_OR_USERNAME
2
+ module Keybase
3
+ module Request
4
+ class Root < Base
5
+ def self.get_salt_and_login_session(email_or_username)
6
+ result = get('getsalt.json', email_or_username: email_or_username)
7
+ TokenStore.csrf = result['csrf_token']
8
+ return [result['salt'], result['login_session']]
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # POST /login.json
2
+ # email_or_username: "chris"
3
+ # hmac_pwh: "38902fe098f..."
4
+ # login_session: "lgHZIwfee99..."
5
+ module Keybase
6
+ module Request
7
+ class Root < Base
8
+ def self.login(email_or_username, hmac_pwh, login_session)
9
+ post('login.json', email_or_username: email_or_username,
10
+ hmac_pwh: hmac_pwh, login_session: login_session)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # POST sig/post_auth.json
2
+ # sig : "----- BEGIN PGP MESSAGE ----- ..."
3
+ # email_or_username : "maxtaco"
4
+ module Keybase
5
+ module Request
6
+ class Sig < Base
7
+ def self.post_auth(email_or_username, sig)
8
+ post('sig/post_auth.json', email_or_username: email_or_username,
9
+ sig: sig)['auth_token']
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # GET /user/lookup.json?username=USERNAME
2
+ module Keybase
3
+ module Request
4
+ class User < Base
5
+ def self.lookup(username)
6
+ get('user/lookup.json', username: username)['them']
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module Keybase
2
+ # @private
3
+ class Response
4
+
5
+ attr_reader :body, :cookie
6
+
7
+ def initialize(server_response)
8
+ @body = JSON.parse(server_response.body)
9
+ Error.raise_unless_successful(@body['status'])
10
+ @cookie = server_response.headers['Set-Cookie'] ? server_response.headers['Set-Cookie'] : nil
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ module Keybase
2
+ # @private
3
+ class TokenStore
4
+
5
+ attr_accessor :csrf, :cookie
6
+
7
+ def self.csrf
8
+ instance.csrf
9
+ end
10
+
11
+ def self.csrf=(csrf)
12
+ instance.csrf = csrf
13
+ end
14
+
15
+ def self.cookie
16
+ instance.cookie
17
+ end
18
+
19
+ def self.cookie=(cookie)
20
+ instance.cookie = cookie
21
+ end
22
+
23
+ def self.instance
24
+ @@instance ||= new
25
+ end
26
+
27
+ private
28
+
29
+ def initialize ; end
30
+
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(dir)
3
+
4
+ Dir["#{dir}/**/*_test.rb"].sort.each do |file|
5
+ require file.sub(/^#{dir}\/(.*)\.rb$/, '\1')
6
+ end
@@ -0,0 +1,9 @@
1
+ {
2
+ "dump_id": "472eba4586993583b395b00e",
3
+ "full_data": "https://s3.amazonaws.com/keybase_data_dumps/2014-02-24-13-46-31-...json",
4
+ "full_data_sha256": "bea8863f1b17d35d7db4480197886e2f2d9f8deb0633c5f2adcc427541b79bd7",
5
+ "parent_dump_id": "c56698ea60027c2e9f98620e",
6
+ "changes_from_parent": "https://s3.amazonaws.com/keybase_data_dumps/2014-02-...json",
7
+ "changes_sha256": "88a248688299eb2fd25d1031d35abca8243ad5100fc614a213d300512fd2b634",
8
+ "ctime": 1393267591
9
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "bf5f1650fc1361a",
3
+ "basics": {
4
+ "uid": "55c079bf5f1650fc1361a1c3a4709900",
5
+ "salt": "533a6842479b5e925856d1ce2a444...",
6
+ "username": "chris",
7
+ "ctime": 1383161061,
8
+ "mtime": 1383162061
9
+ },
10
+ "invitation_stats": {
11
+ "available": 3,
12
+ "used": 2,
13
+ "power": 1,
14
+ "open": 0
15
+ },
16
+ "profile": {
17
+ "full_name": "Chris Coyne",
18
+ "location": null,
19
+ "bio": "I am the terror that flaps in the night.",
20
+ "mtime": 1383247339
21
+ },
22
+ "emails": {
23
+ "primary": {
24
+ "email": "chris@okcupid.com",
25
+ "is_verified": 1
26
+ },
27
+ "secondary": {
28
+ "email": "chris@theinter.net",
29
+ "is_verified": 0
30
+ }
31
+ },
32
+ "public_keys": {
33
+ "primary": {
34
+ "kid": "d028ac1542b24a5f77f123ba467873e3ce96a992570a",
35
+ "key_type": 1,
36
+ "bundle": "-----BEGIN PGP PUBLIC KEY BLOCK-----\\nVersion => Keybase OpenPGP JS 0.0.1\\nComment => https => //keybase.io\\n\\nxo0EUnlbBAEEALU9OLTEIA0h2o8NZc+0b9f8RaCa8nLfqm3cKNZ3aAV9iYLkeHnd\\nqhP99c+VTiB3Rtjeqf9hZAqKCIJlVrURPgN/mPRzRk5u4OmI/vw/HMn+3lBLwTJZ\\n+FQGEGvLvzBz9wEpcM3IKrXl70BJbq/uM3SP0fsu60/fIy4twEvYOS9JABEBAAHN\\nI2tleWJhc2UuaW8vY2hyaXMgPGNocmlzQGtleWJhc2UuaW8+wrMEEAEKAB0FAlJ5\\nWwQCGy8FCRLMAwADCwkHAxUKCAIeAQIXgAAKCRAb5d5e+VB/cndDA/0Rgsu84CpW\\nN2lwvjCr/MJM9MV8Y75NAl5SFArKksg1d4DYH40M0boJuHETk3AR60bHDRlrhS5/\\nUPm9+A3PAa4IRXKw967e/8nhH3o8mhstDRCMJUrNfo2so5JzFSgWQaflDta0kE/E\\nNlUEOBqmt9Y2f/elhuxiGa0GkoBpJja2ks6NBFJ5WwQBBACva+Om+XqIrBMyDO54\\nNptSiNnjEEJoFJ6hMCqR3S88Kj/Jzu3jTiY+sqOsCDqMOv83AiKWBEL7U28eqhb7\\nwUEOL9nbKOWsQVvCqtla8vgQBLPZ3kYxs1CfJCnaC/bT6abh8+OHECSTENmurSV0\\na22IlMNJrU+3DZ/A3nrca8GGcQARAQABwsCDBBgBCgAPBQJSeVsEBQkB4TOAAhsu\\nAKgJEBvl3l75UH9ynSAEGQEKAAYFAlJ5WwQACgkQl6sBE/Y073xyYwQAryX0fcCA\\nO/ZCZi3pC26CWUtu6Vr6uSl9VSIRQFqcNoZ7PIcjQwGif/iHbBSap1wDkRBlMKkd\\n76vJi67z3etGZ5bJUb4iG/IJUOx6Yn2RGFtyV6N3t1w84dafqaR3cuOsT+UPYSIB\\njxgUPqfN68hqhW0ZBjXCva/P3dPhLulm6zY1AgQAqNDxGPui5OfNBzczq2raoF8E\\nyVJ1gRtP1eGJv6LZ01ABCSJg4h+tgKZuVHIX+i+rcqOY3ABEYWrhfjJBuVNPkM2I\\nFoMllSH7/ywNv8k8PvNBtybqrMiIUJyGrtLvjKhxmD+yxxvPcHWbTkearCycY4QW\\nVlLXYoBhF0AWKph5v/M=\\n=3FfV\\n-----END PGP PUBLIC KEY BLOCK-----\\n",
37
+ "mtime": 1383685212,
38
+ "ctime": 1383685212
39
+ }
40
+ },
41
+ "private_keys": {
42
+ "primary": {
43
+ "kid": "d028ac1542b24a5f77f123ba467873e3ce96a992570a",
44
+ "key_type": 2,
45
+ "bundle": "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",
46
+ "mtime": 1383685212,
47
+ "ctime": 1383685212
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,23 @@
1
+ require_relative '../test_helper'
2
+ require_relative '../integration_test_helper'
3
+
4
+ module Keybase
5
+ class DumpsIntegrationTest < Minitest::Test
6
+
7
+ def setup
8
+ VCR.use_cassette('dumps') do
9
+ @all = Keybase.dump_all
10
+ @latest = Keybase.dump_latest
11
+ end
12
+ end
13
+
14
+ def test_latest_dump_object_has_expected_fields
15
+ assert @latest.respond_to? :id
16
+ end
17
+
18
+ def test_first_dump_object_has_expected_fields
19
+ assert @all[0].respond_to? :id
20
+ end
21
+
22
+ end
23
+ end