security 0.1.5 → 0.3.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/Gemfile +6 -0
- data/Gemfile.lock +55 -39
- data/README.md +47 -7
- data/UPGRADE.md +7 -0
- data/lib/security/command.rb +52 -0
- data/lib/security/error.rb +16 -0
- data/lib/security/keychain.rb +18 -11
- data/lib/security/password.rb +34 -13
- data/lib/security/version.rb +1 -1
- data/lib/security.rb +2 -0
- data/security.gemspec +5 -10
- metadata +12 -81
- data/spec/certificate_spec.rb +0 -15
- data/spec/keychain_spec.rb +0 -48
- data/spec/password_spec.rb +0 -102
- data/spec/spec_helper.rb +0 -106
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c157bfbd48ec870177f4a269569efc87edad32606bc9a7332b4d26644a13edf9
|
|
4
|
+
data.tar.gz: 275fbb21545cde55939d4512cec08d3c52bcff6a96d5300cb746941813685bed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f8a12a0decc2ab2e277816af45860be94894bf2b01dc307e5f4d3b88251d8a6676dbf5c0469af862c19a73679a8d444eb1b8502345756ac3595c35d7e38e9fb
|
|
7
|
+
data.tar.gz: e2b77b3d17ec02afb5c99d1a69d638633ab83026777fbb7c883fe4c8ae4b6896f269f38df28208508e15af937ea549eb7a9f95ecd82a32472956b3334605b13c
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,58 +1,74 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
security (0.
|
|
4
|
+
security (0.3.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
-
ast (2.4.
|
|
10
|
-
diff-lcs (1.
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
ast (2.4.3)
|
|
10
|
+
diff-lcs (1.6.2)
|
|
11
|
+
docile (1.4.1)
|
|
12
|
+
json (3.0.2)
|
|
13
|
+
language_server-protocol (3.17.0.6)
|
|
14
|
+
lint_roller (1.1.0)
|
|
15
|
+
parallel (2.2.0)
|
|
16
|
+
parser (3.3.12.0)
|
|
13
17
|
ast (~> 2.4.1)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
rspec-
|
|
22
|
-
|
|
23
|
-
rspec-
|
|
24
|
-
rspec-
|
|
18
|
+
racc
|
|
19
|
+
prism (1.9.0)
|
|
20
|
+
racc (1.8.1)
|
|
21
|
+
rainbow (3.1.1)
|
|
22
|
+
rake (13.4.2)
|
|
23
|
+
regexp_parser (2.12.0)
|
|
24
|
+
rspec (3.13.2)
|
|
25
|
+
rspec-core (~> 3.13.0)
|
|
26
|
+
rspec-expectations (~> 3.13.0)
|
|
27
|
+
rspec-mocks (~> 3.13.0)
|
|
28
|
+
rspec-core (3.13.6)
|
|
29
|
+
rspec-support (~> 3.13.0)
|
|
30
|
+
rspec-expectations (3.13.5)
|
|
25
31
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
26
|
-
rspec-support (~> 3.
|
|
27
|
-
rspec-
|
|
28
|
-
rspec-core (~> 3.0)
|
|
29
|
-
rspec-mocks (3.10.2)
|
|
32
|
+
rspec-support (~> 3.13.0)
|
|
33
|
+
rspec-mocks (3.13.8)
|
|
30
34
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
31
|
-
rspec-support (~> 3.
|
|
32
|
-
rspec-support (3.
|
|
33
|
-
rubocop (1.
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
rspec-support (~> 3.13.0)
|
|
36
|
+
rspec-support (3.13.7)
|
|
37
|
+
rubocop (1.91.0)
|
|
38
|
+
json (>= 2.3)
|
|
39
|
+
language_server-protocol (~> 3.17.0.2)
|
|
40
|
+
lint_roller (~> 1.1.0)
|
|
41
|
+
parallel (>= 1.10)
|
|
42
|
+
parser (>= 3.3.0.2)
|
|
36
43
|
rainbow (>= 2.2.2, < 4.0)
|
|
37
|
-
regexp_parser (>=
|
|
38
|
-
|
|
39
|
-
rubocop-ast (>= 1.2.0, < 2.0)
|
|
44
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
45
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
40
46
|
ruby-progressbar (~> 1.7)
|
|
41
|
-
unicode-display_width (>=
|
|
42
|
-
rubocop-ast (1.
|
|
43
|
-
parser (>=
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
48
|
+
rubocop-ast (1.50.0)
|
|
49
|
+
parser (>= 3.3.7.2)
|
|
50
|
+
prism (~> 1.7)
|
|
51
|
+
ruby-progressbar (1.13.0)
|
|
52
|
+
simplecov (0.22.0)
|
|
53
|
+
docile (~> 1.1)
|
|
54
|
+
simplecov-html (~> 0.11)
|
|
55
|
+
simplecov_json_formatter (~> 0.1)
|
|
56
|
+
simplecov-html (0.13.2)
|
|
57
|
+
simplecov_json_formatter (0.1.4)
|
|
58
|
+
unicode-display_width (3.2.0)
|
|
59
|
+
unicode-emoji (~> 4.1)
|
|
60
|
+
unicode-emoji (4.2.0)
|
|
46
61
|
|
|
47
62
|
PLATFORMS
|
|
48
|
-
|
|
63
|
+
ruby
|
|
64
|
+
x86_64-linux
|
|
49
65
|
|
|
50
66
|
DEPENDENCIES
|
|
51
|
-
rake (~>
|
|
52
|
-
rspec
|
|
53
|
-
|
|
54
|
-
rubocop
|
|
67
|
+
rake (~> 13.4)
|
|
68
|
+
rspec (~> 3.13)
|
|
69
|
+
rubocop (~> 1.90)
|
|
55
70
|
security!
|
|
71
|
+
simplecov (~> 0.22)
|
|
56
72
|
|
|
57
73
|
BUNDLED WITH
|
|
58
|
-
2.
|
|
74
|
+
2.6.9
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Security
|
|
2
2
|
|
|
3
|
-
[](https://github.com/fastlane-community/security/actions/workflows/ci.yml)
|
|
4
4
|
[](https://rubygems.org/gems/security)
|
|
5
5
|
[](https://github.com/fastlane-community/security/blob/main/LICENSE.md)
|
|
6
6
|
|
|
@@ -14,14 +14,54 @@
|
|
|
14
14
|
```ruby
|
|
15
15
|
require 'security'
|
|
16
16
|
|
|
17
|
-
Security::Keychain
|
|
17
|
+
Security::Keychain.default_keychain.filename #=> "/Users/jappleseed/Library/Keychains/login.keychain-db"
|
|
18
18
|
|
|
19
|
-
Security::InternetPassword.find(server: "itunesconnect.apple.com")
|
|
19
|
+
item = Security::InternetPassword.find(server: "itunesconnect.apple.com")
|
|
20
|
+
item&.password #=> "p4ssw0rd"
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
+
## Keychains
|
|
24
|
+
|
|
25
|
+
`find`, `add` and `delete` all take an optional `keychain:`, naming the keychain
|
|
26
|
+
to act on. It accepts a `Security::Keychain` or a path. Without one, `security`
|
|
27
|
+
adds to the default keychain and searches the default search list.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
keychain = Security::Keychain.new("/path/to/build.keychain-db")
|
|
31
|
+
|
|
32
|
+
Security::InternetPassword.add("example.com", "jappleseed", "p4ssw0rd", keychain: keychain)
|
|
33
|
+
Security::InternetPassword.find(server: "example.com", keychain: keychain)
|
|
34
|
+
Security::InternetPassword.delete(server: "example.com", keychain: keychain)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Errors
|
|
38
|
+
|
|
39
|
+
The `security` command line tool reports failures through its exit status, and
|
|
40
|
+
this library distinguishes the two cases a caller needs to tell apart:
|
|
23
41
|
|
|
24
|
-
|
|
42
|
+
- **Nothing matched.** `find` returns `nil`. The keychain answered, and it holds
|
|
43
|
+
no such item.
|
|
44
|
+
- **The question could not be answered.** `find` raises `Security::Error`,
|
|
45
|
+
carrying the tool's exit `status` and its `output`. A locked keychain, a
|
|
46
|
+
keychain this process is not allowed to read, or a malformed request all
|
|
47
|
+
land here.
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
begin
|
|
51
|
+
item = Security::InternetPassword.find(server: "itunesconnect.apple.com")
|
|
52
|
+
rescue Security::Error => e
|
|
53
|
+
warn "could not read the keychain: #{e.message}"
|
|
54
|
+
item = nil
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`Keychain.list`, `Keychain.default_keychain` and `Keychain.login_keychain`
|
|
59
|
+
raise `Security::Error` on failure in the same way.
|
|
60
|
+
|
|
61
|
+
The methods that change the keychain — `add`, `delete`, and the `Keychain`
|
|
62
|
+
instance methods — return `true` or `false` and print what the tool reported,
|
|
63
|
+
the way `Kernel#system` does.
|
|
64
|
+
|
|
65
|
+
## License
|
|
25
66
|
|
|
26
|
-
[
|
|
27
|
-
[build status badge]: https://github.com/mattt/Security/workflows/CI/badge.svg
|
|
67
|
+
[MIT](https://github.com/fastlane-community/security/blob/main/LICENSE.md)
|
data/UPGRADE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Upgrade Guide
|
|
2
|
+
|
|
3
|
+
## Upgrading from 0.2 to 0.3
|
|
4
|
+
|
|
5
|
+
In previous versions, `find` attempted to detect failures by searching for a `security: ` prefix in the output. This approach was unreliable: certain errors (like ACL issues) produced no output, while others (like malformed requests) returned a usage banner. These cases would incorrectly result in a `Password` object with `nil` attributes, making it impossible for callers to distinguish a failed request from a successful one. Additionally, failures that *did* have the prefix returned `nil`, which was indistinguishable from a missing item.
|
|
6
|
+
|
|
7
|
+
Starting with version 0.3, both cases now raise a `Security::Error`. If your code relies on `nil` to handle missing keychain items, you should now rescue `Security::Error` to prevent a locked or misconfigured keychain from being treated as empty.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module Security
|
|
7
|
+
# :nodoc:
|
|
8
|
+
module Command
|
|
9
|
+
# TODO: replace with Data.define once Ruby 3.1 support is dropped.
|
|
10
|
+
# :nodoc:
|
|
11
|
+
Result = Struct.new(:stdout, :stderr, :status) do
|
|
12
|
+
def success?
|
|
13
|
+
!status.nil? && status.success?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def exitstatus
|
|
17
|
+
status&.exitstatus
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# `security` splits what it has to say across both streams: find-*-password
|
|
21
|
+
# prints the attributes on stdout and the password on stderr.
|
|
22
|
+
def output
|
|
23
|
+
stdout + stderr
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Runs a `security` subcommand and captures what it produced. A missing
|
|
30
|
+
# `security` is a failed result rather than an exception: the tool is only
|
|
31
|
+
# present on macOS, and callers elsewhere should see the same failure they
|
|
32
|
+
# would get from a keychain that could not answer.
|
|
33
|
+
def run(command)
|
|
34
|
+
Result.new(*Open3.capture3(command))
|
|
35
|
+
rescue Errno::ENOENT => e
|
|
36
|
+
# Nothing ran, but the child Ruby forked exited 127 before exec, which is
|
|
37
|
+
# what a shell reports for a missing command, and what this library
|
|
38
|
+
# produced while it still went through one.
|
|
39
|
+
Result.new('', "#{e.message}\n", $CHILD_STATUS)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Runs a `security` subcommand and lets the caller see what it said. The
|
|
43
|
+
# tool reports on stderr whether or not it succeeded: show-keychain-info
|
|
44
|
+
# prints its result there on exit 0.
|
|
45
|
+
def relay(command)
|
|
46
|
+
result = run(command)
|
|
47
|
+
warn result.stderr.chomp unless result.stderr.empty?
|
|
48
|
+
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Security
|
|
4
|
+
# :nodoc:
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :status, :output
|
|
7
|
+
|
|
8
|
+
def initialize(status, output)
|
|
9
|
+
@status = status
|
|
10
|
+
@output = output
|
|
11
|
+
|
|
12
|
+
details = output.strip
|
|
13
|
+
super(details.empty? ? "`security` exited with status #{status}" : "#{details} (status #{status})")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/security/keychain.rb
CHANGED
|
@@ -14,19 +14,19 @@ module Security
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def info
|
|
17
|
-
|
|
17
|
+
Command.relay(%(security show-keychain-info #{@filename.shellescape})).success?
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def lock
|
|
21
|
-
|
|
21
|
+
Command.relay(%(security lock-keychain #{@filename.shellescape})).success?
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def unlock(password)
|
|
25
|
-
|
|
25
|
+
Command.relay(%(security unlock-keychain -p #{password.shellescape} #{@filename.shellescape})).success?
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def delete
|
|
29
|
-
|
|
29
|
+
Command.relay(%(security delete-keychain #{@filename.shellescape})).success?
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
class << self
|
|
@@ -35,31 +35,38 @@ module Security
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def list(domain = :user)
|
|
38
|
-
raise ArgumentError "Invalid domain #{domain}, expected one of: #{DOMAINS}" unless DOMAINS.include?(domain)
|
|
38
|
+
raise ArgumentError, "Invalid domain #{domain}, expected one of: #{DOMAINS}" unless DOMAINS.include?(domain)
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
keychains_from_command("security list-keychains -d #{domain}")
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def lock
|
|
44
|
-
|
|
44
|
+
Command.relay(%(security lock-keychain -a)).success?
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def unlock(password)
|
|
48
|
-
|
|
48
|
+
Command.relay(%(security unlock-keychain -p #{password.shellescape})).success?
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def default_keychain
|
|
52
|
-
|
|
52
|
+
keychains_from_command('security default-keychain').first
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def login_keychain
|
|
56
|
-
|
|
56
|
+
keychains_from_command('security login-keychain').first
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
+
def keychains_from_command(command)
|
|
62
|
+
result = Command.run(command)
|
|
63
|
+
raise Error.new(result.exitstatus, result.stderr) unless result.success?
|
|
64
|
+
|
|
65
|
+
keychains_from_output(result.stdout)
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
def keychains_from_output(output)
|
|
62
|
-
output.split(
|
|
69
|
+
output.split("\n").collect { |line| new(line.strip.gsub(/^"|"$/, '')) }
|
|
63
70
|
end
|
|
64
71
|
end
|
|
65
72
|
end
|
data/lib/security/password.rb
CHANGED
|
@@ -5,6 +5,10 @@ require 'shellwords'
|
|
|
5
5
|
module Security
|
|
6
6
|
# :nodoc:
|
|
7
7
|
class Password
|
|
8
|
+
# `security` reports a missing item with this exit status. Every other
|
|
9
|
+
# non-zero status is a failure the caller needs to know about.
|
|
10
|
+
ITEM_NOT_FOUND = 44
|
|
11
|
+
|
|
8
12
|
attr_reader :keychain, :attributes, :password
|
|
9
13
|
|
|
10
14
|
private_class_method :new
|
|
@@ -18,13 +22,19 @@ module Security
|
|
|
18
22
|
class << self
|
|
19
23
|
private
|
|
20
24
|
|
|
21
|
-
def
|
|
22
|
-
|
|
25
|
+
def password_from_command(command)
|
|
26
|
+
result = Command.run(command)
|
|
27
|
+
return nil if result.exitstatus == ITEM_NOT_FOUND
|
|
28
|
+
raise Error.new(result.exitstatus, result.stderr) unless result.success?
|
|
23
29
|
|
|
30
|
+
password_from_output(result.output)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def password_from_output(output)
|
|
24
34
|
keychain = nil
|
|
25
35
|
attributes = {}
|
|
26
36
|
password = nil
|
|
27
|
-
output.split(
|
|
37
|
+
output.split("\n").each do |line|
|
|
28
38
|
case line
|
|
29
39
|
when /^keychain: "(.+)"/
|
|
30
40
|
keychain = Regexp.last_match(1)
|
|
@@ -44,6 +54,7 @@ module Security
|
|
|
44
54
|
|
|
45
55
|
def flags_for_options(options = {})
|
|
46
56
|
flags = options.dup
|
|
57
|
+
keychain = flags.delete(:keychain)
|
|
47
58
|
flags[:a] ||= flags.delete(:account)
|
|
48
59
|
flags[:c] ||= flags.delete(:creator)
|
|
49
60
|
flags[:C] ||= flags.delete(:type)
|
|
@@ -51,7 +62,18 @@ module Security
|
|
|
51
62
|
flags[:G] ||= flags.delete(:value)
|
|
52
63
|
flags[:j] ||= flags.delete(:comment)
|
|
53
64
|
|
|
54
|
-
|
|
65
|
+
arguments = flags.compact.collect { |k, v| "-#{k} #{v.shellescape}".strip }
|
|
66
|
+
arguments << filename_for_keychain(keychain) if keychain
|
|
67
|
+
|
|
68
|
+
arguments.join(' ')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# `security` takes the keychain to act on as a trailing argument. Without
|
|
72
|
+
# one it uses the default keychain, or the default search list.
|
|
73
|
+
def filename_for_keychain(keychain)
|
|
74
|
+
filename = keychain.is_a?(Keychain) ? keychain.filename : keychain
|
|
75
|
+
|
|
76
|
+
filename.shellescape
|
|
55
77
|
end
|
|
56
78
|
|
|
57
79
|
def decode_hex_blob(string)
|
|
@@ -68,22 +90,22 @@ module Security
|
|
|
68
90
|
options[:s] = service
|
|
69
91
|
options[:w] = password
|
|
70
92
|
|
|
71
|
-
|
|
93
|
+
Command.relay("security add-generic-password #{flags_for_options(options)}").success?
|
|
72
94
|
end
|
|
73
95
|
|
|
74
96
|
def find(options)
|
|
75
|
-
|
|
97
|
+
password_from_command("security find-generic-password -g #{flags_for_options(options)}")
|
|
76
98
|
end
|
|
77
99
|
|
|
78
100
|
def delete(options)
|
|
79
|
-
|
|
101
|
+
Command.run("security delete-generic-password #{flags_for_options(options)}").success?
|
|
80
102
|
end
|
|
81
103
|
|
|
82
104
|
private
|
|
83
105
|
|
|
84
106
|
def flags_for_options(options = {})
|
|
85
107
|
options[:s] ||= options.delete(:service)
|
|
86
|
-
super
|
|
108
|
+
super
|
|
87
109
|
end
|
|
88
110
|
end
|
|
89
111
|
end
|
|
@@ -95,16 +117,15 @@ module Security
|
|
|
95
117
|
options[:a] = account
|
|
96
118
|
options[:s] = server
|
|
97
119
|
options[:w] = password
|
|
98
|
-
|
|
99
|
-
system "security add-internet-password #{flags_for_options(options)}"
|
|
120
|
+
Command.relay("security add-internet-password #{flags_for_options(options)}").success?
|
|
100
121
|
end
|
|
101
122
|
|
|
102
123
|
def find(options)
|
|
103
|
-
|
|
124
|
+
password_from_command("security find-internet-password -g #{flags_for_options(options)}")
|
|
104
125
|
end
|
|
105
126
|
|
|
106
127
|
def delete(options)
|
|
107
|
-
|
|
128
|
+
Command.run("security delete-internet-password #{flags_for_options(options)}").success?
|
|
108
129
|
end
|
|
109
130
|
|
|
110
131
|
private
|
|
@@ -114,7 +135,7 @@ module Security
|
|
|
114
135
|
options[:p] ||= options.delete(:path)
|
|
115
136
|
options[:P] ||= options.delete(:port)
|
|
116
137
|
options[:r] ||= options.delete(:protocol)
|
|
117
|
-
super
|
|
138
|
+
super
|
|
118
139
|
end
|
|
119
140
|
end
|
|
120
141
|
end
|
data/lib/security/version.rb
CHANGED
data/lib/security.rb
CHANGED
data/security.gemspec
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require 'security'
|
|
3
|
+
require_relative 'lib/security/version'
|
|
5
4
|
|
|
6
5
|
Gem::Specification.new do |s|
|
|
7
6
|
s.name = 'security'
|
|
8
|
-
s.authors = ['Josh Holtz', 'Mattt']
|
|
7
|
+
s.authors = ['Josh Holtz', 'Mattt', 'iBotPeaches']
|
|
9
8
|
s.email = 'me@joshholtz.com'
|
|
10
9
|
s.homepage = 'https://github.com/fastlane-community/security'
|
|
11
10
|
s.version = Security::VERSION
|
|
@@ -13,14 +12,10 @@ Gem::Specification.new do |s|
|
|
|
13
12
|
s.license = 'MIT'
|
|
14
13
|
s.summary = 'Interact with the macOS Keychain'
|
|
15
14
|
|
|
16
|
-
s.files = Dir['./**/*'].
|
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
15
|
+
s.files = Dir['./**/*'].grep_v(%r{\./(bin|log|pkg|script|spec|test|vendor)})
|
|
18
16
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
19
17
|
s.require_paths = ['lib']
|
|
20
|
-
s.required_ruby_version = '>=
|
|
18
|
+
s.required_ruby_version = '>= 3.1.0'
|
|
21
19
|
|
|
22
|
-
s.
|
|
23
|
-
s.add_development_dependency 'rspec'
|
|
24
|
-
s.add_development_dependency 'rspec-github'
|
|
25
|
-
s.add_development_dependency 'rubocop'
|
|
20
|
+
s.metadata['rubygems_mfa_required'] = 'true'
|
|
26
21
|
end
|
metadata
CHANGED
|
@@ -1,79 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: security
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Josh Holtz
|
|
8
8
|
- Mattt
|
|
9
|
-
|
|
9
|
+
- iBotPeaches
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
13
|
-
dependencies:
|
|
14
|
-
- !ruby/object:Gem::Dependency
|
|
15
|
-
name: rake
|
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
requirements:
|
|
18
|
-
- - "~>"
|
|
19
|
-
- !ruby/object:Gem::Version
|
|
20
|
-
version: '12.3'
|
|
21
|
-
- - ">="
|
|
22
|
-
- !ruby/object:Gem::Version
|
|
23
|
-
version: 12.3.3
|
|
24
|
-
type: :development
|
|
25
|
-
prerelease: false
|
|
26
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
27
|
-
requirements:
|
|
28
|
-
- - "~>"
|
|
29
|
-
- !ruby/object:Gem::Version
|
|
30
|
-
version: '12.3'
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 12.3.3
|
|
34
|
-
- !ruby/object:Gem::Dependency
|
|
35
|
-
name: rspec
|
|
36
|
-
requirement: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
type: :development
|
|
42
|
-
prerelease: false
|
|
43
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
48
|
-
- !ruby/object:Gem::Dependency
|
|
49
|
-
name: rspec-github
|
|
50
|
-
requirement: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
type: :development
|
|
56
|
-
prerelease: false
|
|
57
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
- !ruby/object:Gem::Dependency
|
|
63
|
-
name: rubocop
|
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
type: :development
|
|
70
|
-
prerelease: false
|
|
71
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
description:
|
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
77
14
|
email: me@joshholtz.com
|
|
78
15
|
executables: []
|
|
79
16
|
extensions: []
|
|
@@ -84,21 +21,20 @@ files:
|
|
|
84
21
|
- "./LICENSE.md"
|
|
85
22
|
- "./README.md"
|
|
86
23
|
- "./Rakefile"
|
|
24
|
+
- "./UPGRADE.md"
|
|
87
25
|
- "./lib/security.rb"
|
|
88
26
|
- "./lib/security/certificate.rb"
|
|
27
|
+
- "./lib/security/command.rb"
|
|
28
|
+
- "./lib/security/error.rb"
|
|
89
29
|
- "./lib/security/keychain.rb"
|
|
90
30
|
- "./lib/security/password.rb"
|
|
91
31
|
- "./lib/security/version.rb"
|
|
92
32
|
- "./security.gemspec"
|
|
93
|
-
- spec/certificate_spec.rb
|
|
94
|
-
- spec/keychain_spec.rb
|
|
95
|
-
- spec/password_spec.rb
|
|
96
|
-
- spec/spec_helper.rb
|
|
97
33
|
homepage: https://github.com/fastlane-community/security
|
|
98
34
|
licenses:
|
|
99
35
|
- MIT
|
|
100
|
-
metadata:
|
|
101
|
-
|
|
36
|
+
metadata:
|
|
37
|
+
rubygems_mfa_required: 'true'
|
|
102
38
|
rdoc_options: []
|
|
103
39
|
require_paths:
|
|
104
40
|
- lib
|
|
@@ -106,19 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
106
42
|
requirements:
|
|
107
43
|
- - ">="
|
|
108
44
|
- !ruby/object:Gem::Version
|
|
109
|
-
version:
|
|
45
|
+
version: 3.1.0
|
|
110
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
47
|
requirements:
|
|
112
48
|
- - ">="
|
|
113
49
|
- !ruby/object:Gem::Version
|
|
114
50
|
version: '0'
|
|
115
51
|
requirements: []
|
|
116
|
-
rubygems_version: 3.
|
|
117
|
-
signing_key:
|
|
52
|
+
rubygems_version: 3.6.9
|
|
118
53
|
specification_version: 4
|
|
119
54
|
summary: Interact with the macOS Keychain
|
|
120
|
-
test_files:
|
|
121
|
-
- spec/certificate_spec.rb
|
|
122
|
-
- spec/keychain_spec.rb
|
|
123
|
-
- spec/password_spec.rb
|
|
124
|
-
- spec/spec_helper.rb
|
|
55
|
+
test_files: []
|
data/spec/certificate_spec.rb
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
describe Certificate do
|
|
4
|
-
describe '#find' do
|
|
5
|
-
it 'should raise NotImplementedError' do
|
|
6
|
-
expect { Certificate.find }.to raise_error(NotImplementedError)
|
|
7
|
-
end
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
describe '#initialize' do
|
|
11
|
-
it 'should raise NoMethodError' do
|
|
12
|
-
expect { Certificate.new }.to raise_error(NoMethodError, /private method/)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
data/spec/keychain_spec.rb
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'tempfile'
|
|
4
|
-
|
|
5
|
-
describe Keychain do
|
|
6
|
-
describe '#login_keychain' do
|
|
7
|
-
subject { Keychain.login_keychain }
|
|
8
|
-
|
|
9
|
-
it 'should be located in the user home directory' do
|
|
10
|
-
expect(subject.filename).to be == File.expand_path('~/Library/Keychains/login.keychain-db')
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
describe '#create' do
|
|
15
|
-
let(:password) { 'p4ssw0rd!' }
|
|
16
|
-
|
|
17
|
-
it 'should raise NotImplementedError' do
|
|
18
|
-
Tempfile.open('example.keychain-db') do |tmp|
|
|
19
|
-
expect { Keychain.create(tmp.path, password) }.to raise_error(NotImplementedError)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
describe '#list' do
|
|
25
|
-
describe 'when passing no arguments' do
|
|
26
|
-
it 'should list keychains in user domain' do
|
|
27
|
-
expect(Keychain.list).to satisfy { |keychains|
|
|
28
|
-
keychains.map(&:filename) == Keychain.list(:user).map(&:filename)
|
|
29
|
-
}
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
describe 'when passing a valid domain' do
|
|
34
|
-
it 'should not raise an error' do
|
|
35
|
-
expect { Keychain.list(:user) }.not_to raise_error
|
|
36
|
-
expect { Keychain.list(:system) }.not_to raise_error
|
|
37
|
-
expect { Keychain.list(:common) }.not_to raise_error
|
|
38
|
-
expect { Keychain.list(:dynamic) }.not_to raise_error
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
describe 'when passing an invalid domain' do
|
|
43
|
-
it 'should raise an error' do
|
|
44
|
-
expect { Keychain.list(:invalid) }.to raise_error(NoMethodError) # FIXME
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
data/spec/password_spec.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
describe GenericPassword do
|
|
4
|
-
let(:keychain) { Keychain.login_keychain } # FIXME: we should create a temporary keychain for tests
|
|
5
|
-
|
|
6
|
-
describe '#add' do
|
|
7
|
-
let(:service) { 'com.example.service' }
|
|
8
|
-
let(:account) { 'jappleseed' }
|
|
9
|
-
let(:password) { 'p4ssw0rd!' }
|
|
10
|
-
let(:comment) { 'Some comment' }
|
|
11
|
-
|
|
12
|
-
around(:example) do |example|
|
|
13
|
-
GenericPassword.add(service, account, password, comment: comment)
|
|
14
|
-
example.run
|
|
15
|
-
GenericPassword.delete({ service: service, account: account })
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it 'should be added to the keychain' do
|
|
19
|
-
entry = GenericPassword.find({ account: account })
|
|
20
|
-
expect(entry.keychain.filename).to be == keychain.filename
|
|
21
|
-
expect(entry.attributes).to include({
|
|
22
|
-
'acct' => account,
|
|
23
|
-
'svce' => service,
|
|
24
|
-
'icmt' => comment
|
|
25
|
-
})
|
|
26
|
-
expect(entry.password).to be == password
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
describe InternetPassword do
|
|
32
|
-
let(:keychain) { Keychain.login_keychain } # FIXME: we should create a temporary keychain for tests
|
|
33
|
-
|
|
34
|
-
describe '#add' do
|
|
35
|
-
let(:server) { 'example.com' }
|
|
36
|
-
let(:account) { 'jappleseed@example.com' }
|
|
37
|
-
|
|
38
|
-
describe 'ascii password' do
|
|
39
|
-
let(:password) { 'p4ssw0rd!' }
|
|
40
|
-
let(:comment) { 'Some comment' }
|
|
41
|
-
|
|
42
|
-
around(:example) do |example|
|
|
43
|
-
InternetPassword.add(server, account, password, comment: comment)
|
|
44
|
-
example.run
|
|
45
|
-
InternetPassword.delete({ server: server, account: account })
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it 'should be added to the keychain' do
|
|
49
|
-
entry = InternetPassword.find({ account: account })
|
|
50
|
-
expect(entry.keychain.filename).to be == keychain.filename
|
|
51
|
-
expect(entry.attributes).to include({
|
|
52
|
-
'acct' => account,
|
|
53
|
-
'srvr' => server,
|
|
54
|
-
'icmt' => comment
|
|
55
|
-
})
|
|
56
|
-
expect(entry.password).to be == password
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
describe 'ascii password with backslash' do
|
|
61
|
-
let(:password) { 'p4ssw\0rd!' }
|
|
62
|
-
|
|
63
|
-
around(:example) do |example|
|
|
64
|
-
InternetPassword.add(server, account, password)
|
|
65
|
-
example.run
|
|
66
|
-
InternetPassword.delete({ server: server, account: account })
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it 'should be added to the keychain' do
|
|
70
|
-
entry = InternetPassword.find({ account: account })
|
|
71
|
-
expect(entry.keychain.filename).to be == keychain.filename
|
|
72
|
-
expect(entry.attributes).to include({
|
|
73
|
-
'acct' => account,
|
|
74
|
-
'srvr' => server
|
|
75
|
-
})
|
|
76
|
-
expect(entry.password).to be == password
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
describe 'non-ascii password' do
|
|
81
|
-
let(:password) { '•••p4ssw0rd!••' }
|
|
82
|
-
let(:comment) { '•••Some comment•••' }
|
|
83
|
-
|
|
84
|
-
around(:example) do |example|
|
|
85
|
-
InternetPassword.add(server, account, password, comment: comment)
|
|
86
|
-
example.run
|
|
87
|
-
InternetPassword.delete({ server: server, account: account })
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it 'should be added to the keychain' do
|
|
91
|
-
entry = InternetPassword.find({ account: account })
|
|
92
|
-
expect(entry.keychain.filename).to be == keychain.filename
|
|
93
|
-
expect(entry.attributes).to include({
|
|
94
|
-
'acct' => account,
|
|
95
|
-
'srvr' => server,
|
|
96
|
-
'icmt' => comment
|
|
97
|
-
})
|
|
98
|
-
expect(entry.password).to be == password
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
data/spec/spec_helper.rb
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../lib/security'
|
|
4
|
-
|
|
5
|
-
# rubocop:disable Style/MixinUsage
|
|
6
|
-
include Security
|
|
7
|
-
# rubocop:enable Style/MixinUsage
|
|
8
|
-
|
|
9
|
-
# This file was generated by the `rspec --init` command. Conventionally, all
|
|
10
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
|
11
|
-
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
|
12
|
-
# this file to always be loaded, without a need to explicitly require it in any
|
|
13
|
-
# files.
|
|
14
|
-
#
|
|
15
|
-
# Given that it is always loaded, you are encouraged to keep this file as
|
|
16
|
-
# light-weight as possible. Requiring heavyweight dependencies from this file
|
|
17
|
-
# will add to the boot time of your test suite on EVERY test run, even for an
|
|
18
|
-
# individual file that may not need all of that loaded. Instead, consider making
|
|
19
|
-
# a separate helper file that requires the additional dependencies and performs
|
|
20
|
-
# the additional setup, and require it from the spec files that actually need
|
|
21
|
-
# it.
|
|
22
|
-
#
|
|
23
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
|
24
|
-
RSpec.configure do |config|
|
|
25
|
-
# rspec-expectations config goes here. You can use an alternate
|
|
26
|
-
# assertion/expectation library such as wrong or the stdlib/minitest
|
|
27
|
-
# assertions if you prefer.
|
|
28
|
-
config.expect_with :rspec do |expectations|
|
|
29
|
-
# This option will default to `true` in RSpec 4. It makes the `description`
|
|
30
|
-
# and `failure_message` of custom matchers include text for helper methods
|
|
31
|
-
# defined using `chain`, e.g.:
|
|
32
|
-
# be_bigger_than(2).and_smaller_than(4).description
|
|
33
|
-
# # => "be bigger than 2 and smaller than 4"
|
|
34
|
-
# ...rather than:
|
|
35
|
-
# # => "be bigger than 2"
|
|
36
|
-
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# rspec-mocks config goes here. You can use an alternate test double
|
|
40
|
-
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
|
41
|
-
config.mock_with :rspec do |mocks|
|
|
42
|
-
# Prevents you from mocking or stubbing a method that does not exist on
|
|
43
|
-
# a real object. This is generally recommended, and will default to
|
|
44
|
-
# `true` in RSpec 4.
|
|
45
|
-
mocks.verify_partial_doubles = true
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
|
49
|
-
# have no way to turn it off -- the option exists only for backwards
|
|
50
|
-
# compatibility in RSpec 3). It causes shared context metadata to be
|
|
51
|
-
# inherited by the metadata hash of host groups and examples, rather than
|
|
52
|
-
# triggering implicit auto-inclusion in groups with matching metadata.
|
|
53
|
-
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
54
|
-
|
|
55
|
-
# The settings below are suggested to provide a good initial experience
|
|
56
|
-
# with RSpec, but feel free to customize to your heart's content.
|
|
57
|
-
# # This allows you to limit a spec run to individual examples or groups
|
|
58
|
-
# # you care about by tagging them with `:focus` metadata. When nothing
|
|
59
|
-
# # is tagged with `:focus`, all examples get run. RSpec also provides
|
|
60
|
-
# # aliases for `it`, `describe`, and `context` that include `:focus`
|
|
61
|
-
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
|
62
|
-
# config.filter_run_when_matching :focus
|
|
63
|
-
#
|
|
64
|
-
# # Allows RSpec to persist some state between runs in order to support
|
|
65
|
-
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
|
66
|
-
# # you configure your source control system to ignore this file.
|
|
67
|
-
# config.example_status_persistence_file_path = "spec/examples.txt"
|
|
68
|
-
#
|
|
69
|
-
# # Limits the available syntax to the non-monkey patched syntax that is
|
|
70
|
-
# # recommended. For more details, see:
|
|
71
|
-
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
|
72
|
-
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
|
73
|
-
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
|
74
|
-
# config.disable_monkey_patching!
|
|
75
|
-
#
|
|
76
|
-
# # This setting enables warnings. It's recommended, but in some cases may
|
|
77
|
-
# # be too noisy due to issues in dependencies.
|
|
78
|
-
# config.warnings = true
|
|
79
|
-
#
|
|
80
|
-
# # Many RSpec users commonly either run the entire suite or an individual
|
|
81
|
-
# # file, and it's useful to allow more verbose output when running an
|
|
82
|
-
# # individual spec file.
|
|
83
|
-
# if config.files_to_run.one?
|
|
84
|
-
# # Use the documentation formatter for detailed output,
|
|
85
|
-
# # unless a formatter has already been configured
|
|
86
|
-
# # (e.g. via a command-line flag).
|
|
87
|
-
# config.default_formatter = "doc"
|
|
88
|
-
# end
|
|
89
|
-
#
|
|
90
|
-
# # Print the 10 slowest examples and example groups at the
|
|
91
|
-
# # end of the spec run, to help surface which specs are running
|
|
92
|
-
# # particularly slow.
|
|
93
|
-
# config.profile_examples = 10
|
|
94
|
-
#
|
|
95
|
-
# # Run specs in random order to surface order dependencies. If you find an
|
|
96
|
-
# # order dependency and want to debug it, you can fix the order by providing
|
|
97
|
-
# # the seed, which is printed after each run.
|
|
98
|
-
# # --seed 1234
|
|
99
|
-
# config.order = :random
|
|
100
|
-
#
|
|
101
|
-
# # Seed global randomization in this process using the `--seed` CLI option.
|
|
102
|
-
# # Setting this allows you to use `--seed` to deterministically reproduce
|
|
103
|
-
# # test failures related to randomization by passing the same `--seed` value
|
|
104
|
-
# # as the one that triggered the failure.
|
|
105
|
-
# Kernel.srand config.seed
|
|
106
|
-
end
|