Skip to content

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

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

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

Conversation

@alexandru-petre

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.

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
@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
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

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.

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

3 participants