mobile-secrets 0.0.4 → 0.0.9

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: 96bdba0202a6a478beea1f412bbac5e8abd333af7f3f605dfb7ca6e09fb892de
4
- data.tar.gz: 20545830edba12a31d17e141fe4330790fce4110eb401756c497d64c93ea28d5
3
+ metadata.gz: 0f4f08928281f7ec09b0f307fde69ac6a240fc006cdb398741b94b94906952f6
4
+ data.tar.gz: 2b28b5fccdd4b442bf229733eaad00e170baee9e4c5505a925761f947929bda8
5
5
  SHA512:
6
- metadata.gz: 8563eabd178ac86fd1b65da16a1686c275f364e0033b6801536069039a00f66285273629b34f1fb3eda32156d8166155dc43fa0df746880d80d5cc7578b2153f
7
- data.tar.gz: 1c5ed3422664c6c8139053994fc7cf6f7f1c43baf0f3e687e581421478bb6a052334befa76c783e71410758d503c5ec0b78ad710fe2ab3e43f61280ef7817b6d
6
+ metadata.gz: 2f7805f87c8cf0713394b29e4f40da1654661ea736640de9fb59f95b8aff7e2928b5941a67f993af709c3b31cb62f4d1f1fbcbca88bd856303b05ddb9966ed13
7
+ data.tar.gz: 693fabe5ec237be1fcaf50ca841b60df85dccde3d017bf97e059cffe8ad6b216e8656be7ebef97688fb6e25318e93bfd36cbc09b2752fbd69ba42d39be1f72a3
@@ -20,14 +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 \tAdds MobileSecrets to GPG secrets\n"
26
- opt << "--export PATH \t\t\tCreates source file with obfuscated secrets at given PATH\n"
27
- opt << "--usage \t\t\tManual for using MobileSecrets.\n\n"
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"
28
30
  opt << "Examples:\n"
29
31
  opt << "--import \"./MobileSecrets.yml\"\n"
30
- opt << "--export \"./Project/Src\"\n"
32
+ opt << "--export \"./Project/Src\\n"
31
33
  opt << "--init-gpg \".\""
32
34
  opt
33
35
  end
@@ -37,7 +39,7 @@ module MobileSecrets
37
39
  usage << "1) Create gpg first with --init-gpg \".\"\n"
38
40
  usage << "2) Create a template for MobileSecrets with --create-template\n"
39
41
  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"
42
+ usage << "4) Import edited template to encrypted secret.gpg with --import ./MobileSecrets.yml\n"
41
43
  usage << "5) Export secrets from secrets.gpg to source file with --export and PATH to project\n"
42
44
  usage << "6) Add exported source file to the project\n"
43
45
  end
@@ -48,18 +50,31 @@ module MobileSecrets
48
50
  FileUtils.cp("#{__dir__}/../lib/resources/example.yml", "#{Dir.pwd}#{File::SEPARATOR}MobileSecrets.yml")
49
51
  when "--export"
50
52
  return print_options if argv_1 == nil
53
+ encrypted_file_path = argv_2 ||= "secrets.gpg"
51
54
 
52
55
  secrets_handler = MobileSecrets::SecretsHandler.new
53
- secrets_handler.export_secrets argv_1
56
+ secrets_handler.export_secrets argv_1, argv_2
54
57
  when "--init-gpg"
55
58
  return print_options if argv_1 == nil
56
59
 
57
60
  Dotgpg::Cli.new.init(argv_1)
58
61
  when "--import"
59
62
  return print_options if argv_1 == nil
60
-
63
+ gpg_file = argv_2 ||= "secrets.gpg"
61
64
  file = IO.read argv_1
62
- MobileSecrets::SecretsHandler.new.encrypt "./secrets.gpg", file, nil
65
+ MobileSecrets::SecretsHandler.new.encrypt gpg_file, file, nil
66
+ when "--encrypt-file"
67
+ file = argv_1
68
+ password = argv_2
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}")
63
78
  when "--usage"
64
79
  puts usage
65
80
  else
@@ -0,0 +1,140 @@
1
+ //
2
+ // Autogenerated file by Mobile Secrets
3
+ //
4
+
5
+ <% if should_decrypt_files %>import CommonCrypto<% end %>
6
+ import Foundation
7
+
8
+ // swiftlint:disable all
9
+ class Secrets {
10
+ static let standard = Secrets()
11
+ private let bytes: [[UInt8]] = <%= secrets_array.to_s.gsub "],", "],\n "%>
12
+ <% if should_decrypt_files %>
13
+ private let fileNames: [[UInt8]] = <%= file_names_array.to_s.gsub "],", "],\n "%>
14
+ <% end %>
15
+
16
+ private init() {}
17
+
18
+ func string(forKey key: String, password: String? = nil) -> String? {
19
+ let pwdBytes = password == nil ? bytes[0] : password?.map({ c in c.asciiValue ?? 0 })
20
+ guard let index = bytes.firstIndex(where: { String(data: Data($0), encoding: .utf8) == key }),
21
+ let pwd = pwdBytes,
22
+ let value = decrypt(bytes[index + 1], password: pwd) else { return nil }
23
+
24
+ return String(data: Data(value), encoding: .utf8)
25
+ }
26
+
27
+ private func decrypt(_ input: [UInt8], password: [UInt8]) -> [UInt8]? {
28
+ guard !password.isEmpty else { return nil }
29
+ var output = [UInt8]()
30
+ for byte in input.enumerated() {
31
+ output.append(byte.element ^ password[byte.offset % password.count])
32
+ }
33
+ return output
34
+ }
35
+
36
+ <% if should_decrypt_files %>
37
+ func decryptFiles(bundle: Bundle = Bundle.main, password: String? = nil) throws {
38
+ try fileNames.forEach({ (fileNameBytes) in
39
+ guard let name = String(data: Data(fileNameBytes), encoding: .utf8) else {
40
+ fatalError("Wrong name in file names")
41
+ }
42
+
43
+ try decryptFile(name, bundle: bundle, password: password)
44
+ })
45
+ }
46
+
47
+ func decryptFile(_ fileName: String, bundle: Bundle = Bundle.main, password: String? = nil) throws {
48
+ let password = password == nil ? String(data: Data(bytes[0]), encoding: .utf8) : password
49
+
50
+ guard let pwd = password else {
51
+ fatalError("No password for decryption was provided!")
52
+ }
53
+
54
+ guard let filePath = bundle.path(forResource: fileName, ofType: "enc"),
55
+ let fileURL = URL(string: "file://" + filePath),
56
+ let fileData = try? Data(contentsOf: fileURL) else {
57
+ fatalError("File \(fileName) was not found in bundle!")
58
+ }
59
+
60
+ var outputURL = bundle.bundleURL
61
+ outputURL.appendPathComponent(fileName)
62
+
63
+ do {
64
+ let aes = try AES(keyString: pwd)
65
+ let decryptedString = try aes.decrypt(fileData)
66
+ try decryptedString.write(to: outputURL, atomically: true, encoding: .utf8)
67
+ } catch let e {
68
+ throw e
69
+ }
70
+ }
71
+
72
+ struct AES {
73
+ enum Error: Swift.Error {
74
+ case invalidKeySize
75
+ case encryptionFailed
76
+ case decryptionFailed
77
+ case dataToStringFailed
78
+ }
79
+
80
+ private var key: Data
81
+ private var ivSize: Int = kCCBlockSizeAES128
82
+ private let options: CCOptions = CCOptions()
83
+
84
+ init(keyString: String) throws {
85
+ guard keyString.count == kCCKeySizeAES256 else {
86
+ throw Error.invalidKeySize
87
+ }
88
+ self.key = Data(keyString.utf8)
89
+ }
90
+
91
+ func decrypt(_ data: Data) throws -> String {
92
+ let bufferSize: Int = data.count - ivSize
93
+ var buffer = Data(count: bufferSize)
94
+ var numberBytesDecrypted: Int = 0
95
+
96
+ do {
97
+ try key.withUnsafeBytes { keyBytes in
98
+ try data.withUnsafeBytes { dataToDecryptBytes in
99
+ try buffer.withUnsafeMutableBytes { bufferBytes in
100
+
101
+ guard let keyBytesBaseAddress = keyBytes.baseAddress,
102
+ let dataToDecryptBytesBaseAddress = dataToDecryptBytes.baseAddress,
103
+ let bufferBytesBaseAddress = bufferBytes.baseAddress else {
104
+ throw Error.encryptionFailed
105
+ }
106
+
107
+ let cryptStatus: CCCryptorStatus = CCCrypt( // Stateless, one-shot encrypt operation
108
+ CCOperation(kCCDecrypt), // op: CCOperation
109
+ CCAlgorithm(kCCAlgorithmAES), // alg: CCAlgorithm
110
+ options, // options: CCOptions
111
+ keyBytesBaseAddress, // key: the "password"
112
+ key.count, // keyLength: the "password" size
113
+ dataToDecryptBytesBaseAddress, // iv: Initialization Vector
114
+ dataToDecryptBytesBaseAddress + ivSize, // dataIn: Data to decrypt bytes
115
+ bufferSize, // dataInLength: Data to decrypt size
116
+ bufferBytesBaseAddress, // dataOut: decrypted Data buffer
117
+ bufferSize, // dataOutAvailable: decrypted Data buffer size
118
+ &numberBytesDecrypted // dataOutMoved: the number of bytes written
119
+ )
120
+
121
+ guard cryptStatus == CCCryptorStatus(kCCSuccess) else {
122
+ throw Error.decryptionFailed
123
+ }
124
+ }
125
+ }
126
+ }
127
+ } catch {
128
+ throw Error.encryptionFailed
129
+ }
130
+
131
+ let decryptedData: Data = buffer[..<numberBytesDecrypted]
132
+ guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
133
+ throw Error.dataToStringFailed
134
+ }
135
+
136
+ return decryptedString
137
+ }
138
+ }
139
+ <% end %>
140
+ }
@@ -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
+ }
@@ -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,46 @@
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
- def export_secrets path
9
- decrypted_config = decrypt_secrets()
10
- bytes = process_yaml_config decrypted_config
11
- inject_secrets(bytes, "#{path}/secrets.swift")
11
+ def export_secrets path, from_encrypted_file_name
12
+ decrypted_config = decrypt_secrets(from_encrypted_file_name)
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"
17
+ decrypted_config
12
18
  end
13
19
 
14
20
  def process_yaml_config yaml_string
15
21
  config = YAML.load(yaml_string)["MobileSecrets"]
16
22
  hash_key = config["hashKey"]
17
- obfuscator = MobileSecrets::Obfuscator.new hash_key
18
-
19
- bytes = [hash_key.bytes]
20
23
  secrets_dict = config["secrets"]
24
+ files = config["files"]
25
+ should_include_password = config["shouldIncludePassword"]
26
+ secrets_bytes = should_include_password ? [hash_key.bytes] : []
27
+ file_names_bytes = []
28
+ obfuscator = MobileSecrets::Obfuscator.new hash_key
21
29
 
22
30
  secrets_dict.each do |key, value|
23
31
  encrypted = obfuscator.obfuscate(value)
24
- bytes << key.bytes << encrypted.bytes
32
+ secrets_bytes << key.bytes << encrypted.bytes
25
33
  end
26
34
 
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\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
33
- bytes_variable = "private let bytes: [[UInt8]] = #{secret_bytes}"
34
- swift_secrets = template.sub "/* SECRET BYTES */", bytes_variable
35
+ if files
36
+ abort("Password must be 32 characters long for files encryption.") if hash_key.length != 32
37
+ files.each do |f|
38
+ encrypt_file hash_key, f, "#{f}.enc"
39
+ file_names_bytes << f.bytes
40
+ end
41
+ end
35
42
 
36
- File.open(file, "w") { |f| f.puts swift_secrets }
43
+ return file_names_bytes, secrets_bytes
37
44
  end
38
45
 
39
46
  def encrypt output_file_path, string, gpg_path
@@ -43,12 +50,20 @@ module MobileSecrets
43
50
  dotgpg.encrypt output_file_path, string
44
51
  end
45
52
 
53
+ def encrypt_file password, file, output_file_path
54
+ encryptor = FileHandler.new password
55
+ 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
56
+ encrypted_content = encryptor.encrypt file
57
+
58
+ File.open(output_file_path, "wb") { |f| f.write encrypted_content }
59
+ end
60
+
46
61
  private
47
62
 
48
- def decrypt_secrets
49
- gpg = Dotgpg::Dir.new "#{Dir.pwd}/"
63
+ def decrypt_secrets encrypted_file_name
64
+ gpg = Dotgpg::Dir.closest encrypted_file_name
50
65
  output = StringIO.new
51
- gpg.decrypt "#{Dir.pwd}/secrets.gpg", output
66
+ gpg.decrypt "#{Dir.pwd}/#{encrypted_file_name}", output
52
67
  output.string
53
68
  end
54
69
 
@@ -0,0 +1,36 @@
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
+ def render_empty_template output_file_path
25
+ template = File.read("#{__dir__}/../resources/SecretsSwiftEmpty.erb")
26
+
27
+ case @source_type
28
+ when "swift"
29
+ File.open(output_file_path, "w") do |file|
30
+ file.puts template
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ 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
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Cermak
@@ -35,11 +35,13 @@ 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
+ - lib/resources/SecretsSwiftEmpty.erb
39
40
  - lib/resources/example.yml
41
+ - lib/src/file_handler.rb
40
42
  - lib/src/obfuscator.rb
41
43
  - lib/src/secrets_handler.rb
42
- - test/test_hola.rb
44
+ - lib/src/source_renderer.rb
43
45
  homepage: https://github.com/CyrilCermak/mobile-secrets
44
46
  licenses:
45
47
  - MIT
@@ -59,9 +61,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
61
  - !ruby/object:Gem::Version
60
62
  version: '0'
61
63
  requirements: []
62
- rubygems_version: 3.0.6
64
+ rubygems_version: 3.0.8
63
65
  signing_key:
64
66
  specification_version: 4
65
67
  summary: mobile-secrets tool for handling your mobile secrets
66
- test_files:
67
- - test/test_hola.rb
68
+ test_files: []
@@ -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
- }
@@ -1,16 +0,0 @@
1
- require 'test/unit'
2
- require 'hola'
3
-
4
- class HolaTest < Test::Unit::TestCase
5
- def test_english_hello
6
- assert_equal "hello world", Hola.hi("english")
7
- end
8
-
9
- def test_any_hello
10
- assert_equal "hello world", Hola.hi("ruby")
11
- end
12
-
13
- def test_spanish_hello
14
- assert_equal "hola mundo", Hola.hi("spanish")
15
- end
16
- end