mobile-secrets 0.0.5 → 0.0.6
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 +6 -1
- data/lib/resources/SecretsSwift.erb +139 -0
- data/lib/resources/example.yml +14 -3
- data/lib/src/file_handler.rb +20 -0
- data/lib/src/secrets_handler.rb +28 -15
- data/lib/src/source_renderer.rb +25 -0
- metadata +4 -2
- data/lib/resources/SecretsTemplate.swift +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22e621754c652265b906fb99949160cba7f05f169e1a742bed477cc93213d8ef
|
4
|
+
data.tar.gz: 6cf400c9265bb3bbc3e8b0f3e969d4139f86643063a00fbfb49f957ecdbd5580
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6856b5a3cf2b2885c30837eaaa6b3eb731d852ea0299bb505b2efa585103b537b7cdecf0f244acdff9b2d68266d7dae3f8a65648e4a448f29543e519e45dc9fc
|
7
|
+
data.tar.gz: 31602061ae1ba76be8171fdeac0539d29522bf643ab5268628187fac5e898d193d47cdce7025a9189353bf269e4451495874a04c960740c38f8933d2d560ace8
|
data/lib/mobile-secrets.rb
CHANGED
@@ -24,6 +24,7 @@ module MobileSecrets
|
|
24
24
|
opt << "--create-template \t\tCreates a template yml file to configure the MobileSecrets\n"
|
25
25
|
opt << "--import SECRETS_PATH \tAdds MobileSecrets to GPG secrets\n"
|
26
26
|
opt << "--export PATH \t\t\tCreates source file with obfuscated secrets at given PATH\n"
|
27
|
+
opt << "--encrypt-file \t\t\tEncrypt a single file with AES"
|
27
28
|
opt << "--usage \t\t\tManual for using MobileSecrets.\n\n"
|
28
29
|
opt << "Examples:\n"
|
29
30
|
opt << "--import \"./MobileSecrets.yml\"\n"
|
@@ -37,7 +38,7 @@ module MobileSecrets
|
|
37
38
|
usage << "1) Create gpg first with --init-gpg \".\"\n"
|
38
39
|
usage << "2) Create a template for MobileSecrets with --create-template\n"
|
39
40
|
usage << "3) Configure MobileSecrets.yml with your hash key, secrets etc\n"
|
40
|
-
usage << "4) Import edited template to encrypted secret.gpg with --import\n"
|
41
|
+
usage << "4) Import edited template to encrypted secret.gpg with --import ./MobileSecrets.yml\n"
|
41
42
|
usage << "5) Export secrets from secrets.gpg to source file with --export and PATH to project\n"
|
42
43
|
usage << "6) Add exported source file to the project\n"
|
43
44
|
end
|
@@ -60,6 +61,10 @@ module MobileSecrets
|
|
60
61
|
|
61
62
|
file = IO.read argv_1
|
62
63
|
MobileSecrets::SecretsHandler.new.encrypt "./secrets.gpg", file, nil
|
64
|
+
when "--encrypt-file"
|
65
|
+
file = argv_1
|
66
|
+
password = argv_2
|
67
|
+
MobileSecrets::SecretsHandler.new.encrypt_file password, file, "#{file}.enc"
|
63
68
|
when "--usage"
|
64
69
|
puts usage
|
65
70
|
else
|
@@ -0,0 +1,139 @@
|
|
1
|
+
//
|
2
|
+
// Autogenerated file by Mobile Secrets
|
3
|
+
//
|
4
|
+
|
5
|
+
<% if should_decrypt_files %>import CommonCrypto<% end %>
|
6
|
+
import Foundation
|
7
|
+
|
8
|
+
class Secrets {
|
9
|
+
static let standard = Secrets()
|
10
|
+
private let bytes: [[UInt8]] = <%= secrets_array.to_s.gsub "],", "],\n "%>
|
11
|
+
<% if should_decrypt_files %>
|
12
|
+
private let fileNames: [[UInt8]] = <%= file_names_array.to_s.gsub "],", "],\n "%>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
private init() {}
|
16
|
+
|
17
|
+
func string(forKey key: String, password: String? = nil) -> String? {
|
18
|
+
let pwdBytes = password == nil ? bytes[0] : password?.map({ c in c.asciiValue ?? 0 })
|
19
|
+
guard let index = bytes.firstIndex(where: { String(data: Data($0), encoding: .utf8) == key }),
|
20
|
+
let pwd = pwdBytes,
|
21
|
+
let value = decrypt(bytes[index + 1], password: pwd) else { return nil }
|
22
|
+
|
23
|
+
return String(data: Data(value), encoding: .utf8)
|
24
|
+
}
|
25
|
+
|
26
|
+
private func decrypt(_ input: [UInt8], password: [UInt8]) -> [UInt8]? {
|
27
|
+
guard !password.isEmpty else { return nil }
|
28
|
+
var output = [UInt8]()
|
29
|
+
for byte in input.enumerated() {
|
30
|
+
output.append(byte.element ^ password[byte.offset % password.count])
|
31
|
+
}
|
32
|
+
return output
|
33
|
+
}
|
34
|
+
|
35
|
+
<% if should_decrypt_files %>
|
36
|
+
func decryptFiles(bundle: Bundle = Bundle.main, password: String? = nil) throws {
|
37
|
+
try fileNames.forEach({ (fileNameBytes) in
|
38
|
+
guard let name = String(data: Data(fileNameBytes), encoding: .utf8) else {
|
39
|
+
fatalError("Wrong name in file names")
|
40
|
+
}
|
41
|
+
|
42
|
+
try decryptFile(name, bundle: bundle, password: password)
|
43
|
+
})
|
44
|
+
}
|
45
|
+
|
46
|
+
func decryptFile(_ fileName: String, bundle: Bundle = Bundle.main, password: String? = nil) throws {
|
47
|
+
let password = password == nil ? String(data: Data(bytes[0]), encoding: .utf8) : password
|
48
|
+
|
49
|
+
guard let pwd = password else {
|
50
|
+
fatalError("No password for decryption was provided!")
|
51
|
+
}
|
52
|
+
|
53
|
+
guard let filePath = bundle.path(forResource: fileName, ofType: "enc"),
|
54
|
+
let fileURL = URL(string: "file://" + filePath),
|
55
|
+
let fileData = try? Data(contentsOf: fileURL) else {
|
56
|
+
fatalError("File \(fileName) was not found in bundle!")
|
57
|
+
}
|
58
|
+
|
59
|
+
var outputURL = bundle.bundleURL
|
60
|
+
outputURL.appendPathComponent(fileName)
|
61
|
+
|
62
|
+
do {
|
63
|
+
let aes = try AES(keyString: pwd)
|
64
|
+
let decryptedString = try aes.decrypt(fileData)
|
65
|
+
try decryptedString.write(to: outputURL, atomically: true, encoding: .utf8)
|
66
|
+
} catch let e {
|
67
|
+
throw e
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
struct AES {
|
72
|
+
enum Error: Swift.Error {
|
73
|
+
case invalidKeySize
|
74
|
+
case encryptionFailed
|
75
|
+
case decryptionFailed
|
76
|
+
case dataToStringFailed
|
77
|
+
}
|
78
|
+
|
79
|
+
private var key: Data
|
80
|
+
private var ivSize: Int = kCCBlockSizeAES128
|
81
|
+
private let options: CCOptions = CCOptions()
|
82
|
+
|
83
|
+
init(keyString: String) throws {
|
84
|
+
guard keyString.count == kCCKeySizeAES256 else {
|
85
|
+
throw Error.invalidKeySize
|
86
|
+
}
|
87
|
+
self.key = Data(keyString.utf8)
|
88
|
+
}
|
89
|
+
|
90
|
+
func decrypt(_ data: Data) throws -> String {
|
91
|
+
let bufferSize: Int = data.count - ivSize
|
92
|
+
var buffer = Data(count: bufferSize)
|
93
|
+
var numberBytesDecrypted: Int = 0
|
94
|
+
|
95
|
+
do {
|
96
|
+
try key.withUnsafeBytes { keyBytes in
|
97
|
+
try data.withUnsafeBytes { dataToDecryptBytes in
|
98
|
+
try buffer.withUnsafeMutableBytes { bufferBytes in
|
99
|
+
|
100
|
+
guard let keyBytesBaseAddress = keyBytes.baseAddress,
|
101
|
+
let dataToDecryptBytesBaseAddress = dataToDecryptBytes.baseAddress,
|
102
|
+
let bufferBytesBaseAddress = bufferBytes.baseAddress else {
|
103
|
+
throw Error.encryptionFailed
|
104
|
+
}
|
105
|
+
|
106
|
+
let cryptStatus: CCCryptorStatus = CCCrypt( // Stateless, one-shot encrypt operation
|
107
|
+
CCOperation(kCCDecrypt), // op: CCOperation
|
108
|
+
CCAlgorithm(kCCAlgorithmAES), // alg: CCAlgorithm
|
109
|
+
options, // options: CCOptions
|
110
|
+
keyBytesBaseAddress, // key: the "password"
|
111
|
+
key.count, // keyLength: the "password" size
|
112
|
+
dataToDecryptBytesBaseAddress, // iv: Initialization Vector
|
113
|
+
dataToDecryptBytesBaseAddress + ivSize, // dataIn: Data to decrypt bytes
|
114
|
+
bufferSize, // dataInLength: Data to decrypt size
|
115
|
+
bufferBytesBaseAddress, // dataOut: decrypted Data buffer
|
116
|
+
bufferSize, // dataOutAvailable: decrypted Data buffer size
|
117
|
+
&numberBytesDecrypted // dataOutMoved: the number of bytes written
|
118
|
+
)
|
119
|
+
|
120
|
+
guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
|
121
|
+
throw Error.decryptionFailed
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
} catch {
|
127
|
+
throw Error.encryptionFailed
|
128
|
+
}
|
129
|
+
|
130
|
+
let decryptedData: Data = buffer[..<numberBytesDecrypted]
|
131
|
+
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
|
132
|
+
throw Error.dataToStringFailed
|
133
|
+
}
|
134
|
+
|
135
|
+
return decryptedString
|
136
|
+
}
|
137
|
+
}
|
138
|
+
<% end %>
|
139
|
+
}
|
data/lib/resources/example.yml
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
MobileSecrets:
|
2
|
-
hashKey:
|
3
|
-
|
4
|
-
|
2
|
+
# hashKey: Key that will be used to hash the secret values.
|
3
|
+
# For encrypting files the key needs to be 32 chars long as an AES standard.
|
4
|
+
hashKey: "KokoBelloKokoKokoBelloKokoKokoBe"
|
5
|
+
# shouldIncludePassword: By default the password is saved in the code as a series of bytes, however it can also
|
6
|
+
# be fetched from your API, saved in keychain and passed to the Secrets for improving the security.
|
7
|
+
shouldIncludePassword: true
|
8
|
+
# language: Swift is currently only supported language, Kotlin is coming soon.
|
9
|
+
language: "Swift"
|
10
|
+
# Key-value dictionary for secrets. The key is then referenced in the code to get the secret.
|
11
|
+
secrets:
|
5
12
|
googleMaps: "123123123"
|
6
13
|
firebase: "asdasdasd"
|
7
14
|
amazon: "asd123asd123"
|
15
|
+
# Optional, remove files if you do not want to encrypt them
|
16
|
+
files:
|
17
|
+
- tmp.txt
|
18
|
+
- Info.plist
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module MobileSecrets
|
4
|
+
class FileHandler
|
5
|
+
|
6
|
+
def initialize password
|
7
|
+
@password = password
|
8
|
+
end
|
9
|
+
|
10
|
+
def encrypt file
|
11
|
+
file_content = File.read file
|
12
|
+
cipher = OpenSSL::Cipher::AES256.new :CBC
|
13
|
+
cipher.encrypt
|
14
|
+
iv = cipher.random_iv
|
15
|
+
cipher.key = @password
|
16
|
+
cipher_text = iv + cipher.update(file_content) + cipher.final
|
17
|
+
cipher_text
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/src/secrets_handler.rb
CHANGED
@@ -1,39 +1,45 @@
|
|
1
1
|
require "dotgpg"
|
2
2
|
require "yaml"
|
3
|
+
|
3
4
|
require_relative '../src/obfuscator'
|
5
|
+
require_relative '../src/file_handler'
|
6
|
+
require_relative '../src/source_renderer'
|
4
7
|
|
5
8
|
module MobileSecrets
|
6
9
|
class SecretsHandler
|
7
10
|
|
8
11
|
def export_secrets path
|
9
12
|
decrypted_config = decrypt_secrets()
|
10
|
-
|
11
|
-
|
13
|
+
file_names_bytes, secrets_bytes = process_yaml_config decrypted_config
|
14
|
+
|
15
|
+
renderer = MobileSecrets::SourceRenderer.new "swift"
|
16
|
+
renderer.render_template secrets_bytes, file_names_bytes, "#{path}/secrets.swift"
|
12
17
|
end
|
13
18
|
|
14
19
|
def process_yaml_config yaml_string
|
15
20
|
config = YAML.load(yaml_string)["MobileSecrets"]
|
16
21
|
hash_key = config["hashKey"]
|
17
|
-
obfuscator = MobileSecrets::Obfuscator.new hash_key
|
18
|
-
|
19
|
-
bytes = [hash_key.bytes]
|
20
22
|
secrets_dict = config["secrets"]
|
23
|
+
files = config["files"]
|
24
|
+
should_include_password = config["shouldIncludePassword"]
|
25
|
+
secrets_bytes = should_include_password ? [hash_key.bytes] : []
|
26
|
+
file_names_bytes = []
|
27
|
+
obfuscator = MobileSecrets::Obfuscator.new hash_key
|
21
28
|
|
22
29
|
secrets_dict.each do |key, value|
|
23
30
|
encrypted = obfuscator.obfuscate(value)
|
24
|
-
|
31
|
+
secrets_bytes << key.bytes << encrypted.bytes
|
25
32
|
end
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
swift_secrets = template.sub "/* SECRET BYTES */", bytes_variable
|
34
|
+
if files
|
35
|
+
abort("Password must be 13 characters long for files encryption.") if hash_key.length != 32
|
36
|
+
files.each do |f|
|
37
|
+
encrypt_file hash_key, f, "#{f}.enc"
|
38
|
+
file_names_bytes << f.bytes
|
39
|
+
end
|
40
|
+
end
|
35
41
|
|
36
|
-
|
42
|
+
return file_names_bytes, secrets_bytes
|
37
43
|
end
|
38
44
|
|
39
45
|
def encrypt output_file_path, string, gpg_path
|
@@ -43,6 +49,13 @@ module MobileSecrets
|
|
43
49
|
dotgpg.encrypt output_file_path, string
|
44
50
|
end
|
45
51
|
|
52
|
+
def encrypt_file password, file, output_file_path
|
53
|
+
encryptor = FileHandler.new password
|
54
|
+
encrypted_content = encryptor.encrypt file
|
55
|
+
|
56
|
+
File.open(output_file_path, "wb") { |f| f.write encrypted_content }
|
57
|
+
end
|
58
|
+
|
46
59
|
private
|
47
60
|
|
48
61
|
def decrypt_secrets
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
Book = Struct.new(:title, :author)
|
4
|
+
module MobileSecrets
|
5
|
+
class SourceRenderer
|
6
|
+
|
7
|
+
def initialize source_type
|
8
|
+
@source_type = source_type.downcase
|
9
|
+
end
|
10
|
+
|
11
|
+
def render_template secrets_bytes, file_names_bytes, output_file_path
|
12
|
+
template = ERB.new(File.read("#{__dir__}/../resources/SecretsSwift.erb"))
|
13
|
+
|
14
|
+
case @source_type
|
15
|
+
when "swift"
|
16
|
+
File.open(output_file_path, "w") do |file|
|
17
|
+
file.puts template.result_with_hash(secrets_array: secrets_bytes,
|
18
|
+
file_names_array: file_names_bytes,
|
19
|
+
should_decrypt_files: file_names_bytes.length > 0)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Cermak
|
@@ -35,10 +35,12 @@ files:
|
|
35
35
|
- Rakefile
|
36
36
|
- bin/mobile-secrets
|
37
37
|
- lib/mobile-secrets.rb
|
38
|
-
- lib/resources/
|
38
|
+
- lib/resources/SecretsSwift.erb
|
39
39
|
- lib/resources/example.yml
|
40
|
+
- lib/src/file_handler.rb
|
40
41
|
- lib/src/obfuscator.rb
|
41
42
|
- lib/src/secrets_handler.rb
|
43
|
+
- lib/src/source_renderer.rb
|
42
44
|
homepage: https://github.com/CyrilCermak/mobile-secrets
|
43
45
|
licenses:
|
44
46
|
- MIT
|
@@ -1,28 +0,0 @@
|
|
1
|
-
//
|
2
|
-
// Autogenerated file by Mobile Secrets
|
3
|
-
//
|
4
|
-
|
5
|
-
import Foundation
|
6
|
-
|
7
|
-
class Secrets {
|
8
|
-
static let standard = Secrets()
|
9
|
-
/* SECRET BYTES */
|
10
|
-
|
11
|
-
private init() {}
|
12
|
-
|
13
|
-
func string(forKey key: String) -> String? {
|
14
|
-
guard let index = bytes.firstIndex(where: { String(data: Data($0), encoding: .utf8) == key }),
|
15
|
-
let value = decrypt(bytes[index + 1]) else { return nil }
|
16
|
-
return String(data: Data(value), encoding: .utf8)
|
17
|
-
}
|
18
|
-
|
19
|
-
private func decrypt(_ input: [UInt8]) -> [UInt8]? {
|
20
|
-
let key = bytes[0]
|
21
|
-
guard !key.isEmpty else { return nil }
|
22
|
-
var output = [UInt8]()
|
23
|
-
for byte in input.enumerated() {
|
24
|
-
output.append(byte.element ^ key[byte.offset % key.count])
|
25
|
-
}
|
26
|
-
return output
|
27
|
-
}
|
28
|
-
}
|