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
10 changes: 6 additions & 4 deletions Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,10 @@ + (void)decryptFile:(NSString *)fullPath withCert:(NSData *)certData withToken:(
return completion(nil, [NSError cryptoError:@"Failed to find lock for cert"]);
}
std::vector<uint8_t> fmk;
if(reader->getFMK(fmk, unsigned(idx)) != 0 || fmk.empty()) {
return completion(nil, token.lastError() ?: [NSError cryptoError:@"Failed to get FMK"]);
libcdoc::result_t fmkResult = reader->getFMK(fmk, unsigned(idx));
if(fmkResult != 0 || fmk.empty()) {
return completion(nil, token.lastError() ?: [NSError cryptoError:@"Failed to get FMK"
code:fmkResult != 0 ? fmkResult : 1000]);
}
NSError *error = nil;
completion([self decryptReader:*reader withFMK:fmk error:&error], error);
Expand Down Expand Up @@ -246,7 +248,7 @@ + (void)decryptFile:(NSString *)fullPath withCert:(NSData *)certData withToken:(
+ (NSDictionary<NSString*,NSData*> *)decryptReader:(libcdoc::CDocReader&)reader withFMK:(const std::vector<uint8_t>&)fmk error:(NSError**)error {

if(reader.beginDecryption(fmk) != 0) {
return [NSError cryptoError:@"Failed to start encryption" error:error];
return [NSError cryptoError:@"Failed to start decryption" error:error];
}

NSMutableDictionary<NSString*,NSData*> *response = [NSMutableDictionary new];
Expand Down Expand Up @@ -275,7 +277,7 @@ + (void)decryptFile:(NSString *)fullPath withCert:(NSData *)certData withToken:(
[response setObject:data forKey:[NSString stringWithStdString:name]];
}
if (reader.finishDecryption() != 0)
return [NSError cryptoError:@"Failed to end encryption" error:error];
return [NSError cryptoError:@"Failed to end decryption" error:error];
return response;
}

Expand Down
10 changes: 5 additions & 5 deletions Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.mm
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ + (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray<CryptoDataFile*>
}
}
}
if (writer->beginEncryption() != 0) {
return completion([NSError cryptoError:@"Failed to start encryption"]);

if (libcdoc::result_t result = writer->beginEncryption(); result != 0) {
return completion([NSError cryptoError:@"Failed to start encryption" code:result]);
}

for (CryptoDataFile *dataFile in dataFiles) {
Expand Down Expand Up @@ -219,8 +219,8 @@ + (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray<CryptoDataFile*>
return completion([NSError cryptoError:@"Failed to create key"]);
}

if (writer->beginEncryption() != 0) {
return completion([NSError cryptoError:@"Failed to start encryption"]);
if (libcdoc::result_t result = writer->beginEncryption(); result != 0) {
return completion([NSError cryptoError:@"Failed to start encryption" code:result]);
}

for (CryptoDataFile *dataFile in dataFiles) {
Expand Down
8 changes: 7 additions & 1 deletion Modules/CryptoLib/Sources/CryptoObjC/include/Extensions.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
#include <vector>

static const NSInteger CryptoLibWrongKeyErrorCode = -109; // libcdoc::WRONG_KEY
static const NSInteger CryptoLibNetworkErrorCode = -300; // libcdoc::NetworkBackend::NETWORK_ERROR

@interface NSError (CryptoLib)
+ (NSError*)cryptoError:(NSString*)msg;
+ (NSError*)cryptoError:(NSString*)msg code:(NSInteger)code;
+ (id)cryptoError:(NSString*)msg error:(NSError**)error;
+ (id)cryptoWrongKeyError:(NSError**)error;
@end
Expand Down Expand Up @@ -71,7 +73,11 @@ static const NSInteger CryptoLibWrongKeyErrorCode = -109; // libcdoc::WRONG_KEY

@implementation NSError (CryptoLib)
+ (NSError*)cryptoError:(NSString *)msg {
return [[NSError alloc] initWithDomain:@"ee.ria.digidoc.CryptoLib" code:1000 userInfo: @{NSLocalizedDescriptionKey: msg}];
return [NSError cryptoError:msg code:1000];
}

+ (NSError*)cryptoError:(NSString *)msg code:(NSInteger)code {
return [[NSError alloc] initWithDomain:@"ee.ria.digidoc.CryptoLib" code:code userInfo: @{NSLocalizedDescriptionKey: msg}];
}

+ (id)cryptoError:(NSString*)msg error:(NSError**)error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ import CryptoObjCWrapper
public struct OpenLdapSearchResult: Sendable {
public var addressees: [Addressee]
public var tooManyResults: Bool
public var isNetworkError: Bool

public init(addressees: [Addressee], tooManyResults: Bool, isNetworkError: Bool = false) {
self.addressees = addressees
self.tooManyResults = tooManyResults
self.isNetworkError = isNetworkError
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2017 - 2026 Riigi Infosüsteemi Amet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

import Foundation

public extension NSError {
// libcdoc NetworkBackend::NETWORK_ERROR
var isCryptoNetworkError: Bool {
return domain == "ee.ria.digidoc.CryptoLib" && code == -300
}
}
64 changes: 46 additions & 18 deletions Modules/CryptoLib/Sources/CryptoSwift/Ldap/OpenLdap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {
case decipherOnly = 8
}

private static let unreachableServerCodes: Set<Int32> = [
LDAP_SERVER_DOWN, LDAP_CONNECT_ERROR, LDAP_TIMEOUT, LDAP_X_CONNECTING
]

private struct SearchOutcome {
let addressees: [Addressee]
let totalAddressees: Int
let isNetworkError: Bool

init(addressees: [Addressee] = [], totalAddressees: Int = 0, isNetworkError: Bool = false) {
self.addressees = addressees
self.totalAddressees = totalAddressees
self.isNetworkError = isNetworkError
}
}

enum SearchType {
case personalCode(String)
case registryCode(String)
Expand Down Expand Up @@ -99,33 +115,39 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {
OpenLdap.logger().info("Searching with personal code from LDAP")
var result = [Addressee]()
var tooManyResults = false
var isNetworkError = false
for url in await self.ldapConfiguration.getLdapPersonURLS() {
let ldapPersonUrl = url
let (addresses, found) = OpenLdap.search(
let outcome = OpenLdap.search(
searchType: searchType,
url: ldapPersonUrl,
certificatePath: filePath
)
result.append(contentsOf: addresses)
if found >= 50 {
result.append(contentsOf: outcome.addressees)
if outcome.totalAddressees >= 50 {
tooManyResults = true
}
if outcome.isNetworkError {
isNetworkError = true
}
}
return OpenLdapSearchResult(
addressees: result,
tooManyResults: tooManyResults
tooManyResults: tooManyResults,
isNetworkError: isNetworkError
)
} else {
if let ldapCorpURL = await self.ldapConfiguration.getLdapCorpURL() {
OpenLdap.logger().info("Searching with corporation keyword from LDAP")
let (addresses, found) = OpenLdap.search(
let outcome = OpenLdap.search(
searchType: searchType,
url: ldapCorpURL,
certificatePath: filePath
)
return OpenLdapSearchResult(
addressees: addresses,
tooManyResults: found >= 50
addressees: outcome.addressees,
tooManyResults: outcome.totalAddressees >= 50,
isNetworkError: outcome.isNetworkError
)
} else {
return OpenLdapSearchResult(
Expand All @@ -141,21 +163,23 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {
searchType: SearchType,
url: URL,
certificatePath: String?
) -> (addressees: [Addressee], totalAddressees: Int) {
) -> SearchOutcome {
if url.scheme?.lowercased() == "ldaps" {
if let certificatePath = certificatePath, !certificatePath.isEmpty {
guard setLdapOption(option: LDAP_OPT_X_TLS_CACERTFILE, value: certificatePath) else { return ([], 0) }
guard setLdapOption(option: LDAP_OPT_X_TLS_CACERTFILE, value: certificatePath) else {
return SearchOutcome()
}
} else {
guard let bundlePath = Bundle(for: OpenLdap.self).resourcePath else { return ([], 0) }
guard setLdapOption(option: LDAP_OPT_X_TLS_CACERTDIR, value: bundlePath) else { return ([], 0) }
guard let bundlePath = Bundle(for: OpenLdap.self).resourcePath else { return SearchOutcome() }
guard setLdapOption(option: LDAP_OPT_X_TLS_CACERTDIR, value: bundlePath) else { return SearchOutcome() }
}
var ldapConnectionReset = 0
let result = ldap_set_option(nil, LDAP_OPT_X_TLS_NEWCTX, &ldapConnectionReset)
guard result == LDAP_SUCCESS else {
OpenLdap.logger().info(
"ldap_set_option(LDAP_OPT_X_TLS_NEWCTX) failed: \(String(cString: ldap_err2string(result)))"
)
return ([], 0)
return SearchOutcome()
}
}

Expand All @@ -175,7 +199,7 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {
}
guard ldapReturnCode == LDAP_SUCCESS else {
OpenLdap.logger().info("Failed to initialize LDAP: \(String(cString: ldap_err2string(ldapReturnCode)))")
return ([], 0)
return SearchOutcome()
}

var ldapVersion = LDAP_VERSION3
Expand All @@ -184,7 +208,7 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {
OpenLdap.logger().info(
"ldap_set_option(PROTOCOL_VERSION) failed: \(String(cString: ldap_err2string(ldapReturnCode)))"
)
return ([], 0)
return SearchOutcome()
}

var distinguishedName = url.path
Expand Down Expand Up @@ -217,7 +241,7 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {

guard ldapReturnCode == LDAP_SUCCESS else {
OpenLdap.logger().info("ldap_search_ext failed: \(String(cString: ldap_err2string(ldapReturnCode)))")
return ([], 0)
return SearchOutcome(isNetworkError: unreachableServerCodes.contains(ldapReturnCode))
}

var result = [Addressee]()
Expand All @@ -234,18 +258,22 @@ final public class OpenLdap: OpenLdapProtocol, Loggable {
result.append(contentsOf: addressees)
totalAddressees += 1
case Int32(LDAP_RES_SEARCH_RESULT):
return (addressees: result, totalAddressees: totalAddressees)
return SearchOutcome(addressees: result, totalAddressees: totalAddressees)
case Int32(LDAP_SUCCESS):
break
default:
OpenLdap.logger().info("ldap_result failed: \(String(cString: ldap_err2string(ldapReturnCode)))")
return (addressees: result, totalAddressees: totalAddressees)
return SearchOutcome(
addressees: result,
totalAddressees: totalAddressees,
isNetworkError: unreachableServerCodes.contains(ldapReturnCode)
)
}
}

ldap_abandon_ext(ldap, msgId, nil, nil)

return (addressees: result, totalAddressees: totalAddressees)
return SearchOutcome(addressees: result, totalAddressees: totalAddressees)
}

static private func setLdapOption(option: Int32, value: String) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2017 - 2026 Riigi Infosüsteemi Amet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/

import Foundation
import Testing

@testable import CryptoSwift

struct NSErrorCryptoNetworkTests {

@Test
func isCryptoNetworkError_trueForLibcdocNetworkErrorFromCryptoLib() {
let error = NSError(domain: "ee.ria.digidoc.CryptoLib", code: -300)

#expect(error.isCryptoNetworkError)
}

@Test
func isCryptoNetworkError_falseForOtherCryptoLibCodes() {
let error = NSError(domain: "ee.ria.digidoc.CryptoLib", code: 1000)

#expect(!error.isCryptoNetworkError)
}

@Test
func isCryptoNetworkError_falseForSameCodeFromAnotherDomain() {
let error = NSError(domain: "LibdigidocLib", code: -300)

#expect(!error.isCryptoNetworkError)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import Foundation
import LibdigidocLibObjC

public struct ErrorDetail: Sendable {
private static let networkErrorCode = 20

public let message: String
public let code: Int
public let userInfo: [String: Sendable]
Expand All @@ -44,6 +46,11 @@ public struct ErrorDetail: Sendable {
.merging(extraInfo) { (_, combined) in combined }
}

// libdigidocpp reports connection failures as Exception::NetworkError (20)
public var isNetworkError: Bool {
return code == ErrorDetail.networkErrorCode
}

public var description: String {
return """
Error: \(self.message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ final class DigiDocErrorTests {
#expect(retrievedDetail.userInfo.isEmpty)
}

@Test
func isNetworkError_trueWhenCodeIsLibdigidocppNetworkError() {
let errorDetail = ErrorDetail(message: "Failed to create connection with host", code: 20)

#expect(errorDetail.isNetworkError)
}

@Test
func isNetworkError_falseWhenCodeIsGeneral() {
let errorDetail = ErrorDetail(message: "Failed to send request to SiVa", code: 0)

#expect(!errorDetail.isNetworkError)
}

@Test
func errorDetailDescription_successWithContainerOpeningFailedError() {
let errorDetail = ErrorDetail(message: "An error occurred", code: 123, userInfo: ["reason": "test case"])
Expand Down
11 changes: 11 additions & 0 deletions RIADigiDoc/Domain/Model/ToastMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import Foundation
import LibdigidocLibSwift

struct ToastMessage: Sendable, Equatable {
let key: String
Expand All @@ -28,3 +29,13 @@ struct ToastMessage: Sendable, Equatable {
self.args = args
}
}

extension ToastMessage {
static func containerOpeningFailed(fileName: String, error: Error) -> ToastMessage {
guard let digiDocError = error as? DigiDocError, digiDocError.errorDetail.isNetworkError else {
return ToastMessage(key: "Failed to open container", args: [fileName])
}

return ToastMessage(key: "No Internet connection")
}
}
7 changes: 7 additions & 0 deletions RIADigiDoc/Domain/NFC/OperationDecrypt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol {
return
}

if (error as NSError).isCryptoNetworkError {
OperationDecrypt.logger().error("NFC: Unable to reach the key server")
operationError = error
session.invalidate(errorMessage: strings?.networkErrorMessage ?? "")
return
}

handleUnknownError(error, session: session)
}
}
Expand Down
Loading
Loading