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 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
- }