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 18 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
|
…STUD-64429] Reviewer flagged that SymmetricEncryptOptions.Raw stored the caller's iv byte array by reference. Since SymmetricEncryptOptions is reusable, later mutation of the caller's buffer would silently change the IV used by the service — violating the "NEVER reuse the same (Key, IV) pair" warning already in the factory's docstring. Mirror the defensive-copy pattern that RawKey.FromBytes, PgpPublicKey.FromBytes, and PgpPrivateKey.FromBytes already follow: Buffer.BlockCopy the iv into a new array inside the factory. Preserves null → null (cipher generates a random IV) and normalises empty arrays to null. Adds CryptographyServiceTests.SymmetricEncryptOptions_Raw_DefensiveCopiesIv_CallerMutationDoesNotAffectEncryption which Array.Clears the caller's iv buffer between two encryptions reusing the same options instance and asserts the two ciphertexts are byte-equal. Without the defensive copy the second encryption uses an all-zero IV and the test fails — verified pre-fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>




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.