├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .spi.yml ├── .swiftlint.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── Package@swift-6.0.swift ├── README.md ├── Sources ├── ConfidentialCore │ ├── Coding │ │ └── DataEncoder.swift │ ├── Constants │ │ ├── Constants+Experimental.swift │ │ └── Constants.swift │ ├── Extensions │ │ ├── ConfidentialKit │ │ │ └── Obfuscation │ │ │ │ ├── Compression │ │ │ │ ├── CompressionAlgorithm+CaseIterable.swift │ │ │ │ ├── CompressionAlgorithm+CustomStringConvertible.swift │ │ │ │ └── CompressionAlgorithm+Name.swift │ │ │ │ └── Encryption │ │ │ │ ├── SymmetricEncryptionAlgorithm+CaseIterable.swift │ │ │ │ ├── SymmetricEncryptionAlgorithm+CustomStringConvertible.swift │ │ │ │ └── SymmetricEncryptionAlgorithm+Name.swift │ │ ├── Foundation │ │ │ └── Data │ │ │ │ └── Data+HexString.swift │ │ ├── Swift │ │ │ ├── Encodable │ │ │ │ └── Encodable+TypeErasure.swift │ │ │ └── FixedWidthInteger │ │ │ │ └── FixedWidthInteger+SecureRandom.swift │ │ └── SwiftSyntax │ │ │ └── TokenSyntax │ │ │ └── TokenSyntax+Tokens.swift │ ├── Models │ │ ├── Configuration │ │ │ ├── Configuration.Secret.Value+UnderlyingValue.swift │ │ │ └── Configuration.swift │ │ └── SourceFileSpec │ │ │ └── SourceFileSpec.swift │ ├── Obfuscation │ │ ├── DataObfuscationStep.swift │ │ ├── SourceObfuscator+ObfuscationStepResolver.swift │ │ ├── SourceObfuscator.swift │ │ └── Techniques │ │ │ ├── Compression │ │ │ └── DataCompressor+DataObfuscationStep.swift │ │ │ ├── Encryption │ │ │ └── DataCrypter+DataObfuscationStep.swift │ │ │ └── Randomization │ │ │ └── DataShuffler+DataObfuscationStep.swift │ ├── Parsing │ │ ├── ConvenienceTypealiases.swift │ │ ├── Parsers │ │ │ ├── CodeGeneration │ │ │ │ ├── DeobfuscateDataFunctionDeclParser.swift │ │ │ │ ├── ImportDeclParser.swift │ │ │ │ ├── NamespaceDeclParser.swift │ │ │ │ ├── NamespaceMembersParser.swift │ │ │ │ ├── Parsers+CodeGeneration.swift │ │ │ │ ├── SecretVariableDeclParser.swift │ │ │ │ └── SourceFileParser.swift │ │ │ ├── ModelTransform │ │ │ │ ├── AlgorithmParser.swift │ │ │ │ ├── CompressionTechniqueParser.swift │ │ │ │ ├── EncryptionTechniqueParser.swift │ │ │ │ ├── ObfuscationStepParser.swift │ │ │ │ ├── Parsers+ModelTransform.swift │ │ │ │ ├── RandomizationTechniqueParser.swift │ │ │ │ ├── SecretAccessModifierParser.swift │ │ │ │ ├── SecretNamespaceParser.swift │ │ │ │ ├── SecretsParser.swift │ │ │ │ └── SourceFileSpecParser.swift │ │ │ └── ParsingError.swift │ │ └── ParsersConvenienceInitializers.swift │ ├── SyntaxNodes │ │ ├── Attributes │ │ │ ├── ImplementationOnlyAttribute.swift │ │ │ └── InlineAlwaysAttribute.swift │ │ ├── Declarations │ │ │ ├── DeobfuscateDataFunctionDecl.swift │ │ │ ├── IfSwiftVersionOrFeatureDecl.swift │ │ │ └── SecretVariableDecl.swift │ │ └── Expressions │ │ │ ├── DataCompressorInitializerCallExpr.swift │ │ │ ├── DataCrypterInitializerCallExpr.swift │ │ │ ├── DataShufflerInitializerCallExpr.swift │ │ │ ├── DeobfuscateFunctionAccessExpr.swift │ │ │ └── InitializerCallExpr.swift │ └── SyntaxText │ │ └── SourceFileText.swift ├── ConfidentialKit │ ├── Coding │ │ └── DataDecoder.swift │ └── Obfuscation │ │ ├── DataDeobfuscationStep.swift │ │ ├── Obfuscation+Nonce.swift │ │ ├── Obfuscation+Secret.swift │ │ ├── Obfuscation+SupportedDataTypes.swift │ │ ├── Obfuscation.swift │ │ ├── PropertyWrappers │ │ └── Obfuscated.swift │ │ └── Techniques │ │ ├── Compression │ │ ├── CompressionAlgorithm.swift │ │ ├── DataCompressor.swift │ │ └── Obfuscation+Compression.swift │ │ ├── Encryption │ │ ├── DataCrypter.swift │ │ ├── Obfuscation+Encryption.swift │ │ └── SymmetricEncryptionAlgorithm.swift │ │ └── Randomization │ │ ├── DataShuffler.swift │ │ └── Obfuscation+Randomization.swift ├── ConfidentialKitMacros │ ├── Diagnostics.swift │ ├── Extensions │ │ └── SwiftSyntax │ │ │ ├── TokenKind │ │ │ └── TokenKind+Sendable.swift │ │ │ ├── TokenSyntax │ │ │ └── TokenSyntax+IsWildcardToken.swift │ │ │ └── VariableDeclSyntax │ │ │ └── VariableDeclSyntax+ConvenienceInitializers.swift │ ├── Macros │ │ └── Obfuscated │ │ │ ├── ObfuscatedMacro+Constants.swift │ │ │ ├── ObfuscatedMacro+Diagnostics.swift │ │ │ └── ObfuscatedMacro.swift │ ├── Plugin.swift │ └── SyntaxNodes │ │ ├── Declarations │ │ └── SecretProjectionVariableDecl.swift │ │ └── Expressions │ │ └── PreconditionFailureFunctionCallExpr.swift ├── ConfidentialUtils │ ├── Extensions │ │ ├── CryptoKit │ │ │ └── SymmetricKeySize │ │ │ │ └── SymmetricKeySize+ByteCount.swift │ │ ├── Foundation │ │ │ └── NSData │ │ │ │ └── NSData.CompressionAlgorithm+MagicNumbers.swift │ │ └── Swift │ │ │ ├── BinaryInteger │ │ │ └── BinaryInteger+Bytes.swift │ │ │ └── FixedWidthInteger │ │ │ └── FixedWidthInteger+ByteWidth.swift │ └── TypeInfo.swift ├── _ConfidentialKit │ └── Obfuscation │ │ └── Macros │ │ └── Obfuscated.swift └── swift-confidential │ ├── Errors │ └── RuntimeError.swift │ ├── Subcommands │ └── Obfuscate.swift │ └── SwiftConfidential.swift ├── Tests ├── .swiftlint.yml ├── ConfidentialCoreTests │ ├── Extensions │ │ ├── Foundation │ │ │ └── Data │ │ │ │ └── Data+HexStringTests.swift │ │ ├── Swift │ │ │ ├── Encodable │ │ │ │ └── Encodable+TypeErasureTests.swift │ │ │ └── FixedWidthInteger │ │ │ │ └── FixedWidthInteger+SecureRandomTests.swift │ │ └── SwiftSyntax │ │ │ └── TokenSyntax │ │ │ └── TokenSyntax+TokensTests.swift │ ├── Models │ │ ├── Configuration │ │ │ └── ConfigurationTests.swift │ │ └── SourceFileSpec │ │ │ └── SourceFileSpecTests.swift │ ├── Obfuscation │ │ ├── SourceObfuscator+ObfuscationStepResolverTests.swift │ │ ├── SourceObfuscatorTests.swift │ │ └── Techniques │ │ │ ├── Compression │ │ │ └── DataCompressor+DataObfuscationStepTests.swift │ │ │ ├── Encryption │ │ │ └── DataCrypter+DataObfuscationStepTests.swift │ │ │ └── Randomization │ │ │ └── DataShuffler+DataObfuscationStepTests.swift │ ├── Parsing │ │ └── Parsers │ │ │ ├── CodeGeneration │ │ │ ├── DeobfuscateDataFunctionDeclParserTests.swift │ │ │ ├── ImportDeclParserTests.swift │ │ │ ├── NamespaceDeclParserTests.swift │ │ │ ├── NamespaceMembersParserTests.swift │ │ │ ├── SecretVariableDeclParserTests.swift │ │ │ └── SourceFileParserTests.swift │ │ │ └── ModelTransform │ │ │ ├── AlgorithmParserTests.swift │ │ │ ├── CompressionTechniqueParserTests.swift │ │ │ ├── EncryptionTechniqueParserTests.swift │ │ │ ├── ObfuscationStepParserTests.swift │ │ │ ├── RandomizationTechniqueParserTests.swift │ │ │ ├── SecretAccessModifierParserTests.swift │ │ │ ├── SecretNamespaceParserTests.swift │ │ │ ├── SecretsParserTests.swift │ │ │ └── SourceFileSpecParserTests.swift │ ├── SyntaxText │ │ └── SourceFileTextTests.swift │ └── TestDoubles │ │ ├── Dummies │ │ └── ErrorDummy.swift │ │ ├── Spies │ │ ├── DataEncoderSpy.swift │ │ ├── EncodableSpy.swift │ │ ├── ObfuscationStepResolverSpy.swift │ │ ├── ObfuscationStepSpy.swift │ │ ├── ParameterlessClosureSpy.swift │ │ └── ParserSpy.swift │ │ └── Stubs │ │ ├── Configuration+StubFactory.swift │ │ ├── SourceFileSpec+StubFactory.swift │ │ └── SourceFileSpec.Secret+StubFactory.swift ├── ConfidentialKitMacrosTests │ └── Macros │ │ └── ObfuscatedMacroTests.swift ├── ConfidentialKitTests │ ├── Obfuscation │ │ ├── Obfuscation+SecretTests.swift │ │ ├── PropertyWrappers │ │ │ └── ObfuscatedTests.swift │ │ └── Techniques │ │ │ ├── Compression │ │ │ └── DataCompressorTests.swift │ │ │ ├── Encryption │ │ │ ├── DataCrypterTests.swift │ │ │ └── SymmetricEncryptionAlgorithmTests.swift │ │ │ └── Randomization │ │ │ └── DataShufflerTests.swift │ └── TestDoubles │ │ ├── Spies │ │ ├── DataDecoderSpy.swift │ │ └── DeobfuscateDataFuncSpy.swift │ │ └── Stubs │ │ ├── Nonce+StubFactory.swift │ │ ├── Secret+StubFactory.swift │ │ └── SingleValue+StubFactory.swift └── ConfidentialUtilsTests │ ├── Extensions │ ├── CryptoKit │ │ └── SymmetricKeySize │ │ │ └── SymmetricKeySize+ByteCountTests.swift │ ├── Foundation │ │ └── NSData │ │ │ └── NSData.CompressionAlgorithm+MagicNumbersTests.swift │ └── Swift │ │ ├── BinaryInteger │ │ └── BinaryInteger+BytesTests.swift │ │ └── FixedWidthInteger │ │ └── FixedWidthInteger+ByteWidthTests.swift │ └── TypeInfoTests.swift ├── codecov.yml ├── resources └── machoview-cstring-literals.png └── scripts ├── commons └── common.sh ├── generate_release_artifacts.sh ├── run_tests.sh ├── templates └── artifactbundle-info.json.template └── upgrade_bash.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'release/**' 8 | paths-ignore: 9 | - '.gitignore' 10 | - '.spi.yml' 11 | - 'CODE_OF_CONDUCT.md' 12 | - 'LICENSE' 13 | - 'README.md' 14 | pull_request: 15 | branches-ignore: 16 | - 'bugfix/**' 17 | - 'feature/**' 18 | - 'topic/**' 19 | paths-ignore: 20 | - '.gitignore' 21 | - '.spi.yml' 22 | - 'CODE_OF_CONDUCT.md' 23 | - 'LICENSE' 24 | - 'README.md' 25 | workflow_dispatch: 26 | 27 | concurrency: 28 | group: ci-${{ github.ref }} 29 | cancel-in-progress: true 30 | 31 | jobs: 32 | lint: 33 | name: Lint 34 | runs-on: macos-13 35 | 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | 40 | - name: Lint Swift Code 41 | run: swiftlint lint --strict --reporter github-actions-logging 42 | 43 | - name: Lint Bash Code 44 | uses: ludeeus/action-shellcheck@master 45 | with: 46 | scandir: './Scripts' 47 | ignore_paths: Templates 48 | version: v0.10.0 49 | env: 50 | SHELLCHECK_OPTS: -e SC1009 -e SC1072 -e SC1073 -e SC1090 -e SC1091 51 | 52 | tests: 53 | name: Tests (Xcode ${{ matrix.xcode }}${{ matrix.upload_coverage && ', Upload Coverage Report' || '' }}) 54 | needs: [lint] 55 | runs-on: macos-${{ matrix.macos }} 56 | env: 57 | DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer" 58 | strategy: 59 | matrix: 60 | include: 61 | - macos: 14 62 | xcode: '15.2' # Swift 5.9 63 | upload_coverage: false 64 | - macos: 14 65 | xcode: '15.4' # Swift 5.10 66 | upload_coverage: true 67 | 68 | steps: 69 | - name: Runner Overview 70 | run: system_profiler SPHardwareDataType SPSoftwareDataType SPDeveloperToolsDataType 71 | 72 | - name: Checkout 73 | uses: actions/checkout@v4 74 | 75 | - name: Upgrade Bash 76 | run: make bash 77 | 78 | - name: Run Tests 79 | run: make tests 80 | 81 | - name: Upload Code Coverage Report 82 | if: ${{ matrix.upload_coverage }} 83 | uses: codecov/codecov-action@v5 84 | with: 85 | token: ${{ secrets.CODECOV_TOKEN }} 86 | directory: ./.coverage/ 87 | fail_ci_if_error: true 88 | verbose: true 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | 3 | .DS_Store 4 | 5 | ### SwiftPM ### 6 | 7 | /.build 8 | /Packages 9 | /.swiftpm 10 | 11 | ### Xcode ### 12 | 13 | /*.xcodeproj 14 | xcuserdata/ 15 | 16 | ### Other ### 17 | 18 | # Code coverage reports 19 | /.coverage 20 | 21 | # Release artifacts 22 | /.release 23 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [ConfidentialKit] -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | #################### 2 | # PATHS # 3 | #################### 4 | 5 | included: 6 | - Package.swift 7 | - Sources 8 | - Tests 9 | 10 | #################### 11 | # RULES STATE # 12 | #################### 13 | 14 | opt_in_rules: 15 | - closure_body_length 16 | - closure_end_indentation 17 | - closure_spacing 18 | - collection_alignment 19 | - contains_over_filter_count 20 | - contains_over_filter_is_empty 21 | - contains_over_first_not_nil 22 | - contains_over_range_nil_comparison 23 | - convenience_type 24 | - discouraged_optional_boolean 25 | - empty_collection_literal 26 | - empty_count 27 | - enum_case_associated_values_count 28 | - extension_access_modifier 29 | - empty_string 30 | - fallthrough 31 | - fatal_error_message 32 | - file_name_no_space 33 | - first_where 34 | - flatmap_over_map_reduce 35 | - force_unwrapping 36 | - identical_operands 37 | - implicitly_unwrapped_optional 38 | - last_where 39 | - legacy_multiple 40 | - legacy_random 41 | - literal_expression_end_indentation 42 | - lower_acl_than_parent 43 | - modifier_order 44 | - multiline_arguments 45 | - multiline_arguments_brackets 46 | - multiline_function_chains 47 | - multiline_literal_brackets 48 | - multiline_parameters 49 | - multiline_parameters_brackets 50 | - operator_usage_whitespace 51 | - optional_enum_case_matching 52 | - pattern_matching_keywords 53 | - prefer_self_type_over_type_of_self 54 | - prefer_zero_over_explicit_init 55 | - reduce_into 56 | - redundant_nil_coalescing 57 | - sorted_first_last 58 | - sorted_imports 59 | - toggle_bool 60 | - unavailable_function 61 | - untyped_error_in_catch 62 | - unneeded_parentheses_in_closure_argument 63 | - vertical_parameter_alignment_on_call 64 | - vertical_whitespace_closing_braces 65 | - yoda_condition 66 | 67 | #################### 68 | # RULES CONFIG # 69 | #################### 70 | 71 | ### Idiomatic ### 72 | generic_type_name: 73 | max_length: 40 74 | type_name: 75 | excluded: 76 | - C 77 | 78 | ### Metrics ### 79 | closure_body_length: 80 | warning: 25 81 | file_length: 82 | warning: 300 83 | ignore_comment_only_lines: true 84 | line_length: 85 | ignores_comments: true 86 | nesting: 87 | type_level: 3 88 | 89 | ### Style ### 90 | opening_brace: 91 | ignore_multiline_function_signatures: true 92 | trailing_whitespace: 93 | ignores_empty_lines: true 94 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | .PHONY: help bash release-artifacts release tests 4 | 5 | help: 6 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 7 | 8 | bash: ## Upgrade Bash to the latest version 9 | @./scripts/upgrade_bash.sh 10 | 11 | release-artifacts: ## Generate release artifacts 12 | @test -z "$$(git status --porcelain)" || { echo "Aborting due to uncommitted changes" >&2; exit 1; } 13 | @./scripts/generate_release_artifacts.sh $(version) 14 | 15 | release: release-artifacts ## Generate release artifacts and tag the release with given version 16 | @git tag -s $(version) -m "Release $(version)" 17 | @git push origin $(version) 18 | 19 | tests: ## Run package tests 20 | @./scripts/run_tests.sh 21 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-argument-parser", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-argument-parser.git", 7 | "state" : { 8 | "revision" : "41982a3656a71c768319979febd796c6fd111d5c", 9 | "version" : "1.5.0" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-case-paths", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/pointfreeco/swift-case-paths", 16 | "state" : { 17 | "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81", 18 | "version" : "1.0.0" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-parsing", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/pointfreeco/swift-parsing.git", 25 | "state" : { 26 | "revision" : "3432cb81164dd3d69a75d0d63205be5fbae2c34b", 27 | "version" : "0.14.1" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-syntax", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/swiftlang/swift-syntax.git", 34 | "state" : { 35 | "revision" : "0687f71944021d616d34d922343dcef086855920", 36 | "version" : "600.0.1" 37 | } 38 | }, 39 | { 40 | "identity" : "xctest-dynamic-overlay", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 43 | "state" : { 44 | "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631", 45 | "version" : "1.0.2" 46 | } 47 | }, 48 | { 49 | "identity" : "yams", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/jpsim/Yams.git", 52 | "state" : { 53 | "revision" : "b4b8042411dc7bbb696300a34a4bf3ba1b7ad19b", 54 | "version" : "5.3.1" 55 | } 56 | } 57 | ], 58 | "version" : 2 59 | } 60 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | var package = Package( 6 | name: "swift-confidential", 7 | platforms: [ 8 | .iOS(.v13), 9 | .macOS(.v10_15), 10 | .macCatalyst(.v15), 11 | .watchOS(.v8), 12 | .tvOS(.v15), 13 | .visionOS(.v1) 14 | ], 15 | products: [ 16 | .library( 17 | name: "ConfidentialKit", 18 | targets: ["ConfidentialKit"] 19 | ) 20 | ], 21 | targets: [ 22 | // Client Library 23 | .target( 24 | name: "ConfidentialKit", 25 | dependencies: ["ConfidentialUtils"] 26 | ), 27 | 28 | // Utils 29 | .target(name: "ConfidentialUtils"), 30 | 31 | // Tests 32 | .testTarget( 33 | name: "ConfidentialKitTests", 34 | dependencies: ["ConfidentialKit"] 35 | ), 36 | .testTarget( 37 | name: "ConfidentialUtilsTests", 38 | dependencies: ["ConfidentialUtils"] 39 | ) 40 | ], 41 | swiftLanguageVersions: [.v5] 42 | ) 43 | 44 | #if os(macOS) 45 | package.dependencies.append(contentsOf: [ 46 | .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), 47 | .package(url: "https://github.com/swiftlang/swift-syntax.git", "509.1.1"..<"601.0.0"), 48 | .package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.14.1"), 49 | .package(url: "https://github.com/jpsim/Yams.git", from: "5.3.1") 50 | ]) 51 | package.targets.append(contentsOf: [ 52 | // Core Module 53 | .target( 54 | name: "ConfidentialCore", 55 | dependencies: [ 56 | "ConfidentialKit", 57 | "ConfidentialUtils", 58 | .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), 59 | .product(name: "Parsing", package: "swift-parsing") 60 | ] 61 | ), 62 | 63 | // CLI Tool 64 | .executableTarget( 65 | name: "swift-confidential", 66 | dependencies: [ 67 | "ConfidentialCore", 68 | "Yams", 69 | .product(name: "ArgumentParser", package: "swift-argument-parser") 70 | ] 71 | ), 72 | 73 | // Tests 74 | .testTarget( 75 | name: "ConfidentialCoreTests", 76 | dependencies: ["ConfidentialCore"] 77 | ) 78 | ]) 79 | #endif 80 | -------------------------------------------------------------------------------- /Package@swift-6.0.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | 3 | import CompilerPluginSupport 4 | import PackageDescription 5 | 6 | var package = Package( 7 | name: "swift-confidential", 8 | platforms: [ 9 | .iOS(.v13), 10 | .macOS(.v10_15), 11 | .macCatalyst(.v15), 12 | .watchOS(.v8), 13 | .tvOS(.v15), 14 | .visionOS(.v1) 15 | ], 16 | products: [ 17 | .library( 18 | name: "ConfidentialKit", 19 | targets: ["ConfidentialKit"] 20 | ), 21 | .library( 22 | name: "_ConfidentialKit", 23 | targets: ["_ConfidentialKit"] 24 | ) 25 | ], 26 | dependencies: [ 27 | .package(url: "https://github.com/swiftlang/swift-syntax.git", "509.1.1"..<"601.0.0") 28 | ], 29 | targets: [ 30 | // Client Library 31 | .target( 32 | name: "ConfidentialKit", 33 | dependencies: ["ConfidentialUtils"] 34 | ), 35 | .target( 36 | name: "_ConfidentialKit", 37 | dependencies: [ 38 | "ConfidentialKit", 39 | "ConfidentialKitMacros" 40 | ] 41 | ), 42 | 43 | // Macros 44 | .macro( 45 | name: "ConfidentialKitMacros", 46 | dependencies: [ 47 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), 48 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax") 49 | ] 50 | ), 51 | 52 | // Utils 53 | .target(name: "ConfidentialUtils"), 54 | 55 | // Tests 56 | .testTarget( 57 | name: "ConfidentialKitTests", 58 | dependencies: ["ConfidentialKit"] 59 | ), 60 | .testTarget( 61 | name: "ConfidentialKitMacrosTests", 62 | dependencies: [ 63 | "ConfidentialKitMacros", 64 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax") 65 | ] 66 | ), 67 | .testTarget( 68 | name: "ConfidentialUtilsTests", 69 | dependencies: ["ConfidentialUtils"] 70 | ) 71 | ], 72 | swiftLanguageModes: [.v5, .v6] 73 | ) 74 | 75 | #if os(macOS) 76 | package.dependencies.append(contentsOf: [ 77 | .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), 78 | .package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.14.1"), 79 | .package(url: "https://github.com/jpsim/Yams.git", from: "5.3.1") 80 | ]) 81 | package.targets.append(contentsOf: [ 82 | // Core Module 83 | .target( 84 | name: "ConfidentialCore", 85 | dependencies: [ 86 | "ConfidentialKit", 87 | "ConfidentialUtils", 88 | .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), 89 | .product(name: "Parsing", package: "swift-parsing") 90 | ] 91 | ), 92 | 93 | // CLI Tool 94 | .executableTarget( 95 | name: "swift-confidential", 96 | dependencies: [ 97 | "ConfidentialCore", 98 | "Yams", 99 | .product(name: "ArgumentParser", package: "swift-argument-parser") 100 | ] 101 | ), 102 | 103 | // Tests 104 | .testTarget( 105 | name: "ConfidentialCoreTests", 106 | dependencies: ["ConfidentialCore"] 107 | ) 108 | ]) 109 | #endif 110 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Coding/DataEncoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol DataEncoder { 4 | func encode(_ value: E) throws -> Data 5 | } 6 | 7 | extension JSONEncoder: DataEncoder {} 8 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Constants/Constants+Experimental.swift: -------------------------------------------------------------------------------- 1 | extension C.Code.Generation { 2 | 3 | enum Experimental { 4 | static let confidentialKitModuleName: String = "_ConfidentialKit" 5 | 6 | static let obfuscatedMacroFullyQualifiedName: String = "\(confidentialKitModuleName).Obfuscated" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Constants/Constants.swift: -------------------------------------------------------------------------------- 1 | enum C { 2 | 3 | enum Code { 4 | 5 | enum Format { 6 | static let indentWidth: Int = 4 7 | } 8 | 9 | enum Generation { 10 | static let confidentialKitModuleName: String = "ConfidentialKit" 11 | static let foundationModuleName: String = "Foundation" 12 | 13 | static let deobfuscateDataFuncName: String = "deobfuscateData" 14 | static let deobfuscateDataFuncDataParamName: String = "data" 15 | static let deobfuscateDataFuncNonceParamName: String = "nonce" 16 | } 17 | } 18 | 19 | enum Parsing { 20 | 21 | enum Keywords { 22 | static let compress: String = "compress" 23 | static let encrypt: String = "encrypt" 24 | static let shuffle: String = "shuffle" 25 | static let using: String = "using" 26 | static let create: String = "create" 27 | static let extend: String = "extend" 28 | static let from: String = "from" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CaseIterable.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | extension Obfuscation.Compression.CompressionAlgorithm: Swift.CaseIterable { 5 | 6 | public static var allCases: [Self] { 7 | [ 8 | .lzfse, 9 | .lz4, 10 | .lzma, 11 | .zlib 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+CustomStringConvertible.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | extension Obfuscation.Compression.CompressionAlgorithm: Swift.CustomStringConvertible { 4 | 5 | public var description: String { 6 | switch self { 7 | case .lzfse: 8 | return "lzfse" 9 | case .lz4: 10 | return "lz4" 11 | case .lzma: 12 | return "lzma" 13 | case .zlib: 14 | return "zlib" 15 | @unknown default: 16 | return "unknown" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Compression/CompressionAlgorithm+Name.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | extension Obfuscation.Compression.CompressionAlgorithm { 4 | 5 | var name: String { 6 | switch self { 7 | case .lzfse: 8 | return "lzfse" 9 | case .lz4: 10 | return "lz4" 11 | case .lzma: 12 | return "lzma" 13 | case .zlib: 14 | return "zlib" 15 | @unknown default: 16 | return "unknown" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CaseIterable.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm: Swift.CaseIterable { 4 | 5 | public static var allCases: [Self] { 6 | [ 7 | .aes128GCM, 8 | .aes192GCM, 9 | .aes256GCM, 10 | .chaChaPoly 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+CustomStringConvertible.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm: Swift.CustomStringConvertible { 4 | 5 | public var description: String { 6 | switch self { 7 | case .aes128GCM: 8 | return "aes-128-gcm" 9 | case .aes192GCM: 10 | return "aes-192-gcm" 11 | case .aes256GCM: 12 | return "aes-256-gcm" 13 | case .chaChaPoly: 14 | return "chacha20-poly" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/ConfidentialKit/Obfuscation/Encryption/SymmetricEncryptionAlgorithm+Name.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm { 4 | 5 | var name: String { rawValue } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/Foundation/Data/Data+HexString.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Data { 4 | 5 | struct HexEncodingOptions: OptionSet { 6 | static let upperCase: Self = .init(rawValue: 1 << 0) 7 | static let numericLiteral: Self = .init(rawValue: 1 << 1) 8 | 9 | let rawValue: Int 10 | } 11 | 12 | func hexEncodedStringComponents(options: HexEncodingOptions = []) -> [String] { 13 | var format = options.contains(.upperCase) ? "%02hhX" : "%02hhx" 14 | if options.contains(.numericLiteral) { 15 | format = "0x\(format)" 16 | } 17 | 18 | return map { String(format: format, $0) } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/Swift/Encodable/Encodable+TypeErasure.swift: -------------------------------------------------------------------------------- 1 | struct AnyEncodable: Encodable { 2 | 3 | private let encode: (Encoder) throws -> Void 4 | 5 | init(_ encodable: any Encodable) { 6 | self.encode = encodable.encode(to:) 7 | } 8 | 9 | func encode(to encoder: Encoder) throws { 10 | try encode(encoder) 11 | } 12 | } 13 | 14 | extension Encodable { 15 | 16 | func eraseToAnyEncodable() -> AnyEncodable { 17 | .init(self) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/Swift/FixedWidthInteger/FixedWidthInteger+SecureRandom.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension FixedWidthInteger { 4 | 5 | typealias SecureRandomNumberSource = (inout [UInt8]) -> OSStatus 6 | 7 | static func secureRandom( 8 | using source: SecureRandomNumberSource = { 9 | SecRandomCopyBytes(kSecRandomDefault, $0.count, &$0) 10 | } 11 | ) throws -> Self { 12 | let count = MemoryLayout.stride 13 | var bytes = [UInt8](repeating: .zero, count: count) 14 | let status = source(&bytes) 15 | guard status == errSecSuccess else { 16 | let errorDescription = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error" 17 | throw NSError( 18 | domain: NSOSStatusErrorDomain, 19 | code: .init(status), 20 | userInfo: [NSLocalizedDescriptionKey: errorDescription] 21 | ) 22 | } 23 | 24 | return bytes.withUnsafeBytes { $0.load(as: Self.self) } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Extensions/SwiftSyntax/TokenSyntax/TokenSyntax+Tokens.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension TokenSyntax { 4 | 5 | static func atSignToken( 6 | leadingNewlines: Int, 7 | followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth 8 | ) -> Self { 9 | makeToken( 10 | .atSignToken(), 11 | withLeadingNewlines: leadingNewlines, 12 | followedByLeadingSpaces: leadingSpaces 13 | ) 14 | } 15 | 16 | static func periodToken( 17 | leadingNewlines: Int, 18 | followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth 19 | ) -> Self { 20 | makeToken( 21 | .periodToken(), 22 | withLeadingNewlines: leadingNewlines, 23 | followedByLeadingSpaces: leadingSpaces 24 | ) 25 | } 26 | 27 | static func rightParenToken( 28 | leadingNewlines: Int, 29 | followedByLeadingSpaces leadingSpaces: Int = C.Code.Format.indentWidth 30 | ) -> Self { 31 | makeToken( 32 | .rightParenToken(), 33 | withLeadingNewlines: leadingNewlines, 34 | followedByLeadingSpaces: leadingSpaces 35 | ) 36 | } 37 | } 38 | 39 | private extension TokenSyntax { 40 | 41 | static func makeToken( 42 | _ token: Self, 43 | withLeadingNewlines leadingNewlines: Int, 44 | followedByLeadingSpaces leadingSpaces: Int 45 | ) -> Self { 46 | guard leadingNewlines > .zero else { 47 | return token 48 | } 49 | 50 | return token.with( 51 | \.leadingTrivia, 52 | .newlines(leadingNewlines) 53 | .appending(Trivia.spaces(leadingSpaces)) 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Models/Configuration/Configuration.Secret.Value+UnderlyingValue.swift: -------------------------------------------------------------------------------- 1 | extension Configuration.Secret.Value { 2 | 3 | var underlyingValue: AnyEncodable { 4 | switch self { 5 | case let .array(value): 6 | return value.eraseToAnyEncodable() 7 | case let .singleValue(value): 8 | return value.eraseToAnyEncodable() 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Models/Configuration/Configuration.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | // swiftlint:disable discouraged_optional_boolean 4 | package struct Configuration: Equatable, Decodable { 5 | var algorithm: ArraySlice 6 | var defaultAccessModifier: String? 7 | var defaultNamespace: String? 8 | var experimentalMode: Bool? 9 | var internalImport: Bool? 10 | var secrets: ArraySlice 11 | } 12 | // swiftlint:enable discouraged_optional_boolean 13 | 14 | package extension Configuration { 15 | 16 | init(from decoder: Decoder) throws { 17 | let container = try decoder.container(keyedBy: CodingKeys.self) 18 | let internalImport = if container.contains(.internalImport) { 19 | try container.decode(Bool.self, forKey: .internalImport) 20 | } else { 21 | // For backward compatibility: 22 | try container.decodeIfPresent(Bool.self, forKey: .implementationOnlyImport) 23 | } 24 | self = .init( 25 | algorithm: try container.decode([String].self, forKey: .algorithm)[...], 26 | defaultAccessModifier: try container.decodeIfPresent(String.self, forKey: .defaultAccessModifier), 27 | defaultNamespace: try container.decodeIfPresent(String.self, forKey: .defaultNamespace), 28 | experimentalMode: try container.decodeIfPresent(Bool.self, forKey: .experimentalMode), 29 | internalImport: internalImport, 30 | secrets: try container.decode([Secret].self, forKey: .secrets)[...] 31 | ) 32 | } 33 | } 34 | 35 | extension Configuration { 36 | 37 | var isExperimentalModeEnabled: Bool { experimentalMode ?? false } 38 | var isInternalImportEnabled: Bool { internalImport ?? false } 39 | } 40 | 41 | extension Configuration { 42 | 43 | struct Secret: Hashable, Decodable { 44 | let name: String 45 | let value: Value 46 | let namespace: String? 47 | let accessModifier: String? 48 | } 49 | } 50 | 51 | extension Configuration.Secret { 52 | 53 | enum Value: Hashable, Decodable { 54 | 55 | typealias DataTypes = Obfuscation.SupportedDataTypes 56 | 57 | case array(DataTypes.Array) 58 | case singleValue(DataTypes.SingleValue) 59 | 60 | init(from decoder: Decoder) throws { 61 | let container = try decoder.singleValueContainer() 62 | if let value = try? container.decode(DataTypes.Array.self) { 63 | self = .array(value) 64 | } else { 65 | let value = try container.decode(DataTypes.SingleValue.self) 66 | self = .singleValue(value) 67 | } 68 | } 69 | } 70 | } 71 | 72 | private extension Configuration { 73 | 74 | enum CodingKeys: String, CodingKey { 75 | case algorithm 76 | case defaultAccessModifier 77 | case defaultNamespace 78 | case experimentalMode 79 | case implementationOnlyImport 80 | case internalImport 81 | case secrets 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Models/SourceFileSpec/SourceFileSpec.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | package struct SourceFileSpec: Equatable { 5 | var algorithm: Algorithm 6 | var experimentalMode: Bool 7 | var internalImport: Bool 8 | var secrets: Secrets 9 | } 10 | 11 | package extension SourceFileSpec { 12 | 13 | typealias Algorithm = ArraySlice 14 | 15 | struct ObfuscationStep: Equatable { 16 | let technique: Technique 17 | } 18 | 19 | struct Secret { 20 | let accessModifier: AccessModifier 21 | let name: String 22 | var data: Data 23 | let nonce: Obfuscation.Nonce 24 | let dataProjectionAttribute: DataProjectionAttribute 25 | } 26 | 27 | struct Secrets: Hashable { 28 | 29 | package typealias Secret = SourceFileSpec.Secret 30 | 31 | private var secrets: [Secret.Namespace: ArraySlice] 32 | 33 | var namespaces: Dictionary>.Keys { 34 | secrets.keys 35 | } 36 | 37 | init(_ secrets: [Secret.Namespace: ArraySlice]) { 38 | self.secrets = secrets 39 | } 40 | 41 | subscript(namespace: Secret.Namespace) -> ArraySlice? { 42 | _read { yield self.secrets[namespace] } 43 | _modify { yield &self.secrets[namespace] } 44 | } 45 | } 46 | } 47 | 48 | package extension SourceFileSpec.ObfuscationStep { 49 | 50 | enum Technique: Hashable { 51 | case compression(algorithm: Obfuscation.Compression.CompressionAlgorithm) 52 | case encryption(algorithm: Obfuscation.Encryption.SymmetricEncryptionAlgorithm) 53 | case randomization 54 | } 55 | } 56 | 57 | extension SourceFileSpec.Secret: Hashable { 58 | 59 | package static func == (lhs: Self, rhs: Self) -> Bool { 60 | lhs.accessModifier == rhs.accessModifier && 61 | lhs.name == rhs.name && 62 | lhs.data == rhs.data && 63 | lhs.nonce == rhs.nonce 64 | } 65 | 66 | package func hash(into hasher: inout Hasher) { 67 | hasher.combine(accessModifier) 68 | hasher.combine(name) 69 | hasher.combine(data) 70 | hasher.combine(nonce) 71 | } 72 | } 73 | 74 | extension SourceFileSpec.Secret { 75 | 76 | enum AccessModifier: String, CaseIterable { 77 | case `internal` 78 | case `package` 79 | case `public` 80 | } 81 | 82 | struct DataProjectionAttribute { 83 | 84 | typealias Argument = (label: String?, value: String) 85 | 86 | let name: String 87 | let arguments: [Argument] 88 | let isPropertyWrapper: Bool 89 | } 90 | 91 | package enum Namespace: Hashable { 92 | case create(identifier: String) 93 | case extend(identifier: String, moduleName: String? = nil) 94 | } 95 | } 96 | 97 | extension SourceFileSpec.Secrets: Collection { 98 | 99 | package typealias Element = Dictionary>.Element 100 | package typealias Index = Dictionary>.Index 101 | 102 | package var startIndex: Index { 103 | secrets.startIndex 104 | } 105 | 106 | package var endIndex: Index { 107 | secrets.endIndex 108 | } 109 | 110 | package subscript(position: Index) -> Element { 111 | secrets[position] 112 | } 113 | 114 | package func index(after index: Index) -> Index { 115 | secrets.index(after: index) 116 | } 117 | } 118 | 119 | extension SourceFileSpec.Secrets: ExpressibleByDictionaryLiteral { 120 | 121 | package init(dictionaryLiteral elements: (Secret.Namespace, ArraySlice)...) { 122 | self.init(.init(uniqueKeysWithValues: elements)) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Obfuscation/DataObfuscationStep.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | protocol DataObfuscationStep { 5 | func obfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data 6 | } 7 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Obfuscation/SourceObfuscator+ObfuscationStepResolver.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | protocol DataObfuscationStepResolver { 4 | 5 | typealias Technique = SourceFileSpec.ObfuscationStep.Technique 6 | 7 | func obfuscationStep(for technique: Technique) -> any DataObfuscationStep 8 | } 9 | 10 | extension SourceObfuscator { 11 | 12 | struct ObfuscationStepResolver: DataObfuscationStepResolver { 13 | 14 | @inline(__always) 15 | func obfuscationStep(for technique: Technique) -> any DataObfuscationStep { 16 | switch technique { 17 | case let .compression(algorithm): 18 | return Obfuscation.Compression.DataCompressor(algorithm: algorithm) 19 | case let .encryption(algorithm): 20 | return Obfuscation.Encryption.DataCrypter(algorithm: algorithm) 21 | case .randomization: 22 | return Obfuscation.Randomization.DataShuffler() 23 | } 24 | } 25 | } 26 | } 27 | 28 | package extension SourceObfuscator { 29 | 30 | init() { 31 | self.init(obfuscationStepResolver: ObfuscationStepResolver()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Obfuscation/SourceObfuscator.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | package struct SourceObfuscator { 5 | 6 | private let obfuscationStepResolver: DataObfuscationStepResolver 7 | 8 | init(obfuscationStepResolver: DataObfuscationStepResolver) { 9 | self.obfuscationStepResolver = obfuscationStepResolver 10 | } 11 | 12 | package func obfuscate(_ source: inout SourceFileSpec) throws { 13 | guard !source.secrets.isEmpty else { 14 | return 15 | } 16 | 17 | let obfuscateData = obfuscationFunc(given: source.algorithm) 18 | try source.secrets.namespaces.forEach { namespace in 19 | guard let secrets = source.secrets[namespace] else { 20 | fatalError("Unexpected source file spec integrity violation") 21 | } 22 | 23 | source.secrets[namespace] = try secrets.map { secret in 24 | var secret = secret 25 | secret.data = try obfuscateData(secret.data, secret.nonce) 26 | return secret 27 | }[...] 28 | } 29 | } 30 | } 31 | 32 | private extension SourceObfuscator { 33 | 34 | typealias Algorithm = SourceFileSpec.Algorithm 35 | typealias Nonce = Obfuscation.Nonce 36 | typealias ObfuscationFunc = (Data, Nonce) throws -> Data 37 | 38 | @inline(__always) 39 | func obfuscationFunc(given algorithm: Algorithm) -> ObfuscationFunc { 40 | algorithm 41 | .map(\.technique) 42 | .map(obfuscationStepResolver.obfuscationStep(for:)) 43 | .reduce({ data, _ in data }, { partialFunc, step in 44 | return { 45 | try step.obfuscate(partialFunc($0, $1), nonce: $1) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Obfuscation/Techniques/Compression/DataCompressor+DataObfuscationStep.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | extension Obfuscation.Compression.DataCompressor: DataObfuscationStep { 5 | 6 | func obfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 7 | var obfuscatedData = Data( 8 | referencing: try NSData(data: data).compressed(using: algorithm) 9 | ) 10 | 11 | if algorithm.headerMagicByteCount > .zero { 12 | let magicByteCount = algorithm.headerMagicByteCount 13 | let magicBytes = obfuscatedData.prefix(magicByteCount) 14 | let obfuscatedMagicBytes = obfuscateMagicBytes( 15 | magicBytes, 16 | nonce: nonce 17 | ) 18 | obfuscatedData.replaceSubrange(.. .zero { 21 | let magicByteCount = algorithm.footerMagicByteCount 22 | let magicBytes = obfuscatedData.suffix(magicByteCount) 23 | let obfuscatedMagicBytes = obfuscateMagicBytes( 24 | magicBytes, 25 | nonce: nonce.byteSwapped 26 | ) 27 | let endIndex = obfuscatedData.endIndex 28 | let startIndex = obfuscatedData.index(endIndex, offsetBy: -magicByteCount) 29 | obfuscatedData.replaceSubrange(startIndex..( 40 | _ magicBytes: Bytes, 41 | nonce: Obfuscation.Nonce 42 | ) -> Data where Bytes.Element == UInt8, Bytes.Index == Int { 43 | let nonceBytes = nonce.bytes 44 | let nonceByteWidth = nonceBytes.count 45 | let obfuscatedBytes = magicBytes.enumerated().map { index, byte in 46 | byte ^ nonceBytes[index % nonceByteWidth] 47 | } 48 | 49 | return .init(obfuscatedBytes) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Obfuscation/Techniques/Encryption/DataCrypter+DataObfuscationStep.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import CryptoKit 3 | import Foundation 4 | 5 | extension Obfuscation.Encryption.DataCrypter: DataObfuscationStep { 6 | 7 | func obfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 8 | let key = SymmetricKey(size: algorithm.keySize) 9 | 10 | var obfuscatedData = try encryptData(data, using: key) 11 | 12 | let obfuscatedKeyData = obfuscateKeyData( 13 | key.withUnsafeBytes(Data.init(_:)), 14 | nonce: nonce 15 | ) 16 | let magicBit: UInt64 = 1 << 7 17 | if nonce & magicBit == 0 { 18 | obfuscatedData.insert(contentsOf: obfuscatedKeyData, at: .zero) 19 | } else { 20 | obfuscatedData.append(contentsOf: obfuscatedKeyData) 21 | } 22 | 23 | return obfuscatedData 24 | } 25 | } 26 | 27 | private extension Obfuscation.Encryption.DataCrypter { 28 | 29 | @inline(__always) 30 | func encryptData(_ data: Data, using key: SymmetricKey) throws -> Data { 31 | switch algorithm { 32 | case .aes128GCM, .aes192GCM, .aes256GCM: 33 | let sealedBox = try AES.GCM.seal(data, using: key, nonce: .init()) 34 | /* 35 | As the official documentation states, when we use the nonce of the 36 | default size of 12 bytes, the combined representation is available, 37 | hence the use of force unwrap. 38 | See https://developer.apple.com/documentation/cryptokit/aes/gcm/sealedbox 39 | for more details. 40 | */ 41 | return sealedBox.combined! // swiftlint:disable:this force_unwrapping 42 | case .chaChaPoly: 43 | let sealedBox = try ChaChaPoly.seal(data, using: key, nonce: .init()) 44 | return sealedBox.combined 45 | } 46 | } 47 | 48 | @inline(__always) 49 | func obfuscateKeyData(_ keyData: Data, nonce: Obfuscation.Nonce) -> Data { 50 | let nonceBytes = nonce.bytes 51 | let nonceByteWidth = nonceBytes.count 52 | let obfuscatedKeyBytes = keyData.enumerated().map { index, byte in 53 | byte ^ nonceBytes[index % nonceByteWidth] 54 | } 55 | 56 | return .init(obfuscatedKeyBytes) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Obfuscation/Techniques/Randomization/DataShuffler+DataObfuscationStep.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | extension Obfuscation.Randomization.DataShuffler: DataObfuscationStep { 5 | 6 | func obfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 7 | let shuffledIndexes: [Int] = (0.. [UInt8] { 26 | var result: [UInt8] = .init(repeating: .zero, count: bytes.count) 27 | indexes.enumerated().forEach { oldIdx, newIdx in 28 | result[newIdx] = bytes[oldIdx] 29 | } 30 | 31 | return result 32 | } 33 | 34 | @inline(__always) 35 | func obfuscateIndexes( 36 | _ indexes: [Int], 37 | nonce: Obfuscation.Nonce 38 | ) -> (bytes: [UInt8], byteWidth: UInt8) { 39 | let highestIndex = indexes.count - 1 40 | switch highestIndex { 41 | case _ where highestIndex <= UInt8.max: 42 | return ( 43 | bytes: obfuscateIndexes(indexes, indexTransform: UInt8.init, nonce: nonce), 44 | byteWidth: .init(UInt8.byteWidth) 45 | ) 46 | case _ where highestIndex <= UInt16.max: 47 | return ( 48 | bytes: obfuscateIndexes(indexes, indexTransform: UInt16.init, nonce: nonce), 49 | byteWidth: .init(UInt16.byteWidth) 50 | ) 51 | default: 52 | return ( 53 | bytes: obfuscateIndexes(indexes, indexTransform: { $0 }, nonce: nonce), 54 | byteWidth: .init(Int.byteWidth) 55 | ) 56 | } 57 | } 58 | 59 | @inline(__always) 60 | func obfuscateIndexes( 61 | _ indexes: [Int], 62 | indexTransform: (Int) -> I, 63 | nonce: Obfuscation.Nonce 64 | ) -> [UInt8] { 65 | indexes 66 | .map(indexTransform) 67 | .flatMap { withUnsafeBytes(of: $0 ^ .init(bytes: nonce.bytes), [UInt8].init) } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/ConvenienceTypealiases.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | package typealias Parsers = Parsing.Parsers 4 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/CodeGeneration/NamespaceDeclParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | import SwiftSyntax 3 | 4 | struct NamespaceDeclParser: Parser 5 | where 6 | MembersParser.Input == ArraySlice, 7 | MembersParser.Output == [MemberBlockItemSyntax], 8 | DeobfuscateDataFunctionDeclParser.Input == SourceFileSpec.Algorithm, 9 | DeobfuscateDataFunctionDeclParser.Output == any DeclSyntaxProtocol 10 | { // swiftlint:disable:this opening_brace 11 | 12 | private let membersParser: MembersParser 13 | private let deobfuscateDataFunctionDeclParser: DeobfuscateDataFunctionDeclParser 14 | 15 | init( 16 | membersParser: MembersParser, 17 | deobfuscateDataFunctionDeclParser: DeobfuscateDataFunctionDeclParser 18 | ) { 19 | self.membersParser = membersParser 20 | self.deobfuscateDataFunctionDeclParser = deobfuscateDataFunctionDeclParser 21 | } 22 | 23 | func parse(_ input: inout SourceFileSpec) throws -> [CodeBlockItemSyntax] { 24 | let deobfuscateDataFunctionDecl = try deobfuscateDataFunctionDeclParser.parse(&input.algorithm) 25 | let codeBlocks = try input.secrets.namespaces 26 | .map { namespace -> CodeBlockItemSyntax in 27 | guard var secrets = input.secrets[namespace] else { 28 | fatalError("Unexpected source file spec integrity violation") 29 | } 30 | 31 | let decl: any DeclSyntaxProtocol 32 | switch namespace { 33 | case let .create(identifier): 34 | decl = try enumDecl( 35 | identifier: identifier, 36 | secrets: &secrets, 37 | deobfuscateDataFunctionDecl: deobfuscateDataFunctionDecl 38 | ) 39 | case let .extend(identifier, moduleName): 40 | let extendedTypeIdentifier: String = [moduleName, identifier] 41 | .compactMap { $0 } 42 | .joined(separator: ".") 43 | decl = try extensionDecl( 44 | extendedTypeIdentifier: extendedTypeIdentifier, 45 | secrets: &secrets, 46 | deobfuscateDataFunctionDecl: deobfuscateDataFunctionDecl 47 | ) 48 | } 49 | input.secrets[namespace] = secrets.isEmpty ? nil : secrets 50 | 51 | return .init(leadingTrivia: .newline, item: .init(decl)) 52 | } 53 | 54 | return codeBlocks 55 | } 56 | } 57 | 58 | private extension NamespaceDeclParser { 59 | 60 | func enumDecl( 61 | identifier: String, 62 | secrets: inout ArraySlice, 63 | deobfuscateDataFunctionDecl: some DeclSyntaxProtocol 64 | ) throws -> EnumDeclSyntax { 65 | let accessModifiers = Set(secrets.map(\.accessModifier)) 66 | let accessModifier: TokenSyntax = if accessModifiers.contains(.public) { 67 | .keyword(.public) 68 | } else if accessModifiers.contains(.package) { 69 | .keyword(.package) 70 | } else { 71 | .keyword(.internal) 72 | } 73 | 74 | return .init( 75 | leadingTrivia: .newline, 76 | modifiers: .init { 77 | DeclModifierSyntax(name: accessModifier) 78 | }, 79 | name: .identifier(identifier), 80 | memberBlock: try memberBlock(from: &secrets, with: deobfuscateDataFunctionDecl) 81 | ) 82 | } 83 | 84 | func extensionDecl( 85 | extendedTypeIdentifier: String, 86 | secrets: inout ArraySlice, 87 | deobfuscateDataFunctionDecl: some DeclSyntaxProtocol 88 | ) throws -> ExtensionDeclSyntax { 89 | .init( 90 | leadingTrivia: .newline, 91 | extendedType: IdentifierTypeSyntax(name: .identifier(extendedTypeIdentifier)), 92 | memberBlock: try memberBlock(from: &secrets, with: deobfuscateDataFunctionDecl) 93 | ) 94 | } 95 | 96 | func memberBlock( 97 | from secrets: inout ArraySlice, 98 | with deobfuscateDataFunctionDecl: some DeclSyntaxProtocol 99 | ) throws -> MemberBlockSyntax { 100 | var declarations = try membersParser.parse(&secrets) 101 | declarations.append( 102 | .init( 103 | leadingTrivia: .newline, 104 | decl: deobfuscateDataFunctionDecl 105 | ) 106 | ) 107 | 108 | return .init( 109 | leftBrace: .leftBraceToken(leadingTrivia: .spaces(1)), 110 | members: MemberBlockItemListSyntax(declarations) 111 | ) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/CodeGeneration/NamespaceMembersParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | import SwiftSyntax 3 | 4 | struct NamespaceMembersParser: Parser 5 | where 6 | SecretDeclParser.Input == SourceFileSpec.Secret, 7 | SecretDeclParser.Output == any DeclSyntaxProtocol 8 | { // swiftlint:disable:this opening_brace 9 | 10 | private let secretDeclParser: SecretDeclParser 11 | 12 | init(secretDeclParser: SecretDeclParser) { 13 | self.secretDeclParser = secretDeclParser 14 | } 15 | 16 | func parse(_ input: inout ArraySlice) throws -> [MemberBlockItemSyntax] { 17 | var parsedSecretsCount: Int = .zero 18 | let declarations: [any DeclSyntaxProtocol] 19 | do { 20 | declarations = try input.map { secret in 21 | let decl = try secretDeclParser.parse(secret) 22 | parsedSecretsCount += 1 23 | 24 | return decl 25 | } 26 | input.removeFirst(parsedSecretsCount) 27 | } catch { 28 | input.removeFirst(parsedSecretsCount) 29 | throw error 30 | } 31 | 32 | return declarations.map { 33 | MemberBlockItemSyntax(leadingTrivia: .newline, decl: $0) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/CodeGeneration/Parsers+CodeGeneration.swift: -------------------------------------------------------------------------------- 1 | package extension Parsers { 2 | 3 | enum CodeGeneration {} 4 | } 5 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/CodeGeneration/SecretVariableDeclParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | import SwiftSyntax 3 | 4 | struct SecretVariableDeclParser: Parser { 5 | 6 | typealias Secret = SourceFileSpec.Secret 7 | 8 | func parse(_ input: inout Secret) throws -> any DeclSyntaxProtocol { 9 | let valueHexComponents = input.data.hexEncodedStringComponents(options: .numericLiteral) 10 | let dataArgumentElements = valueHexComponents 11 | .enumerated() 12 | .map { idx, component in 13 | ArrayElementSyntax( 14 | expression: IntegerLiteralExprSyntax(literal: .identifier(component)), 15 | trailingComma: idx < valueHexComponents.endIndex - 1 ? .commaToken() : .none 16 | ) 17 | } 18 | 19 | return VariableDeclSyntax.makeSecretVariableDecl( 20 | dataProjectionAttribute: attribute(from: input.dataProjectionAttribute), 21 | accessModifier: keyword(for: input.accessModifier), 22 | bindingSpecifier: input.dataProjectionAttribute.isPropertyWrapper ? .var : .let, 23 | name: input.name, 24 | dataArgumentExpression: ArrayExprSyntax(elements: .init(dataArgumentElements)), 25 | nonceArgumentExpression: IntegerLiteralExprSyntax(literal: "\(raw: input.nonce)") 26 | ) 27 | } 28 | } 29 | 30 | private extension SecretVariableDeclParser { 31 | 32 | func keyword(for accessModifier: Secret.AccessModifier) -> Keyword { 33 | switch accessModifier { 34 | case .internal: .internal 35 | case .package: .package 36 | case .public: .public 37 | } 38 | } 39 | 40 | func attribute(from attribute: Secret.DataProjectionAttribute) -> AttributeSyntax { 41 | .init( 42 | atSign: .atSignToken(leadingNewlines: 1), 43 | attributeName: IdentifierTypeSyntax( 44 | name: .identifier(attribute.name) 45 | ), 46 | leftParen: .leftParenToken(), 47 | arguments: .argumentList( 48 | .init( 49 | attribute.arguments 50 | .map { argument in 51 | LabeledExprSyntax( 52 | label: argument.label.map { .identifier($0) }, 53 | colon: argument.label.map { _ in .colonToken() }, 54 | expression: DeclReferenceExprSyntax( 55 | baseName: .identifier(argument.value) 56 | ) 57 | ) 58 | } 59 | ) 60 | ), 61 | rightParen: .rightParenToken() 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/CodeGeneration/SourceFileParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | import SwiftSyntax 3 | 4 | package struct SourceFileParser: Parser 5 | where 6 | CodeBlockParsers.Input == SourceFileSpec, 7 | CodeBlockParsers.Output == [CodeBlockItemSyntax] 8 | { // swiftlint:disable:this opening_brace 9 | 10 | private let codeBlockParsers: CodeBlockParsers 11 | 12 | init(@ParserBuilder with build: () -> CodeBlockParsers) { 13 | self.codeBlockParsers = build() 14 | } 15 | 16 | package func parse(_ input: inout SourceFileSpec) throws -> SourceFileText { 17 | let statements = try codeBlockParsers.parse(&input) 18 | 19 | return .init( 20 | from: SourceFileSyntax( 21 | statements: CodeBlockItemListSyntax(statements) 22 | ) 23 | ) 24 | } 25 | } 26 | 27 | package extension Parsers.CodeGeneration { 28 | 29 | typealias SourceFile = SourceFileParser 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/AlgorithmParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | struct AlgorithmParser: Parser 4 | where 5 | ObfuscationStepParser.Input == Substring, 6 | ObfuscationStepParser.Output == SourceFileSpec.ObfuscationStep 7 | { // swiftlint:disable:this opening_brace 8 | 9 | typealias Algorithm = SourceFileSpec.Algorithm 10 | 11 | private let obfuscationStepParser: ObfuscationStepParser 12 | 13 | init(obfuscationStepParser: ObfuscationStepParser) { 14 | self.obfuscationStepParser = obfuscationStepParser 15 | } 16 | 17 | func parse(_ input: inout Configuration) throws -> Algorithm { 18 | var parsedObfuscationStepsCount: Int = .zero 19 | let output: [ObfuscationStepParser.Output] 20 | do { 21 | output = try input.algorithm.reduce(into: .init(), { steps, step in 22 | let obfuscationStep = try obfuscationStepParser.parse(step) 23 | parsedObfuscationStepsCount += 1 24 | 25 | steps.append(obfuscationStep) 26 | }) 27 | input.algorithm.removeFirst(parsedObfuscationStepsCount) 28 | } catch { 29 | input.algorithm.removeFirst(parsedObfuscationStepsCount) 30 | throw error 31 | } 32 | 33 | return output[...] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/CompressionTechniqueParser.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Parsing 3 | 4 | struct CompressionTechniqueParser: Parser { 5 | 6 | typealias Technique = SourceFileSpec.ObfuscationStep.Technique 7 | 8 | private typealias Algorithm = Obfuscation.Compression.CompressionAlgorithm 9 | 10 | func parse(_ input: inout Substring) throws -> Technique { 11 | try Parse(input: Substring.self, Technique.compression(algorithm:)) { 12 | Parse(input: Substring.self) { 13 | Whitespace(.horizontal) 14 | C.Parsing.Keywords.compress 15 | Whitespace(1..., .horizontal) 16 | C.Parsing.Keywords.using 17 | Whitespace(1..., .horizontal) 18 | } 19 | OneOf { 20 | for algorithm in Algorithm.allCases { 21 | algorithm.description.map { algorithm } 22 | } 23 | } 24 | End() 25 | }.parse(&input) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/EncryptionTechniqueParser.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Parsing 3 | 4 | struct EncryptionTechniqueParser: Parser { 5 | 6 | typealias Technique = SourceFileSpec.ObfuscationStep.Technique 7 | 8 | private typealias Algorithm = Obfuscation.Encryption.SymmetricEncryptionAlgorithm 9 | 10 | func parse(_ input: inout Substring) throws -> Technique { 11 | try Parse(input: Substring.self, Technique.encryption(algorithm:)) { 12 | Parse(input: Substring.self) { 13 | Whitespace(.horizontal) 14 | C.Parsing.Keywords.encrypt 15 | Whitespace(1..., .horizontal) 16 | C.Parsing.Keywords.using 17 | Whitespace(1..., .horizontal) 18 | } 19 | OneOf { 20 | for algorithm in Algorithm.allCases { 21 | algorithm.description.map { algorithm } 22 | } 23 | } 24 | End() 25 | }.parse(&input) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/ObfuscationStepParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | struct ObfuscationStepParser: Parser 4 | where 5 | TechniqueParsers.Input == Substring, 6 | TechniqueParsers.Output == SourceFileSpec.ObfuscationStep.Technique 7 | { // swiftlint:disable:this opening_brace 8 | 9 | typealias ObfuscationStep = SourceFileSpec.ObfuscationStep 10 | 11 | private let techniqueParsers: TechniqueParsers 12 | 13 | init(@OneOfBuilder with build: () -> TechniqueParsers) { 14 | self.techniqueParsers = build() 15 | } 16 | 17 | func parse(_ input: inout Substring) throws -> ObfuscationStep { 18 | try techniqueParsers 19 | .map(ObfuscationStep.init(technique:)) 20 | .parse(&input) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/Parsers+ModelTransform.swift: -------------------------------------------------------------------------------- 1 | package extension Parsers { 2 | 3 | enum ModelTransform {} 4 | } 5 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/RandomizationTechniqueParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | struct RandomizationTechniqueParser: Parser { 4 | 5 | typealias Technique = SourceFileSpec.ObfuscationStep.Technique 6 | 7 | func parse(_ input: inout Substring) throws -> Technique { 8 | try Parse(input: Substring.self, Technique.randomization) { 9 | Whitespace(.horizontal) 10 | C.Parsing.Keywords.shuffle 11 | End() 12 | }.parse(&input) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/SecretAccessModifierParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | struct SecretAccessModifierParser: Parser { 4 | 5 | typealias AccessModifier = SourceFileSpec.Secret.AccessModifier 6 | 7 | func parse(_ input: inout Substring) throws -> AccessModifier { 8 | guard !input.isEmpty else { 9 | return .internal 10 | } 11 | 12 | return try Parse(input: Substring.self) { 13 | Whitespace(.horizontal) 14 | AccessModifier.parser() 15 | End() 16 | } 17 | .parse(&input) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/SecretNamespaceParser.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import ConfidentialUtils 3 | import Parsing 4 | 5 | struct SecretNamespaceParser: Parser { 6 | 7 | typealias Namespace = SourceFileSpec.Secret.Namespace 8 | 9 | private enum NamespaceKind: Equatable { 10 | case create 11 | case extend 12 | } 13 | 14 | func parse(_ input: inout Substring) throws -> Namespace { 15 | guard !input.isEmpty else { 16 | let defaultNamespaceInfo = TypeInfo(of: Obfuscation.Secret.self) 17 | return .extend( 18 | identifier: defaultNamespaceInfo.fullName, 19 | moduleName: defaultNamespaceInfo.moduleName 20 | ) 21 | } 22 | 23 | return try Parse(input: Substring.self) { 24 | Whitespace(.horizontal) 25 | OneOf { 26 | C.Parsing.Keywords.create.map { NamespaceKind.create } 27 | C.Parsing.Keywords.extend.map { NamespaceKind.extend } 28 | } 29 | } 30 | .flatMap { namespaceKind in 31 | Always(namespaceKind) 32 | Whitespace(1..., .horizontal) 33 | Prefix(1...) { !$0.isWhitespace } 34 | if case .extend = namespaceKind { 35 | OneOf { 36 | Parse(input: Substring.self, Substring?.some) { 37 | Whitespace(1..., .horizontal) 38 | C.Parsing.Keywords.from 39 | Whitespace(1..., .horizontal) 40 | Prefix(1...) { !$0.isWhitespace } 41 | End() 42 | } 43 | End().map { Substring?.none } 44 | } 45 | } else { 46 | End().map { Substring?.none } 47 | } 48 | } 49 | .map { namespaceKind, identifier, moduleName -> Namespace in 50 | switch namespaceKind { 51 | case .create: 52 | return .create(identifier: .init(identifier)) 53 | case .extend: 54 | return .extend(identifier: .init(identifier), moduleName: moduleName.map(String.init)) 55 | } 56 | } 57 | .parse(&input) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ModelTransform/SourceFileSpecParser.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | package struct SourceFileSpecParser: Parser 4 | where 5 | AlgorithmParser.Input == Configuration, 6 | AlgorithmParser.Output == SourceFileSpec.Algorithm, 7 | SecretsParser.Input == Configuration, 8 | SecretsParser.Output == SourceFileSpec.Secrets 9 | { // swiftlint:disable:this opening_brace 10 | 11 | private let algorithmParser: AlgorithmParser 12 | private let secretsParser: SecretsParser 13 | 14 | init(algorithmParser: AlgorithmParser, secretsParser: SecretsParser) { 15 | self.algorithmParser = algorithmParser 16 | self.secretsParser = secretsParser 17 | } 18 | 19 | package func parse(_ input: inout Configuration) throws -> SourceFileSpec { 20 | let spec = SourceFileSpec( 21 | algorithm: try algorithmParser.parse(&input), 22 | experimentalMode: input.isExperimentalModeEnabled, 23 | internalImport: input.isInternalImportEnabled, 24 | secrets: try secretsParser.parse(&input) 25 | ) 26 | input.defaultAccessModifier = nil 27 | input.defaultNamespace = nil 28 | input.experimentalMode = nil 29 | input.internalImport = nil 30 | 31 | return spec 32 | } 33 | } 34 | 35 | package extension Parsers.ModelTransform { 36 | 37 | typealias SourceFileSpec = SourceFileSpecParser 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/Parsers/ParsingError.swift: -------------------------------------------------------------------------------- 1 | enum ParsingError: Error { 2 | case assertionFailed(description: String) 3 | } 4 | 5 | extension ParsingError: CustomStringConvertible { 6 | 7 | var description: String { 8 | switch self { 9 | case let .assertionFailed(description): 10 | return description 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/Parsing/ParsersConvenienceInitializers.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | import SwiftSyntax 3 | 4 | package typealias AnyAlgorithmParser = AnyParser 5 | package typealias AnySecretsParser = AnyParser 6 | 7 | package extension Parsers.ModelTransform.SourceFileSpec 8 | where 9 | AlgorithmParser == AnyAlgorithmParser, 10 | SecretsParser == AnySecretsParser 11 | { // swiftlint:disable:this opening_brace 12 | 13 | private typealias Technique = SourceFileSpec.ObfuscationStep.Technique 14 | private typealias AnyTechniqueParser = AnyParser 15 | private typealias OneOfManyTechniques = OneOfBuilder.OneOf2< 16 | OneOfBuilder.OneOf2, 17 | AnyTechniqueParser 18 | > 19 | 20 | init() { 21 | self.init( 22 | algorithmParser: ConfidentialCore.AlgorithmParser( 23 | obfuscationStepParser: ObfuscationStepParser { 24 | CompressionTechniqueParser().eraseToAnyParser() 25 | EncryptionTechniqueParser().eraseToAnyParser() 26 | RandomizationTechniqueParser().eraseToAnyParser() 27 | } 28 | ) 29 | .eraseToAnyParser(), 30 | secretsParser: ConfidentialCore.SecretsParser( 31 | namespaceParser: SecretNamespaceParser(), 32 | accessModifierParser: SecretAccessModifierParser() 33 | ) 34 | .eraseToAnyParser() 35 | ) 36 | } 37 | } 38 | 39 | package typealias AnyCodeBlockParser = AnyParser 40 | 41 | package extension Parsers.CodeGeneration.SourceFile 42 | where 43 | CodeBlockParsers == AnyCodeBlockParser 44 | { // swiftlint:disable:this opening_brace 45 | 46 | init() { 47 | self.init { 48 | Parse(input: SourceFileSpec.self) { 49 | ImportDeclParser() 50 | NamespaceDeclParser( 51 | membersParser: NamespaceMembersParser( 52 | secretDeclParser: SecretVariableDeclParser() 53 | ), 54 | deobfuscateDataFunctionDeclParser: DeobfuscateDataFunctionDeclParser(functionNestingLevel: 1) 55 | ) 56 | } 57 | .map { imports, namespaces in 58 | imports + namespaces 59 | } 60 | .eraseToAnyParser() 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Attributes/ImplementationOnlyAttribute.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension AttributeSyntax { 4 | 5 | // swiftlint:disable:next identifier_name 6 | static var _implementationOnly: Self { 7 | .init( 8 | attributeName: IdentifierTypeSyntax( 9 | name: .identifier("_implementationOnly") 10 | ) 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Attributes/InlineAlwaysAttribute.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension AttributeSyntax { 4 | 5 | static var inlineAlways: Self { 6 | .init( 7 | attributeName: IdentifierTypeSyntax(name: .identifier("inline")), 8 | leftParen: .leftParenToken(), 9 | arguments: .token(.identifier("__always")), 10 | rightParen: .rightParenToken() 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Declarations/DeobfuscateDataFunctionDecl.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import ConfidentialUtils 3 | import Foundation 4 | import SwiftSyntax 5 | 6 | extension FunctionDeclSyntax { 7 | 8 | static func makeDeobfuscateDataFunctionDecl( 9 | nestingLevel: Int = .zero, 10 | body: some ExprSyntaxProtocol 11 | ) -> Self { 12 | let dataTypeInfo = TypeInfo(of: Data.self) 13 | let nonceTypeInfo = TypeInfo(of: Obfuscation.Nonce.self) 14 | let indentation = nestingLevel * C.Code.Format.indentWidth 15 | return .init( 16 | attributes: .init { 17 | AttributeSyntax 18 | .inlineAlways 19 | .with(\.leadingTrivia, .newlines(1).appending(Trivia.spaces(indentation))) 20 | }, 21 | modifiers: .init { 22 | DeclModifierSyntax( 23 | name: .keyword( 24 | .private, 25 | leadingTrivia: .newlines(1).appending(Trivia.spaces(indentation)), 26 | trailingTrivia: .spaces(1) 27 | ) 28 | ) 29 | DeclModifierSyntax(name: .keyword(.static)) 30 | }, 31 | name: .identifier(C.Code.Generation.deobfuscateDataFuncName), 32 | signature: FunctionSignatureSyntax( 33 | parameterClause: FunctionParameterClauseSyntax( 34 | parameters: .init { 35 | FunctionParameterSyntax( 36 | firstName: .wildcardToken(), 37 | secondName: .identifier(C.Code.Generation.deobfuscateDataFuncDataParamName), 38 | type: IdentifierTypeSyntax(name: .identifier(dataTypeInfo.fullyQualifiedName)), 39 | trailingComma: .commaToken() 40 | ) 41 | FunctionParameterSyntax( 42 | firstName: .identifier(C.Code.Generation.deobfuscateDataFuncNonceParamName), 43 | type: IdentifierTypeSyntax(name: .identifier(nonceTypeInfo.fullyQualifiedName)) 44 | ) 45 | } 46 | ), 47 | effectSpecifiers: effectSpecifiersSyntax, 48 | returnClause: ReturnClauseSyntax( 49 | type: IdentifierTypeSyntax(name: .identifier(dataTypeInfo.fullyQualifiedName)) 50 | ) 51 | ), 52 | body: CodeBlockSyntax( 53 | leftBrace: .leftBraceToken(leadingTrivia: .spaces(1)), 54 | rightBrace: .rightBraceToken(leadingTrivia: .spaces(indentation)) 55 | ) { 56 | CodeBlockItemSyntax(item: .init(body)) 57 | } 58 | ) 59 | } 60 | } 61 | 62 | private extension FunctionDeclSyntax { 63 | 64 | static var effectSpecifiersSyntax: FunctionEffectSpecifiersSyntax { 65 | let throwsSpecifier: TokenSyntax = .keyword(.throws, leadingTrivia: .spaces(1)) 66 | #if canImport(SwiftSyntax600) 67 | return .init(throwsClause: .init(throwsSpecifier: throwsSpecifier)) 68 | #else 69 | return .init(throwsSpecifier: throwsSpecifier) 70 | #endif 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Declarations/IfSwiftVersionOrFeatureDecl.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension IfConfigDeclSyntax { 4 | 5 | static func makeIfSwiftVersionOrFeatureDecl( 6 | swiftVersion: Float, 7 | featureFlag: String, 8 | ifElements: IfConfigClauseSyntax.Elements, 9 | elseElements: IfConfigClauseSyntax.Elements 10 | ) -> Self { 11 | .init( 12 | clauses: IfConfigClauseListSyntax { 13 | IfConfigClauseSyntax( 14 | poundKeyword: .poundIfToken(), 15 | condition: InfixOperatorExprSyntax( 16 | leftOperand: compilerFunctionCallExpr( 17 | prefixOperator: ">=", 18 | swiftVersion: swiftVersion 19 | ), 20 | operator: BinaryOperatorExprSyntax( 21 | operator: .binaryOperator("||") 22 | ), 23 | rightOperand: hasFeatureFunctionCallExpr( 24 | featureFlag: featureFlag 25 | ) 26 | ), 27 | elements: ifElements 28 | ) 29 | IfConfigClauseSyntax( 30 | poundKeyword: .poundElseToken(), 31 | elements: elseElements 32 | ) 33 | } 34 | ) 35 | } 36 | } 37 | 38 | private extension IfConfigDeclSyntax { 39 | 40 | static func compilerFunctionCallExpr(prefixOperator: String, swiftVersion: Float) -> FunctionCallExprSyntax { 41 | .init( 42 | callee: DeclReferenceExprSyntax( 43 | baseName: .identifier("compiler") 44 | ) 45 | ) { 46 | LabeledExprSyntax( 47 | expression: PrefixOperatorExprSyntax( 48 | operator: .prefixOperator(">="), 49 | expression: FloatLiteralExprSyntax(swiftVersion) 50 | ) 51 | ) 52 | } 53 | } 54 | 55 | static func hasFeatureFunctionCallExpr(featureFlag: String) -> FunctionCallExprSyntax { 56 | .init( 57 | callee: DeclReferenceExprSyntax( 58 | baseName: .identifier("hasFeature") 59 | ) 60 | ) { 61 | LabeledExprSyntax( 62 | expression: DeclReferenceExprSyntax( 63 | baseName: .identifier(featureFlag) 64 | ) 65 | ) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Declarations/SecretVariableDecl.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import ConfidentialUtils 3 | import SwiftSyntax 4 | 5 | extension VariableDeclSyntax { 6 | 7 | private static let dataArgumentName: String = "data" 8 | private static let nonceArgumentName: String = "nonce" 9 | 10 | // swiftlint:disable:next function_parameter_count 11 | static func makeSecretVariableDecl( 12 | dataProjectionAttribute: AttributeSyntax, 13 | accessModifier: Keyword, 14 | bindingSpecifier: Keyword, 15 | name: String, 16 | dataArgumentExpression: ArrayExprSyntax, 17 | nonceArgumentExpression: IntegerLiteralExprSyntax 18 | ) -> Self { 19 | assert([.internal, .package, .public].contains(accessModifier)) 20 | assert([.let, .var].contains(bindingSpecifier)) 21 | return .init( 22 | attributes: .init { 23 | dataProjectionAttribute 24 | }, 25 | modifiers: .init { 26 | DeclModifierSyntax( 27 | name: .keyword(accessModifier) 28 | .with( 29 | \.leadingTrivia, .newlines(1) 30 | .appending(Trivia.spaces(C.Code.Format.indentWidth)) 31 | ) 32 | .with(\.trailingTrivia, .spaces(1)) 33 | ) 34 | DeclModifierSyntax(name: .keyword(.static)) 35 | }, 36 | bindingSpecifier, 37 | name: PatternSyntax(IdentifierPatternSyntax(identifier: .identifier(name))), 38 | type: TypeAnnotationSyntax( 39 | type: IdentifierTypeSyntax( 40 | name: .identifier( 41 | TypeInfo(of: Obfuscation.Secret.self).fullyQualifiedName 42 | ) 43 | ) 44 | ), 45 | initializer: InitializerClauseSyntax( 46 | value: FunctionCallExprSyntax.makeInitializerCallExpr { 47 | LabeledExprSyntax( 48 | label: .identifier(Self.dataArgumentName), 49 | colon: .colonToken(), 50 | expression: dataArgumentExpression, 51 | trailingComma: .commaToken() 52 | ) 53 | LabeledExprSyntax( 54 | label: .identifier(Self.nonceArgumentName), 55 | colon: .colonToken(), 56 | expression: nonceArgumentExpression 57 | ) 58 | } 59 | ) 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Expressions/DataCompressorInitializerCallExpr.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import ConfidentialUtils 3 | import SwiftSyntax 4 | 5 | extension FunctionCallExprSyntax { 6 | 7 | private static let algorithmArgumentName: String = "algorithm" 8 | 9 | static func makeDataCompressorInitializerCallExpr( 10 | algorithm: Obfuscation.Compression.CompressionAlgorithm 11 | ) -> Self { 12 | .init( 13 | calledExpression: DeclReferenceExprSyntax( 14 | baseName: .identifier( 15 | TypeInfo(of: Obfuscation.Compression.DataCompressor.self).fullyQualifiedName 16 | ) 17 | ), 18 | leftParen: .leftParenToken(), 19 | arguments: .init { 20 | LabeledExprSyntax( 21 | label: .identifier(Self.algorithmArgumentName), 22 | colon: .colonToken(), 23 | expression: MemberAccessExprSyntax( 24 | name: .identifier(algorithm.name) 25 | ) 26 | ) 27 | }, 28 | rightParen: .rightParenToken() 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Expressions/DataCrypterInitializerCallExpr.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import ConfidentialUtils 3 | import SwiftSyntax 4 | 5 | extension FunctionCallExprSyntax { 6 | 7 | private static let algorithmArgumentName: String = "algorithm" 8 | 9 | static func makeDataCrypterInitializerCallExpr( 10 | algorithm: Obfuscation.Encryption.SymmetricEncryptionAlgorithm 11 | ) -> Self { 12 | .init( 13 | calledExpression: DeclReferenceExprSyntax( 14 | baseName: .identifier( 15 | TypeInfo(of: Obfuscation.Encryption.DataCrypter.self).fullyQualifiedName 16 | ) 17 | ), 18 | leftParen: .leftParenToken(), 19 | arguments: .init { 20 | LabeledExprSyntax( 21 | label: .identifier(Self.algorithmArgumentName), 22 | colon: .colonToken(), 23 | expression: MemberAccessExprSyntax( 24 | name: .identifier(algorithm.name) 25 | ) 26 | ) 27 | }, 28 | rightParen: .rightParenToken() 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Expressions/DataShufflerInitializerCallExpr.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import ConfidentialUtils 3 | import SwiftSyntax 4 | 5 | extension FunctionCallExprSyntax { 6 | 7 | static func makeDataShufflerInitializerCallExpr() -> Self { 8 | .init( 9 | calledExpression: DeclReferenceExprSyntax( 10 | baseName: .identifier( 11 | TypeInfo(of: Obfuscation.Randomization.DataShuffler.self).fullyQualifiedName 12 | ) 13 | ), 14 | leftParen: .leftParenToken(), 15 | arguments: [], 16 | rightParen: .rightParenToken() 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Expressions/DeobfuscateFunctionAccessExpr.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension MemberAccessExprSyntax { 4 | 5 | private static let deobfuscateFuncName: String = "deobfuscate" 6 | 7 | static func makeDeobfuscateFunctionAccessExpr( 8 | _ deobfuscationStepInitializerExpr: some ExprSyntaxProtocol, 9 | dotIndentWidth: Int 10 | ) -> Self { 11 | .init( 12 | base: deobfuscationStepInitializerExpr, 13 | period: .periodToken( 14 | leadingNewlines: 1, 15 | followedByLeadingSpaces: dotIndentWidth 16 | ), 17 | declName: .init(baseName: .identifier(Self.deobfuscateFuncName)) 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxNodes/Expressions/InitializerCallExpr.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | import SwiftSyntaxBuilder 3 | 4 | extension FunctionCallExprSyntax { 5 | 6 | static func makeInitializerCallExpr( 7 | @LabeledExprListBuilder argumentListBuilder: () -> LabeledExprListSyntax = { 8 | LabeledExprListSyntax([]) 9 | } 10 | ) -> Self { 11 | .init( 12 | calledExpression: MemberAccessExprSyntax( 13 | declName: .init(baseName: .keyword(.`init`)) 14 | ), 15 | leftParen: .leftParenToken(), 16 | arguments: argumentListBuilder(), 17 | rightParen: .rightParenToken() 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/ConfidentialCore/SyntaxText/SourceFileText.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftSyntax 3 | 4 | package struct SourceFileText: Equatable { 5 | 6 | private let syntax: Syntax 7 | 8 | init(from sourceFile: SourceFileSyntax) { 9 | self.syntax = sourceFile 10 | .formatted(using: .init(indentationWidth: .spaces(0))) 11 | } 12 | 13 | package func write(to url: URL, encoding: String.Encoding = .utf8) throws { 14 | var text = "" 15 | syntax.write(to: &text) 16 | 17 | try text 18 | .trimmingCharacters(in: .newlines) 19 | .write(to: url, atomically: true, encoding: encoding) 20 | } 21 | } 22 | 23 | extension SourceFileText: CustomStringConvertible { 24 | 25 | package var description: String { syntax.description } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Coding/DataDecoder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @usableFromInline 4 | protocol DataDecoder { 5 | func decode(_ type: D.Type, from data: Data) throws -> D 6 | } 7 | 8 | extension JSONDecoder: DataDecoder {} 9 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/DataDeobfuscationStep.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that can deobfuscate the data for further processing. 4 | /// 5 | /// The instances of types conforming to ``DataDeobfuscationStep`` can be composed 6 | /// together to form a deobfuscation algorithm. 7 | public protocol DataDeobfuscationStep { 8 | /// Deobfuscates the given data using operations that reverse the obfuscation process. 9 | /// 10 | /// - Parameter data: An obfuscated input data. 11 | /// - Parameter nonce: A nonce used during the deobfuscation process. 12 | /// - Returns: A deobfuscated output data. 13 | func deobfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data 14 | } 15 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Obfuscation+Nonce.swift: -------------------------------------------------------------------------------- 1 | public extension Obfuscation { 2 | 3 | /// A type that represents a cryptographic nonce. 4 | typealias Nonce = UInt64 5 | } 6 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Obfuscation+Secret.swift: -------------------------------------------------------------------------------- 1 | public extension Obfuscation { 2 | 3 | /// A model representing an obfuscated secret. 4 | struct Secret: Equatable, Sendable { 5 | 6 | /// The obfuscated secret's bytes. 7 | public let data: [UInt8] 8 | 9 | /// The nonce used during the obfuscation process of this secret. 10 | public let nonce: Nonce 11 | 12 | /// Creates a new instance from the given sequence containing obfuscated secret's bytes and 13 | /// associated nonce. 14 | /// 15 | /// - Parameter data: The sequence of obfuscated secret's bytes. 16 | /// - Parameter nonce: The nonce associated with the obfuscated secret's bytes. 17 | public init(data: Data, nonce: Nonce) where Data.Element == UInt8 { 18 | self.data = .init(data) 19 | self.nonce = nonce 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Obfuscation+SupportedDataTypes.swift: -------------------------------------------------------------------------------- 1 | public extension Obfuscation { 2 | 3 | /// A namespace for the plain data types supported by ``Obfuscation`` API. 4 | enum SupportedDataTypes { 5 | /// A type that represents a sequence of values. 6 | public typealias Array = [String] 7 | 8 | /// A type that represents a single value. 9 | public typealias SingleValue = String 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Obfuscation.swift: -------------------------------------------------------------------------------- 1 | /// A namespace for types associated with obfuscation-related tasks, which all together 2 | /// constitute an API for (de)obfuscating secret data. 3 | /// 4 | /// The various implementations of obfuscation techniques are defined within their own 5 | /// namespaces defined as extensions on ``Obfuscation``. 6 | /// 7 | /// Your choice of technique determines the strategy used for data obfuscation: 8 | /// - The ``Obfuscation/Compression`` namespace encapsulates 9 | /// implementations of technique involving data compression/decompression. 10 | /// - The ``Obfuscation/Encryption`` namespace encapsulates 11 | /// implementations of technique involving data encryption/decryption. 12 | /// - The ``Obfuscation/Randomization`` namespace encapsulates 13 | /// implementations of technique involving data randomization. 14 | /// 15 | /// > Important: The ``ConfidentialKit`` library was designed to be used in 16 | /// conjunction with `swift-confidential` CLI tool and, as such, it 17 | /// only ships with a subset of API needed for deobfuscating obfuscated 18 | /// secret data embedded in the application code. 19 | public enum Obfuscation {} 20 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/PropertyWrappers/Obfuscated.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A property wrapper that can deobfuscate the wrapped secret value. 4 | @propertyWrapper 5 | public struct Obfuscated { 6 | 7 | /// A type that represents a wrapped value. 8 | public typealias Value = Obfuscation.Secret 9 | 10 | /// A type that represents a deobfuscation function. 11 | public typealias DeobfuscateDataFunc = (Data, Obfuscation.Nonce) throws -> Data 12 | 13 | @usableFromInline 14 | let deobfuscateData: DeobfuscateDataFunc 15 | 16 | @usableFromInline 17 | let decoder: any DataDecoder 18 | 19 | /// The underlying secret value. 20 | public let wrappedValue: Value 21 | 22 | /// A plain secret value after transforming obfuscated secret's data with a deobfuscation function. 23 | @inlinable 24 | public var projectedValue: PlainValue { 25 | let data = Data(wrappedValue.data) 26 | let nonce = wrappedValue.nonce 27 | let value: PlainValue 28 | 29 | do { 30 | let deobfuscatedData = try deobfuscateData(data, nonce) 31 | value = try decoder.decode(PlainValue.self, from: deobfuscatedData) 32 | } catch { 33 | preconditionFailure("Unexpected error: \(error)") 34 | } 35 | 36 | return value 37 | } 38 | 39 | @usableFromInline 40 | init( 41 | wrappedValue: Value, 42 | deobfuscateData: @escaping DeobfuscateDataFunc, 43 | decoder: any DataDecoder 44 | ) { 45 | self.wrappedValue = wrappedValue 46 | self.deobfuscateData = deobfuscateData 47 | self.decoder = decoder 48 | } 49 | 50 | /// Creates a property that can deobfuscate the wrapped secret value using the given closure. 51 | /// 52 | /// - Parameters: 53 | /// - wrappedValue: A secret value containing obfuscated data. 54 | /// - deobfuscateData: The closure to execute when calling ``projectedValue``. 55 | @inlinable 56 | public init( 57 | wrappedValue: Value, 58 | _ deobfuscateData: @escaping DeobfuscateDataFunc 59 | ) { 60 | self.init( 61 | wrappedValue: wrappedValue, 62 | deobfuscateData: deobfuscateData, 63 | decoder: JSONDecoder() 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Compression/CompressionAlgorithm.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Obfuscation.Compression { 4 | 5 | /// An algorithm that indicates how to compress or decompress data. 6 | typealias CompressionAlgorithm = NSData.CompressionAlgorithm 7 | } 8 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Compression/DataCompressor.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialUtils 2 | import Foundation 3 | 4 | public extension Obfuscation.Compression { 5 | 6 | /// An implementation of obfuscation technique utilizing data compression. 7 | /// 8 | /// See ``CompressionAlgorithm`` for a list of supported compression algorithms. 9 | struct DataCompressor: DataDeobfuscationStep { 10 | 11 | /// An algorithm used to compress and decompress the data. 12 | public let algorithm: CompressionAlgorithm 13 | 14 | /// Creates a new instance with the specified compression algorithm. 15 | /// 16 | /// - Parameter algorithm: An algorithm used to compress and decompress the data. 17 | public init(algorithm: CompressionAlgorithm) { 18 | self.algorithm = algorithm 19 | } 20 | 21 | /// Decompresses the given data using preset ``algorithm``. 22 | /// 23 | /// - Parameter data: A compressed input data. 24 | /// - Parameter nonce: A nonce used to deobfuscate the magic numbers identifying the 25 | /// compression algorithm. 26 | /// - Returns: A decompressed output data. 27 | @inlinable 28 | @inline(__always) 29 | public func deobfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 30 | var obfuscatedData = data 31 | 32 | if algorithm.headerMagicByteCount > .zero { 33 | let magicByteCount = algorithm.headerMagicByteCount 34 | let obfuscatedMagicBytes = obfuscatedData.prefix(magicByteCount) 35 | let magicBytes = Internal.deobfuscateMagicBytes( 36 | obfuscatedMagicBytes, 37 | nonce: nonce 38 | ) 39 | obfuscatedData.replaceSubrange(.. .zero { 42 | let magicByteCount = algorithm.footerMagicByteCount 43 | let obfuscatedMagicBytes = obfuscatedData.suffix(magicByteCount) 44 | let magicBytes = Internal.deobfuscateMagicBytes( 45 | obfuscatedMagicBytes, 46 | nonce: nonce.byteSwapped 47 | ) 48 | let endIndex = obfuscatedData.endIndex 49 | let startIndex = obfuscatedData.index(endIndex, offsetBy: -magicByteCount) 50 | obfuscatedData.replaceSubrange(startIndex..( 67 | _ magicBytes: Bytes, 68 | nonce: Obfuscation.Nonce 69 | ) -> Data where Bytes.Element == UInt8, Bytes.Index == Int { 70 | let nonceBytes = nonce.bytes 71 | let nonceByteWidth = nonceBytes.count 72 | let deobfuscatedBytes = magicBytes.enumerated().map { index, byte in 73 | byte ^ nonceBytes[index % nonceByteWidth] 74 | } 75 | 76 | return .init(deobfuscatedBytes) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Compression/Obfuscation+Compression.swift: -------------------------------------------------------------------------------- 1 | public extension Obfuscation { 2 | 3 | /// A namespace for types that use data compression as obfuscation technique. 4 | enum Compression {} 5 | } 6 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Encryption/DataCrypter.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialUtils 2 | import CryptoKit 3 | import Foundation 4 | 5 | public extension Obfuscation.Encryption { 6 | 7 | /// An implementation of obfuscation technique utilizing data encryption. 8 | /// 9 | /// See ``SymmetricEncryptionAlgorithm`` for a list of supported encryption algorithms. 10 | struct DataCrypter: DataDeobfuscationStep { 11 | 12 | /// An algorithm used to encrypt and decrypt the data. 13 | public let algorithm: SymmetricEncryptionAlgorithm 14 | 15 | /// Creates a new instance with the specified symmetric encryption algorithm. 16 | /// 17 | /// - Parameter algorithm: An algorithm used to encrypt and decrypt the data. 18 | public init(algorithm: SymmetricEncryptionAlgorithm) { 19 | self.algorithm = algorithm 20 | } 21 | 22 | /// Decrypts the given data using preset ``algorithm``. 23 | /// 24 | /// - Parameter data: An encrypted input data. 25 | /// - Parameter nonce: A nonce used to deobfuscate the encryption key data. 26 | /// - Returns: A decrypted output data. 27 | @inlinable 28 | @inline(__always) 29 | public func deobfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 30 | var obfuscatedData = data 31 | 32 | let obfuscatedKeyData: Data 33 | let keySize = algorithm.keySize.byteCount 34 | let magicBit: UInt64 = 1 << 7 35 | if nonce & magicBit == 0 { 36 | obfuscatedKeyData = obfuscatedData.prefix(keySize) 37 | obfuscatedData.removeFirst(keySize) 38 | } else { 39 | obfuscatedKeyData = obfuscatedData.suffix(keySize) 40 | obfuscatedData.removeLast(keySize) 41 | } 42 | let keyData = Internal.deobfuscateKeyData(obfuscatedKeyData, nonce: nonce) 43 | 44 | let deobfuscatedData = try Internal.decryptData( 45 | obfuscatedData, 46 | using: .init(data: keyData), 47 | algorithm: algorithm 48 | ) 49 | 50 | return deobfuscatedData 51 | } 52 | } 53 | } 54 | 55 | extension Obfuscation.Encryption.DataCrypter { 56 | 57 | @usableFromInline 58 | enum Internal { 59 | 60 | @usableFromInline 61 | @inline(__always) 62 | static func decryptData( 63 | _ data: Data, 64 | using key: SymmetricKey, 65 | algorithm: Obfuscation.Encryption.SymmetricEncryptionAlgorithm 66 | ) throws -> Data { 67 | switch algorithm { 68 | case .aes128GCM, .aes192GCM, .aes256GCM: 69 | let sealedBox = try AES.GCM.SealedBox(combined: data) 70 | return try AES.GCM.open(sealedBox, using: key) 71 | case .chaChaPoly: 72 | let sealedBox = try ChaChaPoly.SealedBox(combined: data) 73 | return try ChaChaPoly.open(sealedBox, using: key) 74 | } 75 | } 76 | 77 | @usableFromInline 78 | @inline(__always) 79 | static func deobfuscateKeyData(_ keyData: Data, nonce: Obfuscation.Nonce) -> Data { 80 | let nonceBytes = nonce.bytes 81 | let nonceByteWidth = nonceBytes.count 82 | let deobfuscatedKeyBytes = keyData.enumerated().map { index, byte in 83 | byte ^ nonceBytes[index % nonceByteWidth] 84 | } 85 | 86 | return .init(deobfuscatedKeyBytes) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Encryption/Obfuscation+Encryption.swift: -------------------------------------------------------------------------------- 1 | public extension Obfuscation { 2 | 3 | /// A namespace for types that use data encryption as obfuscation technique. 4 | enum Encryption {} 5 | } 6 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Encryption/SymmetricEncryptionAlgorithm.swift: -------------------------------------------------------------------------------- 1 | import CryptoKit 2 | 3 | public extension Obfuscation.Encryption { 4 | 5 | /// A symmetric algorithm that indicates how to encrypt or decrypt data. 6 | enum SymmetricEncryptionAlgorithm: String { 7 | /// The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) 8 | /// with 128-bit key. 9 | case aes128GCM 10 | 11 | /// The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) 12 | /// with 192-bit key. 13 | case aes192GCM 14 | 15 | /// The Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) 16 | /// with 256-bit key. 17 | case aes256GCM 18 | 19 | /// The ChaCha20-Poly1305 algorithm. 20 | case chaChaPoly 21 | } 22 | } 23 | 24 | public extension Obfuscation.Encryption.SymmetricEncryptionAlgorithm { 25 | 26 | /// The size of the symmetric cryptographic key associated with the algorithm. 27 | var keySize: SymmetricKeySize { 28 | switch self { 29 | case .aes128GCM: 30 | return .bits128 31 | case .aes192GCM: 32 | return .bits192 33 | case .aes256GCM: 34 | return .bits256 35 | case .chaChaPoly: 36 | return .bits256 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Randomization/DataShuffler.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialUtils 2 | import Foundation 3 | 4 | public extension Obfuscation.Randomization { 5 | 6 | /// An implementation of obfuscation technique utilizing data randomization. 7 | /// 8 | /// The ``DataShuffler`` uses a pseudorandom number generator (PRNG) to 9 | /// shuffle the bytes stored in ``Data`` instance being processed, along with a 10 | /// ``nonce``, which is used to obfuscate the shuffling parameters. 11 | /// 12 | /// > Warning: The current implementation of this technique is best suited for secrets of 13 | /// which size does not exceed 256 bytes. For larger secrets, the size of the 14 | /// obfuscated data will grow from 2N to 3N, where N is the input data size 15 | /// in bytes, or even 5N (32-bit platform) or 9N (64-bit platform) if the size of 16 | /// input data is larger than 65 536 bytes. 17 | struct DataShuffler: DataDeobfuscationStep { 18 | 19 | /// Creates a new instance. 20 | public init() {} 21 | 22 | /// Deshuffles the given data. 23 | /// 24 | /// - Parameter data: A shuffled input data. 25 | /// - Parameter nonce: A nonce used to deobfuscate the shuffling parameters. 26 | /// - Returns: A deshuffled output data. 27 | @inlinable 28 | @inline(__always) 29 | public func deobfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 30 | let countByteWidth = Int.byteWidth 31 | let nonceBytes = nonce.bytes 32 | let count = data 33 | .prefix(upTo: countByteWidth) 34 | .withUnsafeBytes { $0.load(as: Int.self) } ^ .init(bytes: nonceBytes) 35 | let indexByteWidthPos = countByteWidth + count 36 | let indexByteWidth = data[indexByteWidthPos] 37 | let indexes = Internal.deobfuscateIndexes( 38 | bytes: .init(data.suffix(from: indexByteWidthPos + 1)), 39 | byteWidth: indexByteWidth, 40 | nonceBytes: nonceBytes 41 | ) 42 | let shuffledBytes = data.subdata(in: countByteWidth.. [UInt8] { 58 | var result: [UInt8] = .init(repeating: .zero, count: bytes.count) 59 | indexes.enumerated().forEach { newIdx, oldIdx in 60 | result[newIdx] = bytes[oldIdx] 61 | } 62 | 63 | return result 64 | } 65 | 66 | @usableFromInline 67 | @inline(__always) 68 | static func deobfuscateIndexes(bytes: [UInt8], byteWidth: UInt8, nonceBytes: [UInt8]) -> [Int] { 69 | var bytes = bytes[...] 70 | let byteWidth = Int(byteWidth) 71 | switch byteWidth { 72 | case UInt8.byteWidth: 73 | return deobfuscateIndexes(bytes: &bytes, indexType: UInt8.self, nonceBytes: nonceBytes) 74 | case UInt16.byteWidth: 75 | return deobfuscateIndexes(bytes: &bytes, indexType: UInt16.self, nonceBytes: nonceBytes) 76 | default: 77 | return deobfuscateIndexes(bytes: &bytes, indexType: Int.self, nonceBytes: nonceBytes) 78 | } 79 | } 80 | 81 | @usableFromInline 82 | @inline(__always) 83 | static func deobfuscateIndexes( 84 | bytes: inout ArraySlice, 85 | indexType: I.Type, 86 | nonceBytes: [UInt8] 87 | ) -> [Int] { 88 | let byteWidth = indexType.byteWidth 89 | var indexes: [Int] = [] 90 | while !bytes.isEmpty { 91 | let index = Int( 92 | bytes 93 | .prefix(upTo: bytes.startIndex + byteWidth) 94 | .withUnsafeBytes { $0.load(as: indexType) } ^ .init(bytes: nonceBytes) 95 | ) 96 | indexes.append(index) 97 | bytes.removeFirst(byteWidth) 98 | } 99 | 100 | return indexes 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/ConfidentialKit/Obfuscation/Techniques/Randomization/Obfuscation+Randomization.swift: -------------------------------------------------------------------------------- 1 | public extension Obfuscation { 2 | 3 | /// A namespace for types that use data randomization as obfuscation technique. 4 | enum Randomization {} 5 | } 6 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Diagnostics.swift: -------------------------------------------------------------------------------- 1 | import SwiftDiagnostics 2 | import SwiftSyntax 3 | 4 | protocol DiagnosticMessageDomain: Sendable { 5 | static func diagnosticID(for messageID: String) -> MessageID 6 | } 7 | 8 | struct MacroExpansionDiagnosticMessage: DiagnosticMessage where Domain: DiagnosticMessageDomain { 9 | 10 | let message: String 11 | 12 | var diagnosticID: MessageID { Domain.diagnosticID(for: messageID) } 13 | 14 | let severity: DiagnosticSeverity 15 | 16 | private let messageID: String 17 | 18 | init(message: String, messageID: String = #function, severity: DiagnosticSeverity) { 19 | self.message = message 20 | self.messageID = messageID 21 | self.severity = severity 22 | } 23 | } 24 | 25 | extension DiagnosticsError { 26 | 27 | init( 28 | node: some SyntaxProtocol, 29 | position: AbsolutePosition? = nil, 30 | message: some DiagnosticMessage, 31 | highlights: [Syntax]? = nil, 32 | notes: [Note] = [], 33 | fixIts: [FixIt] = [] 34 | ) { 35 | self.init( 36 | diagnostics: [ 37 | Diagnostic( 38 | node: node, 39 | position: position, 40 | message: message, 41 | highlights: highlights, 42 | notes: notes, 43 | fixIts: fixIts 44 | ) 45 | ] 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Extensions/SwiftSyntax/TokenKind/TokenKind+Sendable.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | #if !canImport(SwiftSyntax600) 4 | extension TokenKind: @unchecked Swift.Sendable {} 5 | #endif 6 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Extensions/SwiftSyntax/TokenSyntax/TokenSyntax+IsWildcardToken.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension TokenSyntax { 4 | 5 | var isWildcardToken: Bool { tokenKind == .wildcard } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Extensions/SwiftSyntax/VariableDeclSyntax/VariableDeclSyntax+ConvenienceInitializers.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension VariableDeclSyntax { 4 | 5 | init( 6 | leadingTrivia: Trivia = [], 7 | attributes: AttributeListSyntax = [], 8 | modifiers: DeclModifierListSyntax = [], 9 | _ bindingSpecifier: Keyword, 10 | name: String, 11 | type: TypeSyntax? = nil, 12 | initializer: InitializerClauseSyntax? = nil 13 | ) { 14 | self.init( 15 | leadingTrivia: leadingTrivia, 16 | attributes: attributes, 17 | modifiers: modifiers, 18 | bindingSpecifier, 19 | name: PatternSyntax(IdentifierPatternSyntax(identifier: .identifier(name))), 20 | type: type.map { TypeAnnotationSyntax(type: $0) }, 21 | initializer: initializer 22 | ) 23 | } 24 | 25 | init( 26 | leadingTrivia: Trivia = [], 27 | attributes: AttributeListSyntax = [], 28 | modifiers: DeclModifierListSyntax = [], 29 | _ bindingSpecifier: Keyword, 30 | name: String, 31 | type: TypeSyntax, 32 | accessors: AccessorBlockSyntax.Accessors, 33 | trailingTrivia: Trivia = [] 34 | ) { 35 | self.init( 36 | leadingTrivia: leadingTrivia, 37 | attributes: attributes, 38 | modifiers: modifiers, 39 | bindingSpecifier: .keyword(bindingSpecifier), 40 | bindings: .init( 41 | [ 42 | PatternBindingSyntax( 43 | pattern: IdentifierPatternSyntax( 44 | identifier: .identifier(name) 45 | ), 46 | typeAnnotation: TypeAnnotationSyntax(type: type), 47 | accessorBlock: .init(accessors: accessors) 48 | ) 49 | ] 50 | ), 51 | trailingTrivia: trailingTrivia 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Macros/Obfuscated/ObfuscatedMacro+Constants.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension ObfuscatedMacro { 4 | 5 | enum C { 6 | 7 | static let genericParameterName: String = "PlainValue" 8 | 9 | enum ExpandedCode { 10 | 11 | enum DeobfuscateDataFunction { 12 | 13 | static var defaultDataArgumentLabel: TokenSyntax { .wildcardToken() } 14 | static var defaultNonceArgumentLabel: TokenSyntax { .identifier("nonce") } 15 | } 16 | 17 | enum ProjectionVariable { 18 | 19 | static let allowedModifiers: [TokenKind] = [ 20 | .keyword(.private), 21 | .keyword(.internal), 22 | .keyword(.package), 23 | .keyword(.public), 24 | .keyword(.static) 25 | ] 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Macros/Obfuscated/ObfuscatedMacro+Diagnostics.swift: -------------------------------------------------------------------------------- 1 | import SwiftDiagnostics 2 | import SwiftSyntax 3 | 4 | extension ObfuscatedMacro { 5 | 6 | enum DiagnosticErrors { 7 | 8 | private typealias DiagnosticMessage = MacroExpansionDiagnosticMessage 9 | 10 | static func macroCanOnlyBeAttachedToVariableDeclaration(node: some SyntaxProtocol) -> some Error { 11 | DiagnosticsError( 12 | node: node, 13 | message: DiagnosticMessage( 14 | message: "'@Obfuscated' can only be attached to a variable declaration", 15 | severity: .error 16 | ) 17 | ) 18 | } 19 | 20 | static func macroDoesNotSupportClosureExpressions( 21 | node: some SyntaxProtocol, 22 | highlight: some SyntaxProtocol 23 | ) -> some Error { 24 | DiagnosticsError( 25 | node: node, 26 | message: DiagnosticMessage( 27 | message: """ 28 | '@Obfuscated' does not support closure expressions, use function reference instead 29 | """, 30 | severity: .error 31 | ), 32 | highlights: [Syntax(highlight)] 33 | ) 34 | } 35 | 36 | static func macroMissingArgumentForParameter(at position: Int, node: some SyntaxProtocol) -> some Error { 37 | DiagnosticsError( 38 | node: node, 39 | message: DiagnosticMessage( 40 | message: "Missing argument for parameter #\(position) in macro expansion", 41 | severity: .error 42 | ) 43 | ) 44 | } 45 | 46 | static func macroMissingGenericParameter(named name: String, node: some SyntaxProtocol) -> some Error { 47 | DiagnosticsError( 48 | node: node, 49 | message: DiagnosticMessage( 50 | message: "Generic parameter '\(name)' could not be inferred", 51 | severity: .error 52 | ) 53 | ) 54 | } 55 | 56 | static func secretVariableDeclarationMustHaveValidIdentifier(node: some SyntaxProtocol) -> some Error { 57 | DiagnosticsError( 58 | node: node, 59 | message: DiagnosticMessage( 60 | message: "Secret variable declaration must have a valid identifier", 61 | severity: .error 62 | ) 63 | ) 64 | } 65 | } 66 | } 67 | 68 | extension ObfuscatedMacro.DiagnosticErrors: DiagnosticMessageDomain { 69 | 70 | static func diagnosticID(for messageID: String) -> MessageID { 71 | .init(domain: "\(ObfuscatedMacro.self)", id: "\(self).\(messageID)") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/Plugin.swift: -------------------------------------------------------------------------------- 1 | #if canImport(SwiftCompilerPlugin) 2 | import SwiftCompilerPlugin 3 | import SwiftSyntaxMacros 4 | 5 | @main 6 | struct ConfidentialCompilerPlugin: CompilerPlugin { 7 | 8 | let providingMacros: [Macro.Type] = [ 9 | ObfuscatedMacro.self 10 | ] 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /Sources/ConfidentialKitMacros/SyntaxNodes/Expressions/PreconditionFailureFunctionCallExpr.swift: -------------------------------------------------------------------------------- 1 | import SwiftSyntax 2 | 3 | extension FunctionCallExprSyntax { 4 | 5 | static func makePreconditionFailureFunctionCallExpr( 6 | message: StringLiteralExprSyntax 7 | ) -> Self { 8 | .init( 9 | callee: DeclReferenceExprSyntax( 10 | baseName: .identifier("preconditionFailure") 11 | ) 12 | ) { 13 | LabeledExprSyntax( 14 | expression: message 15 | ) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/ConfidentialUtils/Extensions/CryptoKit/SymmetricKeySize/SymmetricKeySize+ByteCount.swift: -------------------------------------------------------------------------------- 1 | import CryptoKit 2 | 3 | package extension SymmetricKeySize { 4 | 5 | /// The number of bytes in the key. 6 | /// 7 | /// The returned value is not rounded up, since ``init(bitCount:)`` only accepts 8 | /// positive integers that are a multiple of 8. 9 | @inlinable 10 | @inline(__always) 11 | var byteCount: Int { bitCount / 8 } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/ConfidentialUtils/Extensions/Foundation/NSData/NSData.CompressionAlgorithm+MagicNumbers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | package extension NSData.CompressionAlgorithm { 4 | 5 | /// The number of bytes in the header magic number. 6 | @inlinable 7 | @inline(__always) 8 | var headerMagicByteCount: Int { 9 | switch self { 10 | case .lzfse, .lz4: 11 | return 4 12 | case .lzma: 13 | return 6 14 | case .zlib: 15 | /* 16 | Apple's zlib implementation uses raw DEFLATE format 17 | as described in IETF RFC 1951. 18 | */ 19 | return 0 20 | @unknown default: 21 | return 0 22 | } 23 | } 24 | 25 | /// The number of bytes in the footer magic number. 26 | @inlinable 27 | @inline(__always) 28 | var footerMagicByteCount: Int { 29 | switch self { 30 | case .lzfse, .lz4: 31 | return 4 32 | case .lzma: 33 | return 2 34 | case .zlib: 35 | return 0 36 | @unknown default: 37 | return 0 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/ConfidentialUtils/Extensions/Swift/BinaryInteger/BinaryInteger+Bytes.swift: -------------------------------------------------------------------------------- 1 | package extension BinaryInteger { 2 | 3 | /// This value's binary representation, as a sequence of contiguous bytes. 4 | @inlinable 5 | @inline(__always) 6 | var bytes: [UInt8] { withUnsafeBytes(of: self, [UInt8].init) } 7 | 8 | /// Creates a new instance from the given binary representation. 9 | /// 10 | /// - Parameter bytes: A collection containing the bytes of this value’s binary representation, 11 | /// in order from the least significant to most significant. 12 | @inlinable 13 | @inline(__always) 14 | init(bytes: Bytes) where Bytes.Element == UInt8 { 15 | var bytes = [Bytes.Element](bytes) 16 | let size = MemoryLayout.stride 17 | if bytes.count < size { 18 | bytes.append(contentsOf: (bytes.count.. 1 45 | else { 46 | fatalError("Unexpected metatype string representation") 47 | } 48 | 49 | return .init(fullyQualifiedName.suffix(from: index).dropFirst()) 50 | } 51 | 52 | /// The name of the type. 53 | @inlinable 54 | @inline(__always) 55 | var name: String { 56 | .init(describing: type) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/_ConfidentialKit/Obfuscation/Macros/Obfuscated.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | /// Creates a projection variable that exposes a plain secret value. 5 | /// 6 | /// The generated projection variable returns a plain secret value after transforming obfuscated 7 | /// secret’s data using the specified `deobfuscateData` function and JSON decoding the 8 | /// data into a value of the type specified by the `PlainValue` generic argument. 9 | /// 10 | /// The name of the projection variable is the same as the variable to which the macro is attached, 11 | /// except it begins with a dollar sign ($). 12 | /// 13 | /// - Important: This macro is only meant to be attached to variables of type 14 | /// ``/ConfidentialKit/Obfuscation/Secret``. 15 | /// 16 | /// - Parameter deobfuscateData: A reference to the deobfuscation function. If you omit 17 | /// the argument labels, then the default `(_:nonce:)` 18 | /// will be used. Note that closure expressions are not 19 | /// supported. 20 | @attached(peer, names: prefixed(`$`)) 21 | public macro Obfuscated( 22 | _ deobfuscateData: (Data, Obfuscation.Nonce) throws -> Data 23 | ) 24 | = #externalMacro(module: "ConfidentialKitMacros", type: "ObfuscatedMacro") 25 | -------------------------------------------------------------------------------- /Sources/swift-confidential/Errors/RuntimeError.swift: -------------------------------------------------------------------------------- 1 | struct RuntimeError: Error, CustomStringConvertible { 2 | let description: String 3 | } 4 | -------------------------------------------------------------------------------- /Sources/swift-confidential/Subcommands/Obfuscate.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import ConfidentialCore 3 | import Foundation 4 | import Yams 5 | 6 | extension SwiftConfidential { 7 | 8 | struct Obfuscate: ParsableCommand { 9 | 10 | static let configuration = CommandConfiguration( 11 | commandName: "obfuscate", 12 | abstract: "Obfuscate secret literals.", 13 | discussion: """ 14 | The generated Swift code provides accessors for each secret literal, \ 15 | grouped into namespaces as defined in configuration file. \ 16 | The accessor allows for retrieving a deobfuscated literal at \ 17 | runtime. 18 | """ 19 | ) 20 | 21 | @Option( 22 | help: "The path to a Confidential configuration file.", 23 | transform: URL.init(fileURLWithPath:) 24 | ) 25 | var configuration: URL 26 | 27 | @Option( 28 | help: "The path to an output source file where the generated Swift code is to be written.", 29 | transform: URL.init(fileURLWithPath:) 30 | ) 31 | var output: URL 32 | 33 | private var fileManager: FileManager { .default } 34 | 35 | mutating func run() throws { 36 | guard fileManager.isReadableFile(atPath: configuration.path) else { 37 | throw RuntimeError(description: #"Unable to read configuration file at "\#(configuration.path)""#) 38 | } 39 | 40 | let configurationYAML = try Data(contentsOf: configuration) 41 | let configuration = try YAMLDecoder().decode(Configuration.self, from: configurationYAML) 42 | 43 | var sourceFileSpec = try Parsers.ModelTransform.SourceFileSpec() 44 | .parse(configuration) 45 | 46 | try SourceObfuscator().obfuscate(&sourceFileSpec) 47 | 48 | let sourceFileText = try Parsers.CodeGeneration.SourceFile() 49 | .parse(&sourceFileSpec) 50 | 51 | guard fileManager.createFile(atPath: output.path, contents: .none) else { 52 | throw RuntimeError(description: #"Failed to create output file at "\#(output.path)""#) 53 | } 54 | 55 | try sourceFileText.write(to: output) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/swift-confidential/SwiftConfidential.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | 3 | @main 4 | struct SwiftConfidential: ParsableCommand { 5 | 6 | static let configuration = CommandConfiguration( 7 | commandName: "swift-confidential", 8 | abstract: "A command-line tool to obfuscate secret literals embedded in Swift project.", 9 | subcommands: [ 10 | Obfuscate.self 11 | ], 12 | defaultSubcommand: Obfuscate.self 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /Tests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | #################### 2 | # RULES STATE # 3 | #################### 4 | 5 | disabled_rules: 6 | - force_cast 7 | - force_try 8 | - force_unwrapping 9 | - implicitly_unwrapped_optional 10 | 11 | opt_in_rules: 12 | - single_test_class 13 | - test_case_accessibility 14 | - xct_specific_matcher 15 | 16 | #################### 17 | # RULES CONFIG # 18 | #################### 19 | 20 | ### Idiomatic ### 21 | type_name: 22 | max_length: 60 23 | allowed_symbols: 24 | - "_" 25 | 26 | ### Metrics ### 27 | function_body_length: 28 | warning: 80 29 | closure_body_length: 30 | warning: 50 31 | line_length: 32 | ignores_function_declarations: true 33 | ignores_interpolated_strings: true 34 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Extensions/Foundation/Data/Data+HexStringTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class Data_HexStringTests: XCTestCase { 5 | 6 | private let bytesStub: [UInt8] = [0xff, 0x36, 0xb4, 0xcb, 0x34, 0xff, 0x8c, 0x8f, 0xbf, 0x0f, 0x43, 0x45] 7 | 8 | func test_givenEmptyData_whenHexEncodedStringComponents_thenReturnsEmptyArray() { 9 | // given 10 | let data = Data() 11 | 12 | // when 13 | let hexComponents = data.hexEncodedStringComponents() 14 | 15 | // then 16 | XCTAssertTrue(hexComponents.isEmpty) 17 | } 18 | 19 | func test_givenNonEmptyData_whenHexEncodedStringComponents_thenReturnsExpectedComponents() { 20 | // given 21 | let data = Data(bytesStub) 22 | 23 | // when 24 | let hexComponents = data.hexEncodedStringComponents() 25 | 26 | // then 27 | XCTAssertEqual( 28 | ["ff", "36", "b4", "cb", "34", "ff", "8c", "8f", "bf", "0f", "43", "45"], 29 | hexComponents 30 | ) 31 | } 32 | 33 | func test_givenNonEmptyData_whenHexEncodedStringComponentsOptionsUpperCase_thenReturnsExpectedComponents() { 34 | // given 35 | let data = Data(bytesStub) 36 | 37 | // when 38 | let hexComponents = data.hexEncodedStringComponents(options: .upperCase) 39 | 40 | // then 41 | XCTAssertEqual( 42 | ["FF", "36", "B4", "CB", "34", "FF", "8C", "8F", "BF", "0F", "43", "45"], 43 | hexComponents 44 | ) 45 | } 46 | 47 | func test_givenNonEmptyData_whenHexEncodedStringComponentsOptionsNumericLiteral_thenReturnsExpectedComponents() { 48 | // given 49 | let data = Data(bytesStub) 50 | 51 | // when 52 | let hexComponents = data.hexEncodedStringComponents(options: .numericLiteral) 53 | 54 | // then 55 | XCTAssertEqual( 56 | ["0xff", "0x36", "0xb4", "0xcb", "0x34", "0xff", "0x8c", "0x8f", "0xbf", "0x0f", "0x43", "0x45"], 57 | hexComponents 58 | ) 59 | } 60 | 61 | func test_givenNonEmptyData_whenHexEncodedStringComponentsOptionsUpperCaseAndNumericLiteral_thenReturnsExpectedComponents() { 62 | // given 63 | let data = Data(bytesStub) 64 | 65 | // when 66 | let hexComponents = data.hexEncodedStringComponents(options: [.upperCase, .numericLiteral]) 67 | 68 | // then 69 | XCTAssertEqual( 70 | ["0xFF", "0x36", "0xB4", "0xCB", "0x34", "0xFF", "0x8C", "0x8F", "0xBF", "0x0F", "0x43", "0x45"], 71 | hexComponents 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Extensions/Swift/Encodable/Encodable+TypeErasureTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class Encodable_TypeErasureTests: XCTestCase { 5 | 6 | func test_givenTypeErasedEncodable_whenJSONEncoded_thenNoThrowAndUnderlyingEncodableProducesExpectedResult() { 7 | // given 8 | let encodableValue = "Test" 9 | let encodableSpy = EncodableSpy() 10 | encodableSpy.encodableValue = encodableValue 11 | let anyEncodable = encodableSpy.eraseToAnyEncodable() 12 | 13 | // when & then 14 | var result: Data = .init() 15 | XCTAssertNoThrow( 16 | result = try JSONEncoder().encode(anyEncodable) 17 | ) 18 | XCTAssertEqual(1, encodableSpy.encodeCallCount) 19 | XCTAssertEqual(#""\#(encodableValue)""#, String(data: result, encoding: .utf8)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Extensions/Swift/FixedWidthInteger/FixedWidthInteger+SecureRandomTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class FixedWidthInteger_SecureRandomTests: XCTestCase { 5 | 6 | func test_givenFixedWidthIntegerTypes_whenSecureRandom_thenReturnsNonZeroValues() throws { 7 | // given 8 | let types: [Any] = [ 9 | UInt8.self, UInt16.self, UInt32.self, UInt64.self, 10 | Int8.self, Int16.self, Int32.self, Int64.self 11 | ] 12 | 13 | // when 14 | let values = try types.map { try ($0 as! any FixedWidthInteger.Type).secureRandom() } 15 | 16 | // then 17 | values.forEach { 18 | XCTAssertNotEqual(.zero, $0.nonzeroBitCount) 19 | } 20 | } 21 | 22 | func test_givenSecureRandomNumberSource_whenSecureRandom_thenReturnsExpectedValue() throws { 23 | // given 24 | let source: FixedWidthInteger.SecureRandomNumberSource = { bytes in 25 | (0.. \(expectedFuncReturnTypeName) { 54 | try \(dataCompressorFullyQualifiedName)(algorithm: .\(compressionAlgorithmStub.name)) 55 | .deobfuscate( 56 | try \(dataShufflerFullyQualifiedName)() 57 | .deobfuscate( 58 | try \(dataCrypterFullyQualifiedName)(algorithm: .\(encryptionAlgorithmStub.name)) 59 | .deobfuscate(\(expectedFuncDataParamName), nonce: \(expectedFuncNonceParamName)), 60 | nonce: \(expectedFuncNonceParamName) 61 | ), 62 | nonce: \(expectedFuncNonceParamName) 63 | ) 64 | } 65 | """, 66 | .init(describing: functionDeclSyntax) 67 | ) 68 | XCTAssertTrue(algorithm.isEmpty) 69 | } 70 | 71 | func test_givenEmptyAlgorithm_whenParse_thenThrowsExpectedError() { 72 | // given 73 | var algorithm = Algorithm() 74 | let parser = DeobfuscateDataFunctionDeclParser(functionNestingLevel: .zero) 75 | 76 | // when & then 77 | XCTAssertThrowsError(try parser.parse(&algorithm)) { error in 78 | XCTAssertEqual( 79 | "Obfuscation algorithm must consist of at least one obfuscation step.", 80 | "\(error)" 81 | ) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/CodeGeneration/NamespaceMembersParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | import SwiftBasicFormat 5 | import SwiftSyntax 6 | 7 | final class NamespaceMembersParserTests: XCTestCase { 8 | 9 | private typealias Secret = SourceFileSpec.Secret 10 | private typealias SecretDeclParserSpy = ParserSpy 11 | 12 | private let secretDeclStub = VariableDeclSyntax.makeSecretVariableDecl( 13 | dataProjectionAttribute: AttributeSyntax( 14 | attributeName: IdentifierTypeSyntax(name: .identifier("Test")) 15 | ), 16 | accessModifier: .internal, 17 | bindingSpecifier: .let, 18 | name: "secret", 19 | dataArgumentExpression: ArrayExprSyntax { 20 | ArrayElementSyntax( 21 | expression: IntegerLiteralExprSyntax(literal: "0x20"), 22 | trailingComma: .commaToken() 23 | ) 24 | ArrayElementSyntax( 25 | expression: IntegerLiteralExprSyntax(literal: "0x20") 26 | ) 27 | }, 28 | nonceArgumentExpression: IntegerLiteralExprSyntax(literal: "123456789") 29 | ) 30 | private let secretsStub: ArraySlice = [ 31 | .StubFactory.makeInternalSecret(), 32 | .StubFactory.makeInternalSecret() 33 | ] 34 | 35 | private var secretDeclParserSpy: SecretDeclParserSpy! 36 | 37 | private var sut: NamespaceMembersParser! 38 | 39 | override func setUp() { 40 | super.setUp() 41 | secretDeclParserSpy = .init(result: secretDeclStub) 42 | sut = .init(secretDeclParser: secretDeclParserSpy) 43 | } 44 | 45 | override func tearDown() { 46 | sut = nil 47 | secretDeclParserSpy = nil 48 | super.tearDown() 49 | } 50 | 51 | func test_givenSecrets_whenParse_thenReturnsExpectedMemberDeclarationsAndSecretsIsEmpty() throws { 52 | // given 53 | var secrets = secretsStub 54 | 55 | // when 56 | let memberDeclarations: [MemberBlockItemSyntax] = try sut.parse(&secrets) 57 | 58 | // then 59 | let format: BasicFormat = .init(indentationWidth: .spaces(0)) 60 | let membersSourceText = memberDeclarations 61 | .map { $0.formatted(using: format) } 62 | .map { String(describing: $0) } 63 | let expectedMembersSourceText = secretsStub 64 | .map { _ in MemberBlockItemSyntax(leadingTrivia: .newline, decl: secretDeclStub) } 65 | .map { $0.formatted(using: format) } 66 | .map { String(describing: $0) } 67 | XCTAssertEqual(expectedMembersSourceText, membersSourceText) 68 | XCTAssertEqual(secretsStub, secretDeclParserSpy.parseRecordedInput[...]) 69 | XCTAssertTrue(secrets.isEmpty) 70 | } 71 | 72 | func test_givenSecretDeclParserFailsOnFirstSecret_whenParse_thenThrowsErrorAndSecretsLeftIntact() { 73 | // given 74 | var secrets = secretsStub 75 | secretDeclParserSpy.consumeInput = { _ in throw ErrorDummy() } 76 | 77 | // when & then 78 | XCTAssertThrowsError(try sut.parse(&secrets)) 79 | XCTAssertEqual([secretsStub.first!], secretDeclParserSpy.parseRecordedInput) 80 | XCTAssertEqual(secretsStub, secrets) 81 | } 82 | 83 | func test_givenSecretDeclParserFailsOnSecondSecret_whenParse_thenThrowsErrorAndSecretsContainsAllButFirstSecret() { 84 | // given 85 | var secrets = secretsStub 86 | var secretCount: Int = .zero 87 | secretDeclParserSpy.consumeInput = { _ in 88 | guard secretCount == .zero else { 89 | throw ErrorDummy() 90 | } 91 | secretCount += 1 92 | } 93 | 94 | // when & then 95 | XCTAssertThrowsError(try sut.parse(&secrets)) 96 | XCTAssertEqual(secretsStub, secretDeclParserSpy.parseRecordedInput[...]) 97 | XCTAssertEqual(secretsStub.dropFirst(), secrets) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/CodeGeneration/SecretVariableDeclParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | import ConfidentialKit 5 | import ConfidentialUtils 6 | import SwiftSyntax 7 | 8 | final class SecretVariableDeclParserTests: XCTestCase { 9 | 10 | private let dataProjectionAttributeNameStub = "ConfidentialKit.Obfuscated" 11 | private let deobfuscateArgumentNameStub = "deobfuscate" 12 | private let deobfuscateDataFuncNameStub = "deobfuscateData" 13 | 14 | func test_givenSecretWithPropertyWrapperAttribute_whenParse_thenReturnsExpectedSecretDecl() throws { 15 | // given 16 | let secrets = [ 17 | makeSecretStub( 18 | accessModifier: .internal, 19 | attribute: dataProjectionAttribute(isPropertyWrapper: true) 20 | ), 21 | makeSecretStub( 22 | accessModifier: .package, 23 | attribute: dataProjectionAttribute(isPropertyWrapper: true) 24 | ), 25 | makeSecretStub( 26 | accessModifier: .public, 27 | attribute: dataProjectionAttribute(isPropertyWrapper: true) 28 | ) 29 | ] 30 | 31 | // when 32 | let secretDecls = try secrets.map { secret in 33 | (secret, try SecretVariableDeclParser().parse(secret)) 34 | } 35 | 36 | // then 37 | let expectedDeclTypeName = TypeInfo(of: Obfuscation.Secret.self).fullyQualifiedName 38 | secretDecls.forEach { secret, secretDecl in 39 | XCTAssertEqual( 40 | """ 41 | 42 | @\(dataProjectionAttributeNameStub)(\(deobfuscateArgumentNameStub): \(deobfuscateDataFuncNameStub)) 43 | \(secret.accessModifier) static var \(secret.name): \(expectedDeclTypeName) = \ 44 | .init(data: [0x20, 0x20], nonce: 123456789) 45 | """, 46 | .init(describing: syntax(from: secretDecl)) 47 | ) 48 | } 49 | } 50 | 51 | func test_givenSecretWithMacroAttribute_whenParse_thenReturnsExpectedSecretDecl() throws { 52 | // given 53 | let secrets = [ 54 | makeSecretStub( 55 | accessModifier: .internal, 56 | attribute: dataProjectionAttribute(isPropertyWrapper: false) 57 | ), 58 | makeSecretStub( 59 | accessModifier: .package, 60 | attribute: dataProjectionAttribute(isPropertyWrapper: false) 61 | ), 62 | makeSecretStub( 63 | accessModifier: .public, 64 | attribute: dataProjectionAttribute(isPropertyWrapper: false) 65 | ) 66 | ] 67 | 68 | // when 69 | let secretDecls = try secrets.map { secret in 70 | (secret, try SecretVariableDeclParser().parse(secret)) 71 | } 72 | 73 | // then 74 | let expectedDeclTypeName = TypeInfo(of: Obfuscation.Secret.self).fullyQualifiedName 75 | secretDecls.forEach { secret, secretDecl in 76 | XCTAssertEqual( 77 | """ 78 | 79 | @\(dataProjectionAttributeNameStub)(\(deobfuscateArgumentNameStub): \(deobfuscateDataFuncNameStub)) 80 | \(secret.accessModifier) static let \(secret.name): \(expectedDeclTypeName) = \ 81 | .init(data: [0x20, 0x20], nonce: 123456789) 82 | """, 83 | .init(describing: syntax(from: secretDecl)) 84 | ) 85 | } 86 | } 87 | } 88 | 89 | private extension SecretVariableDeclParserTests { 90 | 91 | typealias Secret = SourceFileSpec.Secret 92 | 93 | func makeSecretStub( 94 | accessModifier: Secret.AccessModifier, 95 | attribute: Secret.DataProjectionAttribute 96 | ) -> Secret { 97 | .init( 98 | accessModifier: accessModifier, 99 | name: "secret", 100 | data: .init([0x20, 0x20]), 101 | nonce: 123456789, 102 | dataProjectionAttribute: attribute 103 | ) 104 | } 105 | 106 | func dataProjectionAttribute(isPropertyWrapper: Bool) -> Secret.DataProjectionAttribute { 107 | .init( 108 | name: dataProjectionAttributeNameStub, 109 | arguments: [(label: deobfuscateArgumentNameStub, value: deobfuscateDataFuncNameStub)], 110 | isPropertyWrapper: isPropertyWrapper 111 | ) 112 | } 113 | } 114 | 115 | private extension SecretVariableDeclParserTests { 116 | 117 | func syntax(from secretDecl: any DeclSyntaxProtocol) -> Syntax { 118 | secretDecl 119 | .formatted(using: .init(indentationWidth: .spaces(0))) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/CodeGeneration/SourceFileParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | import SwiftSyntax 5 | 6 | final class SourceFileParserTests: XCTestCase { 7 | 8 | private typealias CodeBlockParserSpy = ParserSpy 9 | 10 | private let enumNameStub = "Secrets" 11 | 12 | private var codeBlockParserSpy: CodeBlockParserSpy! 13 | 14 | private var sut: SourceFileParser! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | codeBlockParserSpy = .init(result: codeBlockStub) 19 | codeBlockParserSpy.consumeInput = { 20 | $0.algorithm = [] 21 | $0.secrets = [:] 22 | } 23 | sut = .init { 24 | codeBlockParserSpy! 25 | } 26 | } 27 | 28 | override func tearDown() { 29 | sut = nil 30 | codeBlockParserSpy = nil 31 | super.tearDown() 32 | } 33 | 34 | func test_givenSourceFileSpec_whenParse_thenReturnsExpectedSourceFileTextAndInputIsConsumed() throws { 35 | // given 36 | var sourceFileSpec = SourceFileSpec.StubFactory.makeSpec( 37 | secrets: [ 38 | .create(identifier: enumNameStub): [] 39 | ] 40 | ) 41 | 42 | // when 43 | let sourceFileText: SourceFileText = try sut.parse(&sourceFileSpec) 44 | 45 | // then 46 | XCTAssertEqual( 47 | """ 48 | 49 | enum \(enumNameStub) { 50 | } 51 | """, 52 | .init(describing: sourceFileText) 53 | ) 54 | XCTAssertTrue(sourceFileSpec.algorithm.isEmpty) 55 | XCTAssertTrue(sourceFileSpec.secrets.isEmpty) 56 | } 57 | 58 | func test_givenCodeBlockParserFails_whenParse_thenThrowsError() { 59 | // given 60 | var sourceFileSpec = SourceFileSpec.StubFactory.makeSpec() 61 | codeBlockParserSpy.consumeInput = { _ in throw ErrorDummy() } 62 | 63 | // when & then 64 | XCTAssertThrowsError(try sut.parse(&sourceFileSpec)) 65 | } 66 | } 67 | 68 | private extension SourceFileParserTests { 69 | 70 | var codeBlockStub: [CodeBlockItemSyntax] { 71 | [ 72 | .init( 73 | leadingTrivia: .newline, 74 | item: .init( 75 | EnumDeclSyntax( 76 | name: .identifier(enumNameStub), 77 | memberBlock: MemberBlockSyntax( 78 | leftBrace: .leftBraceToken(leadingTrivia: .spaces(1)), 79 | members: [] 80 | ) 81 | ) 82 | ) 83 | ) 84 | ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/ModelTransform/AlgorithmParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class AlgorithmParserTests: XCTestCase { 5 | 6 | private typealias ObfuscationStepParserSpy = ParserSpy 7 | 8 | private let obfuscationStepStub = "test" 9 | private lazy var algorithmStub = (0..<2).map { _ in obfuscationStepStub }[...] 10 | 11 | private var obfuscationStepParserSpy: ObfuscationStepParserSpy! 12 | 13 | private var sut: AlgorithmParser! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | obfuscationStepParserSpy = .init(result: .init(technique: .compression(algorithm: .lzfse))) 18 | obfuscationStepParserSpy.consumeInput = { $0 = "" } 19 | sut = .init(obfuscationStepParser: obfuscationStepParserSpy) 20 | } 21 | 22 | override func tearDown() { 23 | sut = nil 24 | obfuscationStepParserSpy = nil 25 | super.tearDown() 26 | } 27 | 28 | func test_givenConfiguration_whenParse_thenReturnsExpectedValueAndConfigurationAlgorithmIsEmpty() throws { 29 | // given 30 | var configuration = Configuration.StubFactory.makeConfiguration(algorithm: algorithmStub) 31 | 32 | // when 33 | let algorithm = try sut.parse(&configuration) 34 | 35 | // then 36 | XCTAssertEqual( 37 | (0.. 10 | 11 | private let inputStub = "test"[...] 12 | 13 | private var compressionTechniqueParserSpy: TechniqueParserSpy! 14 | private var randomizationTechniqueParserSpy: TechniqueParserSpy! 15 | 16 | private var sut: ObfuscationStepParser< 17 | OneOfBuilder.OneOf2 18 | >! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | compressionTechniqueParserSpy = .init(result: .compression(algorithm: .lz4)) 23 | randomizationTechniqueParserSpy = .init(result: .randomization) 24 | sut = ObfuscationStepParser { 25 | compressionTechniqueParserSpy! 26 | randomizationTechniqueParserSpy! 27 | } 28 | } 29 | 30 | override func tearDown() { 31 | sut = nil 32 | randomizationTechniqueParserSpy = nil 33 | compressionTechniqueParserSpy = nil 34 | super.tearDown() 35 | } 36 | 37 | func test_givenFirstTechniqueParserSucceeds_whenParse_thenReturnsExpectedValue() throws { 38 | // given 39 | var input = inputStub 40 | compressionTechniqueParserSpy.consumeInput = { _ in } 41 | randomizationTechniqueParserSpy.consumeInput = { _ in throw ErrorDummy() } 42 | 43 | // when 44 | let obfuscationStep = try sut.parse(&input) 45 | 46 | // then 47 | XCTAssertEqual(.init(technique: compressionTechniqueParserSpy.result), obfuscationStep) 48 | XCTAssertEqual([inputStub], compressionTechniqueParserSpy.parseRecordedInput) 49 | XCTAssertEqual([], randomizationTechniqueParserSpy.parseRecordedInput) 50 | } 51 | 52 | func test_givenSecondTechniqueParserSucceeds_whenParse_thenReturnsExpectedValue() throws { 53 | // given 54 | var input = inputStub 55 | compressionTechniqueParserSpy.consumeInput = { _ in throw ErrorDummy() } 56 | randomizationTechniqueParserSpy.consumeInput = { _ in } 57 | 58 | // when 59 | let obfuscationStep = try sut.parse(&input) 60 | 61 | // then 62 | XCTAssertEqual(.init(technique: randomizationTechniqueParserSpy.result), obfuscationStep) 63 | XCTAssertEqual([inputStub], compressionTechniqueParserSpy.parseRecordedInput) 64 | XCTAssertEqual([inputStub], randomizationTechniqueParserSpy.parseRecordedInput) 65 | } 66 | 67 | func test_givenBothTechniqueParsersFail_whenParse_thenThrowsError() { 68 | // given 69 | var input = inputStub 70 | compressionTechniqueParserSpy.consumeInput = { _ in throw ErrorDummy() } 71 | randomizationTechniqueParserSpy.consumeInput = { _ in throw ErrorDummy() } 72 | 73 | // when & then 74 | XCTAssertThrowsError(try sut.parse(&input)) 75 | XCTAssertEqual([inputStub], compressionTechniqueParserSpy.parseRecordedInput) 76 | XCTAssertEqual([inputStub], randomizationTechniqueParserSpy.parseRecordedInput) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/ModelTransform/RandomizationTechniqueParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class RandomizationTechniqueParserTests: XCTestCase { 5 | 6 | private var sut: RandomizationTechniqueParser! 7 | 8 | override func setUp() { 9 | super.setUp() 10 | sut = .init() 11 | } 12 | 13 | override func tearDown() { 14 | sut = nil 15 | super.tearDown() 16 | } 17 | 18 | func test_givenValidInput_whenParse_thenReturnsExpectedEnumValueAndInputIsEmpty() throws { 19 | // given 20 | var input = "\(C.Parsing.Keywords.shuffle)"[...] 21 | 22 | // when 23 | let technique = try sut.parse(&input) 24 | 25 | // then 26 | XCTAssertEqual(.randomization, technique) 27 | XCTAssertTrue(input.isEmpty) 28 | } 29 | 30 | func test_givenValidInputWithExtraWhitespaces_whenParse_thenReturnsExpectedEnumValueAndInputIsEmpty() throws { 31 | // given 32 | var input = " \(C.Parsing.Keywords.shuffle)"[...] 33 | 34 | // when 35 | let technique = try sut.parse(&input) 36 | 37 | // then 38 | XCTAssertEqual(.randomization, technique) 39 | XCTAssertTrue(input.isEmpty) 40 | } 41 | 42 | func test_givenInvalidInput_whenParse_thenThrowsErrorAndInputLeftIntact() { 43 | // given 44 | var input = "\(C.Parsing.Keywords.compress)"[...] 45 | 46 | // when & then 47 | XCTAssertThrowsError(try sut.parse(&input)) 48 | XCTAssertEqual("\(C.Parsing.Keywords.compress)", input) 49 | } 50 | 51 | func test_givenInputWithLeadingVerticalWhitespace_whenParse_thenThrowsErrorAndInputLeftIntact() { 52 | // given 53 | var input = """ 54 | 55 | \(C.Parsing.Keywords.shuffle) 56 | """[...] 57 | 58 | // when & then 59 | XCTAssertThrowsError(try sut.parse(&input)) 60 | XCTAssertEqual( 61 | """ 62 | 63 | \(C.Parsing.Keywords.shuffle) 64 | """, 65 | input 66 | ) 67 | } 68 | 69 | func test_givenInputWithUnexpectedTrailingData_whenParse_thenThrowsErrorAndInputEqualsTrailingData() { 70 | // given 71 | var input = "\(C.Parsing.Keywords.shuffle) Test"[...] 72 | 73 | // when & then 74 | XCTAssertThrowsError(try sut.parse(&input)) 75 | XCTAssertEqual(" Test", input) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/ModelTransform/SecretAccessModifierParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class SecretAccessModifierParserTests: XCTestCase { 5 | 6 | private typealias AccessModifier = SourceFileSpec.Secret.AccessModifier 7 | 8 | private var sut: SecretAccessModifierParser! 9 | 10 | override func setUp() { 11 | super.setUp() 12 | sut = .init() 13 | } 14 | 15 | override func tearDown() { 16 | sut = nil 17 | super.tearDown() 18 | } 19 | 20 | func test_givenValidInput_whenParse_thenReturnsExpectedEnumValueAndInputIsEmpty() throws { 21 | // given 22 | var inputData = [ 23 | ""[...], 24 | "internal"[...], 25 | "public"[...] 26 | ] 27 | 28 | // when 29 | let accessModifiers = try inputData.indices.map { 30 | try sut.parse(&inputData[$0]) 31 | } 32 | 33 | // then 34 | let expectedAccessModifiers: [AccessModifier] = [ 35 | .internal, 36 | .internal, 37 | .public 38 | ] 39 | XCTAssertEqual(inputData.count, accessModifiers.count) 40 | XCTAssertEqual(expectedAccessModifiers.count, accessModifiers.count) 41 | accessModifiers.enumerated().forEach { idx, modifier in 42 | XCTAssertEqual(expectedAccessModifiers[idx], modifier) 43 | XCTAssertTrue(inputData[idx].isEmpty) 44 | } 45 | } 46 | 47 | func test_givenValidInputWithExtraWhitespaces_whenParse_thenReturnsExpectedEnumValueAndInputIsEmpty() throws { 48 | // given 49 | var input = " internal"[...] 50 | 51 | // when 52 | let accessModifier = try sut.parse(&input) 53 | 54 | // then 55 | XCTAssertEqual(.internal, accessModifier) 56 | XCTAssertTrue(input.isEmpty) 57 | } 58 | 59 | func test_givenInvalidInput_whenParse_thenThrowsErrorAndInputLeftIntact() { 60 | // given 61 | var input = "private"[...] 62 | 63 | // when & then 64 | XCTAssertThrowsError(try sut.parse(&input)) 65 | XCTAssertEqual("private", input) 66 | } 67 | 68 | func test_givenInputWithVerticalWhitespace_whenParse_thenThrowsErrorAndInputEqualsExpectedRemainder() { 69 | // given 70 | var input = """ 71 | 72 | public 73 | """[...] 74 | 75 | // when & then 76 | XCTAssertThrowsError(try sut.parse(&input)) 77 | XCTAssertEqual( 78 | """ 79 | 80 | public 81 | """, 82 | input 83 | ) 84 | } 85 | 86 | func test_givenInputWithUnexpectedTrailingData_whenParse_thenThrowsErrorAndInputEqualsTrailingData() { 87 | // given 88 | var input = "internal Test"[...] 89 | 90 | // when & then 91 | XCTAssertThrowsError(try sut.parse(&input)) 92 | XCTAssertEqual(" Test", input) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/Parsing/Parsers/ModelTransform/SourceFileSpecParserTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | final class SourceFileSpecParserTests: XCTestCase { 5 | 6 | private typealias AlgorithmParserSpy = ParserSpy 7 | private typealias SecretsParserSpy = ParserSpy 8 | 9 | private let configurationStub: Configuration = { 10 | var configuration = Configuration.StubFactory.makeConfiguration() 11 | configuration.defaultAccessModifier = "internal" 12 | configuration.defaultNamespace = "Secrets" 13 | configuration.experimentalMode = false 14 | configuration.internalImport = false 15 | return configuration 16 | }() 17 | private let algorithmStub: SourceFileSpec.Algorithm = [ 18 | .init(technique: .encryption(algorithm: .chaChaPoly)) 19 | ] 20 | private let secretsStub: SourceFileSpec.Secrets = [ 21 | .extend(identifier: "Secrets", moduleName: "SecretModule"): [.StubFactory.makePublicSecret()] 22 | ] 23 | 24 | private var algorithmParserSpy: AlgorithmParserSpy! 25 | private var secretsParserSpy: SecretsParserSpy! 26 | 27 | private var sut: SourceFileSpecParser! 28 | 29 | override func setUp() { 30 | super.setUp() 31 | algorithmParserSpy = .init(result: algorithmStub) 32 | algorithmParserSpy.consumeInput = { $0.algorithm = [] } 33 | secretsParserSpy = .init(result: secretsStub) 34 | secretsParserSpy.consumeInput = { $0.secrets = [] } 35 | sut = .init( 36 | algorithmParser: algorithmParserSpy, 37 | secretsParser: secretsParserSpy 38 | ) 39 | } 40 | 41 | override func tearDown() { 42 | sut = nil 43 | secretsParserSpy = nil 44 | algorithmParserSpy = nil 45 | super.tearDown() 46 | } 47 | 48 | func test_givenConfiguration_whenParse_thenReturnsExpectedValueAndInputIsConsumed() throws { 49 | // given 50 | var configuration = configurationStub 51 | 52 | // when 53 | let spec: SourceFileSpec = try sut.parse(&configuration) 54 | 55 | // then 56 | let expectedSpec = SourceFileSpec( 57 | algorithm: algorithmStub, 58 | experimentalMode: false, 59 | internalImport: false, 60 | secrets: secretsStub 61 | ) 62 | XCTAssertEqual(expectedSpec, spec) 63 | XCTAssertEqual([configurationStub], algorithmParserSpy.parseRecordedInput) 64 | XCTAssertEqual([configurationStub], secretsParserSpy.parseRecordedInput) 65 | XCTAssertTrue(configuration.algorithm.isEmpty) 66 | XCTAssertNil(configuration.defaultAccessModifier) 67 | XCTAssertNil(configuration.defaultNamespace) 68 | XCTAssertNil(configuration.experimentalMode) 69 | XCTAssertNil(configuration.internalImport) 70 | XCTAssertTrue(configuration.secrets.isEmpty) 71 | } 72 | 73 | func test_givenAlgorithmParserFails_whenParse_thenThrowsError() { 74 | // given 75 | var configuration = configurationStub 76 | algorithmParserSpy.consumeInput = { _ in throw ErrorDummy() } 77 | 78 | // when & then 79 | XCTAssertThrowsError(try sut.parse(&configuration)) 80 | } 81 | 82 | func test_givenSecretsParserFails_whenParse_thenThrowsError() { 83 | // given 84 | var configuration = configurationStub 85 | secretsParserSpy.consumeInput = { _ in throw ErrorDummy() } 86 | 87 | // when & then 88 | XCTAssertThrowsError(try sut.parse(&configuration)) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/SyntaxText/SourceFileTextTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import XCTest 3 | 4 | import SwiftSyntax 5 | 6 | final class SourceFileTextTests: XCTestCase { 7 | 8 | private var temporaryFileURL: URL! 9 | 10 | override func setUp() { 11 | super.setUp() 12 | temporaryFileURL = .init(fileURLWithPath: NSTemporaryDirectory()) 13 | .appendingPathComponent("\(UUID().uuidString).swift") 14 | } 15 | 16 | override func tearDownWithError() throws { 17 | try FileManager.default.removeItem(at: temporaryFileURL) 18 | temporaryFileURL = nil 19 | try super.tearDownWithError() 20 | } 21 | 22 | func test_givenSourceFileTextWithSourceFileSyntax_whenWriteToFile_thenFileContainsExpectedSyntaxText() throws { 23 | // given 24 | let sourceFile = SourceFileSyntax( 25 | statements: CodeBlockItemListSyntax(itemsBuilder: { 26 | CodeBlockItemSyntax( 27 | item: .init( 28 | ImportDeclSyntax( 29 | path: [ImportPathComponentSyntax(name: .identifier("Foundation"))] 30 | ) 31 | ), 32 | trailingTrivia: .newline 33 | ) 34 | CodeBlockItemSyntax( 35 | item: .init( 36 | StructDeclSyntax( 37 | structKeyword: .keyword(.struct, leadingTrivia: .newlines(1)), 38 | name: "Test", 39 | memberBlock: .init( 40 | leftBrace: .leftBraceToken(leadingTrivia: .spaces(1)), 41 | rightBrace: .rightBraceToken() 42 | ) { 43 | VariableDeclSyntax( 44 | leadingTrivia: .spaces(2), 45 | .let, 46 | name: PatternSyntax(stringLiteral: "id"), 47 | type: TypeAnnotationSyntax(type: TypeSyntax(stringLiteral: "UUID")) 48 | ) 49 | VariableDeclSyntax( 50 | leadingTrivia: .spaces(2), 51 | .var, 52 | name: PatternSyntax(stringLiteral: "data"), 53 | type: TypeAnnotationSyntax(type: TypeSyntax(stringLiteral: "Data")) 54 | ) 55 | } 56 | ) 57 | ) 58 | ) 59 | }) 60 | ) 61 | let sourceFileText = SourceFileText(from: sourceFile) 62 | let encoding = String.Encoding.utf8 63 | 64 | // when 65 | try sourceFileText.write(to: temporaryFileURL, encoding: encoding) 66 | 67 | // then 68 | let fileContents = try String(contentsOf: temporaryFileURL, encoding: encoding) 69 | XCTAssertEqual( 70 | """ 71 | import Foundation 72 | 73 | struct Test { 74 | let id: UUID 75 | var data: Data 76 | } 77 | """, 78 | fileContents 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Dummies/ErrorDummy.swift: -------------------------------------------------------------------------------- 1 | struct ErrorDummy: Error, Equatable {} 2 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Spies/DataEncoderSpy.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import Foundation 3 | 4 | final class DataEncoderSpy: DataEncoder { 5 | 6 | var underlyingEncoder: any DataEncoder 7 | 8 | private(set) var encodeRecordedValues: [any Encodable] = [] 9 | 10 | init(underlyingEncoder: any DataEncoder) { 11 | self.underlyingEncoder = underlyingEncoder 12 | } 13 | 14 | func encode(_ value: E) throws -> Data { 15 | encodeRecordedValues.append(value) 16 | return try underlyingEncoder.encode(value) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Spies/EncodableSpy.swift: -------------------------------------------------------------------------------- 1 | final class EncodableSpy: Encodable { 2 | 3 | var encodableValue: Value? 4 | 5 | private(set) var encodeCallCount: Int = .zero 6 | 7 | func encode(to encoder: Encoder) throws { 8 | encodeCallCount += 1 9 | guard let encodableValue = encodableValue else { return } 10 | var container = encoder.singleValueContainer() 11 | try container.encode(encodableValue) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Spies/ObfuscationStepResolverSpy.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | 3 | final class ObfuscationStepResolverSpy: DataObfuscationStepResolver { 4 | 5 | var obfuscationStepReturnValue: any DataObfuscationStep 6 | 7 | private(set) var recordedTechniques: [Technique] = [] 8 | 9 | init(obfuscationStepReturnValue: any DataObfuscationStep) { 10 | self.obfuscationStepReturnValue = obfuscationStepReturnValue 11 | } 12 | 13 | func obfuscationStep(for technique: Technique) -> DataObfuscationStep { 14 | recordedTechniques.append(technique) 15 | return obfuscationStepReturnValue 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Spies/ObfuscationStepSpy.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import ConfidentialKit 3 | import Foundation 4 | 5 | final class ObfuscationStepSpy: DataObfuscationStep { 6 | 7 | var obfuscateReturnValue: Data? 8 | 9 | private(set) var recordedData: [Data] = [] 10 | private(set) var recordedNonces: [Obfuscation.Nonce] = [] 11 | 12 | func obfuscate(_ data: Data, nonce: Obfuscation.Nonce) throws -> Data { 13 | recordedData.append(data) 14 | recordedNonces.append(nonce) 15 | return obfuscateReturnValue ?? data 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Spies/ParameterlessClosureSpy.swift: -------------------------------------------------------------------------------- 1 | final class ParameterlessClosureSpy { 2 | 3 | var result: Result 4 | var error: (any Error)? 5 | 6 | private(set) var callCount: Int = .zero 7 | 8 | init(result: Result) { 9 | self.result = result 10 | } 11 | 12 | func closure() -> Result { 13 | callCount += 1 14 | return result 15 | } 16 | 17 | func closureWithError() throws -> Result { 18 | callCount += 1 19 | if let error = error { throw error } 20 | return result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Spies/ParserSpy.swift: -------------------------------------------------------------------------------- 1 | import Parsing 2 | 3 | final class ParserSpy: Parser { 4 | 5 | var result: Output 6 | var consumeInput: ((inout Input) throws -> Void)? 7 | 8 | private(set) var parseRecordedInput: [Input] = [] 9 | 10 | init(result: Output) { 11 | self.result = result 12 | } 13 | 14 | func parse(_ input: inout Input) throws -> Output { 15 | parseRecordedInput.append(input) 16 | try consumeInput?(&input) 17 | return result 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Stubs/Configuration+StubFactory.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | 3 | extension Configuration { 4 | 5 | enum StubFactory { 6 | 7 | static func makeConfiguration( 8 | algorithm: ArraySlice = [], 9 | experimentalMode: Bool = false, 10 | secrets: ArraySlice = [] 11 | ) -> Configuration { 12 | .init( 13 | algorithm: algorithm, 14 | defaultAccessModifier: .none, 15 | defaultNamespace: .none, 16 | experimentalMode: experimentalMode, 17 | internalImport: .none, 18 | secrets: secrets 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Stubs/SourceFileSpec+StubFactory.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | 3 | extension SourceFileSpec { 4 | 5 | enum StubFactory { 6 | 7 | static func makeSpec( 8 | algorithm: SourceFileSpec.Algorithm = [], 9 | experimentalMode: Bool = false, 10 | internalImport: Bool = false, 11 | secrets: SourceFileSpec.Secrets = [:] 12 | ) -> SourceFileSpec { 13 | .init( 14 | algorithm: algorithm, 15 | experimentalMode: experimentalMode, 16 | internalImport: internalImport, 17 | secrets: secrets 18 | ) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/ConfidentialCoreTests/TestDoubles/Stubs/SourceFileSpec.Secret+StubFactory.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialCore 2 | import ConfidentialKit 3 | import Foundation 4 | 5 | extension SourceFileSpec.Secret { 6 | 7 | enum StubFactory { 8 | 9 | static func makeInternalSecret( 10 | named name: String = "secret", 11 | data: Data = .init(), 12 | nonce: Obfuscation.Nonce = .zero 13 | ) -> SourceFileSpec.Secret { 14 | makeSecret( 15 | accessModifier: .internal, 16 | name: name, 17 | data: data, 18 | nonce: nonce 19 | ) 20 | } 21 | 22 | static func makePackageSecret( 23 | named name: String = "secret", 24 | data: Data = .init(), 25 | nonce: Obfuscation.Nonce = .zero 26 | ) -> SourceFileSpec.Secret { 27 | makeSecret( 28 | accessModifier: .package, 29 | name: name, 30 | data: data, 31 | nonce: nonce 32 | ) 33 | } 34 | 35 | static func makePublicSecret( 36 | named name: String = "secret", 37 | data: Data = .init(), 38 | nonce: Obfuscation.Nonce = .zero 39 | ) -> SourceFileSpec.Secret { 40 | makeSecret( 41 | accessModifier: .public, 42 | name: name, 43 | data: data, 44 | nonce: nonce 45 | ) 46 | } 47 | 48 | static func makeSecret( 49 | from secret: Configuration.Secret, 50 | using encoder: any DataEncoder, 51 | accessModifier: (String?) -> SourceFileSpec.Secret.AccessModifier = { 52 | .init(rawValue: $0 ?? "") ?? .internal 53 | }, 54 | nonce: Obfuscation.Nonce = .zero 55 | ) throws -> SourceFileSpec.Secret { 56 | makeSecret( 57 | accessModifier: accessModifier(secret.accessModifier), 58 | name: secret.name, 59 | data: try encoder.encode(secret.value.underlyingValue), 60 | nonce: nonce 61 | ) 62 | } 63 | 64 | private static func makeSecret( 65 | accessModifier: SourceFileSpec.Secret.AccessModifier, 66 | name: String, 67 | data: Data, 68 | nonce: Obfuscation.Nonce 69 | ) -> SourceFileSpec.Secret { 70 | .init( 71 | accessModifier: accessModifier, 72 | name: name, 73 | data: data, 74 | nonce: nonce, 75 | dataProjectionAttribute: .init( 76 | name: "Test", 77 | arguments: [], 78 | isPropertyWrapper: true 79 | ) 80 | ) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/Obfuscation/Obfuscation+SecretTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialKit 2 | import XCTest 3 | 4 | final class Obfuscation_SecretTests: XCTestCase { 5 | 6 | func test_givenSecretWithTestBytes_whenData_thenReturnsTestBytes() { 7 | // given 8 | let testBytes: [UInt8] = [0x35, 0x9d, 0x82, 0x1f, 0x68, 0x81, 0x02, 0x64] 9 | let secret = Obfuscation.Secret(data: testBytes, nonce: .zero) 10 | 11 | // when 12 | let data = secret.data 13 | 14 | // then 15 | XCTAssertEqual(testBytes, data) 16 | } 17 | 18 | func test_givenSecretWithEmptyByteArray_whenData_thenReturnsEmptyArray() { 19 | // given 20 | let secret = Obfuscation.Secret(data: [], nonce: .zero) 21 | 22 | // when 23 | let data = secret.data 24 | 25 | // then 26 | XCTAssertTrue(data.isEmpty) 27 | } 28 | 29 | func test_givenSecretWithTestNonce_whenNonce_thenReturnsTestNonce() { 30 | // given 31 | let testNonce: Obfuscation.Nonce = .StubFactory.makeNonce() 32 | let secret = Obfuscation.Secret(data: [], nonce: testNonce) 33 | 34 | // when 35 | let nonce = secret.nonce 36 | 37 | // then 38 | XCTAssertEqual(testNonce, nonce) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/Obfuscation/PropertyWrappers/ObfuscatedTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialKit 2 | import XCTest 3 | 4 | final class ObfuscatedTests: XCTestCase { 5 | 6 | private let secretPlainValue: SingleValue = .StubFactory.makeSecretMessage() 7 | private let secretNonce: Obfuscation.Nonce = .StubFactory.makeNonce() 8 | 9 | private var secretStub: Obfuscation.Secret! 10 | private var deobfuscateDataSpy: DeobfuscateDataFuncSpy! 11 | private var dataDecoderSpy: DataDecoderSpy! 12 | 13 | private var sut: Obfuscated! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | secretStub = .StubFactory.makeJSONEncodedSecret( 18 | with: secretPlainValue, 19 | nonce: secretNonce 20 | ) 21 | deobfuscateDataSpy = .init() 22 | dataDecoderSpy = .init(underlyingDecoder: JSONDecoder()) 23 | sut = .init( 24 | wrappedValue: secretStub, 25 | deobfuscateData: deobfuscateDataSpy.deobfuscateData, 26 | decoder: dataDecoderSpy 27 | ) 28 | } 29 | 30 | override func tearDown() { 31 | sut = nil 32 | dataDecoderSpy = nil 33 | deobfuscateDataSpy = nil 34 | secretStub = nil 35 | super.tearDown() 36 | } 37 | 38 | func test_whenWrappedValue_thenReturnsExpectedSecretStub() { 39 | // when 40 | let wrappedValue = sut.wrappedValue 41 | 42 | // then 43 | XCTAssertEqual(secretStub, wrappedValue) 44 | } 45 | 46 | func test_whenProjectedValue_thenReturnsExpectedPlainValue() { 47 | // when 48 | let projectedValue = sut.projectedValue 49 | 50 | // then 51 | XCTAssertEqual(secretPlainValue, projectedValue) 52 | } 53 | 54 | func test_whenProjectedValue_thenDeobfuscateDataFuncCalledOnce() { 55 | // when 56 | _ = sut.projectedValue 57 | 58 | // then 59 | XCTAssertEqual([.init(secretStub.data)], deobfuscateDataSpy.recordedData) 60 | XCTAssertEqual([secretStub.nonce], deobfuscateDataSpy.recordedNonces) 61 | } 62 | 63 | func test_whenProjectedValue_thenDataDecoderCalledOnce() { 64 | // when 65 | _ = sut.projectedValue 66 | 67 | // then 68 | XCTAssertEqual([.init(secretStub.data)], dataDecoderSpy.decodeRecordedData) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/Obfuscation/Techniques/Compression/DataCompressorTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialKit 2 | import XCTest 3 | 4 | final class DataCompressorTests: XCTestCase { 5 | 6 | private typealias SUT = Obfuscation.Compression.DataCompressor 7 | 8 | func test_givenCompressedData_whenDeobfuscate_thenReturnsDecompressedData() throws { 9 | // given 10 | let compressedData = compressedDataStub 11 | 12 | // when 13 | let decompressedData = try compressedData 14 | .map { params, data in 15 | try SUT(algorithm: params.algorithm).deobfuscate(data, nonce: params.nonce) 16 | } 17 | 18 | // then 19 | decompressedData.forEach { XCTAssertEqual(plainData, $0) } 20 | } 21 | } 22 | 23 | private extension DataCompressorTests { 24 | 25 | typealias Algorithm = Obfuscation.Compression.CompressionAlgorithm 26 | 27 | struct CompressionParams { 28 | let algorithm: Algorithm 29 | let nonce: Obfuscation.Nonce 30 | } 31 | 32 | var plainData: Data { 33 | .init([ 34 | 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 35 | 0x67, 0x65, 0x20, 0xf0, 0x9f, 0x94, 0x90 36 | ]) 37 | } 38 | 39 | var compressedDataStub: [(CompressionParams, Data)] { 40 | [ 41 | ( 42 | .init( 43 | algorithm: .lzfse, 44 | nonce: 3683273644876213525 45 | ), 46 | .init([ 47 | 0x77, 0xdb, 0x1f, 0x50, 0x13, 0x00, 0x00, 0x00, 0x53, 0x65, 0x63, 0x72, 48 | 0x65, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0xf0, 49 | 0x9f, 0x94, 0x90, 0x51, 0x6b, 0xe5, 0xf9 50 | ]) 51 | ), 52 | ( 53 | .init( 54 | algorithm: .lz4, 55 | nonce: 2467393122768582588 56 | ), 57 | .init([ 58 | 0xde, 0x81, 0xe8, 0xc6, 0x13, 0x00, 0x00, 0x00, 0x53, 0x65, 0x63, 0x72, 59 | 0x65, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0xf0, 60 | 0x9f, 0x94, 0x90, 0x40, 0x4b, 0xc4, 0xdc 61 | ]) 62 | ), 63 | ( 64 | .init( 65 | algorithm: .lzma, 66 | nonce: 11699582232143540816 67 | ), 68 | .init([ 69 | 0xad, 0x05, 0x19, 0xcf, 0x57, 0x44, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41, 70 | 0x02, 0x00, 0x21, 0x01, 0x16, 0x00, 0x00, 0x00, 0x74, 0x2f, 0xe5, 0xa3, 71 | 0x01, 0x00, 0x12, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x20, 0x6d, 0x65, 72 | 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0xf0, 0x9f, 0x94, 0x90, 0x00, 0x00, 73 | 0x00, 0x01, 0x23, 0x13, 0x94, 0x83, 0x91, 0x1a, 0x06, 0x72, 0x9e, 0x7a, 74 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x07 75 | ]) 76 | ), 77 | ( 78 | .init( 79 | algorithm: .zlib, 80 | nonce: 4110998048993541303 81 | ), 82 | .init([ 83 | 0x0b, 0x4e, 0x4d, 0x2e, 0x4a, 0x2d, 0x51, 0xc8, 0x4d, 0x2d, 0x2e, 0x4e, 84 | 0x4c, 0x4f, 0x55, 0xf8, 0x30, 0x7f, 0xca, 0x04, 0x00 85 | ]) 86 | ) 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/Obfuscation/Techniques/Encryption/SymmetricEncryptionAlgorithmTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialKit 2 | import XCTest 3 | 4 | final class SymmetricEncryptionAlgorithmTests: XCTestCase { 5 | 6 | private typealias Algorithm = Obfuscation.Encryption.SymmetricEncryptionAlgorithm 7 | 8 | func test_whenKeySizeBitCount_thenReturnsExpectedNumberOfBits() { 9 | XCTAssertEqual(128, Algorithm.aes128GCM.keySize.bitCount) 10 | XCTAssertEqual(192, Algorithm.aes192GCM.keySize.bitCount) 11 | XCTAssertEqual(256, Algorithm.aes256GCM.keySize.bitCount) 12 | XCTAssertEqual(256, Algorithm.chaChaPoly.keySize.bitCount) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/Obfuscation/Techniques/Randomization/DataShufflerTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialKit 2 | import XCTest 3 | 4 | final class DataShufflerTests: XCTestCase { 5 | 6 | private typealias DataShuffler = Obfuscation.Randomization.DataShuffler 7 | 8 | private let nonce: Obfuscation.Nonce = .StubFactory.makeNonce() 9 | 10 | private var sut: DataShuffler! 11 | 12 | override func setUp() { 13 | super.setUp() 14 | sut = .init() 15 | } 16 | 17 | override func tearDown() { 18 | sut = nil 19 | super.tearDown() 20 | } 21 | 22 | func test_givenShuffledData_whenDeobfuscate_thenReturnsDeshuffledData() throws { 23 | // given 24 | let plainData: [Data] = [ 25 | .init([0x5d, 0x9b, 0xe7, 0xde]), 26 | .init([0xe4, 0xe3, 0x34, 0xd8]), 27 | .init([0x55, 0x2a, 0x49, 0x30]) 28 | ] 29 | let count = Int(4 ^ nonce) 30 | let shuffledIndexes = [3, 0, 2, 1] 31 | let shuffledData = plainData.enumerated().map { idx, data -> Data in 32 | let dataBytes = reorderBytes(.init(data), given: shuffledIndexes) 33 | let indexesByteWidth = UInt8(1 << idx) 34 | let obfuscatedIndexes = obfuscateIndexes( 35 | shuffledIndexes, 36 | byteWidth: indexesByteWidth 37 | ) 38 | return Data( 39 | count.bytes + dataBytes + [indexesByteWidth] + obfuscatedIndexes 40 | ) 41 | } 42 | 43 | // when 44 | let deshuffledData = try shuffledData.map { try sut.deobfuscate($0, nonce: nonce) } 45 | 46 | // then 47 | XCTAssertEqual(plainData, deshuffledData) 48 | } 49 | } 50 | 51 | private extension DataShufflerTests { 52 | 53 | func reorderBytes(_ bytes: [UInt8], given indexes: [Int]) -> [UInt8] { 54 | var result: [UInt8] = .init(repeating: .zero, count: bytes.count) 55 | indexes.enumerated().forEach { oldIdx, newIdx in 56 | result[newIdx] = bytes[oldIdx] 57 | } 58 | 59 | return result 60 | } 61 | 62 | func obfuscateIndexes(_ indexes: [Int], byteWidth: UInt8) -> [UInt8] { 63 | switch Int(byteWidth) { 64 | case UInt8.byteWidth: 65 | return indexes 66 | .map(UInt8.init) 67 | .map { $0 ^ .init(bytes: nonce.bytes) } 68 | case UInt16.byteWidth: 69 | return indexes 70 | .map(UInt16.init) 71 | .flatMap { withUnsafeBytes(of: $0 ^ .init(bytes: nonce.bytes), [UInt8].init) } 72 | default: 73 | return indexes 74 | .flatMap { withUnsafeBytes(of: $0 ^ .init(bytes: nonce.bytes), [UInt8].init) } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/TestDoubles/Spies/DataDecoderSpy.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialKit 2 | import Foundation 3 | 4 | final class DataDecoderSpy: DataDecoder { 5 | 6 | var underlyingDecoder: any DataDecoder 7 | 8 | private(set) var decodeRecordedData: [Data] = [] 9 | 10 | init(underlyingDecoder: any DataDecoder) { 11 | self.underlyingDecoder = underlyingDecoder 12 | } 13 | 14 | func decode(_ type: D.Type, from data: Data) throws -> D { 15 | decodeRecordedData.append(data) 16 | return try underlyingDecoder.decode(type, from: data) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/TestDoubles/Spies/DeobfuscateDataFuncSpy.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | final class DeobfuscateDataFuncSpy { 5 | 6 | private(set) var recordedData: [Data] = [] 7 | private(set) var recordedNonces: [Obfuscation.Nonce] = [] 8 | 9 | func deobfuscateData(_ data: Data, nonce: Obfuscation.Nonce) -> Data { 10 | recordedData.append(data) 11 | recordedNonces.append(nonce) 12 | return data 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/TestDoubles/Stubs/Nonce+StubFactory.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | 3 | extension Obfuscation.Nonce { 4 | 5 | enum StubFactory { 6 | 7 | static func makeNonce() -> Obfuscation.Nonce { 8 | 4213634601671549038 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/TestDoubles/Stubs/Secret+StubFactory.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | extension Obfuscation.Secret { 5 | 6 | enum StubFactory { 7 | 8 | static func makeJSONEncodedSecret( 9 | with singleValue: SingleValue, 10 | nonce: Obfuscation.Nonce 11 | ) -> Obfuscation.Secret { 12 | .init(data: try! JSONEncoder().encode(singleValue).map { $0 }, nonce: nonce) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/ConfidentialKitTests/TestDoubles/Stubs/SingleValue+StubFactory.swift: -------------------------------------------------------------------------------- 1 | import ConfidentialKit 2 | import Foundation 3 | 4 | typealias SingleValue = Obfuscation.SupportedDataTypes.SingleValue 5 | 6 | extension SingleValue { 7 | 8 | enum StubFactory { 9 | 10 | static func makeSecretMessage() -> SingleValue { 11 | "Secret message 🔐" 12 | } 13 | 14 | static func makeSecretMessageData() -> Data { 15 | makeSecretMessage().data(using: .utf8)! 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/ConfidentialUtilsTests/Extensions/CryptoKit/SymmetricKeySize/SymmetricKeySize+ByteCountTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialUtils 2 | import XCTest 3 | 4 | import CryptoKit 5 | 6 | final class SymmetricKeySize_ByteCountTests: XCTestCase { 7 | 8 | func test_givenStandardKeySize_whenByteCount_thenReturnsExpectedValue() { 9 | // given 10 | let keySize = SymmetricKeySize.bits192 11 | 12 | // when 13 | let byteCount = keySize.byteCount 14 | 15 | // then 16 | XCTAssertEqual(24, byteCount) 17 | } 18 | 19 | func test_givenNonStandardKeySize_whenByteCount_thenReturnsExpectedValue() { 20 | // given 21 | let keySize = SymmetricKeySize(bitCount: 72) 22 | 23 | // when 24 | let byteCount = keySize.byteCount 25 | 26 | // then 27 | XCTAssertEqual(9, byteCount) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/ConfidentialUtilsTests/Extensions/Foundation/NSData/NSData.CompressionAlgorithm+MagicNumbersTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialUtils 2 | import XCTest 3 | 4 | final class NSData_CompressionAlgorithm_MagicNumbersTests: XCTestCase { 5 | 6 | private typealias SUT = NSData.CompressionAlgorithm 7 | 8 | func test_whenHeaderMagicByteCount_thenReturnsExpectedNumberOfBytes() { 9 | XCTAssertEqual(4, SUT.lzfse.headerMagicByteCount) 10 | XCTAssertEqual(4, SUT.lz4.headerMagicByteCount) 11 | XCTAssertEqual(6, SUT.lzma.headerMagicByteCount) 12 | XCTAssertEqual(0, SUT.zlib.headerMagicByteCount) 13 | } 14 | 15 | func test_whenFooterMagicByteCount_thenReturnsExpectedNumberOfBytes() { 16 | XCTAssertEqual(4, SUT.lzfse.footerMagicByteCount) 17 | XCTAssertEqual(4, SUT.lz4.footerMagicByteCount) 18 | XCTAssertEqual(2, SUT.lzma.footerMagicByteCount) 19 | XCTAssertEqual(0, SUT.zlib.footerMagicByteCount) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/ConfidentialUtilsTests/Extensions/Swift/BinaryInteger/BinaryInteger+BytesTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialUtils 2 | import XCTest 3 | 4 | final class BinaryInteger_BytesTests: XCTestCase { 5 | 6 | func test_givenBinaryIntegers_whenBytes_thenReturnsExpectedValues() { 7 | // given 8 | let integers: [Any] = [ 9 | UInt8(1), UInt16(1), UInt32(1), UInt64(1), 10 | Int8(1), Int16(1), Int32(1), Int64(1) 11 | ] 12 | 13 | // when 14 | let bytes = integers.map { ($0 as! any FixedWidthInteger).littleEndian.bytes } 15 | 16 | // then 17 | XCTAssertEqual( 18 | [ 19 | [0x01], [0x01, 0x00], [0x01, 0x00, 0x00, 0x00], [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 20 | [0x01], [0x01, 0x00], [0x01, 0x00, 0x00, 0x00], [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 21 | ], 22 | bytes 23 | ) 24 | } 25 | 26 | func test_givenCollectionOfTwoBytes_whenUInt8InitBytes_thenReturnsUInt8WithExpectedValue() { 27 | // given 28 | let bytes: [UInt8] = [0x01, 0x01] 29 | 30 | // when 31 | let uInt8 = UInt8(bytes: bytes) 32 | 33 | // then 34 | XCTAssertEqual(1, uInt8) 35 | } 36 | 37 | func test_givenCollectionOfTwoBytes_whenUInt32InitBytes_thenReturnsUInt32WithExpectedValue() { 38 | // given 39 | let bytes: [UInt8] = [0x01, 0x01] 40 | 41 | // when 42 | let uInt32 = UInt32(bytes: bytes) 43 | 44 | // then 45 | XCTAssertEqual(257, uInt32) 46 | } 47 | 48 | func test_givenEmptyCollection_whenIntInitBytes_thenReturnsIntWithValueEqualToZero() { 49 | // given 50 | let bytes: [UInt8] = [] 51 | 52 | // when 53 | let int = Int(bytes: bytes) 54 | 55 | // then 56 | XCTAssertEqual(.zero, int) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/ConfidentialUtilsTests/Extensions/Swift/FixedWidthInteger/FixedWidthInteger+ByteWidthTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialUtils 2 | import XCTest 3 | 4 | final class FixedWidthInteger_ByteWidthTests: XCTestCase { 5 | 6 | func test_givenFixedWidthIntegerTypes_whenByteWidth_thenReturnsExpectedValues() { 7 | // given 8 | let types: [Any] = [ 9 | UInt8.self, UInt16.self, UInt32.self, UInt64.self, 10 | Int8.self, Int16.self, Int32.self, Int64.self 11 | ] 12 | 13 | // when 14 | let byteWidths = types.map { ($0 as! any FixedWidthInteger.Type).byteWidth } 15 | 16 | // then 17 | XCTAssertEqual( 18 | [ 19 | 1, 2, 4, 8, 20 | 1, 2, 4, 8 21 | ], 22 | byteWidths 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/ConfidentialUtilsTests/TypeInfoTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ConfidentialUtils 2 | import XCTest 3 | 4 | final class TypeInfoTests: XCTestCase { 5 | 6 | func test_givenNestedTypeInfo_whenFullyQualifiedName_thenReturnsExpectedValue() { 7 | // given 8 | let typeInfo = TypeInfo(of: Swift.String.Encoding.self) 9 | 10 | // when 11 | let fullyQualifiedName = typeInfo.fullyQualifiedName 12 | 13 | // then 14 | XCTAssertEqual("Swift.String.Encoding", fullyQualifiedName) 15 | } 16 | 17 | func test_givenNestedTypeInfo_whenModuleName_thenReturnsExpectedValue() { 18 | // given 19 | let typeInfo = TypeInfo(of: Swift.String.Encoding.self) 20 | 21 | // when 22 | let moduleName = typeInfo.moduleName 23 | 24 | // then 25 | XCTAssertEqual("Swift", moduleName) 26 | } 27 | 28 | func test_givenNestedTypeInfo_whenFullName_thenReturnsExpectedValue() { 29 | // given 30 | let typeInfo = TypeInfo(of: Swift.String.Encoding.self) 31 | 32 | // when 33 | let fullName = typeInfo.fullName 34 | 35 | // then 36 | XCTAssertEqual("String.Encoding", fullName) 37 | } 38 | 39 | func test_givenNestedTypeInfo_whenName_thenReturnsExpectedValue() { 40 | // given 41 | let typeInfo = TypeInfo(of: Swift.String.Encoding.self) 42 | 43 | // when 44 | let name = typeInfo.name 45 | 46 | // then 47 | XCTAssertEqual("Encoding", name) 48 | } 49 | 50 | func test_givenTopLevelTypeInfo_whenFullyQualifiedName_thenReturnsExpectedValue() { 51 | // given 52 | let typeInfo = TypeInfo(of: Foundation.Data.self) 53 | 54 | // when 55 | let fullyQualifiedName = typeInfo.fullyQualifiedName 56 | 57 | // then 58 | XCTAssertEqual("Foundation.Data", fullyQualifiedName) 59 | } 60 | 61 | func test_givenTopLevelTypeInfo_whenModuleName_thenReturnsExpectedValue() { 62 | // given 63 | let typeInfo = TypeInfo(of: Foundation.Data.self) 64 | 65 | // when 66 | let moduleName = typeInfo.moduleName 67 | 68 | // then 69 | XCTAssertEqual("Foundation", moduleName) 70 | } 71 | 72 | func test_givenTopLevelTypeInfo_whenFullName_thenReturnsExpectedValue() { 73 | // given 74 | let typeInfo = TypeInfo(of: Foundation.Data.self) 75 | 76 | // when 77 | let fullName = typeInfo.fullName 78 | 79 | // then 80 | XCTAssertEqual("Data", fullName) 81 | } 82 | 83 | func test_givenTopLevelTypeInfo_whenName_thenReturnsExpectedValue() { 84 | // given 85 | let typeInfo = TypeInfo(of: Foundation.Data.self) 86 | 87 | // when 88 | let name = typeInfo.name 89 | 90 | // then 91 | XCTAssertEqual("Data", name) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 90% 6 | threshold: 0.25% 7 | patch: 8 | default: 9 | informational: true -------------------------------------------------------------------------------- /resources/machoview-cstring-literals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securevale/swift-confidential/f1a4ae5b9bbb91b15f14a389d345e8ef0b0f00fe/resources/machoview-cstring-literals.png -------------------------------------------------------------------------------- /scripts/commons/common.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # shellcheck disable=SC2034,SC2155 4 | 5 | ##################### 6 | # CONSTANTS # 7 | ##################### 8 | 9 | if [[ -t 1 && -t 2 ]] 10 | then 11 | # shellcheck disable=SC2086 12 | function tput_set() { tput $1; } 13 | else 14 | function tput_set() { :; } 15 | fi 16 | 17 | readonly BOLD=$(tput_set bold) 18 | readonly UNDERLINE=$(tput_set smul) 19 | readonly NORMAL=$(tput_set sgr0) 20 | 21 | ##################### 22 | # FUNCTIONS # 23 | ##################### 24 | 25 | function pushd_quiet() { 26 | pushd "$1" &>/dev/null || exit 27 | } 28 | 29 | function popd_quiet() { 30 | popd &>/dev/null || exit 31 | } 32 | 33 | function echoerr() { 34 | local IFS=" " 35 | cat <<< "$* ❌" 1>&2; 36 | } 37 | 38 | function echo_progress() { 39 | echo "$1 ⚙️" 40 | } 41 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # shellcheck disable=SC2155 4 | 5 | set -Eeuo pipefail 6 | 7 | source "$(dirname "$0")"/commons/common.sh 8 | 9 | ################# 10 | # CONSTANTS # 11 | ################# 12 | 13 | readonly SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)" 14 | 15 | readonly SWIFT_BUILD_DIR_NAME=".build" 16 | 17 | readonly COVERAGE_DIR_NAME=".coverage" 18 | readonly COVERAGE_INCLUDE_LIST=( 19 | "Sources/ConfidentialCore/" 20 | "Sources/ConfidentialKit/" 21 | "Sources/ConfidentialUtils/" 22 | ) 23 | 24 | ################# 25 | # FUNCTIONS # 26 | ################# 27 | 28 | function set_up() { 29 | echo "---------------------------- SET UP ----------------------------" 30 | echo "🍳" 31 | 32 | echo "Cleaning up SPM build artifacts" 33 | swift package clean 34 | 35 | echo "-------------------------- END SET UP --------------------------" 36 | } 37 | 38 | function clean_up() { 39 | echo "--------------------------- CLEAN UP ---------------------------" 40 | echo "🧽" 41 | 42 | # SPM .build directory generated by SPM interferes with 43 | # Xcode build system, so it needs to be removed once script execution is done. 44 | local -r swift_build_dir_path="$SCRIPT_ABS_PATH/../$SWIFT_BUILD_DIR_NAME" 45 | if [[ -d "$swift_build_dir_path" ]] 46 | then 47 | echo "Deleting SPM $SWIFT_BUILD_DIR_NAME directory" 48 | rm -rf "$swift_build_dir_path" 49 | fi 50 | 51 | echo "------------------------- END CLEAN UP -------------------------" 52 | } 53 | 54 | function swift_test() { 55 | echo "-------------------------- SWIFT TEST --------------------------" 56 | echo "🧪" 57 | 58 | swift test --parallel --enable-code-coverage 59 | 60 | echo "------------------------ END SWIFT TEST ------------------------" 61 | } 62 | 63 | function swift_package_name() { 64 | swift package describe | awk '/Name:/ { print $2; exit; }' 65 | } 66 | 67 | function export_coverage_data() { 68 | echo "------------------------ COVERAGE DATA -------------------------" 69 | 70 | echo_progress "Exporting code coverage data" 71 | 72 | local -r package_name=$(swift_package_name) 73 | local -r bin_path="$SWIFT_BUILD_DIR_NAME/debug/${package_name}PackageTests.xctest/Contents/MacOS/${package_name}PackageTests" 74 | local -r profdata_path="$SWIFT_BUILD_DIR_NAME/debug/codecov/default.profdata" 75 | local -r coverage_report_path="$COVERAGE_DIR_NAME/$package_name.lcov" 76 | 77 | pushd_quiet "$SCRIPT_ABS_PATH/.." 78 | mkdir -p "$COVERAGE_DIR_NAME" 79 | # shellcheck disable=SC2068 80 | xcrun llvm-cov export -format="lcov" "$bin_path" -instr-profile "$profdata_path" ${COVERAGE_INCLUDE_LIST[@]} \ 81 | > "$coverage_report_path" 82 | echo "Code coverage report: $(pwd -P)/$coverage_report_path" 83 | popd_quiet 84 | 85 | echo "---------------------- END COVERAGE DATA -----------------------" 86 | } 87 | 88 | ################### 89 | # ENTRY POINT # 90 | ################### 91 | 92 | trap clean_up EXIT 93 | 94 | set_up 95 | 96 | swift_test 97 | export_coverage_data 98 | -------------------------------------------------------------------------------- /scripts/templates/artifactbundle-info.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "artifacts": { 4 | "__NAME__": { 5 | "type": "executable", 6 | "version": "__VERSION__", 7 | "variants": [ 8 | { 9 | "path": "__NAME__-__VERSION__-macos/bin/__NAME__", 10 | "supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"] 11 | } 12 | ] 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /scripts/upgrade_bash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -Eeuo pipefail 4 | 5 | readonly LOGIN_SHELLS_FILE_PATH="/etc/shells" 6 | 7 | readonly HOMEBREW_BASH_FORMULA="bash" 8 | readonly HOMEBREW_BASH_X86_INSTALL_DIR="/usr/local/bin/bash" 9 | readonly HOMEBREW_BASH_ARM_INSTALL_DIR="/opt/homebrew/bin/bash" 10 | 11 | if ! command -v "brew" &>/dev/null 12 | then 13 | echo "Error: 'brew' command not found. Please install Homebrew before running this script." 1>&2 14 | exit 1 15 | fi 16 | 17 | echo "-------------------------------- UPGRADE BASH --------------------------------" 18 | 19 | if ! brew list $HOMEBREW_BASH_FORMULA &>/dev/null 20 | then 21 | echo "Installing Bash ⚙️" 22 | brew install $HOMEBREW_BASH_FORMULA > /dev/null 23 | else 24 | echo "Updating Bash ⚙️" 25 | brew upgrade $HOMEBREW_BASH_FORMULA > /dev/null 26 | fi 27 | 28 | if [[ -f "$HOMEBREW_BASH_ARM_INSTALL_DIR" ]] 29 | then 30 | HOMEBREW_BASH_INSTALL_DIR="$HOMEBREW_BASH_ARM_INSTALL_DIR" 31 | else 32 | HOMEBREW_BASH_INSTALL_DIR="$HOMEBREW_BASH_X86_INSTALL_DIR" 33 | fi 34 | 35 | if ! grep -q $HOMEBREW_BASH_INSTALL_DIR $LOGIN_SHELLS_FILE_PATH 36 | then 37 | echo $HOMEBREW_BASH_INSTALL_DIR | sudo tee -a $LOGIN_SHELLS_FILE_PATH 38 | fi 39 | 40 | sudo chsh -s $HOMEBREW_BASH_INSTALL_DIR 41 | 42 | echo -e "\n$(bash --version)" 43 | 44 | echo "------------------------------ END UPGRADE BASH ------------------------------" 45 | --------------------------------------------------------------------------------