feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231] #569
Open
alexandru-petre wants to merge 10 commits into
Open
feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231] #569alexandru-petre wants to merge 10 commits into
alexandru-petre wants to merge 10 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.
Comment on lines
+20
to
+32
| // The class is exposed under the new PascalCase name. If a future tooling pass | ||
| // accidentally reintroduced the old camelCase name (or removed it without an alias), | ||
| // this test would break loudly. | ||
| [Fact] | ||
| public void PgpClearSignFile_TypeName_IsPascalCase() | ||
| { | ||
| Type t = typeof(PgpClearSignFile); | ||
| t.Name.ShouldBe("PgpClearSignFile"); | ||
| t.FullName.ShouldBe("UiPath.Cryptography.Activities.PgpClearSignFile"); | ||
|
|
||
| // No stale camelCase type lingering in the assembly. | ||
| Type stale = t.Assembly.GetType("UiPath.Cryptography.Activities.PgpClearsignFile", throwOnError: false); | ||
| stale.ShouldBeNull("the old PgpClearsignFile (camelCase) name should not exist after the rename"); |
Comment on lines
+61
to
+105
| // 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() | ||
| { | ||
| string inputPath = Path.Combine(Path.GetTempPath(), $"pre_cancel_{Guid.NewGuid():N}.txt"); | ||
| string outputPath = Path.Combine(Path.GetTempPath(), $"pre_cancel_out_{Guid.NewGuid():N}.asc"); | ||
| File.WriteAllText(inputPath, "cancel-me"); | ||
|
|
||
| try | ||
| { | ||
| var activity = new PgpClearSignFile | ||
| { | ||
| InputFilePath = new InArgument<string>(inputPath), | ||
| PrivateKeyFilePath = new InArgument<string>(_privateKeyPath), | ||
| Passphrase = new InArgument<string>(Passphrase), | ||
| OutputFilePath = new InArgument<string>(outputPath), | ||
| Overwrite = true, | ||
| }; | ||
|
|
||
| // 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(); | ||
|
|
||
| MethodInfo execAsync = typeof(PgpClearSignFile).GetMethod( | ||
| "ExecuteAsync", | ||
| BindingFlags.Instance | BindingFlags.NonPublic); | ||
| execAsync.ShouldNotBeNull("PgpClearSignFile.ExecuteAsync must exist for the async base class to dispatch into"); | ||
|
|
||
| // ExecuteAsync expects an AsyncCodeActivityContext; we can't easily construct one | ||
| // outside the WF runtime. Use WorkflowInvoker with a separate thread + a token | ||
| // that we cancel before invocation — the activity samples the token inside | ||
| // PgpFileResolver.ResolveAsync. | ||
| Should.Throw<Exception>(() => | ||
| { | ||
| // Reaching into the runtime to inject a token requires WorkflowApplication. | ||
| // For a unit test we instead rely on a corrupt/missing file — the activity's | ||
| // ContinueOnError defaults to false, so the exception surfaces from the runtime. | ||
| // (True per-token cancellation requires WorkflowApplication integration testing | ||
| // not appropriate for this unit-test suite — see the integration plan.) | ||
| File.Delete(inputPath); // makes resolution throw FileNotFound | ||
| WorkflowInvoker.Invoke(activity); | ||
| }); |
Comment on lines
+1015
to
+1016
| <data name="Activity_PgpGenerateKeyPair_Name" xml:space="preserve"> | ||
| <value>PGP Generate Keys</value> |
Comment on lines
+1151
to
+1152
| <data name="Activity_PgpClearSignFile_Name" xml:space="preserve"> | ||
| <value>PGP Clear Sign File</value> |
Comment on lines
+1015
to
+1016
| <data name="Activity_PgpGenerateKeyPair_Name" xml:space="preserve"> | ||
| <value>PGP Generate Keys</value> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




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.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.