From d8c218c880f277d0958172f071a4b1c8cea4f154 Mon Sep 17 00:00:00 2001 From: gkc Date: Wed, 30 Sep 2026 14:07:45 +0100 Subject: [PATCH 1/2] fix(keys): share keyfiles with at_auth 3.3 and later at_auth 3.3.0 and 4.0.0-rc2 read a "version" field as the marker of their typed keys document and require the integer 1, so the string "1" this class wrote made every file it saved unreadable there. In the other direction, a file at_auth has written typed material into holds lists and objects, and parsing it as a map of strings failed. The writer now emits the legacy flat shape with no version field. The reader parses the document as JSON and takes the flat fields, accepting no version, the string "1", or the integer 1. A typed document that authenticates through typed key material names a different enrollment from its flat fields, so it is refused rather than read as the wrong principal; any other version is refused as before. saveKeys refuses to overwrite a typed document, whose typed material this class cannot carry. The fixtures are keyfiles written by at_auth 4.0.0-rc2 and by this class before the change. --- .../atsign/client/impl/util/KeysUtils.java | 146 ++++++++++++++-- .../client/impl/util/KeysUtilsTest.java | 163 ++++++++++++++++++ ...at_auth_4_0_0_rc2_typed_atsign_keys.atKeys | 1 + ...auth_4_0_0_rc2_typed_authentication.atKeys | 1 + .../impl/util/at_java_string_version.atKeys | 8 + 5 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys create mode 100644 at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys create mode 100644 at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys diff --git a/at_client/src/main/java/org/atsign/client/impl/util/KeysUtils.java b/at_client/src/main/java/org/atsign/client/impl/util/KeysUtils.java index a9b6c040..b9a9fb4a 100644 --- a/at_client/src/main/java/org/atsign/client/impl/util/KeysUtils.java +++ b/at_client/src/main/java/org/atsign/client/impl/util/KeysUtils.java @@ -9,8 +9,11 @@ import java.io.IOException; import java.nio.file.Files; import java.util.Base64; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import org.atsign.client.api.AtKeys; import org.atsign.client.api.AtSign; @@ -31,7 +34,7 @@ public class KeysUtils { static private final String EMPTY_IV = Base64.getEncoder().encodeToString(new byte[16]); - private static final TypeReference> STRING_MAP_TYPE = new TypeReference<>() {}; + private static final TypeReference> JSON_OBJECT_TYPE = new TypeReference<>() {}; public static final String ATSIGN_KEYS_DIR = "ATSIGN_KEYS_DIR"; public static final String ATSIGN_KEYS_SUFFIX = "ATSIGN_KEYS_SUFFIX"; @@ -62,6 +65,21 @@ public class KeysUtils { private static final String APKAM_SYMMETRIC_KEY = "apkamSymmetricKey"; private static final String ENROLLMENT_ID = "enrollmentId"; + private static final List FLAT_FIELDS = List.of(SELF_ENCRYPT_KEY, ENROLLMENT_ID, APKAM_SYMMETRIC_KEY, + PKAM_PUBLIC_KEY, PKAM_PRIVATE_KEY, ENCRYPT_PUBLIC_KEY, + ENCRYPT_PRIVATE_KEY); + + /** + * NB: These values are the typed keys document at_auth writes, and MUST match its field names + */ + private static final String TYPED_ENROLLMENTS = "enrollments"; + private static final String TYPED_KEYS = "keys"; + private static final String TYPED_MATERIAL = "material"; + private static final String TYPED_ROLE = "role"; + private static final String TYPED_STATUS = "status"; + private static final String ROLE_PRIVATE_AUTHENTICATION = "privateAuthentication"; + private static final String STATUS_ACTIVE = "active"; + /** * Persists {@link AtKeys} to the default file for the {@link AtSign}. * @@ -74,13 +92,15 @@ public static void saveKeys(AtSign atSign, AtKeys keys) throws Exception { } /** - * Persists {@link AtKeys} to a file. + * Persists {@link AtKeys} to a file, in the legacy flat shape. * * @param keys The {@link AtKeys} to persist. * @param file The file to write / overwrite. - * @throws IOException If anything fails. + * @throws IOException If anything fails, or the file is a typed keys document: this class cannot + * carry its typed key material, so rewriting it would lose that material. */ public static void saveKeys(AtKeys keys, File file) throws IOException { + refuseToOverwriteTypedDocument(file); if (file.getParentFile() != null && !file.getParentFile().exists()) { Files.createDirectories(file.getParentFile().toPath()); } @@ -102,11 +122,14 @@ public static AtKeys loadKeys(AtSign atSign) throws AtClientConfigException { } /** - * Instantiates a {@link AtKeys} loaded with the contents of a keys file. + * Instantiates a {@link AtKeys} loaded with the contents of a keys file: the legacy flat shape, or + * the typed keys document at_auth writes, whose flat fields are read and typed containers ignored. * * @param file The file to read. * @return A populated {@link AtKeys} instance. - * @throws AtClientConfigException If anything fails or the keys file does not exist. + * @throws AtClientConfigException If anything fails, the keys file does not exist, or it is a + * typed document that authenticates through typed key material, which this class cannot + * read. */ public static AtKeys loadKeys(File file) throws AtClientConfigException { try { @@ -116,6 +139,22 @@ public static AtKeys loadKeys(File file) throws AtClientConfigException { } } + private static void refuseToOverwriteTypedDocument(File file) throws IOException { + if (!file.exists()) { + return; + } + Object version; + try { + version = JsonUtils.readValue(Files.readString(file.toPath()), JSON_OBJECT_TYPE).get(VERSION_KEY); + } catch (RuntimeException e) { + return; + } + if (version instanceof Number) { + throw new IOException("refusing to overwrite " + file + ": it is a typed keys document, and rewriting it" + + " would lose the typed key material it holds"); + } + } + private static File getKeysFileFallbackToLegacyLocation(AtSign atSign) throws AtClientConfigException { // check first if file exists at canonical location ~/.atsign/keys/$atSign_key.atKeys File file = getKeysFile(atSign, expectedKeysFilesLocation); @@ -176,8 +215,6 @@ private static String getAsJson(AtKeys keys) { mapPutEncrypted(map, ENCRYPT_PUBLIC_KEY, keys.getEncryptPublicKey(), keys.getSelfEncryptKey()); mapPutEncrypted(map, ENCRYPT_PRIVATE_KEY, keys.getEncryptPrivateKey(), keys.getSelfEncryptKey()); - map.put(VERSION_KEY, VERSION_1); - return JsonUtils.writeValueAsString(map, true); } catch (Exception e) { throw new RuntimeException(e); @@ -185,19 +222,100 @@ private static String getAsJson(AtKeys keys) { } private static AtKeys createAtKeysFromJson(String json) throws AtClientConfigException { + Map document = readDocument(json); + if (isTypedDocument(document)) { + refuseTypedAuthentication(document); + } try { - Map map = JsonUtils.readValue(json, STRING_MAP_TYPE); - String version = map.getOrDefault(VERSION_KEY, VERSION_1); - if (version.equals(VERSION_1)) { - return createAtKeysVersion1(map); - } else { - throw new AtClientConfigException("unsupported version of AtKeys json : " + version); - } + return createAtKeysVersion1(flatFields(document)); } catch (AtDecryptionException e) { throw new AtClientConfigException("failed to create AtKeys from json", e); } } + private static Map readDocument(String json) throws AtClientConfigException { + try { + return JsonUtils.readValue(json, JSON_OBJECT_TYPE); + } catch (RuntimeException e) { + throw new AtClientConfigException("AtKeys json is not a JSON object", e); + } + } + + /** + * An integer version 1 marks the typed keys document at_auth writes. No version, or the string + * "1" that earlier releases of this class wrote, marks the legacy flat shape. + */ + private static boolean isTypedDocument(Map document) throws AtClientConfigException { + if (!document.containsKey(VERSION_KEY)) { + return false; + } + Object version = document.get(VERSION_KEY); + if (VERSION_1.equals(version)) { + return false; + } + if (Integer.valueOf(1).equals(version)) { + return true; + } + throw new AtClientConfigException("unsupported version of AtKeys json : " + version); + } + + /** + * A typed document authenticates as the enrollment holding active typed authentication material, + * and its flat fields then belong to a different enrollment, so reading them would authenticate + * as the wrong principal. + */ + private static void refuseTypedAuthentication(Map document) throws AtClientConfigException { + Set enrollmentIds = new TreeSet<>(); + for (Object enrollment : listOf(document.get(TYPED_ENROLLMENTS), TYPED_ENROLLMENTS)) { + Map enrollmentEntry = mapOf(enrollment, TYPED_ENROLLMENTS); + for (Object key : listOf(enrollmentEntry.get(TYPED_KEYS), TYPED_ENROLLMENTS + "[].keys")) { + Map keyEntry = mapOf(key, TYPED_ENROLLMENTS + "[].keys"); + for (Object material : listOf(keyEntry.get(TYPED_MATERIAL), TYPED_ENROLLMENTS + "[].keys[].material")) { + Map materialEntry = mapOf(material, TYPED_ENROLLMENTS + "[].keys[].material"); + Object status = materialEntry.get(TYPED_STATUS); + if (ROLE_PRIVATE_AUTHENTICATION.equals(materialEntry.get(TYPED_ROLE)) + && (status == null || STATUS_ACTIVE.equals(status))) { + enrollmentIds.add(String.valueOf(enrollmentEntry.get(ENROLLMENT_ID))); + } + } + } + } + if (!enrollmentIds.isEmpty()) { + throw new AtClientConfigException("AtKeys json authenticates as enrollment " + String.join(", ", enrollmentIds) + + " through typed key material, which this client cannot read"); + } + } + + private static List listOf(Object value, String field) throws AtClientConfigException { + if (value == null) { + return List.of(); + } + if (value instanceof List) { + return (List) value; + } + throw new AtClientConfigException("AtKeys json field " + field + " is not a list"); + } + + private static Map mapOf(Object value, String field) throws AtClientConfigException { + if (value instanceof Map) { + return (Map) value; + } + throw new AtClientConfigException("AtKeys json field " + field + " holds an entry that is not an object"); + } + + private static Map flatFields(Map document) throws AtClientConfigException { + Map map = new TreeMap<>(); + for (String field : FLAT_FIELDS) { + Object value = document.get(field); + if (value instanceof String) { + map.put(field, (String) value); + } else if (value != null) { + throw new AtClientConfigException("AtKeys json field " + field + " is not a string"); + } + } + return map; + } + private static AtKeys createAtKeysVersion1(Map map) throws AtDecryptionException { String selfEncryptKey = mapGet(map, SELF_ENCRYPT_KEY); return AtKeys.builder() diff --git a/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java b/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java index dcddb6ae..e8534812 100644 --- a/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java @@ -7,13 +7,21 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.atsign.client.api.AtKeys; import org.atsign.client.api.AtSign; +import org.atsign.client.impl.exceptions.AtClientConfigException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.fasterxml.jackson.core.type.TypeReference; public class KeysUtilsTest { @@ -96,6 +104,161 @@ public void testLoadKeysFileLegacy() throws Exception { assertContentsMatch(keys, loadedKeys); } + @Test + public void savedKeysFileIsTheLegacyFlatShape(@TempDir Path dir) throws Exception { + File file = dir.resolve("keys.atKeys").toFile(); + + KeysUtils.saveKeys(newKeys(), file); + + // NOTE the legacy .atKeys shape at_auth reads. A "version" field makes at_auth 3.3.0 and + // later read the file as its typed keys document and refuse it. + assertEquals( + Set.of("aesEncryptPrivateKey", "aesEncryptPublicKey", "aesPkamPrivateKey", "aesPkamPublicKey", + "selfEncryptionKey"), + readJson(file).keySet()); + } + + @Test + public void loadsAFileWrittenWithAStringVersion(@TempDir Path dir) throws Exception { + File file = copyFixture("at_java_string_version.atKeys", dir); + + AtKeys keys = KeysUtils.loadKeys(file); + + assertEquals(readJson(file).get("selfEncryptionKey"), keys.getSelfEncryptKey()); + assertNotNull(keys.getApkamPrivateKey()); + assertNotNull(keys.getEncryptPrivateKey()); + } + + @Test + public void loadsTheFlatFieldsOfATypedDocumentThatAuthenticatesThroughThem(@TempDir Path dir) throws Exception { + File file = copyFixture("at_auth_4_0_0_rc2_typed_atsign_keys.atKeys", dir); + + AtKeys keys = KeysUtils.loadKeys(file); + + assertEquals("flat-enrollment-1", keys.getEnrollmentId().toString()); + assertEquals(readJson(file).get("selfEncryptionKey"), keys.getSelfEncryptKey()); + assertNotNull(keys.getApkamPrivateKey()); + } + + @Test + public void refusesATypedDocumentThatAuthenticatesThroughTypedMaterial(@TempDir Path dir) throws Exception { + File file = copyFixture("at_auth_4_0_0_rc2_typed_authentication.atKeys", dir); + + AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); + + assertTrue(e.getMessage().contains("typed-enrollment-2"), e.getMessage()); + } + + @Test + @SuppressWarnings("unchecked") + public void loadsATypedDocumentWhoseTypedAuthenticationIsNotActive(@TempDir Path dir) throws Exception { + File file = copyFixture("at_auth_4_0_0_rc2_typed_authentication.atKeys", dir); + Map json = readJson(file); + for (Object enrollment : (List) json.get("enrollments")) { + for (Object key : (List) ((Map) enrollment).get("keys")) { + for (Object material : (List) ((Map) key).get("material")) { + ((Map) material).put("status", "pending"); + } + } + } + writeJson(file, json); + + AtKeys keys = KeysUtils.loadKeys(file); + + assertEquals("flat-enrollment-1", keys.getEnrollmentId().toString()); + } + + @Test + public void refusesAnUnsupportedVersion(@TempDir Path dir) throws Exception { + File file = copyFixture("at_java_string_version.atKeys", dir); + Map json = readJson(file); + json.put("version", 2); + writeJson(file, json); + + AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); + + assertTrue(e.getMessage().contains("unsupported version"), e.getMessage()); + } + + @Test + public void refusesAFlatFieldThatIsNotAString(@TempDir Path dir) throws Exception { + File file = copyFixture("at_java_string_version.atKeys", dir); + Map json = readJson(file); + json.put("selfEncryptionKey", List.of("not", "a", "string")); + writeJson(file, json); + + AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); + + assertTrue(e.getMessage().contains("selfEncryptionKey"), e.getMessage()); + } + + @Test + public void refusesATypedDocumentWhoseEnrollmentsAreNotAList(@TempDir Path dir) throws Exception { + File file = copyFixture("at_auth_4_0_0_rc2_typed_authentication.atKeys", dir); + Map json = readJson(file); + json.put("enrollments", Map.of("enrollmentId", "typed-enrollment-2")); + writeJson(file, json); + + AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); + + assertTrue(e.getMessage().contains("enrollments"), e.getMessage()); + } + + @Test + public void refusesAFileThatIsNotAJsonObject(@TempDir Path dir) throws Exception { + File file = dir.resolve("keys.atKeys").toFile(); + Files.writeString(file.toPath(), "[]"); + + assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); + } + + @Test + public void saveKeysRefusesToOverwriteATypedDocument(@TempDir Path dir) throws Exception { + File file = copyFixture("at_auth_4_0_0_rc2_typed_atsign_keys.atKeys", dir); + byte[] before = Files.readAllBytes(file.toPath()); + + IOException e = assertThrows(IOException.class, () -> KeysUtils.saveKeys(newKeys(), file)); + + assertTrue(e.getMessage().contains("typed keys document"), e.getMessage()); + assertArrayEquals(before, Files.readAllBytes(file.toPath())); + } + + @Test + public void saveKeysOverwritesALegacyFile(@TempDir Path dir) throws Exception { + File file = copyFixture("at_java_string_version.atKeys", dir); + AtKeys keys = newKeys(); + + KeysUtils.saveKeys(keys, file); + + assertContentsMatch(keys, KeysUtils.loadKeys(file)); + assertFalse(readJson(file).containsKey("version")); + } + + private static AtKeys newKeys() throws Exception { + return AtKeys.builder() + .encryptKeyPair(generateRSAKeyPair()) + .apkamKeyPair(generateRSAKeyPair()) + .selfEncryptKey(generateAESKeyBase64()) + .build(); + } + + private static File copyFixture(String name, Path dir) throws Exception { + Path target = dir.resolve(name); + try (InputStream in = KeysUtilsTest.class.getResourceAsStream(name)) { + assertNotNull(in, "missing fixture " + name); + Files.copy(in, target); + } + return target.toFile(); + } + + private static Map readJson(File file) throws IOException { + return JsonUtils.readValue(Files.readString(file.toPath()), new TypeReference>() {}); + } + + private static void writeJson(File file, Map json) throws IOException { + Files.writeString(file.toPath(), JsonUtils.writeValueAsString(json)); + } + private static void assertContentsMatch(AtKeys keys1, AtKeys keys2) { assertEquals(keys1.getEnrollmentId(), keys2.getEnrollmentId()); assertEquals(keys1.getApkamPublicKey(), keys2.getApkamPublicKey()); diff --git a/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys b/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys new file mode 100644 index 00000000..fd4d183c --- /dev/null +++ b/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys @@ -0,0 +1 @@ +{"aesPkamPublicKey":"MrA8/lv4MT5VftzVBVDmxGg7YyPehTmBhrajj/p7jdBr53og3Gc2g14+Lzn5l+KL9O8T9RgkgKhCxGr/u7zF0kwMaJBHSWyGUWJJdBL78c9dp5dKc4IQ85BLv+/uZl/7lrwxr2deos1LwAsTS4ZEvJpNHTdNV9Bs5NjWgu7RNjB4CuLctz+I7BOvV5QTfmgk6KmSSEC0WgvzsCQF8S4i49Z3ka7168kiFR/ffHVg/pITesOiihXB2UAJWFtLEtLcP1F88pnXniywLjBC96E7CuogCinEBlFEwBJ75kwi9izhYrwfXxrYLVmrJYsLdADfxta9dBU0Ja2/bUS7xVLZAVFaPPQrQN4V4ijASHsnOfNTUOHTLYO3Uf57wpc+YL99jmC1mA+Zy53L6k9+IkZ+jxYPwXk41dtat+t8q1L/rvugomIFa++DksiE0aCSf9A1Nn3V2AEtrRkJfi4pTnXTDk2c5CaBmdeT+s5dgnDt/PEYkFgD85QR53O9YeHo/P9/kUXnsgEsCF8gZSb3CXnWjw==","aesPkamPrivateKey":"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","aesEncryptPublicKey":"MrA8/lv4MT5VftzVBVDmxGg7YyPehTmBhrajj/p7jdBr53og3Gc2g14+Lzn39+7K1e8Cpy83vqhsjCzcvJbMxTdIVJtecxepGi47RwDr38Ztna9nTaMX0fpy+MLRWRDQt71qyzNys+FC5jJtfPxDu4ZbPkQAaNFU6eOrp8OKBgc1Q+eagDa2gwnPMcpsT347xKKRPgKncheorCFbwREhyo8Rrq3ksNt5EnHybmVg+5t2R7OT3B7ioG5wFxRGScHwWDN0kbzR3QDHeitznIEpEtQAORrfAEcZ1EVkrwFD21/bOrM7eCviVXKuOKtHKT3xy9iGfHUIE63HJi2JnCLsc0xTOe4pOdQQ/n7Nb1g2PYtRarnsLuDVXPBi75ghT7hCrQOa9AeO2LSJ0kUYHBNHjiUC+GFn3vc97vtAqlG7sd65jyAoYc+Jv7zy39SNXNgzOlSK2R4OmCsuaRcIC3zdcWWHyCqkgtGx989Xq3PakeonqzQE99B37gSIW87U4bl10kXnsgEsCF8gZSb3CXnWjw==","aesEncryptPrivateKey":"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","selfEncryptionKey":"pe/oCV4p7fYjvsXcSM1oEMAjuAVXG6cXRJsy20Ty4MQ=","apkamSymmetricKey":"IrpVUIks7YCJn945Z1nIzatDiABaAz5BZKY0Sqjjrog=","enrollmentId":"flat-enrollment-1","version":1,"atsign":"@probeđź› ","keys":[],"atsignKeys":[{"keyId":"root:rsa2048:1","material":[{"role":"privateSigning","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:25.585320Z","status":"active","bytes":"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCD/Z9H1Jm0D1yBvQCL3iru/SgzT8BEOqIMEHrTvVpRptvCgyY3FZ/uXvmgbgOYiOOWK15A7y5DPUU7MJJTcL+Cq2z0uKnsXKvJSFUbVEQtN/h04ctB02nKMfHjMm+DRYQOletdyRazyKfum7cf6FgtvHGr05AujyHU15zXqkVym/jTenVJUskbG2gh0sF5BG/HS2E32MkpPQhT2jOFRxAtHH37e9vllZZk71nN1+c/xegYiIXLjjlWspMKunXho16yeyLhrfQXaUHF4cXR3dYuDhypRydC8cKK07VEEfkkJUrGAanjKe8H+mlO8O8scejg1FyQNvWLjy+8nrAceqHvAgMBAAECggEAHSYAa/OGch5GVpxvp3PL1XVw0EwXcLnmGXX2OhaCALAxrfYalWWgKRsuU4CYKLJqWDqBmSN4+v5n8JdcaTLoCCplaHHzjLA2e8AOk3d7iuTmaKGIqqPOpAl9mqgDumC6tiHjlZWX/uIcO/SFb3V3K7qG++Q/3hhb504qY/JFXfbDZ1PGUs//8qD1sAL4UqmoZYI6Kn2hne4cCQ+fthqxKPM3JinuFK08/S1jn3O8gsWVVEo9971bAopVi0FNJcSLZqX4tBDvCPtK1F5D6x1O+HyYSg0bLhnjf9L2aIIqXbeISXWEYD759K1+EY19wiOJeunlS0IeyBos8B+EyNdICQKBgQDcbp6yTiJ/xKPxBLh0SOIBu0nNE8d3MidNeX15GRLTFzox3Cy9M8u/GlC7nq8ylmwKtPU9lpJO0aGuWiEYUzOFKDfHDTMQJFxkYH9I7sYa7hUSkduG/MFgSyY9Axfzq/Nt/bGko/qc7iDZ9bSMrVBzKQsQx0ls4rhrcpzM2rbqwwKBgQCZScHRwhFoQ1150gpbzg3klvPyZ+MwDBFcjf2xq/94ZmlKjx08MAFRGiXCMzQG2WPo7IGthlf4LUYZADdLoxohVSytJD39DOMEol8jxIYM+Pr9M/9V20xtL2DyZknVE7FXLzCpktNf1UPett3oNSLYo7iqYM7uxOAWcfBlVDHBZQKBgBrIihnKKqDxqKXRWAQxEtz4/7eeTp8qBeKA/4DmTgY+DUEfqoP2tivrK6IQXuM7qbcHU8D++ZoAGLARnXHSMjFcuKJD9U9hy6QaoXygHowJH6BY/p6BOZHUr7m8FX3zB6n2qE4D6qCEwpMZKq3pJRxJAWIxhLdpONU9YNX3QUB3AoGAMXzUbAnBYO3OsdOTsIvXhGMPosTTXeGngT/+FWw38ADwITgO8i11X1+XrRimxUdNsBcazpC+ucq0BMJDF9IyT2yWHdfDg3yzlf1txbheprWDIle1KVzBUcan/9nyIC5ZZJJBnZ1fTGMnpDFZduwgsdw5dBgI9qDsz5I9UBlcbtkCgYEAujwBK+LAqSZDxCxTZJcX8FEYcN1IgRPejPTHa7tcTXgSIFvbrh7ckiwB0ifhkq5Ui0aduR3e+HQ93XlVpnvVafn1X4BA22DN9uEmVID09LzImt5E9NSZAqGi2cRLgvG0JMPJA5j1IQCzMQDVBfQ3MNodhep02T49g4G7+SdQiL4="},{"role":"publicVerification","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:25.585320Z","status":"active","bytes":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg/2fR9SZtA9cgb0Ai94q7v0oM0/ARDqiDBB6071aUabbwoMmNxWf7l75oG4DmIjjliteQO8uQz1FOzCSU3C/gqts9Lip7FyryUhVG1RELTf4dOHLQdNpyjHx4zJvg0WEDpXrXckWs8in7pu3H+hYLbxxq9OQLo8h1Nec16pFcpv403p1SVLJGxtoIdLBeQRvx0thN9jJKT0IU9ozhUcQLRx9+3vb5ZWWZO9ZzdfnP8XoGIiFy445VrKTCrp14aNesnsi4a30F2lBxeHF0d3WLg4cqUcnQvHCitO1RBH5JCVKxgGp4ynvB/ppTvDvLHHo4NRckDb1i48vvJ6wHHqh7wIDAQAB"}]}]} \ No newline at end of file diff --git a/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys b/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys new file mode 100644 index 00000000..eaa38ee8 --- /dev/null +++ b/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys @@ -0,0 +1 @@ +{"aesPkamPublicKey":"05hc78F0jsAsZ/Rm00/9P+e5CYPpY+gj7Jy3eogrUmK0wL2ZzOtf56iGfQ84fuV3vOZRCVOC5EA/slWkoFPu+2NEdVuw3hNRvNlsAPHb/n7PtHWDWK9GRnA6I8oKlaHRd4NyLo++n7jmPqFNCJs86yj9lMnaQW/zPu+maXtNzqWGYbF0+aRjIkQm0TmV//xKTHi+ysfDoaOrR8BEVc3riGw2oAiiBR4VXkwwsg0tU9v9yIcqs54ysCLQcQKrVNFfuSSXDPUd/UlIfAqzvQsc8ADt5tziiCQtklkRgCv4RMju91PwyHKHBqSV4XQdRknIxR+ZyAE/gPLUQiU8iDM2nzU/bB8X5eUoqYY2K9grD7JKdvRnG+bTjJXPUq6O2MBS/iYalc1ZvpotHNt8xTekyx6A3mnJafiRjPbFItffZhcUkIa+wSEK3/W0zqyUUCCS9ff8rnN0yy1WUX4jthFF7Pwo45U1XkGcO0j5LCc5GdrAkfhJ0XwKpzq0VkSB2UbzHt7M5qeLGnFMRRtiO/rxgg==","aesPkamPrivateKey":"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","aesEncryptPublicKey":"05hc78F0jsAsZ/Rm00/9P+e5CYPpY+gj7Jy3eogrUmK0wL2ZzOtf56iGfQ92DPgnzuxuG3u07gBGtnuDowLsx1k+N0uwightvNBJCeaO/ETnyk+/ZspdAx9VBYtF67jkX+EFLejB3fnpYpFnZNxUqhLDj7TwQ274N+3oY1IU7LSQX8d1yeRECWU/zD2s4Z1zNWeE6PPgiquCTL5mMM3xiREUrRq9Zgc9aDctkg0Hb8PP75RLnu1n00DAagK6AuZehS3JBsYp9ktufQzXvxs88kDR7PPc+hkymG9wx1Hheemf32rB83KQD9X/mA1KdEXMrR6NgjBVqsbAYyUxnSEwmF0+XUU1r5cI6MdIGcM/YsBsRLpvBc/19r2YV6Sl9eBbp2AWruc+mZphHI5P21jWzHndwTnqafvWhZLsPPD1Q1xwrsOblioa8fLLs4aSVD2fs/LhlWRXt392XVUnrUJhvtJX5as3W2PGCFaDZi8aOInYkrxL1EMEgxKaVkSS8xbLBPjM5qeLGnFMRRtiO/rxgg==","aesEncryptPrivateKey":"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","selfEncryptionKey":"o9APyqp7FirG0BYysGsjOwp9GG3xeOOJhS5HbdA3VrU=","apkamSymmetricKey":"bmYkhaYpN2z3MiHlJLocM5jBxfAyk94baAafXRWSwNA=","enrollmentId":"flat-enrollment-1","version":1,"atsign":"@probeđź› ","keys":[],"enrollments":[{"enrollmentId":"typed-enrollment-2","keys":[{"keyId":"auth:rsa2048:1","material":[{"role":"privateAuthentication","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:23.939393Z","status":"active","bytes":"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5pEByqLEwQbfGa5pxc7rOJ0Hrv5O96QyKN5EOxgWKPceQySi7b00LIgW9NhsV018iaL8cMpUiaRRC2POAURjgQFHQnTVeBNoBMLYNOljsRBG+ZltkNYXpXTGs6XEBsgDb1yt84YBgVOL1swTXWdSmrUosdCBzrcEd5qTkSYSSTCFcfr2DjDgcE+TGo98gJOLs3zSV9jFumI1aldrT5qCX1FEUAt27e6Qa67jVb/oNs98k28a/qdaKysk7wntf14grDyrjWEn5sjHNiFkBHZmq1bxeVjAQN+zWiklONIyu4oN7kzF4Zwb/Iqmmfan1lJw1aJIFgfHhCvJIk8JIElAZAgMBAAECggEBAI7FzzcfnB5z/IXizcYBbqMMODu6fELrkFRgxfQpwar/NpOY0fXNdOBiCy3pBc20YY9SJeDm2iG71PRQbH28YThe301lCt1DN/MAZpMn06np9AeOKy2XqGTA4uqMIvhd0SFCi9SgjZ4IywCR7OA+2zhRQCGOMkMlV8fKUfisvgPQrMmAyXMtVQC2WqkFNegfuFXgG9/nNO/fTKLtQFE0wBHDSwjyiz+BSU9Zp93KN2z0BC2oi4KIgQQs8Rph2Hl+XFNK4gxarAgByefVjWoXibKP7MOn8rNkv7/5BfHlbsYVJeQS9p0lES+wN3Wv8/1n8hFBbO67otOQtXa+/PslWHECgYEA+dnAlv7qdExEkEemahu9azW5BW+ohOYa7OxLuJZ2jsgiIjC3ZJSTER1saalvXBQTeGuv8D7i6XMF+/rzRXm7B8JYoxT2ELZnn0UozyoUfoJavcSLqyhCEvidNGrOiB7HlFp+7O5GeNIxxC0/2lwYyLrZ4gBA6E8TdOixv+ChC50CgYEAvjXvLH8Pr1+WD84OdlDUqkeQoefpa01noG3+3apNU8+mAnmu1DGl4xQnuYzzfd/jgp/QGVkiiM7yA6DCHMQVG36xstW1iJl1YRHTVv1+lfwOSSNb5TarFiWJdhYyhyiCCgl2V0ffVqmikPqSllOCgK3IRJj4euncv5Ke0eITI60CgYBHol0Lbc7+xRcrXr+h0I5AfMitfFQOtoKlxyH2rH0VduACeYE7PzXjRFJfAQtClSb38bgh44XfdPEUS+EP3FT5ZolPSKU8KXAQRB3tN51koC8wFXgN/J7lHY7FBpJsiJakPEYD3FUsMue93VNfYeRhOOFbYMM7V+C2NjkwUFEcKQKBgDiRy88c/uo2cGfE7bIsegMmg5JTaa1a+P3SmOEN/C936zSDQgajuWVeoi3iCKygAIbhxr8zLj0n5vxDPFfXzTs/I9Q/EN1OXoV/Cq6td49dxlXkKTlzV9CfW1F+aoqd9obLp6Fvo7Gfg9TAYQZKUd65UH1uTUAo+1kSXx+mr/CFAoGAdvNbO60+rbhO24sQ+qAlu36WGR1FFmKme6WGKd19z2ojjsrKOyCxaTaVPC9PDzEbgozhojdzFZK1LlU1yX9V7lJfOwtKLKlac99w1yFquGVrT4B4grUdCSZviU2R92uSgXfts4/ZTDzOmE24QaF6w5AwFqiNOkL6W1cyiW8/OfY="},{"role":"publicAuthentication","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:23.939393Z","status":"active","bytes":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuaRAcqixMEG3xmuacXO6zidB67+TvekMijeRDsYFij3HkMkou29NCyIFvTYbFdNfImi/HDKVImkUQtjzgFEY4EBR0J01XgTaATC2DTpY7EQRvmZbZDWF6V0xrOlxAbIA29crfOGAYFTi9bME11nUpq1KLHQgc63BHeak5EmEkkwhXH69g4w4HBPkxqPfICTi7N80lfYxbpiNWpXa0+agl9RRFALdu3ukGuu41W/6DbPfJNvGv6nWisrJO8J7X9eIKw8q41hJ+bIxzYhZAR2ZqtW8XlYwEDfs1opJTjSMruKDe5MxeGcG/yKppn2p9ZScNWiSBYHx4QrySJPCSBJQGQIDAQAB"}]}]}]} \ No newline at end of file diff --git a/at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys b/at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys new file mode 100644 index 00000000..b804eb15 --- /dev/null +++ b/at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys @@ -0,0 +1,8 @@ +{ + "aesEncryptPrivateKey" : "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", + "aesEncryptPublicKey" : "r5wM9BxkuFb347lG1oxK0TgCdY/hom/6QXfws8hT3jX2E2bTvJQ3oLN7/FxbjNPqkIILGRzbMRLm61SM63Sq21S6XCkXbIPps72f0/WnYnsSKllivBe6qDPV+9umXGtce6k3TvZ/+zEMDi2WJPzLaRm/MXUySO/ajjuN6ufESc+psy2n6UWfV173ZEf0KbvKfpxEI8tDCohwjloTRqI+GMP4oxZAPGQJnF9I4s3Y3hLlMHgWt0Q3XqG6WJLmfU0ZkvnMrwcLoLn92ipc/Dl476E8/7DEiQ5HbTFocIpgs8eEwTtw0qUSZwky6DwJIXQWy+8ilBMoZkDZOWdr0pTXgUia+BinQB7BwG0clOvyIjVh86XYWsaLQdp1IPqtkYrwQKjR8OLylurumfd/0KiO77zcZ6IoJIckezO/AuBnoMiFV1XNkUMZcWyGdAqNlLXVMouOuwSzvwGmhkiCulQEMdB01grpxtV2ryTpclDiLcttknbRRhntoQR+liOuMz+xTS3r9Ahl5iXLHsmUuy9YhQ==", + "aesPkamPrivateKey" : "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", + "aesPkamPublicKey" : "r5wM9BxkuFb347lG1oxK0TgCdY/hom/6QXfws8hT3jX2E2bTvJQ3oLN7/FxcnOrngqw5CSf5BSamiQTx437E2wWMQ3A5LZX7sLiGrJr+alBQRmNdvUD8qTLn9tOxXFtCbrojAuAt5Tg/KiysEsjQdgiFbXJwNvTWoHK+66XFRtey9kGZ/XLsDy+rFxj/fZm/X6k6FsElAqg23wxxQ8Z2FO/dhxFCf3MdtSIzodOj1AfuTmcHpkYNa+mdJcK7VlIRupKiwXAxjay8iQZ7h38wy50Y/uae6FswaTJGBbJ4nuCQyQghz8w+Hwwq3G8ebVghy+gH3gggc0XvTw1S6MyLgFKE5SvVVRHDujpu2uf5PykW0IL4bOyCXfZRBPiMkKLxTv35mvalsurnlIlypbawtfvEZ4sKV6xiKg3SKtJtusr3FH3akEIhFFGIBSDzorjbbqDqtTiIkB63qFzellIQMu0hlRPrqeR1vAHSfk/eMYc/5jWOTx3h0xt99X3QBSawYgvr9Ahl5iXLHsmUuy9YhQ==", + "selfEncryptionKey" : "Yr5dpVWRaEP+fNngk6QvFM2LdxrApxUZTS47Y6Nk7EY=", + "version" : "1" +} \ No newline at end of file From 7a3d8524c9dc115de3e4224d31858fe48c156b7a Mon Sep 17 00:00:00 2001 From: gkc Date: Fri, 2 Oct 2026 12:38:30 +0100 Subject: [PATCH 2/2] test: generate the keyfile fixtures in the test The tests now write each keyfile they read instead of copying committed .atKeys files. KeysUtils.saveKeys writes the flat fields from freshly generated keys, and each test adds the fields its case needs: the string version earlier releases wrote, or the typed containers at_auth 4.0.0-rc2 writes beside the flat fields. The typed containers spell at_auth's field names and roles as raw literals, so a change to KeysUtils's constants cannot move them. Their structure matches the at_auth 4.0.0-rc2 keyfiles they replace, path for path, and the reader tests now check every flat field round-trips. --- .../client/impl/util/KeysUtilsTest.java | 115 ++++++++++-------- ...at_auth_4_0_0_rc2_typed_atsign_keys.atKeys | 1 - ...auth_4_0_0_rc2_typed_authentication.atKeys | 1 - .../impl/util/at_java_string_version.atKeys | 8 -- 4 files changed, 66 insertions(+), 59 deletions(-) delete mode 100644 at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys delete mode 100644 at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys delete mode 100644 at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys diff --git a/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java b/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java index e8534812..821ab270 100644 --- a/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java +++ b/at_client/src/test/java/org/atsign/client/impl/util/KeysUtilsTest.java @@ -1,15 +1,19 @@ package org.atsign.client.impl.util; import static org.atsign.client.api.AtSign.createAtSign; +import static org.atsign.client.impl.common.EnrollmentId.createEnrollmentId; import static org.atsign.client.impl.util.EncryptionUtils.generateAESKeyBase64; import static org.atsign.client.impl.util.EncryptionUtils.generateRSAKeyPair; +import static org.atsign.client.impl.util.EncryptionUtils.toStringBase64; import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.security.Key; +import java.security.KeyPair; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Set; @@ -120,29 +124,23 @@ public void savedKeysFileIsTheLegacyFlatShape(@TempDir Path dir) throws Exceptio @Test public void loadsAFileWrittenWithAStringVersion(@TempDir Path dir) throws Exception { - File file = copyFixture("at_java_string_version.atKeys", dir); - - AtKeys keys = KeysUtils.loadKeys(file); + AtKeys keys = newKeys(); + File file = writeKeysFile(keys, dir, Map.of("version", "1")); - assertEquals(readJson(file).get("selfEncryptionKey"), keys.getSelfEncryptKey()); - assertNotNull(keys.getApkamPrivateKey()); - assertNotNull(keys.getEncryptPrivateKey()); + assertContentsMatch(keys, KeysUtils.loadKeys(file)); } @Test public void loadsTheFlatFieldsOfATypedDocumentThatAuthenticatesThroughThem(@TempDir Path dir) throws Exception { - File file = copyFixture("at_auth_4_0_0_rc2_typed_atsign_keys.atKeys", dir); - - AtKeys keys = KeysUtils.loadKeys(file); + AtKeys keys = newEnrolledKeys(); + File file = writeKeysFile(keys, dir, typedAtSignKeysDocument()); - assertEquals("flat-enrollment-1", keys.getEnrollmentId().toString()); - assertEquals(readJson(file).get("selfEncryptionKey"), keys.getSelfEncryptKey()); - assertNotNull(keys.getApkamPrivateKey()); + assertContentsMatch(keys, KeysUtils.loadKeys(file)); } @Test public void refusesATypedDocumentThatAuthenticatesThroughTypedMaterial(@TempDir Path dir) throws Exception { - File file = copyFixture("at_auth_4_0_0_rc2_typed_authentication.atKeys", dir); + File file = writeKeysFile(newEnrolledKeys(), dir, typedAuthenticationDocument("active")); AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); @@ -150,30 +148,16 @@ public void refusesATypedDocumentThatAuthenticatesThroughTypedMaterial(@TempDir } @Test - @SuppressWarnings("unchecked") public void loadsATypedDocumentWhoseTypedAuthenticationIsNotActive(@TempDir Path dir) throws Exception { - File file = copyFixture("at_auth_4_0_0_rc2_typed_authentication.atKeys", dir); - Map json = readJson(file); - for (Object enrollment : (List) json.get("enrollments")) { - for (Object key : (List) ((Map) enrollment).get("keys")) { - for (Object material : (List) ((Map) key).get("material")) { - ((Map) material).put("status", "pending"); - } - } - } - writeJson(file, json); - - AtKeys keys = KeysUtils.loadKeys(file); + AtKeys keys = newEnrolledKeys(); + File file = writeKeysFile(keys, dir, typedAuthenticationDocument("pending")); - assertEquals("flat-enrollment-1", keys.getEnrollmentId().toString()); + assertContentsMatch(keys, KeysUtils.loadKeys(file)); } @Test public void refusesAnUnsupportedVersion(@TempDir Path dir) throws Exception { - File file = copyFixture("at_java_string_version.atKeys", dir); - Map json = readJson(file); - json.put("version", 2); - writeJson(file, json); + File file = writeKeysFile(newKeys(), dir, Map.of("version", 2)); AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); @@ -182,10 +166,7 @@ public void refusesAnUnsupportedVersion(@TempDir Path dir) throws Exception { @Test public void refusesAFlatFieldThatIsNotAString(@TempDir Path dir) throws Exception { - File file = copyFixture("at_java_string_version.atKeys", dir); - Map json = readJson(file); - json.put("selfEncryptionKey", List.of("not", "a", "string")); - writeJson(file, json); + File file = writeKeysFile(newKeys(), dir, Map.of("selfEncryptionKey", List.of("not", "a", "string"))); AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); @@ -194,10 +175,8 @@ public void refusesAFlatFieldThatIsNotAString(@TempDir Path dir) throws Exceptio @Test public void refusesATypedDocumentWhoseEnrollmentsAreNotAList(@TempDir Path dir) throws Exception { - File file = copyFixture("at_auth_4_0_0_rc2_typed_authentication.atKeys", dir); - Map json = readJson(file); - json.put("enrollments", Map.of("enrollmentId", "typed-enrollment-2")); - writeJson(file, json); + File file = writeKeysFile(newEnrolledKeys(), dir, + typedDocument("enrollments", Map.of("enrollmentId", "typed-enrollment-2"))); AtClientConfigException e = assertThrows(AtClientConfigException.class, () -> KeysUtils.loadKeys(file)); @@ -214,7 +193,7 @@ public void refusesAFileThatIsNotAJsonObject(@TempDir Path dir) throws Exception @Test public void saveKeysRefusesToOverwriteATypedDocument(@TempDir Path dir) throws Exception { - File file = copyFixture("at_auth_4_0_0_rc2_typed_atsign_keys.atKeys", dir); + File file = writeKeysFile(newEnrolledKeys(), dir, typedAtSignKeysDocument()); byte[] before = Files.readAllBytes(file.toPath()); IOException e = assertThrows(IOException.class, () -> KeysUtils.saveKeys(newKeys(), file)); @@ -225,7 +204,7 @@ public void saveKeysRefusesToOverwriteATypedDocument(@TempDir Path dir) throws E @Test public void saveKeysOverwritesALegacyFile(@TempDir Path dir) throws Exception { - File file = copyFixture("at_java_string_version.atKeys", dir); + File file = writeKeysFile(newKeys(), dir, Map.of("version", "1")); AtKeys keys = newKeys(); KeysUtils.saveKeys(keys, file); @@ -242,13 +221,51 @@ private static AtKeys newKeys() throws Exception { .build(); } - private static File copyFixture(String name, Path dir) throws Exception { - Path target = dir.resolve(name); - try (InputStream in = KeysUtilsTest.class.getResourceAsStream(name)) { - assertNotNull(in, "missing fixture " + name); - Files.copy(in, target); - } - return target.toFile(); + private static AtKeys newEnrolledKeys() throws Exception { + return newKeys().toBuilder() + .enrollmentId(createEnrollmentId("flat-enrollment-1")) + .apkamSymmetricKey(generateAESKeyBase64()) + .build(); + } + + private static File writeKeysFile(AtKeys keys, Path dir, Map fields) throws Exception { + File file = dir.resolve("keys.atKeys").toFile(); + KeysUtils.saveKeys(keys, file); + Map json = readJson(file); + json.putAll(fields); + writeJson(file, json); + return file; + } + + // NOTE the typed keys document at_auth 4.0.0-rc2 writes beside the flat fields. These spellings are + // at_auth's, so they stay raw literals rather than references to KeysUtils. + private static Map typedDocument(String container, Object entries) { + return Map.of("version", 1, "atsign", "@alice", "keys", List.of(), container, entries); + } + + private static Map typedAtSignKeysDocument() throws Exception { + return typedDocument("atsignKeys", + List.of(typedKey("root:rsa2048:1", "privateSigning", "publicVerification", "active"))); + } + + private static Map typedAuthenticationDocument(String status) throws Exception { + return typedDocument("enrollments", + List.of(Map.of("enrollmentId", "typed-enrollment-2", + "keys", List.of(typedKey("auth:rsa2048:1", "privateAuthentication", + "publicAuthentication", status))))); + } + + private static Map typedKey(String keyId, String privateRole, String publicRole, String status) + throws Exception { + KeyPair keyPair = generateRSAKeyPair(); + return Map.of("keyId", keyId, + "material", List.of(typedMaterial(privateRole, keyPair.getPrivate(), status), + typedMaterial(publicRole, keyPair.getPublic(), status))); + } + + private static Map typedMaterial(String role, Key key, String status) { + return Map.of("role", role, "algorithm", "rsa2048", "createdAt", Instant.now().toString(), "status", status, + "bytes", toStringBase64(key)); } private static Map readJson(File file) throws IOException { diff --git a/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys b/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys deleted file mode 100644 index fd4d183c..00000000 --- a/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_atsign_keys.atKeys +++ /dev/null @@ -1 +0,0 @@ -{"aesPkamPublicKey":"MrA8/lv4MT5VftzVBVDmxGg7YyPehTmBhrajj/p7jdBr53og3Gc2g14+Lzn5l+KL9O8T9RgkgKhCxGr/u7zF0kwMaJBHSWyGUWJJdBL78c9dp5dKc4IQ85BLv+/uZl/7lrwxr2deos1LwAsTS4ZEvJpNHTdNV9Bs5NjWgu7RNjB4CuLctz+I7BOvV5QTfmgk6KmSSEC0WgvzsCQF8S4i49Z3ka7168kiFR/ffHVg/pITesOiihXB2UAJWFtLEtLcP1F88pnXniywLjBC96E7CuogCinEBlFEwBJ75kwi9izhYrwfXxrYLVmrJYsLdADfxta9dBU0Ja2/bUS7xVLZAVFaPPQrQN4V4ijASHsnOfNTUOHTLYO3Uf57wpc+YL99jmC1mA+Zy53L6k9+IkZ+jxYPwXk41dtat+t8q1L/rvugomIFa++DksiE0aCSf9A1Nn3V2AEtrRkJfi4pTnXTDk2c5CaBmdeT+s5dgnDt/PEYkFgD85QR53O9YeHo/P9/kUXnsgEsCF8gZSb3CXnWjw==","aesPkamPrivateKey":"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","aesEncryptPublicKey":"MrA8/lv4MT5VftzVBVDmxGg7YyPehTmBhrajj/p7jdBr53og3Gc2g14+Lzn39+7K1e8Cpy83vqhsjCzcvJbMxTdIVJtecxepGi47RwDr38Ztna9nTaMX0fpy+MLRWRDQt71qyzNys+FC5jJtfPxDu4ZbPkQAaNFU6eOrp8OKBgc1Q+eagDa2gwnPMcpsT347xKKRPgKncheorCFbwREhyo8Rrq3ksNt5EnHybmVg+5t2R7OT3B7ioG5wFxRGScHwWDN0kbzR3QDHeitznIEpEtQAORrfAEcZ1EVkrwFD21/bOrM7eCviVXKuOKtHKT3xy9iGfHUIE63HJi2JnCLsc0xTOe4pOdQQ/n7Nb1g2PYtRarnsLuDVXPBi75ghT7hCrQOa9AeO2LSJ0kUYHBNHjiUC+GFn3vc97vtAqlG7sd65jyAoYc+Jv7zy39SNXNgzOlSK2R4OmCsuaRcIC3zdcWWHyCqkgtGx989Xq3PakeonqzQE99B37gSIW87U4bl10kXnsgEsCF8gZSb3CXnWjw==","aesEncryptPrivateKey":"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","selfEncryptionKey":"pe/oCV4p7fYjvsXcSM1oEMAjuAVXG6cXRJsy20Ty4MQ=","apkamSymmetricKey":"IrpVUIks7YCJn945Z1nIzatDiABaAz5BZKY0Sqjjrog=","enrollmentId":"flat-enrollment-1","version":1,"atsign":"@probeđź› ","keys":[],"atsignKeys":[{"keyId":"root:rsa2048:1","material":[{"role":"privateSigning","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:25.585320Z","status":"active","bytes":"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCD/Z9H1Jm0D1yBvQCL3iru/SgzT8BEOqIMEHrTvVpRptvCgyY3FZ/uXvmgbgOYiOOWK15A7y5DPUU7MJJTcL+Cq2z0uKnsXKvJSFUbVEQtN/h04ctB02nKMfHjMm+DRYQOletdyRazyKfum7cf6FgtvHGr05AujyHU15zXqkVym/jTenVJUskbG2gh0sF5BG/HS2E32MkpPQhT2jOFRxAtHH37e9vllZZk71nN1+c/xegYiIXLjjlWspMKunXho16yeyLhrfQXaUHF4cXR3dYuDhypRydC8cKK07VEEfkkJUrGAanjKe8H+mlO8O8scejg1FyQNvWLjy+8nrAceqHvAgMBAAECggEAHSYAa/OGch5GVpxvp3PL1XVw0EwXcLnmGXX2OhaCALAxrfYalWWgKRsuU4CYKLJqWDqBmSN4+v5n8JdcaTLoCCplaHHzjLA2e8AOk3d7iuTmaKGIqqPOpAl9mqgDumC6tiHjlZWX/uIcO/SFb3V3K7qG++Q/3hhb504qY/JFXfbDZ1PGUs//8qD1sAL4UqmoZYI6Kn2hne4cCQ+fthqxKPM3JinuFK08/S1jn3O8gsWVVEo9971bAopVi0FNJcSLZqX4tBDvCPtK1F5D6x1O+HyYSg0bLhnjf9L2aIIqXbeISXWEYD759K1+EY19wiOJeunlS0IeyBos8B+EyNdICQKBgQDcbp6yTiJ/xKPxBLh0SOIBu0nNE8d3MidNeX15GRLTFzox3Cy9M8u/GlC7nq8ylmwKtPU9lpJO0aGuWiEYUzOFKDfHDTMQJFxkYH9I7sYa7hUSkduG/MFgSyY9Axfzq/Nt/bGko/qc7iDZ9bSMrVBzKQsQx0ls4rhrcpzM2rbqwwKBgQCZScHRwhFoQ1150gpbzg3klvPyZ+MwDBFcjf2xq/94ZmlKjx08MAFRGiXCMzQG2WPo7IGthlf4LUYZADdLoxohVSytJD39DOMEol8jxIYM+Pr9M/9V20xtL2DyZknVE7FXLzCpktNf1UPett3oNSLYo7iqYM7uxOAWcfBlVDHBZQKBgBrIihnKKqDxqKXRWAQxEtz4/7eeTp8qBeKA/4DmTgY+DUEfqoP2tivrK6IQXuM7qbcHU8D++ZoAGLARnXHSMjFcuKJD9U9hy6QaoXygHowJH6BY/p6BOZHUr7m8FX3zB6n2qE4D6qCEwpMZKq3pJRxJAWIxhLdpONU9YNX3QUB3AoGAMXzUbAnBYO3OsdOTsIvXhGMPosTTXeGngT/+FWw38ADwITgO8i11X1+XrRimxUdNsBcazpC+ucq0BMJDF9IyT2yWHdfDg3yzlf1txbheprWDIle1KVzBUcan/9nyIC5ZZJJBnZ1fTGMnpDFZduwgsdw5dBgI9qDsz5I9UBlcbtkCgYEAujwBK+LAqSZDxCxTZJcX8FEYcN1IgRPejPTHa7tcTXgSIFvbrh7ckiwB0ifhkq5Ui0aduR3e+HQ93XlVpnvVafn1X4BA22DN9uEmVID09LzImt5E9NSZAqGi2cRLgvG0JMPJA5j1IQCzMQDVBfQ3MNodhep02T49g4G7+SdQiL4="},{"role":"publicVerification","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:25.585320Z","status":"active","bytes":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg/2fR9SZtA9cgb0Ai94q7v0oM0/ARDqiDBB6071aUabbwoMmNxWf7l75oG4DmIjjliteQO8uQz1FOzCSU3C/gqts9Lip7FyryUhVG1RELTf4dOHLQdNpyjHx4zJvg0WEDpXrXckWs8in7pu3H+hYLbxxq9OQLo8h1Nec16pFcpv403p1SVLJGxtoIdLBeQRvx0thN9jJKT0IU9ozhUcQLRx9+3vb5ZWWZO9ZzdfnP8XoGIiFy445VrKTCrp14aNesnsi4a30F2lBxeHF0d3WLg4cqUcnQvHCitO1RBH5JCVKxgGp4ynvB/ppTvDvLHHo4NRckDb1i48vvJ6wHHqh7wIDAQAB"}]}]} \ No newline at end of file diff --git a/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys b/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys deleted file mode 100644 index eaa38ee8..00000000 --- a/at_client/src/test/resources/org/atsign/client/impl/util/at_auth_4_0_0_rc2_typed_authentication.atKeys +++ /dev/null @@ -1 +0,0 @@ -{"aesPkamPublicKey":"05hc78F0jsAsZ/Rm00/9P+e5CYPpY+gj7Jy3eogrUmK0wL2ZzOtf56iGfQ84fuV3vOZRCVOC5EA/slWkoFPu+2NEdVuw3hNRvNlsAPHb/n7PtHWDWK9GRnA6I8oKlaHRd4NyLo++n7jmPqFNCJs86yj9lMnaQW/zPu+maXtNzqWGYbF0+aRjIkQm0TmV//xKTHi+ysfDoaOrR8BEVc3riGw2oAiiBR4VXkwwsg0tU9v9yIcqs54ysCLQcQKrVNFfuSSXDPUd/UlIfAqzvQsc8ADt5tziiCQtklkRgCv4RMju91PwyHKHBqSV4XQdRknIxR+ZyAE/gPLUQiU8iDM2nzU/bB8X5eUoqYY2K9grD7JKdvRnG+bTjJXPUq6O2MBS/iYalc1ZvpotHNt8xTekyx6A3mnJafiRjPbFItffZhcUkIa+wSEK3/W0zqyUUCCS9ff8rnN0yy1WUX4jthFF7Pwo45U1XkGcO0j5LCc5GdrAkfhJ0XwKpzq0VkSB2UbzHt7M5qeLGnFMRRtiO/rxgg==","aesPkamPrivateKey":"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","aesEncryptPublicKey":"05hc78F0jsAsZ/Rm00/9P+e5CYPpY+gj7Jy3eogrUmK0wL2ZzOtf56iGfQ92DPgnzuxuG3u07gBGtnuDowLsx1k+N0uwightvNBJCeaO/ETnyk+/ZspdAx9VBYtF67jkX+EFLejB3fnpYpFnZNxUqhLDj7TwQ274N+3oY1IU7LSQX8d1yeRECWU/zD2s4Z1zNWeE6PPgiquCTL5mMM3xiREUrRq9Zgc9aDctkg0Hb8PP75RLnu1n00DAagK6AuZehS3JBsYp9ktufQzXvxs88kDR7PPc+hkymG9wx1Hheemf32rB83KQD9X/mA1KdEXMrR6NgjBVqsbAYyUxnSEwmF0+XUU1r5cI6MdIGcM/YsBsRLpvBc/19r2YV6Sl9eBbp2AWruc+mZphHI5P21jWzHndwTnqafvWhZLsPPD1Q1xwrsOblioa8fLLs4aSVD2fs/LhlWRXt392XVUnrUJhvtJX5as3W2PGCFaDZi8aOInYkrxL1EMEgxKaVkSS8xbLBPjM5qeLGnFMRRtiO/rxgg==","aesEncryptPrivateKey":"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","selfEncryptionKey":"o9APyqp7FirG0BYysGsjOwp9GG3xeOOJhS5HbdA3VrU=","apkamSymmetricKey":"bmYkhaYpN2z3MiHlJLocM5jBxfAyk94baAafXRWSwNA=","enrollmentId":"flat-enrollment-1","version":1,"atsign":"@probeđź› ","keys":[],"enrollments":[{"enrollmentId":"typed-enrollment-2","keys":[{"keyId":"auth:rsa2048:1","material":[{"role":"privateAuthentication","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:23.939393Z","status":"active","bytes":"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5pEByqLEwQbfGa5pxc7rOJ0Hrv5O96QyKN5EOxgWKPceQySi7b00LIgW9NhsV018iaL8cMpUiaRRC2POAURjgQFHQnTVeBNoBMLYNOljsRBG+ZltkNYXpXTGs6XEBsgDb1yt84YBgVOL1swTXWdSmrUosdCBzrcEd5qTkSYSSTCFcfr2DjDgcE+TGo98gJOLs3zSV9jFumI1aldrT5qCX1FEUAt27e6Qa67jVb/oNs98k28a/qdaKysk7wntf14grDyrjWEn5sjHNiFkBHZmq1bxeVjAQN+zWiklONIyu4oN7kzF4Zwb/Iqmmfan1lJw1aJIFgfHhCvJIk8JIElAZAgMBAAECggEBAI7FzzcfnB5z/IXizcYBbqMMODu6fELrkFRgxfQpwar/NpOY0fXNdOBiCy3pBc20YY9SJeDm2iG71PRQbH28YThe301lCt1DN/MAZpMn06np9AeOKy2XqGTA4uqMIvhd0SFCi9SgjZ4IywCR7OA+2zhRQCGOMkMlV8fKUfisvgPQrMmAyXMtVQC2WqkFNegfuFXgG9/nNO/fTKLtQFE0wBHDSwjyiz+BSU9Zp93KN2z0BC2oi4KIgQQs8Rph2Hl+XFNK4gxarAgByefVjWoXibKP7MOn8rNkv7/5BfHlbsYVJeQS9p0lES+wN3Wv8/1n8hFBbO67otOQtXa+/PslWHECgYEA+dnAlv7qdExEkEemahu9azW5BW+ohOYa7OxLuJZ2jsgiIjC3ZJSTER1saalvXBQTeGuv8D7i6XMF+/rzRXm7B8JYoxT2ELZnn0UozyoUfoJavcSLqyhCEvidNGrOiB7HlFp+7O5GeNIxxC0/2lwYyLrZ4gBA6E8TdOixv+ChC50CgYEAvjXvLH8Pr1+WD84OdlDUqkeQoefpa01noG3+3apNU8+mAnmu1DGl4xQnuYzzfd/jgp/QGVkiiM7yA6DCHMQVG36xstW1iJl1YRHTVv1+lfwOSSNb5TarFiWJdhYyhyiCCgl2V0ffVqmikPqSllOCgK3IRJj4euncv5Ke0eITI60CgYBHol0Lbc7+xRcrXr+h0I5AfMitfFQOtoKlxyH2rH0VduACeYE7PzXjRFJfAQtClSb38bgh44XfdPEUS+EP3FT5ZolPSKU8KXAQRB3tN51koC8wFXgN/J7lHY7FBpJsiJakPEYD3FUsMue93VNfYeRhOOFbYMM7V+C2NjkwUFEcKQKBgDiRy88c/uo2cGfE7bIsegMmg5JTaa1a+P3SmOEN/C936zSDQgajuWVeoi3iCKygAIbhxr8zLj0n5vxDPFfXzTs/I9Q/EN1OXoV/Cq6td49dxlXkKTlzV9CfW1F+aoqd9obLp6Fvo7Gfg9TAYQZKUd65UH1uTUAo+1kSXx+mr/CFAoGAdvNbO60+rbhO24sQ+qAlu36WGR1FFmKme6WGKd19z2ojjsrKOyCxaTaVPC9PDzEbgozhojdzFZK1LlU1yX9V7lJfOwtKLKlac99w1yFquGVrT4B4grUdCSZviU2R92uSgXfts4/ZTDzOmE24QaF6w5AwFqiNOkL6W1cyiW8/OfY="},{"role":"publicAuthentication","algorithm":"rsa2048","createdAt":"2026-09-30T12:58:23.939393Z","status":"active","bytes":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuaRAcqixMEG3xmuacXO6zidB67+TvekMijeRDsYFij3HkMkou29NCyIFvTYbFdNfImi/HDKVImkUQtjzgFEY4EBR0J01XgTaATC2DTpY7EQRvmZbZDWF6V0xrOlxAbIA29crfOGAYFTi9bME11nUpq1KLHQgc63BHeak5EmEkkwhXH69g4w4HBPkxqPfICTi7N80lfYxbpiNWpXa0+agl9RRFALdu3ukGuu41W/6DbPfJNvGv6nWisrJO8J7X9eIKw8q41hJ+bIxzYhZAR2ZqtW8XlYwEDfs1opJTjSMruKDe5MxeGcG/yKppn2p9ZScNWiSBYHx4QrySJPCSBJQGQIDAQAB"}]}]}]} \ No newline at end of file diff --git a/at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys b/at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys deleted file mode 100644 index b804eb15..00000000 --- a/at_client/src/test/resources/org/atsign/client/impl/util/at_java_string_version.atKeys +++ /dev/null @@ -1,8 +0,0 @@ -{ - "aesEncryptPrivateKey" : "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", - "aesEncryptPublicKey" : "r5wM9BxkuFb347lG1oxK0TgCdY/hom/6QXfws8hT3jX2E2bTvJQ3oLN7/FxbjNPqkIILGRzbMRLm61SM63Sq21S6XCkXbIPps72f0/WnYnsSKllivBe6qDPV+9umXGtce6k3TvZ/+zEMDi2WJPzLaRm/MXUySO/ajjuN6ufESc+psy2n6UWfV173ZEf0KbvKfpxEI8tDCohwjloTRqI+GMP4oxZAPGQJnF9I4s3Y3hLlMHgWt0Q3XqG6WJLmfU0ZkvnMrwcLoLn92ipc/Dl476E8/7DEiQ5HbTFocIpgs8eEwTtw0qUSZwky6DwJIXQWy+8ilBMoZkDZOWdr0pTXgUia+BinQB7BwG0clOvyIjVh86XYWsaLQdp1IPqtkYrwQKjR8OLylurumfd/0KiO77zcZ6IoJIckezO/AuBnoMiFV1XNkUMZcWyGdAqNlLXVMouOuwSzvwGmhkiCulQEMdB01grpxtV2ryTpclDiLcttknbRRhntoQR+liOuMz+xTS3r9Ahl5iXLHsmUuy9YhQ==", - "aesPkamPrivateKey" : "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", - "aesPkamPublicKey" : "r5wM9BxkuFb347lG1oxK0TgCdY/hom/6QXfws8hT3jX2E2bTvJQ3oLN7/FxcnOrngqw5CSf5BSamiQTx437E2wWMQ3A5LZX7sLiGrJr+alBQRmNdvUD8qTLn9tOxXFtCbrojAuAt5Tg/KiysEsjQdgiFbXJwNvTWoHK+66XFRtey9kGZ/XLsDy+rFxj/fZm/X6k6FsElAqg23wxxQ8Z2FO/dhxFCf3MdtSIzodOj1AfuTmcHpkYNa+mdJcK7VlIRupKiwXAxjay8iQZ7h38wy50Y/uae6FswaTJGBbJ4nuCQyQghz8w+Hwwq3G8ebVghy+gH3gggc0XvTw1S6MyLgFKE5SvVVRHDujpu2uf5PykW0IL4bOyCXfZRBPiMkKLxTv35mvalsurnlIlypbawtfvEZ4sKV6xiKg3SKtJtusr3FH3akEIhFFGIBSDzorjbbqDqtTiIkB63qFzellIQMu0hlRPrqeR1vAHSfk/eMYc/5jWOTx3h0xt99X3QBSawYgvr9Ahl5iXLHsmUuy9YhQ==", - "selfEncryptionKey" : "Yr5dpVWRaEP+fNngk6QvFM2LdxrApxUZTS47Y6Nk7EY=", - "version" : "1" -} \ No newline at end of file