Skip to content
Open
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
23 changes: 21 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ var package = Package(
)

#if os(macOS)
package.products.append(
.library(
name: "ConfidentialObfuscator",
targets: ["ConfidentialObfuscator"]
)
)
package.dependencies.append(contentsOf: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.6.1"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", "509.1.1"..<"602.0.0"),
Expand All @@ -60,12 +66,21 @@ package.targets.append(contentsOf: [
]
),

// Obfuscator
.target(
name: "ConfidentialObfuscator",
dependencies: [
"ConfidentialCore",
.product(name: "Yams", package: "Yams"),
.product(name: "Parsing", package: "swift-parsing")
]
),

// CLI Tool
.executableTarget(
name: "swift-confidential",
dependencies: [
"ConfidentialCore",
"Yams",
"ConfidentialObfuscator",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
Expand All @@ -74,6 +89,10 @@ package.targets.append(contentsOf: [
.testTarget(
name: "ConfidentialCoreTests",
dependencies: ["ConfidentialCore"]
),
.testTarget(
name: "ConfidentialObfuscatorTests",
dependencies: ["ConfidentialObfuscator"]
)
])
#endif
23 changes: 21 additions & 2 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ var package = Package(
)

#if os(macOS)
package.products.append(
.library(
name: "ConfidentialObfuscator",
targets: ["ConfidentialObfuscator"]
)
)
package.dependencies.append(contentsOf: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.6.1"),
.package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.14.1"),
Expand All @@ -90,12 +96,21 @@ package.targets.append(contentsOf: [
]
),

// Obfuscator
.target(
name: "ConfidentialObfuscator",
dependencies: [
"ConfidentialCore",
.product(name: "Yams", package: "Yams"),
.product(name: "Parsing", package: "swift-parsing")
]
),

// CLI Tool
.executableTarget(
name: "swift-confidential",
dependencies: [
"ConfidentialCore",
"Yams",
"ConfidentialObfuscator",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
Expand All @@ -104,6 +119,10 @@ package.targets.append(contentsOf: [
.testTarget(
name: "ConfidentialCoreTests",
dependencies: ["ConfidentialCore"]
),
.testTarget(
name: "ConfidentialObfuscatorTests",
dependencies: ["ConfidentialObfuscator"]
)
])
#endif
6 changes: 2 additions & 4 deletions Sources/ConfidentialCore/SyntaxText/SourceFileText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ package struct SourceFileText: Equatable {
.formatted(using: .init(indentationWidth: .spaces(0)))
}

package func write(to url: URL, encoding: String.Encoding = .utf8) throws {
package func text() -> String {
var text = ""
syntax.write(to: &text)

try text
.trimmingCharacters(in: .newlines)
.write(to: url, atomically: true, encoding: encoding)
return text.trimmingCharacters(in: .newlines)
}
}

Expand Down
27 changes: 27 additions & 0 deletions Sources/ConfidentialObfuscator/ConfidentialObfuscator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import ConfidentialCore
import Foundation
import Parsing
import Yams

/// Obfuscates a source file based on a configuration.
public enum ConfidentialObfuscator {
/// Creates a string with obfuscated source code based on a given configuration data.
///
/// - Parameters:
/// - configurationData: The `Data` contents of a YAML file containing the configuration.
/// - Returns: A string with obfuscated source code.
/// - Throws: An error if the configuration is invalid or the obfuscation fails.
public static func obfuscate(configurationData: Data) throws -> String {
let configuration: Configuration = try YAMLDecoder().decode(Configuration.self, from: configurationData)

var sourceFileSpec: SourceFileSpec = try Parsing.Parsers.ModelTransform.SourceFileSpec()
.parse(configuration)

try SourceObfuscator().obfuscate(&sourceFileSpec)

let sourceFileText: SourceFileText = try Parsing.Parsers.CodeGeneration.SourceFile()
.parse(&sourceFileSpec)

return sourceFileText.text()
}
}
15 changes: 3 additions & 12 deletions Sources/swift-confidential/Subcommands/Obfuscate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ArgumentParser
import ConfidentialCore
import ConfidentialObfuscator
import Foundation
import Yams

extension SwiftConfidential {

Expand Down Expand Up @@ -38,21 +37,13 @@ extension SwiftConfidential {
}

let configurationYAML = try Data(contentsOf: configuration)
let configuration = try YAMLDecoder().decode(Configuration.self, from: configurationYAML)

var sourceFileSpec = try Parsers.ModelTransform.SourceFileSpec()
.parse(configuration)

try SourceObfuscator().obfuscate(&sourceFileSpec)

let sourceFileText = try Parsers.CodeGeneration.SourceFile()
.parse(&sourceFileSpec)
let obfuscatedText = try ConfidentialObfuscator.obfuscate(configurationData: configurationYAML)

guard fileManager.createFile(atPath: output.path, contents: .none) else {
throw RuntimeError(description: #"Failed to create output file at "\#(output.path)""#)
}

try sourceFileText.write(to: output)
try obfuscatedText.write(to: output, atomically: true, encoding: .utf8)
}
}
}
22 changes: 3 additions & 19 deletions Tests/ConfidentialCoreTests/SyntaxText/SourceFileTextTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,7 @@ final class SourceFileTextTests: XCTestCase {

private typealias SUT = SourceFileText

private var temporaryFileURL: URL!

override func setUp() {
super.setUp()
temporaryFileURL = .init(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("\(UUID().uuidString).swift")
}

override func tearDownWithError() throws {
try FileManager.default.removeItem(at: temporaryFileURL)
temporaryFileURL = nil
try super.tearDownWithError()
}

func test_givenSourceFileTextWithSourceFileSyntax_whenWriteToFile_thenFileContainsExpectedSyntaxText() throws {
func test_givenSourceFileTextWithSourceFileSyntax_whenGetText_thenReturnsExpectedSyntaxText() {
// given
let sourceFile = SourceFileSyntax(
statements: CodeBlockItemListSyntax(itemsBuilder: {
Expand Down Expand Up @@ -61,13 +47,11 @@ final class SourceFileTextTests: XCTestCase {
})
)
let sut = SUT(from: sourceFile)
let encoding = String.Encoding.utf8

// when
try sut.write(to: temporaryFileURL, encoding: encoding)
let sourceFileText = sut.text()

// then
let fileContents = try String(contentsOf: temporaryFileURL, encoding: encoding)
XCTAssertEqual(
"""
import Foundation
Expand All @@ -77,7 +61,7 @@ final class SourceFileTextTests: XCTestCase {
var data: Data
}
""",
fileContents
sourceFileText
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
@testable import ConfidentialObfuscator
import ConfidentialKit
import XCTest

final class ConfidentialObfuscatorTests: XCTestCase {

func test_givenConfig_whenObfuscate_thenReturnsStringWithObfuscatedSecrets() throws {
// given
let value: String = "variable_value"
let crypter = Obfuscation.Encryption.DataCrypter(algorithm: .aes128GCM)

let configuration = """
algorithm:
- encrypt using aes-128-gcm
defaultAccessModifier: public
secrets:
- name: variable_name
value: \(value)
"""

// when
let obfuscatedString: String = try ConfidentialObfuscator.obfuscate(configurationData: Data(configuration.utf8))

// then

// 1. Check the output string
do {
var obfuscatedString = obfuscatedString
// Modify Secret initialization in two steps:
// 1. Strip random data array.
// Look for all `0xFF` with coma+space or closing square bracket in the end.
// If ends with square bracket replace it with square bracket, else just empty string.
obfuscatedString.replace(#/0x[0-9a-fA-F]{1,2}(,\s?|\])/#) { match in if match.output.1 == "]" { "]" } else { "" } }
// 2. Replace `, nonce: 00000` with zero.
obfuscatedString.replace(#/,\s?nonce:\s?\d+/#, with: ", nonce: 0")

let expectedString: String = """
import ConfidentialKit
import Foundation

extension ConfidentialKit.Obfuscation.Secret {

@ConfidentialKit.Obfuscated<Swift.String>(deobfuscateData)
public static var variable_name: 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)
}
}
"""

XCTAssertEqual(obfuscatedString, expectedString)
}

// 2. Check if the obfuscated value is correct
do {
// Get nonce value
let nonceString = try XCTUnwrap(obfuscatedString.firstMatch(of: #/,\s?nonce:\s?(\d+)/#)?.output.1)
let nonceValue: UInt64 = try XCTUnwrap(UInt64(nonceString))

// Get obfuscated bytes
let obfuscatedBytes = try obfuscatedString
// Get matches for all `0xFF`
.matches(of: /0x([0-9a-fA-F]{1,2})/)
// Convert to a byte
.map { match in try XCTUnwrap(UInt8(match.output.1, radix: 16)) }

let deobfuscatedData = try crypter.deobfuscate(Data(obfuscatedBytes), nonce: nonceValue)

let deobfuscatedValue = try XCTUnwrap(String(bytes: deobfuscatedData, encoding: .utf8))

XCTAssertEqual(deobfuscatedValue, "\"\(value)\"")
}
}
}