lastpass 1.5.0 → 1.6.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 +5 -5
- data/.travis.yml +3 -0
- data/CHANGELOG.md +7 -0
- data/lib/lastpass/blob.rb +4 -2
- data/lib/lastpass/fetcher.rb +15 -10
- data/lib/lastpass/parser.rb +40 -25
- data/lib/lastpass/session.rb +4 -2
- data/lib/lastpass/vault.rb +10 -13
- data/lib/lastpass/version.rb +1 -1
- data/spec/blob_spec.rb +3 -1
- data/spec/fetcher_spec.rb +11 -11
- data/spec/parser_spec.rb +5 -4
- data/spec/session_spec.rb +3 -1
- data/spec/vault_spec.rb +2 -2
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7051161f3612e54ae85ce0930e91973c84fd12d0395c3c4703d18c78151d49c6
|
4
|
+
data.tar.gz: b586d087e2697a0f4f63d3157677bd85b6583828ab124b13d43d4c5dd65a6a0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3fff4b432c45316ee806e854d4b95c8c0e7e77671bed0d65ce091a611217bfd90878fd6e995845f3551d9927f0ecc4dbd3fafd88236f82dd6d2f00fd9a4f996
|
7
|
+
data.tar.gz: 32ef67cac6f64a4478210ba82eedf21a34e4e7f3edc1b66601db97473c675b6b662b4d91d0a5e746df2c66f194eb6feb341918528260c3a70d901bf78e4d89a4
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/lib/lastpass/blob.rb
CHANGED
@@ -4,11 +4,13 @@
|
|
4
4
|
module LastPass
|
5
5
|
class Blob
|
6
6
|
attr_reader :bytes,
|
7
|
-
:key_iteration_count
|
7
|
+
:key_iteration_count,
|
8
|
+
:encrypted_private_key
|
8
9
|
|
9
|
-
def initialize bytes, key_iteration_count
|
10
|
+
def initialize bytes, key_iteration_count, encrypted_private_key
|
10
11
|
@bytes = bytes
|
11
12
|
@key_iteration_count = key_iteration_count
|
13
|
+
@encrypted_private_key = encrypted_private_key
|
12
14
|
end
|
13
15
|
|
14
16
|
def encryption_key username, password
|
data/lib/lastpass/fetcher.rb
CHANGED
@@ -9,20 +9,22 @@ module LastPass
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.logout session, web_client = http
|
12
|
-
response = web_client.get "https://lastpass.com/logout.php?
|
12
|
+
response = web_client.get "https://lastpass.com/logout.php?method=cli&noredirect=1",
|
13
13
|
cookies: {"PHPSESSID" => URI.encode(session.id)}
|
14
14
|
|
15
15
|
raise NetworkError unless response.response.is_a? Net::HTTPOK
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.fetch session, web_client = http
|
19
|
-
response = web_client.get "https://lastpass.com/getaccts.php?mobile=1&b64=1&hash=0.0&hasplugin=3.0.23&requestsrc=
|
19
|
+
response = web_client.get "https://lastpass.com/getaccts.php?mobile=1&b64=1&hash=0.0&hasplugin=3.0.23&requestsrc=cli",
|
20
20
|
format: :plain,
|
21
21
|
cookies: {"PHPSESSID" => URI.encode(session.id)}
|
22
22
|
|
23
23
|
raise NetworkError unless response.response.is_a? Net::HTTPOK
|
24
24
|
|
25
|
-
Blob.new decode_blob(response.parsed_response),
|
25
|
+
Blob.new decode_blob(response.parsed_response),
|
26
|
+
session.key_iteration_count,
|
27
|
+
session.encrypted_private_key
|
26
28
|
end
|
27
29
|
|
28
30
|
def self.request_iteration_count username, web_client = http
|
@@ -50,12 +52,12 @@ module LastPass
|
|
50
52
|
web_client = http
|
51
53
|
|
52
54
|
body = {
|
53
|
-
method: "
|
54
|
-
|
55
|
-
xml: 1,
|
55
|
+
method: "cli",
|
56
|
+
xml: 2,
|
56
57
|
username: username,
|
57
58
|
hash: make_hash(username, password, key_iteration_count),
|
58
|
-
iterations: key_iteration_count
|
59
|
+
iterations: key_iteration_count,
|
60
|
+
includeprivatekeyenc: 1
|
59
61
|
}
|
60
62
|
|
61
63
|
body[:otp] = multifactor_password if multifactor_password
|
@@ -75,11 +77,14 @@ module LastPass
|
|
75
77
|
end
|
76
78
|
|
77
79
|
def self.create_session parsed_response, key_iteration_count
|
78
|
-
ok = parsed_response["ok"]
|
80
|
+
ok = (parsed_response["response"] || {})["ok"]
|
79
81
|
if ok.is_a? Hash
|
80
82
|
session_id = ok["sessionid"]
|
81
83
|
if session_id.is_a? String
|
82
|
-
|
84
|
+
private_key = ok["privatekeyenc"]
|
85
|
+
private_key = nil if private_key == ""
|
86
|
+
|
87
|
+
return Session.new session_id, key_iteration_count, private_key
|
83
88
|
end
|
84
89
|
end
|
85
90
|
|
@@ -95,7 +100,7 @@ module LastPass
|
|
95
100
|
"unknownpassword" => LastPassInvalidPasswordError,
|
96
101
|
"googleauthrequired" => LastPassIncorrectGoogleAuthenticatorCodeError,
|
97
102
|
"googleauthfailed" => LastPassIncorrectGoogleAuthenticatorCodeError,
|
98
|
-
"
|
103
|
+
"otprequired" => LastPassIncorrectYubikeyPasswordError,
|
99
104
|
}
|
100
105
|
|
101
106
|
cause = error["cause"]
|
data/lib/lastpass/parser.rb
CHANGED
@@ -62,30 +62,6 @@ module LastPass
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
# Parse PRIK chunk which contains private RSA key
|
66
|
-
def self.parse_PRIK chunk, encryption_key
|
67
|
-
decrypted = decode_aes256 "cbc",
|
68
|
-
encryption_key[0, 16],
|
69
|
-
decode_hex(chunk.payload),
|
70
|
-
encryption_key
|
71
|
-
|
72
|
-
/^LastPassPrivateKey<(?<hex_key>.*)>LastPassPrivateKey$/ =~ decrypted
|
73
|
-
asn1_encoded_all = OpenSSL::ASN1.decode decode_hex hex_key
|
74
|
-
asn1_encoded_key = OpenSSL::ASN1.decode asn1_encoded_all.value[2].value
|
75
|
-
|
76
|
-
rsa_key = OpenSSL::PKey::RSA.new
|
77
|
-
rsa_key.n = asn1_encoded_key.value[1].value
|
78
|
-
rsa_key.e = asn1_encoded_key.value[2].value
|
79
|
-
rsa_key.d = asn1_encoded_key.value[3].value
|
80
|
-
rsa_key.p = asn1_encoded_key.value[4].value
|
81
|
-
rsa_key.q = asn1_encoded_key.value[5].value
|
82
|
-
rsa_key.dmp1 = asn1_encoded_key.value[6].value
|
83
|
-
rsa_key.dmq1 = asn1_encoded_key.value[7].value
|
84
|
-
rsa_key.iqmp = asn1_encoded_key.value[8].value
|
85
|
-
|
86
|
-
rsa_key
|
87
|
-
end
|
88
|
-
|
89
65
|
# TODO: Fake some data and make a test
|
90
66
|
def self.parse_SHAR chunk, encryption_key, rsa_key
|
91
67
|
StringIO.open chunk.payload do |io|
|
@@ -112,6 +88,45 @@ module LastPass
|
|
112
88
|
end
|
113
89
|
end
|
114
90
|
|
91
|
+
# Parse and decrypt the encrypted private RSA key
|
92
|
+
def self.parse_private_key encrypted_private_key, encryption_key
|
93
|
+
decrypted = decode_aes256 "cbc",
|
94
|
+
encryption_key[0, 16],
|
95
|
+
decode_hex(encrypted_private_key),
|
96
|
+
encryption_key
|
97
|
+
|
98
|
+
/^LastPassPrivateKey<(?<hex_key>.*)>LastPassPrivateKey$/ =~ decrypted
|
99
|
+
asn1_encoded_all = OpenSSL::ASN1.decode decode_hex hex_key
|
100
|
+
asn1_encoded_key = OpenSSL::ASN1.decode asn1_encoded_all.value[2].value
|
101
|
+
|
102
|
+
rsa_key = OpenSSL::PKey::RSA.new
|
103
|
+
n = asn1_encoded_key.value[1].value
|
104
|
+
e = asn1_encoded_key.value[2].value
|
105
|
+
d = asn1_encoded_key.value[3].value
|
106
|
+
p = asn1_encoded_key.value[4].value
|
107
|
+
q = asn1_encoded_key.value[5].value
|
108
|
+
dmp1 = asn1_encoded_key.value[6].value
|
109
|
+
dmq1 = asn1_encoded_key.value[7].value
|
110
|
+
iqmp = asn1_encoded_key.value[8].value
|
111
|
+
|
112
|
+
if rsa_key.respond_to? :set_key
|
113
|
+
rsa_key.set_key n, e, d
|
114
|
+
rsa_key.set_factors p, q
|
115
|
+
rsa_key.set_crt_params dmp1, dmq1, iqmp
|
116
|
+
else
|
117
|
+
rsa_key.n = n
|
118
|
+
rsa_key.e = e
|
119
|
+
rsa_key.d = d
|
120
|
+
rsa_key.p = p
|
121
|
+
rsa_key.q = q
|
122
|
+
rsa_key.dmp1 = dmp1
|
123
|
+
rsa_key.dmq1 = dmq1
|
124
|
+
rsa_key.iqmp = iqmp
|
125
|
+
end
|
126
|
+
|
127
|
+
rsa_key
|
128
|
+
end
|
129
|
+
|
115
130
|
def self.parse_secure_note_server notes
|
116
131
|
info = {}
|
117
132
|
|
@@ -275,7 +290,7 @@ module LastPass
|
|
275
290
|
# Allowed ciphers are: :ecb, :cbc.
|
276
291
|
# If for :ecb iv is not used and should be set to "".
|
277
292
|
def self.decode_aes256 cipher, iv, data, encryption_key
|
278
|
-
aes = OpenSSL::Cipher
|
293
|
+
aes = OpenSSL::Cipher.new "aes-256-#{cipher}"
|
279
294
|
aes.decrypt
|
280
295
|
aes.key = encryption_key
|
281
296
|
aes.iv = iv
|
data/lib/lastpass/session.rb
CHANGED
@@ -4,11 +4,13 @@
|
|
4
4
|
module LastPass
|
5
5
|
class Session
|
6
6
|
attr_reader :id,
|
7
|
-
:key_iteration_count
|
7
|
+
:key_iteration_count,
|
8
|
+
:encrypted_private_key
|
8
9
|
|
9
|
-
def initialize id, key_iteration_count
|
10
|
+
def initialize id, key_iteration_count, encrypted_private_key
|
10
11
|
@id = id
|
11
12
|
@key_iteration_count = key_iteration_count
|
13
|
+
@encrypted_private_key = encrypted_private_key
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/lastpass/vault.rb
CHANGED
@@ -11,12 +11,6 @@ module LastPass
|
|
11
11
|
open blob, username, password
|
12
12
|
end
|
13
13
|
|
14
|
-
# Creates a vault from a locally stored blob
|
15
|
-
def self.open_local blob_filename, username, password
|
16
|
-
# TODO: read the blob here
|
17
|
-
raise NotImplementedError
|
18
|
-
end
|
19
|
-
|
20
14
|
# Creates a vault from a blob object
|
21
15
|
def self.open blob, username, password
|
22
16
|
new blob, blob.encryption_key(username, password)
|
@@ -38,18 +32,21 @@ module LastPass
|
|
38
32
|
raise InvalidResponseError, "Blob is truncated"
|
39
33
|
end
|
40
34
|
|
41
|
-
|
35
|
+
private_key = nil
|
36
|
+
if blob.encrypted_private_key
|
37
|
+
private_key = Parser.parse_private_key blob.encrypted_private_key, encryption_key
|
38
|
+
end
|
39
|
+
|
40
|
+
@accounts = parse_accounts chunks, encryption_key, private_key
|
42
41
|
end
|
43
42
|
|
44
43
|
def complete? chunks
|
45
44
|
!chunks.empty? && chunks.last.id == "ENDM" && chunks.last.payload == "OK"
|
46
45
|
end
|
47
46
|
|
48
|
-
def parse_accounts chunks, encryption_key
|
47
|
+
def parse_accounts chunks, encryption_key, private_key
|
49
48
|
accounts = []
|
50
|
-
|
51
49
|
key = encryption_key
|
52
|
-
rsa_private_key = nil
|
53
50
|
|
54
51
|
chunks.each do |i|
|
55
52
|
case i.id
|
@@ -59,11 +56,11 @@ module LastPass
|
|
59
56
|
if account
|
60
57
|
accounts << account
|
61
58
|
end
|
62
|
-
when "PRIK"
|
63
|
-
rsa_private_key = Parser.parse_PRIK i, encryption_key
|
64
59
|
when "SHAR"
|
60
|
+
raise "private_key must be provided" if !private_key
|
61
|
+
|
65
62
|
# After SHAR chunk all the folliwing accounts are enrypted with a new key
|
66
|
-
key = Parser.parse_SHAR(i, encryption_key,
|
63
|
+
key = Parser.parse_SHAR(i, encryption_key, private_key)[:encryption_key]
|
67
64
|
end
|
68
65
|
end
|
69
66
|
|
data/lib/lastpass/version.rb
CHANGED
data/spec/blob_spec.rb
CHANGED
@@ -6,14 +6,16 @@ require "spec_helper"
|
|
6
6
|
describe LastPass::Blob do
|
7
7
|
let(:bytes) { "TFBBVgAAAAMxMjJQUkVNAAAACjE0MTQ5".decode64 }
|
8
8
|
let(:key_iteration_count) { 500 }
|
9
|
+
let(:encrypted_private_key) { "DEADBEEF" }
|
9
10
|
let(:username) { "postlass@gmail.com" }
|
10
11
|
let(:password) { "pl1234567890" }
|
11
12
|
let(:encryption_key) { "OfOUvVnQzB4v49sNh4+PdwIFb9Fr5+jVfWRTf+E2Ghg=".decode64 }
|
12
13
|
|
13
|
-
subject { LastPass::Blob.new bytes, key_iteration_count }
|
14
|
+
subject { LastPass::Blob.new bytes, key_iteration_count, encrypted_private_key }
|
14
15
|
|
15
16
|
its(:bytes) { should eq bytes }
|
16
17
|
its(:key_iteration_count) { should eq key_iteration_count }
|
18
|
+
its(:encrypted_private_key) { should eq encrypted_private_key }
|
17
19
|
|
18
20
|
describe "#encryption_key" do
|
19
21
|
it "returns encryption key" do
|
data/spec/fetcher_spec.rb
CHANGED
@@ -10,18 +10,18 @@ describe LastPass::Fetcher do
|
|
10
10
|
|
11
11
|
let(:hash) { "7880a04588cfab954aa1a2da98fd9c0d2c6eba4c53e36a94510e6dbf30759256" }
|
12
12
|
let(:session_id) { "53ru,Hb713QnEVM5zWZ16jMvxS0" }
|
13
|
-
let(:session) { LastPass::Session.new session_id, key_iteration_count }
|
13
|
+
let(:session) { LastPass::Session.new session_id, key_iteration_count, "DEADBEEF" }
|
14
14
|
|
15
15
|
let(:blob_response) { "TFBBVgAAAAMxMjJQUkVNAAAACjE0MTQ5" }
|
16
16
|
let(:blob_bytes) { blob_response.decode64 }
|
17
|
-
let(:blob) { LastPass::Blob.new blob_bytes, key_iteration_count }
|
17
|
+
let(:blob) { LastPass::Blob.new blob_bytes, key_iteration_count, "DEADBEEF" }
|
18
18
|
|
19
|
-
let(:login_post_data) { {method: "
|
20
|
-
|
21
|
-
xml: 1,
|
19
|
+
let(:login_post_data) { {method: "cli",
|
20
|
+
xml: 2,
|
22
21
|
username: username,
|
23
22
|
hash: hash,
|
24
|
-
iterations: key_iteration_count
|
23
|
+
iterations: key_iteration_count,
|
24
|
+
includeprivatekeyenc: 1} }
|
25
25
|
|
26
26
|
let(:device_id) { "492378378052455" }
|
27
27
|
let(:login_post_data_with_device_id) { login_post_data.merge({imei: device_id}) }
|
@@ -36,7 +36,7 @@ describe LastPass::Fetcher do
|
|
36
36
|
it "makes a GET request" do
|
37
37
|
web_client = double "web_client"
|
38
38
|
expect(web_client).to receive(:get)
|
39
|
-
.with("https://lastpass.com/logout.php?
|
39
|
+
.with("https://lastpass.com/logout.php?method=cli&noredirect=1", cookies: {"PHPSESSID" => session_id})
|
40
40
|
.and_return(http_ok "")
|
41
41
|
LastPass::Fetcher.logout session, web_client
|
42
42
|
end
|
@@ -98,7 +98,7 @@ describe LastPass::Fetcher do
|
|
98
98
|
web_client = double("web_client")
|
99
99
|
expect(web_client).to receive(:post)
|
100
100
|
.with("https://lastpass.com/login.php", format: :xml, body: post_data)
|
101
|
-
.and_return(http_ok("ok" => {"sessionid" => session_id}))
|
101
|
+
.and_return(http_ok("response" => {"ok" => {"sessionid" => session_id, "privatekeyenc" => "DEADBEEF"}}))
|
102
102
|
|
103
103
|
LastPass::Fetcher.request_login username,
|
104
104
|
password,
|
@@ -125,7 +125,7 @@ describe LastPass::Fetcher do
|
|
125
125
|
end
|
126
126
|
|
127
127
|
it "returns a session" do
|
128
|
-
expect(request_login_with_xml "<ok sessionid='#{session_id}'
|
128
|
+
expect(request_login_with_xml "<response><ok sessionid='#{session_id}' /></response>").to eq session
|
129
129
|
end
|
130
130
|
|
131
131
|
it "raises an exception on HTTP error" do
|
@@ -177,7 +177,7 @@ describe LastPass::Fetcher do
|
|
177
177
|
it "raises an exception on missing/incorrect Yubikey password" do
|
178
178
|
message = "Your account settings have restricted you from logging in " +
|
179
179
|
"from mobile devices that do not support YubiKey authentication."
|
180
|
-
expect { request_login_with_lastpass_error "
|
180
|
+
expect { request_login_with_lastpass_error "otprequired", message }
|
181
181
|
.to raise_error LastPass::LastPassIncorrectYubikeyPasswordError, message
|
182
182
|
end
|
183
183
|
|
@@ -197,7 +197,7 @@ describe LastPass::Fetcher do
|
|
197
197
|
describe ".fetch" do
|
198
198
|
it "makes a GET request" do
|
199
199
|
expect(web_client = double("web_client")).to receive(:get)
|
200
|
-
.with("https://lastpass.com/getaccts.php?mobile=1&b64=1&hash=0.0&hasplugin=3.0.23&requestsrc=
|
200
|
+
.with("https://lastpass.com/getaccts.php?mobile=1&b64=1&hash=0.0&hasplugin=3.0.23&requestsrc=cli",
|
201
201
|
format: :plain,
|
202
202
|
cookies: {"PHPSESSID" => session_id})
|
203
203
|
.and_return(http_ok(blob_response))
|
data/spec/parser_spec.rb
CHANGED
@@ -6,7 +6,7 @@ require_relative "test_data"
|
|
6
6
|
|
7
7
|
describe LastPass::Parser do
|
8
8
|
let(:key_iteration_count) { 5000 }
|
9
|
-
let(:blob) { LastPass::Blob.new TEST_BLOB, key_iteration_count }
|
9
|
+
let(:blob) { LastPass::Blob.new TEST_BLOB, key_iteration_count, "DEADBEEF" }
|
10
10
|
let(:padding) { "BEEFFACE"}
|
11
11
|
let(:encryption_key) { "OfOUvVnQzB4v49sNh4+PdwIFb9Fr5+jVfWRTf+E2Ghg=".decode64 }
|
12
12
|
let(:encoded_rsa_key) { "98F3F5518AE7C03EBBF195A616361619033509FB1FFA0408E883B7C5E80381F8" +
|
@@ -114,9 +114,10 @@ describe LastPass::Parser do
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
-
describe ".
|
118
|
-
let(:
|
119
|
-
|
117
|
+
describe ".parse_private_key" do
|
118
|
+
let(:rsa_key) {
|
119
|
+
LastPass::Parser.parse_private_key encoded_rsa_key, rsa_key_encryption_key
|
120
|
+
}
|
120
121
|
|
121
122
|
it "parses private key" do
|
122
123
|
expect(rsa_key).to be_instance_of OpenSSL::PKey::RSA
|
data/spec/session_spec.rb
CHANGED
@@ -6,9 +6,11 @@ require "spec_helper"
|
|
6
6
|
describe LastPass::Session do
|
7
7
|
let(:id) { "53ru,Hb713QnEVM5zWZ16jMvxS0" }
|
8
8
|
let(:key_iteration_count) { 5000 }
|
9
|
+
let(:encrypted_private_key) { "DEADBEEF" }
|
9
10
|
|
10
|
-
subject { LastPass::Session.new id, key_iteration_count }
|
11
|
+
subject { LastPass::Session.new id, key_iteration_count, encrypted_private_key }
|
11
12
|
|
12
13
|
its(:id) { should eq id }
|
13
14
|
its(:key_iteration_count) { should eq key_iteration_count }
|
15
|
+
its(:encrypted_private_key) { should eq encrypted_private_key }
|
14
16
|
end
|
data/spec/vault_spec.rb
CHANGED
@@ -6,7 +6,7 @@ require "test_data"
|
|
6
6
|
|
7
7
|
describe LastPass::Vault do
|
8
8
|
let(:vault) {
|
9
|
-
LastPass::Vault.new LastPass::Blob.new(TEST_BLOB, TEST_KEY_ITERATION_COUNT),
|
9
|
+
LastPass::Vault.new LastPass::Blob.new(TEST_BLOB, TEST_KEY_ITERATION_COUNT, nil),
|
10
10
|
TEST_ENCRYPTION_KEY
|
11
11
|
}
|
12
12
|
|
@@ -15,7 +15,7 @@ describe LastPass::Vault do
|
|
15
15
|
[1, 2, 3, 4, 5, 10, 100, 1000].each do |i|
|
16
16
|
expect {
|
17
17
|
blob = TEST_BLOB[0..(-1 - i)]
|
18
|
-
LastPass::Vault.new LastPass::Blob.new(blob, TEST_KEY_ITERATION_COUNT),
|
18
|
+
LastPass::Vault.new LastPass::Blob.new(blob, TEST_KEY_ITERATION_COUNT, nil),
|
19
19
|
TEST_ENCRYPTION_KEY
|
20
20
|
}.to raise_error LastPass::InvalidResponseError, "Blob is truncated"
|
21
21
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lastpass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Yakimenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -138,8 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
140
|
requirements: []
|
141
|
-
|
142
|
-
rubygems_version: 2.5.1
|
141
|
+
rubygems_version: 3.0.3
|
143
142
|
signing_key:
|
144
143
|
specification_version: 4
|
145
144
|
summary: Unofficial LastPass API
|