feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231][STUD-80425]#569
feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231][STUD-80425]#569alexandru-petre wants to merge 17 commits into
Conversation
…64429]
Closes STUD-64429. The previous symmetric activities produced ciphertext
in a UiPath-specific format with no opt-in interop for openssl, Java,
Python, ServiceNow, etc. The coded-workflow ICryptographyService surface
also had a 51-method shape dominated by per-key-form overload sprawl.
Symmetric wire-format interop
- New SymmetricWireFormat enum (Classic, Owasp2026, Raw, OpenSslEnc) and
KeyBytesFormat enum (Encoded, Hex, Base64) in UiPath.Cryptography.
- Four new properties on Encrypt/Decrypt Text/File: Format, KeyFormat,
Iv (Encrypt only), KdfIterations. Classic is the default and
byte-stable with prior releases - existing workflows produce
identical output.
- Owasp2026: Classic layout with OWASP-recommended PBKDF2-HMAC-SHA1
iterations (1,300,000 default, caller-overridable).
- Raw: caller-supplied key + IV, no KDF - third-party interop.
- OpenSslEnc: Salted__ magic + PBKDF2-HMAC-SHA256 @ 600,000 iter
(default) to interop with openssl enc -pbkdf2 -md sha256 -salt.
- CryptographyHelper extensions: EncryptDataWithIterations,
EncryptDataRaw, EncryptDataOpenSslEnc, GetRecommendedIterations,
ParseKeyBytes, GetRawKeySizes. Existing public EncryptData /
DecryptData remain bit-stable.
- New SymmetricInteropHelper in the core library (validation +
dispatch). Six runtime invariants enforced (raw key/IV/length,
KDF iterations bounds, format/keyformat compatibility).
- ViewModel UX: Format dropdown always visible (non-PGP); KeyFormat /
Iv / KdfIterations auto-show/hide based on Format. KdfIterations
auto-fills the format's OWASP default. Label swaps Pbkdf2-Sha1 /
Pbkdf2-Sha256 per format.
Nonce-reuse safety on Raw IV
- EncryptText / EncryptFile emit a design-time warning when the Iv
property is bound. Nonce reuse with the same key is catastrophic
in Raw mode: CTR-keystream collision breaks confidentiality on every
algorithm, and under AEAD (AES-GCM, ChaCha20-Poly1305) a single
collision additionally lets an attacker recover the authentication
subkey H from the GHASH polynomial and forge arbitrary messages
under that key forever (Joux 2006, "Forbidden Attack"). New resx
key Iv_NonceReuseWarning routed through the existing isWarning:true
CacheMetadata pattern.
- SymmetricEncryptOptions.Raw(iv) carries the same warning in XML doc
so IntelliSense surfaces it at coded-workflow author time.
- docs/symmetric-wire-format.md Raw section documents both failure
modes and recommends leaving Iv empty for cipher-generated random
IVs.
ICryptographyService redesign (51 -> 30 methods)
- New API models under UiPath.Cryptography.Activities.API/Models/:
CryptoKey (factories: FromPassword string / SecureString, FromRawBytes,
FromHexString, FromBase64String) - collapses the three key-form
overloads at the type level.
- SymmetricEncryptOptions / SymmetricDecryptOptions with factory
methods (Classic, Owasp2026, Raw, OpenSslEnc); properties are
{ get; private init; } so the factories are the only construction
path - invalid Format/KdfIterations/Iv combinations are unreachable
from valid C#.
- PgpPublicKey / PgpPrivateKey / PgpKeyPair handles. Sign / verify
implied by passing a private / public key handle to encrypt / decrypt
(no separate bool flags). PgpPrivateKey is IDisposable; passphrase
stored as SecureString and materialised to a managed string only
just-in-time inside Open() - heap residency is bounded by the
operation rather than the lifetime of the handle.
- PgpGenerateKeys returns a PgpKeyPair in memory (writes / reads /
deletes temp files internally to satisfy the underlying file-only
PgpCore API).
PgpClearsignFile -> PgpClearSignFile
- C# class, ViewModel, resx keys (English + 13 locales), Designer.cs,
ActivitiesMetadata.json, packaging docs, and all internal references
renamed. The activity has not reached customers, so no back-compat
shim is needed.
- User-facing display strings (incl. the PgpVerify mode label
"ClearSigned File (Text)" and the "ClearSigned file" property)
re-cased for consistency with the new identifier.
Tests
- New SymmetricInteropTests at the activity layer: per-format
round-trips, backward-compat pin (Classic blob produced by current
code decrypts to the known plaintext), cross-format wire compat
(Owasp2026 with explicit 10,000 iter decrypts as Classic and
vice-versa), cross-tool interop with .NET's Aes / AesGcm primitives,
validation matrix, Obsolete-algorithm round-trip on Raw.
- CryptographyServiceTests rewritten for the new surface: per-key-form
theories collapsed into parameterized CryptoKey factory tests;
Format coverage across {Classic, Owasp2026, Raw, OpenSslEnc};
cross-format wire compat; KDF-iteration mismatch (AEAD makes the
failure deterministic via tag check); AEAD ciphertext + tag tamper
with explicit wire-layout length pin (salt + IV + ct + tag);
round-trip pin (cipher != plain + cipher1 != cipher2 - kills the
"no-op encrypt passes every test" loophole); explicit-IV
verification against the wire prefix.
Docs
- docs/symmetric-wire-format.md rewritten with per-format byte layouts,
"Choose when" guidance, Python reference encoders / decoders,
openssl interop commands, validation matrix.
- coded-api.md rewritten for the new 30-method surface with
CryptoKey / Options / Pgp{Public,Private}Key factory tables,
SymmetricWireFormat reference, and updated examples.
- Per-activity docs (Encrypt/Decrypt Text/File) document the new
Format / KeyFormat / Iv / KdfIterations properties + XAML examples
for Raw and OpenSslEnc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oOptions [STUD-64429] Follow-on cleanup to the symmetric-interop redesign (8370532). Closes two open design smells exposed by reviewing the previous shape: 1. CryptoKey was a tagged union with five From* factories that gave no naming signal about which were password-mode (PBKDF2-stretched) vs raw-key-mode. The IsRawKey flag was the symptom. 2. The (key kind x wire format) compatibility check was runtime-only. CryptoKey hierarchy - CryptoKey becomes an abstract base; each subclass owns its KeyBytes storage strategy. - PasswordKey: password material (PBKDF2-stretched). Factories FromPassword(string, Encoding) / FromPassword(SecureString, Encoding). Stores the password internally as a SecureString (the string factory copies characters into one); cipher-key bytes materialise just-in-time on every KeyBytes access via Marshal.SecureStringToGlobalAllocUnicode + zero-on-finally for the unmanaged buffer and intermediate char[]. Heap residency of plain password bytes is bounded by the operation, not the lifetime of the handle. PasswordKey is IDisposable; Dispose() zeros the SecureString, nulls the field, and KeyBytes throws ObjectDisposedException after disposal. - RawKey: literal cipher key (no KDF). Factories FromBytes / FromHex / FromBase64 (FromHexString / FromBase64String dropped — the class name carries the category). IDisposable; Dispose() zeros the stored bytes in place and nulls the field; KeyBytes throws ObjectDisposedException after disposal to prevent silent-wrong-key encryption with an all-zero buffer. CryptoOptions hierarchy - New abstract CryptoOptions base holding Key, Format, KdfIterations. - SymmetricEncryptOptions / SymmetricDecryptOptions derive from it and fold the key into the options object — the format factories accept either a PasswordKey or a RawKey, so mismatched pairings fail at compile time. Example: SymmetricEncryptOptions.Classic(PasswordKey) vs SymmetricEncryptOptions.Raw(RawKey, byte[] iv = null) — the type system rules out Classic(rawKey) / Raw(passwordKey) etc. - All init setters are private protected, so the factories are the only construction path; with statements cannot bypass the type constraint. Service signatures - The six symmetric methods drop the standalone CryptoKey key parameter (key now lives on options) and become 3-param: EncryptBytes(input, algorithm, SymmetricEncryptOptions options) Options is non-nullable; the previous implicit-Classic-default path goes away — caller writes SymmetricEncryptOptions.Classic(key) for the default explicitly. - Keyed-hash methods keep their CryptoKey parameter — option (a) from the design discussion. No wire-format axis on keyed hash, no options object needed; the asymmetry with symmetric methods is justified. - Validate{Encrypt,Decrypt} consolidate into a single ValidateSymmetric(algorithm, options, iv) — defence in depth behind the compile-time check. Other internal cleanups - AsKeyBytesFormat() method renamed to BytesFormat property: the As* prefix incorrectly signalled coercion of the receiver, and the value is a per-subtype constant (idiomatic as a property in C#). - The two now-unreachable runtime validation tests (Encrypt_RawFormat_WithPasswordKey_Throws, Encrypt_NonRawFormat_WithRawKey_Throws) dropped — the bad states cannot be constructed in valid C#. Comment in the test file flags this for reviewers. - CryptographyHelper: drop an unused using + a redundant local MinKdfIterations const (the floor is enforced via SymmetricInteropHelper.MinKdfIterations). XML doc coverage - All 30 ICryptographyService methods now have summaries (most had none after the previous redesign). - SymmetricWireFormat and KeyBytesFormat enums get per-value summaries covering byte layout, KDF parameters, interop guarantees, and the nonce-reuse warning on Raw. - PasswordKey / RawKey factories get full XML docs (params, exceptions, lifecycle semantics). Documentation - coded-api.md: Key material section rewritten to describe PasswordKey + RawKey (separate factory tables + disposal semantics); symmetric wire-format table updated with the typed factory signatures; common patterns updated to use PasswordKey.FromPassword / RawKey.From* and the new SymmetricEncryptOptions.<Format>(key, ...) shape. Tests - CryptographyServiceTests rewritten for the new shape — all 77 API tests pass. Activity-layer suite (622 tests) untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ycle, and wire-format stability [STUD-64429] Adds 13 new test files (~150 tests) covering gaps identified for the symmetric wire-format interop and CryptoKey hierarchy work on this branch: - Model lifecycle: PasswordKey + RawKey disposal, defensive copying, hex/base64 parsing tolerance. - Helper-direct: SymmetricInteropHelper validation matrix, dispatch routing, ParseKeyOrIv branches; CryptographyHelper byte-layout assertions for Raw / OpenSslEnc / Owasp2026. - Activity branches: EncryptText/DecryptText SecureString + Shift-JIS paths, error-hint substrings, ContinueOnError swallowing. - File-form interop: EncryptFile/DecryptFile across all four wire formats (Classic, Owasp2026, Raw, OpenSslEnc) with new args. - PGP: PgpPrivateKey FromFilePath round-trip, wrong-passphrase translation, overwrite semantics; PgpClearSignFile rename + async base type pinning. - Wire-format stability fixtures: hex-literal blobs for Classic, Owasp2026 @ 1.3M iter, OpenSslEnc, and Raw AEAD; each decrypts to known plaintext to guard against silent layout drift. - External-tool interop (bidirectional): in-process BCL counterparts for Classic/OpenSslEnc/Raw (no external deps, runs every CI); gated OpenSSL CLI tests (real `openssl enc`); gated GPG CLI tests (real `gpg --encrypt`/--verify, isolated --homedir, MSYS path probe). - Activity CacheMetadata warnings: FIPS, IV nonce-reuse. Full Cryptography pack: 849 passed / 0 failed. Follow-up: STUD-80390 tracks the AES-key-size product gap (OpenSslEnc currently always derives AES-256; aes-128-cbc / aes-192-cbc interop needs a configurable knob). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e KDF iterations [STUD-64429] P2 — PasswordKey heap residency. `PasswordKey.KeyBytes` returns a freshly-allocated `byte[]` per call (the SecureString is materialised through Marshal.SecureStringToGlobalAllocUnicode and copied to a fresh managed buffer). `CryptographyService` passed those bytes straight into the dispatcher and never zeroed them, leaving password material on the heap until GC — which defeats the point of using SecureString. Add `internal virtual void ReleaseMaterialisedBytes(byte[])` on CryptoKey (default no-op so RawKey's instance-owned storage is not corrupted) with PasswordKey overriding to Array.Clear. Wrap each `key.KeyBytes` access in CryptographyService (EncryptBytes, DecryptBytes, KeyedHashBytes/Text/File) in a try/finally so the password bytes are zeroed even when the inner operation throws. P3 — negative KDF iterations. `ValidateInteropSettings` rejected positive iterations below the floor (< 1000) but silently accepted negatives; `Dispatch*` then treated any `kdfIterations <= 0` as "use the recommended default". A caller passing `Owasp2026(key, -1)` would silently run at 1,300,000 iter instead of throwing. Tighten the check so anything `< 0` also throws; only `0` means "default". Same-pattern issue at the activity layer (`ParseKeyOrIv → DispatchEncrypt` in EncryptText/DecryptText/EncryptFile/DecryptFile) is acknowledged but out of scope here; raise as a follow-up if needed. New / extended tests: - PasswordKeyTests.ReleaseMaterialisedBytes_ZeroesTheBuffer - PasswordKeyTests.ReleaseMaterialisedBytes_NullOrEmpty_NoThrow - PasswordKeyTests.Service_ClearsPerCall_KeyInstanceSurvives_ManyOperations - RawKeyTests.ReleaseMaterialisedBytes_IsNoOp_DoesNotCorruptInstance - SymmetricInteropHelperTests.Validate_KdfIterations_AtFloor +2 rows (-1, int.MinValue → throw) Full Cryptography pack: 856 passed / 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-64429] Mirrors the P2 service-layer fix in 7d9ac6c for the activity execute path. EncryptText / DecryptText / EncryptFile / DecryptFile materialise key bytes via `SymmetricInteropHelper.ParseKeyOrIv` (returns a fresh `byte[]` in all branches — `encoding.GetBytes`, `Convert.FromBase64String`, or `FromHexString`), then pass them into the dispatcher and let them go out of scope. The secret material — a low-entropy password derived to bytes, or a literal raw cipher key — lingered on the managed heap until GC. Add a new `SymmetricInteropHelper.ClearKeyBytes(byte[])` helper (zero in-place; safe on null/empty) and wrap each Dispatch call site in a try/finally that calls it. Existing catch blocks for CryptographicException in DecryptText / DecryptFile are preserved by adding finally alongside. IV bytes are intentionally not cleared — IVs are not secret (they're written into the ciphertext stream). Tests: - SymmetricInteropHelperTests.ClearKeyBytes_ZeroesTheBuffer - SymmetricInteropHelperTests.ClearKeyBytes_NullOrEmpty_NoThrow Full Cryptography pack: 858 passed / 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TUD-64429] Aligns docs/symmetric-wire-format.md with the P3 validation fix from 7d9ac6c: ValidateInteropSettings rejects KdfIterations < 0 as well as 0 < KdfIterations < 1000. Only exactly 0 is accepted as the "use the format's shipped value" sentinel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…repo convention [STUD-64429] Localization for other cultures is handled by a separate localization process — code changes only touch the English (.resx) file. Reverting the 12 localized files (de, es, es-MX, fr, ja, ko, pt, pt-BR, ro, tr, zh-CN, zh-TW) to their origin/develop state so this PR doesn't conflict with the localization workflow. The English UiPath.Cryptography.Activities.resx and the inner-library UiPath.Cryptography.resx are intentionally retained — they carry the new resource keys the code references. Build verified: dotnet build Activities/Activities.Cryptography.sln succeeds with no new warnings; missing localized keys fall back to the English values at runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four tests relied on implicit "doesn't throw" semantics: - ExternalInteropGpgCliTests.UiPathSigns_GpgVerifies - ExternalInteropGpgCliTests.UiPathClearSigns_GpgVerifies - PasswordKeyTests.Dispose_Idempotent - RawKeyTests.Dispose_Idempotent The GPG tests relied on GpgCli.Run throwing on non-zero gpg exit; the Dispose tests relied on a bare second `key.Dispose()` not throwing. Behaviour was correct but the assertion was invisible to a reader, and fragile: if GpgCli.Run ever changed to log-and-continue, the positive tests would silently pass while verifying nothing. Wrap each implicit-no-throw call in Should.NotThrow(). For the two GPG-verify tests, also add a tampered-signature negative companion (Should.Throw<InvalidOperationException>) so the positive case can't silently regress if the helper's error contract changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…211) [STUD-64429]
CryptographyServiceTests exposed AlgorithmsForPasswordKey as a public
static field for xUnit [MemberData] to discover. The analyzer flagged
it as CA2211 / S2223 "Non-constant fields should not be visible".
Convert to a read-only auto-property (`{ get; } = new() { ... }`).
xUnit's [MemberData(nameof(...))] resolves properties identically;
all 4 theory rows still execute and pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves the 5 categories of CA warnings that surface when the test projects are built with -p:AnalysisMode=All: - CA1307 / CA1310 (8 sites): add explicit StringComparison.Ordinal to string.Contains / .StartsWith / .Replace. - CA5394 (5 sites): replace `new Random(seed).NextBytes(...)` with a deterministic literal byte array (`MakeDeterministicKey(size, offset)`). Tests need stable bytes, not actual randomness. - CA1819 (2 sites): suppress with justification on PgpKeyFixture byte[] properties — they're a test-only convenience that feeds straight into File.WriteAllBytes / PgpPublicKey.FromBytes. - CA1031 (4 sites): suppress with justification on broad-catches in test fixtures and CLI helpers (Probe, Run timeout-kill, Dispose cleanup, CygPath detector). All are intentional — gpg/openssl helpers degrade gracefully when the CLI is missing or behaves unexpectedly. Full Cryptography pack: 858 passed / 0 failed. Analyzer pass with AnalysisMode=All: 0 of the 5 target rules fire on the new test files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the Cryptography activities and coded-workflow API to support interoperable symmetric encryption/decryption formats (OpenSSL-compatible, OWASP-iteration variants, and raw key/IV mode), while keeping the legacy “Classic” wire format byte-stable for backward compatibility.
Changes:
- Introduces
SymmetricWireFormat+KeyBytesFormatand aSymmetricInteropHelpervalidator/dispatcher to enforce format/key/IV/KDF invariants. - Adds new interop-facing properties (
Format,KeyFormat,Iv,KdfIterations) across Encrypt/Decrypt Text/File activities and Studio viewmodels, plus updated documentation. - Adds a new coded-workflow API key/options model (
PasswordKey,RawKey,Symmetric*Options, etc.) and extensive stability + interop test coverage (including optional OpenSSL CLI gated tests).
Reviewed changes
Copilot reviewed 68 out of 70 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Activities/Cryptography/UiPath.Cryptography/SymmetricWireFormat.cs | New enum defining supported symmetric wire formats (Classic/Owasp2026/Raw/OpenSslEnc). |
| Activities/Cryptography/UiPath.Cryptography/SymmetricInteropHelper.cs | New central validator/parser/dispatcher for symmetric interop modes. |
| Activities/Cryptography/UiPath.Cryptography/KeyBytesFormat.cs | New enum for interpreting Key/Iv strings (Encoded/Hex/Base64). |
| Activities/Cryptography/UiPath.Cryptography/Properties/UiPath.Cryptography.resx | Adds localized labels + validation strings for new wire/key formats. |
| Activities/Cryptography/UiPath.Cryptography/Properties/UiPath.Cryptography.Designer.cs | Generated resource accessors for new .resx entries. |
| Activities/Cryptography/UiPath.Cryptography.Activities/EncryptText.cs | Adds format/key/IV/KDF knobs and routes symmetric encryption via interop helper. |
| Activities/Cryptography/UiPath.Cryptography.Activities/DecryptText.cs | Adds format/key/KDF knobs and routes symmetric decryption via interop helper. |
| Activities/Cryptography/UiPath.Cryptography.Activities/EncryptFile.cs | Adds format/key/IV/KDF knobs and routes symmetric encryption via interop helper. |
| Activities/Cryptography/UiPath.Cryptography.Activities/DecryptFile.cs | Adds format/key/KDF knobs and routes symmetric decryption via interop helper. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptTextViewModel.cs | Wires interop properties into EncryptText designer. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptFileViewModel.cs | Wires interop properties into EncryptFile designer. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptTextViewModel.cs | Wires interop properties into DecryptText designer. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptFileViewModel.cs | Wires interop properties into DecryptFile designer. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/EncryptCryptoViewModelBase.cs | Adds designer rules/visibility logic for Format/KeyFormat/Iv/KdfIterations. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/DecryptCryptoViewModelBase.cs | Adds designer rules/visibility logic for Format/KeyFormat/KdfIterations. |
| Activities/Cryptography/UiPath.Cryptography.Activities/PgpClearSignFile.cs | Renames Clearsign activity type and updates resource bindings. |
| Activities/Cryptography/UiPath.Cryptography.Activities/NetCore/ViewModels/PgpClearSignFileViewModel.cs | Updates activity/viewmodel type names for ClearSign rename. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.zh-CN.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.tr.resx | Resource key renames (notably includes value changes). |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.pt.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.pt-BR.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.ko.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.fr.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.es.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.es-MX.resx | Resource key renames for PGP activity naming consistency. |
| Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.de.resx | Resource key renames (notably includes value changes). |
| Activities/Cryptography/UiPath.Cryptography.Activities.Tests/WireFormatStabilityTests.cs | New fixtures pinning byte-stable layouts and recommended iteration constants. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Tests/CryptographyHelperInteropTests.cs | Adds helper-level assertions for OpenSslEnc/Raw/Owasp wire-format behavior. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Tests/ExternalInteropOpenSslCliTests.cs | Optional live OpenSSL CLI interop tests (gated on openssl availability). |
| Activities/Cryptography/UiPath.Cryptography.Activities.Tests/CacheMetadataWarningTests.cs | Pins design-time warning surface for FIPS/IV warnings. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpStandaloneTests.cs | Updates tests for the renamed PGP ClearSign activity. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Tests/PgpClearSignFileBranchTests.cs | Adds branch coverage tests for renamed ClearSign activity. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/overview.md | Updates docs index to renamed PGP ClearSign entry. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/PgpVerify.md | Updates ClearSignature producer reference to renamed activity. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/PgpSignFile.md | Updates guidance to use renamed ClearSign activity. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/PgpClearSignFile.md | Updates activity doc header/type/XAML example for renamed ClearSign activity. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptText.md | Documents new symmetric interop properties and formats + examples. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/EncryptFile.md | Documents new symmetric interop properties and formats + examples. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptText.md | Documents new symmetric interop properties and formats + examples. |
| Activities/Cryptography/UiPath.Cryptography.Activities.Packaging/docs/activities/DecryptFile.md | Documents new symmetric interop properties and formats + examples. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/CryptoOptions.cs | New shared base for coded symmetric encrypt/decrypt options. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/CryptoKey.cs | New base key abstraction with per-call buffer release hook. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/PasswordKey.cs | SecureString-backed password key materialization + per-call zeroing. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/RawKey.cs | Literal key bytes wrapper with disposal zeroing + parsing helpers. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/SymmetricEncryptOptions.cs | Format-specific factories enforcing key-kind × wire-format pairing. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/SymmetricDecryptOptions.cs | Format-specific factories enforcing key-kind × wire-format pairing. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/PgpPublicKey.cs | New coded API model for loading/saving PGP public keys. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/PgpPrivateKey.cs | New coded API model for private key + passphrase handling and disposal. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API/Models/PgpKeyPair.cs | New coded API wrapper for matched public/private PGP keys. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/RawKeyTests.cs | Tests RawKey lifecycle, parsing tolerance, and disposal behavior. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/PasswordKeyTests.cs | Tests PasswordKey lifecycle, encoding fidelity, and per-call clearing. |
| Activities/Cryptography/UiPath.Cryptography.Activities.API.Tests/PgpPrivateKeyTests.cs | Tests PgpPrivateKey file IO, overwrite rules, and wrong-passphrase translation. |
Files not reviewed (2)
- Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs: Language not supported
- Activities/Cryptography/UiPath.Cryptography/Properties/UiPath.Cryptography.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…UD-80425]
The per-activity Result description claimed a lower-case hex string, but
KeyedHash{File,Text} return upper-case (BitConverter.ToString default).
Align with coded-api.md and the ICryptographyService XML doc-comments,
which already say upper-case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LiviuPonova
left a comment
There was a problem hiding this comment.
Reviewed with focus on API compatibility and key-material handling. Four comments inline: two on the coded-API contract (breaking overload removal, forced UTF-8) and two on the key-zeroing pattern (duplicated across the four activities, and enforceable-by-construction in the service). The crypto core itself held up well under review — wire-format offsets, Raw round-trips, and KDF usage all checked out.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 70 out of 72 changed files in this pull request and generated 2 comments.
Files not reviewed (2)
- Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs: Generated file
- Activities/Cryptography/UiPath.Cryptography/Properties/UiPath.Cryptography.Designer.cs: Generated file
…STUD-64429]
The 12 localized *.{lang}.resx files are owned by the Localization
pipeline (periodic chore(l10n) sync PRs, e.g. #567). Hand edits risk
conflict with or overwrite by the next sync, and accidentally regress
prior translations (the PgpGenerateKeyPair rename replaced German
"Generierte PGP-Schluessel", Romanian "Generati chei PGP", Turkish
"PGP Anahtar Olustur" with English). Keep only the neutral
UiPath.Cryptography.Activities.resx edits; the renamed keys will fall
back to English in non-English locales until the next l10n sync picks
them up from the neutral file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…put failure test [STUD-64429] The test name and prose claimed pre-cancelled-token cancellation, but the body never injected a cancellation token into the runtime: it unused the CancellationTokenSource and reflected unused ExecuteAsync, then forced a FileNotFound by deleting the input file and asserted a generic Exception. Reviewer (copilot-pull-request-reviewer) flagged that this is overly broad and reduces value as a regression guard. Rename to PgpClearSignFile_DefaultContinueOnError_MissingInput_Throws and drop the unused reflection + token plumbing. The test now reflects what it actually exercises: with ContinueOnError default (false), a missing input file surfaces as a runtime exception (complement of the existing ContinueOnError=true sibling). Removes the now-unused System.Reflection and System.Threading usings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eropProperties docstring [STUD-64429] The XML doc on DecryptCryptoViewModelBase.ConfigureInteropProperties listed an Iv property in the configured set, but the decrypt side has no IV property — the IV is read from the ciphertext stream at decrypt time. Reviewer (copilot-pull-request-reviewer) flagged this as misleading for maintainers and reviewers. Update the docstring to list only the properties actually configured (Format, KeyFormat, KdfIterations) and call out the IV asymmetry explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… [STUD-64429] Reviewer flagged that upgrading from the prior coded API (key+Encoding overloads, path-based PgpGenerateKeys) hits compile errors with no pointer to the fix. Add a "Migrating from the prior coded API" section to coded-api.md covering: * One-to-one replacement table (old overload -> new options-based shape) for every symmetric / keyed-hash / PGP entry point. * Plaintext-encoding callout: text APIs now read encoding from SymmetricEncryptOptions.TextEncoding (defaults to UTF-8); the PasswordKey factory still takes its own encoding for password bytes. * Path-based PgpGenerateKeys migration to the new in-memory PgpKeyPair + .Save(path) pattern. * Behaviour-change notes: no [Obsolete] shims, why the dash is now an options object, etc. Also updates the factory signature table and the EncryptText/DecryptText method docs to reflect the new optional encoding: parameter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to a single helper [STUD-64429]
The validate -> parse key/IV -> re-validate(Raw) -> dispatch ->
finally{ClearKeyBytes} block was duplicated nearly verbatim in
EncryptText, DecryptText, EncryptFile, and DecryptFile (each ~30-40
lines, only the payload source/direction and error policy differ).
Reviewer flagged that this is security-sensitive duplication: a future
fix applied to three of the four copies would silently leave the
fourth leaking key bytes, and per-file review wouldn't catch it.
Add SymmetricInteropHelper.RunSymmetricWithKeyLifecycle that owns the
whole sequence. Each activity hands over its resolved settings + a
dispatch lambda; the helper guarantees keyOrPasswordBytes is zeroed
even when dispatch throws. Per-activity error policy (Decrypt* wrapping
CryptographicException in InvalidOperationException) lives inside the
caller's lambda, so the helper signature stays simple.
Adds three SymmetricInteropHelper tests:
* RunSymmetricWithKeyLifecycle_DispatchThrows_KeyBytesCleared
— captures the parsed key buffer from inside a throwing lambda and
asserts it is zeroed after the throw escapes. Regression guard for
the whole reason the helper exists.
* RunSymmetricWithKeyLifecycle_HappyPath_EncryptThenDecrypt_RoundTrip
* RunSymmetricWithKeyLifecycle_NullDispatch_Throws
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…option on symmetric options [STUD-64429]
Two related coded-API improvements, bundled because both touch
CryptographyService.cs.
UseKeyBytes scoped accessor (T9)
--------------------------------
Reviewer flagged that the key-zeroing contract relied on a
hand-written try/finally { Key.ReleaseMaterialisedBytes(keyBytes) } at
every KeyBytes consumption site (six in CryptographyService.cs).
Nothing stopped the next method added to the service from forgetting
the finally, which would silently reintroduce the unzeroed-password-
bytes issue this design fixes.
Add CryptoKey.UseKeyBytes<T>(Func<byte[], T>) — a scoped accessor that
materialises KeyBytes, invokes the lambda, and releases in a finally.
Internal (matches the visibility of KeyBytes / ReleaseMaterialisedBytes
— no public-surface change). Each of the 5 callsites in
CryptographyService.cs (EncryptBytes, DecryptBytes, KeyedHashBytes,
KeyedHashText, KeyedHashFile) collapses from a 6-line try/finally to a
one-liner; a future call site can't bypass the cleanup by construction.
Add PasswordKeyTests.UseKeyBytes_FuncThrows_StillReleasesBytes that
captures the materialised buffer from inside a throwing lambda and
asserts it is zeroed after the throw escapes, plus a null-func guard.
TextEncoding option on symmetric encrypt/decrypt options (T7)
-------------------------------------------------------------
Reviewer flagged that EncryptText/DecryptText hardcoded UTF-8 with no
way to express a non-UTF-8 plaintext encoding via the options object —
ciphertext produced by non-UTF-8 callers of the prior API would
silently decrypt to mojibake after migrating.
Add TextEncoding to CryptoOptions (default Encoding.UTF8) and an
optional trailing encoding: parameter to every format factory on
SymmetricEncryptOptions / SymmetricDecryptOptions (Classic, Owasp2026,
Raw, OpenSslEnc). EncryptText/DecryptText now read options.TextEncoding
instead of hard-coding Encoding.UTF8 — non-UTF-8 callers can opt in via
SymmetricEncryptOptions.Classic(key, Encoding.Latin1) etc. Bytes/File
APIs ignore the option (no string to encode).
Add CryptographyServiceTests:
* EncryptText_DecryptText_NonUtf8Encoding_RoundTrip — proves the
option drives the encode AND decode sides (UTF-16 round-trip; same
ciphertext decoded as UTF-8 produces mojibake, confirming no hidden
default).
* EncryptText_NullOptions_Throws and DecryptText_NullOptions_Throws.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 70 out of 72 changed files in this pull request and generated 7 comments.
Files not reviewed (2)
- Activities/Cryptography/UiPath.Cryptography.Activities/Properties/UiPath.Cryptography.Activities.Designer.cs: Generated file
- Activities/Cryptography/UiPath.Cryptography/Properties/UiPath.Cryptography.Designer.cs: Generated file
| // Pre-cancelled token — the async file resolution awaits the token and must throw | ||
| // OperationCanceledException before reaching the synchronous sign helper. | ||
| [Fact] | ||
| public void PgpClearSignFile_PreCancelledToken_ThrowsOperationCancelled() | ||
| { |
| // Reach the async ExecuteAsync directly with a pre-cancelled token. The activity's | ||
| // PgpFileResolver.ResolveAsync awaits the token internally — pre-cancelling | ||
| // it makes the first await observe cancellation and throw. | ||
| using var cts = new CancellationTokenSource(); | ||
| cts.Cancel(); |
| <data name="Activity_PgpGenerateKeys_Name" xml:space="preserve"> | ||
| <value>PGP Anahtar Oluştur</value> | ||
| <data name="Activity_PgpGenerateKeyPair_Name" xml:space="preserve"> | ||
| <value>PGP Generate Keys</value> |
| <data name="Activity_PgpClearsignFile_Name" xml:space="preserve"> | ||
| <value>PGP Şeffaf İmza Dosyası</value> | ||
| <data name="Activity_PgpClearSignFile_Name" xml:space="preserve"> | ||
| <value>PGP Clear Sign File</value> |
| <data name="Activity_PgpGenerateKeys_Name" xml:space="preserve"> | ||
| <value>Generierte PGP-Schlüssel</value> | ||
| <data name="Activity_PgpGenerateKeyPair_Name" xml:space="preserve"> | ||
| <value>PGP Generate Keys</value> |
| public static SymmetricEncryptOptions Raw(RawKey key, byte[] iv = null) => | ||
| new() { Key = key, Format = SymmetricWireFormat.Raw, IV = iv }; |
|




Context
The Cryptography activity package (
Activities/Cryptography/) — symmetric encrypt/decrypt activities (EncryptText, DecryptText, EncryptFile, DecryptFile) and the coded-workflowCryptographyService. Consumed from XAML workflows via the Studio toolbox and from coded workflows viaICryptographyService. Internally delegates toCryptographyHelperfor cipher operations.Problem Statement
UiPath's symmetric ciphertext layout (
salt(8) || IV || ct [|| tag(16)], PBKDF2-HMAC-SHA1 @ 10,000 iter, max-legal key size) is UiPath-specific. Output fromEncryptText/EncryptFilecannot be read by openssl, Java javax.crypto, Python cryptography, ServiceNow, or browser SubtleCrypto; conversely,DecryptText/DecryptFilecannot consume blobs from those tools. The root customer report (STUD-64429) was TripleDES round-trip failure, but the same issue affects every algorithm and blocks every integration scenario.Behavior Before This PR
A workflow encrypts a payload with
EncryptText(Algorithm = AES, Key = "shared")and hands the Base64 to an external system.openssl enc -aes-256-cbc -pbkdf2 -k shared -drejects it (no magic prefix). The reverse direction also fails: openssl-produced blobs returnDecryption failed. UiPath wire format is Base64(salt | IV | ciphertext)…. No interop path exists.Behavior After This PR
Same scenario, opting into
Format = OpenSslEnc: output isSalted__ || salt(8) || ctwith PBKDF2-HMAC-SHA256 @ 600,000 iter — whatopenssl enc -aes-256-cbc -pbkdf2 -iter 600000 -dexpects. Classic stays the default and remains byte-stable (existing customer ciphertext still works; pinned by hex-literal fixtures). Three new opt-in formats:Owasp2026(Classic layout, caller-controlled iter),Raw(caller-supplied key + IV, no KDF — interops with .NET native AES, openssl-K -iv, KMS keys),OpenSslEnc.The
CryptoKeyhierarchy is refactored so(key kind × wire format)pairings are enforced at compile time (Classic(PasswordKey),Raw(RawKey)).PasswordKeybytes are now zeroed per call (P2 fix). KDF-iteration validation rejects negatives (P3 fix).Implementation
Owasp2026is a year-snapshot; a future revision would addOwasp2030rather than mutate the constant) — enforced byWireFormatStabilityTestsdecrypting pre-captured Classic blobs.ReleaseMaterialisedBytesvirtual onCryptoKey. Defaults to no-op becauseRawKey.KeyBytesreturns instance-owned storage; clearing it would corrupt the key.PasswordKeyoverrides to zero. EveryKeyBytesaccess in the service and the four activities is wrapped in try/finally that calls it.Documentation
KeyedHash{File,Text}.mdResult description now correctly says "upper-case hexadecimal string" —KeyedHash{File,Text}always returned upper-case viaBitConverter.ToStringdefault, but the per-activity docs (added ondevelopby cryptography: Add AI-generated XAML activity docs for Cryptography package [STUD-79287] #525) claimed lower-case. Aligned withcoded-api.mdand theICryptographyServiceXML doc-comments, which already said upper-case. Source code unchanged (STUD-80425).Caveats
OpenSslEncis hardcoded to AES-256 (max legal key size).aes-128-cbc/aes-192-cbcinterop via the activity surface is tracked as STUD=80390 (Improvement, follow-up). OpenSSL CLI tests only exerciseaes-256-cbc; a code comment inExternalInteropOpenSslCliTests.csdocuments the limitation.OpenSslEnc(AES-GCM +Salted__prefix) is a UiPath extension, not a cross-tool standard —openssl encitself doesn't support GCM. Called out indocs/symmetric-wire-format.md.How to Test
dotnet test Activities/Activities.Cryptography.sln→ 858 passing / 0 failing.dotnet test … --filter "FullyQualifiedName~Interop".dotnet test … --filter "FullyQualifiedName~WireFormatStability"decrypts hex-literal blobs (any future layout change fails these).ExternalInteropOpenSslCliTests/ExternalInteropGpgCliTestsno-op when the CLI is missing; on agents with OpenSSL/GnuPG they round-trip against the reference implementation.EncryptTextwithFormat = OpenSslEnc, Algorithm = AES, encrypt; pipe Base64 →openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -k <password> -A -base64and verify plaintext.