mobile-secrets 0.0.4 → 0.0.9

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