fastlane-plugin-cryptex 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +138 -0
- data/bin/cryptex +7 -0
- data/lib/fastlane/plugin/cryptex.rb +25 -0
- data/lib/fastlane/plugin/cryptex/actions/cryptex_action.rb +29 -0
- data/lib/fastlane/plugin/cryptex/actions/cryptex_generate_keystore.rb +62 -0
- data/lib/fastlane/plugin/cryptex/assets/CryptexfileTemplate +1 -0
- data/lib/fastlane/plugin/cryptex/assets/READMETemplate.md +2 -0
- data/lib/fastlane/plugin/cryptex/change_password.rb +32 -0
- data/lib/fastlane/plugin/cryptex/commands_generator.rb +92 -0
- data/lib/fastlane/plugin/cryptex/encrypt.rb +95 -0
- data/lib/fastlane/plugin/cryptex/git_helper.rb +129 -0
- data/lib/fastlane/plugin/cryptex/helper/cryptex_helper.rb +12 -0
- data/lib/fastlane/plugin/cryptex/options.rb +85 -0
- data/lib/fastlane/plugin/cryptex/runner.rb +88 -0
- data/lib/fastlane/plugin/cryptex/setup.rb +17 -0
- data/lib/fastlane/plugin/cryptex/version.rb +6 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6286c3236e933597fcaa231151783c966173268c
|
4
|
+
data.tar.gz: c2e7ecfbfbc3fc0fcec787d3197373b389ab4c92
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b655c7072780622600f14be3cde5d3b5991999ab8d3616fc7ceef21d2b420ee3e23549fb0264ee11a8fdd4e45e7dd4040ed5b2a92dcf568fa7a383334b28941f
|
7
|
+
data.tar.gz: cde2607785293bb1c723d6d9af184424e65f124edb6df600f434fcd9dbf8dc74d1b03f0e06457c0179a7be5d0c04cdcd8b2b003bf0ed48412ecc4941365bbf0c
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Helmut Januschka <h.januschka@krone.at>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# cryptex plugin
|
2
|
+
|
3
|
+
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-cryptex)
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
This project is a [fastlane](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-cryptex`, add it to your project by running:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
fastlane add_plugin cryptex
|
11
|
+
```
|
12
|
+
|
13
|
+
## About cryptex
|
14
|
+
|
15
|
+
Android Key Store Git repo
|
16
|
+
|
17
|
+
**Note to author:** Add a more detailed description about this plugin here. If your plugin contains multiple actions, make sure to mention them here.
|
18
|
+
|
19
|
+
## Example
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
|
23
|
+
lane :test do
|
24
|
+
# Generate a new Android Keystore
|
25
|
+
|
26
|
+
cryptex_generate_keystore(
|
27
|
+
destination: "sample.keystore",
|
28
|
+
fullname: "Helmut Januschka",
|
29
|
+
city: "Vienna",
|
30
|
+
alias: "releaseKey"
|
31
|
+
)
|
32
|
+
|
33
|
+
# import/update this in cryptex
|
34
|
+
cryptex(
|
35
|
+
type: "import",
|
36
|
+
in: "sample.keystore",
|
37
|
+
key: "my_awesome_app_production"
|
38
|
+
)
|
39
|
+
|
40
|
+
# export a file from cryptex
|
41
|
+
cryptex(
|
42
|
+
type: "export",
|
43
|
+
out: "releaseKey.keystore",
|
44
|
+
key: "my_awesome_app_production"
|
45
|
+
)
|
46
|
+
|
47
|
+
file_output = File.read("../releaseKey.keystore")
|
48
|
+
puts "File Content: #{file_output.tr("\n", ' ')}"
|
49
|
+
|
50
|
+
# delete the file
|
51
|
+
cryptex(
|
52
|
+
type: "delete",
|
53
|
+
key: "my_awesome_app_production"
|
54
|
+
)
|
55
|
+
|
56
|
+
# Nuke's all files
|
57
|
+
cryptex(
|
58
|
+
type: "nuke"
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
#ENV SAMPLES
|
63
|
+
#import some env into the space of `my_group`
|
64
|
+
cryptex(
|
65
|
+
type: "import_env",
|
66
|
+
hash: {
|
67
|
+
"helmut" => "go",
|
68
|
+
"some_url" => "http://lets.do.it"
|
69
|
+
},
|
70
|
+
key: "my_group"
|
71
|
+
)
|
72
|
+
env_out = cryptex(
|
73
|
+
type: "export_env",
|
74
|
+
key: "my_group",
|
75
|
+
set_env: true #THIS one sets the values found directly into to ENV
|
76
|
+
#hash: {"my_key"=>true, "some_url"=>true} # only returned specific keys
|
77
|
+
)
|
78
|
+
|
79
|
+
puts "IN ENV it is:"
|
80
|
+
puts ENV['some_url']
|
81
|
+
puts "returned: #{env_out.inspect}"
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
```
|
87
|
+
|
88
|
+
## Commandline Examples:
|
89
|
+
|
90
|
+
```bash
|
91
|
+
#init
|
92
|
+
cryptex init
|
93
|
+
|
94
|
+
# import file
|
95
|
+
cryptex -s import -i file_to_hide.txt -k file_key
|
96
|
+
|
97
|
+
# export file
|
98
|
+
cryptex -s export -k file_key -o file_to_hide.txt
|
99
|
+
|
100
|
+
#delete file
|
101
|
+
cryptex -s delete -k file_key
|
102
|
+
|
103
|
+
#nuke repo
|
104
|
+
cryptex nuke
|
105
|
+
|
106
|
+
|
107
|
+
```
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
## Run tests for this plugin
|
112
|
+
|
113
|
+
To run both the tests, and code style validation, run
|
114
|
+
|
115
|
+
```
|
116
|
+
rake
|
117
|
+
```
|
118
|
+
|
119
|
+
To automatically fix many of the styling issues, use
|
120
|
+
```
|
121
|
+
rubocop -a
|
122
|
+
```
|
123
|
+
|
124
|
+
## Issues and Feedback
|
125
|
+
|
126
|
+
For any other issues and feedback about this plugin, please submit it to this repository.
|
127
|
+
|
128
|
+
## Troubleshooting
|
129
|
+
|
130
|
+
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://github.com/fastlane/fastlane/blob/master/fastlane/docs/PluginsTroubleshooting.md) doc in the main `fastlane` repo.
|
131
|
+
|
132
|
+
## Using `fastlane` Plugins
|
133
|
+
|
134
|
+
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Plugins.md).
|
135
|
+
|
136
|
+
## About `fastlane`
|
137
|
+
|
138
|
+
`fastlane` is the easiest way to automate building and releasing your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
|
data/bin/cryptex
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
module Fastlane
|
3
|
+
module Cryptex
|
4
|
+
ROOT = Pathname.new(File.expand_path('lib/fastlane/plugin/cryptex'))
|
5
|
+
# Return all .rb files inside the "actions" and "helper" directory
|
6
|
+
def self.all_classes
|
7
|
+
Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'fastlane/plugin/cryptex/options'
|
13
|
+
require 'fastlane/plugin/cryptex/change_password'
|
14
|
+
require 'fastlane/plugin/cryptex/version'
|
15
|
+
require 'fastlane/plugin/cryptex/setup'
|
16
|
+
require 'fastlane/plugin/cryptex/git_helper'
|
17
|
+
require 'fastlane/plugin/cryptex/encrypt'
|
18
|
+
require 'fastlane/plugin/cryptex/runner'
|
19
|
+
require 'fastlane/plugin/cryptex/commands_generator'
|
20
|
+
|
21
|
+
# By default we want to import all available actions and helpers
|
22
|
+
# A plugin can contain any number of actions and plugins
|
23
|
+
Fastlane::Cryptex.all_classes.each do |current|
|
24
|
+
require current
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class CryptexAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
UI.message("The cryptex plugin is working!")
|
6
|
+
|
7
|
+
params.load_configuration_file("Cryptexfile")
|
8
|
+
|
9
|
+
Cryptex::Runner.new.run(params)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
"Secure Git-Backed Storage"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.authors
|
17
|
+
["Helmut Januschka"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.available_options
|
21
|
+
Cryptex::Options.available_options
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.is_supported?(platform)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class CryptexGenerateKeystoreAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
require "fileutils"
|
6
|
+
cmd = "keytool -genkey -v -keystore #{File.expand_path(params[:destination])} -storepass #{params[:password]} -keypass #{params[:password]} -alias #{params[:alias]} -dname 'CN=#{params[:fullname]},L=#{params[:city]}' -validity 1000"
|
7
|
+
FastlaneCore::CommandExecutor.execute(command: cmd,
|
8
|
+
print_all: true,
|
9
|
+
print_command: true)
|
10
|
+
|
11
|
+
params[:destination]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.description
|
15
|
+
"Generate a new Android Keystore"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.authors
|
19
|
+
["Helmut Januschka"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.available_options
|
23
|
+
[
|
24
|
+
FastlaneCore::ConfigItem.new(key: :password,
|
25
|
+
env_name: "MATCH_KEYSTORE_PASSWORD",
|
26
|
+
description: "Password for the Keystore",
|
27
|
+
optional: true,
|
28
|
+
is_string: true,
|
29
|
+
default_value: ""),
|
30
|
+
FastlaneCore::ConfigItem.new(key: :alias,
|
31
|
+
env_name: "MATCH_KEYSTORE_ALIAS",
|
32
|
+
description: "ALIAS for the Keystore",
|
33
|
+
optional: true,
|
34
|
+
is_string: true,
|
35
|
+
default_value: ""),
|
36
|
+
FastlaneCore::ConfigItem.new(key: :destination,
|
37
|
+
env_name: "MATCH_KEYSTORE_DESTINATION",
|
38
|
+
description: "Where to put decrypted keystore",
|
39
|
+
optional: true,
|
40
|
+
default_value: ""),
|
41
|
+
FastlaneCore::ConfigItem.new(key: :fullname,
|
42
|
+
env_name: "MATCH_KEYSTORE_FULLNAME",
|
43
|
+
description: "Fullname of keystore owner",
|
44
|
+
optional: true,
|
45
|
+
is_string: true,
|
46
|
+
default_value: ""),
|
47
|
+
FastlaneCore::ConfigItem.new(key: :city,
|
48
|
+
env_name: "MATCH_KEYSTORE_CITY",
|
49
|
+
description: "City of keystore owner",
|
50
|
+
optional: true,
|
51
|
+
is_string: true,
|
52
|
+
default_value: "")
|
53
|
+
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.is_supported?(platform)
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
git_url "[[GIT_URL]]"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Cryptex
|
3
|
+
class ChangePassword
|
4
|
+
def self.update(params: nil, from: nil, to: nil)
|
5
|
+
to ||= ChangePassword.ask_password(message: "New passphrase for Git Repo: ", confirm: false)
|
6
|
+
from ||= ChangePassword.ask_password(message: "Old passphrase for Git Repo: ", confirm: true)
|
7
|
+
GitHelper.clear_changes
|
8
|
+
workspace = GitHelper.clone(params[:git_url], params[:shallow_clone], manual_password: from, skip_docs: params[:skip_docs], branch: params[:git_branch])
|
9
|
+
Encrypt.new.clear_password(params[:git_url])
|
10
|
+
Encrypt.new.store_password(params[:git_url], to)
|
11
|
+
|
12
|
+
message = "[fastlane] Changed passphrase"
|
13
|
+
GitHelper.commit_changes(workspace, message, params[:git_url], params[:git_branch])
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ask_password(message: "Passphrase for Git Repo: ", confirm: true)
|
17
|
+
loop do
|
18
|
+
password = UI.password(message)
|
19
|
+
if confirm
|
20
|
+
password2 = UI.password("Type passphrase again: ")
|
21
|
+
if password == password2
|
22
|
+
return password
|
23
|
+
end
|
24
|
+
else
|
25
|
+
return password
|
26
|
+
end
|
27
|
+
UI.error("Passhprases differ. Try again")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'commander'
|
2
|
+
|
3
|
+
HighLine.track_eof = false
|
4
|
+
module Fastlane
|
5
|
+
module Cryptex
|
6
|
+
class CommandsGenerator
|
7
|
+
include Commander::Methods
|
8
|
+
UI = FastlaneCore::UI
|
9
|
+
|
10
|
+
def self.start
|
11
|
+
FastlaneCore::UpdateChecker.start_looking_for_update('cryptex')
|
12
|
+
self.new.run
|
13
|
+
ensure
|
14
|
+
|
15
|
+
FastlaneCore::UpdateChecker.show_update_status('cryptex', Cryptex::VERSION)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
program :version, Cryptex::VERSION
|
20
|
+
program :description, "Cryptex"
|
21
|
+
program :help, 'Author', 'Helmut Januschka <helmut@januschka.com>'
|
22
|
+
program :help, 'Website', 'https://fastlane.tools'
|
23
|
+
program :help, 'GitHub', 'https://github.com/hjanuschka/fastlane-plugin-cryptex'
|
24
|
+
program :help_formatter, :compact
|
25
|
+
|
26
|
+
global_option('--verbose') { $verbose = true }
|
27
|
+
|
28
|
+
FastlaneCore::CommanderGenerator.new.generate(Cryptex::Options.available_options)
|
29
|
+
|
30
|
+
command :run do |c|
|
31
|
+
c.syntax = 'cryptex'
|
32
|
+
c.description = Cryptex::DESCRIPTION
|
33
|
+
c.action do |args, options|
|
34
|
+
params = FastlaneCore::Configuration.create(Cryptex::Options.available_options, options.__hash__)
|
35
|
+
params.load_configuration_file("Cryptexfile")
|
36
|
+
Cryptex::Runner.new.run(params)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
command :init do |c|
|
41
|
+
c.syntax = 'cryptex init'
|
42
|
+
c.description = 'Create the Cryptexfile for you'
|
43
|
+
c.action do |args, options|
|
44
|
+
containing = (File.directory?("fastlane") ? 'fastlane' : '.')
|
45
|
+
path = File.join(containing, "Cryptexfile")
|
46
|
+
|
47
|
+
if File.exist?(path)
|
48
|
+
FastlaneCore::UI.user_error!("You already got a Cryptexfile in this directory")
|
49
|
+
return 0
|
50
|
+
end
|
51
|
+
|
52
|
+
Cryptex::Setup.new.run(path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
command :change_password do |c|
|
57
|
+
c.syntax = 'cryptex change_password'
|
58
|
+
c.description = 'Re-encrypt all files with a different password'
|
59
|
+
c.action do |args, options|
|
60
|
+
puts "CHANGE PASSWORD"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
command :decrypt do |c|
|
65
|
+
c.syntax = "cryptex decrypt"
|
66
|
+
c.description = "Decrypts the repository and keeps it on the filesystem"
|
67
|
+
c.action do |args, options|
|
68
|
+
params = FastlaneCore::Configuration.create(Match::Options.available_options, options.__hash__)
|
69
|
+
params.load_configuration_file("Cryptexfile")
|
70
|
+
decrypted_repo = Cryptex::GitHelper.clone(params[:git_url], params[:shallow_clone], branch: params[:git_branch])
|
71
|
+
UI.success "Repo is at: '#{decrypted_repo}'"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
command "nuke" do |c|
|
75
|
+
# We have this empty command here, since otherwise the normal `match` command will be executed
|
76
|
+
c.syntax = "cryptex nuke"
|
77
|
+
c.description = "Delete all Crypted Files in the repository"
|
78
|
+
c.action do |args, options|
|
79
|
+
params = FastlaneCore::Configuration.create(Cryptex::Options.available_options, options.__hash__)
|
80
|
+
params.load_configuration_file("Cryptexfile")
|
81
|
+
params[:type] = "nuke"
|
82
|
+
Cryptex::Runner.new.run(params)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
default_command :run
|
87
|
+
|
88
|
+
run!
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Cryptex
|
3
|
+
class Encrypt
|
4
|
+
require 'security'
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
def server_name(git_url)
|
8
|
+
["cryptex", git_url].join("_")
|
9
|
+
end
|
10
|
+
|
11
|
+
def password(git_url)
|
12
|
+
password = ENV["CRYPTEX_PASSWORD"]
|
13
|
+
unless password
|
14
|
+
item = Security::InternetPassword.find(server: server_name(git_url))
|
15
|
+
password = item.password if item
|
16
|
+
end
|
17
|
+
|
18
|
+
unless password
|
19
|
+
UI.important "Enter the passphrase that should be used to encrypt/decrypt your repository"
|
20
|
+
UI.important "This passphrase is specific per repository and will be stored in your local keychain"
|
21
|
+
UI.important "Make sure to remember the password, as you'll need it when you run cryptex on a different machine"
|
22
|
+
password = ChangePassword.ask_password(confirm: true)
|
23
|
+
store_password(git_url, password)
|
24
|
+
end
|
25
|
+
|
26
|
+
return password
|
27
|
+
end
|
28
|
+
|
29
|
+
def store_password(git_url, password)
|
30
|
+
Security::InternetPassword.add(server_name(git_url), "", password)
|
31
|
+
end
|
32
|
+
|
33
|
+
# removes the password from the keychain again
|
34
|
+
def clear_password(git_url)
|
35
|
+
Security::InternetPassword.delete(server: server_name(git_url))
|
36
|
+
end
|
37
|
+
|
38
|
+
def encrypt_repo(path: nil, git_url: nil)
|
39
|
+
iterate(path) do |current|
|
40
|
+
crypt(path: current,
|
41
|
+
password: password(git_url),
|
42
|
+
encrypt: true)
|
43
|
+
UI.success "🔒 Encrypted '#{File.basename(current)}'" if $verbose
|
44
|
+
end
|
45
|
+
UI.success "🔒 Successfully encrypted certificates repo"
|
46
|
+
end
|
47
|
+
|
48
|
+
def decrypt_repo(path: nil, git_url: nil, manual_password: nil)
|
49
|
+
iterate(path) do |current|
|
50
|
+
begin
|
51
|
+
crypt(path: current,
|
52
|
+
password: manual_password || password(git_url),
|
53
|
+
encrypt: false)
|
54
|
+
rescue
|
55
|
+
UI.error "Couldn't decrypt the repo, please make sure you enter the right password!"
|
56
|
+
UI.user_error!("Invalid password passed via 'CRYPTEX_PASSWORD'") if ENV["CRYPTEX_PASSWORD"]
|
57
|
+
clear_password(git_url)
|
58
|
+
decrypt_repo(path: path, git_url: git_url)
|
59
|
+
return
|
60
|
+
end
|
61
|
+
UI.success "🔓 Decrypted '#{File.basename(current)}'" if $verbose
|
62
|
+
end
|
63
|
+
UI.success "🔓 Successfully decrypted certificates repo"
|
64
|
+
end
|
65
|
+
|
66
|
+
def iterate(source_path)
|
67
|
+
Dir[File.join(source_path, "**", "*.{crypt}")].each do |path|
|
68
|
+
next if File.directory?(path)
|
69
|
+
yield(path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def crypt(path: nil, password: nil, encrypt: true)
|
76
|
+
if password.to_s.strip.length.zero? && encrypt
|
77
|
+
UI.user_error!("No password supplied")
|
78
|
+
end
|
79
|
+
|
80
|
+
tmpfile = File.join(Dir.mktmpdir, "temporary")
|
81
|
+
command = ["openssl aes-256-cbc"]
|
82
|
+
command << "-k #{password.shellescape}"
|
83
|
+
command << "-in #{path.shellescape}"
|
84
|
+
command << "-out #{tmpfile.shellescape}"
|
85
|
+
command << "-a"
|
86
|
+
command << "-d" unless encrypt
|
87
|
+
command << "&> /dev/null" unless $verbose # to show show an error message is something goes wrong
|
88
|
+
success = system(command.join(' '))
|
89
|
+
|
90
|
+
UI.crash!("Error decrypting '#{path}'") unless success
|
91
|
+
FileUtils.mv(tmpfile, path)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Cryptex
|
3
|
+
class GitHelper
|
4
|
+
def self.clone(git_url, shallow_clone, manual_password: nil, skip_docs: false, branch: "master")
|
5
|
+
return @dir if @dir
|
6
|
+
|
7
|
+
@dir = Dir.mktmpdir
|
8
|
+
|
9
|
+
command = "git clone '#{git_url}' '#{@dir}'"
|
10
|
+
command << " --depth 1" if shallow_clone
|
11
|
+
|
12
|
+
UI.message "Cloning remote git repo..."
|
13
|
+
begin
|
14
|
+
FastlaneCore::CommandExecutor.execute(command: "GIT_TERMINAL_PROMPT=0 #{command}",
|
15
|
+
print_all: $verbose,
|
16
|
+
print_command: $verbose)
|
17
|
+
rescue
|
18
|
+
UI.error("Error cloning Repo")
|
19
|
+
UI.error("Run the following command manually to make sure you're properly authenticated:")
|
20
|
+
UI.command(command)
|
21
|
+
UI.user_error!("Error cloning cryptex git repo, please make sure you have access to the repository - see instructions above")
|
22
|
+
end
|
23
|
+
|
24
|
+
UI.user_error!("Error cloning repo, make sure you have access to it '#{git_url}'") unless File.directory?(@dir)
|
25
|
+
|
26
|
+
checkout_branch(branch) unless branch == "master"
|
27
|
+
|
28
|
+
copy_readme(@dir) unless skip_docs
|
29
|
+
Cryptex::Encrypt.new.decrypt_repo(path: @dir, git_url: git_url, manual_password: manual_password)
|
30
|
+
|
31
|
+
return @dir
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.generate_commit_message(params)
|
35
|
+
# 'Automatic commit via fastlane'
|
36
|
+
[
|
37
|
+
"[fastlane]",
|
38
|
+
"Updated"
|
39
|
+
].join(" ")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.cryptex_version(workspace)
|
43
|
+
path = File.join(workspace, "cryptex_version.txt")
|
44
|
+
if File.exist?(path)
|
45
|
+
Gem::Version.new(File.read(path))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.commit_changes(path, message, git_url, branch = "master")
|
50
|
+
Dir.chdir(path) do
|
51
|
+
return if `git status`.include?("nothing to commit")
|
52
|
+
|
53
|
+
Cryptex::Encrypt.new.encrypt_repo(path: path, git_url: git_url)
|
54
|
+
File.write("cryptex_version.txt", Cryptex::VERSION) # unencrypted
|
55
|
+
|
56
|
+
commands = []
|
57
|
+
commands << "git add -A"
|
58
|
+
commands << "git commit -m #{message.shellescape}"
|
59
|
+
commands << "GIT_TERMINAL_PROMPT=0 git push origin #{branch.shellescape}"
|
60
|
+
|
61
|
+
UI.message "Pushing changes to remote git repo..."
|
62
|
+
|
63
|
+
commands.each do |command|
|
64
|
+
FastlaneCore::CommandExecutor.execute(command: command,
|
65
|
+
print_all: $verbose,
|
66
|
+
print_command: $verbose)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
FileUtils.rm_rf(path)
|
70
|
+
@dir = nil
|
71
|
+
rescue => ex
|
72
|
+
UI.error("Couldn't commit or push changes back to git...")
|
73
|
+
UI.error(ex)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.clear_changes
|
77
|
+
return unless @dir
|
78
|
+
|
79
|
+
FileUtils.rm_rf(@dir)
|
80
|
+
UI.success "🔒 Successfully encrypted certificates repo" # so the user is happy
|
81
|
+
@dir = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create and checkout an specific branch in the git repo
|
85
|
+
def self.checkout_branch(branch)
|
86
|
+
return unless @dir
|
87
|
+
|
88
|
+
commands = []
|
89
|
+
if branch_exists?(branch)
|
90
|
+
# Checkout the branch if it already exists
|
91
|
+
commands << "git checkout #{branch.shellescape}"
|
92
|
+
else
|
93
|
+
# If a new branch is being created, we create it as an 'orphan' to not inherit changes from the master branch.
|
94
|
+
commands << "git checkout --orphan #{branch.shellescape}"
|
95
|
+
# We also need to reset the working directory to not transfer any uncommitted changes to the new branch.
|
96
|
+
commands << "git reset --hard"
|
97
|
+
end
|
98
|
+
|
99
|
+
UI.message "Checking out branch #{branch}..."
|
100
|
+
|
101
|
+
Dir.chdir(@dir) do
|
102
|
+
commands.each do |command|
|
103
|
+
FastlaneCore::CommandExecutor.execute(command: command,
|
104
|
+
print_all: $verbose,
|
105
|
+
print_command: $verbose)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Checks if a specific branch exists in the git repo
|
111
|
+
def self.branch_exists?(branch)
|
112
|
+
return unless @dir
|
113
|
+
|
114
|
+
result = Dir.chdir(@dir) do
|
115
|
+
FastlaneCore::CommandExecutor.execute(command: "git branch --list origin/#{branch.shellescape} --no-color -r",
|
116
|
+
print_all: $verbose,
|
117
|
+
print_command: $verbose)
|
118
|
+
end
|
119
|
+
return !result.empty?
|
120
|
+
end
|
121
|
+
|
122
|
+
# Copies the README.md into the git repo
|
123
|
+
def self.copy_readme(directory)
|
124
|
+
template = File.read("#{Cryptex::ROOT}/assets/READMETemplate.md")
|
125
|
+
File.write(File.join(directory, "README.md"), template)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
class CryptexHelper
|
4
|
+
# class methods that you define here become available in your action
|
5
|
+
# as `Helper::CryptexHelper.your_method`
|
6
|
+
#
|
7
|
+
def self.show_message
|
8
|
+
UI.message("Hello from the cryptex plugin helper!")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'fastlane_core'
|
2
|
+
require 'credentials_manager'
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Cryptex
|
6
|
+
class Options
|
7
|
+
def self.available_options
|
8
|
+
[
|
9
|
+
FastlaneCore::ConfigItem.new(key: :git_url,
|
10
|
+
env_name: "CRYPTEX_GIT_URL",
|
11
|
+
description: "URL to the git repo containing all the certificates",
|
12
|
+
optional: false,
|
13
|
+
short_option: "-r"),
|
14
|
+
FastlaneCore::ConfigItem.new(key: :workspace,
|
15
|
+
description: nil,
|
16
|
+
verify_block: proc do |value|
|
17
|
+
unless Helper.test?
|
18
|
+
if value.start_with?("/var/folders") or value.include?("tmp/") or value.include?("temp/")
|
19
|
+
# that's fine
|
20
|
+
else
|
21
|
+
UI.user_error!("Specify the `git_url` instead of the `path`")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end,
|
25
|
+
optional: true),
|
26
|
+
FastlaneCore::ConfigItem.new(key: :git_branch,
|
27
|
+
env_name: "CRYPTEX_GIT_BRANCH",
|
28
|
+
description: "Specific git branch to use",
|
29
|
+
default_value: 'master'),
|
30
|
+
FastlaneCore::ConfigItem.new(key: :key,
|
31
|
+
short_option: "-k",
|
32
|
+
env_name: "CRYPTEX_FILE_KEY",
|
33
|
+
description: "The File KEY",
|
34
|
+
default_value: ""),
|
35
|
+
FastlaneCore::ConfigItem.new(key: :in,
|
36
|
+
short_option: "-i",
|
37
|
+
env_name: "CRYPTEX_IN",
|
38
|
+
description: "The File KEY",
|
39
|
+
default_value: ""),
|
40
|
+
|
41
|
+
FastlaneCore::ConfigItem.new(key: :hash,
|
42
|
+
short_option: "-H",
|
43
|
+
env_name: "CRYPTEX_HASH",
|
44
|
+
description: "Hash",
|
45
|
+
optional: true,
|
46
|
+
is_string: false),
|
47
|
+
FastlaneCore::ConfigItem.new(key: :set_env,
|
48
|
+
short_option: "-e",
|
49
|
+
env_name: "CRYPTEX_SET_ENV",
|
50
|
+
description: "set found variables as ENV",
|
51
|
+
default_value: false,
|
52
|
+
is_string: false),
|
53
|
+
FastlaneCore::ConfigItem.new(key: :out,
|
54
|
+
short_option: "-o",
|
55
|
+
env_name: "CRYPTEX_OUT",
|
56
|
+
description: "The File KEY",
|
57
|
+
default_value: ""),
|
58
|
+
FastlaneCore::ConfigItem.new(key: :type,
|
59
|
+
short_option: "-s",
|
60
|
+
env_name: "CRYPTEX_TYPE",
|
61
|
+
description: "Sub-Action Type (export, import, decrypt)",
|
62
|
+
default_value: "export"),
|
63
|
+
FastlaneCore::ConfigItem.new(key: :verbose,
|
64
|
+
env_name: "CRYPTEX_VERBOSE",
|
65
|
+
description: "Print out extra information and all commands",
|
66
|
+
is_string: false,
|
67
|
+
default_value: false,
|
68
|
+
verify_block: proc do |value|
|
69
|
+
$verbose = true if value
|
70
|
+
end),
|
71
|
+
FastlaneCore::ConfigItem.new(key: :shallow_clone,
|
72
|
+
env_name: "CRYPTEX_SHALLOW_CLONE",
|
73
|
+
description: "Make a shallow clone of the repository (truncate the history to 1 revision)",
|
74
|
+
is_string: false,
|
75
|
+
default_value: false),
|
76
|
+
FastlaneCore::ConfigItem.new(key: :skip_docs,
|
77
|
+
env_name: "CRYPTEX_SKIP_DOCS",
|
78
|
+
description: "Skip generation of a README.md for the created git repository",
|
79
|
+
is_string: false,
|
80
|
+
default_value: false)
|
81
|
+
]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Cryptex
|
3
|
+
class Runner
|
4
|
+
attr_accessor :git_changed
|
5
|
+
|
6
|
+
def import_env(params)
|
7
|
+
env_world_file = "#{params[:workspace]}/#{params[:key]}_env_world.crypt"
|
8
|
+
File.write(env_world_file, params[:hash].to_json)
|
9
|
+
@git_changed = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def export_env(params)
|
13
|
+
env_world_file = "#{params[:workspace]}/#{params[:key]}_env_world.crypt"
|
14
|
+
world_data = File.read(env_world_file)
|
15
|
+
world_data_parsed = JSON.parse(world_data)
|
16
|
+
ret_json = {}
|
17
|
+
|
18
|
+
world_data_parsed.keys.each do |el|
|
19
|
+
next unless !params[:hash] || (params[:hash] && params[:hash].keys.include?(el))
|
20
|
+
ret_json[el] = world_data_parsed[el]
|
21
|
+
if params[:set_env]
|
22
|
+
ENV[el] = world_data_parsed[el]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
return world_data_parsed
|
26
|
+
end
|
27
|
+
|
28
|
+
def import_file(params)
|
29
|
+
File.write("#{params[:workspace]}/#{params[:key]}.crypt", File.read(File.expand_path(params[:in])))
|
30
|
+
@git_changed = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_file(params)
|
34
|
+
FileUtils.rm_f "#{params[:workspace]}/#{params[:key]}.crypt"
|
35
|
+
@git_changed = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def export_file(params)
|
39
|
+
File.write(File.expand_path(params[:out]), File.read("#{params[:workspace]}/#{params[:key]}.crypt"))
|
40
|
+
@git_changed = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def nuke_all(params)
|
44
|
+
Encrypt.new.iterate(params[:workspace]) do |item|
|
45
|
+
FileUtils.rm_f item
|
46
|
+
end
|
47
|
+
@git_changed = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def run(params)
|
51
|
+
FastlaneCore::PrintTable.print_values(config: params,
|
52
|
+
hide_keys: [],
|
53
|
+
title: "Summary for cryptex #{Cryptex::VERSION}")
|
54
|
+
@git_changed = false
|
55
|
+
params[:workspace] = GitHelper.clone(params[:git_url], params[:shallow_clone], skip_docs: params[:skip_docs], branch: params[:git_branch])
|
56
|
+
@params = params
|
57
|
+
if params[:type] == "import_env"
|
58
|
+
return import_env(params)
|
59
|
+
end
|
60
|
+
if params[:type] == "export_env"
|
61
|
+
return export_env(params)
|
62
|
+
end
|
63
|
+
if params[:type] == "import"
|
64
|
+
import_file(params)
|
65
|
+
return
|
66
|
+
end
|
67
|
+
if params[:type] == "delete"
|
68
|
+
delete_file(params)
|
69
|
+
return
|
70
|
+
end
|
71
|
+
if params[:type] == "export"
|
72
|
+
export_file(params)
|
73
|
+
return
|
74
|
+
end
|
75
|
+
if params[:type] == "nuke"
|
76
|
+
nuke_all(params)
|
77
|
+
return
|
78
|
+
end
|
79
|
+
ensure
|
80
|
+
if git_changed
|
81
|
+
message = GitHelper.generate_commit_message(params)
|
82
|
+
GitHelper.commit_changes(params[:workspace], message, params[:git_url], params[:git_branch])
|
83
|
+
end
|
84
|
+
GitHelper.clear_changes
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Cryptex
|
3
|
+
class Setup
|
4
|
+
def run(path)
|
5
|
+
template = File.read("#{Cryptex::ROOT}/assets/CryptexfileTemplate")
|
6
|
+
|
7
|
+
UI.important "Please create a new, private git repository"
|
8
|
+
UI.important "to store the certificates and profiles there"
|
9
|
+
url = UI.input("URL of the Git Repo: ")
|
10
|
+
|
11
|
+
template.gsub!("[[GIT_URL]]", url)
|
12
|
+
File.write(path, template)
|
13
|
+
UI.success "Successfully created '#{path}'. You can open the file using a code editor."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-cryptex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Helmut Januschka
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: commander
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.3.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.3.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: fastlane
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.105.2
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.105.2
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: match
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.8.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.8.1
|
125
|
+
description:
|
126
|
+
email: h.januschka@krone.at
|
127
|
+
executables:
|
128
|
+
- cryptex
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- LICENSE
|
133
|
+
- README.md
|
134
|
+
- bin/cryptex
|
135
|
+
- lib/fastlane/plugin/cryptex.rb
|
136
|
+
- lib/fastlane/plugin/cryptex/actions/cryptex_action.rb
|
137
|
+
- lib/fastlane/plugin/cryptex/actions/cryptex_generate_keystore.rb
|
138
|
+
- lib/fastlane/plugin/cryptex/assets/CryptexfileTemplate
|
139
|
+
- lib/fastlane/plugin/cryptex/assets/READMETemplate.md
|
140
|
+
- lib/fastlane/plugin/cryptex/change_password.rb
|
141
|
+
- lib/fastlane/plugin/cryptex/commands_generator.rb
|
142
|
+
- lib/fastlane/plugin/cryptex/encrypt.rb
|
143
|
+
- lib/fastlane/plugin/cryptex/git_helper.rb
|
144
|
+
- lib/fastlane/plugin/cryptex/helper/cryptex_helper.rb
|
145
|
+
- lib/fastlane/plugin/cryptex/options.rb
|
146
|
+
- lib/fastlane/plugin/cryptex/runner.rb
|
147
|
+
- lib/fastlane/plugin/cryptex/setup.rb
|
148
|
+
- lib/fastlane/plugin/cryptex/version.rb
|
149
|
+
homepage:
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.4.8
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: fastlane Crypt Store Git repo
|
173
|
+
test_files: []
|