Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)libdigidocppVersion;
+ (NSString *)mediaType;

+ (void)extendLastSignatureToLTA:(NSString *)containerPath completion:(void (^)(NSError * _Nullable error))completion;

// Extends validity of all signatures. Legacy containers (DDOC, BDOC time-mark) are wrapped into a new
// ASiC-S container (written to outputAsicsPath) with an archive timestamp. ASiC-E containers are extended in place.
// The completion returns the path that was actually written.
+ (void)extendContainerToLTA:(NSString *)containerPath
outputAsicsPath:(NSString *)outputAsicsPath
completion:(void (^)(NSString * _Nullable savedPath, NSError * _Nullable error))completion;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@
#import <digidocpp/Signature.h>
#import <digidocpp/Exception.h>
#import <digidocpp/crypto/X509Cert.h>
#import <digidocpp/crypto/Signer.h>

#import "DigiDocContainerWrapper.h"
#import "../Model/DigiDocContainer.h"
#import "../Model/DigiDocDataFile.h"
#import "../Model/DigiDocSignature.h"
#import "Exception/Util/ExceptionUtil.h"

@interface DigiDocContainerWrapper ()
+ (DigiDocSignatureStatus)determineSignatureStatus:(int)status;
@end

struct DigiDocContainerOpenCB: public digidoc::ContainerOpenCB {
private:
bool validate;
Expand All @@ -44,6 +49,50 @@ virtual bool validateOnline() const override {
}
};

class TimestampSigner: public digidoc::Signer {
public:
explicit TimestampSigner(const std::string &userAgent) {
setUserAgent(userAgent);
}

digidoc::X509Cert cert() const override {
return digidoc::X509Cert();
}

std::vector<unsigned char> sign(const std::string &, const std::vector<unsigned char> &) const override {
throw digidoc::Exception(__FILE__, __LINE__, "TimestampSigner cannot sign. It only creates timestamps.");
}
};

static bool isSignatureInvalid(const digidoc::Signature *signature) {
digidoc::Signature::Validator validator(signature);
return [DigiDocContainerWrapper determineSignatureStatus:validator.status()] == Invalid;
}

static bool canExtend(const std::vector<digidoc::Signature *> &signatures, const std::string &mediaType) {
if (signatures.empty()) {
return false;
}
// An ASiC-S timestamped container is re-timestamped in place.
// It must not be wrapped into another ASiC-S.
if (mediaType == "application/vnd.etsi.asic-s+zip") {
return true;
}
if (mediaType != "application/vnd.etsi.asic-e+zip") {
return false;
}
for (const auto *signature : signatures) {
if (signature->profile().find("time-stamp") == std::string::npos) {
return false;
}
// Invalid signatures cannot be extended in place — wrap the container into an ASiC-S container instead.
if (isSignatureInvalid(signature)) {
return false;
}
}
return true;
}

@implementation DigiDocContainerWrapper {}

+ (void)dispatch:(void (^)(void))command completion:(void (^)(NSError *error))completion {
Expand Down Expand Up @@ -86,6 +135,7 @@ + (NSData *)getNSDataFromVector:(const std::vector<unsigned char>&)vectorData {
return [NSData dataWithBytes:vectorData.data() length:vectorData.size()];
}


+ (DigiDocSignatureStatus)determineSignatureStatus:(int)status {
typedef digidoc::Signature::Validator::Status Status;

Expand All @@ -106,7 +156,7 @@ + (DigiDocSignature *)getSignature:(digidoc::Signature *)signature pos:(int)pos

DigiDocSignature *digiDocSignature = [DigiDocSignature new];
digiDocSignature.signingCert = [DigiDocContainerWrapper getNSDataFromVector:signingCert];
digiDocSignature.timestampCert = [DigiDocContainerWrapper getNSDataFromVector:ocspCert];
digiDocSignature.timestampCert = [DigiDocContainerWrapper getNSDataFromVector:timestampCert];
digiDocSignature.ocspCert = [DigiDocContainerWrapper getNSDataFromVector:ocspCert];

std::string givename = signingCert.subjectName("GN");
Expand All @@ -133,6 +183,16 @@ + (DigiDocSignature *)getSignature:(digidoc::Signature *)signature pos:(int)pos
digiDocSignature.messageImprint = [NSData dataWithBytes:signature->messageImprint().data() length:signature->messageImprint().size()];
digiDocSignature.trustedSigningTime = [NSString stringWithUTF8String:signature->trustedSigningTime().c_str()];

auto archiveTimestamps = signature->ArchiveTimeStamps();
if (!archiveTimestamps.empty()) {
const auto& firstTS = archiveTimestamps.front();
digiDocSignature.archiveTimestampTime = [NSString stringWithUTF8String:firstTS.time.c_str()];
digiDocSignature.archiveTimestampCert = [DigiDocContainerWrapper getNSDataFromVector:firstTS.cert];
} else {
digiDocSignature.archiveTimestampTime = @"";
digiDocSignature.archiveTimestampCert = [NSData data];
}

std::vector<std::string> signerRoles = signature->signerRoles();
NSMutableArray* signerRolesList = [NSMutableArray arrayWithCapacity: signerRoles.size()];
for (auto const& signerRole: signerRoles) {
Expand Down Expand Up @@ -358,5 +418,52 @@ + (void)removeDataFileFromContainerWithPath:(NSString *)containerPath atIndex:(N
} completion:completion];
}

+ (void)extendLastSignatureToLTA:(NSString *)containerPath completion:(void (^)(NSError * _Nullable error))completion {
[self open:containerPath validateOnline:YES command:^(digidoc::Container &container) {
auto sigs = container.signatures();
if (!sigs.empty()) {
TimestampSigner signer(digidoc::userAgent());
signer.setProfile("time-stamp-archive");
sigs.back()->extendSignatureProfile(&signer);
}
container.save(containerPath.UTF8String);
} completion:completion];
}

+ (void)extendContainerToLTA:(NSString *)containerPath
outputAsicsPath:(NSString *)outputAsicsPath
completion:(void (^)(NSString * _Nullable savedPath, NSError * _Nullable error))completion {
__block NSString *savedPath = nil;
[self open:containerPath validateOnline:YES command:^(digidoc::Container &container) {
auto signatures = container.signatures();
const std::string mediaType = container.mediaType();

if (canExtend(signatures, mediaType)) {
TimestampSigner signer(digidoc::userAgent());
signer.setProfile("time-stamp-archive");
for (auto *signature : signatures) {
signature->extendSignatureProfile(&signer);
}
container.save(containerPath.UTF8String);
savedPath = containerPath;
} else {
auto asics = digidoc::Container::createPtr(outputAsicsPath.UTF8String);
if (!asics) {
throw digidoc::Exception(__FILE__, __LINE__, "Failed to create ASiC-S container");
}
TimestampSigner signer(digidoc::userAgent());
signer.setProfile("TimeStampToken");
asics->addDataFile(containerPath.UTF8String, mediaType);
asics->sign(&signer);
asics->save(outputAsicsPath.UTF8String);
savedPath = outputAsicsPath;
}
} completion:^(NSError * _Nullable error) {
if (completion) {
completion(error ? nil : savedPath, error);
}
}];
}


@end
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,8 @@ typedef NS_ENUM(int, DigiDocSignatureStatus) {
@property (nonatomic, assign) DigiDocSignatureStatus status;
@property (nonatomic, strong) NSString *diagnosticsInfo;

@property (nonatomic, strong) NSString *archiveTimestampTime;
@property (nonatomic, strong) NSData *archiveTimestampCert;

@end

Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable {
return self.mediatype
}

public func getContainerURL() async -> URL {
return self.containerURL
}

@MainActor
public func saveDataFile(dataFile: DataFileWrapper, to directory: URL?) async throws -> URL {
let savedFilesDirectory = try directory ?? Directories.getCacheDirectory(
Expand Down Expand Up @@ -267,6 +271,71 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable {
}
}

@discardableResult
public func extendSignatureToLTA(containerFile: URL) async throws -> ContainerWrapperProtocol {
do {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
DigiDocContainerWrapper.extendLastSignature(toLTA: containerFile.resolvedPath) { error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
}
}
return try await open(containerFile: containerFile, isSivaConfirmed: true)
} catch {
let nsError = (error as NSError?) ??
NSError(domain: "ContainerWrapper - cannot extend signature to LTA", code: 8)
throw DigiDocError.signatureExtensionFailed(ErrorDetail(nsError: nsError))
}
}

@discardableResult
public func extendSignaturesToLTA(containerFile: URL) async throws -> ContainerWrapperProtocol {
do {
ContainerWrapper.logger().info("Extending signatures to LTA")
let outputURL = containerFile
.deletingPathExtension()
.appendingPathExtension(CommonsLib.Constants.Extension.Asics)

let savedPath: String = try await withCheckedThrowingContinuation { continuation in
DigiDocContainerWrapper.extendContainer(
toLTA: containerFile.resolvedPath,
outputAsicsPath: outputURL.resolvedPath
) { resultPath, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: resultPath ?? containerFile.resolvedPath)
}
}
}

let isWrappedIntoAsics = savedPath != containerFile.resolvedPath
ContainerWrapper.logger().info(
"Extended signatures to LTA. Wrapped into new ASiC-S: \(isWrappedIntoAsics, privacy: .public)"
)

if isWrappedIntoAsics {
do {
try fileManager.removeItem(at: containerFile)
} catch {
ContainerWrapper.logger().error(
"Unable to remove original container after wrapping to ASiC-S: \(error, privacy: .public)"
)
}
}

return try await open(containerFile: URL(fileURLWithPath: savedPath), isSivaConfirmed: true)
} catch {
ContainerWrapper.logger().error("Unable to extend signatures to LTA: \(error, privacy: .public)")
let nsError = (error as NSError?) ??
NSError(domain: "ContainerWrapper - cannot extend signatures to LTA", code: 9)
throw DigiDocError.signatureExtensionFailed(ErrorDetail(nsError: nsError))
}
}

private static func signatureStatusToDigiDocStatus(_ status: DigiDocSignatureStatus) -> SignatureStatus {
switch status {
case .Valid:
Expand Down Expand Up @@ -343,7 +412,9 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable {
status: signatureStatusToDigiDocStatus(signature.status),
format: signature.format,
messageImprint: signature.messageImprint,
diagnosticsInfo: signature.diagnosticsInfo
diagnosticsInfo: signature.diagnosticsInfo,
archiveTimestampTime: signature.archiveTimestampTime,
archiveTimestampCert: signature.archiveTimestampCert
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public protocol ContainerWrapperProtocol: Sendable {
func getSignatures() async -> [SignatureWrapper]
func getDataFiles() async -> [DataFileWrapper]
func getMimetype() async -> String
func getContainerURL() async -> URL
func create(file: URL, dataFiles: [String]) async throws
func open(containerFile: URL, isSivaConfirmed: Bool) async throws -> ContainerWrapper
@discardableResult func addDataFiles(containerFile: URL, dataFiles: [URL]) async throws -> ContainerWrapperProtocol
Expand All @@ -38,6 +39,8 @@ public protocol ContainerWrapperProtocol: Sendable {
userAgent: String
) async throws -> Data
func addSignature(signature: Data, containerFile: URL) async throws -> ContainerWrapperProtocol
@discardableResult func extendSignatureToLTA(containerFile: URL) async throws -> ContainerWrapperProtocol
@discardableResult func extendSignaturesToLTA(containerFile: URL) async throws -> ContainerWrapperProtocol
}

extension ContainerWrapperProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public struct SignatureWrapper: Sendable, Identifiable, Hashable {
public var status: SignatureStatus
public var diagnosticsInfo: String

public var archiveTimestampTime: String
public var archiveTimestampCert: Data

public var isLTAExtended: Bool {
!archiveTimestampCert.isEmpty
}

public init(pos: Int,
signingCert: Data,
timestampCert: Data,
Expand All @@ -71,7 +78,9 @@ public struct SignatureWrapper: Sendable, Identifiable, Hashable {
status: SignatureStatus = .unknown,
format: String,
messageImprint: Data,
diagnosticsInfo: String) {
diagnosticsInfo: String,
archiveTimestampTime: String = "",
archiveTimestampCert: Data = Data()) {
self.pos = pos
self.signingCert = signingCert
self.timestampCert = timestampCert
Expand All @@ -92,5 +101,7 @@ public struct SignatureWrapper: Sendable, Identifiable, Hashable {
self.format = format
self.messageImprint = messageImprint
self.diagnosticsInfo = diagnosticsInfo
self.archiveTimestampTime = archiveTimestampTime
self.archiveTimestampCert = archiveTimestampCert
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum DigiDocError: Error {
case signatureRemovingFailed(ErrorDetail)
case dataFileRemovingFailed(ErrorDetail)
case signatureAddingFailed(ErrorDetail)
case signatureExtensionFailed(ErrorDetail)

public var errorDetail: ErrorDetail {
switch self {
Expand All @@ -43,7 +44,8 @@ public enum DigiDocError: Error {
.containerDataFileSavingFailed(let errorDetail),
.signatureRemovingFailed(let errorDetail),
.dataFileRemovingFailed(let errorDetail),
.signatureAddingFailed(let errorDetail):
.signatureAddingFailed(let errorDetail),
.signatureExtensionFailed(let errorDetail):
return errorDetail

case .alreadyInitialized:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,45 @@ public actor SignedContainer: SignedContainerProtocol, Loggable {
containerUtil: containerUtil
)
}

@discardableResult
public func extendSignature() async throws -> SignedContainerProtocol {
guard let containerFile else {
throw DigiDocError.signatureExtensionFailed(
ErrorDetail(message: "Cannot extend signature: container file is nil", code: 0)
)
}
let containerWrapper = try await container.extendSignatureToLTA(containerFile: containerFile)
return SignedContainer(
containerFile: containerFile,
isExistingContainer: true,
container: containerWrapper,
timestamps: timestamps,
fileManager: fileManager,
containerUtil: containerUtil
)
}

@discardableResult
public func extendSignatures() async throws -> SignedContainerProtocol {
guard let containerFile else {
SignedContainer.logger().error("Cannot extend signatures: container file is nil")
throw DigiDocError.signatureExtensionFailed(
ErrorDetail(message: "Cannot extend signatures: container file is nil", code: 0)
)
}
SignedContainer.logger().info("Extending signatures to LTA")
let containerWrapper = try await container.extendSignaturesToLTA(containerFile: containerFile)
let extendedContainerFile = await containerWrapper.getContainerURL()
return SignedContainer(
containerFile: extendedContainerFile,
isExistingContainer: true,
container: containerWrapper,
timestamps: timestamps,
fileManager: fileManager,
containerUtil: containerUtil
)
}
}

extension SignedContainer {
Expand Down
Loading
Loading