Skip to content

Managing containers

Boriss Melikjan edited this page Apr 6, 2026 · 1 revision

Creating or opening container

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 -> SignedContainerProtocol
let container = try await SignedContainer.openOrCreate(dataFiles: dataFiles, isSivaConfirmed: isSivaConfirmed)

Open

static func open(file: URL, isSivaConfirmed: Bool) async throws -> SignedContainerProtocol

Create

static func create(
    containerFile: URL,
    dataFiles: [URL]
) async throws -> SignedContainerProtocol

Adding and removing files

Files 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 -> SignedContainerProtocol
do {
    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 -> SignedContainerProtocol
do {
    if dataFiles.count == 1 {
        try fileManager.removeItem(at: containerFile)
        ...
    }

    let container = try await container.removeDataFile(index: index, containerFile: containerFile)
    await loadContainerData(signedContainer: container)
    ...
} catch {}

Adding and removing signatures

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.

Adding signature with a physical ID-card

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() async

When finished working with ID-card related functionality, stopDiscoveringReaders method should be called.

func stopDiscoveringReaders(with status: UsbReaderStatus = .sInitial) async

Implement 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
)

Adding signature with Mobile-ID

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 -> MobileIdCertificateResponse
import 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 -> MobileIdSignatureResponse
import 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 -> MobileIdSessionResponse
import 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
)

Adding signature with Smart-ID

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 -> SmartIdSessionIdResponse
import 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 -> SmartIdSessionResponse
import 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 -> String
return 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 -> SmartIdSessionIdResponse
import 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
)

Container contents

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 -> SignedContainerProtocol
func 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 -> URL
let 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 { ... }