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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1bb1e6c36ce56b0181aab583f65c8b6935ac47b74ae52f7e3cb5a2bb27ad73f
4
- data.tar.gz: f475935731fc3fb87a1f8cac527dabec2553c2a4f297fdc3e626bddb8e27921b
3
+ metadata.gz: 22e621754c652265b906fb99949160cba7f05f169e1a742bed477cc93213d8ef
4
+ data.tar.gz: 6cf400c9265bb3bbc3e8b0f3e969d4139f86643063a00fbfb49f957ecdbd5580
5
5
  SHA512:
6
- metadata.gz: 0f0906f30fab6cc56ba9f0f3ce263d4fa3027b6bda2fcf7654b0494b1f495694e8bf42591ec16cb0db48eed974c9812d89ecd47f185111d5ac8b191c34ee38dc
7
- data.tar.gz: e08c129ba0d265079c9c9a9e7d9207f2e7a1060f76691b22bf344d466a57716493f018c35204077ad8dd48d7bfa7b148d819bf5be81685217b127f5910c01e46
6
+ metadata.gz: 6856b5a3cf2b2885c30837eaaa6b3eb731d852ea0299bb505b2efa585103b537b7cdecf0f244acdff9b2d68266d7dae3f8a65648e4a448f29543e519e45dc9fc
7
+ data.tar.gz: 31602061ae1ba76be8171fdeac0539d29522bf643ab5268628187fac5e898d193d47cdce7025a9189353bf269e4451495874a04c960740c38f8933d2d560ace8
@@ -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
+ }
@@ -1,7 +1,18 @@
1
1
  MobileSecrets:
2
- hashKey: "KokoBelloKoko" # Key that will be used to hashed the secret values.
3
- language: "Swift" # Swift is currently only supported language, Kotlin is coming soon.
4
- secrets: # Key-value dictionary for secrets. The key is then referenced in the code to get the secret.
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
@@ -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
- bytes = process_yaml_config decrypted_config
11
- inject_secrets(bytes, "#{path}/secrets.swift")
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
- bytes << key.bytes << encrypted.bytes
31
+ secrets_bytes << key.bytes << encrypted.bytes
25
32
  end
26
33
 
27
- bytes
28
- end
29
-
30
- def inject_secrets secret_bytes, file
31
- template = IO.read "#{__dir__}/../resources/SecretsTemplate.swift"
32
- secret_bytes = "#{secret_bytes}".gsub "],", "],\n " #Formating the array
33
- bytes_variable = "private let bytes: [[UInt8]] = #{secret_bytes}"
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
- File.open(file, "w") { |f| f.puts swift_secrets }
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.5
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/SecretsTemplate.swift
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
- }