Skip to content

Conversation

@akafredperry
Copy link
Collaborator

@akafredperry akafredperry commented Jan 26, 2026

- What I did
Added support for PKAMS per app per device, specifically

  • pkam authenticatiojn will send the enrollment id (if present in the atKeys)
  • replaced Onboard utility with Activation utility which mirrors the features of activate_cli in dart

- How I did it

  • Modified the withRemoteSecondary static methods in AtClient. These no longer automagically load atKeys from a file based on the name of the atsign. The api now requires the caller to provide an AtKeys instance, which holds the necessary keys and enrollment id. AtKeys replaces the use of a map of strings in AtClientImpl and RemoteSecondary.
  • KeysUtil has been modified to support creating an AtKeys instance from a file and to load and save the additional JSON members required for PKAMs per app / device.
  • All the CLI utilities have been modifed to reflect the above changes to AtClientImpl and RemoteSecondary
    typed API)
  • EnryptionUtil has been enhanced to support decryption with initialisation vector
  • AtClient and Secondary now extends java.io.Closeable, the implementations disconnect the socket and ensure the monitor is stopped.
  • A new CLI class Activate which provides the implementation of otp, onboard, enroll, approve, deny, revoke, unrevoke, list
  • Added new BDD tests for onboarding and enrollment workflows
  • Added new BDD tests to verify namespace behaviour
  • Added Activate unit tests which work against the virtual env to test command line invocation

- How to verify it
Run all the tests.
NOTE: I have verified scenarios in which the onboarding and enrollment workflow is done by a combination of the dart activate_cli and the new Java Activate.

- Description for the changelog
closes #324 closes #71

SecretKey key = _aesKeyFromBase64(keyBase64);
IvParameterSpec iv = ivNonce != null ? _ivFromBase64(ivNonce) : EMPTY_IV;
Cipher cipher = Cipher.getInstance("AES/SIC/PKCS7Padding", "BC");
cipher.init(mode, key, iv);

Check failure

Code scanning / CodeQL

Using a static initialization vector for encryption High

A
static initialization vector
should not be used for encryption.

Copilot Autofix

AI 3 days ago

In general terms, the problem is the use of a static, all-zero IV (EMPTY_IV) whenever no IV is supplied. For AES in SIC (CTR) mode, the IV/nonce must be unique for each encryption with the same key; using a fixed IV breaks confidentiality. The fix is to ensure that an unpredictable, random IV is always used for encryption, and that the same IV is only reused for decryption of the corresponding ciphertext. If the caller doesn’t supply an IV, this method should generate one instead of falling back to a static one.

The single best way to fix this without changing existing functionality more than necessary is:

  • Remove the static EMPTY_IV constant (or stop using it for encryption).
  • In createAesCipher, when mode == Cipher.ENCRYPT_MODE and ivNonce is null, generate a fresh, random IV of the correct length using SecureRandom, and construct an IvParameterSpec from it.
  • When ivNonce is non-null, keep the existing behavior and use _ivFromBase64(ivNonce).
  • For decryption (DECRYPT_MODE), keep using the IV provided by ivNonce; if it is null, it’s safer to fail (e.g., throw InvalidAlgorithmParameterException) rather than silently using a static IV. However, to avoid changing external behavior unexpectedly, we can still avoid EMPTY_IV and instead require an IV, or at least avoid the static zero IV. The minimal security-improving change is to eliminate EMPTY_IV and never use a static all-zero vector.

We are constrained to modify only at_client/src/main/java/org/atsign/client/util/EncryptionUtil.java within the shown snippet. Concretely:

  • Delete or stop using the line defining EMPTY_IV.
  • Update createAesCipher so line 126 no longer references EMPTY_IV, and instead:
    • If ivNonce != null, use _ivFromBase64(ivNonce) (as before).
    • If ivNonce == null:
      • If mode == Cipher.ENCRYPT_MODE, generate a 16-byte random IV using SecureRandom and wrap it in IvParameterSpec.
      • Otherwise (e.g., decrypt), throw an InvalidAlgorithmParameterException indicating an IV is required.
  • No new imports are needed because SecureRandom is already imported, and IvParameterSpec is already imported.

This addresses CodeQL’s complaint by ensuring that no static, deterministic IV is ever used for encryption, while keeping the current method signature and base64-handling intact.

Suggested changeset 1
at_client/src/main/java/org/atsign/client/util/EncryptionUtil.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/at_client/src/main/java/org/atsign/client/util/EncryptionUtil.java b/at_client/src/main/java/org/atsign/client/util/EncryptionUtil.java
--- a/at_client/src/main/java/org/atsign/client/util/EncryptionUtil.java
+++ b/at_client/src/main/java/org/atsign/client/util/EncryptionUtil.java
@@ -14,7 +14,6 @@
 import java.util.Base64;
 
 public class EncryptionUtil {
-    static private final IvParameterSpec EMPTY_IV = new IvParameterSpec(new byte[16]);
 
     public static final String SIGNING_ALGO_RSA = "rsa2048";
     public static final String HASHING_ALGO_SHA256 = "sha256";
@@ -123,7 +122,19 @@
 
     private static Cipher createAesCipher(int mode, String keyBase64, String ivNonce) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
         SecretKey key = _aesKeyFromBase64(keyBase64);
-        IvParameterSpec iv = ivNonce != null ? _ivFromBase64(ivNonce) : EMPTY_IV;
+        IvParameterSpec iv;
+        if (ivNonce != null) {
+            iv = _ivFromBase64(ivNonce);
+        } else {
+            if (mode == Cipher.ENCRYPT_MODE) {
+                byte[] ivBytes = new byte[16];
+                SecureRandom secureRandom = new SecureRandom();
+                secureRandom.nextBytes(ivBytes);
+                iv = new IvParameterSpec(ivBytes);
+            } else {
+                throw new InvalidAlgorithmParameterException("IV is required for this operation");
+            }
+        }
         Cipher cipher = Cipher.getInstance("AES/SIC/PKCS7Padding", "BC");
         cipher.init(mode, key, iv);
         return cipher;
EOF
@@ -14,7 +14,6 @@
import java.util.Base64;

public class EncryptionUtil {
static private final IvParameterSpec EMPTY_IV = new IvParameterSpec(new byte[16]);

public static final String SIGNING_ALGO_RSA = "rsa2048";
public static final String HASHING_ALGO_SHA256 = "sha256";
@@ -123,7 +122,19 @@

private static Cipher createAesCipher(int mode, String keyBase64, String ivNonce) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
SecretKey key = _aesKeyFromBase64(keyBase64);
IvParameterSpec iv = ivNonce != null ? _ivFromBase64(ivNonce) : EMPTY_IV;
IvParameterSpec iv;
if (ivNonce != null) {
iv = _ivFromBase64(ivNonce);
} else {
if (mode == Cipher.ENCRYPT_MODE) {
byte[] ivBytes = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(ivBytes);
iv = new IvParameterSpec(ivBytes);
} else {
throw new InvalidAlgorithmParameterException("IV is required for this operation");
}
}
Cipher cipher = Cipher.getInstance("AES/SIC/PKCS7Padding", "BC");
cipher.init(mode, key, iv);
return cipher;
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

Choose a reason for hiding this comment

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

@akafredperry support for null or 0'd IV is no longer required, this is a legacy from ancient times

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@gkc yes understood but not simply a case of removing the capability as Java SDK needs to be enhanced.
I only realised yesterday that it requires changes to support the IV in the meta data and to create / expect an IV for shared and and self keys. I created this #342. But if you think this work should be combined into a single PR I can set this PR to draft and pick this up next week. Ideally I need a bit of your time to confirm some things

@akafredperry akafredperry changed the title wip: Implement enrollment feature: Implement onborading and enrollment workflow Jan 27, 2026
@akafredperry akafredperry requested a review from gkc January 27, 2026 11:18
@akafredperry akafredperry changed the title feature: Implement onborading and enrollment workflow feature: Implement onboarding and enrollment workflow Jan 27, 2026
@akafredperry akafredperry changed the title feature: Implement onboarding and enrollment workflow feat: Implement onboarding and enrollment workflow Jan 27, 2026
@akafredperry akafredperry self-assigned this Jan 28, 2026
}

private static EnrollmentId inferEnrollmentId(String key) {
Matcher matcher = Pattern.compile("^([^\\.]+).+").matcher(key);

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on a
user-provided value
may run slow on strings starting with '-' and with many repetitions of '-'.
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.

Add support for PKAMs per app per device Java SDK searches for atKeys in root project dir

3 participants