Skip to content

feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231][STUD-80425]#569

Open
alexandru-petre wants to merge 17 commits into
developfrom
feature/cryptography-symmetric-interop
Open

feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231][STUD-80425]#569
alexandru-petre wants to merge 17 commits into
developfrom
feature/cryptography-symmetric-interop

Conversation

@alexandru-petre

@alexandru-petre alexandru-petre commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Context

The Cryptography activity package (Activities/Cryptography/) — symmetric encrypt/decrypt activities (EncryptText, DecryptText, EncryptFile, DecryptFile) and the coded-workflow CryptographyService. Consumed from XAML workflows via the Studio toolbox and from coded workflows via ICryptographyService. Internally delegates to CryptographyHelper for 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 from EncryptText/EncryptFile cannot be read by openssl, Java javax.crypto, Python cryptography, ServiceNow, or browser SubtleCrypto; conversely, DecryptText/DecryptFile cannot 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 -d rejects it (no magic prefix). The reverse direction also fails: openssl-produced blobs return Decryption failed. UiPath wire format is Base64(salt | IV | ciphertext)…. No interop path exists.

Behavior After This PR

Same scenario, opting into Format = OpenSslEnc: output is Salted__ || salt(8) || ct with PBKDF2-HMAC-SHA256 @ 600,000 iter — what openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -d expects. 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 CryptoKey hierarchy is refactored so (key kind × wire format) pairings are enforced at compile time (Classic(PasswordKey), Raw(RawKey)). PasswordKey bytes are now zeroed per call (P2 fix). KDF-iteration validation rejects negatives (P3 fix).

Implementation

  • Classic is intentionally frozen. It is the byte-stable contract for existing UiPath ciphertext. Future OWASP iter upgrades go into new enum entries (Owasp2026 is a year-snapshot; a future revision would add Owasp2030 rather than mutate the constant) — enforced by WireFormatStabilityTests decrypting pre-captured Classic blobs.
  • ReleaseMaterialisedBytes virtual on CryptoKey. Defaults to no-op because RawKey.KeyBytes returns instance-owned storage; clearing it would corrupt the key. PasswordKey overrides to zero. Every KeyBytes access in the service and the four activities is wrapped in try/finally that calls it.

Documentation

  • KeyedHash{File,Text}.md Result description now correctly says "upper-case hexadecimal string" — KeyedHash{File,Text} always returned upper-case via BitConverter.ToString default, but the per-activity docs (added on develop by cryptography: Add AI-generated XAML activity docs for Cryptography package [STUD-79287] #525) claimed lower-case. Aligned with coded-api.md and the ICryptographyService XML doc-comments, which already said upper-case. Source code unchanged (STUD-80425).

Caveats

  • OpenSslEnc is hardcoded to AES-256 (max legal key size). aes-128-cbc / aes-192-cbc interop via the activity surface is tracked as STUD=80390 (Improvement, follow-up). OpenSSL CLI tests only exercise aes-256-cbc; a code comment in ExternalInteropOpenSslCliTests.cs documents the limitation.
  • AEAD over OpenSslEnc (AES-GCM + Salted__ prefix) is a UiPath extension, not a cross-tool standard — openssl enc itself doesn't support GCM. Called out in docs/symmetric-wire-format.md.

How to Test

  1. dotnet test Activities/Activities.Cryptography.sln → 858 passing / 0 failing.
  2. Interop-only subset: dotnet test … --filter "FullyQualifiedName~Interop".
  3. Wire-format stability: dotnet test … --filter "FullyQualifiedName~WireFormatStability" decrypts hex-literal blobs (any future layout change fails these).
  4. Live external-tool interop (gated): ExternalInteropOpenSslCliTests / ExternalInteropGpgCliTests no-op when the CLI is missing; on agents with OpenSSL/GnuPG they round-trip against the reference implementation.
  5. Manual: in Studio, create EncryptText with Format = OpenSslEnc, Algorithm = AES, encrypt; pipe Base64 → openssl enc -d -aes-256-cbc -pbkdf2 -iter 600000 -k <password> -A -base64 and verify plaintext.

alexandru-petre and others added 7 commits June 8, 2026 02:40
…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>
@alexandru-petre alexandru-petre changed the title [STUD-64429][STUD-80231]: Cryptography: third-party symmetric interop feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231] Jun 9, 2026
Comment thread Activities/Cryptography/UiPath.Cryptography/CryptographyHelper.cs Fixed
Comment thread Activities/Cryptography/UiPath.Cryptography/CryptographyHelper.cs Fixed
Comment thread Activities/Cryptography/UiPath.Cryptography/CryptographyHelper.cs Fixed
Comment thread Activities/Cryptography/UiPath.Cryptography/CryptographyHelper.cs Fixed
Comment thread Activities/Cryptography/UiPath.Cryptography/CryptographyHelper.cs Fixed
Comment thread Activities/Cryptography/UiPath.Cryptography/CryptographyHelper.cs Fixed
alexandru-petre and others added 3 commits June 9, 2026 12:37
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>
@alexandru-petre alexandru-petre marked this pull request as ready for review June 9, 2026 15:36
@alexandru-petre alexandru-petre self-assigned this Jun 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 + KeyBytesFormat and a SymmetricInteropHelper validator/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>
@alexandru-petre alexandru-petre changed the title feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231] ping Jun 10, 2026
@alexandru-petre alexandru-petre changed the title ping feat(cryptography): third-party symmetric interoperability [STUD-64429][STUD-80231][STUD-80425] Jun 10, 2026

@LiviuPonova LiviuPonova left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Activities/Cryptography/UiPath.Cryptography.Activities/EncryptText.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@alexandru-petre alexandru-petre requested a review from Copilot June 12, 2026 17:56
alexandru-petre and others added 6 commits June 12, 2026 21:02
…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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +61 to +65
// 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()
{
Comment on lines +81 to +85
// 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>
Comment on lines +51 to +52
public static SymmetricEncryptOptions Raw(RawKey key, byte[] iv = null) =>
new() { Key = key, Format = SymmetricWireFormat.Raw, IV = iv };
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants