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"