-
Notifications
You must be signed in to change notification settings - Fork 0
Managing containers
Before you can sign documents, you need to add them to a container.
Use openOrCreate method from LibdigidocLib SignedContainer class. Make sure you include container name and .asice extension in container path.
@MainActor
public static func openOrCreate(
dataFiles: [URL],
containerUtil: ContainerUtilProtocol = Container.shared.containerUtil(),
isSivaConfirmed: Bool
) async throws -> SignedContainerProtocollet container = try await SignedContainer.openOrCreate(dataFiles: dataFiles, isSivaConfirmed: isSivaConfirmed)static func open(file: URL, isSivaConfirmed: Bool) async throws -> SignedContainerProtocolstatic func create(
containerFile: URL,
dataFiles: [URL]
) async throws -> SignedContainerProtocolFiles can be added to existing container or removed, but only if container has not been signed by anyone yet.
To add files to container use addDataFiles method from LibdigidocLib SignedContainer class:
@discardableResult func addDataFiles(
_ dataFiles: [URL],
to containerFile: URL
) async throws -> SignedContainerProtocoldo {
try validateFiles(files)
} catch {
handleFileValidationError(error)
...
}
do {
let updatedContainer = try await signedContainer?.addDataFiles(files, to: container)
...
await loadContainerData(signedContainer: updatedContainer)
} catch {
await handleAddFilesError(error, container: container)
}To remove files from container use removeDataFile method from LibdigidocLib SignedContainer class:
@discardableResult func removeDataFile(index: Int, containerFile: URL) async throws -> SignedContainerProtocoldo {
if dataFiles.count == 1 {
try fileManager.removeItem(at: containerFile)
...
}
let container = try await container.removeDataFile(index: index, containerFile: containerFile)
await loadContainerData(signedContainer: container)
...
} catch {}Signatures can be added to containers only. Add all documents to container before signing it. Additional files can't be added to already signed container. If you still need to add files to signed container, you need to remove all signatures from it first and have all parties sign updated container again.
There are two options for signing - with Mobile-ID, with Smart-ID or with physical ID-card (via USB card reader or NFC). You should let user choose between those four methods. RIA-DigiDoc-iOS has separate methods for these cases.
One option signing with a physical ID-card, user must have a card reader, that they can connect to their device.
RIA-DigiDoc-iOS currently supports one card reader:
In order to add a signature to the container with ID-card or acquiring ID-card information you must first start discovering supported card readers by using startDiscoveringReaders method.
func startDiscoveringReaders() asyncWhen finished working with ID-card related functionality, stopDiscoveringReaders method should be called.
func stopDiscoveringReaders(with status: UsbReaderStatus = .sInitial) asyncImplement UsbReaderConnection protocol of IdCardLib module to receive status about the card reader state.
Set the delegate by calling ReaderInterface's method setDelegate:
private final class UsbReaderInterfaceHandler: NSObject, ReaderInterfaceDelegate, Sendable, Loggable {
@MainActor
private let readerInterface = ReaderInterface()
private let usbReaderConnection: UsbReaderConnectionProtocol
init(
usbReaderConnection: UsbReaderConnectionProtocol
) {
self.usbReaderConnection = usbReaderConnection
super.init()
Task { @MainActor in
readerInterface.setDelegate(self)
}
}
ID-card related methods can be used only when state sCardConnected has been returned within UsbReaderConnection updateStatus method to the global UsbReaderConnection class status parameter.
To add signature with ID-card, use UsbReaderConnection calculateSignature method:
func calculateSignature(for dataToSign: Data, pin2: SecureData) async throws -> Data {
try await idCardService.calculateSignature(for: dataToSign, pin2: pin2)
}func calculateSignature(for dataToSign: Data, pin2: SecureData) async throws -> Data {
try await usbReaderConnection.calculateSignature(for: dataToSign, pin2: pin2)
}let signatureData = try await idCardRepository.calculateSignature(
for: dataToSign,
pin2: pinSecureData
)For signing with Mobile-ID, you must provide some data about the person that is providing signature. ID code and phone number are required to verify, that SIM card is allowed to give signature.
Mobile-ID signing is in MobileIdLib module and uses Mobile-ID REST API.
To get the certificate use getCertificateRequest method of MobileIdSignService class:
public func getCertificateRequest(
url: String,
relyingPartyName: String,
relyingPartyUUID: String,
phoneNumber: String,
nationalIdentityNumber: String,
trustedCertificates: [SecCertificate],
proxyInfo: ProxyInfo,
userAgent: String
) async throws -> MobileIdCertificateResponseimport MobileIdLib
let certResponse = try await mobileIdSignService
.getCertificateRequest(
url: "\(midUrl)\(MobileIdViewModel.certificateEndpoint)",
relyingPartyName: Constants.Signing.RelyingPartyName,
relyingPartyUUID: uuid,
phoneNumber: phoneNumber,
nationalIdentityNumber: personalCode,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)
guard let certData = Data(
base64Encoded: certResponse.cert ?? ""
) else {
...
}To get the hash, use the prepareSignature method in SignedContainer class:
return try await signedContainer.prepareSignature(
cert: cert,
containerPath: containerFile,
roleData: roleData,
userAgent: userAgent
)To get the verification code, use the getVerificationCode method of MobileIdSignService class: You need to display challenge ID in your application, so user can verify, that signature is given with correct device.
public func getVerificationCode(hash: Data) async -> String?guard let verificationCode = await mobileIdSignService.getVerificationCode(hash: hash) else { ... }After getting the certificate, you need to get the session ID. Use the getSignatureRequest method of MobileIdSignService class:
public func getSignatureRequest(
url: String,
relyingPartyName: String,
relyingPartyUUID: String,
phoneNumber: String,
nationalIdentityNumber: String,
hash: Data,
hashType: String,
language: String,
displayText: String,
displayTextFormat: String,
trustedCertificates: [SecCertificate],
proxyInfo: ProxyInfo,
userAgent: String
) async throws -> MobileIdSignatureResponseimport MobileIdLib
let signatureResponse = try await mobileIdSignService.getSignatureRequest(
url: "\(midUrl)\(MobileIdViewModel.signatureEndpoint)",
relyingPartyName: Constants.Signing.RelyingPartyName,
relyingPartyUUID: uuid,
phoneNumber: phoneNumber,
nationalIdentityNumber: personalCode,
hash: hash,
hashType: Constants.Signing.HashType,
language: getThreeLetterLanguage(from: language),
displayText: NSLocalizedString("Sign document", comment: ""),
displayTextFormat: Constants.MobileId.DisplayTextFormat,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)
guard let sessionId = signatureResponse.sessionID else { ... }After getting the session ID, you need to poll the session status. This is method is used to poll as long as Mobile-ID signing service responds. Use the getSessionRequest method of MobileIdSignService class:
public func getSessionRequest(
url: String,
sessionId: String,
pollingTimeout: Int,
trustedCertificates: [SecCertificate],
proxyInfo: ProxyInfo,
userAgent: String
) async throws -> MobileIdSessionResponseimport MobileIdLib
let sessionResponse = try await mobileIdSignService.getSessionRequest(
url: "\(midUrl)\(MobileIdViewModel.signatureSessionEndpoint)",
sessionId: sessionId,
pollingTimeout: Constants.Signing.DefaultTimeout,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)
guard let signatureData = sessionResponse.signature?.value else { ... }Obtaining signature can take some time. It is up to you if you want to block user actions until everything is finished, but it is not required.
In the end, use the addSignature method in SignedContainer class to add signature to container:
try Task.checkCancellation()
let updatedContainer = try await signedContainer.addSignature(
signature: signatureData,
containerFile: containerFile
)For signing with Smart-ID, you must provide some data about the person that is providing signature. Country and ID code are required to sign with Smart-ID.
Smart-ID signing is in SmartIdLib module and uses Smart-ID REST API.
To get the CertResponse use getCertificateRequest method of SmartIdSignService class:
public func getCertificateRequest(
url: String,
relyingPartyName: String,
relyingPartyUUID: String,
country: String,
nationalIdentityNumber: String,
trustedCertificates: [SecCertificate],
proxyInfo: ProxyInfo,
userAgent: String
) async throws -> SmartIdSessionIdResponseimport SmartIdLib
let certResponse = try await smartIdSignService
.getCertificateRequest(
url: "\(sidUrl)\(SmartIdViewModel.certificateEndpoint)",
relyingPartyName: Constants.Signing.RelyingPartyName,
relyingPartyUUID: uuid,
country: getCountry(smartIdCountry: country),
nationalIdentityNumber: personalCode,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)
guard let sessionId = certResponse.sessionID else {
throw SmartIdError.missingSessionId
}After getting the CertResponse, you need to request Session (SmartIdSessionResponse). Use the getSessionRequest method of SmartIdSignService class:
public func getSessionRequest(
url: String,
sessionId: String,
pollingTimeout: Int,
trustedCertificates: [SecCertificate],
proxyInfo: ProxyInfo,
userAgent: String
) async throws -> SmartIdSessionResponseimport SmartIdLib
return try await smartIdSignService.getSessionRequest(
url: sidUrl,
sessionId: sessionId,
pollingTimeout: pollingTimout,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)To get the hash, use the prepareSignature method in SignedContainer class:
return try await signedContainer.prepareSignature(
cert: cert,
containerPath: containerFile,
roleData: roleData,
userAgent: userAgent
)To get the verification code, use the getVerificationCode method of SmartIdSignService class:
public func getVerificationCode(digest: Data) async -> Stringreturn await smartIdSignService.getVerificationCode(digest: sha256(data: hash))After getting certificate session request, you need to request signature data. Use the getSignatureRequest method of SmartIdSignService class:
public func getSignatureRequest(
url: String,
relyingPartyName: String,
relyingPartyUUID: String,
documentNumber: String,
hash: Data,
hashType: String,
allowedInteractionsOrderType: String,
displayText200: String,
trustedCertificates: [SecCertificate],
proxyInfo: ProxyInfo,
userAgent: String
) async throws -> SmartIdSessionIdResponseimport SmartIdLib
let certResponse = try await smartIdSignService
.getSignatureRequest(
url: "\(sidUrl)\(SmartIdViewModel.signatureEndpoint)",
relyingPartyName: Constants.Signing.RelyingPartyName,
relyingPartyUUID: uuid,
documentNumber: documentNumber,
hash: hash,
hashType: hashType,
allowedInteractionsOrderType: allowedInteractionsOrderType,
displayText200: displayText,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)
guard let sessionId = certResponse.sessionID else {
throw SmartIdError.missingSessionId
}After getting the SignResponse, you need to request Session (SmartIdSessionResponse). Use the getSessionRequest method of SmartIdSignService class.
let sessionResponse = try await requestSession(
sidUrl: "\(sidUrl)\(SmartIdViewModel.sessionEndpoint)",
sessionId: sessionId,
pollingTimout: pollingTimeout,
trustedCertificates: trustedCertificates,
proxyInfo: proxyInfo,
userAgent: userAgent
)
guard let signature = sessionResponse.signature?.value else {
throw SmartIdError.technicalError
}Obtaining signature can take some time. It is up to you if you want to block user actions until everything is finished, but it is not required.
In the end, use the addSignature method in SignedContainer class to add signature to container:
try Task.checkCancellation()
let updatedContainer = try await signedContainer.addSignature(
signature: signatureData,
containerFile: containerFile
)SignedContainer container parameter of ContainerWrapper object type gives you basic information about contents of container. If you don't have SignedContainer object, you can let LibdigiDocLib SignedContainer class to create it with method openOrCreate like this:
@MainActor
public static func openOrCreate(
dataFiles: [URL],
containerUtil: ContainerUtilProtocol = Container.shared.containerUtil(),
isSivaConfirmed: Bool
) async throws -> SignedContainerProtocolfunc openOrCreateContainer(dataFiles: [URL], isSivaConfirmed: Bool) async throws -> SignedContainerProtocol {
return try await SignedContainer.openOrCreate(dataFiles: dataFiles, isSivaConfirmed: isSivaConfirmed)
}SignedContainer will give you a list of signatures and file attached to container. If you need to access one of the files in it, you need to export it somewhere where your application has access.
To extract data file from container and save it use saveDataFile method:
public func saveDataFile(dataFile: DataFileWrapper, to directory: URL?) async throws -> URLlet savedFilesDirectory = try directory ?? Directories.getCacheDirectory(
subfolders: [CommonsLib.Constants.Folder.SavedFiles],
fileManager: fileManager
)
let sanitizedFilename = {
let name = dataFile.fileName.sanitized()
return name.isEmpty ? CommonsLib.Constants.Container.DefaultName : name
}()
let tempSavedFileLocation = savedFilesDirectory.appending(path: sanitizedFilename)
do {
try await DigiDocContainerWrapper.container(
containerURL.resolvedPath,
saveDataFile: dataFile.fileName,
to: tempSavedFileLocation.resolvedPath
)
return tempSavedFileLocation
} catch { ... }