mobile-secrets 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
}
|