-
Notifications
You must be signed in to change notification settings - Fork 43
feat: Dodanie obsługi .NET Standard 2.0 — kompatybilność z .NET Framework 4.7.2+ oraz .NET Core 2.1–7.0 (Windows) #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…on, ECDsa, ECDH+AES-GCM, certificates, CSRs, QR codes, XAdES - everything passes the same e2e tests as net8-10.
… UTC offsets; timeout fix in Tests.
baf9a5b to
3fd5ee8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
PR dodaje targetowanie netstandard2.0 dla bibliotek KSeF.Client i KSeF.Client.ClientFactory (wraz z warstwą kompatybilności kryptograficznej i polyfillami), oraz rozszerza testy o net48, aby umożliwić użycie SDK w aplikacjach opartych o .NET Framework 4.7.2+/4.8 (issue #80).
Changes:
- Dodanie
netstandard2.0do projektów produkcyjnych + warstw kompatybilności (Compatibility/*) dla brakujących API. - Adaptacje kodu produkcyjnego do różnic API (m.in. HTTP, regex source generator, kryptografia/certyfikaty, guard clauses).
- Rozszerzenie testów o
net48+ polyfille testowe i aktualizacje dokumentacji.
Reviewed changes
Copilot reviewed 118 out of 122 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| nuget-package.md | Aktualizacja opisów paczek/TFM |
| README.md | Dokumentacja wsparcia netstandard2.0 i net48 testów |
| Directory.Build.props | Globalne LangVersion=12 |
| KSeF.Client/KSeF.Client.csproj | Dodanie netstandard2.0 + zależności warunkowe |
| KSeF.Client/Validation/RegexPatterns.cs | Dual path: [GeneratedRegex] vs Regex dla ns2.0 |
| KSeF.Client/Http/RestClient.cs | API różnice: ReadAsStringAsync/StreamAsync, 429 |
| KSeF.Client/Http/JsonUtil.cs | Polyfille StreamReader/AsSpan dla ns2.0 |
| KSeF.Client/Helpers/BatchPartsSender.cs | Migracja na Guard.* |
| KSeF.Client/GlobalUsings.netstandard.cs | Global usings pod ns2.0 + Guard |
| KSeF.Client/Extensions/X509CertificateLoaderExtensions.cs | Różnice ns2.0 (EphemeralKeySet/Contains/Pkcs) |
| KSeF.Client/Extensions/SessionsFilterExtensions.cs | Formatowanie invariant dla ns2.0 |
| KSeF.Client/Extensions/Base64UrlExtensions.cs | Lokalizacja komunikatów wyjątków |
| KSeF.Client/DI/ServiceCollectionExtensions.cs | Zmiana timeout HttpClient |
| KSeF.Client/Compatibility/GuardClauses.cs | Wspólne guard clauses dla wszystkich TFM |
| KSeF.Client/Compatibility/HashCompat.cs | Polyfill SHA256.HashData |
| KSeF.Client/Compatibility/RandomCompat.cs | Polyfill Random.Shared |
| KSeF.Client/Compatibility/StringCompat.cs | Polyfill string.Contains(..., StringComparison) |
| KSeF.Client/Compatibility/CompositeFormatCompat.cs | Polyfill CompositeFormat |
| KSeF.Client/Compatibility/CryptoPolyfills.cs | Polyfill DSASignatureFormat |
| KSeF.Client/Compatibility/CryptoCompat.Pem.cs | PEM helper (Decode/Encode/Create cert) |
| KSeF.Client/Compatibility/Pkcs8Decryptor.cs | PBES2/PBKDF2 decryptor dla ns2.0 |
| KSeF.Client/Compatibility/CertificateCompat.cs | Polyfill CopyWithPrivateKey via reflection |
| KSeF.Client/Compatibility/EcdhCompat.cs | ECDH via reflection + SPKI encode/decode |
| KSeF.Client/Compatibility/CsrCompat.cs | PKCS#10 CSR builder dla ns2.0 |
| KSeF.Client/Compatibility/AesGcmCompat.cs | AES-GCM via BCrypt P/Invoke (ns2.0) |
| KSeF.Client/Clients/ClientBase.cs | Migracja na Guard.* |
| KSeF.Client/Clients/ActiveSessionsClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/AuthorizationClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/BatchSessionClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/CertificateClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/GrantPermissionClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/InvoiceDownloadClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/KsefTokenClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/LighthouseClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/OnlineSessionClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/PeppolClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/PermissionOperationClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/RevokePermissionClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/SearchPermissionClient.cs | Migracja na Guard.* |
| KSeF.Client/Clients/SessionStatusClient.cs | Migracja na Guard.* + invariant formatting ns2.0 |
| KSeF.Client/Api/Services/SignatureService.cs | SHA-256 polyfill na ns2.0 |
| KSeF.Client/Api/Services/QrCodeService.cs | Render QR bez MAUI na ns2.0 |
| KSeF.Client/Api/Services/CryptographyService.cs | Ścieżki kryptograficzne ns2.0 + compat |
| KSeF.Client/Api/Builders/X509Certificates/SelfSignedCertificateForSealBuilder.cs | UtcNow + compat self-signed ns2.0 |
| KSeF.Client/Api/Builders/X509Certificates/SelfSignedCertificateForSignatureBuilder.cs | UtcNow + compat self-signed ns2.0 |
| KSeF.Client/Api/Builders/Auth/AuthTokenRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/Auth/AuthKsefTokenBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/AuthorizationEntityPermissions/GrantAuthorizationPermissionsRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/EntityPermissions/GrantEntityPermissionsRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/EUEntityPermissions/GrantEUEntityPermissionsRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/EUEntityRepresentativePermissions/GrantEUEntityRepresentativePermissionsRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/IndirectEntityPermissions/GrantIndirectEntityPermissionsRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/PersonPermissions/GrantPermissionsPersonRequestRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client/Api/Builders/SubEntityPermissions/GrantSubEntityPermissionsRequestBuilder.cs | Migracja na Guard.* |
| KSeF.Client.ClientFactory/KSeF.Client.ClientFactory.csproj | Dodanie netstandard2.0 + PolySharp |
| KSeF.Client.ClientFactory/GlobalUsings.netstandard.cs | Global usings pod ns2.0 |
| KSeF.Client.ClientFactory/KSeFFactoryCryptographyServices.cs | #nullable enable |
| KSeF.Client.ClientFactory/KSeFFactoryCertificateFetcherServices.cs | #nullable enable |
| KSeF.Client.Tests/KSeF.Client.Tests.csproj | Dodanie net48 + ref assemblies |
| KSeF.Client.Tests/GlobalUsings.netframework.cs | Global usings pod net48 |
| KSeF.Client.Tests/Authorization.cs | #nullable enable |
| KSeF.Client.Tests/VerificationLinkServiceTests.cs | Polyfille SHA256 + UtcNow |
| KSeF.Client.Tests/Features/QrCode/QrCode.Feature.cs | Polyfill SHA256 dla net48 |
| KSeF.Client.Tests/Features/Invoice/Invoice.feature.cs | Stabilizacja dat (Warsaw) |
| KSeF.Client.Tests/Features/Batch/Batch.feature.cs | RNG polyfill dla net48 |
| KSeF.Client.Tests/Features/Authenticate/Authenticate.feature.cs | StartsWith fix (\"[\") |
| KSeF.Client.Tests/Compatibility/CryptoCompat.cs | Polyfill importów kluczy (net48) |
| KSeF.Client.Tests.Utils/KSeF.Client.Tests.Utils.csproj | Dodanie net48 + polyfille |
| KSeF.Client.Tests.Utils/GlobalUsings.netframework.cs | Global usings + Guard (tests) |
| KSeF.Client.Tests.Utils/KsefDateTimeHelper.cs | Helper strefy czasu Warsaw |
| KSeF.Client.Tests.Utils/MiscellaneousUtils.cs | Polyfille regex/RNG/Random dla net48 |
| KSeF.Client.Tests.Utils/OnlineSessionUtils.cs | Daty w strefie Warsaw |
| KSeF.Client.Tests.Utils/AsyncPollingUtils.cs | Migracja na Guard.* (tests) |
| KSeF.Client.Tests.Utils/AuthenticationUtils.cs | #nullable enable |
| KSeF.Client.Tests.Utils/BatchSessionUtils.cs | Polyfille Stream/Write/Read dla net48 |
| KSeF.Client.Tests.Utils/CertificateUtils.cs | SHA256 polyfill dla net48 |
| KSeF.Client.Tests.Utils/PermissionUtils.cs | #nullable enable |
| KSeF.Client.Tests.Utils/Upo/UpoUtils.cs | Migracja na Guard.* (tests) |
| KSeF.Client.Tests.Utils/Upo/InvoiceUpoV4_2.cs | #nullable enable |
| KSeF.Client.Tests.Utils/Upo/InvoiceUpoV4_3.cs | #nullable enable |
| KSeF.Client.Tests.Utils/Upo/SessionUpoV4_2.cs | #nullable enable |
| KSeF.Client.Tests.Utils/Upo/SessionUpoV4_3.cs | #nullable enable |
| KSeF.Client.Tests.Utils/Compatibility/GuardClauses.cs | Guard polyfill dla net48 tests |
| KSeF.Client.Tests.Utils/Compatibility/RandomCompat.cs | Polyfill NextInt64 (net48) |
| KSeF.Client.Tests.Utils/Compatibility/StringCompat.cs | Polyfill Contains(StringComparison) (net48) |
| KSeF.Client.Tests.Utils/Compatibility/CryptoCompat.cs | RSA PKCS#1 decode (net48) |
| KSeF.Client.Tests.Core/KSeF.Client.Tests.Core.csproj | Dodanie net48 + PolySharp |
| KSeF.Client.Tests.Core/GlobalUsings.netframework.cs | Global usings + compat (net48) |
| KSeF.Client.Tests.Core/config/TestConfig.cs | #nullable enable |
| KSeF.Client.Tests.Core/Utils/RateLimit/KsefApiLimits.cs | AsReadOnly polyfill (net48) |
| KSeF.Client.Tests.Core/Utils/RateLimit/KsefRateLimitWrapper.cs | #nullable enable |
| KSeF.Client.Tests.Core/Compatibility/CryptoCompat.cs | RSA ImportRSAPrivateKey polyfill (net48) |
| KSeF.Client.Tests.Core/UnitTests/SelfSignedCertificateBuilderTests.cs | Nowe testy regresji UtcNow |
| KSeF.Client.Tests.Core/UnitTests/TypeValueValidatorTests.cs | #nullable enable |
| KSeF.Client.Tests.Core/UnitTests/KsefNumberValidatorTests.cs | RNG polyfille (net48) |
| KSeF.Client.Tests.Core/UnitTests/X509CertificateLoaderExtensionsTests.cs | UtcNow + wykluczenie net48 |
| KSeF.Client.Tests.Core/E2E/QrCode/QrCodeE2ETests.cs | SHA256 polyfill (net48) |
| KSeF.Client.Tests.Core/E2E/QrCode/QrCodeOfflineE2ETests.cs | EC import polyfill (net48) |
| KSeF.Client.Tests.Core/E2E/QrCode/QrCodeOnlineE2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/TestData/TestDataE2ETests.cs | DateOnly fallback (net48) |
| KSeF.Client.Tests.Core/E2E/Invoice/InvoiceE2ETests.cs | DateTime.UtcNow zamiast Now |
| KSeF.Client.Tests.Core/E2E/BatchSession/BatchSessionE2ETests.cs | UtcNow w asercjach |
| KSeF.Client.Tests.Core/E2E/BatchSession/BatchSessionStreamE2ETests.cs | Stream.WriteAsync polyfill (net48) |
| KSeF.Client.Tests.Core/E2E/Authorization/Sessions/SessionE2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/Limits/RateLimitsE2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/KsefToken/KsefTokenE2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/OnlineSession/OnlineSessionE2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/Permissions/SubunitPermission/SubunitPermissionsE2ETests.cs | Enum.IsDefined fallback (net48) |
| KSeF.Client.Tests.Core/E2E/Permissions/EntityPermission/EntityPermissionE2ETests.cs | Wykluczenie net48 |
| KSeF.Client.Tests.Core/E2E/Permissions/EuRepresentativePermission/EuRepresentativePermissionE2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/Permissions/AuthorizationPermission/AuthorizationPermissions_ReceivedOwnerNip_Direct_E2ETests.cs | #nullable enable |
| KSeF.Client.Tests.Core/E2E/Peppol/PeppolPefE2ETests.cs | Wykluczenie net48 |
| KSeF.Client.Tests.Core/E2E/Invoice/IncrementalInvoiceRetrievalE2ETests.cs | Wykluczenie net48 |
| KSeF.Client.Tests.ClientFactory/KSeF.Client.Tests.ClientFactory.csproj | Dodanie net48 + PolySharp |
| KSeF.Client.Tests.ClientFactory/GlobalUsings.netframework.cs | Global usings (net48) |
| .idea/.idea.KSeF.Client/.idea/.gitignore | Rider/IDE ignore rules |
| .idea/.idea.KSeF.Client/.idea/.name | Rider project name |
| .idea/.idea.KSeF.Client/.idea/indexLayout.xml | Rider index layout |
| .idea/.idea.KSeF.Client/.idea/copilot.data.migration.ask2agent.xml | Rider/Copilot migration state |
| .idea/.idea.KSeF.Client/.idea/vcs.xml | Rider VCS mapping |
Files not reviewed (4)
- KSeF.Client.Tests/Features/Authenticate/Authenticate.feature.cs: Language not supported
- KSeF.Client.Tests/Features/Batch/Batch.feature.cs: Language not supported
- KSeF.Client.Tests/Features/Invoice/Invoice.feature.cs: Language not supported
- KSeF.Client.Tests/Features/QrCode/QrCode.Feature.cs: Language not supported
Comments suppressed due to low confidence (1)
KSeF.Client/Api/Services/CryptographyService.cs:535
GetRSAPublicPem/GetECDSAPublicPemtworząX509Certificate2bez Dispose oraz pobierają klucze (GetRSAPublicKey/GetECDsaPublicKey) bez zwalniania. Na Windows/.NET Framework może to zostawiać uchwyty do kontekstu certyfikatu i providerów. Rozwiązanie: opakować certyfikat i pobrany klucz wusingi zwracać tylko wynik PEM.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #if NETSTANDARD2_0 | ||
| bool isEncrypted = privateKeyPem.Contains("ENCRYPTED PRIVATE KEY"); | ||
| #else |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
W NETSTANDARD2_0 sprawdzenie privateKeyPem.Contains("ENCRYPTED PRIVATE KEY") jest case-sensitive, podczas gdy w pozostałych TFM jest OrdinalIgnoreCase. To zmienia zachowanie (np. dla nagłówków PEM w innym casing). Ponieważ w projekcie jest polyfill string.Contains(string, StringComparison) dla netstandard2.0, można zachować porównanie OrdinalIgnoreCase także w tej gałęzi.
| int status = BCryptOpenAlgorithmProvider(out IntPtr hAlg, "AES", null, 0); | ||
| ThrowIfFailed(status, "BCryptOpenAlgorithmProvider"); | ||
|
|
||
| try | ||
| { | ||
| // Ustaw tryb łańcuchowania na GCM | ||
| byte[] gcmMode = System.Text.Encoding.Unicode.GetBytes("ChainingModeGCM\0"); | ||
| status = BCryptSetProperty(hAlg, "ChainingMode", gcmMode, gcmMode.Length, 0); | ||
| ThrowIfFailed(status, "BCryptSetProperty(ChainingMode)"); | ||
|
|
||
| status = BCryptGenerateSymmetricKey(hAlg, out IntPtr hKey, IntPtr.Zero, 0, key, key.Length, 0); | ||
| ThrowIfFailed(status, "BCryptGenerateSymmetricKey"); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BCryptGenerateSymmetricKey jest wywoływany z pbKeyObject = IntPtr.Zero i cbKeyObject = 0. Zgodnie z kontraktem CNG dla symetrycznych kluczy należy najpierw odczytać BCRYPT_OBJECT_LENGTH, zaalokować bufor key object i przekazać go do BCryptGenerateSymmetricKey — w przeciwnym razie wywołanie może kończyć się błędem w runtime. Rozwiązanie: dodać BCryptGetProperty(BCRYPT_OBJECT_LENGTH) + alokację bufora + poprawne zwalnianie.
| private static MethodInfo? _rsaCopyMethod; | ||
| private static MethodInfo? _ecdsaCopyMethod; | ||
| private static bool _rsaResolved; | ||
| private static bool _ecdsaResolved; | ||
|
|
||
| /// <summary> | ||
| /// Tworzy nowy <see cref="X509Certificate2"/> łącząc certyfikat z kluczem prywatnym RSA. | ||
| /// Wywołuje <c>RSACertificateExtensions.CopyWithPrivateKey</c> w runtime przez refleksję. | ||
| /// </summary> | ||
| public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 cert, RSA rsa) | ||
| { | ||
| if (!_rsaResolved) | ||
| { | ||
| _rsaCopyMethod = ResolveMethod("RSACertificateExtensions", typeof(RSA)); | ||
| _rsaResolved = true; | ||
| } | ||
|
|
||
| if (_rsaCopyMethod != null) | ||
| { | ||
| return (X509Certificate2)_rsaCopyMethod.Invoke(null, new object[] { cert, rsa })!; | ||
| } | ||
|
|
||
| throw new PlatformNotSupportedException( | ||
| "CopyWithPrivateKey(RSA) nie jest dostępne na tej platformie. " + | ||
| "Wymagany jest .NET Framework 4.7.2+ lub .NET Core 2.0+."); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tworzy nowy <see cref="X509Certificate2"/> łącząc certyfikat z kluczem prywatnym ECDsa. | ||
| /// Wywołuje <c>ECDsaCertificateExtensions.CopyWithPrivateKey</c> w runtime przez refleksję. | ||
| /// </summary> | ||
| public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 cert, ECDsa ecdsa) | ||
| { | ||
| if (!_ecdsaResolved) | ||
| { | ||
| _ecdsaCopyMethod = ResolveMethod("ECDsaCertificateExtensions", typeof(ECDsa)); | ||
| _ecdsaResolved = true; | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache metod refleksji (_rsaCopyMethod/_ecdsaCopyMethod + flagi _rsaResolved/_ecdsaResolved) nie jest zabezpieczony współbieżnościowo. Równoległe wywołania mogą prowadzić do inicjalizacji w połowie lub wielokrotnego ResolveMethod. Rozwiązanie: zastosować Lazy<MethodInfo?> lub lock przy resolve.
| <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" /> | ||
| <PackageReference Include="Microsoft.Maui.Graphics" Version="8.0.100" /> | ||
| <PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="8.0.100" /> | ||
| <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.6" /> |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
W ItemGroup dla netstandard2.0 dodane są zależności Microsoft.Maui.Graphics/Microsoft.Maui.Graphics.Skia, ale kod w tym TFM jest skompilowany tak, że nie używa MAUI (#if !NETSTANDARD2_0 w QrCodeService). To niepotrzebnie zwiększa graf zależności i rozmiar paczki dla konsumentów .NET Framework. Rozwiązanie: dla netstandard2.0 zależeć bezpośrednio od SkiaSharp (managed + odpowiednie NativeAssets), a pakiety MAUI pozostawić tylko dla net8+ (gdzie są faktycznie używane).
| // iterations jest BigInteger, ale praktycznie mieści się w int | ||
| System.Numerics.BigInteger iterBig = pbkdf2Params.ReadInteger(); | ||
| iterations = (int)iterBig; | ||
|
|
||
| // Opcjonalna długość klucza | ||
| keyLength = 0; | ||
| if (pbkdf2Params.HasData) | ||
| { | ||
| Asn1Tag nextTag = pbkdf2Params.PeekTag(); | ||
| if (nextTag.TagValue == (int)UniversalTagNumber.Integer && nextTag.TagClass == TagClass.Universal) | ||
| { | ||
| System.Numerics.BigInteger keyLenBig = pbkdf2Params.ReadInteger(); | ||
| keyLength = (int)keyLenBig; | ||
| } | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
W ParsePbes2Parameters rzutujesz iterations i opcjonalne keyLength z BigInteger na int bez walidacji zakresu oraz bez sprawdzenia wartości <= 0. Przy złośliwym/zepsutym kluczu może to prowadzić do overflow, ujemnych iteracji albo prób alokacji ogromnych buforów (DoS). Rozwiązanie: zweryfikować iterations (np. > 0 i sensowny górny limit) oraz keyLength (np. 16/24/32 dla AES lub 24 dla 3DES, ewentualnie odrzucić inne) i rzucić CryptographicException przy wartościach spoza zakresu.
| #if NETSTANDARD2_0 | ||
| CryptoStream cryptoStream = new(output, encryptor, CryptoStreamMode.Write); | ||
| await input.CopyToAsync(cryptoStream, 81920, cancellationToken).ConfigureAwait(false); | ||
| cryptoStream.FlushFinalBlock(); | ||
| #else |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
W wersji asynchronicznej dla NETSTANDARD2_0 CryptoStream również nie jest Dispose’owany. To ten sam problem co w metodzie synchronicznej — warto zapewnić deterministyczne zwalnianie zasobów CryptoStream/ICryptoTransform bez zamykania output (np. przez wrapper strumienia).
| // NAPRAWA: DateTimeOffset.UtcNow zamiast .Now — spójność z NotBefore (UTC). | ||
| // Mieszanie .UtcNow (NotBefore) z .Now (NotAfter) powodowało zależność certyfikatu | ||
| // od strefy czasowej maszyny — różne offsety w jednym wywołaniu CreateSelfSigned. | ||
| X509Certificate2 certificate = new CertificateRequest(subjectDN, RSA.Create(2048), HashAlgorithmName.SHA256, RSASignaturePadding.Pss) |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disposable 'RSA' is created but not disposed.
| canvas.DrawBitmap(skBitmap, 0, 0); | ||
| canvas.DrawText(label, width / 2f, height + labelHeight - 2, textPaint); | ||
| #else | ||
| IImage qrImage = new SkiaImage(skBitmap); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disposable 'SkiaImage' is created but not disposed.
| { | ||
| // Próba użycia identyfikatora IANA (Linux, macOS, .NET 6+, Mono) | ||
| try { return TimeZoneInfo.FindSystemTimeZoneById("Europe/Warsaw"); } | ||
| catch (TimeZoneNotFoundException) { } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Poor error handling: empty catch block.
|
|
||
| // Fallback: identyfikator Windows (.NET Framework 4.8 na Windows) | ||
| try { return TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); } | ||
| catch (TimeZoneNotFoundException) { } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Poor error handling: empty catch block.
|
Thanks for the automated review. I've gone through each of the 13 comments (plus 1 suppressed) and analyzed them against the upstream Addressed in follow-up commitThese are valid observations. All four have been fixed:
Pre-existing in upstream
|
…y barriers, harden PKCS#8 parsing - Replace Microsoft.Maui.Graphics with direct SkiaSharp reference for netstandard2.0 (MAUI types unused in ns2.0 path) - Unify string.Contains to OrdinalIgnoreCase across all TFMs using existing polyfill - Add Volatile.Read/Write memory barriers in CertificateCompat and EcdhCompat reflection cache - Add range validation for PBKDF2 iterations (1–10M) and key length (0–256 bytes) in Pkcs8Decryptor
689541d to
bd56d76
Compare
|
Nice ale biorąc pod uwagę że to projekt rządowy, nie wiem czy ktokolwiek zainteresuje się tym PR... :( |
|
@Krzysztof318 ja mam wiarę, że ktoś jednak popatrzy. Wydaje mi się, że wycinanie prawie 20% rynku da się załatać (w firmach w PL). Liczę na konstruktywny feedback - i dołączenie PR do release-u. Oczywiście każdy może zabrać teraz te elementy i już budować software u siebie - ale migrując to do pełnego repo zyskujemy wsparcie kolejnych przyszłych zmian. To co najtrudniejsze wydaje się być zrobione - czyli kryptografia. Reszta zmian jaką będzie robić MF. -zakładam będzie czyniona w warstwie wyżej. |
…from raw ASN.1 On .NET Framework 4.8, CopyWithPrivateKey() + PFX reimport silently drops the AllowPlaintextExport policy from CNG key handles. This causes GetRSAPrivateKey().ExportParameters(true) to throw CryptographicException after the certificate is constructed. Fix: export RSA/ECDsa key parameters to PKCS#8 immediately after creation (while the CNG key is still exportable), then build the PKCS#12/PFX file manually using AsnWriter (KeyBag + CertBag structure per RFC 7292). Import with X509KeyStorageFlags.Exportable produces a certificate with a fully exportable private key. Also fixes ArgumentException in AsnWriter.WriteInteger for certificate serial numbers with redundant leading zero bytes (set bit 0 of first byte to guarantee non-zero). Test results after fix (identical on both frameworks): - KSeF.Client.Tests: net48=83/83, net10=83/83 - KSeF.Client.Tests.Core: net48=162/164, net10=178/180 (same CertificatesLimits API rate-limit failure on both) - KSeF.Client.Tests.ClientFactory: net48=9/9, net10=9/9
Poprawki po audycie kodu — hardening warstwy netstandard2.0Zmiany z audytu kodu + fix znaleziony przy dodatkowych testach. Wszystko kryptograficzne siedzi pod Co i dlaczego — po kolei. PlatformGuard
Dodany RSACng — handle leak + jawna export policyDwa tematy:
Ciche fallbacki w kryptografiiDwa miejsca, ten sam wzorzec: nieznana wartość → zwróć default i leć dalej.
Hardcoded P-256
RFC 4055 — brakujący NULL w RSA-PSS
Refleksja w EcdhCompat
HttpClient.Timeout = 5 minutDefault to 100s. System KSeF MF przy operacjach wsadowych (paczki do 100 MB) odpowiada dłużej. Bez tego batch upload/download timeout-uje na produkcji. Cross-TFM, celowo. Na przyszłość warto dodać CNG export policy — .NET Framework 4.8 gubi klucze prywatneZnalezione dopiero na prawdziwym Windowsie. Na net8+ ten problem nie istnieje. Stary flow: Fix: eksportuj klucz do PKCS#8 od razu (póki CNG key jest świeży), potem buduj PFX ręcznie z Przy okazji: Testy — odblokowanie + polyfilleWcześniej: plik testowy miał jedno API niedostępne na net48 = cały plik pod
Trick z extension methods: na net48 kompilator bierze polyfill (bo instance method nie istnieje), na net8+ preferuje wbudowaną. Ten sam kod, zero Wyniki testówIdentyczny wzorzec failures na obu frameworkach — jedyny "prawdziwy" failure to Kompatybilność — pełna macierzNuGet wybiera najbliższy kompatybilny TFM. Paczka oferuje
.NET 5–7 na Linux/macOS dostaną netstandard2.0 target i PlatformGuard je zablokuje. Te runtimes mają cross-platformowe crypto API, ale NuGet nie może im dać net8.0 (wyższa wersja). Wszystkie są EOL — kto potrzebuje Linuxa, powinien przejść na .NET 8+.
Dlaczego ten PR jest ważnyKSeF to system obowiązkowy dla wszystkich polskich podatników. Dane z telemetrii Microsoftu:
Biblioteka rządowa odcinała ~15–18% ekosystemu — w tym PR rozwiązuje issue #80, gdzie zespół odpowiedział że migracja na netstandard2.0 jest "impossible due to the cryptographic methods used in the implementation". Da się — wymaga ręcznej warstwy kompatybilności opartej na Windows CNG, ale działa. |
Pull Request: Dodanie obsługi .NET Standard 2.0 — pełna kompatybilność z .NET Framework 4.7.2+/4.8
Tytuł PR:
feat: Dodanie obsługi .NET Standard 2.0 — pełna kompatybilność z .NET Framework 4.7.2+/4.8Closes: #80
Podsumowanie
Ten Pull Request dodaje pełną obsługę
.NET Standard 2.0do bibliotekKSeF.ClientiKSeF.Client.ClientFactory, umożliwiając ich bezpośrednie użycie w aplikacjach opartych na .NET Framework 4.7.2+/4.8 — bez konieczności reimplementacji logiki kryptograficznej po stronie konsumenta.Zmiana jest w pełni addytywna — istniejące API publiczne pozostaje niezmienione, a wszystkie dotychczasowe testy przechodzą bez regresji na
net8.0,net9.0inet10.0.Kontekst i uzasadnienie biznesowe
Problem (issue #80)
W zgłoszeniu #80 użytkownik poprosił o obsługę
.NET Standard 2.0, aby móc zintegrować bibliotekę z istniejącym systemem opartym na.NET Framework 4.8. Odpowiedź zespołu brzmiała:W rezultacie użytkownik otrzymał jedynie dostęp do modeli danych (
KSeF.Client.Core), bez możliwości korzystania z pełnej funkcjonalności biblioteki — w szczególności z kluczowych operacji kryptograficznych (podpisy, szyfrowanie, generowanie certyfikatów).Dlaczego to istotne
KSeF to system obowiązkowy dla wszystkich polskich podatników. Biblioteka kliencka powinna być dostępna dla jak najszerszego grona użytkowników — w tym tych, których systemy działają na
.NET Framework 4.8.Dane z publicznej telemetrii narzędzi Microsoft (.NET) wskazują na następujący rozkład aktywności według Target Framework:
net8.0–net10.0.NET Frameworkw próbieObecna biblioteka pokrywa ~82% ekosystemu .NET. Dodanie
netstandard2.0poszerza to pokrycie o dodatkowe ~15–18%, obejmując.NET Framework 4.7.2+,.NET Core 2.0+,Mono 5.4+iXamarin.W języku biznesowym: biblioteka rządowa odcinała się od ~15–18% aktywności celów kompilacji widocznych w publicznej telemetrii — w tym od
.NET Framework, który nadal jest szeroko stosowany w polskich systemach korporacyjnych i administracji publicznej.Precedens w projekcie
Zespół projektu sam wyodrębnił
KSeF.Client.Corejako celnetstandard2.0— potwierdzając zasadność wieloplatformowego podejścia. Niniejszy PR rozszerza tę strategię na pełną bibliotekę.Kompatybilność wersji — .NET Standard 2.0 a .NET Framework
Czym jest .NET Standard 2.0
.NET Standard 2.0to specyfikacja API (kontrakt), a nie konkretne środowisko uruchomieniowe. Biblioteka skompilowana podnetstandard2.0może być używana przez dowolne środowisko implementujące ten kontrakt. Oficjalna macierz kompatybilności Microsoftu:Źródło: .NET Standard — Microsoft Learn
Dlaczego minimum to 4.7.2, a nie 4.6.1
Choć
netstandard2.0formalnie obsługuje.NET Framework 4.6.1, w praktyce istnieją dwa ograniczenia:1. Rekomendacja Microsoftu — problemy z bindingiem na < 4.7.2
Microsoft oficjalnie rekomenduje .NET Framework 4.7.2+ do konsumowania pakietów
netstandard2.0. Wersje 4.6.1–4.7.1 wymagają dodatkowych przekierowań bindingów (binding redirects), a narzędzia (MSBuild, ClickOnce, test runnery) mają udokumentowane problemy z identyfikacją assembly.2. Wymagania runtime naszej warstwy kryptograficznej
Warstwa kompatybilności
KSeF.Clientwykorzystuje API kryptograficzne, które pojawiły się w kolejnych wersjach.NET Framework:RSACng(OAEP-SHA256)CryptoCompat.Rsa.cs,CsrCompat.csECDsa.Create(ECCurve)SelfSignedCertificateCompat.cs,CsrCompat.csECDiffieHellman.Create(ECCurve)EcdhCompat.csECParameters/ImportParametersEcdhCompat.csRSACertificateExtensions.CopyWithPrivateKeyCertificateCompat.csECDsaCertificateExtensions.CopyWithPrivateKeyCertificateCompat.csbcrypt.dll)AesGcmCompat.csWąskie gardło:
.NET Framework 4.7.2— wymuszone przezCopyWithPrivateKey, które jest niezbędne do łączenia certyfikatów X.509 z kluczami prywatnymi (operacja kluczowa przy budowaniu certyfikatów self-signed i obsłudze sesji KSeF).Podsumowanie kompatybilności
Dlaczego testy celują w
net48Projekty testowe nie mogą targetować
netstandard2.0bezpośrednio (test runner wymaga konkretnego runtime). Wybranonet48ponieważ:.NET Framework.NET Frameworkw środowiskach produkcyjnychMicrosoft.NETFramework.ReferenceAssemblies.net48umożliwia kompilację krzyżową na macOS/LinuxCo zostało zrobione
Podsumowanie ilościowe
.csproj,Directory.Build.props)Compatibility/)#ifbloki).csproj, polyfille)README.md,nuget-package.md)1. Infrastruktura buildów
Pliki
.csprojKSeF.Client.csprojiKSeF.Client.ClientFactory.csproj— dodanonetstandard2.0do<TargetFrameworks>:ImplicitUsingsiNullableustawione warunkowo (niedostępne nanetstandard2.0):4 projekty testowe — dodano
net48oboknet8.0;net9.0;net10.0w celu weryfikacji kompatybilności z.NET Framework 4.8.Directory.Build.props
Dodano
<LangVersion>12</LangVersion>globalnie — umożliwia użycie C# 12 (file-scoped namespaces, required members) na wszystkich TFM-ach.Nowe zależności NuGet (warunkowe, tylko dla
netstandard2.0)PolySharp[NotNullWhen],[CallerArgumentExpression], itp.)Microsoft.Bcl.Cryptographynetstandard2.0System.MemoryMicrosoft.Bcl.Cryptography(wersja krytyczna — 4.6.0 nie wystarcza)System.Formats.Asn1System.Text.JsonSystem.Security.Cryptography.XmlSystem.Security.Cryptography.CngSystem.Security.Cryptography.PkcsMicrosoft.Extensions.*(Hosting, DI, Http, Config, Localization)SkiaSharpSystem.Memory.DataBinaryDatai powiązane typyUwaga: Wszystkie powyższe pakiety dodane są w warunkowym
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">— nie wpływają na istniejące TFM-ynet8.0/net9.0/net10.0.2. Warstwa kompatybilności (
KSeF.Client/Compatibility/)Serce migracji — 15 nowych plików implementujących polyfille i warstwy kompatybilności dla API niedostępnych na
netstandard2.0. Wszystkie pliki są kompilowane warunkowo (#if NETSTANDARD2_0) z wyjątkiemGuardClauses.cs, który działa na wszystkich TFM-ach.Kryptografia — rozwiązania kluczowych wyzwań
AesGcmCompat.csAesGcmniedostępna nanetstandard2.0EcdhCompat.csECDiffieHellmannieobecna w kontrakcienetstandard2.0ECDiffieHellmanCng(obecna w runtime .NET FW 4.7+)CryptoCompat.Rsa.csCryptoCompat.Ecdsa.csCryptoCompat.Pem.csPemEncoding.WriteiX509Certificate2.CreateFromPemniedostępnePemHelper— dekodowanie/kodowanie PEM, tworzenie certyfikatów z PEM. Uwaga: polyfill samej klasyPemEncodingniemożliwy z powodu konfliktu z typem wewnętrznym wMicrosoft.Bcl.CryptographyPkcs8Decryptor.csRfc2898DeriveBytesnanetstandard2.0obsługuje tylko HMAC-SHA1CertificateCompat.csX509Certificate2.CopyWithPrivateKey()— nanetstandard2.0dostępny tylko overload dlaMLKemRSACertificateExtensions/ECDsaCertificateExtensionsSelfSignedCertificateCompat.csCertificateRequest.CreateSelfSigned()niedostępneCsrCompat.csCertificateRequest.CreateSigningRequest()niedostępnePolyfille ogólne
GuardClauses.csGuarddziałająca na wszystkich TFM-ach. Nanetstandard2.0: pełna implementacjaThrowIfNull,ThrowIfNullOrWhiteSpace,ThrowIfNullOrEmpty. Nanet8.0+:[MethodImpl(AggressiveInlining)]delegujące do wbudowanych metod. Eliminuje potrzebę ~244 indywidualnych bloków#ifw kodzieHashCompat.csSHA256.HashData()— używaSHA256.Create().ComputeHash()RandomCompat.csRandom.Shared(.NET 6+) —[ThreadStatic]dla bezpieczeństwa wątkowegoCompositeFormatCompat.csSystem.Text.CompositeFormat(.NET 8+)StringCompat.csstringniedostępnych nanetstandard2.0CryptoPolyfills.csDSASignatureFormatKluczowa zasada projektowa: Żadne z powyższych rozwiązań nie jest obejściem ani hackiem. Wszystkie wykorzystują te same prymitywy kryptograficzne Windows CNG, których wewnętrznie używa .NET 8+. Różnica polega wyłącznie na powierzchni API — warstwa kompatybilności wypełnia tę lukę.
3. Adaptacja kodu produkcyjnego
13 plików produkcyjnych otrzymało bloki
#if NETSTANDARD2_0(łącznie 39 bloków warunkowych) w miejscach, gdzie API różni się między TFM-ami. Zmiany pogrupowane tematycznie:Usługi kryptograficzne (
Api/Services/)CryptographyService.cs— najobszerniejsze zmiany (~95 linii dodanych):CryptoStream— nanetstandard2.0brak parametruleaveOpenw konstruktorzeFlushFinalBlockAsync→ synchronicznyFlushFinalBlock(niedostępne asynchronicznie)ReadAsync(Memory<byte>)→ReadAsync(byte[], int, int)RSA.Create(2048)→new RSACng(2048)(dla obsługi RSA-PSS)CsrCompat.CreateSigningRequestRsa/EcdsaHashCompat.SHA256HashDataRsaCompat.CreateFromPemWithOaepSupport(RSACryptoServiceProvider nie obsługuje OAEP-SHA256)EcdhCompat+AesGcmCompatPemHelper.CreateCertificateFromPem/PemHelper.EncodePemRandomCompat.SharedQrCodeService.cs— nanetstandard2.0bezpośrednie użycie SkiaSharp (SKCanvas.DrawRect,DrawBitmap,DrawText) zamiast abstrakcjiMicrosoft.Maui.Graphics/SkiaCanvasSignatureService.cs—HashCompat.SHA256HashDatadla digestu certyfikatu XAdESVerificationLinkService.cs—HashCompat.SHA256HashData+ecdsa.SignHash(sha)bez parametru formatu (niedostępny na ns2.0)Builderzy certyfikatów (
Api/Builders/)SelfSignedCertificateForSealBuilder.csiSelfSignedCertificateForSignatureBuilder.cs:#if NETSTANDARD2_0używająceSelfSignedCertificateCompatdo budowania certyfikatów self-signedDateTimeOffset.Now→DateTimeOffset.UtcNow— eliminacja niespójności stref czasowych międzyNotBefore(UTC) aNotAfter(czas lokalny)Warstwa HTTP (
Http/)RestClient.cs:ReadAsStringAsync(cancellationToken)→ReadAsStringAsync()(overload z CT niedostępny na ns2.0, 5 wystąpień)ReadAsStreamAsync(cancellationToken)→ReadAsStreamAsync()(2 wystąpienia)HttpStatusCode.TooManyRequests→(HttpStatusCode)429(wartość enuma niezdefiniowana na ns2.0)JsonUtil.cs— alternatywny konstruktorStreamReaderistring.Concatdla ns2.0Walidacja i wyrażenia regularne (
Validators/)RegexPatterns.cs— wydzielenie wzorców regex do pólconst string(DRY) + nanetstandard2.0użycienew Regex(pattern, RegexOptions.Compiled)zamiast[GeneratedRegex](source generator niedostępny). 16 metod z dualną implementacją.Rozszerzenia
X509CertificateLoaderExtensions.cs:EphemeralKeySet→Exportable(flaga niedostępna na ns2.0)string.Contains(string, StringComparison)— zunifikowane z polyfillStringCompat.Contains(jedna ścieżka kodu dla wszystkich TFM)MergeWithPemKeyNoProfileForEcdsawykluczona na ns2.0 (#if !NETSTANDARD2_0)Ecdsa256SignatureDescription.cs—[RequiresUnreferencedCode]wykluczone na ns2.04. Unifikacja Guard clauses
~30 plików, ~100+ miejsc wywołań — wszystkie
ArgumentNullException.ThrowIfNull()iArgumentException.ThrowIfNullOrWhiteSpace()zastąpione uniwersalną klasąGuard:Klasa
Guardnanet8.0+deleguje do wbudowanych metod z[MethodImpl(AggressiveInlining)]— zero narzutu wydajnościowego na nowoczesnych TFM-ach. Nanetstandard2.0zapewnia pełną implementację z zachowaniem semantyki[CallerArgumentExpression](dzięki PolySharp).Uzasadnienie: Bez tej unifikacji konieczne byłoby dodanie ~244 indywidualnych bloków
#ifw całym kodzie — co drastycznie pogorszyłoby czytelność.5. Zmiany w testach
4 projekty testowe rozszerzone o TFM
net48:Polyfille testowe
Każdy projekt testowy otrzymał katalog
Compatibility/z polyfillami specyficznymi dla kodu testowego:SHA256.HashData()→SHA256.Create().ComputeHash()RandomNumberGenerator.Fill()→RNG.Create().GetBytes()string.StartsWith(char)→StartsWith(string)DateOnly→DateTime.Parse().DateDictionary.AsReadOnly()→new ReadOnlyDictionary<>()Enum.IsDefined<T>(value)→Enum.IsDefined(typeof(T), value)Stream.WriteAsync(ReadOnlyMemory)→WriteAsync(byte[], int, int)Nowe testy
SelfSignedCertificateBuilderTests.csQrCodeOfflineE2ETests.csAdaptacja istniejących testów
#nullable enable,#if !NETFRAMEWORKguards, alternatywne API)net48(np.ExportPkcs8PrivateKey,DistinctBy) są warunkowo wykluczane przez#if !NETFRAMEWORKKsefDateTimeHelper.cs(obsługa stref czasowych Warszawa),MiscellaneousUtils.cs6. Dokumentacja
README.md— zaktualizowane informacje o platformach:nuget-package.md— dodane informacje o TFM-ach w opisach pakietów.7. Lokalizacja
Wszystkie nowe pliki oraz wybrane istniejące pliki zostały zlokalizowane do języka polskiego:
Zgodnie z konwencją projektu, w którym
CONTRIBUTING.md,README.mdi cała dokumentacja utrzymywana jest w języku polskim.8. Naprawione błędy
DateTimeOffset.Now→DateTimeOffset.UtcNoww builderach certyfikatówPliki:
SelfSignedCertificateForSealBuilder.cs,SelfSignedCertificateForSignatureBuilder.csProblem: Oryginalne builderzy używały
DateTimeOffset.Nowdo ustawianiaNotBeforeiNotAftercertyfikatu. Powodowało to niespójność:CertificateRequestwewnętrznie normalizujeNotBeforedo UTC, aleNotAfterzDateTimeOffset.Nowzawierał offset lokalnej strefy czasowej — potencjalnie generując certyfikat zNotAfterprzesuniętym o offset strefy.Naprawa: Zmiana na
DateTimeOffset.UtcNowna wszystkich TFM-ach (nie tylkonetstandard2.0).Zgodność wsteczna
✅ Zero zmian w publicznym API
GuardzastępujeArgumentNullException.ThrowIfNull/ArgumentException.ThrowIfNullOrWhiteSpacez identyczną semantyką — typy wyjątków, komunikaty i zachowanie pozostają takie same✅ Wszystkie istniejące testy przechodzą
Na wszystkich dotychczasowych TFM-ach (
net8.0,net9.0,net10.0) — zero regresji.✅ Struktura pakietów NuGet zachowana
Istniejące pakiety NuGet rozszerzone o nowy TFM bez zmian w strukturze:
Weryfikacja
Build — zero błędów na wszystkich TFM-ach
Wynik: ✅ 0 błędów dla
netstandard2.0,net8.0,net9.0,net10.0Testy jednostkowe
net8.0net9.0net10.0net48Testy E2E
Testy E2E na
net48wymagają środowiska Windows z .NET Framework 4.8 oraz dostępu do API KSeF — weryfikacja w środowisku CI po stronie zespołu projektu.Weryfikacja pakietu NuGet
Wynik: ✅ Poprawne pakiety
.nupkgz bibliotekami dlanetstandard2.0+net8.0+net9.0+net10.0(wraz z zasobamipl/).Znane ograniczenia
netstandard2.0formalnie wspiera .NET FW 4.6.1+, nasza biblioteka wymaga 4.7.2+ z powoduCopyWithPrivateKey(łączenie certyfikatów z kluczami prywatnymi). Na 4.6.1–4.7.1 biblioteka się skompiluje, ale operacje certyfikatowe zgłosząPlatformNotSupportedExceptionw runtime.netstandard2.0korzystają z Windows CNG (P/Invoke, refleksja do*Cngklas)net8.0+EphemeralKeySetniedostępnenetstandard2.0certyfikaty używają flagiExportablezamiastEphemeralKeySetMergeWithPemKeyNoProfileForEcdsawykluczonaPkcs8PrivateKeyInfoniedostępna na ns2.0Przewodnik po przeglądzie kodu
Ze względu na rozmiar PR, proponujemy następującą kolejność przeglądu:
Faza 1: Infrastruktura (7 plików)
Zrozumienie zakresu zmian buildowych:
Directory.Build.props—LangVersion=12KSeF.Client/KSeF.Client.csproj— nowy TFM + zależności warunkoweKSeF.Client.ClientFactory/KSeF.Client.ClientFactory.csproj.csprojtestoweFaza 2: Warstwa kompatybilności (15 nowych plików)
Samodzielne, nowe pliki — nie modyfikują istniejącego kodu:
GuardClauses.cs— fundament, najważniejszy do zrozumieniaCryptoCompat.*.cs,AesGcmCompat.cs,EcdhCompat.cs,Pkcs8Decryptor.cs)SelfSignedCertificateCompat.cs,CsrCompat.cs,CertificateCompat.cs)HashCompat.cs,RandomCompat.cs,CompositeFormatCompat.cs,StringCompat.cs,CryptoPolyfills.cs)Faza 3: Adaptacja produkcyjna (13 plików)
Bloki
#if NETSTANDARD2_0w istniejącym kodzie:CryptographyService.cs— najbardziej rozbudowane zmianyQrCodeService.cs,SignatureService.cs,VerificationLinkService.csUtcNow)RestClient.cs,JsonUtil.csRegexPatterns.cs,X509CertificateLoaderExtensions.csFaza 4: Guard clauses (~30 plików)
Mechaniczna zamiana —
ArgumentNullException.ThrowIfNull→Guard.ThrowIfNull. Weryfikowalna grepem.Faza 5: Testy (~65 plików)
Lustrzane odbicie zmian produkcyjnych — te same wzorce, te same polyfille.
Faza 6: Dokumentacja i lokalizacja
README.md,nuget-package.md, komentarze w języku polskim.Faza 7: Poprawki po review Copilot (commit
baf9a5b)Zmiany wynikające z analizy automatycznego review Copilot na PR:
KSeF.Client.csprojMicrosoft.Maui.Graphics+Microsoft.Maui.Graphics.Skiaznetstandard2.0— zastąpiono bezpośrednią referencją doSkiaSharp 2.88.6. Ścieżka#if NETSTANDARD2_0wQrCodeServiceużywa wyłącznie typów SkiaSharp.ContainszOrdinalIgnoreCaseX509CertificateLoaderExtensions.cs#if/#else— polyfillStringCompat.Containszapewnia tę samą sygnaturę na ns2.0, więc oba TFM-y dzielą jedną ścieżkę kodu.CertificateCompat.cs,EcdhCompat.csVolatile.Read/Volatile.Writeprzy odczycie/zapisie flag cache refleksji — poprawność na architekturach ze słabym modelem pamięci (ARM).Pkcs8Decryptor.cs[1, 10 000 000]) i długości klucza ([0, 256]bajtów) przed rzutowaniem naint.Podsumowanie
Ten PR realizuje cel, który w issue #80 został uznany za niemożliwy do osiągnięcia — pełna obsługa
.NET Standard 2.0obejmująca wszystkie ścieżki kryptograficzne: RSA, ECDsa, ECDH+AES-GCM, certyfikaty self-signed, CSR, kody QR, XAdES.Zmiany są:
net8.0/net9.0/net10.0Dzięki temu ~15–18% ekosystemu .NET, dotychczas wykluczonych z korzystania z oficjalnej biblioteki KSeF, zyskuje pełny dostęp do jej funkcjonalności.