diff --git a/.gitignore b/.gitignore
index 2f5e0368..3bc301d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@ timeline.xctimeline
playground.xcworkspace
.build/
+build-cdoc/
+build-cdoc-debug/
# Swift Package Manager
Packages/
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/Info.plist b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/Info.plist
index cf729eac..fec02694 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/Info.plist
+++ b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/Info.plist
@@ -8,32 +8,32 @@
BinaryPath
cdoc.framework/cdoc
LibraryIdentifier
- ios-arm64_x86_64-simulator
+ ios-arm64
LibraryPath
cdoc.framework
SupportedArchitectures
arm64
- x86_64
SupportedPlatform
ios
- SupportedPlatformVariant
- simulator
BinaryPath
cdoc.framework/cdoc
LibraryIdentifier
- ios-arm64
+ ios-arm64_x86_64-simulator
LibraryPath
cdoc.framework
SupportedArchitectures
arm64
+ x86_64
SupportedPlatform
ios
+ SupportedPlatformVariant
+ simulator
CFBundlePackageType
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Headers/Io.h b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Headers/Io.h
index c7d3d81a..cdf9fd5c 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Headers/Io.h
+++ b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Headers/Io.h
@@ -48,17 +48,17 @@ struct CDOC_EXPORT DataConsumer {
* @param size the number of bytes to write
* @return size or error code
*/
- virtual result_t write(const uint8_t *src, size_t size) = 0;
+ virtual result_t write(const uint8_t *src, size_t size) noexcept = 0;
/**
* @brief informs DataConsumer that the writing is finished
* @return error code or OK
*/
- virtual result_t close() = 0;
+ virtual result_t close() noexcept = 0;
/**
* @brief checks whether DataSource is in error state
* @return true if error state
*/
- virtual bool isError() = 0;
+ virtual bool isError() noexcept = 0;
/**
* @brief get textual description of the last error
*
@@ -74,7 +74,7 @@ struct CDOC_EXPORT DataConsumer {
* @param src a vector
* @return vector size or error code
*/
- result_t write(const std::vector& src) {
+ result_t write(const std::vector& src) noexcept {
return write(src.data(), src.size());
}
/**
@@ -82,7 +82,7 @@ struct CDOC_EXPORT DataConsumer {
* @param src a string
* @return string length or error code
*/
- result_t write(const std::string& src) {
+ result_t write(const std::string& src) noexcept {
return write((const uint8_t *) src.data(), src.size());
}
/**
@@ -93,7 +93,7 @@ struct CDOC_EXPORT DataConsumer {
* @param src the input DataSource
* @return the number of bytes copied or error
*/
- result_t writeAll(DataSource& src);
+ result_t writeAll(DataSource& src) noexcept;
DataConsumer (const DataConsumer&) = delete;
DataConsumer& operator= (const DataConsumer&) = delete;
@@ -128,17 +128,17 @@ struct CDOC_EXPORT DataSource {
* @param size the number of bytes to read
* @return the number of bytes read or error code
*/
- virtual result_t read(uint8_t *dst, size_t size) { return NOT_IMPLEMENTED; }
+ virtual result_t read(uint8_t *dst, size_t size) noexcept { return NOT_IMPLEMENTED; }
/**
* @brief check whether DataConsumer is in error state
* @return true if error state
*/
- virtual bool isError() { return true; }
+ virtual bool isError() noexcept { return true; }
/**
* @brief check whether DataConsumer is reached to the end of data
* @return true if end of stream
*/
- virtual bool isEof() { return true; }
+ virtual bool isEof() noexcept { return true; }
/**
* @brief get textual description of the last error
*
@@ -169,7 +169,7 @@ struct CDOC_EXPORT DataSource {
* @param dst the destination DataConsumer
* @return error code or OK
*/
- result_t readAll(DataConsumer& dst) {
+ result_t readAll(DataConsumer& dst) noexcept {
return dst.writeAll(*this);
}
@@ -214,14 +214,14 @@ struct CDOC_EXPORT ChainedConsumer : public DataConsumer {
~ChainedConsumer() {
if (_owned) delete _dst;
}
- result_t write(const uint8_t *src, size_t size) override {
+ result_t write(const uint8_t *src, size_t size) noexcept override {
return _dst->write(src, size);
}
- result_t close() override {
+ result_t close() noexcept override {
if (_owned) return _dst->close();
return OK;
}
- bool isError() override {
+ bool isError() noexcept override {
return _dst->isError();
}
protected:
@@ -234,13 +234,13 @@ struct CDOC_EXPORT ChainedSource : public DataSource {
~ChainedSource() {
if (_owned) delete _src;
}
- result_t read(uint8_t *dst, size_t size) {
+ result_t read(uint8_t *dst, size_t size) noexcept override {
return _src->read(dst, size);
}
- bool isError() {
+ bool isError() noexcept override {
return _src->isError();
}
- bool isEof() {
+ bool isEof() noexcept override {
return _src->isEof();
}
protected:
@@ -264,13 +264,15 @@ struct CDOC_EXPORT IStreamSource : public DataSource {
return bool(_ifs->bad()) ? INPUT_STREAM_ERROR : OK;
}
- result_t read(uint8_t *dst, size_t size) {
+ result_t read(uint8_t *dst, size_t size) noexcept override try {
_ifs->read((char *) dst, size);
return (_ifs->bad()) ? INPUT_STREAM_ERROR : _ifs->gcount();
+ } catch(...) {
+ return INPUT_STREAM_ERROR;
}
- bool isError() { return _ifs->bad(); }
- bool isEof() { return _ifs->eof(); }
+ bool isError() noexcept override { return _ifs->bad(); }
+ bool isEof() noexcept override { return _ifs->eof(); }
protected:
std::istream *_ifs;
bool _owned;
@@ -285,17 +287,17 @@ struct CDOC_EXPORT OStreamConsumer : public DataConsumer {
if (_owned) delete _ofs;
}
- result_t write(const uint8_t *src, size_t size) {
+ result_t write(const uint8_t *src, size_t size) noexcept override {
_ofs->write((const char *) src, size);
return (_ofs->bad()) ? OUTPUT_STREAM_ERROR : size;
}
- result_t close() {
+ result_t close() noexcept override {
_ofs->flush();
return (_ofs->bad()) ? OUTPUT_STREAM_ERROR : OK;
}
- bool isError() { return _ofs->bad(); }
+ bool isError() noexcept override { return _ofs->bad(); }
protected:
std::ostream *_ofs;
bool _owned;
@@ -310,15 +312,15 @@ struct CDOC_EXPORT VectorSource : public DataSource {
return OK;
}
- result_t read(uint8_t *dst, size_t size) override {
+ result_t read(uint8_t *dst, size_t size) noexcept override {
size = std::min(size, _data.size() - _ptr);
std::copy_n(_data.cbegin() + _ptr, size, dst);
_ptr += size;
return size;
}
- bool isError() override { return false; }
- bool isEof() override { return _ptr >= _data.size(); }
+ bool isError() noexcept override { return false; }
+ bool isEof() noexcept override { return _ptr >= _data.size(); }
protected:
const std::vector& _data;
size_t _ptr;
@@ -326,12 +328,14 @@ struct CDOC_EXPORT VectorSource : public DataSource {
struct CDOC_EXPORT VectorConsumer : public DataConsumer {
VectorConsumer(std::vector& data) : _data(data) {}
- result_t write(const uint8_t *src, size_t size) override final {
+ result_t write(const uint8_t *src, size_t size) noexcept final try {
_data.insert(_data.end(), src, src + size);
return size;
+ } catch(...) {
+ return OUTPUT_STREAM_ERROR;
}
- result_t close() override final { return OK; }
- virtual bool isError() override final { return false; }
+ result_t close() noexcept final { return OK; }
+ virtual bool isError() noexcept final { return false; }
protected:
std::vector& _data;
};
@@ -340,15 +344,17 @@ struct CDOC_EXPORT FileListConsumer : public MultiDataConsumer {
FileListConsumer(const std::string& base_path) {
base = base_path;
}
- result_t write(const uint8_t *src, size_t size) override final {
+ result_t write(const uint8_t *src, size_t size) noexcept final try {
ofs.write((const char *) src, size);
return (ofs.bad()) ? OUTPUT_STREAM_ERROR : size;
+ } catch(...) {
+ return OUTPUT_STREAM_ERROR;
}
- result_t close() override final {
+ result_t close() noexcept final {
ofs.close();
return (ofs.bad()) ? OUTPUT_STREAM_ERROR : OK;
}
- bool isError() override final {
+ bool isError() noexcept final {
return ofs.bad();
}
result_t open(const std::string& name, int64_t size) override final {
@@ -378,11 +384,11 @@ struct CDOC_EXPORT FileListConsumer : public MultiDataConsumer {
struct CDOC_EXPORT FileListSource : public MultiDataSource {
FileListSource(const std::string& base, const std::vector& files);
- result_t read(uint8_t *dst, size_t size) override final;
- bool isError() override final;
- bool isEof() override final;
- result_t getNumComponents() override final;
- result_t next(std::string& name, int64_t& size) override final;
+ result_t read(uint8_t *dst, size_t size) noexcept final;
+ bool isError() noexcept final;
+ bool isEof() noexcept final;
+ result_t getNumComponents() final;
+ result_t next(std::string& name, int64_t& size) final;
protected:
std::filesystem::path _base;
const std::vector& _files;
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Info.plist b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Info.plist
index 831df5dd..f1db506c 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Info.plist
+++ b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/Info.plist
@@ -12,8 +12,6 @@
ee.ria.cdoc
CFBundleInfoDictionaryVersion
6.0
- CFBundleName
-
CFBundlePackageType
FMWK
CFBundleShortVersionString
@@ -21,7 +19,7 @@
CFBundleSignature
????
CFBundleVersion
- 552
+ 0
CSResourcesFileMapped
MinimumOSVersion
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/cdoc b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/cdoc
index a111b491..9ab727cb 100755
Binary files a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/cdoc and b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64/cdoc.framework/cdoc differ
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Headers/Io.h b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Headers/Io.h
index c7d3d81a..cdf9fd5c 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Headers/Io.h
+++ b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Headers/Io.h
@@ -48,17 +48,17 @@ struct CDOC_EXPORT DataConsumer {
* @param size the number of bytes to write
* @return size or error code
*/
- virtual result_t write(const uint8_t *src, size_t size) = 0;
+ virtual result_t write(const uint8_t *src, size_t size) noexcept = 0;
/**
* @brief informs DataConsumer that the writing is finished
* @return error code or OK
*/
- virtual result_t close() = 0;
+ virtual result_t close() noexcept = 0;
/**
* @brief checks whether DataSource is in error state
* @return true if error state
*/
- virtual bool isError() = 0;
+ virtual bool isError() noexcept = 0;
/**
* @brief get textual description of the last error
*
@@ -74,7 +74,7 @@ struct CDOC_EXPORT DataConsumer {
* @param src a vector
* @return vector size or error code
*/
- result_t write(const std::vector& src) {
+ result_t write(const std::vector& src) noexcept {
return write(src.data(), src.size());
}
/**
@@ -82,7 +82,7 @@ struct CDOC_EXPORT DataConsumer {
* @param src a string
* @return string length or error code
*/
- result_t write(const std::string& src) {
+ result_t write(const std::string& src) noexcept {
return write((const uint8_t *) src.data(), src.size());
}
/**
@@ -93,7 +93,7 @@ struct CDOC_EXPORT DataConsumer {
* @param src the input DataSource
* @return the number of bytes copied or error
*/
- result_t writeAll(DataSource& src);
+ result_t writeAll(DataSource& src) noexcept;
DataConsumer (const DataConsumer&) = delete;
DataConsumer& operator= (const DataConsumer&) = delete;
@@ -128,17 +128,17 @@ struct CDOC_EXPORT DataSource {
* @param size the number of bytes to read
* @return the number of bytes read or error code
*/
- virtual result_t read(uint8_t *dst, size_t size) { return NOT_IMPLEMENTED; }
+ virtual result_t read(uint8_t *dst, size_t size) noexcept { return NOT_IMPLEMENTED; }
/**
* @brief check whether DataConsumer is in error state
* @return true if error state
*/
- virtual bool isError() { return true; }
+ virtual bool isError() noexcept { return true; }
/**
* @brief check whether DataConsumer is reached to the end of data
* @return true if end of stream
*/
- virtual bool isEof() { return true; }
+ virtual bool isEof() noexcept { return true; }
/**
* @brief get textual description of the last error
*
@@ -169,7 +169,7 @@ struct CDOC_EXPORT DataSource {
* @param dst the destination DataConsumer
* @return error code or OK
*/
- result_t readAll(DataConsumer& dst) {
+ result_t readAll(DataConsumer& dst) noexcept {
return dst.writeAll(*this);
}
@@ -214,14 +214,14 @@ struct CDOC_EXPORT ChainedConsumer : public DataConsumer {
~ChainedConsumer() {
if (_owned) delete _dst;
}
- result_t write(const uint8_t *src, size_t size) override {
+ result_t write(const uint8_t *src, size_t size) noexcept override {
return _dst->write(src, size);
}
- result_t close() override {
+ result_t close() noexcept override {
if (_owned) return _dst->close();
return OK;
}
- bool isError() override {
+ bool isError() noexcept override {
return _dst->isError();
}
protected:
@@ -234,13 +234,13 @@ struct CDOC_EXPORT ChainedSource : public DataSource {
~ChainedSource() {
if (_owned) delete _src;
}
- result_t read(uint8_t *dst, size_t size) {
+ result_t read(uint8_t *dst, size_t size) noexcept override {
return _src->read(dst, size);
}
- bool isError() {
+ bool isError() noexcept override {
return _src->isError();
}
- bool isEof() {
+ bool isEof() noexcept override {
return _src->isEof();
}
protected:
@@ -264,13 +264,15 @@ struct CDOC_EXPORT IStreamSource : public DataSource {
return bool(_ifs->bad()) ? INPUT_STREAM_ERROR : OK;
}
- result_t read(uint8_t *dst, size_t size) {
+ result_t read(uint8_t *dst, size_t size) noexcept override try {
_ifs->read((char *) dst, size);
return (_ifs->bad()) ? INPUT_STREAM_ERROR : _ifs->gcount();
+ } catch(...) {
+ return INPUT_STREAM_ERROR;
}
- bool isError() { return _ifs->bad(); }
- bool isEof() { return _ifs->eof(); }
+ bool isError() noexcept override { return _ifs->bad(); }
+ bool isEof() noexcept override { return _ifs->eof(); }
protected:
std::istream *_ifs;
bool _owned;
@@ -285,17 +287,17 @@ struct CDOC_EXPORT OStreamConsumer : public DataConsumer {
if (_owned) delete _ofs;
}
- result_t write(const uint8_t *src, size_t size) {
+ result_t write(const uint8_t *src, size_t size) noexcept override {
_ofs->write((const char *) src, size);
return (_ofs->bad()) ? OUTPUT_STREAM_ERROR : size;
}
- result_t close() {
+ result_t close() noexcept override {
_ofs->flush();
return (_ofs->bad()) ? OUTPUT_STREAM_ERROR : OK;
}
- bool isError() { return _ofs->bad(); }
+ bool isError() noexcept override { return _ofs->bad(); }
protected:
std::ostream *_ofs;
bool _owned;
@@ -310,15 +312,15 @@ struct CDOC_EXPORT VectorSource : public DataSource {
return OK;
}
- result_t read(uint8_t *dst, size_t size) override {
+ result_t read(uint8_t *dst, size_t size) noexcept override {
size = std::min(size, _data.size() - _ptr);
std::copy_n(_data.cbegin() + _ptr, size, dst);
_ptr += size;
return size;
}
- bool isError() override { return false; }
- bool isEof() override { return _ptr >= _data.size(); }
+ bool isError() noexcept override { return false; }
+ bool isEof() noexcept override { return _ptr >= _data.size(); }
protected:
const std::vector& _data;
size_t _ptr;
@@ -326,12 +328,14 @@ struct CDOC_EXPORT VectorSource : public DataSource {
struct CDOC_EXPORT VectorConsumer : public DataConsumer {
VectorConsumer(std::vector& data) : _data(data) {}
- result_t write(const uint8_t *src, size_t size) override final {
+ result_t write(const uint8_t *src, size_t size) noexcept final try {
_data.insert(_data.end(), src, src + size);
return size;
+ } catch(...) {
+ return OUTPUT_STREAM_ERROR;
}
- result_t close() override final { return OK; }
- virtual bool isError() override final { return false; }
+ result_t close() noexcept final { return OK; }
+ virtual bool isError() noexcept final { return false; }
protected:
std::vector& _data;
};
@@ -340,15 +344,17 @@ struct CDOC_EXPORT FileListConsumer : public MultiDataConsumer {
FileListConsumer(const std::string& base_path) {
base = base_path;
}
- result_t write(const uint8_t *src, size_t size) override final {
+ result_t write(const uint8_t *src, size_t size) noexcept final try {
ofs.write((const char *) src, size);
return (ofs.bad()) ? OUTPUT_STREAM_ERROR : size;
+ } catch(...) {
+ return OUTPUT_STREAM_ERROR;
}
- result_t close() override final {
+ result_t close() noexcept final {
ofs.close();
return (ofs.bad()) ? OUTPUT_STREAM_ERROR : OK;
}
- bool isError() override final {
+ bool isError() noexcept final {
return ofs.bad();
}
result_t open(const std::string& name, int64_t size) override final {
@@ -378,11 +384,11 @@ struct CDOC_EXPORT FileListConsumer : public MultiDataConsumer {
struct CDOC_EXPORT FileListSource : public MultiDataSource {
FileListSource(const std::string& base, const std::vector& files);
- result_t read(uint8_t *dst, size_t size) override final;
- bool isError() override final;
- bool isEof() override final;
- result_t getNumComponents() override final;
- result_t next(std::string& name, int64_t& size) override final;
+ result_t read(uint8_t *dst, size_t size) noexcept final;
+ bool isError() noexcept final;
+ bool isEof() noexcept final;
+ result_t getNumComponents() final;
+ result_t next(std::string& name, int64_t& size) final;
protected:
std::filesystem::path _base;
const std::vector& _files;
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Info.plist b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Info.plist
index 831df5dd..f1db506c 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Info.plist
+++ b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/Info.plist
@@ -12,8 +12,6 @@
ee.ria.cdoc
CFBundleInfoDictionaryVersion
6.0
- CFBundleName
-
CFBundlePackageType
FMWK
CFBundleShortVersionString
@@ -21,7 +19,7 @@
CFBundleSignature
????
CFBundleVersion
- 552
+ 0
CSResourcesFileMapped
MinimumOSVersion
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/cdoc b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/cdoc
index ab05d002..f63e1714 100755
Binary files a/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/cdoc and b/Modules/CryptoLib/Sources/CryptoObjC/Libs/cdoc.xcframework/ios-arm64_x86_64-simulator/cdoc.framework/cdoc differ
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.h b/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.h
index a40cc483..b3711ad8 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.h
+++ b/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.h
@@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface Decrypt : NSObject
+ (nullable id)cdocInfo:(NSString *)fullPath error:(NSError **)error;
-+ (void)decryptFile:(NSString *)fullPath withToken:(id)smartToken
++ (void)decryptFile:(NSString *)fullPath withCert:(NSData *)certData withToken:(id)smartToken
completion:(void (^)(NSDictionary * _Nullable, NSError * _Nullable))completion;
+ (NSDictionary * _Nullable)decryptFile:(NSString *)fullPath withPassword:(NSString*)password error:(NSError**)error;
@end
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm b/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm
index 6ba5cd4c..97581b08 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm
+++ b/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm
@@ -102,46 +102,50 @@ + (CdocInfo*)cdocInfo:(NSString *)fullPath error:(NSError**)error {
return [[CdocInfo alloc] initWithAddressees:addressees];
}
-+ (void)decryptFile:(NSString *)fullPath withToken:(id)smartToken
++ (void)decryptFile:(NSString *)fullPath withCert:(NSData *)certData withToken:(id)smartToken
completion:(void (^)(NSDictionary *, NSError *))completion {
- [smartToken getCertificateWithCompletionHandler:^(NSData *certData, NSError *error) {
- auto cert = [certData toVector];
- if(cert.empty()) {
- return completion(nil, error);
- }
-
- struct TokenBackend: public SmartCardTokenWrapper, public Network
- {
- std::vector cert;
+ auto cert = [certData toVector];
+ if(cert.empty()) {
+ return completion(nil, [NSError cryptoError:@"Failed to get certData"]);
+ }
- TokenBackend(id smartToken, std::vector &&_cert)
- : SmartCardTokenWrapper(smartToken)
- , cert(std::move(_cert))
- {}
+ struct TokenBackend: public SmartCardTokenWrapper, public Network
+ {
+ std::vector cert;
- libcdoc::result_t getClientTLSCertificate(std::vector &dst) final {
- dst = cert;
- return dst.empty() ? libcdoc::IO_ERROR : libcdoc::OK;
- }
+ TokenBackend(id smartToken, std::vector &&_cert)
+ : SmartCardTokenWrapper(smartToken)
+ , cert(std::move(_cert))
+ {}
- libcdoc::result_t signTLS(std::vector &dst, libcdoc::CryptoBackend::HashAlgorithm algorithm, const std::vector &digest) final {
- return sign(dst, algorithm, digest, 0);
- }
- };
- TokenBackend token(smartToken, std::move(cert));
- Settings conf;
- std::unique_ptr reader(libcdoc::CDocReader::createReader(fullPath.UTF8String, &conf, &token, &token));
-
- auto idx = reader->getLockForCert(cert);
- if(idx < 0) {
- return completion(nil, [NSError cryptoError:@"Failed to find lock for cert"]);
+ libcdoc::result_t getClientTLSCertificate(std::vector &dst) final {
+ dst = cert;
+ return dst.empty() ? libcdoc::IO_ERROR : libcdoc::OK;
}
- std::vector fmk;
- if(reader->getFMK(fmk, unsigned(idx)) != 0 || fmk.empty()) {
- return completion(nil, token.lastError() ?: [NSError cryptoError:@"Failed to get FMK"]);
+
+ libcdoc::result_t signTLS(std::vector &dst, libcdoc::CryptoBackend::HashAlgorithm algorithm, const std::vector &digest) final {
+ return sign(dst, algorithm, digest, 0);
}
- completion([self decryptReader:*reader withFMK:fmk error:&error], error);
- }];
+ };
+ TokenBackend token(smartToken, std::move(cert));
+ Settings conf;
+ std::unique_ptr reader(libcdoc::CDocReader::createReader(fullPath.UTF8String, &conf, &token, &token));
+
+ if (!reader) {
+ return completion(nil, [NSError cryptoError:@"Failed to create CDocReader"]);
+ }
+
+ auto idx = reader->getLockForCert(token.cert);
+
+ if(idx < 0) {
+ return completion(nil, [NSError cryptoError:@"Failed to find lock for cert"]);
+ }
+ std::vector fmk;
+ if(reader->getFMK(fmk, unsigned(idx)) != 0 || fmk.empty()) {
+ return completion(nil, token.lastError() ?: [NSError cryptoError:@"Failed to get FMK"]);
+ }
+ NSError *error = nil;
+ completion([self decryptReader:*reader withFMK:fmk error:&error], error);
}
+ (NSDictionary *)decryptFile:(NSString *)fullPath withPassword:(NSString*)password error:(NSError**)error {
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.h b/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.h
index e7f149d6..89527464 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.h
+++ b/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.h
@@ -28,16 +28,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface Encrypt: NSObject
++ (void)enableLogging:(bool)enabled;
+ (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray *)dataFiles
withAddressees:(NSArray *)addressees completion:(void (^)(NSError * _Nullable))completion;
+ (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray *)dataFiles
withLabel:(NSString*)label withPassword:(NSString *)password completion:(void (^)(NSError * _Nullable))completion;
-//+ (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray *)dataFiles
-// withAddressees:(NSArray *)addressees completion:(void (^)(NSError * _Nullable))completion;
-//+ (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray *)dataFiles
-// withLabel:(NSString*)label withPassword:(NSString *)password completion:(void (^)(NSError * _Nullable))completion;
-
@end
NS_ASSUME_NONNULL_END
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.mm b/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.mm
index bf14439b..7849c483 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.mm
+++ b/Modules/CryptoLib/Sources/CryptoObjC/include/Encrypt.mm
@@ -25,9 +25,61 @@
#include
#include
-
+#include
@implementation Encrypt
+static inline NSString *NSStringFromStringView(std::string_view sv) {
+ return [[NSString alloc] initWithBytes:sv.data()
+ length:sv.size()
+ encoding:NSUTF8StringEncoding] ?: @"";
+}
+
+static inline NSString *NSStringFromLogLevel(libcdoc::ILogger::LogLevel level) {
+ switch (level) {
+ case libcdoc::ILogger::LEVEL_FATAL: return @"FATAL";
+ case libcdoc::ILogger::LEVEL_ERROR: return @"ERROR";
+ case libcdoc::ILogger::LEVEL_WARNING: return @"WARN";
+ case libcdoc::ILogger::LEVEL_INFO: return @"INFO";
+ case libcdoc::ILogger::LEVEL_DEBUG: return @"DEBUG";
+ case libcdoc::ILogger::LEVEL_TRACE: return @"TRACE";
+ }
+ return @"UNKNOWN";
+}
+
+class ObjCLogger final : public libcdoc::ILogger {
+public:
+ void LogMessage(libcdoc::ILogger::LogLevel level,
+ std::string_view file,
+ int line,
+ std::string_view message) override
+ {
+ NSString *nsFile = NSStringFromStringView(file);
+ NSString *nsMsg = NSStringFromStringView(message);
+ NSString *nsLvl = NSStringFromLogLevel(level);
+
+ NSLog(@"CryptoContainer: %@:%d %@ %@",
+ nsFile.length ? nsFile : @"",
+ line,
+ nsLvl,
+ nsMsg);
+ }
+};
+
++ (void)enableLogging:(bool)enabled {
+ static ObjCLogger gLogger;
+
+ if (!enabled) {
+ return;
+ }
+
+ // Install only once, even if enableLogging:YES is called many times
+ static std::once_flag once;
+ std::call_once(once, [] {
+ libcdoc::ILogger::setLogger(&gLogger);
+ gLogger.SetMinLogLevel(libcdoc::ILogger::LEVEL_TRACE);
+ });
+}
+
+ (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray *)dataFiles withAddressees:(NSArray *)addressees
completion:(void (^)(NSError*))completion {
int version = [fullPath.pathExtension caseInsensitiveCompare:@"cdoc2"] == NSOrderedSame ? 2 : 1;
@@ -138,3 +190,5 @@ + (void)encryptFile:(NSString *)fullPath withDataFiles:(NSArray
@end
+
+
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.h b/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.h
index bf63f59b..cc0d1bfb 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.h
+++ b/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.h
@@ -20,8 +20,6 @@
#if __cplusplus
-//@import CryptoObjCWrapper;
-
#import
#import
#include
diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.mm b/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.mm
index 75aa044c..39b434f0 100644
--- a/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.mm
+++ b/Modules/CryptoLib/Sources/CryptoObjC/include/SmartCardTokenWrapper.mm
@@ -39,34 +39,33 @@
return token->error;
}
-libcdoc::result_t SmartCardTokenWrapper::deriveECDH1(std::vector& dst, const std::vector &public_key, unsigned int idx)
+libcdoc::result_t SmartCardTokenWrapper::deriveECDH1(
+ std::vector& dst,
+ const std::vector& public_key,
+ unsigned int idx)
{
__block NSData *resultData = nil;
__block NSError *blockError = nil;
+ __block BOOL finished = NO;
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSData *pub = [NSData dataFromVectorNoCopy:public_key];
- auto invoke = ^{
- [token->smartTokenClass derive:pub
- completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
- resultData = data;
- blockError = error;
- dispatch_semaphore_signal(sema);
- }];
- };
-
- if ([NSThread isMainThread]) {
- /// Avoid deadlock: perform async then wait.
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), invoke);
- } else {
- /// Already off main thread; call directly.
- invoke();
+ [token->smartTokenClass derive:pub
+ completionHandler:^(NSData * _Nullable data,
+ NSError * _Nullable error)
+ {
+ resultData = data;
+ blockError = error;
+ finished = YES;
+ }];
+
+ // Pump the run loop until completion fires
+ // FIXME: Review and fix NFC lib, to use normal completion sync like semaphore.
+ while (!finished) {
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]];
}
- /// Wait for completion (consider a timeout if appropriate)
- dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
-
if (resultData) {
dst = [resultData toVector];
} else {
@@ -81,30 +80,26 @@
{
__block NSData *resultData = nil;
__block NSError *blockError = nil;
+ __block BOOL finished = NO;
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSData *pub = [NSData dataFromVectorNoCopy:data];
- auto invoke = ^{
- [token->smartTokenClass decrypt:pub
- completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
- resultData = data;
- blockError = error;
- dispatch_semaphore_signal(sema);
- }];
- };
-
- if ([NSThread isMainThread]) {
- /// Avoid deadlock: perform async then wait.
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), invoke);
- } else {
- /// Already off main thread; call directly.
- invoke();
+ [token->smartTokenClass decrypt:pub
+ completionHandler:^(NSData * _Nullable data,
+ NSError * _Nullable error)
+ {
+ resultData = data;
+ blockError = error;
+ finished = YES;
+ }];
+
+ // Pump the run loop until completion fires
+ // FIXME: Review and fix NFC lib, to use normal completion sync like semaphore.
+ while (!finished) {
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]];
}
- /// Wait for completion (consider a timeout if appropriate)
- dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
-
if (resultData) {
dst = [resultData toVector];
} else {
@@ -119,30 +114,26 @@
{
__block NSData *resultData = nil;
__block NSError *blockError = nil;
+ __block BOOL finished = NO;
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSData *pub = [NSData dataFromVectorNoCopy:digest];
- auto invoke = ^{
- [token->smartTokenClass authenticate:pub
- completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
- resultData = data;
- blockError = error;
- dispatch_semaphore_signal(sema);
- }];
- };
-
- if ([NSThread isMainThread]) {
- /// Avoid deadlock: perform async then wait.
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), invoke);
- } else {
- /// Already off main thread; call directly.
- invoke();
+ [token->smartTokenClass authenticate:pub
+ completionHandler:^(NSData * _Nullable data,
+ NSError * _Nullable error)
+ {
+ resultData = data;
+ blockError = error;
+ finished = YES;
+ }];
+
+ // Pump the run loop until completion fires
+ // FIXME: Review and fix NFC lib, to use normal completion sync like semaphore.
+ while (!finished) {
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]];
}
- /// Wait for completion (consider a timeout if appropriate)
- dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
-
if (resultData) {
dst = [resultData toVector];
} else {
diff --git a/Modules/CryptoLib/Sources/CryptoSwift/Card/SmartToken.swift b/Modules/CryptoLib/Sources/CryptoSwift/Card/SmartToken.swift
index c0380ea8..09e31334 100644
--- a/Modules/CryptoLib/Sources/CryptoSwift/Card/SmartToken.swift
+++ b/Modules/CryptoLib/Sources/CryptoSwift/Card/SmartToken.swift
@@ -21,7 +21,6 @@ import Foundation
import IdCardLib
import CryptoObjCWrapper
-@MainActor
public class SmartToken: AbstractSmartToken {
let pin1: SecureData
let card: CardCommands
diff --git a/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift b/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift
index 09a0b3fe..10da4bb4 100644
--- a/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift
+++ b/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift
@@ -190,6 +190,11 @@ public actor CryptoContainer: CryptoContainerProtocol, Loggable {
extension CryptoContainer {
+ @MainActor
+ public static func enableLogging(_ bool: Bool) {
+ Encrypt.enableLogging(bool)
+ }
+
@MainActor
public static func openOrCreate(
dataFiles: [URL],
@@ -258,29 +263,37 @@ extension CryptoContainer {
public static func decrypt(
containerFile: URL,
recipients: [Addressee],
+ cert: Data,
cardCommands: CardCommands,
pin: SecureData,
fileManager: FileManagerProtocol = Container.shared.fileManager()
) async throws -> CryptoContainerProtocol {
+
let decryptedData =
- try await Decrypt.decryptFile(
- containerFile.absoluteString,
- withToken: SmartToken(card: cardCommands, pin1: pin)
- )
+ try await Decrypt.decryptFile(
+ containerFile.resolvedPath,
+ withCert: cert,
+ withToken: SmartToken(card: cardCommands, pin1: pin)
+ )
var cryptoDataFiles: [CryptoDataFile] = []
var urlDataFiles: [URL] = []
cryptoDataFiles.removeAll()
for dataFile in decryptedData {
- let destinationPath = try Directories
- .getTempDirectory(subfolder: Constants.Folder.Temp, fileManager: fileManager)
- let fileUrl = destinationPath.appending(path: dataFile.key)
+ let sanitizedName = dataFile.key.sanitized()
+
+ let destinationPath = try Directories.getCacheDirectory(
+ subfolder: Constants.Folder.SignedContainerFolder,
+ fileManager: fileManager
+ ).appending(path: Constants.Folder.Temp, directoryHint: .isDirectory)
+
+ let fileUrl = destinationPath.appending(path: sanitizedName)
cryptoDataFiles.append(CryptoDataFile(filename: dataFile.key, filePath: destinationPath.resolvedPath))
urlDataFiles.append(fileUrl)
let isCreated =
fileManager.createFile(
- atPath: destinationPath.resolvedPath, contents: dataFile.value, attributes: nil
+ atPath: fileUrl.resolvedPath, contents: dataFile.value, attributes: nil
)
if !isCreated {
diff --git a/Modules/IdCardLib/Package.resolved b/Modules/IdCardLib/Package.resolved
index 466e751c..a2a16060 100644
--- a/Modules/IdCardLib/Package.resolved
+++ b/Modules/IdCardLib/Package.resolved
@@ -1,6 +1,15 @@
{
- "originHash" : "b3dd2ed7f8b9bcdacf4ed33375a0673f55c06f9a4bcdd8efcd736bdd4faca5ee",
+ "originHash" : "765492bae74340e47676462cc53b3d303fab61c8ed5e239135e123527ba61139",
"pins" : [
+ {
+ "identity" : "alamofire",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Alamofire/Alamofire.git",
+ "state" : {
+ "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5",
+ "version" : "5.10.2"
+ }
+ },
{
"identity" : "asn1",
"kind" : "remoteSourceControl",
@@ -28,6 +37,15 @@
"version" : "1.13.0"
}
},
+ {
+ "identity" : "factory",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/hmlongco/Factory",
+ "state" : {
+ "revision" : "ccc898f21992ebc130bc04cc197460a5ae230bcf",
+ "version" : "2.5.3"
+ }
+ },
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
@@ -63,6 +81,15 @@
"revision" : "85c529c9c56b649a8d8edcef60aab3925ab0f69f",
"version" : "5.5.0"
}
+ },
+ {
+ "identity" : "zipfoundation",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/weichsel/ZIPFoundation",
+ "state" : {
+ "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0",
+ "version" : "0.9.19"
+ }
}
],
"version" : 3
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommands.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommands.swift
index 1dc0e83f..08662e9a 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommands.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommands.swift
@@ -59,7 +59,7 @@ public enum CodeType: UInt, Sendable {
/**
* A protocol defining commands for interacting with a smart card.
*/
-@MainActor
+
public protocol CardCommands: Sendable {
var canChangePUK: Bool { get }
/**
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommandsInternal.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommandsInternal.swift
index b9c044ef..8f9fc1b7 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommandsInternal.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommandsInternal.swift
@@ -18,7 +18,7 @@
*/
import CryptoTokenKit
-@MainActor
+
protocol CardCommandsInternal: CardCommands {
/**
* The smart card reader used to communicate with the card.
@@ -89,7 +89,7 @@ extension CardCommandsInternal {
}
}
- private func pinTemplate(_ pin: SecureData?, fillChar: UInt8 = 0xFF) -> Data {
+ private func pinTemplate(_ pin: SecureData?) -> Data {
guard let pin = pin else { return Data() }
var out = Data()
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReader.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReader.swift
index 280760be..d6f4d299 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReader.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReader.swift
@@ -20,7 +20,7 @@
import Foundation
typealias Bytes = [UInt8]
-@MainActor
+
protocol CardReader: Sendable {
/**
* Sends an APDU (Application Protocol Data Unit) command to the smart card and retrieves the response.
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderNFC.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderNFC.swift
index 5348a0f3..0404f3a9 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderNFC.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderNFC.swift
@@ -24,7 +24,7 @@ import CryptoTokenKit
internal import SwiftECC
import BigInt
-class CardReaderNFC: CardReader, Loggable {
+class CardReaderNFC: @unchecked CardReader, Loggable {
// swiftlint:disable identifier_name
enum PasswordType: UInt8 {
case id_PasswordType_MRZ = 1 // 0.4.0.127.0.7.2.2.12.1
@@ -64,18 +64,18 @@ class CardReaderNFC: CardReader, Loggable {
}
typealias TLV = TKBERTLVRecord
- let tag: NFCISO7816Tag
+ let tag: SendableISO7816Tag
var ksEnc: Bytes
var ksMac: Bytes
var SSC: Bytes = AES.Zero
init(_ tag: NFCISO7816Tag, CAN: String) async throws {
- self.tag = tag
+ self.tag = SendableISO7816Tag(tag: tag)
CardReaderNFC.logger().debug("Select CardAccess")
- _ = try await tag.sendCommand(cls: 0x00, ins: 0xA4, p1Byte: 0x02, p2Byte: 0x0C, data: Data([0x01, 0x1C]))
+ _ = try await self.tag.sendCommand(cls: 0x00, ins: 0xA4, p1Byte: 0x02, p2Byte: 0x0C, data: Data([0x01, 0x1C]))
CardReaderNFC.logger().debug("Read CardAccess")
- let data = try await tag.sendCommand(cls: 0x00, ins: 0xB0, p1Byte: 0x00, p2Byte: 0x00, leByte: 256)
+ let data = try await self.tag.sendCommand(cls: 0x00, ins: 0xB0, p1Byte: 0x00, p2Byte: 0x00, leByte: 256)
guard let (mappingType, parameterId) = TLV.sequenceOfRecords(from: data)?
.flatMap({ cardAccess in TLV.sequenceOfRecords(from: cardAccess.value) ?? [] })
@@ -94,21 +94,21 @@ class CardReaderNFC: CardReader, Loggable {
}
let domain = parameterId.domain
- _ = try await tag.sendCommand(cls: 0x00, ins: 0x22, p1Byte: 0xc1, p2Byte: 0xa4, records: [
+ _ = try await self.tag.sendCommand(cls: 0x00, ins: 0x22, p1Byte: 0xc1, p2Byte: 0xa4, records: [
TLV(tag: 0x80, value: mappingType.data),
TLV(tag: 0x83, bytes: [PasswordType.id_PasswordType_CAN.rawValue]),
TLV(tag: 0x84, bytes: [parameterId.rawValue])
])
// Step1 - General Authentication
- let nonceEnc = try await tag.sendPaceCommand(records: [], tagExpected: 0x80)
+ let nonceEnc = try await self.tag.sendPaceCommand(records: [], tagExpected: 0x80)
CardReaderNFC.logger().debug("Challenge \(nonceEnc.value.toHex)")
let nonce = try CardReaderNFC.decryptNonce(CAN: CAN, encryptedNonce: nonceEnc.value)
CardReaderNFC.logger().debug("Nonce \(nonce.toHex)")
// Step2
let (terminalPubKey, terminalPrivKey) = domain.makeKeyPair()
- let mappingKey = try await tag.sendPaceCommand(
+ let mappingKey = try await self.tag.sendPaceCommand(
records: [try TLV(
tag: 0x81,
publicKey: terminalPubKey
@@ -146,7 +146,7 @@ class CardReaderNFC: CardReader, Loggable {
cofactor: domain.cofactor
)
let (terminalEphemeralPubKey, terminalEphemeralPrivKey) = mappedDomain.makeKeyPair()
- let ephemeralKey = try await tag.sendPaceCommand(
+ let ephemeralKey = try await self.tag.sendPaceCommand(
records: [try TLV(
tag: 0x83,
publicKey: terminalEphemeralPubKey
@@ -172,7 +172,7 @@ class CardReaderNFC: CardReader, Loggable {
TLV(tag: 0x06, value: mappingType.data),
TLV(tag: 0x86, bytes: try ephemeralCardPubKey.x963Representation())
])
- let macValue = try await tag.sendPaceCommand(
+ let macValue = try await self.tag.sendPaceCommand(
records: [TLV(
tag: 0x85,
bytes: (
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderiR301.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderiR301.swift
index c76e53ba..4dfeae35 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderiR301.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderiR301.swift
@@ -20,7 +20,7 @@
import iR301
import UtilsLib
-class CardReaderiR301: CardReader, Sendable, Loggable {
+class CardReaderiR301: CardReader, @unchecked Sendable, Loggable {
let atr: Bytes
private var cardHandle: SCARDHANDLE = 0
private var pioSendPci = SCARD_IO_REQUEST(dwProtocol: UInt32(SCARD_PROTOCOL_UNDEFINED),
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/IDCardError.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/IDCardError.swift
index eb107116..3e5179e6 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/IDCardError.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/IDCardError.swift
@@ -21,7 +21,8 @@ public enum IdCardError: Error {
case wrongCAN,
wrongPIN(triesLeft: Int),
invalidNewPIN,
- sessionError
+ sessionError,
+ cancelledByUser
}
public enum IdCardInternalError: Error {
@@ -75,7 +76,6 @@ public enum IdCardInternalError: Error {
.connectionFailed,
.multipleTagsDetected,
.couldNotVerifyChipsMAC,
- .cancelledByUser,
.sessionInvalidated,
.readerProcessFailed,
.failedToRemovePadding,
@@ -89,6 +89,8 @@ public enum IdCardInternalError: Error {
return .wrongPIN(triesLeft: value)
case .invalidNewPin:
return .invalidNewPIN
+ case .cancelledByUser:
+ return .cancelledByUser
}
}
}
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/Idemia.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/Idemia.swift
index 136a869c..dcd25174 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/Idemia.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/Idemia.swift
@@ -44,7 +44,7 @@ extension CodeType {
}
}
-class Idemia: CardCommandsInternal {
+final class Idemia: CardCommandsInternal {
let canChangePUK: Bool = true
let reader: CardReader
let fillChar: UInt8 = 0xFF
diff --git a/Modules/IdCardLib/Sources/IdCardLib/CardActions/Thales.swift b/Modules/IdCardLib/Sources/IdCardLib/CardActions/Thales.swift
index 74e5cbbf..2341b2af 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/CardActions/Thales.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/CardActions/Thales.swift
@@ -29,7 +29,7 @@ extension CodeType {
}
}
-class Thales: CardCommandsInternal {
+final class Thales: CardCommandsInternal {
static private let ATR = Bytes(hex: "3B FF 96 00 00 80 31 FE 43 80 31 B8 53 65 49 44 64 B0 85 05 10 12 23 3F 1D")
static private let kAID = Bytes(hex: "A0 00 00 00 63 50 4B 43 53 2D 31 35")
static private let kAIDGlobal = Bytes(hex: "A0 00 00 00 18 10 02 03 00 00 00 00 00 00 00 01")
diff --git a/Modules/IdCardLib/Sources/IdCardLib/Extensions/NFCISO7816Tag+Extensions.swift b/Modules/IdCardLib/Sources/IdCardLib/Extensions/NFCISO7816Tag+Extensions.swift
index 3e011fb9..1b026dca 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/Extensions/NFCISO7816Tag+Extensions.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/Extensions/NFCISO7816Tag+Extensions.swift
@@ -18,12 +18,16 @@
*/
import UtilsLib
-import CoreNFC
+@preconcurrency import CoreNFC
import CryptoTokenKit
+struct SendableISO7816Tag: Sendable {
+ var tag: NFCISO7816Tag
+}
+
private struct NFCISO7816TagLogger: Loggable {}
-extension NFCISO7816Tag {
+extension SendableISO7816Tag {
func sendCommand(
cls: UInt8,
ins: UInt8,
@@ -40,7 +44,7 @@ extension NFCISO7816Tag {
data: data,
expectedResponseLength: leByte
)
- let result = try await _sendCommandNonisolated(apdu: apdu)
+ let result = try await tag.sendCommand(apdu: apdu)
switch result {
case (_, 0x63, 0x00):
throw IdCardInternalError.canAuthenticationFailed
@@ -101,16 +105,4 @@ extension NFCISO7816Tag {
}
}
}
-
- private func _sendCommandNonisolated(apdu: NFCISO7816APDU) async throws -> (Data, UInt8, UInt8) {
- try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<(Data, UInt8, UInt8), Error>) in
- self.sendCommand(apdu: apdu) { data, sw1, sw2, error in
- if let error = error {
- continuation.resume(throwing: error)
- } else {
- continuation.resume(returning: (data, sw1, sw2))
- }
- }
- }
- }
}
diff --git a/Modules/IdCardLib/Sources/IdCardLib/Operations/NFCConnection.swift b/Modules/IdCardLib/Sources/IdCardLib/Operations/NFCConnection.swift
index 042e6bd1..7a9a19f9 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/Operations/NFCConnection.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/Operations/NFCConnection.swift
@@ -24,7 +24,8 @@ import CryptoTokenKit
@MainActor
public class NFCConnection {
- func setup(_ session: NFCTagReaderSession, tags: [NFCTag]) async throws -> NFCISO7816Tag {
+ public init() {}
+ public func setup(_ session: NFCTagReaderSession, tags: [NFCTag]) async throws -> NFCISO7816Tag {
if tags.count > 1 {
session.invalidate(errorMessage: "Failed to read data")
throw IdCardInternalError.multipleTagsDetected
@@ -51,7 +52,7 @@ public class NFCConnection {
}
@MainActor
- func getCardCommands(_: NFCTagReaderSession, tag: NFCISO7816Tag, CAN: String) async throws -> CardCommands {
+ public func getCardCommands(_: NFCTagReaderSession, tag: NFCISO7816Tag, CAN: String) async throws -> CardCommands {
let initialSelectedAID = tag.initialSelectedAID
let reader = try await CardReaderNFC(tag, CAN: CAN)
guard let aid = Bytes(hex: initialSelectedAID) else {
diff --git a/Modules/IdCardLib/Sources/IdCardLib/Utilities/ProgressBar.swift b/Modules/IdCardLib/Sources/IdCardLib/Utilities/ProgressBar.swift
index 9ad9ee37..145b96d5 100644
--- a/Modules/IdCardLib/Sources/IdCardLib/Utilities/ProgressBar.swift
+++ b/Modules/IdCardLib/Sources/IdCardLib/Utilities/ProgressBar.swift
@@ -17,16 +17,16 @@
*
*/
-struct ProgressBar {
+public struct ProgressBar {
private let totalSteps: Int
private let currentStep: Int
- init(currentStep: Int, totalSteps: Int = 4) {
+ public init(currentStep: Int, totalSteps: Int = 4) {
self.currentStep = currentStep
self.totalSteps = totalSteps
}
- func generate() -> String {
+ public func generate() -> String {
if currentStep > 0 {
return (0.. SecCertificate {
+public func convertBytesToX509Certificate(_ data: Data) throws -> SecCertificate {
guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else {
throw CertificateConversionError.creationFailed
}
@@ -35,6 +35,6 @@ func convertBytesToX509Certificate(_ data: Data) throws -> SecCertificate {
return certificate
}
-enum CertificateConversionError: Error {
+public enum CertificateConversionError: Error {
case creationFailed
}
diff --git a/RIADigiDoc.xcodeproj/project.pbxproj b/RIADigiDoc.xcodeproj/project.pbxproj
index 02e7fff7..2356d133 100644
--- a/RIADigiDoc.xcodeproj/project.pbxproj
+++ b/RIADigiDoc.xcodeproj/project.pbxproj
@@ -163,6 +163,8 @@
Domain/Model/SupportedLanguage.swift,
Domain/Model/SupportedTheme.swift,
Domain/Model/ToastMessage.swift,
+ Domain/NFC/NFCSessionStrings.swift,
+ Domain/NFC/OperationDecrypt.swift,
Domain/Preferences/DataStore.swift,
Domain/Preferences/DataStoreProtocol.swift,
Domain/Preferences/KeychainStore.swift,
diff --git a/RIADigiDoc/CryptoSetup.swift b/RIADigiDoc/CryptoSetup.swift
index 0307166c..8ace17a6 100644
--- a/RIADigiDoc/CryptoSetup.swift
+++ b/RIADigiDoc/CryptoSetup.swift
@@ -20,6 +20,7 @@
import Foundation
import CryptoObjCWrapper
import CryptoSwift
+import CommonsLib
import ConfigLib
actor CryptoSetup: CryptoSetupProtocol {
@@ -97,12 +98,21 @@ actor CryptoSetup: CryptoSetupProtocol {
}
public func setCdoc2Settings(_ configurationProvider: ConfigurationProvider?) async {
+ var defaultUseCdoc2Encryption = Constants.CryptoDefaultValues.encryptionUseCdoc2
if let useCdoc2Encryption = configurationProvider?.cdoc2Default {
- await CDoc2Setting.setEncryptionEnabled(await dataStore.getUseCdoc2Encryption(useCdoc2Encryption))
+ defaultUseCdoc2Encryption = useCdoc2Encryption
}
+ await CDoc2Setting.setEncryptionEnabled(
+ await dataStore.getUseCdoc2Encryption(defaultUseCdoc2Encryption)
+ )
+
+ var defaultUseCdoc2Online = Constants.CryptoDefaultValues.encryptionUseKeyTransfer
if let useCdoc2Online = configurationProvider?.cdoc2UseKeyserver {
- await CDoc2Setting.setOnlineEncryptionEnabled(await dataStore.getEncryptionUseKeyTransfer(useCdoc2Online))
+ defaultUseCdoc2Online = useCdoc2Online
}
+ await CDoc2Setting.setOnlineEncryptionEnabled(
+ await dataStore.getEncryptionUseKeyTransfer(defaultUseCdoc2Online)
+ )
if let cdoc2UUID = configurationProvider?.cdoc2DefaultKeyserver {
let serverInfo = await dataStore.getEncryptionServerInfo(cdoc2UUID)
diff --git a/RIADigiDoc/Domain/NFC/NFCSessionStrings.swift b/RIADigiDoc/Domain/NFC/NFCSessionStrings.swift
new file mode 100644
index 00000000..7b22c9a6
--- /dev/null
+++ b/RIADigiDoc/Domain/NFC/NFCSessionStrings.swift
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 - 2025 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
+ *
+ */
+
+public struct NFCSessionStrings {
+ let initialMessage: String
+ let step1Message: String
+ let step2Message: String
+ let step3Message: String
+ let step4Message: String
+ let successMessage: String
+ let canErrorMessage: String
+ let pin1WrongMultipleErrorMessage: String
+ let pin1WrongErrorMessage: String
+ let pin1BlockedErrorMessage: String
+ let technicalErrorMessage: String
+ let sessionErrorMessage: String
+}
diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift
new file mode 100644
index 00000000..ee5f5e6b
--- /dev/null
+++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2017 - 2025 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 CoreNFC
+import CommonCrypto
+import CryptoTokenKit
+internal import SwiftECC
+import BigInt
+import CryptoKit
+import IdCardLib
+import CryptoObjCWrapper
+import CryptoSwift
+
+@MainActor
+public class OperationDecrypt: NSObject {
+
+ private var session: NFCTagReaderSession?
+ private var isFinished = false
+ private var containerFile: URL = URL(fileURLWithPath: "")
+ private var recipients: [Addressee] = []
+ private var CAN: String = ""
+ private var PIN: SecureData = SecureData([0x00])
+ private var continuation: CheckedContinuation?
+ private var connection = NFCConnection()
+
+ private var nfcError: String? = ""
+ private var strings: NFCSessionStrings?
+
+ public func processDecrypt(
+ CAN: String,
+ PIN1: SecureData,
+ containerFile: URL,
+ recipients: [Addressee],
+ strings: NFCSessionStrings,
+ ) async throws -> CryptoContainerProtocol {
+
+ return try await withCheckedThrowingContinuation { continuation in
+ self.continuation = continuation
+
+ guard NFCTagReaderSession.readingAvailable else {
+ continuation.resume(throwing: IdCardInternalError.nfcNotSupported)
+ return
+ }
+ self.CAN = CAN
+ self.PIN = PIN1
+ self.containerFile = containerFile
+ self.recipients = recipients
+ self.strings = strings
+
+ session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self)
+ updateAlertMessage(step: 0)
+ session?.begin()
+ }
+ }
+
+ private func updateAlertMessage(step: Int) {
+ let stepMessages = [
+ strings?.initialMessage ?? "",
+ strings?.step1Message ?? "",
+ strings?.step2Message ?? "",
+ strings?.step3Message ?? "",
+ strings?.step4Message ?? ""
+ ]
+
+ let stepMessage = stepMessages[min(step, stepMessages.count - 1)]
+ let progressBar = ProgressBar(currentStep: step)
+ var message = stepMessage
+ message += "\n\n\(progressBar.generate())"
+ session?.alertMessage = message
+ }
+
+ private func success() {
+ session?.alertMessage = strings?.successMessage ?? ""
+ session?.invalidate()
+ }
+
+ private func failure(_ idCardError: IdCardError) {
+ handleIdCardError(idCardError)
+ session?.invalidate(errorMessage: nfcError ?? "")
+ }
+
+ private func handleIdCardError(_ error: IdCardError) {
+ switch error {
+ case .wrongCAN:
+ nfcError = strings?.canErrorMessage ?? ""
+ case .wrongPIN(let triesLeft):
+ if triesLeft > 1 {
+ nfcError = strings?.pin1WrongMultipleErrorMessage ?? ""
+ } else if triesLeft == 1 {
+ nfcError = strings?.pin1WrongErrorMessage ?? ""
+ } else {
+ nfcError = strings?.pin1BlockedErrorMessage ?? ""
+ }
+ case .sessionError:
+ nfcError = strings?.sessionErrorMessage ?? ""
+ default:
+ nfcError = strings?.technicalErrorMessage ?? ""
+ }
+ }
+}
+
+extension OperationDecrypt: @MainActor NFCTagReaderSessionDelegate {
+ public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
+
+ Task { @MainActor in
+ do {
+ updateAlertMessage(step: 1)
+ let tag = try await connection.setup(session, tags: tags)
+ updateAlertMessage(step: 2)
+ let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: CAN)
+ updateAlertMessage(step: 3)
+ let cert = try await cardCommands.readAuthenticationCertificate()
+ updateAlertMessage(step: 4)
+ let decryptedContainer = try await CryptoContainer.decrypt(
+ containerFile: containerFile,
+ recipients: recipients,
+ cert: cert,
+ cardCommands: cardCommands,
+ pin: PIN,
+ )
+ continuation?.resume(with: .success(decryptedContainer))
+ success()
+ } catch {
+ guard !isFinished else { return }
+ isFinished = true
+ guard let exception = error as? IdCardInternalError else {
+ session.invalidate(errorMessage: strings?.sessionErrorMessage ?? "")
+ continuation?.resume(throwing: error)
+ return
+ }
+
+ let idCardError = exception.getIdCardError()
+ failure(idCardError)
+ continuation?.resume(throwing: error)
+ }
+ }
+ }
+
+ public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) { }
+
+ public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) {
+ self.session = nil
+ guard !isFinished else { return }
+ isFinished = true
+ if let nfcError = error as? NFCReaderError {
+ switch nfcError.code {
+ case .readerSessionInvalidationErrorUserCanceled:
+ continuation?.resume(throwing: IdCardInternalError.cancelledByUser)
+ return
+
+ default:
+ break
+ }
+ }
+ continuation?.resume(throwing: error)
+ }
+}
diff --git a/RIADigiDoc/Info.plist b/RIADigiDoc/Info.plist
index 8028e1ab..b1a8a206 100644
--- a/RIADigiDoc/Info.plist
+++ b/RIADigiDoc/Info.plist
@@ -223,6 +223,12 @@
LSRequiresIPhoneOS
+ NFCReaderUsageDescription
+ This app uses NFC to scan ID-cards
+ NSBluetoothAlwaysUsageDescription
+ Bluetooth card reader is used to read data from ID card
+ NSBluetoothPeripheralUsageDescription
+ Bluetooth card reader is used to read data from ID card
UIAppFonts
Roboto_Condensed-Bold.ttf
@@ -971,5 +977,10 @@
com.ftsafe.bR301
com.ftsafe.iR301
+ com.apple.developer.nfc.readersession.iso7816.select-identifiers
+
+ A000000077010800070000FE00000100
+ A0000000181002030000000000000001
+
diff --git a/RIADigiDoc/LibrarySetup.swift b/RIADigiDoc/LibrarySetup.swift
index c698540a..ce54c52a 100644
--- a/RIADigiDoc/LibrarySetup.swift
+++ b/RIADigiDoc/LibrarySetup.swift
@@ -111,6 +111,8 @@ actor LibrarySetup: Loggable {
)
LibrarySetup.logger().info("Libdigidocpp initialized successfully")
+ await CryptoContainer.enableLogging(isLoggingEnabled)
+
let configurationProvider = await configurationRepository.getConfiguration()
await cryptoSetup.setLdapConfig(configurationProvider)
diff --git a/RIADigiDoc/Supporting files/Localizable.xcstrings b/RIADigiDoc/Supporting files/Localizable.xcstrings
index 1131e27b..b94ad229 100644
--- a/RIADigiDoc/Supporting files/Localizable.xcstrings
+++ b/RIADigiDoc/Supporting files/Localizable.xcstrings
@@ -1,6 +1,9 @@
{
"sourceLanguage" : "en",
"strings" : {
+ "" : {
+
+ },
"Add file" : {
"comment" : "Home screen bottom sheet action",
"extractionState" : "manual",
@@ -1110,6 +1113,24 @@
}
}
},
+ "Decrypting in progress please wait" : {
+ "comment" : "NFC decrypt alert message step 4",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Decrypting in progress, please wait."
+ }
+ },
+ "et" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dekrüpteerimine käib, palun oodake."
+ }
+ }
+ }
+ },
"Decryption method" : {
"comment" : "Title for signing method in container decrypt view",
"extractionState" : "manual",
@@ -4710,6 +4731,42 @@
}
}
},
+ "NFC session error" : {
+ "comment" : "NFC connection lost error message",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "NFC connection lost"
+ }
+ },
+ "et" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kontaktivaba ühendust ei saa luua"
+ }
+ }
+ }
+ },
+ "NFC technical error" : {
+ "comment" : "NFC technical error message",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Technical error. Please try again"
+ }
+ },
+ "et" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Midagi läks valesti. Palun proovi uuesti"
+ }
+ }
+ }
+ },
"No Internet connection" : {
"comment" : "Error shown whne app is unable to conenct to the Internet to do requests",
"extractionState" : "manual",
@@ -5555,6 +5612,24 @@
}
}
},
+ "Reading certificate" : {
+ "comment" : "NFC alert message, step 3",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reading certificate."
+ }
+ },
+ "et" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sertifikaadi lugemine."
+ }
+ }
+ }
+ },
"Reading data please wait" : {
"comment" : "NFC alert message, step 3",
"extractionState" : "manual",
@@ -7488,6 +7563,24 @@
}
}
},
+ "Wrong CAN" : {
+ "comment" : "NFC decrypt wrong CAN message",
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Wrong CAN number"
+ }
+ },
+ "et" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vale CAN number"
+ }
+ }
+ }
+ },
"Xades message" : {
"comment" : "Container notifications view",
"extractionState" : "manual",
diff --git a/RIADigiDoc/UI/Component/Container/Crypto/DecryptRootView.swift b/RIADigiDoc/UI/Component/Container/Crypto/DecryptRootView.swift
index 30357216..1e9c9901 100644
--- a/RIADigiDoc/UI/Component/Container/Crypto/DecryptRootView.swift
+++ b/RIADigiDoc/UI/Component/Container/Crypto/DecryptRootView.swift
@@ -20,11 +20,12 @@
import SwiftUI
import FactoryKit
import CryptoSwift
+import IdCardLib
import CommonsLib
struct DecryptRootView: View {
@Environment(\.dismiss) private var dismiss
-
+ @Environment(LanguageSettings.self) private var languageSettings
@Environment(NavigationPathManager.self) private var pathManager
@State private var chosenMethod: ActionMethod = .idCardViaNFC
@@ -52,10 +53,15 @@ struct DecryptRootView: View {
.idCardViaNFC,
.idCardViaUSB
],
+ pinType: CodeType.pin1,
cryptoContainer: container,
onSuccessDecrypt: { container in
sharedContainerViewModel.removeLastContainer()
sharedContainerViewModel.setCryptoContainer(container)
+
+ Toast.show(languageSettings.localized(
+ "Container successfully decrypted"
+ ))
}
)
}
diff --git a/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift b/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift
index 6389b593..173cdd2b 100644
--- a/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift
+++ b/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift
@@ -178,7 +178,7 @@ struct EncryptView: View {
onLeftClick: {
Task {
if await viewModel.handleBackButton() {
- pathManager.popToRoot()
+ dismiss()
}
}
},
@@ -355,7 +355,7 @@ struct EncryptView: View {
rightButtonAccessibilityLabel: rightButtonLabel.lowercased(),
rightButtonAction: {
if viewModel.isContainerWithoutRecipients {
- pathManager.navigate(to: .encryptRecipientView)
+ pathManager.replaceLast(to: .encryptRecipientView)
} else {
if encryptionButtonEnabled {
encryptionButtonEnabled = false
@@ -421,7 +421,7 @@ struct EncryptView: View {
))
} else {
await viewModel.loadContainerData(
- cryptoContainer: viewModel.cryptoContainer
+ cryptoContainer: nil
)
await updateAsyncLabels()
diff --git a/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift b/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift
index 823e7b66..b65af7b7 100644
--- a/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift
+++ b/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift
@@ -66,7 +66,9 @@ struct EncryptRecipientView: View {
TopBarContainer(
isTopBarHidden: isSearchExpanded,
title: nil,
- onLeftClick: { dismiss() },
+ onLeftClick: {
+ pathManager.replaceLast(to: .encryptView(isWithEncryption: false))
+ },
showRightIcons: true,
content: {
ZStack {
diff --git a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift
index 2ebd89d3..57f91889 100644
--- a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift
+++ b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift
@@ -19,6 +19,7 @@
import SwiftUI
import FactoryKit
+import IdCardLib
struct NFCInputView: View {
@Environment(LanguageSettings.self) private var languageSettings
@@ -31,12 +32,20 @@ struct NFCInputView: View {
@Binding var isActionEnabled: Bool
@Binding var canNumberError: String?
+ @Binding var pinNumber: String
+ @Binding var pinError: String?
+ var pinType: CodeType?
+
let onInputChange: () -> Void
private var canNumberTitle: String {
languageSettings.localized("CAN number")
}
+ private var pinNumberTitle: String {
+ languageSettings.localized("PIN code", [pinType?.name ?? ""])
+ }
+
private var canNumberLocationLabel: String {
languageSettings.localized("CAN number location")
}
@@ -50,12 +59,18 @@ struct NFCInputView: View {
rememberMe: Binding,
isActionEnabled: Binding,
canNumberError: Binding,
+ pinNumber: Binding,
+ pinError: Binding,
+ pinType: CodeType?,
onInputChange: @escaping () -> Void
) {
self._canNumber = canNumber
self._rememberMe = rememberMe
self._isActionEnabled = isActionEnabled
self._canNumberError = canNumberError
+ self._pinNumber = pinNumber
+ self._pinError = pinError
+ self.pinType = pinType
self.onInputChange = onInputChange
}
@@ -81,7 +96,22 @@ struct NFCInputView: View {
.padding(.vertical, Dimensions.Padding.XXSPadding)
}
}
- .padding(.vertical, Dimensions.Padding.ZeroPadding)
+ .padding(.vertical, Dimensions.Padding.MPadding)
+
+ VStack(alignment: .leading, spacing: Dimensions.Padding.XSPadding) {
+ FloatingLabelTextField(
+ title: pinNumberTitle,
+ placeholder: pinNumberTitle,
+ text: $pinNumber,
+ isSecure: true,
+ isError: !(pinError?.isEmpty ?? true),
+ errorText: pinError ?? "",
+ keyboardType: .numberPad
+ )
+ .onChange(of: pinNumber) {
+ onInputChange()
+ }
+ }
VStack(spacing: Dimensions.Padding.ZeroPadding) {
ToggleSection(isOn: $rememberMe, label: languageSettings.localized("Remember me"))
@@ -108,7 +138,10 @@ struct NFCInputView: View {
rememberMe: .constant(true),
isActionEnabled: .constant(true),
canNumberError: .constant(nil),
- onInputChange: {}
+ pinNumber: .constant("123"),
+ pinError: .constant(nil),
+ pinType: CodeType.pin2,
+ onInputChange: {},
)
.environment(Container.shared.languageSettings())
.environment(Container.shared.themeSettings())
diff --git a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift
index c73624b9..9137ac23 100644
--- a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift
+++ b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift
@@ -20,10 +20,9 @@
import SwiftUI
import FactoryKit
import CryptoSwift
+import IdCardLib
import LibdigidocLibSwift
import CommonsLib
-import IdCardLib
-
struct NFCView: View {
@Environment(\.dismiss) private var dismiss
@Environment(LanguageSettings.self) private var languageSettings
@@ -32,6 +31,8 @@ struct NFCView: View {
@State private var actionType: ActionType
@State private var actionMethods: [ActionMethod]
@State private var canNumber = ""
+ @State private var pinNumber = ""
+ @State private var pinType: CodeType?
@State private var rememberMe: Bool = true
@State private var isActionEnabled = false
@State private var isInProgress: Bool = false
@@ -41,10 +42,19 @@ struct NFCView: View {
@State private var viewModel: NFCViewModel
@State private var sharedNfcViewModel: SharedNFCViewModel
+ @State private var taskDecrypt: Task?
+
private var isNFCSupported: Bool {
sharedNfcViewModel.isNFCSupported()
}
+ private var nfcErrorMessage: String {
+ languageSettings.localized(
+ viewModel.nfcErrorKey ?? "",
+ viewModel.nfcErrorExtraArguments
+ )
+ }
+
private var canNumberError: Binding {
Binding(
get: { languageSettings.localized(
@@ -55,6 +65,16 @@ struct NFCView: View {
)
}
+ private var pinNumberError: Binding {
+ Binding(
+ get: { languageSettings.localized(
+ viewModel.pinNumberErrorKey ?? "",
+ viewModel.pinNumberErrorExtraArguments
+ ) },
+ set: { _ in }
+ )
+ }
+
private var displayedMessage: Binding {
Binding(
get: {
@@ -75,6 +95,7 @@ struct NFCView: View {
init(
actionType: ActionType,
actionMethods: [ActionMethod],
+ pinType: CodeType? = nil,
cryptoContainer: CryptoContainerProtocol? = nil,
signedContainer: SignedContainerProtocol? = nil,
onSuccess: @escaping (SignedContainerProtocol) -> Void = { _ in },
@@ -83,6 +104,7 @@ struct NFCView: View {
_viewModel = State(wrappedValue: Container.shared.nfcViewModel())
_sharedNfcViewModel = State(wrappedValue: Container.shared.sharedNfcViewModel())
self.actionType = actionType
+ self.pinType = pinType
self.actionMethods = actionMethods
self.cryptoContainer = cryptoContainer
self.signedContainer = signedContainer
@@ -98,6 +120,7 @@ struct NFCView: View {
isActionEnabled: $isActionEnabled,
isInProgress: $isInProgress,
onBackClick: {
+ cancelDecrypt()
guard isInProgress else {
dismiss()
return
@@ -108,8 +131,9 @@ struct NFCView: View {
switch actionType {
case .decrypt:
saveInputData()
-
- // TODO: Implement decrypt action
+ Task {
+ decrypt()
+ }
isInProgress = true
case .signing:
saveInputData()
@@ -155,9 +179,12 @@ struct NFCView: View {
rememberMe: $rememberMe,
isActionEnabled: $isActionEnabled,
canNumberError: canNumberError,
+ pinNumber: $pinNumber,
+ pinError: pinNumberError,
+ pinType: pinType,
onInputChange: {
isActionEnabled = viewModel
- .isActionEnabled(canNumber: canNumber)
+ .isActionEnabled(canNumber: canNumber, pinNumber: pinNumber, pinType: pinType)
}
)
}
@@ -170,6 +197,10 @@ struct NFCView: View {
rememberMe = inputData.rememberMe
}
}
+ .onChange(of: viewModel.nfcErrorKey) { _, newKey in
+ guard newKey != nil else { return }
+ Toast.show(nfcErrorMessage)
+ }
}
func saveInputData() {
@@ -181,6 +212,71 @@ struct NFCView: View {
)
}
}
+
+ private func decrypt() {
+ taskDecrypt = Task {
+ guard let container = cryptoContainer else { return }
+
+ let (inputCANNumber) = rememberMe ? (canNumber) : ("")
+
+ await viewModel.saveInputData(
+ canNumber: inputCANNumber,
+ rememberMe: rememberMe
+ )
+
+ isInProgress = true
+ nfcActionMessage = "NFC hold card"
+
+ let strings = NFCSessionStrings(
+ initialMessage: languageSettings.localized("Please place your ID card against the smart device"),
+ step1Message:
+ languageSettings.localized(
+ "Hold your ID card against your smart device until the data is read"
+ ),
+ step2Message: languageSettings.localized("Reading data please wait"),
+ step3Message: languageSettings.localized("Reading certificate"),
+ step4Message: languageSettings.localized("Decrypting in progress please wait"),
+ successMessage: languageSettings.localized("Data read"),
+ canErrorMessage: languageSettings.localized("Wrong CAN"),
+ pin1WrongMultipleErrorMessage:
+ languageSettings.localized(
+ "PIN verification error multiple",
+ [CodeType.pin1.name, "2"]
+ ),
+ pin1WrongErrorMessage: languageSettings.localized("PIN verification error one", [CodeType.pin1.name]),
+ pin1BlockedErrorMessage: languageSettings.localized("PIN blocked", [CodeType.pin1.name]),
+ technicalErrorMessage: languageSettings.localized("NFC technical error"),
+ sessionErrorMessage: languageSettings.localized("NFC session error")
+ )
+
+ let decryptedContainer = await viewModel.decrypt(
+ CAN: canNumber,
+ pin1: pinNumber,
+ cryptoContainer: container,
+ strings: strings
+ )
+
+ guard let container = decryptedContainer else {
+ cancelDecrypt()
+ isInProgress = false
+ return
+ }
+
+ cancelDecrypt()
+ isInProgress = false
+
+ onSuccessDecrypt(container)
+ dismiss()
+ }
+ }
+
+ private func cancelDecrypt() {
+ pinNumber.isEmpty ? () : (pinNumber.removeAll())
+ isActionEnabled = viewModel
+ .isActionEnabled(canNumber: canNumber, pinNumber: pinNumber, pinType: pinType)
+ taskDecrypt?.cancel()
+ taskDecrypt = nil
+ }
}
#Preview {
@@ -192,6 +288,7 @@ struct NFCView: View {
.mobileId,
.smartId
],
+ pinType: CodeType.pin2,
signedContainer: SignedContainer(
fileManager: Container.shared.fileManager(),
containerUtil: Container.shared.containerUtil()
diff --git a/RIADigiDoc/UI/Component/Container/Signing/SigningRootView.swift b/RIADigiDoc/UI/Component/Container/Signing/SigningRootView.swift
index 36b115aa..c0c9b1ab 100644
--- a/RIADigiDoc/UI/Component/Container/Signing/SigningRootView.swift
+++ b/RIADigiDoc/UI/Component/Container/Signing/SigningRootView.swift
@@ -19,6 +19,7 @@
import SwiftUI
import FactoryKit
+import IdCardLib
import LibdigidocLibSwift
import CommonsLib
@@ -54,6 +55,7 @@ struct SigningRootView: View {
.mobileId,
.smartId
],
+ pinType: CodeType.pin2,
signedContainer: container,
onSuccess: { container in
sharedContainerViewModel.removeLastContainer()
diff --git a/RIADigiDoc/UI/Component/HomeView.swift b/RIADigiDoc/UI/Component/HomeView.swift
index 996b5043..8defd50f 100644
--- a/RIADigiDoc/UI/Component/HomeView.swift
+++ b/RIADigiDoc/UI/Component/HomeView.swift
@@ -170,7 +170,9 @@ struct HomeView: View {
})
.onChange(of: isNavigatingToEncryptView, { _, newValue in
if newValue {
- pathManager.navigate(to: .encryptView(isWithEncryption: false))
+ pathManager.navigate(to: .encryptView(
+ isWithEncryption: false
+ ))
isNavigatingToEncryptView = false
}
})
diff --git a/RIADigiDoc/UI/Component/My eID/MyEidRootView.swift b/RIADigiDoc/UI/Component/My eID/MyEidRootView.swift
index 4542fb12..3ed947ee 100644
--- a/RIADigiDoc/UI/Component/My eID/MyEidRootView.swift
+++ b/RIADigiDoc/UI/Component/My eID/MyEidRootView.swift
@@ -19,6 +19,7 @@
import SwiftUI
import FactoryKit
+import IdCardLib
import LibdigidocLibSwift
import CommonsLib
diff --git a/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift b/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift
index a4190b85..51e0bcfb 100644
--- a/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift
+++ b/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModel.swift
@@ -18,6 +18,9 @@
*/
import Foundation
+import CryptoObjCWrapper
+import CryptoSwift
+import IdCardLib
import LibdigidocLibSwift
import CommonsLib
import UtilsLib
@@ -29,6 +32,12 @@ class NFCViewModel: NFCViewModelProtocol, Loggable {
var canNumberErrorKey: String?
var canNumberErrorExtraArguments: [String] = []
+ var pinNumberErrorKey: String?
+ var pinNumberErrorExtraArguments: [String] = []
+
+ var nfcErrorKey: String?
+ var nfcErrorExtraArguments: [String] = []
+
private let dataStore: DataStoreProtocol
init(
@@ -37,9 +46,12 @@ class NFCViewModel: NFCViewModelProtocol, Loggable {
self.dataStore = dataStore
}
- func isActionEnabled(canNumber: String) -> Bool {
+ func isActionEnabled(canNumber: String, pinNumber: String, pinType: CodeType?) -> Bool {
checkCANNumberValidity(canNumber: canNumber)
- return (!canNumber.isEmpty && canNumberErrorKey?.isEmpty == true)
+ checkPINNumberValidity(pinNumber: pinNumber, pinType: pinType)
+ let result = (!canNumber.isEmpty && canNumberErrorKey?.isEmpty == true)
+ && (!pinNumber.isEmpty && pinNumberErrorKey?.isEmpty == true)
+ return result
}
func saveInputData(canNumber: String, rememberMe: Bool) async {
@@ -59,12 +71,81 @@ class NFCViewModel: NFCViewModelProtocol, Loggable {
func resetErrors() {
canNumberErrorKey = nil
canNumberErrorExtraArguments = []
+ pinNumberErrorKey = nil
+ pinNumberErrorExtraArguments = []
+ nfcErrorKey = nil
+ nfcErrorExtraArguments = []
}
func loadPersonalData() {
// TODO: Implement with My eID
}
+ func decrypt(
+ CAN: String,
+ pin1: String,
+ cryptoContainer: CryptoContainerProtocol?,
+ strings: NFCSessionStrings
+ ) async
+ -> CryptoContainerProtocol? {
+ do {
+ let containerFile = await cryptoContainer?.getRawContainerFile() ?? URL(fileURLWithPath: "")
+ let recipients = await cryptoContainer?.getRecipients() ?? []
+ let pinSecureData = SecureData(Array(pin1.utf8))
+
+ let container = try await OperationDecrypt().processDecrypt(
+ CAN: CAN,
+ PIN1: pinSecureData,
+ containerFile: containerFile,
+ recipients: recipients,
+ strings: strings
+ )
+ return container
+ } catch {
+ guard let exception = error as? IdCardInternalError else {
+ NFCViewModel.logger().error("NFC: ID Card General error.")
+ nfcErrorKey = "NFC session error"
+ nfcErrorExtraArguments = []
+ return nil
+ }
+
+ let error = exception.getIdCardError()
+ handleIdCardError(error, pinType: CodeType.pin1)
+
+ return nil
+ }
+ }
+
+ private func handleIdCardError(_ error: IdCardError, pinType: CodeType) {
+ NFCViewModel.logger().error("NFC: ID Card error: \(error)")
+
+ switch error {
+ case .cancelledByUser:
+ nfcErrorKey = nil
+ nfcErrorExtraArguments = []
+ case .wrongCAN:
+ nfcErrorKey = "Wrong CAN"
+ nfcErrorExtraArguments = []
+ case .wrongPIN(let triesLeft):
+ if triesLeft > 1 {
+ nfcErrorKey = "PIN verification error multiple"
+ nfcErrorExtraArguments = [pinType.name, String(triesLeft)]
+ } else if triesLeft == 1 {
+ nfcErrorKey = "PIN verification error one"
+ nfcErrorExtraArguments = [pinType.name]
+ } else {
+ nfcErrorKey = "PIN blocked"
+ nfcErrorExtraArguments = [pinType.name]
+ }
+ case .sessionError:
+ nfcErrorKey = "NFC session error"
+ nfcErrorExtraArguments = []
+ default:
+ nfcErrorKey = "NFC technical error"
+ nfcErrorExtraArguments = []
+ }
+ }
+
func sign() async -> SignedContainerProtocol? {
// TODO: Implement with NFC signing
return nil
@@ -87,4 +168,27 @@ class NFCViewModel: NFCViewModelProtocol, Loggable {
}
canNumberErrorKey = ""
}
+
+ private func checkPINNumberValidity(pinNumber: String, pinType: CodeType?) {
+ let minLen = if pinType == .pin1 {
+ Constants.Validation.Pin1MinimumLength
+ } else if pinType == .pin2 {
+ Constants.Validation.Pin2MinimumLength
+ } else {
+ Constants.Validation.PukMinimumLength
+ }
+
+ let maxLen = Constants.Validation.PinMaximumLength
+
+ guard pinNumber.isEmpty || (
+ pinNumber.count >= minLen &&
+ pinNumber.count <= maxLen &&
+ pinNumber.allSatisfy { $0.isNumber }
+ ) else {
+ pinNumberErrorKey = "PIN length requirement"
+ pinNumberErrorExtraArguments = [pinType?.name ?? "", String(minLen), String(maxLen)]
+ return
+ }
+ pinNumberErrorKey = ""
+ }
}
diff --git a/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModelProtocol.swift b/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModelProtocol.swift
index 28fd356d..b8599b6e 100644
--- a/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModelProtocol.swift
+++ b/RIADigiDoc/ViewModel/Signing/NFC/NFCViewModelProtocol.swift
@@ -18,6 +18,8 @@
*/
import Foundation
+import CryptoSwift
+import IdCardLib
import LibdigidocLibSwift
import CommonsLib
@@ -26,6 +28,8 @@ import CommonsLib
public protocol NFCViewModelProtocol: Sendable {
func isActionEnabled(
canNumber: String,
+ pinNumber: String,
+ pinType: CodeType?
) -> Bool
func saveInputData(
@@ -39,6 +43,13 @@ public protocol NFCViewModelProtocol: Sendable {
func sign() async -> SignedContainerProtocol?
+ func decrypt(
+ CAN: String,
+ pin1: String,
+ cryptoContainer: CryptoContainerProtocol?,
+ strings: NFCSessionStrings
+ ) async -> CryptoContainerProtocol?
+
func loadPersonalData()
func isRoleDataEnabled() async -> Bool
diff --git a/scripts/build-libcdoc.sh b/scripts/build-libcdoc.sh
new file mode 100755
index 00000000..66e1eb5d
--- /dev/null
+++ b/scripts/build-libcdoc.sh
@@ -0,0 +1,400 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Move to project folder root
+cd "$(dirname "$0")/.."
+
+# ---- Config ----
+PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BUILD_ROOT="${PROJECT_ROOT}/build-cdoc"
+
+SOURCE_DIR="${PROJECT_ROOT}/../Downloads/libcdoc"
+GIT_URL="https://github.com/open-eid/libcdoc.git"
+
+FLATBUFFERS_GIT_URL="https://github.com/google/flatbuffers.git"
+FLATBUFFERS_SRC="${BUILD_ROOT}/third_party/flatbuffers-src"
+
+# Host (macOS) flatbuffers (provides flatc + cmake package with targets)
+FLATBUFFERS_HOST_BUILD="${BUILD_ROOT}/third_party/flatbuffers-host-build"
+FLATBUFFERS_HOST_INSTALL="${BUILD_ROOT}/third_party/flatbuffers-host-install"
+
+# iOS flatbuffers (provides libflatbuffers.a for each SDK)
+FLATBUFFERS_IOS_BUILD_ROOT="${BUILD_ROOT}/third_party/flatbuffers-build"
+FLATBUFFERS_IOS_INSTALL_ROOT="${BUILD_ROOT}/third_party/flatbuffers-install"
+
+OPENSSL_ROOT_IOS="${PROJECT_ROOT}/../Downloads/libdigidocppFiles_1910/libdigidocpp.iphoneos"
+OPENSSL_ROOT_SIM="${PROJECT_ROOT}/../Downloads/libdigidocppFiles_1910/libdigidocpp.iphonesimulator"
+
+# Defaults
+# Use: BUILD_TYPE=Debug ./build-libcdoc-ios.sh OR BUILD_TYPE=RelWithDebInfo ./build-libcdoc-ios.sh
+BUILD_TYPE="${BUILD_TYPE:-Release}"
+IOS_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET:-15.0}"
+
+# Control symbol stripping (default: keep symbols for Debug/RelWithDebInfo, strip for Release)
+STRIP_SYMBOLS="${STRIP_SYMBOLS:-auto}" # auto|0|1
+# Generate dSYM bundles (default: on for Debug/RelWithDebInfo)
+GENERATE_DSYM="${GENERATE_DSYM:-auto}" # auto|0|1
+
+# Generator
+if command -v ninja >/dev/null 2>&1; then
+ CMAKE_GENERATOR="Ninja"
+else
+ CMAKE_GENERATOR="Unix Makefiles"
+fi
+
+export PATH="$PATH:/usr/local/bin:/opt/homebrew/bin"
+
+# ---- Helpers ----
+need() {
+ command -v "$1" >/dev/null 2>&1 || { echo "Missing required tool: $1" >&2; exit 1; }
+}
+
+sdk_path() {
+ local sdk="$1"
+ xcrun --sdk "$sdk" --show-sdk-path
+}
+
+ensure_dirs() {
+ mkdir -p "${BUILD_ROOT}/third_party" "${BUILD_ROOT}/logs" "${BUILD_ROOT}/build" "${BUILD_ROOT}/install"
+}
+
+ensure_flatbuffers_src() {
+ if [[ ! -d "${FLATBUFFERS_SRC}/.git" ]]; then
+ echo "Cloning flatbuffers into ${FLATBUFFERS_SRC} ..." >&2
+ git clone "${FLATBUFFERS_GIT_URL}" "${FLATBUFFERS_SRC}" >&2
+ fi
+}
+
+is_truthy() {
+ # Portable lowercase (no ${var,,})
+ local v="${1:-}"
+ v="$(printf '%s' "$v" | tr '[:upper:]' '[:lower:]')"
+ case "$v" in
+ 1|true|yes|y|on) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+# Decide stripping based on BUILD_TYPE (unless overridden)
+should_strip() {
+ if [[ "${STRIP_SYMBOLS}" != "auto" ]]; then
+ is_truthy "${STRIP_SYMBOLS}"
+ return $?
+ fi
+
+ case "${BUILD_TYPE}" in
+ Debug|RelWithDebInfo) return 1 ;; # don't strip
+ *) return 0 ;; # strip for Release/MinSizeRel/others
+ esac
+}
+
+# Decide dSYM based on BUILD_TYPE (unless overridden)
+should_dsym() {
+ if [[ "${GENERATE_DSYM}" != "auto" ]]; then
+ is_truthy "${GENERATE_DSYM}"
+ return $?
+ fi
+
+ case "${BUILD_TYPE}" in
+ Debug|RelWithDebInfo) return 0 ;; # generate dSYM
+ *) return 1 ;;
+ esac
+}
+
+# ---- Build FlatBuffers host (macOS): flatc + flatlib ON ----
+flatbuffers_host_pkg_dir() {
+ ensure_flatbuffers_src
+
+ local pkg_dir="${FLATBUFFERS_HOST_INSTALL}/lib/cmake/flatbuffers"
+ if [[ -f "${pkg_dir}/flatbuffers-config.cmake" ]]; then
+ echo "${pkg_dir}"
+ return 0
+ fi
+
+ mkdir -p "${FLATBUFFERS_HOST_BUILD}" "${FLATBUFFERS_HOST_INSTALL}"
+ echo "---- Building FlatBuffers host (flatc + flatlib) ----" >&2
+
+ cmake -G "${CMAKE_GENERATOR}" \
+ -S "${FLATBUFFERS_SRC}" \
+ -B "${FLATBUFFERS_HOST_BUILD}" \
+ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
+ -DCMAKE_INSTALL_PREFIX="${FLATBUFFERS_HOST_INSTALL}" \
+ -DFLATBUFFERS_BUILD_TESTS=OFF \
+ -DFLATBUFFERS_BUILD_FLATHASH=OFF \
+ -DFLATBUFFERS_BUILD_FLATC=ON \
+ -DFLATBUFFERS_BUILD_FLATLIB=ON \
+ -DFLATBUFFERS_INSTALL=ON >&2
+
+ cmake --build "${FLATBUFFERS_HOST_BUILD}" --config "${BUILD_TYPE}" >&2
+ cmake --install "${FLATBUFFERS_HOST_BUILD}" --config "${BUILD_TYPE}" >&2
+
+ if [[ ! -f "${pkg_dir}/flatbuffers-config.cmake" ]]; then
+ echo "ERROR: Host flatbuffers package not found at: ${pkg_dir}" >&2
+ exit 1
+ fi
+
+ echo "${pkg_dir}"
+}
+
+# ---- Build FlatBuffers for iOS (per SDK): flatlib ON, flatc OFF ----
+build_flatbuffers_ios_one() {
+ local name="$1" # iphoneos | iphonesimulator
+ local sdk="$2" # iphoneos | iphonesimulator
+ local archs="$3" # "arm64" or "arm64;x86_64"
+
+ ensure_flatbuffers_src
+
+ local sysroot
+ sysroot="$(sdk_path "$sdk")"
+
+ local build_dir="${FLATBUFFERS_IOS_BUILD_ROOT}/${name}"
+ local install_dir="${FLATBUFFERS_IOS_INSTALL_ROOT}/${name}"
+ local pkg_dir="${install_dir}/lib/cmake/flatbuffers"
+ local lib_path="${install_dir}/lib/libflatbuffers.a"
+
+ # Skip if already installed
+ if [[ -f "${lib_path}" && -f "${pkg_dir}/flatbuffers-config.cmake" ]]; then
+ echo "${pkg_dir}"
+ return 0
+ fi
+
+ mkdir -p "${build_dir}" "${install_dir}"
+ echo "---- Building FlatBuffers for ${name} (iOS) ----" >&2
+
+ cmake -G "${CMAKE_GENERATOR}" \
+ -S "${FLATBUFFERS_SRC}" \
+ -B "${build_dir}" \
+ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
+ -DCMAKE_SYSTEM_NAME=iOS \
+ -DCMAKE_OSX_SYSROOT="${sysroot}" \
+ -DCMAKE_OSX_ARCHITECTURES="${archs}" \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET}" \
+ -DCMAKE_INSTALL_PREFIX="${install_dir}" \
+ -DFLATBUFFERS_BUILD_TESTS=OFF \
+ -DFLATBUFFERS_BUILD_FLATHASH=OFF \
+ -DFLATBUFFERS_BUILD_FLATC=OFF \
+ -DFLATBUFFERS_BUILD_FLATLIB=ON \
+ -DFLATBUFFERS_INSTALL=ON >&2
+
+ cmake --build "${build_dir}" --config "${BUILD_TYPE}" >&2
+ cmake --install "${build_dir}" --config "${BUILD_TYPE}" >&2
+
+ if [[ ! -f "${lib_path}" ]]; then
+ echo "ERROR: iOS libflatbuffers.a not found at: ${lib_path}" >&2
+ exit 1
+ fi
+
+ echo "${pkg_dir}"
+}
+
+# Create dSYM for a framework binary (LOUD: fails if requested and not produced)
+maybe_generate_dsym_for_framework() {
+ local framework_bin="$1" # .../cdoc.framework/cdoc
+
+ if ! should_dsym; then
+ return 0
+ fi
+ if ! command -v dsymutil >/dev/null 2>&1; then
+ echo "dsymutil not found; skipping dSYM generation" >&2
+ return 0
+ fi
+ if [[ ! -f "${framework_bin}" ]]; then
+ echo "Framework binary not found: ${framework_bin}" >&2
+ return 0
+ fi
+
+ # IMPORTANT: if this is a static library (ar archive), dsymutil will fail.
+ # The dSYM will be generated at the FINAL link step (your app/framework that links it).
+ if command -v file >/dev/null 2>&1; then
+ local ft
+ ft="$(file -b "${framework_bin}" || true)"
+ if echo "${ft}" | grep -qiE 'ar archive|current ar archive|static library'; then
+ echo "Skipping dSYM: ${framework_bin} is a static library (${ft}). dSYM will be produced when the app links it." >&2
+ return 0
+ fi
+ fi
+
+ local out="${framework_bin}.dSYM"
+ echo "Generating dSYM: ${out}" >&2
+
+ rm -rf "${out}"
+ dsymutil "${framework_bin}" -o "${out}"
+
+ if [[ ! -d "${out}" ]]; then
+ echo "ERROR: dsymutil ran but dSYM folder was not created: ${out}" >&2
+ exit 1
+ fi
+
+ if command -v dwarfdump >/dev/null 2>&1; then
+ echo "dSYM UUIDs:" >&2
+ dwarfdump --uuid "${out}" >&2 || true
+ fi
+}
+
+# ---- Build libcdoc for one platform ----
+build_one() {
+ local name="$1" # iphoneos | iphonesimulator
+ local sdk="$2" # iphoneos | iphonesimulator
+ local archs="$3" # "arm64" or "arm64;x86_64"
+ local openssl_root="$4" # path to libdigidocpp.
+
+ local sysroot
+ sysroot="$(sdk_path "$sdk")"
+
+ # Ensure deps:
+ local flatbuffers_host_dir
+ flatbuffers_host_dir="$(flatbuffers_host_pkg_dir)"
+
+ local flatbuffers_ios_pkg_dir
+ flatbuffers_ios_pkg_dir="$(build_flatbuffers_ios_one "${name}" "${sdk}" "${archs}")"
+
+ local flatbuffers_install_dir="${FLATBUFFERS_IOS_INSTALL_ROOT}/${name}"
+ local flatbuffers_lib="${flatbuffers_install_dir}/lib/libflatbuffers.a"
+
+ # OpenSSL (iOS) explicit paths
+ local openssl_inc="${openssl_root}/include"
+ local openssl_crypto="${openssl_root}/lib/libcrypto.a"
+ local openssl_ssl="${openssl_root}/lib/libssl.a"
+
+ if [[ ! -d "${openssl_inc}/openssl" ]]; then
+ echo "ERROR: OpenSSL headers not found at: ${openssl_inc}/openssl" >&2
+ exit 1
+ fi
+ if [[ ! -f "${openssl_crypto}" ]]; then
+ echo "ERROR: OpenSSL crypto lib not found at: ${openssl_crypto}" >&2
+ exit 1
+ fi
+ if [[ ! -f "${openssl_ssl}" ]]; then
+ echo "ERROR: OpenSSL ssl lib not found at: ${openssl_ssl}" >&2
+ exit 1
+ fi
+
+ # FlatBuffers iOS lib sanity
+ if [[ ! -f "${flatbuffers_lib}" ]]; then
+ echo "ERROR: iOS flatbuffers lib not found at: ${flatbuffers_lib}" >&2
+ exit 1
+ fi
+
+ local build_dir="${BUILD_ROOT}/build/${name}"
+ local install_dir="${BUILD_ROOT}/install/${name}"
+ local logs_dir="${BUILD_ROOT}/logs"
+ mkdir -p "${build_dir}" "${install_dir}" "${logs_dir}"
+
+ local do_strip="ON"
+ if should_strip; then
+ do_strip="ON"
+ else
+ do_strip="OFF"
+ fi
+
+ echo "============================================================"
+ echo "Building: ${name}"
+ echo " SDK: ${sdk} -> ${sysroot}"
+ echo " ARCHS: ${archs}"
+ echo " Build type: ${BUILD_TYPE}"
+ echo " Strip: ${do_strip} (STRIP_SYMBOLS=${STRIP_SYMBOLS})"
+ echo " dSYM: $(should_dsym && echo ON || echo OFF) (GENERATE_DSYM=${GENERATE_DSYM})"
+ echo " OpenSSL: ${openssl_root}"
+ echo " FlatBuffers host pkg: ${flatbuffers_host_dir}"
+ echo " FlatBuffers iOS pkg: ${flatbuffers_ios_pkg_dir}"
+ echo " BUILD: ${build_dir}"
+ echo " INSTALL: ${install_dir}"
+ echo "============================================================"
+
+ # Configure
+ cmake -G "${CMAKE_GENERATOR}" \
+ -S "${SOURCE_DIR}" \
+ -B "${build_dir}" \
+ -DFRAMEWORK_DESTINATION="${install_dir}/Frameworks" \
+ -DBUILD_TOOLS=OFF \
+ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
+ -DCMAKE_SYSTEM_NAME=iOS \
+ -DCMAKE_OSX_SYSROOT="${sysroot}" \
+ -DCMAKE_OSX_ARCHITECTURES="${archs}" \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET="${IOS_DEPLOYMENT_TARGET}" \
+ -DFRAMEWORK=ON \
+ -DBUILD_SHARED_LIBS=ON \
+ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
+ -DCMAKE_INSTALL_PREFIX="${install_dir}" \
+ -DINSTALL_FRAMEWORKDIR="${install_dir}" \
+ -DOPENSSL_ROOT_DIR="${openssl_root}" \
+ -DOPENSSL_INCLUDE_DIR="${openssl_inc}" \
+ -DOPENSSL_CRYPTO_LIBRARY="${openssl_crypto}" \
+ -DOPENSSL_SSL_LIBRARY="${openssl_ssl}" \
+ -DOPENSSL_USE_STATIC_LIBS=TRUE \
+ -DFlatBuffers_DIR="${flatbuffers_host_dir}" \
+ -DCMAKE_PREFIX_PATH="${flatbuffers_install_dir};${FLATBUFFERS_HOST_INSTALL}" \
+ -DCMAKE_DISABLE_FIND_PACKAGE_SWIG=YES \
+ -DCMAKE_DISABLE_FIND_PACKAGE_Doxygen=YES \
+ -DCMAKE_INSTALL_DO_STRIP="${do_strip}" \
+ -DCMAKE_STRIP="" \
+ -DCMAKE_C_FLAGS_DEBUG="-g" \
+ -DCMAKE_CXX_FLAGS_DEBUG="-g" \
+ -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O2 -g" \
+ -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O2 -g" \
+ -DCMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT=dwarf-with-dsym \
+ 2>&1 | tee "${logs_dir}/configure-${name}.log"
+
+ # Build
+ cmake --build "${build_dir}" --config "${BUILD_TYPE}" 2>&1 | tee "${logs_dir}/build-${name}.log"
+
+ # Install
+ cmake --install "${build_dir}" --config "${BUILD_TYPE}" 2>&1 | tee "${logs_dir}/install-${name}.log"
+
+ # dSYM for your framework binary
+ local fw_bin="${install_dir}/Frameworks/cdoc.framework/cdoc"
+ maybe_generate_dsym_for_framework "${fw_bin}"
+}
+
+# ---- Pre-flight ----
+need git
+need cmake
+need xcrun
+ensure_dirs
+
+# Fetch libcdoc if missing
+if [[ ! -d "${SOURCE_DIR}/.git" ]]; then
+ echo "Cloning libcdoc into ${SOURCE_DIR} ..."
+ git clone "${GIT_URL}" "${SOURCE_DIR}"
+else
+ echo "Using existing libcdoc checkout at ${SOURCE_DIR}"
+fi
+
+# ---- Build both variants ----
+build_one "iphoneos" "iphoneos" "arm64" "${OPENSSL_ROOT_IOS}"
+build_one "iphonesimulator" "iphonesimulator" "arm64;x86_64" "${OPENSSL_ROOT_SIM}"
+
+# ---- Optional: Create XCFramework (FRAMEWORK-based) ----
+if command -v xcodebuild >/dev/null 2>&1; then
+ IOS_FW="${BUILD_ROOT}/install/iphoneos/Frameworks/cdoc.framework"
+ SIM_FW="${BUILD_ROOT}/install/iphonesimulator/Frameworks/cdoc.framework"
+
+ if [[ -d "${IOS_FW}" && -d "${SIM_FW}" ]]; then
+ OUT_XC="${BUILD_ROOT}/cdoc.xcframework"
+ rm -rf "${OUT_XC}"
+
+ echo "Creating XCFramework (frameworks): ${OUT_XC}"
+ xcodebuild -create-xcframework \
+ -framework "${IOS_FW}" \
+ -framework "${SIM_FW}" \
+ -output "${OUT_XC}"
+
+ echo "XCFramework created at: ${OUT_XC}"
+ else
+ echo "Skipping XCFramework: expected frameworks not found:"
+ echo " ${IOS_FW}"
+ echo " ${SIM_FW}"
+ fi
+else
+ echo "xcodebuild not found; skipping XCFramework creation."
+fi
+
+echo
+echo "Done. Outputs:"
+echo " Device framework: ${BUILD_ROOT}/install/iphoneos/Frameworks/cdoc.framework"
+echo " Simulator framework: ${BUILD_ROOT}/install/iphonesimulator/Frameworks/cdoc.framework"
+echo " (Optional) XCFramework: ${BUILD_ROOT}/cdoc.xcframework"
+echo
+echo "Tips:"
+echo " Debug build (max symbols): BUILD_TYPE=Debug STRIP_SYMBOLS=0 ./build-libcdoc.sh"
+echo " RelWithDebInfo (opt+symbols): BUILD_TYPE=RelWithDebInfo ./build-libcdoc.sh"