diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7b68f13
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches-ignore:
+ - 'bugfix/**'
+ - 'feature/**'
+ - 'topic/**'
+ workflow_dispatch:
+
+jobs:
+ tests:
+ name: Tests
+ runs-on: macos-12
+ env:
+ DEVELOPER_DIR: "/Applications/Xcode_13.4.1.app/Contents/Developer"
+
+ steps:
+ - name: Runner Overview
+ run: system_profiler SPHardwareDataType SPSoftwareDataType SPDeveloperToolsDataType
+
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Upgrade Bash
+ run: make bash
+
+ - name: Run Tests
+ run: make tests
+
+ - name: Upload Code Coverage Report
+ uses: codecov/codecov-action@v3
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ directory: ./.coverage/
+ fail_ci_if_error: true
+ verbose: true
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..172d128
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+### macOS ###
+
+.DS_Store
+
+### SwiftPM ###
+
+/.build
+/Packages
+/.swiftpm
+
+### Xcode ###
+
+/*.xcodeproj
+xcuserdata/
+
+### Other ###
+
+# Code coverage reports
+/.coverage
+
+# Release artifacts
+/.release
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..dfd2098
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,132 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+conduct@securevale.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ec77fde
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,211 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+## Runtime Library Exception to the Apache 2.0 License: ##
+
+
+ As an exception, if you use this Software to compile your source code and
+ portions of this Software are embedded into the binary product as a result,
+ you may redistribute such product without providing attribution as would
+ otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a7f3027
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+.DEFAULT_GOAL := help
+
+.PHONY: help bash release tests
+
+help:
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+bash: ## Upgrade Bash to the latest version
+ @./Scripts/upgrade_bash.sh
+
+release: ## Generate release artifacts and tag the release with given version
+ @test -z "$$(git status --porcelain)" || { echo "Aborting due to uncommitted changes" >&2; exit 1; }
+ @./Scripts/generate_release_artifacts.sh $(version)
+ @git tag $(version)
+ @git push origin $(version)
+
+tests: ## Run package tests
+ @./Scripts/run_tests.sh
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 0000000..5dd9caf
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,59 @@
+{
+ "pins" : [
+ {
+ "identity" : "swift-argument-parser",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-argument-parser.git",
+ "state" : {
+ "revision" : "df9ee6676cd5b3bf5b330ec7568a5644f547201b",
+ "version" : "1.1.3"
+ }
+ },
+ {
+ "identity" : "swift-case-paths",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-case-paths",
+ "state" : {
+ "revision" : "b4a872984463070c71e2e97e5c02c73a07d0fe36",
+ "version" : "0.9.0"
+ }
+ },
+ {
+ "identity" : "swift-parsing",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/swift-parsing.git",
+ "state" : {
+ "revision" : "bc92e84968990b41640214b636667f35b6e5d44c",
+ "version" : "0.10.0"
+ }
+ },
+ {
+ "identity" : "swift-syntax",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-syntax.git",
+ "state" : {
+ "revision" : "0b6c22b97f8e9320bca62e82cdbee601cf37ad3f",
+ "version" : "0.50600.1"
+ }
+ },
+ {
+ "identity" : "xctest-dynamic-overlay",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
+ "state" : {
+ "revision" : "8816142e27f1127d87c61cee4a0a5db93e9df7c4",
+ "version" : "0.3.1"
+ }
+ },
+ {
+ "identity" : "yams",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/jpsim/Yams.git",
+ "state" : {
+ "revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6",
+ "version" : "5.0.1"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..fe1e044
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,67 @@
+// swift-tools-version: 5.6
+
+import PackageDescription
+
+var package = Package(
+ name: "swift-confidential",
+ platforms: [
+ .iOS(.v13),
+ .macOS(.v10_15),
+ .macCatalyst(.v15),
+ .watchOS(.v8),
+ .tvOS(.v15)
+ ],
+ products: [
+ .library(
+ name: "ConfidentialKit",
+ targets: ["ConfidentialKit"]
+ )
+ ],
+ targets: [
+ // Client Library
+ .target(name: "ConfidentialKit"),
+
+ // Tests
+ .testTarget(
+ name: "ConfidentialKitTests",
+ dependencies: ["ConfidentialKit"]
+ )
+ ],
+ swiftLanguageVersions: [.v5]
+)
+
+#if os(macOS)
+package.dependencies.append(contentsOf: [
+ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
+ .package(url: "https://github.com/apple/swift-syntax.git", exact: "0.50600.1"),
+ .package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.9.0"),
+ .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0")
+])
+package.targets.append(contentsOf: [
+ // Core Module
+ .target(
+ name: "ConfidentialCore",
+ dependencies: [
+ "ConfidentialKit",
+ .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
+ .product(name: "Parsing", package: "swift-parsing")
+ ]
+ ),
+
+ // CLI Tool
+ .executableTarget(
+ name: "confidential",
+ dependencies: [
+ "ConfidentialCore",
+ "Yams",
+ .product(name: "ArgumentParser", package: "swift-argument-parser")
+ ]
+ ),
+
+ // Tests
+ .testTarget(
+ name: "ConfidentialCoreTests",
+ dependencies: ["ConfidentialCore"]
+ )
+])
+#endif
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bfdbe7d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,353 @@
+# Swift Confidential
+
+[](https://www.swift.org/download)
+[]()
+[](https://github.com/apple/swift-package-manager)
+
+A highly configurable and performant tool for obfuscating Swift literals embedded in the application code that you should protect from static code analysis, making the app more resistant to reverse engineering.
+
+Simply integrate the tool with your Swift package, configure your own obfuscation algorithm along with the list of secret literals, and build the project ๐
+
+> **NOTE:** Swift Confidential is still in development and even though a lot of thought was put into API design, apart from new features, some breaking changes might still be introduced. See [Versioning](#versioning) section for more information.
+
+## Motivation
+
+Pretty much every single app has at least few literals embedded in code, those include: URLs, various client identifiers (e.g. API keys or API tokens), pinning data (e.g. PEM certificates or SPKI digests), Keychain item identifiers, RASP-related literals (e.g. list of suspicious dylibs or list of suspicious file paths for jailbreak detection), and many other context-specific literals. While the listed examples of code literals might seem innocent, not obfuscating them can be considered as giving a handshake to the potential threat actor. This is especially true in security-critical apps, such as mobile banking apps or 2FA authentication apps. As a responsible software engineer, you should be aware that extracting source code literals from the app package is generally easy enough that even less expirienced malicious users can accomplish this with little effort.
+
+
+
+ A sneak peek at the __TEXT.__cstring section in a sample Mach-O file reveals a lot of interesting information about the app.
+
+
+This tool aims to provide an elegant and maintainable solution to the above problem by introducing the composable obfuscation techniques that can be freely combined to form an algorithm for obfuscating selected Swift literals.
+
+> **NOTE:** While Swift Confidential certainly makes the static analysis of the code more challenging, **it is by no means the only code hardening technique that you should employ to protect your app against reverse engineering and tampering**. To achieve a decent level of security, we highly encourage you to supplement this tool's security measures with **runtime application self-protection (RASP) checks**, as well as **Swift code obfuscation**. With that said, no security measure can ever guarantee absolute security. Any motivated and skilled enough attacker will eventually bypass all security protections. For this reason, **always keep your threat models up to date**.
+
+## Getting started
+
+Begin by creating a `confidential.yml` YAML configuration file in the root directory of your target's sources. At minimum, the configuration must contain obfuscation algorithm and one or more secret definitions.
+
+For example, a configuration file for the hypothetical `RASP` module could look like this:
+
+```yaml
+algorithm:
+ - encrypt using aes-192-gcm
+ - shuffle
+defaultNamespace: extend RASP.Literals
+secrets:
+ - name: suspiciousDynamicLibraries
+ value:
+ - Substrate
+ - Substitute
+ - frida
+ # ... other suspicious dylibs
+ - name: suspiciousFilePaths
+ value:
+ - /.installed_unc0ver
+ - /usr/sbin/frida-server
+ - /private/var/lib/cydia
+ # ... other suspicious file paths
+```
+
+> **WARNING:** The algorithm from the above configuration serves as example only, **do not use this particular algorithm in your production code**. Instead, compose your own algorithm from the [obfuscation techniques](#obfuscation-techniques) described below and **don't share your algorithm with anyone**. Moreover, following the [secure SDLC](https://owasp.org/www-project-integration-standards/writeups/owasp_in_sdlc/) best practices, consider not to commit the production algorithm in your repository, but instead configure your CI/CD pipeline to run a custom script (ideally just before the build step), which will modify the configuration file by replacing the algorithm value with the one retrieved from the secrets vault.
+
+Having created the configuration file, you can use [Confidential Swift Package plugin](https://github.com/securevale/swift-confidential-plugin) (see [this section](#integrating-with-your-swiftpm-project) for details) to generate Swift code with obfuscated secret literals.
+
+Under the hood, the Confidential plugin invokes the `confidential` CLI tool by issuing the following command:
+
+```sh
+confidential obfuscate --configuration "path/to/confidential.yml" --output "path/to/Confidential.generated.swift"
+```
+
+Upon successful command execution, the generated `Confidential.generated.swift` file will contain code similar to the following:
+
+```swift
+import ConfidentialKit
+import Foundation
+
+extension RASP.Literals {
+
+ @ConfidentialKit.Obfuscated>(deobfuscateData)
+ static var suspiciousDynamicLibraries: ConfidentialKit.Obfuscation.Secret = .init(data: [0x4a, 0x84, 0x89, 0x73, 0x7c, 0x81, 0x18, 0x86, 0x16, 0x5e, 0x1c, 0x41, 0xdc, 0x2e, 0xe1, 0xe4, 0x92, 0x98, 0xdc, 0xde, 0x98, 0xa5, 0xa7, 0x31, 0x6a, 0x5f, 0x4c, 0x7e, 0x5d, 0x61, 0xfd, 0x9d, 0xc1, 0xd2, 0xd4, 0xb8, 0xaf, 0xba, 0xa3, 0x46, 0xda, 0x61, 0xcb, 0xf1, 0xb7, 0xbd, 0xf9, 0xc7, 0x3a, 0x6d, 0xe8, 0x62, 0x09, 0x29, 0x6e, 0x13, 0x5b, 0x50, 0xfa, 0xde, 0x80, 0x82, 0x80, 0x7e, 0xe2, 0x3c, 0xf0, 0xf1, 0x03, 0x12, 0xf8, 0x50, 0x95, 0x03, 0xc8, 0x4e, 0xc1, 0xb3, 0xa9, 0x2c, 0xed, 0x0b, 0x1b, 0x71, 0xe8, 0xfd, 0xa2, 0x69, 0xca, 0xac, 0x4f, 0x35, 0xc6, 0x4f, 0x01, 0x36, 0x5a, 0x5d, 0x58, 0x3b, 0x37, 0x0b, 0x0c, 0x4e, 0x24, 0x1f, 0x38, 0x25, 0x33, 0x3d, 0x4c, 0x27, 0x1c, 0x20, 0x15, 0x01, 0x07, 0x26, 0x0a, 0x51, 0x1a, 0x3c, 0x11, 0x18, 0x21, 0x12, 0x1e, 0x29, 0x3f, 0x5f, 0x0e, 0x19, 0x09, 0x57, 0x31, 0x04, 0x32, 0x4f, 0x2f, 0x02, 0x35, 0x06, 0x23, 0x03, 0x08, 0x1d, 0x2c, 0x39, 0x2d, 0x10, 0x5e, 0x48, 0x05, 0x28, 0x0d, 0x52, 0x5c, 0x4d, 0x30, 0x17, 0x2e, 0x5b, 0x34, 0x1b, 0x56, 0x49, 0x22, 0x00, 0x53, 0x55, 0x16, 0x2a, 0x50, 0x13, 0x54, 0x2b, 0x59, 0x3a, 0x3e, 0x0f, 0x14])
+
+ @ConfidentialKit.Obfuscated>(deobfuscateData)
+ static var suspiciousFilePaths: ConfidentialKit.Obfuscation.Secret = .init(data: [0x99, 0x84, 0x89, 0x73, 0x7c, 0x81, 0x18, 0x86, 0xe0, 0x5b, 0x88, 0x65, 0xa5, 0x1f, 0x53, 0x5e, 0x3c, 0xa5, 0x58, 0x0b, 0x80, 0x06, 0xad, 0x6d, 0x5e, 0xae, 0x2d, 0x52, 0xea, 0xf1, 0xde, 0xac, 0x9d, 0x36, 0x12, 0x56, 0xf9, 0xce, 0xe4, 0x95, 0x84, 0x7e, 0x47, 0x44, 0xde, 0xd3, 0x76, 0x68, 0x67, 0x90, 0xfb, 0x30, 0x3c, 0xaf, 0x33, 0xce, 0x8e, 0x79, 0xa5, 0xdb, 0x7a, 0x97, 0x9d, 0x60, 0x58, 0x0f, 0x59, 0x5f, 0x2c, 0xe0, 0x3a, 0x4d, 0xf6, 0x38, 0x75, 0x1c, 0x2e, 0x5c, 0x94, 0x3f, 0x7c, 0x7a, 0x61, 0xca, 0xbe, 0xbf, 0x52, 0x05, 0x0d, 0xc7, 0xe3, 0xe2, 0x8e, 0x34, 0x02, 0x5f, 0x6b, 0x56, 0xb0, 0x67, 0xac, 0xf3, 0xf8, 0x3d, 0xf5, 0x26, 0xa0, 0x3b, 0x91, 0xf4, 0x88, 0x38, 0xc4, 0x5b, 0xaa, 0x7a, 0x0c, 0x2e, 0x98, 0xeb, 0xd2, 0xbd, 0x7e, 0x63, 0x53, 0xf7, 0x37, 0xb4, 0xc0, 0xb8, 0x8f, 0xb6, 0xe1, 0xd4, 0x3c, 0x89, 0x7c, 0x0f, 0xa8, 0x2a, 0xea, 0x01, 0x0c, 0x74, 0x7b, 0x65, 0x4c, 0x78, 0x14, 0x0f, 0x09, 0x60, 0x02, 0x2f, 0x1f, 0x4a, 0x2c, 0x1a, 0x52, 0x1e, 0x37, 0x98, 0x71, 0x17, 0x30, 0x34, 0x0a, 0x3c, 0x5f, 0x6a, 0x58, 0x42, 0x13, 0x39, 0x44, 0x4d, 0x79, 0x41, 0x59, 0x4b, 0x7d, 0x64, 0x20, 0x33, 0x21, 0x50, 0x5d, 0x22, 0x45, 0x28, 0x54, 0x4f, 0x66, 0x6d, 0x77, 0x03, 0x11, 0x25, 0x26, 0x08, 0x6c, 0x9d, 0x2b, 0x12, 0x6e, 0x68, 0x5e, 0x69, 0x04, 0x9e, 0x06, 0x0d, 0x3a, 0x3e, 0x63, 0x62, 0x2a, 0x19, 0x55, 0x73, 0x01, 0x24, 0x7a, 0x1c, 0x61, 0x32, 0x23, 0x2e, 0x7c, 0x35, 0x46, 0x1b, 0x5b, 0x76, 0x36, 0x4e, 0x7f, 0x16, 0x3f, 0x51, 0x7e, 0x0b, 0x27, 0x9c, 0x10, 0x57, 0x31, 0x18, 0x3b, 0x2d, 0x1d, 0x47, 0x67, 0x0e, 0x6b, 0x72, 0x6f, 0x56, 0x53, 0x40, 0x9f, 0x5c, 0x05, 0x3d, 0x49, 0x00, 0x5a, 0x15, 0x43, 0x38, 0x29, 0x75, 0x70, 0x48, 0x07])
+
+ @inline(__always)
+ private static func deobfuscateData(_ data: Foundation.Data) throws -> Foundation.Data {
+ try ConfidentialKit.Obfuscation.Encryption.DataCrypter(algorithm: .aes192GCM)
+ .deobfuscate(
+ try ConfidentialKit.Obfuscation.Randomization.DataShuffler(nonce: 9662615372037719068)
+ .deobfuscate(data)
+ )
+ }
+}
+```
+
+You can then, for example, iterate over a deobfuscated array of suspicious dynamic libraries in your own code using the projected value of the generated `suspiciousDynamicLibraries` property:
+
+```swift
+let suspiciousLibraries = RASP.Literals.$suspiciousDynamicLibraries
+ .map { $0.lowercased() }
+let checkPassed = loadedLibraries
+ .allSatisfy { !suspiciousLibraries.contains(where: $0.lowercased().contains) }
+```
+
+### Integrating with your SwiftPM project
+
+To use Swift Confidential with your SwiftPM package, add the `ConfidentialKit` library along with `Confidential` plugin to the package's dependencies and then to your target's dependencies and plugins respectively:
+
+```swift
+// swift-tools-version: 5.6
+
+import PackageDescription
+
+let package = Package(
+ name: "MyLibrary",
+ products: [
+ .library(name: "MyLibrary", targets: ["MyLibrary"])
+ ],
+ dependencies: [
+ // other dependencies
+ .package(url: "https://github.com/securevale/swift-confidential.git", from: "0.1.0"),
+ .package(url: "https://github.com/securevale/swift-confidential-plugin.git", from: "0.1.0")
+ ],
+ targets: [
+ .target(
+ name: "MyLibrary",
+ dependencies: [
+ // other dependencies
+ .product(name: "ConfidentialKit", package: "swift-confidential")
+ ],
+ exclude: ["confidential.yml"],
+ plugins: [
+ // other plugins
+ .plugin(name: "Confidential", package: "swift-confidential-plugin")
+ ]
+ )
+ ]
+)
+```
+
+Please make sure to add a path to the `confidential.yml` configuration file to target's `exclude` list to explicitly exclude this file from the target's resources.
+
+Now simply build the `MyLibrary` target and the plugin will automatically generate a Swift source file with obfuscated secret literals. In addition, the plugin will regenerate the obfuscated secret literals every time it detects a change to `confidential.yml` configuration file or when you clean build your project.
+
+> **NOTE:** Swift 5.6 is required in order to run the plugin.
+> Also, please make sure to use the same version requirements for both `swift-confidential` and `swift-confidential-plugin` packages.
+
+## Configuration
+
+Swift Confidential supports a number of configuration options, all of which are stored in a single YAML configuration file.
+
+### YAML configuration keys
+
+The table below lists the keys to include in the configuration file along with the type of information to include in each. Any other keys in the configuration file are ignored by the CLI tool.
+
+| Key | Value type | Description |
+|------------------|--------------------------|-----------------------------------------------------------------------------------|
+| algorithm | List of strings | The list of obfuscation techniques representing individual steps that are composed together to form the obfuscation algorithm. See [Obfuscation techniques](#obfuscation-techniques) section for usage details. **Required.** |
+| defaultNamespace | String | The default namespace in which to enclose all the generated secret literals without explicitly assigned namespace. The default value is `extend Obfuscation.Secret from ConfidentialKit`. See [Namespaces](#namespaces) section for usage details. |
+| secrets | List of objects | The list of objects defining the secret literals to be obfuscated. See [Secrets](#secrets) section for usage details. **Required.** |
+
+
+Example configuration
+
+```yaml
+algorithm:
+ - encrypt using aes-192-gcm
+ - shuffle
+defaultNamespace: create Secrets
+secrets:
+ - name: apiToken
+ value: 214C1E2E-A87E-4460-8205-4562FDF54D1C
+ - name: trustedSPKIDigests
+ value:
+ - 7a6820614ee600bbaed493522c221c0d9095f3b4d7839415ffab16cbf61767ad
+ - cf84a70a41072a42d0f25580b5cb54d6a9de45db824bbb7ba85d541b099fd49f
+ - c1a5d45809269301993d028313a5c4a5d8b2f56de9725d4d1af9da1ccf186f30
+ namespace: extend Pinning from Crypto
+```
+
+> **WARNING:** The algorithm from the above configuration serves as example only, **do not use this particular algorithm in your production code**.
+
+
+### Obfuscation techniques
+
+The obfuscation techniques are the composable building blocks from which you can create your own obfuscation algorithm. You can compose them in any order you want, so that no one exept you knows how the secret literals are obfuscated.
+
+#### Compression
+
+This technique involves data compression using the algorithm of your choice. The compression technique is **non-polymorphic**, meaning that given the same input data, the same output data is produced with each run.
+
+**Syntax**
+
+```yaml
+compress using
+```
+
+The supported algorithms are shown in the following table:
+| Algorithm | Description |
+|------------------|-----------------------------------------------------------|
+| lzfse | The LZFSE compression algorithm. |
+| lz4 | The LZ4 compression algorithm. |
+| lzma | The LZMA compression algorithm. |
+| zlib | The zlib compression algorithm. |
+
+#### Encryption
+
+This technique involves data encryption using the algorithm of your choice. The encryption technique is **polymorphic**, meaning that given the same input data, different output data is produced with each run.
+
+**Syntax**
+
+```yaml
+encrypt using
+```
+
+The supported algorithms are shown in the following table:
+| Algorithm | Description |
+|------------------|-------------------------------------------------------------------------------------------------|
+| aes-128-gcm | The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 128-bit key. |
+| aes-192-gcm | The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 192-bit key. |
+| aes-256-gcm | The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 256-bit key. |
+| chacha20-poly | The ChaCha20-Poly1305 algorithm. |
+
+#### Randomization
+
+This technique involves data randomization. The randomization technique is **polymorphic**, meaning that given the same input data, different output data is produced with each run.
+
+> **NOTE:** Randomization technique is best suited for secrets of which size does not exceed 256 bytes.
+> For larger secrets, the size of the obfuscated data will grow from 2N to 3N, where N is the input data size in bytes,
+> or even 5N (32-bit platform) or 9N (64-bit platform) if the size of input data is larger than 65 536 bytes.
+> For this reason, the internal implementation of this technique is a subject to change in next releases.
+
+**Syntax**
+
+```yaml
+shuffle
+```
+
+### Secrets
+
+The configuration file utilizes YAML objects to describe the secret literals, which are to be obfuscated. The table below lists the keys to define secret literal along with the type of information to include in each.
+
+| Key | Value type | Description |
+|------------------|---------------------------|----------------------------------------------------------------------------------|
+| name | String | The name of the generated Swift property containing obfuscated secret literal's data. This value is used as-is, without validity checking. Thus, make sure to use a valid property name. **Required.** |
+| namespace | String | The namespace in which to enclose the generated secret literal declaration. See [Namespaces](#namespaces) section for usage details. |
+| value | String or List of strings | The plain value of the secret literal, which is to be obfuscated. The YAML data types are mapped to `String` and `Array` in Swift, respectively. **Required.** |
+
+
+Example secret definition
+
+Supposing that you would like to obfuscate the tag used to reference the private key stored in Keychain or Secure Enclave:
+
+```yaml
+name: secretVaultKeyTag
+value: com.example.app.keys.secret_vault_private_key
+namespace: extend KeychainAccess.Key from Crypto
+```
+
+The above YAML secret definition will result in the following Swift code being generated:
+
+```swift
+import Crypto
+// ... other imports
+
+extension Crypto.KeychainAccess.Key {
+
+ @ConfidentialKit.Obfuscated(deobfuscateData)
+ static var secretVaultKeyTag: ConfidentialKit.Obfuscation.Secret = .init(data: [/* obfuscated data */])
+}
+```
+
+You may also need to obfuscate a list of related values, such as a list of trusted SPKI digests to pin against:
+
+```yaml
+name: trustedSPKIDigests
+value:
+ - 7a6820614ee600bbaed493522c221c0d9095f3b4d7839415ffab16cbf61767ad
+ - cf84a70a41072a42d0f25580b5cb54d6a9de45db824bbb7ba85d541b099fd49f
+ - c1a5d45809269301993d028313a5c4a5d8b2f56de9725d4d1af9da1ccf186f30
+namespace: extend Pinning from Crypto
+```
+
+With the above YAML secret definition, the following Swift code will be generated:
+
+```swift
+import Crypto
+// ... other imports
+
+extension Crypto.Pinning {
+
+ @ConfidentialKit.Obfuscated>(deobfuscateData)
+ static var trustedSPKIDigests: ConfidentialKit.Obfuscation.Secret = .init(data: [/* obfuscated data */])
+}
+```
+
+
+### Namespaces
+
+In accordance with Swift programming best practices, Swift Confidential encapsulates generated secret literal declarations in namespaces (typically enums). The namespaces syntax allows you to either create a new namespace or extend an existing one.
+
+**Syntax**
+
+```yaml
+create # creates new namespace
+
+extend [from ] # extends existing namespace, optionally specifying
+ # the module to which this namespace belongs
+```
+
+
+Example usage
+
+Assuming that you would like to keep the generated secret literal declaration(s) in a new namespace named `Secrets`, use the following YAML code:
+
+```yaml
+create Secrets
+```
+
+The above namespace definition will result in the following Swift code being generated:
+
+```swift
+enum Secrets {
+
+ // Encapsulated declarations ...
+}
+
+```
+
+> **NOTE:** The creation of the nested namespaces is currently not supported.
+
+If, however, you would rather like to keep the generated secret literal declaration(s) in an existing namespace named `Pinning` and imported from `Crypto` module, use the following YAML code instead:
+
+```yaml
+extend Pinning from Crypto
+```
+
+With the above namespace definition, the following Swift code will be generated:
+
+```swift
+import Crypto
+// ... other imports
+
+extension Crypto.Pinning {
+
+ // Encapsulated declarations ...
+}
+```
+
+
+### Additional considerations for Confidential Swift Package plugin
+
+The [Confidential Swift Package plugin](https://github.com/securevale/swift-confidential-plugin) expects the configuration file to be named `confidential.yml` or `confidential.yaml`, and it assumes a single configuration file per target. If you define multiple configuration files in different subdirectories, then the plugin will use the first one it finds, and which one is undefined.
+
+## Versioning
+
+This project follows [semantic versioning](https://semver.org/). While still in major version `0`, source-stability is only guaranteed within minor versions (e.g. between `0.1.0` and `0.1.1`). If you want to guard against potentially source-breaking package updates, you can specify your package dependency using version range (e.g. `"0.1.0"<.."0.2.0"`) as the requirement.
+
+## License
+
+This tool and code is released under Apache License v2.0 with Runtime Library Exception.
+Please see [LICENSE](LICENSE) for more information.
\ No newline at end of file
diff --git a/Resources/machoview-cstring-literals.png b/Resources/machoview-cstring-literals.png
new file mode 100644
index 0000000..d48fef6
Binary files /dev/null and b/Resources/machoview-cstring-literals.png differ
diff --git a/Scripts/Commons/common.sh b/Scripts/Commons/common.sh
new file mode 100644
index 0000000..542ca15
--- /dev/null
+++ b/Scripts/Commons/common.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+# shellcheck disable=SC2034,SC2155
+
+#####################
+# CONSTANTS #
+#####################
+
+if [[ -t 1 && -t 2 ]]
+then
+ # shellcheck disable=SC2086
+ function tput_set() { tput $1; }
+else
+ function tput_set() { :; }
+fi
+
+readonly BOLD=$(tput_set bold)
+readonly UNDERLINE=$(tput_set smul)
+readonly NORMAL=$(tput_set sgr0)
+
+#####################
+# FUNCTIONS #
+#####################
+
+function pushd_quiet() {
+ pushd "$1" &>/dev/null || exit
+}
+
+function popd_quiet() {
+ popd &>/dev/null || exit
+}
+
+function echoerr() {
+ local IFS=" "
+ cat <<< "$* โ" 1>&2;
+}
+
+function echo_progress() {
+ echo "$1 โ๏ธ"
+}
diff --git a/Scripts/Templates/artifactbundle-info.json.template b/Scripts/Templates/artifactbundle-info.json.template
new file mode 100644
index 0000000..f43d382
--- /dev/null
+++ b/Scripts/Templates/artifactbundle-info.json.template
@@ -0,0 +1,15 @@
+{
+ "schemaVersion": "1.0",
+ "artifacts": {
+ "__NAME__": {
+ "type": "executable",
+ "version": "__VERSION__",
+ "variants": [
+ {
+ "path": "__NAME__-__VERSION__-macos/bin/__NAME__",
+ "supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scripts/generate_release_artifacts.sh b/Scripts/generate_release_artifacts.sh
new file mode 100755
index 0000000..d1e3148
--- /dev/null
+++ b/Scripts/generate_release_artifacts.sh
@@ -0,0 +1,224 @@
+#!/usr/bin/env bash
+
+# shellcheck disable=SC1091,SC2155
+
+set -Eeuo pipefail
+
+source "$(dirname "$0")"/Commons/common.sh
+
+#################
+# CONSTANTS #
+#################
+
+readonly SCRIPT_NAME=$(basename -s ".sh" "$0")
+readonly SCRIPT_FULL_NAME=$(basename "$0")
+readonly SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
+readonly SCRIPT_TEMPLATES_ABS_PATH="$SCRIPT_ABS_PATH/Templates"
+
+readonly OPTION_HELP_SHORT="-h"
+readonly OPTION_HELP="--help"
+
+readonly PRODUCT_CONFIDENTIAL="confidential"
+
+readonly SWIFT_BUILD_ARCH_X86="x86_64"
+readonly SWIFT_BUILD_ARCH_ARM="arm64"
+readonly SWIFT_BUILD_DIR_NAME=".build"
+
+readonly UNIVERSAL_BIN_DIR_ABS_PATH="$SCRIPT_ABS_PATH/../$SWIFT_BUILD_DIR_NAME/universal"
+
+readonly LICENSE_ABS_PATH="$SCRIPT_ABS_PATH/../LICENSE"
+readonly ARTIFACT_BUNDLE_INFO_TEMPLATE_ABS_PATH="$SCRIPT_TEMPLATES_ABS_PATH/artifactbundle-info.json.template"
+
+readonly RELEASE_DIR_NAME=".release"
+readonly RELEASE_DIR_ABS_PATH="$SCRIPT_ABS_PATH/../$RELEASE_DIR_NAME"
+
+readonly ERROR_MSG_SEE_USAGE_HELP="Use ${BOLD}$OPTION_HELP_SHORT${NORMAL} | ${BOLD}$OPTION_HELP${NORMAL} option for usage help."
+
+########################
+# GLOBAL VARIABLES #
+########################
+
+VERSION_STRING=""
+
+TMP_DIR_PATH=""
+UNIVERSAL_BIN_ABS_PATH=""
+
+#################
+# FUNCTIONS #
+#################
+
+function help() {
+ cat << MANUAL
+
+${BOLD}NAME${NORMAL}
+ ${BOLD}$SCRIPT_NAME${NORMAL}
+
+${BOLD}SYNOPSIS${NORMAL}
+ ${BOLD}$SCRIPT_FULL_NAME${NORMAL} ${UNDERLINE}version${NORMAL}
+ ${BOLD}$SCRIPT_FULL_NAME${NORMAL} ${BOLD}$OPTION_HELP_SHORT${NORMAL} | ${BOLD}$OPTION_HELP${NORMAL}
+
+${BOLD}DESCRIPTION${NORMAL}
+ ${BOLD}$SCRIPT_NAME${NORMAL} is a script that generates the release artifacts tagging
+ them with the supplied ${UNDERLINE}version${NORMAL} string. The generated artifacts are saved in the
+ ${BOLD}$RELEASE_DIR_NAME${NORMAL} directory located in the package's root directory.
+
+ Generated artifacts include:
+ โข The zip archive containing SwiftPM artifact bundle with ${BOLD}$PRODUCT_CONFIDENTIAL${NORMAL} CLI tool
+ binary for macOS.
+
+${BOLD}DEPENDENCIES${NORMAL}
+ The ${BOLD}$SCRIPT_NAME${NORMAL} script has the following dependencies:
+ โข ${BOLD}Bash 4.2 or newer${NORMAL} - you can upgrade Bash with ${UNDERLINE}upgrade_bash.sh${NORMAL} script.
+ โข ${BOLD}Swift 5.6${NORMAL} - Swift toolchain comes bundled with Xcode.
+
+ Make sure that all dependencies are installed before you start using the script.
+
+${BOLD}ARGUMENTS${NORMAL}
+
+ ${UNDERLINE}version${NORMAL}
+ The release version following the MAJOR.MINOR.PATCH scheme.
+
+${BOLD}OPTIONS${NORMAL}
+ Options start with one or two dashes.
+
+ ${BOLD}$OPTION_HELP_SHORT${NORMAL}, ${BOLD}$OPTION_HELP${NORMAL}
+ Show usage description.
+
+${BOLD}EXAMPLE USAGE${NORMAL}
+ Generate artifacts for release tagged with version 0.0.1.
+ ${BOLD}$SCRIPT_FULL_NAME 0.0.1${NORMAL}
+
+MANUAL
+}
+
+function read_version_argument() {
+ if [[ $# -eq 0 ]]
+ then
+ echoerr "No version provided. $ERROR_MSG_SEE_USAGE_HELP"
+ exit 1
+ fi
+ if [[ ! $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
+ then
+ echoerr "Invalid version format: $1"
+ exit 1
+ fi
+
+ VERSION_STRING="$1"
+}
+
+function set_up() {
+ echo "---------------------------- SET UP ----------------------------"
+ echo "๐ณ"
+
+ echo "Cleaning up SPM build artifacts"
+ swift package clean
+
+ echo "Making temporary output directory"
+ TMP_DIR_PATH=$(mktemp -d "$(pwd -P)/$SCRIPT_NAME.tmp.XXXXXXXXXX")
+
+ echo "-------------------------- END SET UP --------------------------"
+}
+
+function clean_up() {
+ echo "--------------------------- CLEAN UP ---------------------------"
+ echo "๐งฝ"
+
+ # SPM .build directory generated by SPM interferes with
+ # Xcode build system, so it needs to be removed once script execution is done.
+ local -r swift_build_dir_path="$SCRIPT_ABS_PATH/../$SWIFT_BUILD_DIR_NAME"
+ if [[ -d "$swift_build_dir_path" ]]
+ then
+ echo "Deleting SPM $SWIFT_BUILD_DIR_NAME directory"
+ rm -rf "$swift_build_dir_path"
+ fi
+
+ if [[ -d "$TMP_DIR_PATH" ]]
+ then
+ echo "Removing temporary output directory"
+ rm -rf "$TMP_DIR_PATH"
+ fi
+
+ echo "------------------------- END CLEAN UP -------------------------"
+}
+
+function swift_build_cmd() {
+ echo "swift build --product $1 --configuration release -Xlinker -dead_strip --arch $2"
+}
+
+function build_product() {
+ eval "$(swift_build_cmd "$1" "$2") > /dev/null"
+ local -r bin_path=$(eval "$(swift_build_cmd "$1" "$2") --show-bin-path")
+
+ echo "$bin_path/$1"
+}
+
+function build_universal_binary() {
+ local -r product="$1"
+ echo_progress "Building $product product for $SWIFT_BUILD_ARCH_X86 architecture"
+ local -r x86_bin_path=$(build_product "$product" "$SWIFT_BUILD_ARCH_X86")
+ echo_progress "Building $product product for $SWIFT_BUILD_ARCH_ARM architecture"
+ local -r arm_bin_path=$(build_product "$product" "$SWIFT_BUILD_ARCH_ARM")
+
+ echo_progress "Creating fat binary for $SWIFT_BUILD_ARCH_X86+$SWIFT_BUILD_ARCH_ARM"
+ mkdir -p "$UNIVERSAL_BIN_DIR_ABS_PATH"
+ UNIVERSAL_BIN_ABS_PATH="$UNIVERSAL_BIN_DIR_ABS_PATH/$product"
+ lipo "$x86_bin_path" "$arm_bin_path" -create -output "$UNIVERSAL_BIN_ABS_PATH"
+ strip -rSTx "$UNIVERSAL_BIN_ABS_PATH"
+}
+
+function spm_artifactbundle() {
+ echo "---------------------- SPM ARTIFACT BUNDLE ---------------------"
+
+ local -r product="$1"
+ build_universal_binary "$product"
+
+ echo_progress "Generating SPM artifact bundle"
+ local -r bundle_name="$product.artifactbundle"
+ local -r bundle_path="$TMP_DIR_PATH/$bundle_name"
+ local -r bundle_bin_path="$bundle_path/$product-$VERSION_STRING-macos/bin"
+ mkdir -p "$bundle_bin_path"
+ sed "s/__NAME__/$product/g; s/__VERSION__/$VERSION_STRING/g" "$ARTIFACT_BUNDLE_INFO_TEMPLATE_ABS_PATH" > "$bundle_path/info.json"
+ cp -f "$UNIVERSAL_BIN_ABS_PATH" "$bundle_bin_path"
+ cp -f "$LICENSE_ABS_PATH" "$bundle_path"
+
+ mkdir -p "$RELEASE_DIR_ABS_PATH"
+
+ echo_progress "Archiving SPM artifact bundle"
+ local -r bundle_archive_name="${product^}Binary-macos.artifactbundle.zip"
+ pushd_quiet "$TMP_DIR_PATH"
+ zip -qr "$RELEASE_DIR_ABS_PATH/$bundle_archive_name" "$bundle_name"
+ popd_quiet
+
+ pushd_quiet "$SCRIPT_ABS_PATH/.."
+ echo -e "Bundle checksum:\n$(swift package compute-checksum "./$RELEASE_DIR_NAME/$bundle_archive_name")"
+ popd_quiet
+
+ echo "-------------------- END SPM ARTIFACT BUNDLE -------------------"
+}
+
+###################
+# ENTRY POINT #
+###################
+
+while [[ $# -gt 0 ]]
+do
+key="$1"
+
+case $key in
+ "$OPTION_HELP_SHORT" | "$OPTION_HELP")
+ help
+ exit 0
+ ;;
+ *)
+ break
+ ;;
+esac
+done
+
+read_version_argument "$@"
+
+trap clean_up EXIT
+
+set_up
+
+spm_artifactbundle "$PRODUCT_CONFIDENTIAL"
diff --git a/Scripts/run_tests.sh b/Scripts/run_tests.sh
new file mode 100755
index 0000000..bce127b
--- /dev/null
+++ b/Scripts/run_tests.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+
+# shellcheck disable=SC1091,SC2155
+
+set -Eeuo pipefail
+
+source "$(dirname "$0")"/Commons/common.sh
+
+#################
+# CONSTANTS #
+#################
+
+readonly SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
+
+readonly SWIFT_BUILD_DIR_NAME=".build"
+
+readonly COVERAGE_DIR_NAME=".coverage"
+readonly COVERAGE_INCLUDE_LIST=(
+ "Sources/ConfidentialCore/"
+ "Sources/ConfidentialKit/"
+ )
+
+#################
+# FUNCTIONS #
+#################
+
+function set_up() {
+ echo "---------------------------- SET UP ----------------------------"
+ echo "๐ณ"
+
+ echo "Cleaning up SPM build artifacts"
+ swift package clean
+
+ echo "-------------------------- END SET UP --------------------------"
+}
+
+function clean_up() {
+ echo "--------------------------- CLEAN UP ---------------------------"
+ echo "๐งฝ"
+
+ # SPM .build directory generated by SPM interferes with
+ # Xcode build system, so it needs to be removed once script execution is done.
+ local -r swift_build_dir_path="$SCRIPT_ABS_PATH/../$SWIFT_BUILD_DIR_NAME"
+ if [[ -d "$swift_build_dir_path" ]]
+ then
+ echo "Deleting SPM $SWIFT_BUILD_DIR_NAME directory"
+ rm -rf "$swift_build_dir_path"
+ fi
+
+ echo "------------------------- END CLEAN UP -------------------------"
+}
+
+function swift_test() {
+ echo "-------------------------- SWIFT TEST --------------------------"
+ echo "๐งช"
+
+ swift test --parallel --enable-code-coverage
+
+ echo "------------------------ END SWIFT TEST ------------------------"
+}
+
+function swift_package_name() {
+ swift package describe | awk '/Name:/ { print $2; exit; }'
+}
+
+function export_coverage_data() {
+ echo "------------------------ COVERAGE DATA -------------------------"
+
+ echo_progress "Exporting code coverage data"
+
+ local -r package_name=$(swift_package_name)
+ local -r bin_path="$SWIFT_BUILD_DIR_NAME/debug/${package_name}PackageTests.xctest/Contents/MacOS/${package_name}PackageTests"
+ local -r profdata_path="$SWIFT_BUILD_DIR_NAME/debug/codecov/default.profdata"
+ local -r coverage_report_path="$COVERAGE_DIR_NAME/$package_name.lcov"
+
+ pushd_quiet "$SCRIPT_ABS_PATH/.."
+ mkdir -p "$COVERAGE_DIR_NAME"
+ # shellcheck disable=SC2068
+ xcrun llvm-cov export -format="lcov" "$bin_path" -instr-profile "$profdata_path" ${COVERAGE_INCLUDE_LIST[@]} \
+ > "$coverage_report_path"
+ echo "Code coverage report: $(pwd -P)/$coverage_report_path"
+ popd_quiet
+
+ echo "---------------------- END COVERAGE DATA -----------------------"
+}
+
+###################
+# ENTRY POINT #
+###################
+
+trap clean_up EXIT
+
+set_up
+
+swift_test
+export_coverage_data
diff --git a/Scripts/upgrade_bash.sh b/Scripts/upgrade_bash.sh
new file mode 100755
index 0000000..0db3d06
--- /dev/null
+++ b/Scripts/upgrade_bash.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+set -Eeuo pipefail
+
+readonly LOGIN_SHELLS_FILE_PATH="/etc/shells"
+
+readonly HOMEBREW_BASH_FORMULA="bash"
+readonly HOMEBREW_BASH_X86_INSTALL_DIR="/usr/local/bin/bash"
+readonly HOMEBREW_BASH_ARM_INSTALL_DIR="/opt/homebrew/bin/bash"
+
+if ! command -v "brew" &>/dev/null
+then
+ echo "Error: 'brew' command not found. Please install Homebrew before running this script." 1>&2
+ exit 1
+fi
+
+echo "-------------------------------- UPGRADE BASH --------------------------------"
+
+if ! brew list $HOMEBREW_BASH_FORMULA &>/dev/null
+then
+ echo "Installing Bash โ๏ธ"
+ brew install $HOMEBREW_BASH_FORMULA > /dev/null
+else
+ echo "Updating Bash โ๏ธ"
+ brew upgrade $HOMEBREW_BASH_FORMULA > /dev/null
+fi
+
+if [[ -f "$HOMEBREW_BASH_ARM_INSTALL_DIR" ]]
+then
+ HOMEBREW_BASH_INSTALL_DIR="$HOMEBREW_BASH_ARM_INSTALL_DIR"
+else
+ HOMEBREW_BASH_INSTALL_DIR="$HOMEBREW_BASH_X86_INSTALL_DIR"
+fi
+
+if ! grep -q $HOMEBREW_BASH_INSTALL_DIR $LOGIN_SHELLS_FILE_PATH
+then
+ echo $HOMEBREW_BASH_INSTALL_DIR | sudo tee -a $LOGIN_SHELLS_FILE_PATH
+fi
+
+sudo chsh -s $HOMEBREW_BASH_INSTALL_DIR
+
+echo -e "\n$(bash --version)"
+
+echo "------------------------------ END UPGRADE BASH ------------------------------"
diff --git a/Sources/ConfidentialCore/Coding/DataEncoder.swift b/Sources/ConfidentialCore/Coding/DataEncoder.swift
new file mode 100644
index 0000000..56528d8
--- /dev/null
+++ b/Sources/ConfidentialCore/Coding/DataEncoder.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+protocol DataEncoder {
+ func encode(_ value: E) throws -> Data
+}
+
+extension JSONEncoder: DataEncoder {}
diff --git a/Sources/ConfidentialCore/Constants/Constants.swift b/Sources/ConfidentialCore/Constants/Constants.swift
new file mode 100644
index 0000000..888fa78
--- /dev/null
+++ b/Sources/ConfidentialCore/Constants/Constants.swift
@@ -0,0 +1,27 @@
+enum C {
+
+ enum Code {
+
+ enum Format {
+ static let indentWidth: Int = 4
+ }
+
+ enum Generation {
+ static let deobfuscateDataFuncName: String = "deobfuscateData"
+ static let deobfuscateDataFuncParamName: String = "data"
+ }
+ }
+
+ enum Parsing {
+
+ enum Keywords {
+ static let compress: String = "compress"
+ static let encrypt: String = "encrypt"
+ static let shuffle: String = "shuffle"
+ static let using: String = "using"
+ static let create: String = "create"
+ static let extend: String = "extend"
+ static let from: String = "from"
+ }
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CaseIterable.swift b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CaseIterable.swift
new file mode 100644
index 0000000..84c520a
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CaseIterable.swift
@@ -0,0 +1,13 @@
+import ConfidentialKit
+
+extension Obfuscation.Compression.CompressionAlgorithm: CaseIterable {
+
+ public static var allCases: [Self] {
+ [
+ .lzfse,
+ .lz4,
+ .lzma,
+ .zlib
+ ]
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CustomStringConvertible.swift b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CustomStringConvertible.swift
new file mode 100644
index 0000000..f7ca393
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CustomStringConvertible.swift
@@ -0,0 +1,19 @@
+import ConfidentialKit
+
+extension Obfuscation.Compression.CompressionAlgorithm: CustomStringConvertible {
+
+ public var description: String {
+ switch self {
+ case .lzfse:
+ return "lzfse"
+ case .lz4:
+ return "lz4"
+ case .lzma:
+ return "lzma"
+ case .zlib:
+ return "zlib"
+ @unknown default:
+ return "unknown"
+ }
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+Name.swift b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+Name.swift
new file mode 100644
index 0000000..861b19b
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+Name.swift
@@ -0,0 +1,19 @@
+import ConfidentialKit
+
+extension Obfuscation.Compression.CompressionAlgorithm {
+
+ var name: String {
+ switch self {
+ case .lzfse:
+ return "lzfse"
+ case .lz4:
+ return "lz4"
+ case .lzma:
+ return "lzma"
+ case .zlib:
+ return "zlib"
+ @unknown default:
+ return "unknown"
+ }
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CaseIterable.swift b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CaseIterable.swift
new file mode 100644
index 0000000..c56dff7
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CaseIterable.swift
@@ -0,0 +1,13 @@
+import ConfidentialKit
+
+extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm: CaseIterable {
+
+ public static var allCases: [Self] {
+ [
+ .aes128GCM,
+ .aes192GCM,
+ .aes256GCM,
+ .chaChaPoly
+ ]
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CustomStringConvertible.swift b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CustomStringConvertible.swift
new file mode 100644
index 0000000..0d781d9
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CustomStringConvertible.swift
@@ -0,0 +1,17 @@
+import ConfidentialKit
+
+extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm: CustomStringConvertible {
+
+ public var description: String {
+ switch self {
+ case .aes128GCM:
+ return "aes-128-gcm"
+ case .aes192GCM:
+ return "aes-192-gcm"
+ case .aes256GCM:
+ return "aes-256-gcm"
+ case .chaChaPoly:
+ return "chacha20-poly"
+ }
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+Name.swift b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+Name.swift
new file mode 100644
index 0000000..004ac74
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+Name.swift
@@ -0,0 +1,6 @@
+import ConfidentialKit
+
+extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm {
+
+ var name: String { rawValue }
+}
diff --git a/Sources/ConfidentialCore/Extensions/Foundation/Data/Data+HexString.swift b/Sources/ConfidentialCore/Extensions/Foundation/Data/Data+HexString.swift
new file mode 100644
index 0000000..17176bd
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/Foundation/Data/Data+HexString.swift
@@ -0,0 +1,20 @@
+import Foundation
+
+extension Data {
+
+ struct HexEncodingOptions: OptionSet {
+ static let upperCase: Self = .init(rawValue: 1 << 0)
+ static let numericLiteral: Self = .init(rawValue: 1 << 1)
+
+ let rawValue: Int
+ }
+
+ func hexEncodedStringComponents(options: HexEncodingOptions = []) -> [String] {
+ var format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
+ if options.contains(.numericLiteral) {
+ format = "0x\(format)"
+ }
+
+ return map { String(format: format, $0) }
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/Swift/Encodable/Encodable+TypeErasure.swift b/Sources/ConfidentialCore/Extensions/Swift/Encodable/Encodable+TypeErasure.swift
new file mode 100644
index 0000000..294a91c
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/Swift/Encodable/Encodable+TypeErasure.swift
@@ -0,0 +1,19 @@
+struct AnyEncodable: Encodable {
+
+ private let encode: (Encoder) throws -> Void
+
+ init(_ encodable: any Encodable) {
+ self.encode = encodable.encode(to:)
+ }
+
+ func encode(to encoder: Encoder) throws {
+ try encode(encoder)
+ }
+}
+
+extension Encodable {
+
+ func eraseToAnyEncodable() -> AnyEncodable {
+ .init(self)
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/Swift/FixedWidthInteger/FixedWidthInteger+SecureRandom.swift b/Sources/ConfidentialCore/Extensions/Swift/FixedWidthInteger/FixedWidthInteger+SecureRandom.swift
new file mode 100644
index 0000000..1816dc9
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/Swift/FixedWidthInteger/FixedWidthInteger+SecureRandom.swift
@@ -0,0 +1,26 @@
+import Foundation
+
+extension FixedWidthInteger {
+
+ typealias SecureRandomNumberSource = (inout [UInt8]) -> OSStatus
+
+ static func secureRandom(
+ using source: SecureRandomNumberSource = {
+ SecRandomCopyBytes(kSecRandomDefault, $0.count, &$0)
+ }
+ ) throws -> Self {
+ let count = MemoryLayout.stride
+ var bytes = [UInt8](repeating: .zero, count: count)
+ let status = source(&bytes)
+ guard status == errSecSuccess else {
+ let errorDescription = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error"
+ throw NSError(
+ domain: NSOSStatusErrorDomain,
+ code: .init(status),
+ userInfo: [NSLocalizedDescriptionKey: errorDescription]
+ )
+ }
+
+ return bytes.withUnsafeBytes { $0.load(as: Self.self) }
+ }
+}
diff --git a/Sources/ConfidentialCore/Extensions/SwiftSyntax/TokenSyntax/TokenSyntax+Tokens.swift b/Sources/ConfidentialCore/Extensions/SwiftSyntax/TokenSyntax/TokenSyntax+Tokens.swift
new file mode 100644
index 0000000..c8aac9a
--- /dev/null
+++ b/Sources/ConfidentialCore/Extensions/SwiftSyntax/TokenSyntax/TokenSyntax+Tokens.swift
@@ -0,0 +1,79 @@
+import SwiftSyntax
+
+extension TokenSyntax {
+
+ static func atSign(
+ leadingNewlines: Int,
+ followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth
+ ) -> Self {
+ makeToken(
+ .atSign.withoutTrivia(),
+ withLeadingNewlines: leadingNewlines,
+ followedByLeadingSpaces: leadingSpaces
+ )
+ }
+
+ static func period(
+ leadingNewlines: Int,
+ followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth
+ ) -> Self {
+ makeToken(
+ .period.withoutTrivia(),
+ withLeadingNewlines: leadingNewlines,
+ followedByLeadingSpaces: leadingSpaces
+ )
+ }
+
+ static func rightParen(
+ leadingNewlines: Int,
+ followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth
+ ) -> Self {
+ makeToken(
+ .rightParen.withoutTrivia(),
+ withLeadingNewlines: leadingNewlines,
+ followedByLeadingSpaces: leadingSpaces
+ )
+ }
+
+ static func `private`(
+ leadingNewlines: Int,
+ followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth,
+ trailingSpaces: Int = 1
+ ) -> Self {
+ makeToken(
+ .private.withoutTrivia().withTrailingTrivia(.spaces(trailingSpaces)),
+ withLeadingNewlines: leadingNewlines,
+ followedByLeadingSpaces: leadingSpaces
+ )
+ }
+
+ static func `static`(
+ leadingNewlines: Int,
+ followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth,
+ trailingSpaces: Int = 1
+ ) -> Self {
+ makeToken(
+ .static.withoutTrivia().withTrailingTrivia(.spaces(trailingSpaces)),
+ withLeadingNewlines: leadingNewlines,
+ followedByLeadingSpaces: leadingSpaces
+ )
+ }
+}
+
+private extension TokenSyntax {
+
+ static func makeToken(
+ _ token: Self,
+ withLeadingNewlines leadingNewlines: Int,
+ followedByLeadingSpaces leadingSpaces: Int
+ ) -> Self {
+ guard leadingNewlines > .zero else {
+ return token
+ }
+
+ return token.withLeadingTrivia(
+ .newlines(leadingNewlines)
+ .appending(.spaces(leadingSpaces))
+ )
+ }
+}
diff --git a/Sources/ConfidentialCore/Models/Configuration/Configuration.Secret.Value+UnderlyingValue.swift b/Sources/ConfidentialCore/Models/Configuration/Configuration.Secret.Value+UnderlyingValue.swift
new file mode 100644
index 0000000..25d66a7
--- /dev/null
+++ b/Sources/ConfidentialCore/Models/Configuration/Configuration.Secret.Value+UnderlyingValue.swift
@@ -0,0 +1,11 @@
+extension Configuration.Secret.Value {
+
+ var underlyingValue: AnyEncodable {
+ switch self {
+ case let .array(value):
+ return value.eraseToAnyEncodable()
+ case let .singleValue(value):
+ return value.eraseToAnyEncodable()
+ }
+ }
+}
diff --git a/Sources/ConfidentialCore/Models/Configuration/Configuration.swift b/Sources/ConfidentialCore/Models/Configuration/Configuration.swift
new file mode 100644
index 0000000..fabf4f4
--- /dev/null
+++ b/Sources/ConfidentialCore/Models/Configuration/Configuration.swift
@@ -0,0 +1,65 @@
+import ConfidentialKit
+
+public struct Configuration: Equatable, Decodable {
+ var algorithm: ArraySlice
+ var defaultNamespace: String?
+ var secrets: ArraySlice
+
+ init(
+ algorithm: ArraySlice,
+ defaultNamespace: String?,
+ secrets: ArraySlice
+ ) {
+ self.algorithm = algorithm
+ self.defaultNamespace = defaultNamespace
+ self.secrets = secrets
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ self = .init(
+ algorithm: try container.decode([String].self, forKey: .algorithm)[...],
+ defaultNamespace: try? container.decodeIfPresent(String.self, forKey: .defaultNamespace),
+ secrets: try container.decode([Secret].self, forKey: .secrets)[...]
+ )
+ }
+}
+
+extension Configuration {
+
+ struct Secret: Equatable, Hashable, Decodable {
+ let name: String
+ let value: Value
+ let namespace: String?
+ }
+}
+
+extension Configuration.Secret {
+
+ enum Value: Equatable, Hashable, Decodable {
+
+ typealias DataTypes = Obfuscation.SupportedDataTypes
+
+ case array(DataTypes.Array)
+ case singleValue(DataTypes.SingleValue)
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ if let value = try? container.decode(DataTypes.Array.self) {
+ self = .array(value)
+ } else {
+ let value = try container.decode(DataTypes.SingleValue.self)
+ self = .singleValue(value)
+ }
+ }
+ }
+}
+
+private extension Configuration {
+
+ enum CodingKeys: String, CodingKey {
+ case algorithm
+ case defaultNamespace
+ case secrets
+ }
+}
diff --git a/Sources/ConfidentialCore/Models/SourceSpecification/SourceSpecification.swift b/Sources/ConfidentialCore/Models/SourceSpecification/SourceSpecification.swift
new file mode 100644
index 0000000..62a490b
--- /dev/null
+++ b/Sources/ConfidentialCore/Models/SourceSpecification/SourceSpecification.swift
@@ -0,0 +1,108 @@
+import ConfidentialKit
+import Foundation
+
+public struct SourceSpecification: Equatable {
+ var algorithm: Algorithm
+ var secrets: Secrets
+}
+
+public extension SourceSpecification {
+
+ typealias Algorithm = ArraySlice
+
+ struct ObfuscationStep: Equatable {
+ let technique: Technique
+ }
+
+ struct Secret {
+ let name: String
+ var data: Data
+ let dataAccessWrapperInfo: DataAccessWrapperInfo
+ }
+
+ struct Secrets: Equatable, Hashable {
+
+ public typealias Secret = SourceSpecification.Secret
+
+ private var secrets: [Secret.Namespace: ArraySlice]
+
+ var namespaces: Dictionary>.Keys {
+ secrets.keys
+ }
+
+ init(_ secrets: [Secret.Namespace: ArraySlice]) {
+ self.secrets = secrets
+ }
+
+ subscript(namespace: Secret.Namespace) -> ArraySlice? {
+ _read { yield self.secrets[namespace] }
+ _modify { yield &self.secrets[namespace] }
+ }
+ }
+}
+
+public extension SourceSpecification.ObfuscationStep {
+
+ enum Technique: Equatable, Hashable {
+ case compression(algorithm: Obfuscation.Compression.CompressionAlgorithm)
+ case encryption(algorithm: Obfuscation.Encryption.SymmetricEncryptionAlgorithm)
+ case randomization(nonce: UInt64)
+ }
+}
+
+extension SourceSpecification.Secret: Equatable, Hashable {
+
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ lhs.name == rhs.name && lhs.data == rhs.data
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(name)
+ hasher.combine(data)
+ }
+}
+
+extension SourceSpecification.Secret {
+
+ struct DataAccessWrapperInfo {
+
+ typealias Argument = (label: String?, value: String)
+
+ let typeInfo: TypeInfo
+ let arguments: [Argument]
+ }
+
+ public enum Namespace: Equatable, Hashable {
+ case create(identifier: String)
+ case extend(identifier: String, moduleName: String? = nil)
+ }
+}
+
+extension SourceSpecification.Secrets: Collection {
+
+ public typealias Element = Dictionary>.Element
+ public typealias Index = Dictionary>.Index
+
+ public var startIndex: Index {
+ secrets.startIndex
+ }
+
+ public var endIndex: Index {
+ secrets.endIndex
+ }
+
+ public subscript(position: Index) -> Element {
+ secrets[position]
+ }
+
+ public func index(after index: Index) -> Index {
+ secrets.index(after: index)
+ }
+}
+
+extension SourceSpecification.Secrets: ExpressibleByDictionaryLiteral {
+
+ public init(dictionaryLiteral elements: (Secret.Namespace, ArraySlice)...) {
+ self.init(.init(uniqueKeysWithValues: elements))
+ }
+}
diff --git a/Sources/ConfidentialCore/Obfuscation/DataObfuscationStep.swift b/Sources/ConfidentialCore/Obfuscation/DataObfuscationStep.swift
new file mode 100644
index 0000000..f0742ac
--- /dev/null
+++ b/Sources/ConfidentialCore/Obfuscation/DataObfuscationStep.swift
@@ -0,0 +1,5 @@
+import Foundation
+
+protocol DataObfuscationStep {
+ func obfuscate(_ data: Data) throws -> Data
+}
diff --git a/Sources/ConfidentialCore/Obfuscation/SourceObfuscator+ObfuscationStepResolver.swift b/Sources/ConfidentialCore/Obfuscation/SourceObfuscator+ObfuscationStepResolver.swift
new file mode 100644
index 0000000..d4ca477
--- /dev/null
+++ b/Sources/ConfidentialCore/Obfuscation/SourceObfuscator+ObfuscationStepResolver.swift
@@ -0,0 +1,33 @@
+import ConfidentialKit
+
+protocol DataObfuscationStepResolver {
+
+ typealias Technique = SourceSpecification.ObfuscationStep.Technique
+
+ func obfuscationStep(for technique: Technique) -> any DataObfuscationStep
+}
+
+extension SourceObfuscator {
+
+ struct ObfuscationStepResolver: DataObfuscationStepResolver {
+
+ @inline(__always)
+ func obfuscationStep(for technique: Technique) -> any DataObfuscationStep {
+ switch technique {
+ case let .compression(algorithm):
+ return Obfuscation.Compression.DataCompressor(algorithm: algorithm)
+ case let .encryption(algorithm):
+ return Obfuscation.Encryption.DataCrypter(algorithm: algorithm)
+ case let .randomization(nonce):
+ return Obfuscation.Randomization.DataShuffler(nonce: nonce)
+ }
+ }
+ }
+}
+
+public extension SourceObfuscator {
+
+ init() {
+ self.init(obfuscationStepResolver: ObfuscationStepResolver())
+ }
+}
diff --git a/Sources/ConfidentialCore/Obfuscation/SourceObfuscator.swift b/Sources/ConfidentialCore/Obfuscation/SourceObfuscator.swift
new file mode 100644
index 0000000..13da7a9
--- /dev/null
+++ b/Sources/ConfidentialCore/Obfuscation/SourceObfuscator.swift
@@ -0,0 +1,47 @@
+import Foundation
+
+public struct SourceObfuscator {
+
+ private let obfuscationStepResolver: DataObfuscationStepResolver
+
+ init(obfuscationStepResolver: DataObfuscationStepResolver) {
+ self.obfuscationStepResolver = obfuscationStepResolver
+ }
+
+ public func obfuscate(_ source: inout SourceSpecification) throws {
+ guard !source.secrets.isEmpty else {
+ return
+ }
+
+ let obfuscateData = obfuscationFunc(given: source.algorithm)
+ try source.secrets.namespaces.forEach { namespace in
+ guard let secrets = source.secrets[namespace] else {
+ fatalError("Unexpected source specification integrity violation")
+ }
+
+ source.secrets[namespace] = try secrets.map { secret in
+ var secret = secret
+ secret.data = try obfuscateData(secret.data)
+ return secret
+ }[...]
+ }
+ }
+}
+
+private extension SourceObfuscator {
+
+ typealias Algorithm = SourceSpecification.Algorithm
+ typealias ObfuscationFunc = (Data) throws -> Data
+
+ @inline(__always)
+ func obfuscationFunc(given algorithm: Algorithm) -> ObfuscationFunc {
+ algorithm
+ .map(\.technique)
+ .map(obfuscationStepResolver.obfuscationStep(for:))
+ .reduce({ $0 }) { partialFunc, step in
+ return {
+ try step.obfuscate(partialFunc($0))
+ }
+ }
+ }
+}
diff --git a/Sources/ConfidentialCore/Obfuscation/Techniques/Compression/DataCompressor+DataObfuscationStep.swift b/Sources/ConfidentialCore/Obfuscation/Techniques/Compression/DataCompressor+DataObfuscationStep.swift
new file mode 100644
index 0000000..d869740
--- /dev/null
+++ b/Sources/ConfidentialCore/Obfuscation/Techniques/Compression/DataCompressor+DataObfuscationStep.swift
@@ -0,0 +1,10 @@
+import ConfidentialKit
+import Foundation
+
+extension Obfuscation.Compression.DataCompressor: DataObfuscationStep {
+
+ func obfuscate(_ data: Data) throws -> Data {
+ let compressedData = try NSData(data: data).compressed(using: algorithm)
+ return .init(referencing: compressedData)
+ }
+}
diff --git a/Sources/ConfidentialCore/Obfuscation/Techniques/Encryption/DataCrypter+DataObfuscationStep.swift b/Sources/ConfidentialCore/Obfuscation/Techniques/Encryption/DataCrypter+DataObfuscationStep.swift
new file mode 100644
index 0000000..7284484
--- /dev/null
+++ b/Sources/ConfidentialCore/Obfuscation/Techniques/Encryption/DataCrypter+DataObfuscationStep.swift
@@ -0,0 +1,32 @@
+import ConfidentialKit
+import CryptoKit
+import Foundation
+
+extension Obfuscation.Encryption.DataCrypter: DataObfuscationStep {
+
+ func obfuscate(_ data: Data) throws -> Data {
+ let key = SymmetricKey(size: algorithm.keySize)
+
+ var obfuscatedData: Data
+ switch algorithm {
+ case .aes128GCM, .aes192GCM, .aes256GCM:
+ let sealedBox = try AES.GCM.seal(data, using: key, nonce: .init())
+ /*
+ As the official documentation states, when we use the nonce of the
+ default size of 12 bytes, the combined representation is available,
+ hence the use of force unwrap.
+ See https://developer.apple.com/documentation/cryptokit/aes/gcm/sealedbox
+ for more details
+ */
+ obfuscatedData = sealedBox.combined!
+ case .chaChaPoly:
+ let sealedBox = try ChaChaPoly.seal(data, using: key, nonce: .init())
+ obfuscatedData = sealedBox.combined
+ }
+
+ let keyData = key.withUnsafeBytes(Data.init(_:))
+ obfuscatedData.append(keyData)
+
+ return obfuscatedData
+ }
+}
diff --git a/Sources/ConfidentialCore/Obfuscation/Techniques/Randomization/DataShuffler+DataObfuscationStep.swift b/Sources/ConfidentialCore/Obfuscation/Techniques/Randomization/DataShuffler+DataObfuscationStep.swift
new file mode 100644
index 0000000..b051317
--- /dev/null
+++ b/Sources/ConfidentialCore/Obfuscation/Techniques/Randomization/DataShuffler+DataObfuscationStep.swift
@@ -0,0 +1,62 @@
+import ConfidentialKit
+import Foundation
+
+extension Obfuscation.Randomization.DataShuffler: DataObfuscationStep {
+
+ func obfuscate(_ data: Data) throws -> Data {
+ let shuffledIndexes: [Int] = (0.. [UInt8] {
+ var result: [UInt8] = .init(repeating: .zero, count: bytes.count)
+ indexes.enumerated().forEach { oldIdx, newIdx in
+ result[newIdx] = bytes[oldIdx]
+ }
+
+ return result
+ }
+
+ @inline(__always)
+ func obfuscateIndexes(_ indexes: [Int]) -> (bytes: [UInt8], byteWidth: UInt8) {
+ let highestIndex = indexes.count - 1
+ switch highestIndex {
+ case _ where highestIndex <= UInt8.max:
+ return (
+ bytes: obfuscateIndexes(indexes, indexTransform: UInt8.init),
+ byteWidth: .init(UInt8.byteWidth)
+ )
+ case _ where highestIndex <= UInt16.max:
+ return (
+ bytes: obfuscateIndexes(indexes, indexTransform: UInt16.init),
+ byteWidth: .init(UInt16.byteWidth)
+ )
+ default:
+ return (
+ bytes: obfuscateIndexes(indexes, indexTransform: { $0 }),
+ byteWidth: .init(Int.byteWidth)
+ )
+ }
+ }
+
+ @inline(__always)
+ func obfuscateIndexes(_ indexes: [Int], indexTransform: (Int) -> I) -> [UInt8] {
+ indexes
+ .map(indexTransform)
+ .flatMap { withUnsafeBytes(of: $0 ^ .init(bytes: nonce.bytes), [UInt8].init) }
+ }
+}
diff --git a/Sources/ConfidentialCore/Parsing/Builders/Variadics.swift b/Sources/ConfidentialCore/Parsing/Builders/Variadics.swift
new file mode 100644
index 0000000..cb60177
--- /dev/null
+++ b/Sources/ConfidentialCore/Parsing/Builders/Variadics.swift
@@ -0,0 +1,38 @@
+import Parsing
+import SwiftSyntaxBuilder
+
+public struct ExpressibleAsCodeBlockItemFlatMap: Parser
+where
+P.Input == SourceSpecification,
+P.Output == [ExpressibleAsCodeBlockItem]
+{
+
+ private let parsers: [P]
+
+ init(_ parsers: [P]) {
+ self.parsers = parsers
+ }
+
+ public func parse(_ input: inout SourceSpecification) throws -> [ExpressibleAsCodeBlockItem] {
+ try parsers.flatMap { parser in
+ try parser.parse(&input)
+ }
+ }
+}
+
+extension ParserBuilder {
+
+ static func buildBlock