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
3 changes: 3 additions & 0 deletions Modules/CommonsLib/Sources/CommonsLib/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ public struct Constants {
public static let LibDigidocLog = "libdigidocpp.log"
public static let LDAPCertsPem = "ldapCerts.pem"
public static let nfcCANKey = "canKey.txt"

// iOS allows 255 characters in a file name, but app may add "(1)" and more to file names.
public static let MaxNameBytes = 240
}

public struct FileBaseName {
Expand Down
11 changes: 7 additions & 4 deletions Modules/CryptoLib/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ let package = Package(
.package(path: "../ConfigLib"),
.package(path: "../CommonsLib"),
.package(path: "../IdCardLib"),
.package(path: "../UtilsLib")
.package(path: "../UtilsLib"),
.package(path: "../Test/CommonsTestShared")
],
targets: [
.binaryTarget(
Expand Down Expand Up @@ -98,12 +99,14 @@ let package = Package(
name: "CryptoSwiftTests",
dependencies: [
"ConfigLib",
"CryptoLibMocks",
"CryptoSwift",
"CryptoObjCWrapper",
"CryptoLibMocks",
"CommonsLib",
"UtilsLib",
.product(name: "FactoryTesting", package: "Factory"),
.product(name: "CommonsLibMocks", package: "commonslib")
"CommonsTestShared",
.product(name: "CommonsLibMocks", package: "commonslib"),
.product(name: "FactoryTesting", package: "Factory")
]
)
]
Expand Down
8 changes: 7 additions & 1 deletion Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,13 @@ + (void)decryptFile:(NSString *)fullPath withCert:(NSData *)certData withToken:(
}
[data increaseLengthBy:16 * 1024];
}
[response setObject:data forKey:[NSString stringWithStdString:name]];

NSString *fileName = [NSString stringWithStdString:name];
NSUInteger counter = response.count + 1;
while (fileName.length == 0 || response[fileName] != nil) {
fileName = [NSString stringWithFormat:@"datafile-%lu", (unsigned long)counter++];
}
[response setObject:data forKey:fileName];
}
if (reader.finishDecryption() != 0)
return [NSError cryptoError:@"Failed to end encryption" error:error];
Expand Down
13 changes: 12 additions & 1 deletion Modules/CryptoLib/Sources/CryptoObjC/include/Extensions.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,18 @@ static const NSInteger CryptoLibWrongKeyErrorCode = -109; // libcdoc::WRONG_KEY

@implementation NSString (std_string)
+ (instancetype)stringWithStdString:(const std::string&)data {
return data.empty() ? nil : [NSString stringWithUTF8String:data.c_str()];
if (data.empty()) {
return nil;
}
NSString *utf8 = [[NSString alloc] initWithBytes:data.data()
length:data.size()
encoding:NSUTF8StringEncoding];
if (utf8 != nil) {
return utf8;
}
return [[NSString alloc] initWithBytes:data.data()
length:data.size()
encoding:NSISOLatin1StringEncoding];
}

- (std::string)toString {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,13 @@ class CdocParserDelegate: NSObject, XMLParserDelegate {
addressees.append(Addressee(cert: dataFromBase64))
}
case ("denc:EncryptionProperty", "orig_file"):
if let filename = currentData.split(separator: "|").first {
dataFiles.append(CryptoDataFile(filename: String(filename)))
let parts = currentData.components(separatedBy: "|")
let filename = parts.count > 3
? parts.dropLast(3).joined(separator: "|")
: (parts.first ?? "")

if !filename.isEmpty {
dataFiles.append(CryptoDataFile(filename: filename))
}
case ("denc:EncryptionProperty", "DocumentFormat"):
format = currentData
Expand Down
101 changes: 78 additions & 23 deletions Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ public actor CryptoContainer: CryptoContainerProtocol, Loggable {
var failedFileCount = 0
let totalFileCount = filesToAdd.count

let existingDataFiles = Set(dataFiles?.compactMap { $0?.lastPathComponent } ?? [])
let existingDataFiles = Set(dataFiles?.compactMap { $0?.lastPathComponent.sanitized() } ?? [])

var duplicateFileName: String?

for fileToAdd in filesToAdd {
let fileName = fileToAdd.lastPathComponent
let fileName = fileToAdd.lastPathComponent.sanitized()
let destinationURL = cryptoContainersDirectory.appendingPathComponent(fileName)

if existingDataFiles.contains(fileName) ||
Expand All @@ -106,15 +106,26 @@ public actor CryptoContainer: CryptoContainerProtocol, Loggable {
continue
}

guard destinationURL.isWithin(directory: cryptoContainersDirectory) else {
CryptoContainer.logger().error(
"Refusing to add file: destination escapes the container directory"
)
failedFileCount += 1
continue
}

do {
if fileManager.fileExists(atPath: destinationURL.path) {
if fileManager.fileExists(atPath: destinationURL.resolvedPath) {
try fileManager.removeItem(at: destinationURL)
}

try fileManager.copyItem(at: fileToAdd, to: destinationURL)
movedFiles.append(destinationURL)

} catch {
CryptoContainer.logger().error(
"Unable to add '\(fileName, privacy: .public)' to container: \(error.localizedDescription)"
)
failedFileCount += 1
}
}
Expand Down Expand Up @@ -226,31 +237,55 @@ public actor CryptoContainer: CryptoContainerProtocol, Loggable {
}

private func save(_ source: URL, as destination: URL) throws {
if fileManager.fileExists(atPath: destination.resolvedPath) {
try fileManager.removeItem(at: destination)
}
try fileManager.copyItem(at: source, to: destination)
}

public func saveDataFile(dataFile: URL, to directory: URL?) async throws -> URL {
let sanitizedName = dataFile.lastPathComponent.sanitized()
let savedFilesDirectory = try directory ?? Directories.getCacheDirectory(
subfolders: [CommonsLib.Constants.Folder.SavedFiles],
fileManager: fileManager
)
let file = savedFilesDirectory.appending(path: sanitizedName)
let dataFiles = await getDataFiles()

for containerDataFile in dataFiles
where dataFile.lastPathComponent == containerDataFile.lastPathComponent {
if !fileManager.fileExists(atPath: file.resolvedPath) {
try save(containerDataFile, as: file)
}
return file
}
throw CryptoError.containerDataFileSavingFailed(
CryptoErrorDetail(
message: "Could not find file in container",
userInfo: ["fileName": dataFile.lastPathComponent]
guard let index = dataFiles.firstIndex(
where: { $0.lastPathComponent == dataFile.lastPathComponent }
) else {
throw CryptoError.containerDataFileSavingFailed(
CryptoErrorDetail(
message: "Could not find file in container",
userInfo: ["fileName": dataFile.lastPathComponent]
)
)
}

let file = savedFilesDirectory.appending(
path: savedFileName(for: index, in: dataFiles)
)

guard file.isWithin(directory: savedFilesDirectory) else {
throw CryptoError.containerDataFileSavingFailed(
CryptoErrorDetail(
message: "Failed to save file",
userInfo: ["fileName": dataFile.lastPathComponent]
)
)
}

try save(dataFiles[index], as: file)
return file
}

private func savedFileName(for index: Int, in dataFiles: [URL]) -> String {
let sanitizedName = dataFiles[index].lastPathComponent.sanitized()

let hasDuplicateName = dataFiles.enumerated().contains { offset, other in
offset != index && other.lastPathComponent.sanitized() == sanitizedName
}

return hasDuplicateName ? sanitizedName.appendingIndex(index) : sanitizedName
}
}

Expand Down Expand Up @@ -395,15 +430,35 @@ extension CryptoContainer {
subfolders: [Constants.Folder.ContainerFolder, Constants.Folder.Temp],
fileManager: fileManager
)
return try decryptedData.map { name, data in
let fileUrl = destinationPath.appending(path: name.sanitized(), directoryHint: .notDirectory)
var usedNames: Set<String> = []
var urlDataFiles: [URL] = []

for (name, data) in decryptedData {
let fileName = name.sanitized().uniqueFileName(taken: &usedNames)
let fileUrl = destinationPath.appending(path: fileName, directoryHint: .notDirectory)

guard fileUrl.isWithin(directory: destinationPath) else {
throw CryptoError.containerOpeningFailed(
CryptoErrorDetail(
message: "Cannot open container with invalid CDOC info",
userInfo: ["fileName": fileName]
)
)
}

guard fileManager.createFile(atPath: fileUrl.resolvedPath, contents: data, attributes: nil) else {
CryptoContainer.logger().error(
"Unable to create file at path: \(fileUrl.resolvedPath, privacy: .public)"
)
throw CryptoError.containerDataFileSavingFailed(
CryptoErrorDetail(message: "Unable to create decrypted file", userInfo: ["fileName": name])
CryptoErrorDetail(message: "Unable to create decrypted file", userInfo: ["fileName": fileName])
)
}
return fileUrl

urlDataFiles.append(fileUrl)
}

return urlDataFiles
}

@MainActor
Expand Down Expand Up @@ -460,10 +515,9 @@ extension CryptoContainer {
var cryptoDataFiles: [CryptoDataFile] = []

for dataFile in dataFiles {

cryptoDataFiles.append(
CryptoDataFile(
filename: dataFile.lastPathComponent,
filename: dataFile.lastPathComponent.sanitized(),
filePath: dataFile.resolvedPath
)
)
Expand Down Expand Up @@ -493,7 +547,8 @@ extension CryptoContainer {
var dataFiles: [URL] = []

for dataFile in cryptoDataFiles {
let fileUrl = URL(fileURLWithPath: dataFile.filePath ?? "").appending(path: dataFile.filename)
let fileUrl = URL(fileURLWithPath: dataFile.filePath ?? "")
.appending(path: dataFile.filename)

dataFiles.append(fileUrl)
}
Expand Down
Loading
Loading