secure-keys 1.0.0
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 +7 -0
- data/README.md +193 -0
- data/bin/keys.rb +7 -0
- data/lib/core/environment/ci.rb +27 -0
- data/lib/core/environment/keychain.rb +30 -0
- data/lib/core/globals/globals.rb +64 -0
- data/lib/core/utils/openssl/cipher.rb +45 -0
- data/lib/core/utils/swift/package.rb +21 -0
- data/lib/core/utils/swift/swift.rb +22 -0
- data/lib/core/utils/swift/writer.rb +156 -0
- data/lib/core/utils/swift/xcframework.rb +81 -0
- data/lib/keys.rb +83 -0
- data/lib/version.rb +7 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e1c55d0d0f427de2248176c7b637d099d707c49802d6e3ef2b726e0d3e2383a8
|
4
|
+
data.tar.gz: 6cbc1fcdc0a67dce1101ef372849d11f19e1ce07bd48ac52d9c3c34cf2188fed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a8e5c02474072f96dd7da0b61db861c313d8575e2bca0f42c57576f3081b58b7095fb47e17db01eff9ea5b756fac0be1e7d33351e5e4a143110640850fc6e69
|
7
|
+
data.tar.gz: f10ab748b3c5a74d4ef3b562ae555f5f6e1e93064fe25dc8a23fcfd185ab51baa08e148169a216d5694007ed8ddc0a760037d0b8c81b1233a4c9083a37ab515a
|
data/README.md
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
<div style="display: flex; gap: 10px; padding-bottom: 20px;">
|
2
|
+
<img src="https://img.shields.io/badge/version-1.0.0-cyan" alt="Keys Version 1.0.0">
|
3
|
+
|
4
|
+
<img src="https://img.shields.io/badge/iOS-^13.0-blue" alt="iOS version 13.0">
|
5
|
+
|
6
|
+
<img src="https://img.shields.io/badge/Ruby-^3.3.6-red" alt="Ruby version 3.3.6">
|
7
|
+
|
8
|
+
</div>
|
9
|
+
|
10
|
+
# Secure Key Generator for iOS projects
|
11
|
+
|
12
|
+
Utility to generate a `xcframework` for handling secure keys in iOS projects.
|
13
|
+
|
14
|
+
### Prerequisites
|
15
|
+
|
16
|
+
- Ruby 3.3.6 or higher
|
17
|
+
- iOS 13.0 or higher
|
18
|
+
|
19
|
+
### Installation
|
20
|
+
|
21
|
+
Install gems using bundler
|
22
|
+
|
23
|
+
```bash
|
24
|
+
bundle install
|
25
|
+
```
|
26
|
+
|
27
|
+
If you don't have bundler installed, you can install it using:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
gem install bundler
|
31
|
+
```
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
As first step, you need to determine the keys that you want to use in your iOS project. You can define the keys from Keychain or env variables.
|
36
|
+
|
37
|
+
The source is determined by the current platform **local or CI / cloud** using the `CI` environment variable.
|
38
|
+
|
39
|
+
If the `CI` environment variable is set to `true`, the keys are read from the environment variables. Otherwise, the keys are read from the Keychain.
|
40
|
+
|
41
|
+
You can configure your keys like this:
|
42
|
+
|
43
|
+
### From Keychain
|
44
|
+
|
45
|
+
1. You need to define the `secure-keys` record in the Keychain with the key name and the key value.
|
46
|
+
|
47
|
+
The value for this key should be all the key names separated by a comma.
|
48
|
+
|
49
|
+
```bash
|
50
|
+
security add-generic-password -a "secure-keys" -s "secure-keys" -w "githubToken,apiKey"
|
51
|
+
```
|
52
|
+
|
53
|
+
If you want to use another keychain identifier, you can define an env variable named `SECURE_KEYS_IDENTIFIER` to set the keychain identifier.
|
54
|
+
|
55
|
+
```bash
|
56
|
+
export SECURE_KEYS_IDENTIFIER="your-keychain-identifier"
|
57
|
+
|
58
|
+
security add-generic-password -a "$SECURE_KEYS_IDENTIFIER" -s "$SECURE_KEYS_IDENTIFIER" -w "githubToken,apiKey"
|
59
|
+
```
|
60
|
+
|
61
|
+
2. You can add new keys using the `security` command.
|
62
|
+
|
63
|
+
```bash
|
64
|
+
security add-generic-password -a "secure-keys" -s "apiKey" -w "your-api-key"
|
65
|
+
```
|
66
|
+
|
67
|
+
Using custom keychain identifier:
|
68
|
+
|
69
|
+
```bash
|
70
|
+
security add-generic-password -a "$SECURE_KEYS_IDENTIFIER" -s "apiKey" -w "your-api-key"
|
71
|
+
```
|
72
|
+
|
73
|
+
### Environment variables
|
74
|
+
|
75
|
+
1. You can define the keys in the `.env` file or export the keys as environment variables.
|
76
|
+
|
77
|
+
```bash
|
78
|
+
export SECURE_KEYS_IDENTIFIER="github-token,api_key,firebaseToken"
|
79
|
+
|
80
|
+
export GITHUB_TOKEN="your-github-token"
|
81
|
+
export API_KEY="your-api-key"
|
82
|
+
export FIREBASETOKEN="your-firebase-token"
|
83
|
+
```
|
84
|
+
|
85
|
+
> The key names are formatted in uppercase and replace the `-` with `_`.
|
86
|
+
|
87
|
+
> [!IMPORTANT]
|
88
|
+
> If you want to use another demiliter, you can define an env variable named `SECURE_KEYS_DELIMITER` to set the delimiter.
|
89
|
+
|
90
|
+
```bash
|
91
|
+
export SECURE_KEYS_DELIMITER="|"
|
92
|
+
|
93
|
+
export SECURE_KEYS_IDENTIFIER="github-token|api_key|firebaseToken"
|
94
|
+
```
|
95
|
+
|
96
|
+
### Ruby script
|
97
|
+
|
98
|
+
To generate the `Keys.xcframework` use the `keys.rb` script with:
|
99
|
+
|
100
|
+
```bash
|
101
|
+
bundle exec ruby ./bin/keys.rb
|
102
|
+
```
|
103
|
+
|
104
|
+
### iOS project
|
105
|
+
|
106
|
+
Within the iOS project, you can use the `Keys` target dependency like:
|
107
|
+
|
108
|
+
```swift
|
109
|
+
import Keys
|
110
|
+
|
111
|
+
// Using key directly in the code
|
112
|
+
let apiKey = Keys.apiKey.decryptedValue
|
113
|
+
|
114
|
+
// Using key from `Key` enum
|
115
|
+
let someKey: String = key(for: .someKey)
|
116
|
+
|
117
|
+
// Alternative way to use key from `Key` enum
|
118
|
+
let someKey: String = key(.someKey)
|
119
|
+
|
120
|
+
// Using raw value from `Key` enum
|
121
|
+
let apiKey: Keys = "apiKey".secretKey
|
122
|
+
|
123
|
+
// Using raw value from `Key` enum with decrypted value
|
124
|
+
let apiKey: String = "apiKey".secretKey.decryptedValue
|
125
|
+
|
126
|
+
// Using `key` method to get the key
|
127
|
+
let apiKey: String = .key(for: .apiKey)
|
128
|
+
```
|
129
|
+
|
130
|
+
## How to install the `Keys.xcframework` in the iOS project
|
131
|
+
|
132
|
+
1. From the iOS project, click on the project target, select the `General` tab, and scroll down to the `Frameworks, Libraries, and Embedded Content` section.
|
133
|
+
|
134
|
+

|
135
|
+
|
136
|
+
2. Click on the `Add Other...` button and click on the `Add Files...` option.
|
137
|
+
|
138
|
+

|
139
|
+
|
140
|
+
3. Navigate to the `keys` directory and select the `Keys.xcframework` folder.
|
141
|
+
|
142
|
+

|
143
|
+
|
144
|
+
> Now the `Keys.xcframework` is added to the iOS project.
|
145
|
+
|
146
|
+

|
147
|
+
|
148
|
+
4. Click on the `Build settings` tab and search for the `Search Paths` section.
|
149
|
+
|
150
|
+

|
151
|
+
|
152
|
+
> Add the path to the `Keys.xcframework` in the `Framework Search Paths` section.
|
153
|
+
|
154
|
+
```bash
|
155
|
+
$(inherited)
|
156
|
+
$(SRCROOT)/.keys
|
157
|
+
```
|
158
|
+
|
159
|
+
## How it works
|
160
|
+
|
161
|
+
The process when the script is executed is:
|
162
|
+
|
163
|
+
1. Create a `.keys` directory.
|
164
|
+
2. Create a temporary `Swift Package` in the `.keys` directory.
|
165
|
+
3. Copy the `Keys` source code to the temporary `Swift Package`.
|
166
|
+
|
167
|
+
```swift
|
168
|
+
public enum Keys {
|
169
|
+
|
170
|
+
// MARK: - Cases
|
171
|
+
|
172
|
+
case apiKey
|
173
|
+
case someKey
|
174
|
+
case unknown
|
175
|
+
|
176
|
+
// MARK: - Properties
|
177
|
+
|
178
|
+
/// The decrypted value of the key
|
179
|
+
public var decryptedValue: String {
|
180
|
+
switch self {
|
181
|
+
case .apiKey: [1, 2, 4].decrypt(key: [248, 53, 26], iv: [148, 55, 47], tag: [119, 81])
|
182
|
+
case .someKey: [1, 2, 4].decrypt(key: [248, 53, 26], iv: [148, 55, 47], tag: [119, 81])
|
183
|
+
case .unknown: fatalError("Unknown key \(rawValue)")
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
```
|
188
|
+
4. Generate the `Keys.xcframework` using the temporary `Swift Package`.
|
189
|
+
5. Remove the temporary `Swift Package`.
|
190
|
+
|
191
|
+
## License
|
192
|
+
|
193
|
+
This project is licensed under the MIT [License](LICENSE).
|
data/bin/keys.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Keys
|
4
|
+
module Core
|
5
|
+
module Environment
|
6
|
+
class CI
|
7
|
+
# Fetches the value of the environment variable with the given key.
|
8
|
+
# @param key [String] the key of the environment variable to fetch
|
9
|
+
# @return [String] the value of the environment variable
|
10
|
+
def fetch(key:)
|
11
|
+
ENV[formatted_key(key:)]
|
12
|
+
rescue StandardError
|
13
|
+
puts "❌ Error fetching the key: #{key} from ENV variables"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Formats the key to match the format of the environment variables.
|
19
|
+
# @param key [String] the key to format
|
20
|
+
# @return [String] the formatted key
|
21
|
+
def formatted_key(key:)
|
22
|
+
key.gsub('-', '_').uppercase
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'osx_keychain'
|
4
|
+
|
5
|
+
module Keys
|
6
|
+
module Core
|
7
|
+
module Environment
|
8
|
+
class Keychain
|
9
|
+
private
|
10
|
+
|
11
|
+
attr_accessor :keychain
|
12
|
+
|
13
|
+
public
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
self.keychain = OSXKeychain.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fetches the value of the keychain access item with the given key.
|
20
|
+
# @param key [String] the key of the keychain access item to fetch
|
21
|
+
# @return [String] the value of the keychain access item
|
22
|
+
def fetch(key:)
|
23
|
+
keychain[key, Keys::Globals.key_access_identifier]
|
24
|
+
rescue StandardError
|
25
|
+
puts "❌ Error fetching the key: #{key} from Keychain."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Keys
|
4
|
+
module Globals
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Source: https://github.com/fastlane/fastlane/blob/b626fb18597eee88de0e08beba77070e2fecc299/fastlane_core/lib/fastlane_core/helper.rb#L73
|
8
|
+
# Check if the current build is running on a CI
|
9
|
+
# @return [Bool] true if the current build is running on a CI
|
10
|
+
def ci?
|
11
|
+
return true if circle_ci?
|
12
|
+
|
13
|
+
# Check for Jenkins, Travis CI, ... environment variables
|
14
|
+
%w[JENKINS_HOME JENKINS_URL TRAVIS CI APPCENTER_BUILD_ID TEAMCITY_VERSION GO_PIPELINE_NAME bamboo_buildKey GITLAB_CI XCS TF_BUILD GITHUB_ACTION GITHUB_ACTIONS BITRISE_IO BUDDY CODEBUILD_BUILD_ARN].any? do |current|
|
15
|
+
ENV[current].to_s.eql?('true')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Check if the current build is running on CircleCI
|
20
|
+
# @return [Bool] true if the current build is running on CircleCI
|
21
|
+
def circle_ci?
|
22
|
+
ENV.key?('CIRCLECI')
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the supported iOS platforms
|
26
|
+
# @return [Array] supported iOS platforms
|
27
|
+
def ios_platforms
|
28
|
+
[
|
29
|
+
{
|
30
|
+
name: 'iOS Simulator',
|
31
|
+
path: 'iphonesimulator'
|
32
|
+
},
|
33
|
+
{
|
34
|
+
name: 'iOS',
|
35
|
+
path: 'iphoneos'
|
36
|
+
}
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the identifier to get all the key names
|
41
|
+
# @return [String] key access identifier
|
42
|
+
def key_access_identifier
|
43
|
+
ENV['SECURE_KEYS_IDENTIFIER'] || default_key_access_identifier
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the default key access identifier
|
47
|
+
# @return [String] default key access identifier
|
48
|
+
def default_key_access_identifier
|
49
|
+
'secure-keys'
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the keys delimiter
|
53
|
+
# @return [String] keys delimiter
|
54
|
+
def key_delimiter
|
55
|
+
ENV['SECURE_KEYS_DELIMITER'] || default_key_delimiter
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the default keys delimiter
|
59
|
+
# @return [String] default keys delimiter
|
60
|
+
def default_key_delimiter
|
61
|
+
','
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
module Keys
|
8
|
+
module OpenSSL
|
9
|
+
class Cipher
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_accessor :secure_key, :cipher
|
13
|
+
|
14
|
+
public
|
15
|
+
|
16
|
+
# Initialize the cipher with a random generated key
|
17
|
+
# and the aes-256-gcm algorithm
|
18
|
+
# @param bytes [Integer] the number of bytes to generate the key
|
19
|
+
def initialize(bytes: 32)
|
20
|
+
self.secure_key = SecureRandom.random_bytes(bytes)
|
21
|
+
self.cipher = ::OpenSSL::Cipher.new('aes-256-gcm').encrypt
|
22
|
+
|
23
|
+
# Configure the cipher key using the random generated key
|
24
|
+
cipher.key = secure_key
|
25
|
+
end
|
26
|
+
|
27
|
+
# Encrypt a value using the cipher
|
28
|
+
# @param value [String] the value to encrypt
|
29
|
+
# @return [Hash] the encrypted value, the iv and the tag
|
30
|
+
def encrypt(value:)
|
31
|
+
iv = cipher.random_iv
|
32
|
+
encrypted_value = cipher.update(value) + cipher.final
|
33
|
+
tag = cipher.auth_tag
|
34
|
+
|
35
|
+
{ iv: iv.bytes, value: encrypted_value.bytes, tag: tag.bytes }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Fetch the bytes of the secure key
|
39
|
+
# @return [Array] the bytes of the secure key
|
40
|
+
def secure_key_bytes
|
41
|
+
secure_key.bytes
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative './swift'
|
4
|
+
|
5
|
+
module Keys
|
6
|
+
module Swift
|
7
|
+
class Package
|
8
|
+
# Generate the Swift Package using the configured path
|
9
|
+
def generate
|
10
|
+
command = <<~BASH
|
11
|
+
rm -rf #{SWIFT_PACKAGE_DIRECTORY} &&
|
12
|
+
mkdir -p #{SWIFT_PACKAGE_DIRECTORY} &&
|
13
|
+
cd #{SWIFT_PACKAGE_DIRECTORY} &&
|
14
|
+
swift package init --name #{SWIFT_PACKAGE_NAME} --type library
|
15
|
+
BASH
|
16
|
+
|
17
|
+
system(command)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Keys
|
4
|
+
module Swift
|
5
|
+
# Constants
|
6
|
+
|
7
|
+
# The name of the directory that contains the keys
|
8
|
+
KEYS_DIRECTORY = '.keys'.freeze
|
9
|
+
|
10
|
+
# The name of the directory that contains the generated build
|
11
|
+
BUILD_DIRECTORY = 'Build'.freeze
|
12
|
+
|
13
|
+
# The name of the Swift Package
|
14
|
+
SWIFT_PACKAGE_NAME = 'Keys'.freeze
|
15
|
+
|
16
|
+
# The name of the directory that contains the generated Swift package
|
17
|
+
SWIFT_PACKAGE_DIRECTORY = "#{KEYS_DIRECTORY}/Package".freeze
|
18
|
+
|
19
|
+
# The name of the directory that contains the generated xcframework
|
20
|
+
XCFRAMEWORK_DIRECTORY = "#{SWIFT_PACKAGE_NAME}.xcframework".freeze
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# rubocop:disable Layout/HeredocIndentation
|
3
|
+
|
4
|
+
require_relative './swift'
|
5
|
+
|
6
|
+
module Keys
|
7
|
+
module Swift
|
8
|
+
class Writer
|
9
|
+
private
|
10
|
+
|
11
|
+
attr_accessor :mapped_keys, :secure_key_bytes, :key_file, :key_directory
|
12
|
+
|
13
|
+
public
|
14
|
+
|
15
|
+
# Initialize the writer with the mapped keys and the secure key bytes
|
16
|
+
# @param mapped_keys [Array<Hash>] The mapped keys
|
17
|
+
# @param secure_key_bytes [Array<UInt8>] The secure key bytes
|
18
|
+
def initialize(mapped_keys:, secure_key_bytes:)
|
19
|
+
self.mapped_keys = mapped_keys
|
20
|
+
self.secure_key_bytes = secure_key_bytes
|
21
|
+
self.key_file = "#{SWIFT_PACKAGE_NAME}.swift"
|
22
|
+
self.key_directory = "#{SWIFT_PACKAGE_DIRECTORY}/Sources/#{SWIFT_PACKAGE_NAME}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Write the keys to the file
|
26
|
+
def write
|
27
|
+
# Write the file
|
28
|
+
File.open("#{key_directory}/#{key_file}", 'w') do |file|
|
29
|
+
file.write(key_swift_file_template(content: formatted_keys))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Generate the formatted keys content using Swift code format
|
36
|
+
# @return [String] The formatted keys content
|
37
|
+
def formatted_keys
|
38
|
+
<<~SWIFT
|
39
|
+
#{mapped_keys.map { |key| case_key_declaration_template(name: key[:name]) }.join("\t")}
|
40
|
+
case unknown
|
41
|
+
|
42
|
+
// MARK: - Properties
|
43
|
+
|
44
|
+
/// The decrypted value of the key
|
45
|
+
public var decryptedValue: String {
|
46
|
+
switch self {
|
47
|
+
#{mapped_keys.map { |key| switch_case_key_declaration_template(name: key[:name], value: key[:value], iv: key[:iv], tag: key[:tag]) }.join("\t\t\t")}
|
48
|
+
case .unknown: fatalError("Unknown key \\(rawValue)")
|
49
|
+
}
|
50
|
+
}
|
51
|
+
SWIFT
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generate the Swift file template
|
55
|
+
# @param content [String] The content of the file
|
56
|
+
# @return [String] The Swift file template
|
57
|
+
def key_swift_file_template(content:)
|
58
|
+
<<~SWIFT
|
59
|
+
// swiftlint:disable all
|
60
|
+
|
61
|
+
import Foundation
|
62
|
+
import CryptoKit
|
63
|
+
|
64
|
+
// MARK: - Global methods
|
65
|
+
|
66
|
+
/// Fetch the decrypted value of the key
|
67
|
+
///
|
68
|
+
/// - Parameter:
|
69
|
+
/// - key: The key to fetch the decrypted value for
|
70
|
+
///
|
71
|
+
/// - Returns: The decrypted value of the key
|
72
|
+
@available(iOS 13.0, *)
|
73
|
+
public func key(for key: Keys) -> String { key.decryptedValue }
|
74
|
+
|
75
|
+
/// Fetch the decrypted value of the key
|
76
|
+
///
|
77
|
+
/// - Parameter:
|
78
|
+
/// - key: The key to fetch the decrypted value for
|
79
|
+
///
|
80
|
+
/// - Returns: The decrypted value of the key
|
81
|
+
@available(iOS 13.0, *)
|
82
|
+
public func key(_ key: Keys) -> String { key.decryptedValue }
|
83
|
+
|
84
|
+
// MARK: - Keys enum
|
85
|
+
|
86
|
+
/// Keys is a class that contains all the keys that are used in the application.
|
87
|
+
@available(iOS 13.0, *)
|
88
|
+
public enum Keys: String {
|
89
|
+
|
90
|
+
// MARK: - Cases
|
91
|
+
|
92
|
+
#{content}
|
93
|
+
}
|
94
|
+
|
95
|
+
// MARK: - Decrypt keys from array extension
|
96
|
+
|
97
|
+
@available(iOS 13.0, *)
|
98
|
+
extension Array where Element == UInt8 {
|
99
|
+
|
100
|
+
// MARK: - Methods
|
101
|
+
|
102
|
+
func decrypt(key: [UInt8], iv: [UInt8], tag: [UInt8]) -> String {
|
103
|
+
guard let sealedBox = try? AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: Data(iv)),
|
104
|
+
ciphertext: Data(self),
|
105
|
+
tag: Data(tag)),
|
106
|
+
let decryptedData = try? AES.GCM.open(sealedBox, using: SymmetricKey(data: Data(key))),
|
107
|
+
let decryptedKey = String(data: decryptedData, encoding: .utf8) else {
|
108
|
+
fatalError("Failed to decrypt the key")
|
109
|
+
}
|
110
|
+
return decryptedKey
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
// MARK: - String extension for keys
|
115
|
+
|
116
|
+
@available(iOS 13.0, *)
|
117
|
+
extension String {
|
118
|
+
|
119
|
+
// MARK: - Methods
|
120
|
+
|
121
|
+
/// Fetch the key from the keys enum
|
122
|
+
public var secretKey: Keys { Keys(rawValue: self) ?? .unknown }
|
123
|
+
|
124
|
+
/// Fetch the decrypted value of the key
|
125
|
+
///
|
126
|
+
/// - Parameters:
|
127
|
+
/// - key: The key to fetch the decrypted value for
|
128
|
+
///
|
129
|
+
/// - Returns: The decrypted value of the key
|
130
|
+
public static func key(for key: Keys) -> String { key.decryptedValue }
|
131
|
+
}
|
132
|
+
|
133
|
+
// swiftlint:enable all
|
134
|
+
SWIFT
|
135
|
+
end
|
136
|
+
|
137
|
+
# Generate the case key declaration template
|
138
|
+
# @param name [String] The name of the key
|
139
|
+
def case_key_declaration_template(name:)
|
140
|
+
<<~SWIFT
|
141
|
+
case #{name}
|
142
|
+
SWIFT
|
143
|
+
end
|
144
|
+
|
145
|
+
# Generate the switch case key declaration template
|
146
|
+
# @param name [String] The name of the key
|
147
|
+
def switch_case_key_declaration_template(name:, value:, iv:, tag:) # rubocop:disable Naming/MethodParameterName
|
148
|
+
<<~SWIFT
|
149
|
+
case .#{name}: #{value}.decrypt(key: #{secure_key_bytes}, iv: #{iv}, tag: #{tag})
|
150
|
+
SWIFT
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# rubocop:enable Layout/HeredocIndentation
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative './swift'
|
4
|
+
require_relative '../../globals/globals'
|
5
|
+
|
6
|
+
module Keys
|
7
|
+
module Swift
|
8
|
+
class XCFramework
|
9
|
+
# Generate the XCFramework from the Swift package
|
10
|
+
def generate
|
11
|
+
# TODO: Add support for multiple platforms
|
12
|
+
# Currently this is failling with the following error:
|
13
|
+
# "library with the identifier 'ios-arm64' already exists."
|
14
|
+
%w[Release].each do |configuration|
|
15
|
+
Keys::Globals.ios_platforms.each do |platform|
|
16
|
+
generate_key_modules(configuration:, platform:)
|
17
|
+
generate_key_libraries(configuration:, platform: platform[:path])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
generate_key_xcframework
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Generate the Swift package modules
|
26
|
+
# @param configuration [String] The configuration to build
|
27
|
+
# @param platform [Hash] The platform to build
|
28
|
+
def generate_key_modules(configuration:, platform:)
|
29
|
+
command = <<~BASH
|
30
|
+
cd #{SWIFT_PACKAGE_DIRECTORY} &&
|
31
|
+
xcodebuild -scheme #{SWIFT_PACKAGE_NAME} \
|
32
|
+
-sdk #{platform[:path]} \
|
33
|
+
-destination generic/platform="#{platform[:name]}" \
|
34
|
+
-configuration #{configuration} \
|
35
|
+
ARCHS="arm64" BUILD_DIR="../#{BUILD_DIRECTORY}"
|
36
|
+
BASH
|
37
|
+
|
38
|
+
system(command)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Generate the Swift package libraries
|
42
|
+
# @param configuration [String] The configuration to build
|
43
|
+
# @param platform [String] The platform to build
|
44
|
+
def generate_key_libraries(configuration:, platform:)
|
45
|
+
command = <<~BASH
|
46
|
+
cd #{KEYS_DIRECTORY} &&
|
47
|
+
ar -crs #{BUILD_DIRECTORY}/#{configuration}-#{platform}/libKeys.a \
|
48
|
+
#{BUILD_DIRECTORY}/#{configuration}-#{platform}/Keys.o
|
49
|
+
BASH
|
50
|
+
|
51
|
+
system(command)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generate the XCFramework from the Swift package libraries
|
55
|
+
def generate_key_xcframework
|
56
|
+
command = <<~BASH
|
57
|
+
cd #{KEYS_DIRECTORY} &&
|
58
|
+
xcodebuild -create-xcframework \
|
59
|
+
#{xcframework_library_command} \
|
60
|
+
-allow-internal-distribution \
|
61
|
+
-output #{XCFRAMEWORK_DIRECTORY}
|
62
|
+
BASH
|
63
|
+
|
64
|
+
system(command)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Generate the XCFramework library command
|
68
|
+
# @return [String] The XCFramework library command
|
69
|
+
def xcframework_library_command
|
70
|
+
# TODO: Add support for multiple platforms
|
71
|
+
# Currently this is failling with the following error:
|
72
|
+
# "library with the identifier 'ios-arm64' already exists."
|
73
|
+
%w[Release].map do |configuration|
|
74
|
+
Keys::Globals.ios_platforms.map do |platform|
|
75
|
+
"-library #{BUILD_DIRECTORY}/#{configuration}-#{platform[:path]}/libKeys.a"
|
76
|
+
end.join(' ')
|
77
|
+
end.join(' ')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/keys.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative './core/globals/globals'
|
4
|
+
require_relative './core/environment/ci'
|
5
|
+
require_relative './core/environment/keychain'
|
6
|
+
require_relative './core/utils/swift/writer'
|
7
|
+
require_relative './core/utils/swift/package'
|
8
|
+
require_relative './core/utils/swift/swift'
|
9
|
+
require_relative './core/utils/swift/xcframework'
|
10
|
+
require_relative './core/utils/openssl/cipher'
|
11
|
+
|
12
|
+
module Keys
|
13
|
+
class Generator
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_accessor :cipher, :secrets_source, :secret_keys, :mapped_keys
|
17
|
+
|
18
|
+
public
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
# If the secure keys identifier is not set, set it to 'secure-keys'
|
22
|
+
ENV['SECURE_KEYS_IDENTIFIER'] = 'secure-keys' unless ENV.key?('SECURE_KEYS_IDENTIFIER')
|
23
|
+
|
24
|
+
puts "🔔 You're using a custom delimiter '#{Keys::Globals.key_delimiter}'" unless Keys::Globals.key_delimiter.eql?(Keys::Globals.default_key_delimiter)
|
25
|
+
puts "🔔 You're using a custom key access identifier '#{Keys::Globals.key_access_identifier}'" unless Keys::Globals.key_access_identifier.eql?(Keys::Globals.default_key_access_identifier)
|
26
|
+
|
27
|
+
# Configure cipher
|
28
|
+
self.cipher = Keys::OpenSSL::Cipher.new
|
29
|
+
|
30
|
+
# Configure the secret source based on the environment
|
31
|
+
if Keys::Globals.ci?
|
32
|
+
self.secrets_source = Keys::Core::Environment::CI.new
|
33
|
+
else
|
34
|
+
self.secrets_source = Keys::Core::Environment::Keychain.new
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define the keys that we want to map
|
38
|
+
self.secret_keys = secrets_source.fetch(key: Keys::Globals.key_access_identifier)
|
39
|
+
.to_s
|
40
|
+
.split(Keys::Globals.key_delimiter)
|
41
|
+
.map(&:strip)
|
42
|
+
|
43
|
+
# Add the keys that we want to map
|
44
|
+
self.mapped_keys = secret_keys.map do |key|
|
45
|
+
encrypted_data = cipher.encrypt(value: secrets_source.fetch(key:))
|
46
|
+
# Convert the first key chart to downcase
|
47
|
+
key[0] = key[0].downcase
|
48
|
+
{ name: key, **encrypted_data }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup
|
53
|
+
pre_actions
|
54
|
+
|
55
|
+
package = Keys::Swift::Package.new
|
56
|
+
package.generate
|
57
|
+
|
58
|
+
writer = Keys::Swift::Writer.new(mapped_keys: mapped_keys,
|
59
|
+
secure_key_bytes: cipher.secure_key_bytes)
|
60
|
+
writer.write
|
61
|
+
|
62
|
+
xcframework = Keys::Swift::XCFramework.new
|
63
|
+
xcframework.generate
|
64
|
+
|
65
|
+
post_actions
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def pre_actions
|
71
|
+
# Remove the keys directory
|
72
|
+
system("rm -rf #{Keys::Swift::KEYS_DIRECTORY}")
|
73
|
+
end
|
74
|
+
|
75
|
+
def post_actions
|
76
|
+
# Remove the keys directory
|
77
|
+
system("rm -rf #{Keys::Swift::SWIFT_PACKAGE_DIRECTORY}")
|
78
|
+
|
79
|
+
# Remove the build directory
|
80
|
+
system("rm -rf #{Keys::Swift::KEYS_DIRECTORY}/#{Keys::Swift::BUILD_DIRECTORY}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: secure-keys
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Derian Córdoba
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: base64
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: digest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.10.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.10.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: osx_keychain
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.0.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.71.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.71.2
|
83
|
+
description: Keys is a simple tool to manage your secret keys in iOS your project
|
84
|
+
email:
|
85
|
+
- derianricardo451@gmail.com
|
86
|
+
executables:
|
87
|
+
- keys.rb
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- "./lib/core/environment/ci.rb"
|
92
|
+
- "./lib/core/environment/keychain.rb"
|
93
|
+
- "./lib/core/globals/globals.rb"
|
94
|
+
- "./lib/core/utils/openssl/cipher.rb"
|
95
|
+
- "./lib/core/utils/swift/package.rb"
|
96
|
+
- "./lib/core/utils/swift/swift.rb"
|
97
|
+
- "./lib/core/utils/swift/writer.rb"
|
98
|
+
- "./lib/core/utils/swift/xcframework.rb"
|
99
|
+
- "./lib/keys.rb"
|
100
|
+
- "./lib/version.rb"
|
101
|
+
- README.md
|
102
|
+
- bin/keys.rb
|
103
|
+
homepage: https://github.com/DerianCordobaPerez/secure-keys-generator
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '3.3'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubygems_version: 3.5.22
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Keys is a simple tool for managing your secret keys
|
126
|
+
test_files: []
|