Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ jobs:
fi

if [[ "${{ steps.changes.outputs.swift }}" == "true" ]]; then
# Turning off CodeQL for a while, since it's doesn't work with Swift 6.2.0
# matrix_include="$(echo "$matrix_include" | jq -c '. += [{"language": "swift", "build-mode": "manual"}]')"
printf ''
matrix_include="$(echo "$matrix_include" | jq -c '. += [{"language": "swift", "build-mode": "manual"}]')"
# printf ''
fi

echo "matrix={\"include\":$matrix_include}" >> $GITHUB_OUTPUT
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/templates/prepare-swift/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,24 @@ runs:
apt-get install -y zip
fi

- name: Empty mise.toml for Linux
shell: bash
if: ${{ runner.os == 'Linux' }}
run: |
rm -rf mise.toml
touch mise.toml

- name: Install mise tools
if: ${{ runner.os == 'Linux' }}
uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566
with:
mise_toml: |
[tools]
shfmt = "latest"
shellcheck = "latest"

- name: Install mise tools
if: ${{ runner.os != 'Linux' }}
uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566

- name: Select Swift Version
Expand Down
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.1
0.0.2
30 changes: 15 additions & 15 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 5 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ enum SwiftConfidentialSource {

var packageDependency: PackageDescription.Package.Dependency {
switch self {
case .upstream: .package(url: "https://github.com/securevale/swift-confidential.git", from: "0.4.1")
case .upstream: .package(url: "https://github.com/securevale/swift-confidential.git", from: "0.5.1")
case .fork: .package(url: "https://github.com/nekrich/swift-confidential.git", branch: "master")
}
}
Expand Down Expand Up @@ -51,14 +51,14 @@ enum YamsSource {

var packageDependency: PackageDescription.Package.Dependency {
switch self {
case .upstream: .package(url: "https://github.com/jpsim/Yams.git", from: "6.1.0")
case .upstream: .package(url: "https://github.com/jpsim/Yams.git", from: "6.2.1")
case .fork: .package(url: "https://github.com/nekrich/Yams.git", branch: "main")
}
}
}

let swiftConfidentialSource: SwiftConfidentialSource = .fork
let yamsSource: YamsSource = .fork
let yamsSource: YamsSource = .upstream

enum Targets {
static func targetBundle(
Expand Down Expand Up @@ -134,10 +134,7 @@ enum Targets {
commandBundle(
name: "ObfuscateSecrets",
dependencies: [.target(name: "EnvSubst"), .target(name: "Shell"), swiftConfidentialSource.targetDependency],
testsDependencies: [
.product(name: "ConfidentialKit", package: "swift-confidential", condition: .when(platforms: [.macOS])),
swiftConfidentialSource.targetDependency,
],
testsDependencies: [swiftConfidentialSource.targetDependency],
commandDependencies: [.target(name: "EnvSubstCommand"), .target(name: "ExportSecretsCommand")]
)
}
Expand Down Expand Up @@ -201,7 +198,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.6.1")),
.package(url: "https://github.com/swiftlang/swift-format.git", .upToNextMajor(from: "602.0.0")),
.package(url: "https://github.com/swiftlang/swift-syntax.git", "602.0.0"..<"603.0.0"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", .upToNextMajor(from: "602.0.0")),
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.6.4")),
swiftConfidentialSource.packageDependency, yamsSource.packageDependency,
],
Expand Down
93 changes: 10 additions & 83 deletions Tests/ObfuscateSecretsTests/ConfidentialWrapperTests.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if canImport(ConfidentialKit)
import ConfidentialKit
#if !os(Linux)
import Foundation
import Shell
import Testing
Expand Down Expand Up @@ -48,95 +47,23 @@
value: $ENV_VARIABLE_NAME2
"""

/// The resolved values of the obfuscated secret variables.
static let obfuscatedSecretValues: [String: String] = ["variable_name1": "bar", "variable_name2": "baz"]

/// Expected output w/o data and nonce.
static let expectedString: String = """
import ConfidentialKit
import Foundation

extension ConfidentialKit.Obfuscation.Secret {

@ConfidentialKit.Obfuscated<Swift.String>(deobfuscateData)
public static var variable_name1: ConfidentialKit.Obfuscation.Secret = .init(data: [], nonce: 0)
extension ConfidentialCore.Obfuscation.Secret {

@ConfidentialKit.Obfuscated<Swift.String>(deobfuscateData)
public static var variable_name2: ConfidentialKit.Obfuscation.Secret = .init(data: [], nonce: 0)

@inline(__always)
private static func deobfuscateData(_ data: Foundation.Data, nonce: Swift.UInt64) throws -> Foundation.Data {
try ConfidentialKit.Obfuscation.Encryption.DataCrypter(algorithm: .aes128GCM)
.deobfuscate(data, nonce: nonce)
public static #Obfuscate(algorithm: .custom([.encrypt(algorithm: .aes128GCM)])) {
let variable_name1 = "bar"
let variable_name2 = "baz"
}
}
"""

// MARK: Regexes

/// Bytes regex.
///
/// Matches `0xFF`. First group is the string byte representation: `FF`.
let byteRegex: Regex = /0x([0-9a-fA-F]{1,2}),?\s?+/
/// Data init regex.
///
/// Matches `(data: [0xFF, 0xFF...], nonce: 000)`.
let dataReplacementRegex: Regex = #/\(data:\s?+\[((0x([0-9a-fA-F]{1,2}),?\s?+)+)\],\s?+nonce:\s?+(\d+)\)/#
/// Secret variable regex.
///
/// Matches: `var variableName: Type = .init(data: [0xFF, 0xFF...], nonce: 000)`.
/// First group is the variable name (`variableName`).
/// Third group is the bytes list (`0xFF, 0xFF...`). We match it with the `byteRegex` to get the byte string.
/// Sixth group is the nonce value (`value`).
let secretVariableRegex: Regex =
#/var\s+(.*)\s?+:\s?+.*=\s+.*(\(data:\s?+\[((0x([0-9a-fA-F]{1,2}),?\s?+)+)\],\s?+nonce:\s?+(\d+)\))/#

// MARK: Validators

private func validateContentsWithoutDataAndNonce(
obfuscatedString: String,
_ sourceLocation: SourceLocation = #_sourceLocation,
) throws {
let obfuscatedString = obfuscatedString.replacing(dataReplacementRegex, with: "(data: [], nonce: 0)")

#expect(obfuscatedString == Self.expectedString, sourceLocation: sourceLocation)
}

private func validateSecretValues(
_ secretValues: [String: String],
in obfuscatedString: String,
_ sourceLocation: SourceLocation = #_sourceLocation,
) throws {
let crypter = Obfuscation.Encryption.DataCrypter(algorithm: .aes128GCM)

let deobfuscationResult = try obfuscatedString.matches(of: secretVariableRegex)
.reduce(into: [String: String]()) { (accum, globalMatch) in
let variableName = String(globalMatch.output.1)

let obfuscatedBytes = try globalMatch.output.3 // Get matches for all `0xFF`
.matches(of: byteRegex) // Convert to a byte
.map { byteMatch in try #require(UInt8(byteMatch.output.1, radix: 16), sourceLocation: sourceLocation) }

let nonceValue: UInt64 = try #require(UInt64(globalMatch.output.6), sourceLocation: sourceLocation)

let secret = ConfidentialKit.Obfuscation.Secret(data: obfuscatedBytes, nonce: nonceValue)
let obfuscated = ConfidentialKit.Obfuscated<Swift.String>(wrappedValue: secret, crypter.deobfuscate)

accum[variableName] = obfuscated.projectedValue
}

#expect(deobfuscationResult == secretValues, sourceLocation: sourceLocation)
}

func validate(
outputFileURL: URL,
secretValues: [String: String],
_ sourceLocation: SourceLocation = #_sourceLocation,
) throws {
func validate(outputFileURL: URL, _ sourceLocation: SourceLocation = #_sourceLocation, ) throws {
let obfuscatedString = try String(contentsOf: outputFileURL, encoding: .utf8)

try validateContentsWithoutDataAndNonce(obfuscatedString: obfuscatedString, sourceLocation)
try validateSecretValues(secretValues, in: obfuscatedString, sourceLocation)
#expect(obfuscatedString == Self.expectedString, sourceLocation: sourceLocation)
}
}

Expand All @@ -147,7 +74,7 @@
// When running in Xcode: tests run in a temp dir, and mise fails to recognize tool, because is not currently active.
// If swift-confidential is not found by /usr/bin/which - install it with mise.
if (try? Shell.which(cliToolName: "swift-confidential")) == nil {
do { try Shell.Mise().use(cliToolName: "ubi:securevale/swift-confidential", version: "0.4.1") }
do { try Shell.Mise().use(cliToolName: "github:securevale/swift-confidential", version: "0.5.1") }
catch { #expect(Bool(false), "Unexpected error while installing swift-confidential: \(error)") }
}

Expand All @@ -167,7 +94,7 @@
catch { #expect(Bool(false), "Got error: \(error)") }

// THEN
try validate(outputFileURL: self.outputFileURL, secretValues: Self.obfuscatedSecretValues)
try validate(outputFileURL: self.outputFileURL)
}
}

Expand All @@ -188,7 +115,7 @@
)

// THEN
try validate(outputFileURL: self.outputFileURL, secretValues: Self.obfuscatedSecretValues)
try validate(outputFileURL: self.outputFileURL)
}
}
#endif
Expand Down
Loading
Loading