mobile-secrets 0.0.8 → 0.1.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/lib/mobile-secrets.rb +20 -10
- data/lib/resources/SecretsSwift.erb +34 -10
- data/lib/resources/SecretsSwiftEmpty.erb +31 -0
- data/lib/resources/example.yml +9 -4
- data/lib/src/secrets_handler.rb +47 -12
- data/lib/src/source_renderer.rb +14 -2
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bfbc5bc8fa622ef393019dc29b77d0067e329ed7dffb68f966ed659265dc221
|
|
4
|
+
data.tar.gz: 199b3c9c305fc0de836e589dddd36fdd694362a2cff6b2b7ac55719c6f044f7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c48011bde102a1994bd6e4766bc8aef593c33a3fc84448d69727987802272de54165cc9e7059a00c711aaf0c0274372a7a79cca7005e218d5dd3192e8ca2f44d
|
|
7
|
+
data.tar.gz: e67c2c5e894ce0a29487957b976e3f30f023939302a1074ca3cc289ae50df18a77b13d1d1ae5dc7885973a47311b3585bc3ec6b221c0368d46737c97a850c729
|
data/lib/mobile-secrets.rb
CHANGED
|
@@ -20,15 +20,16 @@ module MobileSecrets
|
|
|
20
20
|
|
|
21
21
|
def options
|
|
22
22
|
opt = ""
|
|
23
|
-
opt << "--init-gpg PATH \t\tInitialize GPG in the directory.\n"
|
|
24
|
-
opt << "--create-template \t\tCreates a template yml file to configure the MobileSecrets\n"
|
|
25
|
-
opt << "--import SECRETS_PATH \t\tAdds MobileSecrets to GPG secrets\n"
|
|
26
|
-
opt << "--export PATH \
|
|
27
|
-
opt << "--encrypt-file FILE PASSWORD \tEncrypt a single file with AES\n"
|
|
28
|
-
opt << "--
|
|
23
|
+
opt << "--init-gpg PATH \t\t\tInitialize GPG in the directory.\n"
|
|
24
|
+
opt << "--create-template \t\t\tCreates a template yml file to configure the MobileSecrets\n"
|
|
25
|
+
opt << "--import SECRETS_PATH \t\t\tAdds MobileSecrets to GPG secrets\n"
|
|
26
|
+
opt << "--export PATH opt: ENCRYPTED_FILE_PATH \tCreates source file with obfuscated secrets at given PATH\n"
|
|
27
|
+
opt << "--encrypt-file FILE PASSWORD \t\tEncrypt a single file with AES\n"
|
|
28
|
+
opt << "--empty PATH \t\t\t\tGenerates a Secrets file without any data in it\n"
|
|
29
|
+
opt << "--usage \t\t\t\tManual for using MobileSecrets.\n\n"
|
|
29
30
|
opt << "Examples:\n"
|
|
30
31
|
opt << "--import \"./MobileSecrets.yml\"\n"
|
|
31
|
-
opt << "--export \"./Project/Src
|
|
32
|
+
opt << "--export \"./Project/Src\\n"
|
|
32
33
|
opt << "--init-gpg \".\""
|
|
33
34
|
opt
|
|
34
35
|
end
|
|
@@ -49,22 +50,31 @@ module MobileSecrets
|
|
|
49
50
|
FileUtils.cp("#{__dir__}/../lib/resources/example.yml", "#{Dir.pwd}#{File::SEPARATOR}MobileSecrets.yml")
|
|
50
51
|
when "--export"
|
|
51
52
|
return print_options if argv_1 == nil
|
|
53
|
+
encrypted_file_path = argv_2 ||= "secrets.gpg"
|
|
52
54
|
|
|
53
55
|
secrets_handler = MobileSecrets::SecretsHandler.new
|
|
54
|
-
secrets_handler.export_secrets argv_1
|
|
56
|
+
secrets_handler.export_secrets argv_1, argv_2
|
|
55
57
|
when "--init-gpg"
|
|
56
58
|
return print_options if argv_1 == nil
|
|
57
59
|
|
|
58
60
|
Dotgpg::Cli.new.init(argv_1)
|
|
59
61
|
when "--import"
|
|
60
62
|
return print_options if argv_1 == nil
|
|
61
|
-
|
|
63
|
+
gpg_file = argv_2 ||= "secrets.gpg"
|
|
62
64
|
file = IO.read argv_1
|
|
63
|
-
MobileSecrets::SecretsHandler.new.encrypt
|
|
65
|
+
MobileSecrets::SecretsHandler.new.encrypt gpg_file, file, nil
|
|
64
66
|
when "--encrypt-file"
|
|
65
67
|
file = argv_1
|
|
66
68
|
password = argv_2
|
|
67
69
|
MobileSecrets::SecretsHandler.new.encrypt_file password, file, "#{file}.enc"
|
|
70
|
+
when "--empty"
|
|
71
|
+
return print_options if argv_1 == nil
|
|
72
|
+
file_path = argv_1
|
|
73
|
+
|
|
74
|
+
MobileSecrets::SourceRenderer.new("swift").render_empty_template "#{file_path}/secrets.swift"
|
|
75
|
+
when "--edit"
|
|
76
|
+
return print_options if argv_1 == nil
|
|
77
|
+
exec("dotgpg", "edit", argv_1)
|
|
68
78
|
when "--usage"
|
|
69
79
|
puts usage
|
|
70
80
|
else
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
<% if should_decrypt_files %>import CommonCrypto<% end %>
|
|
6
|
+
<% if algorithm == "AES-GCM" %>import CryptoKit<% end %>
|
|
6
7
|
import Foundation
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// swiftlint:disable all
|
|
10
|
+
<% if algorithm == "AES-GCM" %>@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
|
|
11
|
+
<% end %>final class Secrets: Sendable {
|
|
9
12
|
static let standard = Secrets()
|
|
10
13
|
private let bytes: [[UInt8]] = <%= secrets_array.to_s.gsub "],", "],\n "%>
|
|
11
14
|
<% if should_decrypt_files %>
|
|
12
15
|
private let fileNames: [[UInt8]] = <%= file_names_array.to_s.gsub "],", "],\n "%>
|
|
13
16
|
<% end %>
|
|
14
|
-
|
|
15
17
|
private init() {}
|
|
16
18
|
|
|
17
19
|
func string(forKey key: String, password: String? = nil) -> String? {
|
|
@@ -23,6 +25,27 @@ class Secrets {
|
|
|
23
25
|
return String(data: Data(value), encoding: .utf8)
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
<% if algorithm == "AES-GCM" %>
|
|
29
|
+
// AES-256-GCM decryption. Input layout: IV(12) + AuthTag(16) + Ciphertext(N)
|
|
30
|
+
private func decrypt(_ input: [UInt8], password: [UInt8]) -> [UInt8]? {
|
|
31
|
+
guard password.count == 32 else { return nil }
|
|
32
|
+
let ivSize = 12
|
|
33
|
+
let tagSize = 16
|
|
34
|
+
guard input.count > ivSize + tagSize else { return nil }
|
|
35
|
+
do {
|
|
36
|
+
let key = SymmetricKey(data: Data(password))
|
|
37
|
+
let nonce = try AES.GCM.Nonce(data: Data(input[0..<ivSize]))
|
|
38
|
+
let tag = Data(input[ivSize..<(ivSize + tagSize)])
|
|
39
|
+
let ciphertext = Data(input[(ivSize + tagSize)...])
|
|
40
|
+
let sealedBox = try AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
|
|
41
|
+
let decrypted = try AES.GCM.open(sealedBox, using: key)
|
|
42
|
+
return [UInt8](decrypted)
|
|
43
|
+
} catch {
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
<% else %>
|
|
48
|
+
// XOR-based deobfuscation. The key cycles over the password bytes.
|
|
26
49
|
private func decrypt(_ input: [UInt8], password: [UInt8]) -> [UInt8]? {
|
|
27
50
|
guard !password.isEmpty else { return nil }
|
|
28
51
|
var output = [UInt8]()
|
|
@@ -31,7 +54,7 @@ class Secrets {
|
|
|
31
54
|
}
|
|
32
55
|
return output
|
|
33
56
|
}
|
|
34
|
-
|
|
57
|
+
<% end %>
|
|
35
58
|
<% if should_decrypt_files %>
|
|
36
59
|
func decryptFiles(bundle: Bundle = Bundle.main, password: String? = nil) throws {
|
|
37
60
|
try fileNames.forEach({ (fileNameBytes) in
|
|
@@ -60,7 +83,7 @@ class Secrets {
|
|
|
60
83
|
outputURL.appendPathComponent(fileName)
|
|
61
84
|
|
|
62
85
|
do {
|
|
63
|
-
let aes = try
|
|
86
|
+
let aes = try AESFileCipher(keyString: pwd)
|
|
64
87
|
let decryptedString = try aes.decrypt(fileData)
|
|
65
88
|
try decryptedString.write(to: outputURL, atomically: true, encoding: .utf8)
|
|
66
89
|
} catch let e {
|
|
@@ -68,7 +91,8 @@ class Secrets {
|
|
|
68
91
|
}
|
|
69
92
|
}
|
|
70
93
|
|
|
71
|
-
|
|
94
|
+
// AES-256-CBC cipher used for decrypting .enc file resources.
|
|
95
|
+
private struct AESFileCipher {
|
|
72
96
|
enum Error: Swift.Error {
|
|
73
97
|
case invalidKeySize
|
|
74
98
|
case encryptionFailed
|
|
@@ -78,7 +102,7 @@ class Secrets {
|
|
|
78
102
|
|
|
79
103
|
private var key: Data
|
|
80
104
|
private var ivSize: Int = kCCBlockSizeAES128
|
|
81
|
-
private let options: CCOptions
|
|
105
|
+
private let options: CCOptions = CCOptions(kCCOptionPKCS7Padding)
|
|
82
106
|
|
|
83
107
|
init(keyString: String) throws {
|
|
84
108
|
guard keyString.count == kCCKeySizeAES256 else {
|
|
@@ -103,10 +127,10 @@ class Secrets {
|
|
|
103
127
|
throw Error.encryptionFailed
|
|
104
128
|
}
|
|
105
129
|
|
|
106
|
-
let cryptStatus: CCCryptorStatus = CCCrypt( // Stateless, one-shot
|
|
130
|
+
let cryptStatus: CCCryptorStatus = CCCrypt( // Stateless, one-shot decrypt operation
|
|
107
131
|
CCOperation(kCCDecrypt), // op: CCOperation
|
|
108
|
-
CCAlgorithm(kCCAlgorithmAES),
|
|
109
|
-
options, // options: CCOptions
|
|
132
|
+
CCAlgorithm(kCCAlgorithmAES), // alg: CCAlgorithm
|
|
133
|
+
options, // options: CCOptions (PKCS7 padding)
|
|
110
134
|
keyBytesBaseAddress, // key: the "password"
|
|
111
135
|
key.count, // keyLength: the "password" size
|
|
112
136
|
dataToDecryptBytesBaseAddress, // iv: Initialization Vector
|
|
@@ -134,6 +158,6 @@ class Secrets {
|
|
|
134
158
|
|
|
135
159
|
return decryptedString
|
|
136
160
|
}
|
|
137
|
-
|
|
161
|
+
}
|
|
138
162
|
<% end %>
|
|
139
163
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Autogenerated file by Mobile Secrets
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
// swiftlint:disable all
|
|
8
|
+
class Secrets {
|
|
9
|
+
static let standard = Secrets()
|
|
10
|
+
private let bytes: [[UInt8]] = [[0]]
|
|
11
|
+
|
|
12
|
+
private init() {}
|
|
13
|
+
|
|
14
|
+
func string(forKey key: String, password: String? = nil) -> String? {
|
|
15
|
+
let pwdBytes = password == nil ? bytes[0] : password?.map({ c in c.asciiValue ?? 0 })
|
|
16
|
+
guard let index = bytes.firstIndex(where: { String(data: Data($0), encoding: .utf8) == key }),
|
|
17
|
+
let pwd = pwdBytes,
|
|
18
|
+
let value = decrypt(bytes[index + 1], password: pwd) else { return nil }
|
|
19
|
+
|
|
20
|
+
return String(data: Data(value), encoding: .utf8)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private func decrypt(_ input: [UInt8], password: [UInt8]) -> [UInt8]? {
|
|
24
|
+
guard !password.isEmpty else { return nil }
|
|
25
|
+
var output = [UInt8]()
|
|
26
|
+
for byte in input.enumerated() {
|
|
27
|
+
output.append(byte.element ^ password[byte.offset % password.count])
|
|
28
|
+
}
|
|
29
|
+
return output
|
|
30
|
+
}
|
|
31
|
+
}
|
data/lib/resources/example.yml
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
MobileSecrets:
|
|
2
2
|
# hashKey: Key that will be used to hash the secret values.
|
|
3
3
|
# For encrypting files the key needs to be 32 chars long as an AES standard.
|
|
4
|
-
|
|
4
|
+
# When using alg: "AES-GCM" the key must also be exactly 32 characters.
|
|
5
|
+
hashKey: "REPLACE_THIS_32_CHAR_HASH_KEY___"
|
|
5
6
|
# shouldIncludePassword: By default the password is saved in the code as a series of bytes, however it can also
|
|
6
7
|
# be fetched from your API, saved in keychain and passed to the Secrets for improving the security.
|
|
7
8
|
shouldIncludePassword: true
|
|
8
9
|
# language: Swift is currently only supported language, Kotlin is coming soon.
|
|
9
10
|
language: "Swift"
|
|
11
|
+
# alg: The algorithm used to obfuscate secret values. Options: "XOR" (default) or "AES-GCM".
|
|
12
|
+
# AES-GCM provides authenticated encryption and is stronger than XOR obfuscation.
|
|
13
|
+
# Note: AES-GCM requires hashKey to be exactly 32 characters and iOS 13+ / macOS 10.15+.
|
|
14
|
+
alg: "XOR"
|
|
10
15
|
# Key-value dictionary for secrets. The key is then referenced in the code to get the secret.
|
|
11
16
|
secrets:
|
|
12
|
-
googleMaps: "
|
|
13
|
-
firebase: "
|
|
14
|
-
amazon: "
|
|
17
|
+
googleMaps: "YOUR_GOOGLE_MAPS_KEY"
|
|
18
|
+
firebase: "YOUR_FIREBASE_KEY"
|
|
19
|
+
amazon: "YOUR_AMAZON_KEY"
|
|
15
20
|
# Optional, remove files if you do not want to encrypt them
|
|
16
21
|
files:
|
|
17
22
|
- tmp.txt
|
data/lib/src/secrets_handler.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require "dotgpg"
|
|
2
2
|
require "yaml"
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "stringio"
|
|
3
5
|
|
|
4
6
|
require_relative '../src/obfuscator'
|
|
5
7
|
require_relative '../src/file_handler'
|
|
@@ -8,38 +10,52 @@ require_relative '../src/source_renderer'
|
|
|
8
10
|
module MobileSecrets
|
|
9
11
|
class SecretsHandler
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
SUPPORTED_ALGORITHMS = %w[XOR AES-GCM].freeze
|
|
14
|
+
|
|
15
|
+
def export_secrets path, from_encrypted_file_name
|
|
16
|
+
decrypted_config = decrypt_secrets(from_encrypted_file_name)
|
|
17
|
+
file_names_bytes, secrets_bytes, algorithm = process_yaml_config decrypted_config
|
|
14
18
|
|
|
15
19
|
renderer = MobileSecrets::SourceRenderer.new "swift"
|
|
16
|
-
renderer.render_template secrets_bytes, file_names_bytes, "#{path}/secrets.swift"
|
|
20
|
+
renderer.render_template secrets_bytes, file_names_bytes, "#{path}/secrets.swift", algorithm
|
|
21
|
+
decrypted_config
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def process_yaml_config yaml_string
|
|
20
|
-
config = YAML.
|
|
25
|
+
config = YAML.safe_load(yaml_string)["MobileSecrets"]
|
|
21
26
|
hash_key = config["hashKey"]
|
|
22
27
|
secrets_dict = config["secrets"]
|
|
23
28
|
files = config["files"]
|
|
24
29
|
should_include_password = config["shouldIncludePassword"]
|
|
30
|
+
algorithm = (config["alg"] || "XOR").upcase
|
|
31
|
+
|
|
32
|
+
abort("Unsupported algorithm '#{algorithm}'. Valid options: #{SUPPORTED_ALGORITHMS.join(', ')}.") \
|
|
33
|
+
unless SUPPORTED_ALGORITHMS.include?(algorithm)
|
|
34
|
+
abort("hashKey must be exactly 32 characters for AES-GCM encryption.") \
|
|
35
|
+
if algorithm == "AES-GCM" && hash_key.length != 32
|
|
36
|
+
|
|
25
37
|
secrets_bytes = should_include_password ? [hash_key.bytes] : []
|
|
26
38
|
file_names_bytes = []
|
|
27
39
|
obfuscator = MobileSecrets::Obfuscator.new hash_key
|
|
28
40
|
|
|
29
41
|
secrets_dict.each do |key, value|
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
if algorithm == "AES-GCM"
|
|
43
|
+
secrets_bytes << key.bytes << encrypt_aes_gcm(value.to_s, hash_key, key)
|
|
44
|
+
else
|
|
45
|
+
encrypted = obfuscator.obfuscate(value.to_s)
|
|
46
|
+
secrets_bytes << key.bytes << encrypted.bytes
|
|
47
|
+
end
|
|
32
48
|
end
|
|
33
49
|
|
|
34
50
|
if files
|
|
35
|
-
abort("
|
|
51
|
+
abort("hashKey must be 32 characters long for files encryption.") if hash_key.length != 32
|
|
36
52
|
files.each do |f|
|
|
37
53
|
encrypt_file hash_key, f, "#{f}.enc"
|
|
38
54
|
file_names_bytes << f.bytes
|
|
39
55
|
end
|
|
40
56
|
end
|
|
41
57
|
|
|
42
|
-
return file_names_bytes, secrets_bytes
|
|
58
|
+
return file_names_bytes, secrets_bytes, algorithm
|
|
43
59
|
end
|
|
44
60
|
|
|
45
61
|
def encrypt output_file_path, string, gpg_path
|
|
@@ -51,6 +67,7 @@ module MobileSecrets
|
|
|
51
67
|
|
|
52
68
|
def encrypt_file password, file, output_file_path
|
|
53
69
|
encryptor = FileHandler.new password
|
|
70
|
+
abort("Configuration contains file #{file} that cannot be found! Please check your mobile-secrets configuration or add the file into directory.") unless File.exist? file
|
|
54
71
|
encrypted_content = encryptor.encrypt file
|
|
55
72
|
|
|
56
73
|
File.open(output_file_path, "wb") { |f| f.write encrypted_content }
|
|
@@ -58,10 +75,28 @@ module MobileSecrets
|
|
|
58
75
|
|
|
59
76
|
private
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
# Encrypts a secret value with AES-256-GCM using a deterministic IV.
|
|
79
|
+
# The IV is derived via HMAC-SHA256(key, "secret_name:value") truncated to 12 bytes,
|
|
80
|
+
# so identical inputs always produce identical ciphertext while ensuring that
|
|
81
|
+
# changing either the value or the secret name produces a different IV — preventing
|
|
82
|
+
# nonce reuse, which would be catastrophic for GCM authentication.
|
|
83
|
+
# Returns bytes laid out as: IV(12) + AuthTag(16) + Ciphertext(N)
|
|
84
|
+
def encrypt_aes_gcm(value, key_string, secret_name)
|
|
85
|
+
iv = OpenSSL::HMAC.digest('SHA256', key_string, "#{secret_name}:#{value}")[0, 12]
|
|
86
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
|
87
|
+
cipher.encrypt
|
|
88
|
+
cipher.iv = iv
|
|
89
|
+
cipher.key = key_string
|
|
90
|
+
cipher.auth_data = ""
|
|
91
|
+
ciphertext = cipher.update(value) + cipher.final
|
|
92
|
+
tag = cipher.auth_tag(16)
|
|
93
|
+
(iv + tag + ciphertext).bytes
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def decrypt_secrets encrypted_file_name
|
|
97
|
+
gpg = Dotgpg::Dir.closest encrypted_file_name
|
|
63
98
|
output = StringIO.new
|
|
64
|
-
gpg.decrypt "#{Dir.pwd}
|
|
99
|
+
gpg.decrypt "#{Dir.pwd}/#{encrypted_file_name}", output
|
|
65
100
|
output.string
|
|
66
101
|
end
|
|
67
102
|
|
data/lib/src/source_renderer.rb
CHANGED
|
@@ -8,7 +8,7 @@ module MobileSecrets
|
|
|
8
8
|
@source_type = source_type.downcase
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def render_template secrets_bytes, file_names_bytes, output_file_path
|
|
11
|
+
def render_template secrets_bytes, file_names_bytes, output_file_path, algorithm = "XOR"
|
|
12
12
|
template = ERB.new(File.read("#{__dir__}/../resources/SecretsSwift.erb"))
|
|
13
13
|
|
|
14
14
|
case @source_type
|
|
@@ -16,7 +16,19 @@ module MobileSecrets
|
|
|
16
16
|
File.open(output_file_path, "w") do |file|
|
|
17
17
|
file.puts template.result_with_hash(secrets_array: secrets_bytes,
|
|
18
18
|
file_names_array: file_names_bytes,
|
|
19
|
-
should_decrypt_files: file_names_bytes.length > 0
|
|
19
|
+
should_decrypt_files: file_names_bytes.length > 0,
|
|
20
|
+
algorithm: algorithm)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render_empty_template output_file_path
|
|
26
|
+
template = File.read("#{__dir__}/../resources/SecretsSwiftEmpty.erb")
|
|
27
|
+
|
|
28
|
+
case @source_type
|
|
29
|
+
when "swift"
|
|
30
|
+
File.open(output_file_path, "w") do |file|
|
|
31
|
+
file.puts template
|
|
20
32
|
end
|
|
21
33
|
end
|
|
22
34
|
end
|
metadata
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mobile-secrets
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Cermak
|
|
8
8
|
- Joerg Nestele
|
|
9
|
-
autorequire:
|
|
9
|
+
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
12
|
date: 2019-09-27 00:00:00.000000000 Z
|
|
@@ -25,6 +25,20 @@ dependencies:
|
|
|
25
25
|
- - '='
|
|
26
26
|
- !ruby/object:Gem::Version
|
|
27
27
|
version: 0.7.0
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: minitest-reporters
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '1.8'
|
|
35
|
+
type: :development
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '1.8'
|
|
28
42
|
description: Handle mobile secrets the secure way with ease
|
|
29
43
|
email: cyril.cermakk@gmail.com
|
|
30
44
|
executables:
|
|
@@ -36,6 +50,7 @@ files:
|
|
|
36
50
|
- bin/mobile-secrets
|
|
37
51
|
- lib/mobile-secrets.rb
|
|
38
52
|
- lib/resources/SecretsSwift.erb
|
|
53
|
+
- lib/resources/SecretsSwiftEmpty.erb
|
|
39
54
|
- lib/resources/example.yml
|
|
40
55
|
- lib/src/file_handler.rb
|
|
41
56
|
- lib/src/obfuscator.rb
|
|
@@ -45,7 +60,7 @@ homepage: https://github.com/CyrilCermak/mobile-secrets
|
|
|
45
60
|
licenses:
|
|
46
61
|
- MIT
|
|
47
62
|
metadata: {}
|
|
48
|
-
post_install_message:
|
|
63
|
+
post_install_message:
|
|
49
64
|
rdoc_options: []
|
|
50
65
|
require_paths:
|
|
51
66
|
- lib
|
|
@@ -60,8 +75,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
60
75
|
- !ruby/object:Gem::Version
|
|
61
76
|
version: '0'
|
|
62
77
|
requirements: []
|
|
63
|
-
rubygems_version: 3.
|
|
64
|
-
signing_key:
|
|
78
|
+
rubygems_version: 3.4.10
|
|
79
|
+
signing_key:
|
|
65
80
|
specification_version: 4
|
|
66
81
|
summary: mobile-secrets tool for handling your mobile secrets
|
|
67
82
|
test_files: []
|