diff --git a/cmdline/src/main/java/io/opentdf/platform/Command.java b/cmdline/src/main/java/io/opentdf/platform/Command.java index c4953843..8b9146b5 100644 --- a/cmdline/src/main/java/io/opentdf/platform/Command.java +++ b/cmdline/src/main/java/io/opentdf/platform/Command.java @@ -7,6 +7,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; @@ -179,7 +180,7 @@ public AssertionConfig.AssertionKey deserialize(JsonElement json, java.lang.refl } } - private Gson buildGson() { + private static Gson buildGson() { return new GsonBuilder() .registerTypeAdapter(AssertionConfig.AssertionKey.class, new AssertionKeyDeserializer()) .create(); @@ -217,7 +218,7 @@ void setVerbose(boolean verbose) { @Option(names = { "-p", "--platform-endpoint" }) private String platformEndpoint; - private Object correctKeyType(AssertionConfig.AssertionKeyAlg alg, Object key, boolean publicKey) + private static Object correctKeyType(AssertionConfig.AssertionKeyAlg alg, Object key, boolean publicKey) throws RuntimeException { if (alg == AssertionConfig.AssertionKeyAlg.HS256) { if (key instanceof String) { @@ -397,7 +398,13 @@ void decrypt( @Option(names = { "--with-assertion-verification-disabled" }, defaultValue = "false") boolean disableAssertionVerification, @Option(names = { - "--with-assertion-verification-keys" }, defaultValue = Option.NULL_VALUE) Optional assertionVerification, + "--with-assertion-verification-keys" }, defaultValue = Option.NULL_VALUE, description = "JSON, or the " + + "path of a JSON file, giving the keys assertions are verified with: " + + "{\"keys\": {\"\": {\"alg\": \"RS256\", \"key\": \"\"}}, " + + "\"defaultKey\": {\"alg\": \"HS256\", \"key\": \"\"}}. " + + "Each assertion uses its per-ID key, else the default key, else the payload key. " + + "A default key also applies to payload-key assertions, such as the system-metadata " + + "assertion this tool writes, so those fail verification; set per-ID keys instead.") Optional assertionVerification, @Option(names = { "--kas-allowlist" }, defaultValue = Option.NULL_VALUE) Optional kasAllowlistStr, @Option(names = { "--ignore-kas-allowlist" }, defaultValue = Option.NULL_VALUE) Optional ignoreAllowlist) @@ -405,39 +412,8 @@ void decrypt( try (var sdk = buildSDK()) { var opts = new ArrayList>(); if (assertionVerification.isPresent()) { - var assertionVerificationInput = assertionVerification.get(); - Gson gson = buildGson(); - - Config.AssertionVerificationKeys assertionVerificationKeys; - try { - assertionVerificationKeys = gson.fromJson(assertionVerificationInput, - Config.AssertionVerificationKeys.class); - } catch (JsonSyntaxException e) { - // try it as a file path - try { - String fileJson = Files.readString(Paths.get(assertionVerificationInput)); - assertionVerificationKeys = gson.fromJson(fileJson, - Config.AssertionVerificationKeys.class); - } catch (JsonSyntaxException e2) { - throw new RuntimeException("Failed to parse assertion verification keys from file", e2); - } catch (Exception e3) { - throw new RuntimeException( - "Could not parse assertion verification keys as json string or path to file", - e3); - } - } - - for (Map.Entry entry : assertionVerificationKeys.keys - .entrySet()) { - try { - Object correctedKey = correctKeyType(entry.getValue().alg, entry.getValue().key, true); - entry.setValue(new AssertionConfig.AssertionKey(entry.getValue().alg, correctedKey)); - } catch (Exception e) { - throw new RuntimeException("Error with assertion verification key: " + e.getMessage(), - e); - } - } - opts.add(Config.withAssertionVerificationKeys(assertionVerificationKeys)); + opts.add(Config.withAssertionVerificationKeys( + parseAssertionVerificationKeys(assertionVerification.get()))); } if (disableAssertionVerification) { @@ -462,6 +438,142 @@ void decrypt( } } + /** + * Reads the assertion verification keys, given as JSON or as the path of a JSON file: + *
+     * { "keys": { "<assertion id>": { "alg": "RS256", "key": "<PEM public key>" } },
+     *   "defaultKey": { "alg": "HS256", "key": "<secret>" } }
+     * 
+ * {@code DefaultKey} is accepted for {@code defaultKey}. An HS256 key is the UTF-8 bytes of + * its string, and must not be PEM text; an RS256 key is a PEM public key. + *

+ * Anything that cannot be used as configured is an error with + * {@code reason=configuration-error}, never a key that silently goes missing: an unknown + * top-level member, an {@code alg} other than {@code RS256} or {@code HS256}, or a key with + * members but no {@code alg}. Only an absent, null or empty {@code defaultKey} means none. + *

+ * A default key applies to every assertion without a per-ID key, including payload-key + * assertions such as the {@code system-metadata} assertion this tool writes, so those fail + * verification; use per-ID keys for files that carry them. + */ + static Config.AssertionVerificationKeys parseAssertionVerificationKeys(String jsonOrPath) { + JsonObject root = readKeysJson(jsonOrPath); + var assertionVerificationKeys = new Config.AssertionVerificationKeys(); + String defaultKeyName = null; + JsonElement defaultKey = null; + for (Map.Entry member : root.entrySet()) { + switch (member.getKey()) { + case "keys": + readPerIdKeys(member.getValue(), assertionVerificationKeys.keys); + break; + case "defaultKey": + case "DefaultKey": + if (defaultKeyName != null) { + throw keysFileError("both " + defaultKeyName + " and " + member.getKey() + " are given"); + } + defaultKeyName = member.getKey(); + defaultKey = member.getValue(); + break; + default: + throw keysFileError("unknown member \"" + member.getKey() + "\""); + } + } + if (defaultKey != null && !defaultKey.isJsonNull()) { + if (!defaultKey.isJsonObject()) { + throw keysFileError("the default key is not an object"); + } + if (defaultKey.getAsJsonObject().size() > 0) { + assertionVerificationKeys.defaultKey = readKey(defaultKey.getAsJsonObject(), "default key"); + } + } + return assertionVerificationKeys; + } + + private static JsonObject readKeysJson(String jsonOrPath) { + JsonElement json; + try { + json = JsonParser.parseString(jsonOrPath); + } catch (JsonParseException e) { + json = null; + } + if (json == null || !json.isJsonObject()) { + // try it as a file path + String fileJson; + try { + fileJson = Files.readString(Paths.get(jsonOrPath)); + } catch (Exception e) { + throw new RuntimeException( + "Could not parse assertion verification keys as json string or path to file", e); + } + try { + json = JsonParser.parseString(fileJson); + } catch (JsonParseException e) { + throw new RuntimeException("Failed to parse assertion verification keys from file", e); + } + if (!json.isJsonObject()) { + throw keysFileError("the keys file is not a JSON object"); + } + } + return json.getAsJsonObject(); + } + + private static void readPerIdKeys(JsonElement keys, Map into) { + if (keys == null || keys.isJsonNull()) { + return; + } + if (!keys.isJsonObject()) { + throw keysFileError("keys is not an object"); + } + for (Map.Entry entry : keys.getAsJsonObject().entrySet()) { + JsonElement key = entry.getValue(); + if (key.isJsonNull()) { + // an empty entry; the SDK reports it as a configuration error if it is selected + into.put(entry.getKey(), null); + } else if (key.isJsonObject()) { + into.put(entry.getKey(), readKey(key.getAsJsonObject(), "assertion " + entry.getKey())); + } else { + throw keysFileError("the key for assertion " + entry.getKey() + " is not an object"); + } + } + } + + private static AssertionConfig.AssertionKey readKey(JsonObject json, String which) { + JsonElement alg = json.get("alg"); + if (alg == null || alg.isJsonNull()) { + if (json.entrySet().stream().anyMatch(member -> !member.getValue().isJsonNull())) { + throw keysFileError("the " + which + " has no alg"); + } + // nothing set: an empty per-ID entry, which the SDK rejects if it is selected + return new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.NotDefined, null); + } + boolean supported = alg.isJsonPrimitive() && alg.getAsJsonPrimitive().isString() + && ("RS256".equals(alg.getAsString()) || "HS256".equals(alg.getAsString())); + if (!supported) { + throw keysFileError("the " + which + " has an unsupported alg " + alg); + } + AssertionConfig.AssertionKey key; + try { + key = buildGson().fromJson(json, AssertionConfig.AssertionKey.class); + } catch (JsonParseException e) { + throw keysFileError("the " + which + " cannot be read: " + e.getMessage()); + } + try { + key.key = correctKeyType(key.alg, key.key, true); + } catch (Exception e) { + throw keysFileError("the " + which + " cannot be read: " + e.getMessage()); + } + if (key.key instanceof byte[] + && new String((byte[]) key.key, StandardCharsets.UTF_8).contains("-----BEGIN")) { + throw keysFileError("the " + which + " is HS256 but holds PEM text"); + } + return key; + } + + private static IllegalArgumentException keysFileError(String detail) { + return new IllegalArgumentException( + "invalid assertion verification keys: reason=configuration-error: " + detail); + } + // With an output file, write to a sibling temp file and move it into place only on // success, so a failed decrypt (e.g. an offset past the end) leaves no file behind. private interface PlaintextWriter { diff --git a/cmdline/src/test/java/io/opentdf/platform/AssertionVerificationKeysTest.java b/cmdline/src/test/java/io/opentdf/platform/AssertionVerificationKeysTest.java new file mode 100644 index 00000000..10b40553 --- /dev/null +++ b/cmdline/src/test/java/io/opentdf/platform/AssertionVerificationKeysTest.java @@ -0,0 +1,176 @@ +package io.opentdf.platform; + +import com.google.gson.JsonObject; +import io.opentdf.platform.sdk.AssertionConfig; +import io.opentdf.platform.sdk.AssertionFailureReason; +import io.opentdf.platform.sdk.Config; +import io.opentdf.platform.sdk.CryptoUtils; +import io.opentdf.platform.sdk.Manifest; +import io.opentdf.platform.sdk.SDK; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; +import java.util.Base64; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * The decrypt command's {@code --with-assertion-verification-keys} file: per-ID keys and the + * default key are both converted to SDK key material, so a default key works from the CLI. + */ +class AssertionVerificationKeysTest { + + private static final String HS256_SECRET = Base64.getEncoder().encodeToString(new byte[32]).replace('A', 'k'); + + private static JsonObject key(String alg, String key) { + var json = new JsonObject(); + json.addProperty("alg", alg); + json.addProperty("key", key); + return json; + } + + private static String keysFile(JsonObject perId, JsonObject defaultKey) { + var json = new JsonObject(); + if (perId != null) { + json.add("keys", perId); + } + if (defaultKey != null) { + json.add("defaultKey", defaultKey); + } + return json.toString(); + } + + /** An assertion signed with {@code signingKey}, the way the SDK writer signs it. */ + private static Manifest.Assertion signedAssertion(AssertionConfig.AssertionKey signingKey) throws Exception { + var assertion = new Manifest.Assertion(); + assertion.id = "assertion1"; + assertion.type = "handling"; + assertion.scope = "payload"; + assertion.statement = new AssertionConfig.Statement(); + assertion.statement.format = "string"; + assertion.statement.value = "SECRET"; + assertion.sign(new Manifest.Assertion.HashValues(assertion.hash(), "c2ln"), signingKey); + return assertion; + } + + @Test + void anHs256DefaultKeyIsTheUtf8BytesOfItsString() throws Exception { + var keys = Command.parseAssertionVerificationKeys(keysFile(null, key("HS256", HS256_SECRET))); + assertThat(keys.keys).isEmpty(); + assertThat(keys.defaultKey.alg).isEqualTo(AssertionConfig.AssertionKeyAlg.HS256); + assertThat(keys.defaultKey.key).isEqualTo(HS256_SECRET.getBytes(StandardCharsets.UTF_8)); + + var assertion = signedAssertion(new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, + HS256_SECRET.getBytes(StandardCharsets.UTF_8))); + assertThat(assertion.verify(keys.defaultKey).getAssertionHash()).isEqualTo(assertion.hash()); + } + + @Test + void anRs256DefaultKeyIsAPemPublicKey() throws Exception { + KeyPair signer = CryptoUtils.generateRSAKeypair(); + var keys = Command.parseAssertionVerificationKeys( + keysFile(null, key("RS256", CryptoUtils.getPublicKeyPEM(signer.getPublic())))); + assertThat(keys.defaultKey.key).isInstanceOf(RSAPublicKey.class).isEqualTo(signer.getPublic()); + + var assertion = signedAssertion( + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, signer.getPrivate())); + assertThat(assertion.verify(keys.defaultKey).getAssertionHash()).isEqualTo(assertion.hash()); + + // and an unrelated default does not verify it + var other = Command.parseAssertionVerificationKeys(keysFile(null, + key("RS256", CryptoUtils.getPublicKeyPEM(CryptoUtils.generateRSAKeypair().getPublic())))); + assertThatThrownBy(() -> assertion.verify(other.defaultKey)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.SIGNATURE_INVALID)); + } + + @Test + void perIdKeysAndTheDefaultAreReadTogetherFromAFile(@TempDir Path tempDir) throws Exception { + KeyPair signer = CryptoUtils.generateRSAKeypair(); + var perId = new JsonObject(); + perId.add("assertion1", key("RS256", CryptoUtils.getPublicKeyPEM(signer.getPublic()))); + Path file = Files.writeString(tempDir.resolve("keys.json"), keysFile(perId, key("HS256", HS256_SECRET))); + + Config.AssertionVerificationKeys keys = Command.parseAssertionVerificationKeys(file.toString()); + assertThat(keys.keys.get("assertion1").key).isEqualTo(signer.getPublic()); + assertThat(keys.defaultKey.key).isEqualTo(HS256_SECRET.getBytes(StandardCharsets.UTF_8)); + } + + @Test + void theDefaultKeyMayBeSpelledDefaultKey() { + var json = new JsonObject(); + json.add("DefaultKey", key("HS256", HS256_SECRET)); + var keys = Command.parseAssertionVerificationKeys(json.toString()); + assertThat(keys.defaultKey.key).isEqualTo(HS256_SECRET.getBytes(StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @ValueSource(strings = { + // an alg Gson would read as null, which must not make the key absent + "{'defaultKey':{'alg':'rs256','pem':'x'}}", + "{'defaultKey':{'alg':'ES256'}}", + "{'defaultKey':{'alg':'NotDefined','key':'x'}}", + "{'defaultKey':{'alg':5,'key':'x'}}", + "{'defaultKey':{'key':'x'}}", + "{'defaultKey':{'pem':'x'}}", + "{'keys':{'a':{'alg':'RS512','key':'x'}}}", + "{'keys':{'a':{'key':'x'}}}", + "{'keys':{'a':'x'}}", + "{'keys':['x']}", + "{'defaultKey':'x'}", + // unknown top-level members, such as a misspelled default key + "{'default_key':{'alg':'HS256','key':'x'}}", + "{'keys':{},'extra':1}", + "{'defaultKey':{'alg':'HS256','key':'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk'}," + + "'DefaultKey':{'alg':'HS256','key':'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk'}}", + }) + void keysThatCannotBeUsedAsGivenAreConfigurationErrors(String singleQuoted) { + assertThatThrownBy(() -> Command.parseAssertionVerificationKeys(singleQuoted.replace('\'', '"'))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("reason=configuration-error"); + } + + @Test + void pemTextIsNeverAnHs256Secret() { + String pem = CryptoUtils.getPublicKeyPEM(CryptoUtils.generateRSAKeypair().getPublic()); + assertThatThrownBy(() -> Command.parseAssertionVerificationKeys(keysFile(null, key("HS256", pem)))) + .hasMessageContaining("reason=configuration-error") + .hasMessageContaining("PEM"); + } + + @ParameterizedTest + @ValueSource(strings = {"{}", "{'defaultKey':null}", "{'defaultKey':{}}", "{'keys':null}", "{'keys':{}}"}) + void noDefaultKeyMeansNone(String singleQuoted) { + var keys = Command.parseAssertionVerificationKeys(singleQuoted.replace('\'', '"')); + assertThat(keys.defaultKey).isNull(); + assertThat(keys.keys).isEmpty(); + } + + @Test + void anUnreadableKeyIsAConfigurationError() { + assertThatThrownBy(() -> Command.parseAssertionVerificationKeys(keysFile(null, key("RS256", "not a pem")))) + .hasMessageContaining("reason=configuration-error") + .hasMessageContaining("default key"); + } + + @Test + void anEmptyPerIdEntryIsLeftForTheSdkToReject() throws Exception { + var keys = Command.parseAssertionVerificationKeys("{\"keys\":{\"assertion1\":null,\"assertion2\":{}}}"); + assertThat(keys.keys).containsKeys("assertion1", "assertion2"); + var assertion = signedAssertion(new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, + HS256_SECRET.getBytes(StandardCharsets.UTF_8))); + for (var entry : keys.keys.values()) { + assertThatThrownBy(() -> assertion.verify(entry)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.CONFIGURATION_ERROR)); + } + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AssertionFailureReason.java b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionFailureReason.java new file mode 100644 index 00000000..93f9ebc7 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionFailureReason.java @@ -0,0 +1,68 @@ +package io.opentdf.platform.sdk; + +import java.util.Optional; + +/** + * Why an assertion, or the manifest that carries it, was rejected. Every + * {@link SDK.AssertionFailureException} carries exactly one of these. + *

+ * The {@link #code() codes} are the stable strings from Section 9 of the ZTDF assertions + * specification. They are shared with the other OpenTDF SDKs and appear in error messages as + * {@code reason=}, so tools can match on them. + */ +public enum AssertionFailureReason { + /** The manifest is not I-JSON: invalid UTF-8, a lone surrogate, a noncharacter, or an out-of-range number. */ + INVALID_JSON("invalid-json"), + /** An object in the manifest has two members with the same name, compared after decoding escapes. */ + DUPLICATE_MEMBER("duplicate-member"), + /** An object has a case variant of a modelled member name, such as {@code ID} or {@code Assertions}. */ + AMBIGUOUS_MEMBER("ambiguous-member"), + /** The assertion's {@code id} is missing, empty, or not a JSON string. */ + INVALID_ID("invalid-id"), + /** The assertion's {@code statement} is not an object, or its {@code value} is neither a string nor an object. */ + INVALID_STATEMENT("invalid-statement"), + /** The {@code binding} is missing or malformed, has both binding forms or neither, or its JWS is malformed. */ + INVALID_BINDING("invalid-binding"), + /** The binding uses an unknown {@code method}, or is a binding object this SDK does not support. */ + UNSUPPORTED_BINDING("unsupported-binding"), + /** The JWS {@code alg} is missing, {@code none}, or not supported by this SDK. */ + UNSUPPORTED_ALGORITHM("unsupported-algorithm"), + /** The JWS {@code alg} differs from the algorithm of the selected verification key. */ + ALGORITHM_MISMATCH("algorithm-mismatch"), + /** An embedded {@code jwk} or {@code x5c} is symmetric, private, malformed, contradictory, or present in HMAC mode. */ + INVALID_EMBEDDED_KEY("invalid-embedded-key"), + /** An embedded {@code jwk} or {@code x5c} does not match the selected verification key. */ + SIGNER_NOT_PERMITTED("signer-not-permitted"), + /** The binding signature does not verify under the selected key. */ + SIGNATURE_INVALID("signature-invalid"), + /** The signed {@code assertionHash} or {@code assertionSig} does not match the recomputed value. */ + BINDING_MISMATCH("binding-mismatch"), + /** A configured verification key is empty, of the wrong type for its algorithm, or otherwise unusable. */ + CONFIGURATION_ERROR("configuration-error"); + + private final String code; + + AssertionFailureReason(String code) { + this.code = code; + } + + /** The stable reason string, for example {@code signer-not-permitted}. */ + public String code() { + return code; + } + + /** The reason with the given {@link #code() code}, if there is one. */ + public static Optional fromCode(String code) { + for (AssertionFailureReason reason : values()) { + if (reason.code.equals(code)) { + return Optional.of(reason); + } + } + return Optional.empty(); + } + + @Override + public String toString() { + return code; + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AssertionJson.java b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionJson.java new file mode 100644 index 00000000..31eaa0bb --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionJson.java @@ -0,0 +1,153 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonToken; +import org.apache.commons.codec.binary.Hex; +import org.erdtman.jcs.JsonCanonicalizer; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import java.util.Objects; + +/** + * Parsing and hashing of assertion wire objects, as Sections 3 and 4 of the ZTDF assertions + * specification define them. + */ +final class AssertionJson { + + /** Gson's own JSON tree adapter; it reads through whatever reader it is handed. */ + static final TypeAdapter JSON_ELEMENT = new Gson().getAdapter(JsonElement.class); + + private static final String BINDING = "binding"; + + private AssertionJson() { + } + + /** + * Parses the raw JSON text of one assertion wire object under Section 3: no duplicate member + * names anywhere in it, no case variants of the modelled names at the assertion, statement + * and binding levels, and I-JSON content throughout. + * + * @throws SDK.AssertionFailureException if the text is not such an object + */ + static JsonObject parseAssertion(String wireJson) { + Objects.requireNonNull(wireJson, "wireJson"); + try (StrictJsonReader reader = new StrictJsonReader(new StringReader(wireJson), StrictJsonReader.Mode.ASSERTION)) { + if (reader.peek() != JsonToken.BEGIN_OBJECT) { + throw invalidJson("an assertion must be a JSON object"); + } + JsonObject assertion = JSON_ELEMENT.read(reader).getAsJsonObject(); + if (reader.peek() != JsonToken.END_DOCUMENT) { + throw invalidJson("unexpected content after the assertion object"); + } + return assertion; + } catch (IOException | IllegalStateException | NumberFormatException e) { + throw invalidJson("the assertion is not valid JSON"); + } + } + + /** + * Decodes JSON text from strict UTF-8. + * + * @throws SDK.AssertionFailureException ({@code invalid-json}) if the bytes are not UTF-8 + */ + static String decodeUtf8(byte[] json) { + try { + return StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(json)) + .toString(); + } catch (CharacterCodingException e) { + throw invalidJson("the JSON text is not valid UTF-8"); + } + } + + /** + * Applies only the manifest-level checks of Section 3 to a whole manifest, without building + * the typed manifest. Malformed JSON is reported as {@code invalid-json} here; the manifest + * reader proper reports it as a malformed TDF. + * + * @throws SDK.AssertionFailureException on the first failure in document order + */ + static void checkManifest(Reader manifestJson) { + try (StrictJsonReader reader = new StrictJsonReader(manifestJson, StrictJsonReader.Mode.MANIFEST)) { + if (reader.peek() != JsonToken.BEGIN_OBJECT) { + throw invalidJson("a manifest must be a JSON object"); + } + reader.skipValue(); + if (reader.peek() != JsonToken.END_DOCUMENT) { + throw invalidJson("unexpected content after the manifest"); + } + } catch (IOException | IllegalStateException | NumberFormatException e) { + throw invalidJson("the manifest is not valid JSON"); + } + } + + /** + * Parses any JSON value, rejecting duplicate member names but applying none of the + * assertion-specific rules. Used for JWS headers and claims. + * + * @throws IOException or a {@link SDK.AssertionFailureException} if the text is not strict JSON + */ + static JsonElement parseStrict(String json) throws IOException { + try (StrictJsonReader reader = new StrictJsonReader(new StringReader(json), StrictJsonReader.Mode.GENERIC)) { + JsonElement element = JSON_ELEMENT.read(reader); + if (reader.peek() != JsonToken.END_DOCUMENT) { + throw new IOException("unexpected content after the JSON value"); + } + return element; + } catch (IllegalStateException | NumberFormatException e) { + throw new IOException("not valid JSON", e); + } + } + + /** + * {@code assertionHash} as Section 4 defines it: the lowercase hex SHA-256 of the JCS (RFC + * 8785) form of the assertion with only its top-level {@code binding} member removed. + * Everything else, including members this SDK does not model and the JSON type of + * {@code statement.value}, is hashed exactly as given. + * + * @param assertion an assertion that has passed {@link #parseAssertion} or the manifest reader + */ + static String hash(JsonObject assertion) { + JsonObject withoutBinding = new JsonObject(); + for (Map.Entry member : assertion.entrySet()) { + if (!BINDING.equals(member.getKey())) { + withoutBinding.add(member.getKey(), member.getValue()); + } + } + byte[] canonical; + try { + // JsonElement.toString writes JSON nulls and keeps each number's original text, which + // the canonicalizer then reads as an IEEE 754 double, as RFC 8785 requires + canonical = new JsonCanonicalizer(withoutBinding.toString()).getEncodedUTF8(); + } catch (IOException e) { + throw invalidJson("the assertion cannot be canonicalized: " + e.getMessage()); + } + return Hex.encodeHexString(sha256(canonical)); + } + + private static byte[] sha256(byte[] input) { + try { + return MessageDigest.getInstance("SHA-256").digest(input); + } catch (NoSuchAlgorithmException e) { + throw new SDKException("error creating SHA-256 message digest", e); + } + } + + private static SDK.AssertionFailureException invalidJson(String detail) { + return new SDK.AssertionFailureException(null, AssertionFailureReason.INVALID_JSON, detail); + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AssertionVerifier.java b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionVerifier.java new file mode 100644 index 00000000..8defa011 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AssertionVerifier.java @@ -0,0 +1,708 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.MACVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jose.util.X509CertUtils; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import static io.opentdf.platform.sdk.AssertionFailureReason.ALGORITHM_MISMATCH; +import static io.opentdf.platform.sdk.AssertionFailureReason.BINDING_MISMATCH; +import static io.opentdf.platform.sdk.AssertionFailureReason.CONFIGURATION_ERROR; +import static io.opentdf.platform.sdk.AssertionFailureReason.INVALID_BINDING; +import static io.opentdf.platform.sdk.AssertionFailureReason.INVALID_EMBEDDED_KEY; +import static io.opentdf.platform.sdk.AssertionFailureReason.INVALID_ID; +import static io.opentdf.platform.sdk.AssertionFailureReason.SIGNATURE_INVALID; +import static io.opentdf.platform.sdk.AssertionFailureReason.SIGNER_NOT_PERMITTED; +import static io.opentdf.platform.sdk.AssertionFailureReason.UNSUPPORTED_ALGORITHM; +import static io.opentdf.platform.sdk.AssertionFailureReason.UNSUPPORTED_BINDING; + +/** + * Verifies assertion bindings as Section 8 of the ZTDF assertions specification lays out, in its + * order, so the same input fails with the same reason in every SDK. + *

+ * The verification key comes from the reader's configuration, or is the payload key when nothing + * is configured, and is selected once per assertion. A key embedded in the JWS header, as + * {@code jwk} or as the leaf of {@code x5c}, never replaces it and never chooses the algorithm: + * it is accepted only when it is the same public key as the selected one. The signature is + * always checked with the selected key and its configured algorithm. Nothing here reads the + * network, and an {@code x5c} chain is not validated, so a pass says nothing about certificates. + */ +final class AssertionVerifier { + + private static final String ASSERTION_HASH = "assertionHash"; + private static final String ASSERTION_SIG = "assertionSig"; + private static final List PRIVATE_JWK_MEMBERS = List.of("d", "p", "q", "dp", "dq", "qi", "oth"); + /** Untrusted values quoted in messages are cut to this many characters. */ + private static final int MAX_QUOTED = 64; + + private AssertionVerifier() { + } + + /** The key one assertion is verified with, and the only key it can verify with. */ + static final class SelectedKey { + final AssertionConfig.AssertionKeyAlg alg; + private final JWSVerifier verifier; + /** The public key's {@link #publicKeyIdentity identity}; null for HMAC keys. */ + private final String identity; + + private SelectedKey(AssertionConfig.AssertionKeyAlg alg, JWSVerifier verifier, String identity) { + this.alg = alg; + this.verifier = verifier; + this.identity = identity; + } + + static SelectedKey rsa(RSAPublicKey publicKey) { + try { + return new SelectedKey(AssertionConfig.AssertionKeyAlg.RS256, new RSASSAVerifier(publicKey), + publicKeyIdentity(publicKey)); + } catch (RuntimeException e) { + throw new IllegalArgumentException("unusable RSA public key", e); + } + } + + static SelectedKey hmac(byte[] secret) { + try { + return new SelectedKey(AssertionConfig.AssertionKeyAlg.HS256, new MACVerifier(secret), null); + } catch (JOSEException | RuntimeException e) { + // MACVerifier refuses secrets shorter than 256 bits, as it always has + throw new IllegalArgumentException("unusable HS256 secret: " + e.getMessage(), e); + } + } + + boolean isHmac() { + return alg == AssertionConfig.AssertionKeyAlg.HS256; + } + + private JWSAlgorithm jwsAlgorithm() { + return isHmac() ? JWSAlgorithm.HS256 : JWSAlgorithm.RS256; + } + } + + /** + * Verifies one assertion read from a manifest: Section 8, steps 3.1 to 3.9. + * + * @param keys the reader's configured keys; null means none + * @param payloadKey used with HS256 only when no key is configured for the assertion + * @param aggregateHash the decoded segment hashes, concatenated in manifest order + * @param legacy whether {@code assertionSig} carries the assertion hash as hex text + * @throws SDK.AssertionFailureException on the first failed check + */ + static void verify(Manifest.Assertion assertion, Config.AssertionVerificationKeys keys, byte[] payloadKey, + byte[] aggregateHash, boolean legacy) { + JsonObject json = assertion.received() != null ? assertion.received() : assertion.toJsonTree(); + String id = exactId(json); + SelectedKey key = select(keys, id, payloadKey); + verifyWithKey(json, id, key, aggregateHash, legacy); + } + + /** Section 3.3: the exact {@code id}, which must be a nonempty JSON string. */ + static String exactId(JsonObject assertion) { + JsonElement id = assertion.get("id"); + if (id == null || id.isJsonNull()) { + throw new SDK.AssertionFailureException(null, INVALID_ID, "the assertion has no id"); + } + if (!isString(id)) { + throw new SDK.AssertionFailureException(null, INVALID_ID, "the assertion id is not a string"); + } + if (id.getAsString().isEmpty()) { + throw new SDK.AssertionFailureException(null, INVALID_ID, "the assertion id is empty"); + } + return id.getAsString(); + } + + /** + * Section 6.1: the key for the assertion with this exact id. A per-ID entry wins, then the + * default key, then the payload key with HS256. An empty or invalid configured entry is a + * {@code configuration-error}, never a reason to look further down the list. + */ + static SelectedKey select(Config.AssertionVerificationKeys keys, String id, byte[] payloadKey) { + if (keys != null && (keys.hasKeyFor(id) || keys.hasDefaultKey())) { + String which = keys.hasKeyFor(id) ? "the verification key for this id" : "the default verification key"; + return configuredKey(keys.getKey(id), id, which); + } + try { + return SelectedKey.hmac(payloadKey); + } catch (IllegalArgumentException e) { + throw new SDKException("the payload key cannot verify assertions", e); + } + } + + /** + * A configured key as a {@link SelectedKey}: RS256 with an RSA public key (as + * {@link RSAPublicKey} or an RSA {@link com.nimbusds.jose.jwk.JWK}), or HS256 with a {@code byte[]} secret. + */ + static SelectedKey configuredKey(AssertionConfig.AssertionKey key, String id, String which) { + if (key == null) { + throw configurationError(id, which + " is empty"); + } + if (key.alg == AssertionConfig.AssertionKeyAlg.RS256) { + RSAPublicKey publicKey; + if (key.key instanceof RSAPublicKey) { + publicKey = (RSAPublicKey) key.key; + } else if (key.key instanceof RSAKey) { + try { + publicKey = ((RSAKey) key.key).toRSAPublicKey(); + } catch (JOSEException e) { + throw configurationError(id, which + " is not a usable RSA key"); + } + } else { + throw configurationError(id, which + " is RS256 but is not an RSA public key"); + } + try { + return SelectedKey.rsa(publicKey); + } catch (IllegalArgumentException e) { + throw configurationError(id, which + " is not a usable RSA key"); + } + } + if (key.alg == AssertionConfig.AssertionKeyAlg.HS256) { + if (!(key.key instanceof byte[]) || ((byte[]) key.key).length == 0) { + throw configurationError(id, which + " is HS256 but is not a nonempty byte[] secret"); + } + if (isPemText((byte[]) key.key)) { + // public-key bytes must never serve as an HMAC secret (spec Section 7) + throw configurationError(id, which + " is HS256 but holds PEM text"); + } + try { + return SelectedKey.hmac((byte[]) key.key); + } catch (IllegalArgumentException e) { + throw configurationError(id, which + " is not a usable HS256 secret"); + } + } + throw configurationError(id, which + " has no supported algorithm"); + } + + /** + * Section 8, steps 3.3 to 3.9, with the key already selected. + * + * @param assertion the assertion as parsed under Section 3, binding included + * @param id the assertion's exact id, for error reports; may be null + */ + static void verifyWithKey(JsonObject assertion, String id, SelectedKey key, byte[] aggregateHash, boolean legacy) { + Manifest.Assertion.HashValues claimed = verifyBinding(assertion, id, key); + + String assertionHash = AssertionJson.hash(assertion); + if (!assertionHash.equals(claimed.getAssertionHash())) { + throw new SDK.AssertionFailureException(id, BINDING_MISMATCH, + "the signed assertionHash does not match the assertion"); + } + + byte[] hashBytes; + if (legacy) { + hashBytes = assertionHash.getBytes(StandardCharsets.US_ASCII); + } else { + try { + hashBytes = Hex.decodeHex(assertionHash); + } catch (DecoderException e) { + throw new IllegalStateException("assertion hash is not hex", e); + } + } + byte[] expected = new byte[aggregateHash.length + hashBytes.length]; + System.arraycopy(aggregateHash, 0, expected, 0, aggregateHash.length); + System.arraycopy(hashBytes, 0, expected, aggregateHash.length, hashBytes.length); + if (!Base64.getEncoder().encodeToString(expected).equals(claimed.getSignature())) { + // the other encoding is deliberately not tried + throw new SDK.AssertionFailureException(id, BINDING_MISMATCH, + "failed integrity check on assertion signature: assertionSig does not match the segment hashes"); + } + } + + /** + * Section 8, steps 3.3 to 3.7: the binding form, the JWS and its header, any embedded keys, + * the signature under the selected key, and the presence of the claims. + * + * @return the signed claims, still to be compared with the recomputed values + */ + static Manifest.Assertion.HashValues verifyBinding(JsonObject assertion, String id, SelectedKey key) { + Jws jws = Jws.parse(jwsBinding(assertion, id), id); + + // 3.4: the header + if (jws.header.has("crit")) { + throw invalidBinding(id, "the JWS header has a crit member"); + } + JsonElement alg = jws.header.get("alg"); + if (alg == null || !isString(alg)) { + throw new SDK.AssertionFailureException(id, UNSUPPORTED_ALGORITHM, "the JWS header has no alg"); + } + String algName = alg.getAsString(); + if (!JWSAlgorithm.RS256.getName().equals(algName) && !JWSAlgorithm.HS256.getName().equals(algName)) { + throw new SDK.AssertionFailureException(id, UNSUPPORTED_ALGORITHM, + "JWS alg " + quoted(algName) + " is not supported"); + } + if (!algName.equals(key.jwsAlgorithm().getName())) { + throw new SDK.AssertionFailureException(id, ALGORITHM_MISMATCH, + "JWS alg " + algName + " does not match the selected " + key.alg + " key"); + } + + // 3.5: embedded keys may only name the selected key + checkEmbeddedKeys(jws.header, id, key); + + // 3.6: the signature, with the selected key and its algorithm only + boolean verified = false; + byte[] signature = strictBase64Url(jws.encodedSignature); + if (signature != null) { + try { + verified = key.verifier.verify(new JWSHeader(key.jwsAlgorithm()), jws.signingInput(), + Base64URL.encode(signature)); + } catch (JOSEException e) { + verified = false; + } + } + if (!verified) { + throw new SDK.AssertionFailureException(id, SIGNATURE_INVALID, + "unable to verify the assertion signature with the selected key"); + } + + // 3.7: the claims + JsonElement assertionHash = jws.claims.get(ASSERTION_HASH); + JsonElement assertionSig = jws.claims.get(ASSERTION_SIG); + if (!isString(assertionHash) || !isString(assertionSig)) { + throw new SDK.AssertionFailureException(id, BINDING_MISMATCH, + "the binding claims assertionHash and assertionSig must both be strings"); + } + return new Manifest.Assertion.HashValues(assertionHash.getAsString(), assertionSig.getAsString()); + } + + /** + * Section 5.1: the compact JWS of a JWS binding. The form is decided by which of + * {@code signature} and {@code bdo} are present, before {@code method} is looked at. + */ + static String jwsBinding(JsonObject assertion, String id) { + JsonElement binding = assertion.get("binding"); + if (binding == null) { + throw invalidBinding(id, "the assertion has no binding"); + } + if (!binding.isJsonObject()) { + throw invalidBinding(id, "the binding is not a JSON object"); + } + JsonObject members = binding.getAsJsonObject(); + boolean hasSignature = members.has("signature"); + boolean hasBindingObject = members.has("bdo"); + if (hasSignature && hasBindingObject) { + throw invalidBinding(id, "the binding has both a signature and a bdo"); + } + if (!hasSignature && !hasBindingObject) { + throw invalidBinding(id, "the binding has neither a signature nor a bdo"); + } + if (hasBindingObject) { + throw new SDK.AssertionFailureException(id, UNSUPPORTED_BINDING, + "STANAG 4778 binding objects are not supported"); + } + JsonElement signature = members.get("signature"); + if (!isString(signature)) { + throw invalidBinding(id, "the binding signature is not a string"); + } + // a missing method means jws + JsonElement method = members.get("method"); + if (method != null) { + if (!isString(method)) { + throw invalidBinding(id, "the binding method is not a string"); + } + String name = method.getAsString(); + // current Java writers emitted the upper-case form + if (!"jws".equals(name) && !"JWS".equals(name)) { + throw new SDK.AssertionFailureException(id, UNSUPPORTED_BINDING, + "binding method " + quoted(name) + " is not supported"); + } + } + return signature.getAsString(); + } + + /** + * A compact JWS, split, with its header and claims decoded and parsed as strict JSON. The + * signature is decoded only when it is checked, so a malformed one is + * {@code signature-invalid}. + */ + private static final class Jws { + final String encodedHeader; + final String encodedPayload; + final String encodedSignature; + final JsonObject header; + final JsonObject claims; + + private Jws(String[] parts, JsonObject header, JsonObject claims) { + this.encodedHeader = parts[0]; + this.encodedPayload = parts[1]; + this.encodedSignature = parts[2]; + this.header = header; + this.claims = claims; + } + + static Jws parse(String compact, String id) { + String[] parts = compact.split("\\.", -1); + if (parts.length != 3) { + throw invalidBinding(id, "the binding signature is not a compact JWS of three parts"); + } + JsonObject header = decodeObject(parts[0], id, "header"); + JsonObject claims = decodeObject(parts[1], id, "payload"); + return new Jws(parts, header, claims); + } + + byte[] signingInput() { + return (encodedHeader + "." + encodedPayload).getBytes(StandardCharsets.US_ASCII); + } + + private static JsonObject decodeObject(String part, String id, String what) { + byte[] bytes = strictBase64Url(part); + if (bytes == null) { + throw invalidBinding(id, "the JWS " + what + " is not base64url"); + } + JsonElement json; + try { + String text = StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(bytes)) + .toString(); + json = AssertionJson.parseStrict(text); + } catch (CharacterCodingException e) { + throw invalidBinding(id, "the JWS " + what + " is not UTF-8"); + } catch (IOException | SDK.AssertionFailureException e) { + throw invalidBinding(id, "the JWS " + what + " is not a strict JSON object"); + } + if (!json.isJsonObject()) { + throw invalidBinding(id, "the JWS " + what + " is not a JSON object"); + } + return json.getAsJsonObject(); + } + } + + /** + * Unpadded base64url, as JWS (RFC 7515) requires, or null: the URL alphabet only, no + * padding, and no non-zero bits after the last whole byte, so each byte string has exactly + * one accepted encoding. + */ + static byte[] strictBase64Url(String text) { + if (text.length() % 4 == 1) { + return null; + } + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + boolean alphabet = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + || c == '-' || c == '_'; + if (!alphabet) { + return null; + } + } + byte[] decoded = Base64.getUrlDecoder().decode(text); + return Base64.getUrlEncoder().withoutPadding().encodeToString(decoded).equals(text) ? decoded : null; + } + + /** + * Padded standard base64, as {@code x5c} (RFC 7515 Section 4.1.6) requires, or null: the + * standard alphabet, correct padding, and no non-zero bits after the last whole byte. + */ + private static byte[] strictBase64(String text) { + if (text.length() % 4 != 0) { + return null; + } + int padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0; + for (int i = 0; i < text.length() - padding; i++) { + char c = text.charAt(i); + boolean alphabet = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') + || c == '+' || c == '/'; + if (!alphabet) { + return null; + } + } + byte[] decoded; + try { + decoded = Base64.getDecoder().decode(text); + } catch (IllegalArgumentException e) { + return null; + } + return Base64.getEncoder().encodeToString(decoded).equals(text) ? decoded : null; + } + + /** Section 6.2. */ + private static void checkEmbeddedKeys(JsonObject header, String id, SelectedKey key) { + boolean hasJwk = header.has("jwk"); + boolean hasX5c = header.has("x5c"); + if (!hasJwk && !hasX5c) { + return; + } + if (key.isHmac()) { + throw new SDK.AssertionFailureException(id, INVALID_EMBEDDED_KEY, + "the JWS header embeds a key, but the selected key is an HMAC secret"); + } + String fromJwk = hasJwk ? jwkIdentity(header.get("jwk"), id) : null; + String fromX5c = hasX5c ? x5cLeafIdentity(header.get("x5c"), id) : null; + if (fromJwk != null && fromX5c != null && !fromJwk.equals(fromX5c)) { + throw new SDK.AssertionFailureException(id, INVALID_EMBEDDED_KEY, + "the jwk and x5c headers hold different keys"); + } + String embedded = fromJwk != null ? fromJwk : fromX5c; + if (!embedded.equals(key.identity)) { + throw new SDK.AssertionFailureException(id, SIGNER_NOT_PERMITTED, + "the key embedded in the JWS header is not the selected verification key"); + } + } + + /** The curves an EC jwk may name, with the byte length of each coordinate (RFC 7518 Section 6.2.1). */ + private static final Map EC_CURVES = Map.of( + Curve.P_256.getName(), Curve.P_256, Curve.P_384.getName(), Curve.P_384, Curve.P_521.getName(), Curve.P_521); + + /** + * The OKP signature curves of RFC 8037, with the length of {@code x} and the DER prefix of the + * key's SPKI. The key-agreement curves X25519 and X448 are not signing keys. + */ + private enum OkpCurve { + ED25519("Ed25519", 32, "302a300506032b6570032100"), + ED448("Ed448", 57, "3043300506032b6571033a00"); + + final String jwkName; + final int length; + final byte[] spkiPrefix; + + OkpCurve(String jwkName, int length, String spkiPrefix) { + this.jwkName = jwkName; + this.length = length; + try { + this.spkiPrefix = Hex.decodeHex(spkiPrefix); + } catch (DecoderException e) { + throw new IllegalStateException(e); + } + } + + static OkpCurve named(String name) { + for (OkpCurve curve : values()) { + if (curve.jwkName.equals(name)) { + return curve; + } + } + return null; + } + } + + /** + * The identity of an embedded public JWK, or {@code invalid-embedded-key} if it is not a + * well-formed public key of its key type. Members are read from strict base64url. A + * well-formed key of another type than the selected key simply has a different identity. + */ + private static String jwkIdentity(JsonElement element, String id) { + if (!element.isJsonObject()) { + throw invalidEmbeddedKey(id, "the jwk header is not a JSON object"); + } + JsonObject members = element.getAsJsonObject(); + JsonElement kty = members.get("kty"); + if (!isString(kty)) { + throw invalidEmbeddedKey(id, "the jwk header has no kty"); + } + if ("oct".equals(kty.getAsString())) { + throw invalidEmbeddedKey(id, "the jwk header holds a symmetric key"); + } + for (String member : PRIVATE_JWK_MEMBERS) { + if (members.has(member)) { + throw invalidEmbeddedKey(id, "the jwk header holds private key material"); + } + } + switch (kty.getAsString()) { + case "RSA": + return rsaIdentity(jwkUnsigned(members, "n", id), jwkUnsigned(members, "e", id)); + case "EC": { + JsonElement crv = members.get("crv"); + Curve curve = isString(crv) ? EC_CURVES.get(crv.getAsString()) : null; + if (curve == null) { + throw invalidEmbeddedKey(id, "the jwk header names no supported EC curve"); + } + byte[] x = jwkBytes(members, "x", id); + byte[] y = jwkBytes(members, "y", id); + int size = (curve.toECParameterSpec().getCurve().getField().getFieldSize() + 7) / 8; + if (x.length != size || y.length != size) { + throw invalidEmbeddedKey(id, "the jwk header has EC coordinates of the wrong length"); + } + BigInteger affineX = new BigInteger(1, x); + BigInteger affineY = new BigInteger(1, y); + if (!isOnCurve(curve, affineX, affineY)) { + throw invalidEmbeddedKey(id, "the jwk header has an EC point that is not on its curve"); + } + return ecIdentity(curve, affineX, affineY); + } + case "OKP": { + JsonElement crv = members.get("crv"); + OkpCurve curve = isString(crv) ? OkpCurve.named(crv.getAsString()) : null; + if (curve == null) { + throw invalidEmbeddedKey(id, "the jwk header names no OKP signature curve"); + } + byte[] x = jwkBytes(members, "x", id); + if (x.length != curve.length) { + throw invalidEmbeddedKey(id, "the jwk header has an OKP key of the wrong length"); + } + return okpIdentity(curve, x); + } + default: + throw invalidEmbeddedKey(id, "the jwk header has an unsupported key type"); + } + } + + /** An RSA jwk integer, in the minimum number of octets RFC 7518 Section 6.3.1 requires. */ + private static BigInteger jwkUnsigned(JsonObject jwk, String member, String id) { + byte[] bytes = jwkBytes(jwk, member, id); + if (bytes[0] == 0) { + throw invalidEmbeddedKey(id, "the jwk member " + member + " has a leading zero octet"); + } + return new BigInteger(1, bytes); + } + + private static byte[] jwkBytes(JsonObject jwk, String member, String id) { + JsonElement value = jwk.get(member); + byte[] bytes = isString(value) ? strictBase64Url(value.getAsString()) : null; + if (bytes == null || bytes.length == 0) { + throw invalidEmbeddedKey(id, "the jwk member " + member + " is not base64url"); + } + return bytes; + } + + private static boolean isOnCurve(Curve curve, BigInteger x, BigInteger y) { + ECParameterSpec spec = curve.toECParameterSpec(); + BigInteger p = ((ECFieldFp) spec.getCurve().getField()).getP(); + if (x.compareTo(p) >= 0 || y.compareTo(p) >= 0) { + return false; + } + BigInteger right = x.pow(3).add(spec.getCurve().getA().multiply(x)).add(spec.getCurve().getB()); + return y.pow(2).subtract(right).mod(p).signum() == 0; + } + + /** + * The identity of the public key in the leaf of an embedded {@code x5c}, or + * {@code invalid-embedded-key}. Certificates after the leaf must decode as base64 and are + * otherwise ignored: never parsed, no chain is built or validated, and nothing is said + * about validity. + */ + private static String x5cLeafIdentity(JsonElement element, String id) { + if (!element.isJsonArray() || element.getAsJsonArray().size() == 0) { + throw invalidEmbeddedKey(id, "the x5c header is not a nonempty array"); + } + JsonArray chain = element.getAsJsonArray(); + byte[] leaf = null; + for (JsonElement certificate : chain) { + byte[] der = isString(certificate) ? strictBase64(certificate.getAsString()) : null; + if (der == null || der.length == 0) { + throw invalidEmbeddedKey(id, "the x5c header holds a value that is not padded standard base64"); + } + if (leaf == null) { + leaf = der; + } + } + X509Certificate certificate; + try { + certificate = X509CertUtils.parseWithException(leaf); + } catch (CertificateException | RuntimeException e) { + throw invalidEmbeddedKey(id, "the x5c leaf is not a DER certificate"); + } + // the parser also takes PEM text and ignores bytes after the certificate; accept only + // exactly one DER certificate + try { + if (certificate == null || !Arrays.equals(certificate.getEncoded(), leaf)) { + throw invalidEmbeddedKey(id, "the x5c leaf is not exactly one DER certificate"); + } + } catch (CertificateException e) { + throw invalidEmbeddedKey(id, "the x5c leaf is not a DER certificate"); + } + return publicKeyIdentity(certificate.getPublicKey()); + } + + /** + * Two keys are the same key exactly when their identities are equal. This is equivalent to + * comparing RFC 7638 SHA-256 thumbprints over the required public members: an embedded jwk + * must already use the canonical encoding (minimal RSA integers, full-length EC + * coordinates, strict base64url), so its members encode exactly these values. + */ + static String publicKeyIdentity(PublicKey publicKey) { + if (publicKey instanceof RSAPublicKey) { + RSAPublicKey rsa = (RSAPublicKey) publicKey; + return rsaIdentity(rsa.getModulus(), rsa.getPublicExponent()); + } + if (publicKey instanceof ECPublicKey) { + ECPublicKey ec = (ECPublicKey) publicKey; + Curve curve = Curve.forECParameterSpec(ec.getParams()); + if (curve != null && EC_CURVES.containsKey(curve.getName())) { + return ecIdentity(curve, ec.getW().getAffineX(), ec.getW().getAffineY()); + } + } + byte[] spki = publicKey.getEncoded(); + for (OkpCurve curve : OkpCurve.values()) { + if (spki.length == curve.spkiPrefix.length + curve.length + && Arrays.equals(spki, 0, curve.spkiPrefix.length, curve.spkiPrefix, 0, curve.spkiPrefix.length)) { + return okpIdentity(curve, Arrays.copyOfRange(spki, curve.spkiPrefix.length, spki.length)); + } + } + return "spki:" + Base64.getEncoder().encodeToString(spki); + } + + private static String rsaIdentity(BigInteger modulus, BigInteger exponent) { + return "rsa:" + modulus.toString(16) + ":" + exponent.toString(16); + } + + private static String ecIdentity(Curve curve, BigInteger x, BigInteger y) { + return "ec:" + curve.getName() + ":" + x.toString(16) + ":" + y.toString(16); + } + + private static String okpIdentity(OkpCurve curve, byte[] x) { + return "okp:" + curve.jwkName + ":" + Hex.encodeHexString(x); + } + + private static final byte[] PEM_MARKER = "-----BEGIN".getBytes(StandardCharsets.US_ASCII); + + /** True if the secret contains a PEM armour line, as public-key text does. */ + static boolean isPemText(byte[] secret) { + outer: + for (int i = 0; i + PEM_MARKER.length <= secret.length; i++) { + for (int j = 0; j < PEM_MARKER.length; j++) { + if (secret[i + j] != PEM_MARKER[j]) { + continue outer; + } + } + return true; + } + return false; + } + + private static boolean isString(JsonElement element) { + return element != null && element.isJsonPrimitive() && element.getAsJsonPrimitive().isString(); + } + + private static String quoted(String untrusted) { + return "\"" + (untrusted.length() > MAX_QUOTED ? untrusted.substring(0, MAX_QUOTED) + "..." : untrusted) + "\""; + } + + private static SDK.AssertionFailureException invalidBinding(String id, String detail) { + return new SDK.AssertionFailureException(id, INVALID_BINDING, detail); + } + + private static SDK.AssertionFailureException invalidEmbeddedKey(String id, String detail) { + return new SDK.AssertionFailureException(id, INVALID_EMBEDDED_KEY, detail); + } + + private static SDK.AssertionFailureException configurationError(String id, String detail) { + return new SDK.AssertionFailureException(id, CONFIGURATION_ERROR, detail); + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java index f4dbc4f5..bebd7670 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -106,21 +106,68 @@ public static KASInfo fromSimpleKasKey(SimpleKasKey ki) { } } + /** + * The keys a reader verifies assertion bindings with. + *

+ * Each assertion is verified with exactly one key, chosen by its exact {@code id}: the entry + * in {@link #keys} for that id if there is one, otherwise {@link #defaultKey} if it is set, + * otherwise the TDF's payload key with HS256. The choice is final. An assertion that fails + * with its per-ID key is not retried with the default key or the payload key, and one that + * fails with the default key is not retried with the payload key. An entry that is present + * but empty or unusable is a configuration error, not a reason to fall through. + *

+ * A key embedded in the assertion's JWS header ({@code jwk} or {@code x5c}) is never used in + * place of the chosen key: it must be the same public key, or verification fails. + *

+ * RS256 keys are {@link java.security.interfaces.RSAPublicKey}s or RSA + * {@link com.nimbusds.jose.jwk.JWK}s; HS256 keys are {@code byte[]} secrets. + */ public static class AssertionVerificationKeys { + /** + * Verifies every assertion that has no entry in {@link #keys}. Null, or a key with no + * algorithm and no key, means none. + *

+ * That includes assertions bound with the payload key, such as the + * {@code system-metadata} assertion this SDK's writer adds, so a TDF that carries one + * fails verification when a default key is set. To verify those alongside signed + * assertions, configure a key per assertion id in {@link #keys} instead of a default key. + */ public AssertionConfig.AssertionKey defaultKey; + /** Per-assertion keys, by exact assertion {@code id}. */ public Map keys = new HashMap<>(); + /** True if no per-ID key and no default key is configured. */ Boolean isEmpty() { - return this.defaultKey == null && this.keys.isEmpty(); + return (keys == null || keys.isEmpty()) && !hasDefaultKey(); } - AssertionConfig.AssertionKey getKey(String key) { - var assertionKey = keys.get(key); - if (assertionKey != null) { - return assertionKey; - } + /** True if there is an entry for this exact id, even an empty one. */ + boolean hasKeyFor(String id) { + return keys != null && keys.containsKey(id); + } - return defaultKey; + /** + * True if a default key is configured. A default with any field set counts, valid or not; + * only {@code null} or a zero value (no algorithm, no key) is absent. + */ + boolean hasDefaultKey() { + return defaultKey != null + && (defaultKey.key != null + || (defaultKey.alg != null && defaultKey.alg != AssertionConfig.AssertionKeyAlg.NotDefined) + || defaultKey.jwk != null + || defaultKey.x5c != null); + } + + /** + * The configured key for the assertion with this exact id: its per-ID entry if it has one + * (possibly {@code null}, which is a configuration error), else the default key if one is + * configured, else {@code null}, meaning the payload key applies. + */ + AssertionConfig.AssertionKey getKey(String id) { + if (hasKeyFor(id)) { + return keys.get(id); + } + return hasDefaultKey() ? defaultKey : null; } } @@ -145,6 +192,12 @@ public static TDFReaderConfig newTDFReaderConfig(Consumer... op return config; } + /** + * The keys assertion bindings are verified with. See {@link AssertionVerificationKeys}: each + * assertion uses its per-ID key, else the default key, else the payload key. A default key + * also applies to payload-key assertions such as {@code system-metadata}, which then fail; + * use per-ID keys where a TDF carries both kinds. + */ public static Consumer withAssertionVerificationKeys( AssertionVerificationKeys assertionVerificationKeys) { return (TDFReaderConfig config) -> config.assertionVerificationKeys = assertionVerificationKeys; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java index 5b5edc39..f0b27355 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java @@ -11,6 +11,7 @@ import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.annotations.JsonAdapter; @@ -23,20 +24,11 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.JWSVerifier; import com.nimbusds.jose.KeyLengthException; import com.nimbusds.jose.crypto.MACSigner; -import com.nimbusds.jose.crypto.MACVerifier; import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.crypto.RSASSAVerifier; -import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.util.X509CertUtils; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import io.opentdf.platform.sdk.SDK.AssertionException; -import org.apache.commons.codec.binary.Hex; -import org.erdtman.jcs.JsonCanonicalizer; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -47,12 +39,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Type; +import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.cert.X509Certificate; import java.text.ParseException; import java.util.AbstractList; import java.util.ArrayList; @@ -923,6 +912,18 @@ public int hashCode() { } } + /** + * An assertion in the manifest. + *

+ * An assertion read from a manifest keeps the JSON object it was parsed from. That object, + * and not these typed fields, is what is hashed and verified, and what is written back when + * the manifest is serialized, so members this SDK does not model and the JSON type of + * {@code statement.value} survive. The typed fields are a convenience view of it: object + * statement values, for example, appear in {@link AssertionConfig.Statement#value} as JSON + * text. Changing a typed field changes that member, and only that member, in the serialized + * form and in {@link #hash()}. + */ + @JsonAdapter(AssertionTypeAdapter.class) static public class Assertion { public String id; public String type; @@ -930,6 +931,10 @@ static public class Assertion { public String appliesToState; public AssertionConfig.Statement statement; public Binding binding; + + /** The object this assertion was parsed from, never modified; null if built in code. */ + private transient JsonObject received; + static public class HashValues { private final String assertionHash; private final String signature; @@ -965,17 +970,170 @@ public int hashCode() { return Objects.hash(id, type, scope, appliesToState, statement, binding); } + /** + * Computes {@code assertionHash} for the raw JSON text of one assertion wire object, as + * Section 4 of the ZTDF assertions specification defines it: the lowercase hex SHA-256 + * of the RFC 8785 (JCS) canonical form of the object with only its top-level + * {@code binding} member removed. No other member is dropped or reinterpreted. + *

+ * The text is first parsed under Section 3 of the specification. Use this to check an + * assertion's binding outside the SDK; it does not check the {@code id} or the binding. + * + * @param wireJson the assertion object exactly as it appears in a manifest + * @return 64 lowercase hexadecimal characters + * @throws SDK.AssertionFailureException if the text has duplicate member names, case + * variants of modelled member names, content that is + * not I-JSON, or is not a JSON object at all + */ + public static String computeAssertionHash(String wireJson) { + return AssertionJson.hash(AssertionJson.parseAssertion(wireJson)); + } + + /** + * The {@code assertionHash} of this assertion as it would be written to a manifest now: + * for an unmodified assertion read from a manifest, the hash of the assertion as + * received; after a typed field has been changed, the hash with that change. + * {@link #verify} reads the same form. Reading a TDF uses neither: {@code loadTDF} + * verifies each assertion exactly as received. + * + * @throws SDK.AssertionFailureException if the assertion cannot be represented as I-JSON + */ public String hash() throws IOException { - MessageDigest digest; + return computeAssertionHash(toJsonTree().toString()); + } + + /** + * The hash a writer signs, after checking that the wire form would pass the reader's + * Section 3 parsing and id check. + * + * @throws IllegalArgumentException if it would not; a writer-side configuration error, + * not a tamper error + */ + String hashForWriting() { + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("an assertion cannot be written without an id: reason=" + + AssertionFailureReason.INVALID_ID.code()); + } try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new SDKException("error creating SHA-256 message digest", e); + return computeAssertionHash(toJsonTree().toString()); + } catch (SDK.AssertionFailureException e) { + throw new IllegalArgumentException("assertion " + id + " cannot be written: reason=" + + e.getReason().code()); } + } - var assertionAsJson = gson.toJson(this); - JsonCanonicalizer jc = new JsonCanonicalizer(assertionAsJson); - return Hex.encodeHexString(digest.digest(jc.getEncodedUTF8())); + /** Reads an assertion from the object parsed out of a manifest. */ + static Assertion fromJson(JsonObject json) { + Assertion assertion = new Assertion(); + assertion.received = json; + TypedView view = TypedView.of(json); + assertion.id = view.id; + assertion.type = view.type; + assertion.scope = view.scope; + assertion.appliesToState = view.appliesToState; + assertion.statement = view.statement; + assertion.binding = view.binding; + return assertion; + } + + /** The object this assertion was parsed from, or null if it was built in code. Do not modify it. */ + JsonObject received() { + return received; + } + + /** + * The wire form of this assertion: the object it was parsed from, with any typed field + * that has since been changed written over the corresponding member, or, for an assertion + * built in code, an object holding its non-null typed fields. + */ + JsonObject toJsonTree() { + JsonObject json = received == null ? new JsonObject() : received.deepCopy(); + TypedView original = received == null ? TypedView.EMPTY : TypedView.of(received); + overlay(json, "id", id, original.id); + overlay(json, "type", type, original.type); + overlay(json, "scope", scope, original.scope); + overlay(json, "appliesToState", appliesToState, original.appliesToState); + if (!Objects.equals(statement, original.statement)) { + if (statement == null) { + json.remove("statement"); + } else { + JsonObject target = original.statement == null + ? new JsonObject() + : json.getAsJsonObject("statement"); + overlay(target, "format", statement.format, original.statement == null ? null : original.statement.format); + overlay(target, "schema", statement.schema, original.statement == null ? null : original.statement.schema); + overlay(target, "value", statement.value, original.statement == null ? null : original.statement.value); + json.add("statement", target); + } + } + if (!Objects.equals(binding, original.binding)) { + if (binding == null) { + json.remove("binding"); + } else { + JsonObject target = original.binding == null ? new JsonObject() : json.getAsJsonObject("binding"); + overlay(target, "method", binding.method, original.binding == null ? null : original.binding.method); + overlay(target, "signature", binding.signature, original.binding == null ? null : original.binding.signature); + json.add("binding", target); + } + } + return json; + } + + private static void overlay(JsonObject json, String member, String value, String original) { + if (Objects.equals(value, original)) { + return; + } + if (value == null) { + json.remove(member); + } else { + json.addProperty(member, value); + } + } + + /** The typed fields as they are read from an assertion object. */ + private static final class TypedView { + static final TypedView EMPTY = new TypedView(); + + String id; + String type; + String scope; + String appliesToState; + AssertionConfig.Statement statement; + Binding binding; + + static TypedView of(JsonObject json) { + TypedView view = new TypedView(); + // only a JSON string is an id; anything else fails verification with invalid-id + view.id = stringMember(json, "id"); + view.type = textMember(json, "type"); + view.scope = textMember(json, "scope"); + view.appliesToState = textMember(json, "appliesToState"); + JsonElement statement = json.get("statement"); + if (statement != null && !statement.isJsonNull()) { + view.statement = STATEMENT_ADAPTER.deserialize(statement, AssertionConfig.Statement.class, null); + } + JsonElement binding = json.get("binding"); + if (binding != null && binding.isJsonObject()) { + // the binding's shape is checked when the assertion is verified + view.binding = new Binding(); + view.binding.method = stringMember(binding.getAsJsonObject(), "method"); + view.binding.signature = stringMember(binding.getAsJsonObject(), "signature"); + } + return view; + } + + private static String stringMember(JsonObject json, String member) { + JsonElement element = json.get(member); + return element != null && element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() + ? element.getAsString() + : null; + } + + /** A string, or a number or boolean as its JSON text, as the reflective reader gave them. */ + private static String textMember(JsonObject json, String member) { + JsonElement element = json.get(member); + return element != null && element.isJsonPrimitive() ? element.getAsString() : null; + } } // Sign the assertion with the given hash and signature using the key. @@ -1001,64 +1159,32 @@ public void sign(final HashValues hashValues, final AssertionConfig.AssertionKey // Store the binding and signature this.binding = new Binding(); - this.binding.method = AssertionConfig.BindingMethod.JWS.name(); + this.binding.method = AssertionConfig.BindingMethod.JWS.toString(); this.binding.signature = signedJWT.serialize(); } - // Checks the binding signature of the assertion and - // returns the hash and the signature. It returns an error if the verification - // fails. + /** + * Checks this assertion's binding with exactly the given key and its algorithm, and + * returns the signed claims for the caller to compare with the recomputed hash and + * signature. A key embedded in the JWS header ({@code jwk} or {@code x5c}) is accepted + * only if it is the same public key as {@code assertionKey}; it is never used instead. + * This has no side effects on the assertion. + *

+ * Like {@link #hash()}, this reads the assertion as it would be written now, so a + * changed {@link #binding} is the binding checked; for an unmodified assertion read from + * a manifest that is the binding as received. Readers verify assertions in + * {@code loadTDF}, which uses the assertion exactly as received, selects the key, and + * compares the claims; this method is for callers that do the comparison themselves. + * + * @throws SDK.AssertionFailureException if the binding does not verify with the key, or + * the key is not usable ({@code configuration-error}) + */ public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey) throws ParseException, JOSEException, java.security.cert.CertificateException { - if (binding == null) { - throw new AssertionException("Binding is null in assertion", this.id); - } - - String signatureString = binding.signature; - binding = null; // Clear the binding after use - - SignedJWT signedJWT = SignedJWT.parse(signatureString); - JWSHeader header = signedJWT.getHeader(); - JWSVerifier verifier = null; - - // Check for JWK in header - if (header.getJWK() != null) { - try { - verifier = createVerifier(header.getJWK()); - } catch (JOSEException e) { - throw new SDKException("Invalid JWK in JWT header", e); - } - } - - // Check for X.509 certificate chain in header - if (verifier == null && header.getX509CertChain() != null && !header.getX509CertChain().isEmpty()) { - try { - X509Certificate cert = X509CertUtils.parse(header.getX509CertChain().get(0).decode()); - if (cert.getPublicKey() instanceof RSAPublicKey) { - verifier = createVerifier((RSAPublicKey) cert.getPublicKey()); - } else { - throw new SDKException("Unsupported public key type in X.509 certificate"); - } - } catch (IllegalArgumentException e) { - throw new SDKException("Invalid Base64 in X.509 certificate in JWT header", e); - } - } - - - if (verifier == null) { - verifier = createVerifier(assertionKey); - } - - - if (!signedJWT.verify(verifier)) { - throw new SDKException("Unable to verify assertion signature"); - } - - JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); - String assertionHash = claimsSet.getStringClaim(kAssertionHash); - String signature = claimsSet.getStringClaim(kAssertionSignature); - - return new Assertion.HashValues(assertionHash, signature); + JsonObject json = toJsonTree(); + String validId = id != null && !id.isEmpty() ? id : null; + return AssertionVerifier.verifyBinding(json, validId, + AssertionVerifier.configuredKey(assertionKey, validId, "the given key")); } private SignedJWT createSignedJWT(final JWTClaimsSet claims, final AssertionConfig.AssertionKey assertionKey) @@ -1103,33 +1229,48 @@ private JWSSigner createSigner(final AssertionConfig.AssertionKey assertionKey) throw new SDKException("Unknown signing algorithm: " + assertionKey.alg); } } + } - private JWSVerifier createVerifier(AssertionConfig.AssertionKey assertionKey) throws JOSEException { - switch (assertionKey.alg) { - case RS256: - if (assertionKey.key instanceof JWK) { - return createVerifier((JWK) assertionKey.key); - } else if (assertionKey.key instanceof RSAPublicKey) { - return createVerifier((RSAPublicKey) assertionKey.key); - } else { - throw new SDKException("Expected JWK or RSAPublicKey for RS256 algorithm"); - } - case HS256: - return new MACVerifier((byte[]) assertionKey.key); - default: - throw new SDKException("Unknown verify key, unable to verify assertion signature"); - } + private static final AssertionValueAdapter STATEMENT_ADAPTER = new AssertionValueAdapter(); + + /** + * Reads each assertion as a JSON tree and keeps that tree, so verification hashes the + * assertion as received rather than a re-serialization of the typed fields; writes the + * assertion's wire form. Attached to {@link Assertion} itself so that every {@link Gson} + * instance, including the plain one the TDF writer uses, serializes assertions this way. + */ + static final class AssertionTypeAdapter extends TypeAdapter { + AssertionTypeAdapter() { + // instantiated by Gson through @JsonAdapter } - private JWSVerifier createVerifier(JWK jwk) throws JOSEException { - if (jwk instanceof com.nimbusds.jose.jwk.RSAKey) { - return new RSASSAVerifier(jwk.toRSAKey()); + @Override + public void write(JsonWriter out, Assertion assertion) throws IOException { + if (assertion == null) { + out.nullValue(); + return; + } + // a member that is JSON null is part of what was hashed, so it has to be written + boolean serializeNulls = out.getSerializeNulls(); + out.setSerializeNulls(true); + try { + AssertionJson.JSON_ELEMENT.write(out, assertion.toJsonTree()); + } finally { + out.setSerializeNulls(serializeNulls); } - throw new JOSEException("Unsupported JWK type: " + jwk.getKeyType() + ". Only RSA keys are supported."); } - private JWSVerifier createVerifier(RSAPublicKey publicKey) { - return new RSASSAVerifier(publicKey); + @Override + public Assertion read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + JsonElement json = AssertionJson.JSON_ELEMENT.read(in); + if (!json.isJsonObject()) { + throw new JsonParseException("an assertion must be a JSON object, at " + in.getPreviousPath()); + } + return Assertion.fromJson(json.getAsJsonObject()); } } @@ -1179,17 +1320,29 @@ protected static Manifest readManifest(String manifestJson) { /** * Parses a manifest without materializing it as a {@link String}, which a * manifest with tens of millions of segments cannot be. + *

+ * The JSON must be strict (RFC 8259) and must meet the JSON profile of the ZTDF assertions + * specification, Section 3, which this checks in the same single pass: no duplicate member + * names in any object, no case variants of modelled member names (such as + * {@code Assertions} or an assertion's {@code ID}), and I-JSON content in every assertion. + * Those checks apply whether or not assertions are later verified. + * + * @throws SDK.AssertionFailureException if the manifest breaks that profile, or if + * {@code manifestJson} reports undecodable input */ protected static Manifest readManifest(Reader manifestJson) { FailureTrackingReader reader = new FailureTrackingReader(manifestJson); + StrictJsonReader jsonReader = new StrictJsonReader(reader, StrictJsonReader.Mode.MANIFEST); Manifest result; try { - result = gson.fromJson(reader, Manifest.class); - } catch (JsonParseException e) { - if (reader.failure != null) { - throw new SDKException("error reading manifest from zip file", reader.failure); + result = gson.fromJson(jsonReader, TypeToken.get(Manifest.class)); + if (result != null && jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("unexpected content after the manifest object"); } - throw e; + } catch (JsonParseException e) { + throw readFailure(reader, jsonReader, e); + } catch (IOException e) { + throw readFailure(reader, jsonReader, new JsonSyntaxException(e)); } if (result == null) { throw new IllegalArgumentException("Manifest is empty"); @@ -1198,6 +1351,33 @@ protected static Manifest readManifest(Reader manifestJson) { return result; } + /** + * Applies only the manifest-level parsing rules of the assertions specification, Section 3, + * without building the typed manifest. Malformed JSON is reported as {@code invalid-json}. + */ + static void checkManifestJson(String manifestJson) { + AssertionJson.checkManifest(new StringReader(manifestJson)); + } + + private static RuntimeException readFailure(FailureTrackingReader reader, StrictJsonReader jsonReader, + JsonParseException e) { + if (reader.failure instanceof CharacterCodingException) { + return new SDK.AssertionFailureException(null, AssertionFailureReason.INVALID_JSON, + "the manifest is not valid UTF-8"); + } + if (reader.failure != null) { + return new SDKException("error reading manifest from zip file", reader.failure); + } + if (e.getCause() instanceof IOException && jsonReader.inAssertion()) { + // JSON that does not parse inside an assertion object is not I-JSON there. This also + // covers integer literals that Gson's lexer cannot read: it accumulates digits in a + // long, and treats a 20+ digit integer whose prefix wraps to zero as malformed. + return new SDK.AssertionFailureException(jsonReader.openAssertionId(), AssertionFailureReason.INVALID_JSON, + "an assertion is not valid JSON"); + } + return e; + } + private static final class FailureTrackingReader extends Reader { private final Reader delegate; private IOException failure; @@ -1226,13 +1406,18 @@ private static void validateManifest(Manifest result) { if (result.assertions == null) { result.assertions = new ArrayList<>(); } + for (Assertion assertion : result.assertions) { + if (assertion == null) { + throw new IllegalArgumentException("Invalid null assertion in manifest"); + } + } requireManifestField(result.payload, "Manifest with null payload"); requireManifestField(result.encryptionInformation, "Manifest with null encryptionInformation"); var encryptionInformation = result.encryptionInformation; requireManifestField(encryptionInformation.integrityInformation, "Manifest with null integrityInformation"); - requireManifestField(encryptionInformation.keyAccessObj, "Manifest with null keyAccessObj"); + requireManifestField(encryptionInformation.keyAccessObj, "Manifest with no key access objects: keyAccess is absent"); requireManifestField(encryptionInformation.policy, "Manifest with null policy"); var integrityInformation = encryptionInformation.integrityInformation; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index b26f1f9e..8c3fea48 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -33,6 +33,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -224,6 +225,9 @@ public static boolean isTDF(SeekableByteChannel channel) { * @param tdfBytes A SeekableByteChannel containing the TDF data * @return The parsed {@link Manifest} object * @throws MalformedTDFException if the bytes are not a well formed TDF or its manifest is invalid + * @throws AssertionFailureException if the manifest is not valid UTF-8, or breaks the JSON + * profile the assertions specification requires (for example duplicate member names). + * This does not verify the assertions themselves. * @throws SDKException if an SDK-specific error occurs * @throws IOException if an I/O error occurs */ @@ -560,6 +564,109 @@ public static class AssertionException extends TamperException { public AssertionException(String errorMessage, String id) { super("assertion id: "+ id + "; " + errorMessage); } + + /** For subclasses that format the whole message themselves. */ + protected AssertionException(String errorMessage) { + super(errorMessage); + } + } + + /** + * {@link AssertionFailureException} is thrown when an assertion fails verification, or when + * the manifest that carries the assertions cannot be parsed under the JSON profile the + * assertions specification requires (duplicate member names, case variants of modelled + * member names, or non-I-JSON content in an assertion). + *

+ * It carries the exact {@code id} of the failing assertion when one could be read, and one + * {@link AssertionFailureReason}. The message contains {@code reason=}, and + * {@code id: } when the id is known. + */ + public static class AssertionFailureException extends AssertionException { + private final String assertionId; + private final AssertionFailureReason reason; + + public AssertionFailureException(String assertionId, AssertionFailureReason reason, String detail) { + super(formatMessage(assertionId, reason, detail)); + this.assertionId = assertionId; + this.reason = Objects.requireNonNull(reason, "reason"); + } + + /** + * The exact {@code id} of the failing assertion, or {@code null} when the failure is not + * tied to an assertion with a valid id (for example a duplicate member elsewhere in the + * manifest, or an assertion whose {@code id} is itself invalid). + */ + public String getAssertionId() { + return assertionId; + } + + public AssertionFailureReason getReason() { + return reason; + } + + /** Rendered ids are cut to this many code points, then marked with an ellipsis. */ + static final int MAX_RENDERED_ID = 256; + /** Details quote manifest content, such as member names and paths, so they are cut too. */ + static final int MAX_RENDERED_DETAIL = 512; + + private static String formatMessage(String assertionId, AssertionFailureReason reason, String detail) { + StringBuilder message = new StringBuilder("tdf: issue verifying assertions"); + if (assertionId != null) { + message.append(", id: "); + render(assertionId, MAX_RENDERED_ID, message); + } + message.append(", reason=").append(reason.code()); + if (detail != null && !detail.isEmpty()) { + message.append(": "); + render(detail, MAX_RENDERED_DETAIL, message); + } + return message.toString(); + } + + /** + * Appends untrusted manifest text for a message: {@code \} and the control characters + * U+0000 to U+001F and U+007F to U+009F escaped as in JSON, and at most + * {@code maxCodePoints} code points of the text, followed by an ellipsis when it is + * longer. An id read from a manifest then cannot start a new log line or end the message. + */ + static void render(String text, int maxCodePoints, StringBuilder out) { + int codePoints = 0; + for (int i = 0; i < text.length(); ) { + if (codePoints == maxCodePoints) { + out.append('\u2026'); + return; + } + int codePoint = text.codePointAt(i); + i += Character.charCount(codePoint); + codePoints++; + switch (codePoint) { + case '\\': + out.append("\\\\"); + break; + case '\b': + out.append("\\b"); + break; + case '\t': + out.append("\\t"); + break; + case '\n': + out.append("\\n"); + break; + case '\f': + out.append("\\f"); + break; + case '\r': + out.append("\\r"); + break; + default: + if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f)) { + out.append(String.format("\\u%04x", codePoint)); + } else { + out.appendCodePoint(codePoint); + } + } + } + } } /** diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/StrictJsonReader.java b/sdk/src/main/java/io/opentdf/platform/sdk/StrictJsonReader.java new file mode 100644 index 00000000..d7e61825 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/StrictJsonReader.java @@ -0,0 +1,544 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.Strictness; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +import java.io.FilterReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * A strict {@link JsonReader} that applies the JSON profile of the ZTDF assertions specification + * (Section 3) while the document streams past, so the checks see every member before Gson's + * object model or a typed field can silently keep only one of two duplicates. + *

+ * Gson keeps the last of two members with the same name, and other parsers keep the first, so + * a manifest with duplicates means different things to different readers. This reader rejects: + *

    + *
  • duplicate member names in any object, compared after decoding escapes + * ({@link AssertionFailureReason#DUPLICATE_MEMBER});
  • + *
  • at every level of the specification's Table 3.2, from the manifest root down to each + * assertion's {@code statement} and {@code binding}, a member name that is a case variant of a + * name listed for that level, or two member names that are case variants of each other + * ({@link AssertionFailureReason#AMBIGUOUS_MEMBER}); and
  • + *
  • inside assertion objects, member names and strings with lone surrogates or Unicode + * noncharacters, and numbers that overflow an IEEE 754 double + * ({@link AssertionFailureReason#INVALID_JSON}).
  • + *
+ * Every value has to pass through one of the overridden methods, including values Gson skips + * because the typed model has no field for them: {@link #skipValue()} walks them instead of + * skipping them blind. + *

+ * A failure outside any assertion throws at once. A failure inside an assertion is held until + * the assertion object ends, and then thrown with the assertion's exact {@code id} when it has + * one, so the first failure in document order is the one reported. + */ +final class StrictJsonReader extends JsonReader { + + /** What the top-level value is expected to be. */ + enum Mode { + /** A whole manifest: the root object holds {@code assertions}. */ + MANIFEST, + /** One assertion wire object. */ + ASSERTION, + /** Any JSON: only duplicate member names are checked. */ + GENERIC + } + + /** + * What an open object or array is. The object kinds with a level name are the levels of the + * specification's Table 3.2, each with its listed member names; the rest are not checked for + * case variants. + */ + private enum Kind { + ROOT("manifest root", "assertions", "encryptionInformation", "payload", "schemaVersion", "tdf_spec_version"), + PAYLOAD("payload", "type", "url", "protocol", "mimeType", "isEncrypted", "tdf_spec_version"), + ENCRYPTION_INFORMATION("encryptionInformation", "type", "policy", "keyAccess", "method", "integrityInformation"), + KEY_ACCESS("encryptionInformation.keyAccess[]", "type", "url", "protocol", "wrappedKey", "policyBinding", + "encryptedMetadata", "kid", "sid", "schemaVersion", "ephemeralPublicKey"), + POLICY_BINDING("encryptionInformation.keyAccess[].policyBinding", "alg", "hash"), + METHOD("encryptionInformation.method", "algorithm", "iv", "isStreamable"), + INTEGRITY_INFORMATION("encryptionInformation.integrityInformation", "rootSignature", "segmentHashAlg", + "segmentSizeDefault", "encryptedSegmentSizeDefault", "segments"), + ROOT_SIGNATURE("encryptionInformation.integrityInformation.rootSignature", "alg", "sig"), + SEGMENT("encryptionInformation.integrityInformation.segments[]", "hash", "segmentSize", "encryptedSegmentSize"), + ASSERTION("assertions[]", "id", "type", "scope", "appliesToState", "statement", "binding"), + STATEMENT("assertions[].statement", "format", "schema", "value"), + BINDING("assertions[].binding", "method", "signature", "bdo"), + KEY_ACCESS_ARRAY(null), + SEGMENTS_ARRAY(null), + ASSERTIONS(null), + OTHER(null); + + /** The level's name in Table 3.2, or null if this is not a checked level. */ + final String level; + final List listed; + /** Each listed name by its folded form, so a lookup replaces a scan of the list. */ + final Map listedByFold; + + Kind(String level, String... listed) { + this.level = level; + this.listed = List.of(listed); + Map byFold = new HashMap<>(); + for (String name : listed) { + byFold.put(fold(name), name); + } + this.listedByFold = Map.copyOf(byFold); + } + + boolean checked() { + return level != null; + } + } + + /** + * Names that are not rejected as case variants of a listed name (rule 1) although they are + * one, by level. Java SDK releases up to 0.7.5 wrote {@code IsStreamable}; readers give it no + * meaning. Two members that are case variants of each other are still rejected (rule 2). + */ + private static final Map> CASE_VARIANT_EXCEPTIONS = + Map.of(Kind.METHOD.level, List.of("IsStreamable")); + + /** Table 3.2: the listed member names of each checked level, by level name. */ + static Map> modelledNames() { + Map> table = new LinkedHashMap<>(); + for (Kind kind : Kind.values()) { + if (kind.checked()) { + table.put(kind.level, kind.listed); + } + } + return table; + } + + /** The rule 1 exceptions, by level name. */ + static Map> caseVariantExceptions() { + return CASE_VARIANT_EXCEPTIONS; + } + + /** Above this many members an object's names move from a list scan to a hash set. */ + private static final int LINEAR_SCAN_LIMIT = 16; + + private static final class Frame { + Kind kind; + boolean inAssertion; + /** The most recent member name read in this object; decides what a nested object is. */ + String lastName; + /** + * The member names seen so far: keyed by folded name at a checked level, so that one + * lookup finds both exact duplicates and case variants, and by exact name elsewhere. + */ + private final List keys = new ArrayList<>(); + private final List exactNames = new ArrayList<>(); + private Map nameMap; + // assertion frames only + int idMembers; + String idValue; + AssertionFailureReason pendingReason; + String pendingDetail; + + void reset(Kind kind, boolean inAssertion) { + this.kind = kind; + this.inAssertion = inAssertion; + lastName = null; + keys.clear(); + exactNames.clear(); + nameMap = null; + idMembers = 0; + idValue = null; + pendingReason = null; + pendingDetail = null; + } + + /** + * Records a member name under {@code key}. + * + * @return the exact name of an earlier member with the same key, or null if there is none + */ + String addName(String key, String exactName) { + if (nameMap != null) { + return nameMap.putIfAbsent(key, exactName); + } + int earlier = keys.indexOf(key); + if (earlier >= 0) { + return exactNames.get(earlier); + } + keys.add(key); + exactNames.add(exactName); + if (keys.size() > LINEAR_SCAN_LIMIT) { + nameMap = new HashMap<>(); + for (int i = 0; i < keys.size(); i++) { + nameMap.put(keys.get(i), exactNames.get(i)); + } + keys.clear(); + exactNames.clear(); + } + return null; + } + + /** The assertion's {@code id} if it has exactly one, and it is a valid nonempty string. */ + String exactId() { + if (idMembers != 1 || idValue == null || idValue.isEmpty() || iJsonStringProblem(idValue) != null) { + return null; + } + return idValue; + } + } + + private final Mode mode; + /** One entry per open object or array; entries are reused, so most objects allocate nothing. */ + private Frame[] frames = new Frame[16]; + private int depth; + /** Index of the open assertion object's frame, or -1 outside any assertion. */ + private int assertionDepth = -1; + + StrictJsonReader(Reader in, Mode mode) { + super(new LeadingByteOrderMarkCheck(in)); + this.mode = mode; + setStrictness(Strictness.STRICT); + } + + /** + * Rejects text that starts with U+FEFF. JSON text has no byte order mark (RFC 8259 Section + * 8.1), and Gson would otherwise skip one silently where other readers reject the document. + */ + private static final class LeadingByteOrderMarkCheck extends FilterReader { + private boolean checked; + + LeadingByteOrderMarkCheck(Reader in) { + super(in); + } + + @Override + public int read(char[] buffer, int offset, int length) throws IOException { + int read = super.read(buffer, offset, length); + if (!checked && read > 0) { + checked = true; + if (buffer[offset] == '\uFEFF') { + throw new SDK.AssertionFailureException(null, AssertionFailureReason.INVALID_JSON, + "the JSON text starts with a byte order mark"); + } + } + return read; + } + + @Override + public int read() throws IOException { + char[] one = new char[1]; + return read(one, 0, 1) < 0 ? -1 : one[0]; + } + } + + @Override + public void beginObject() throws IOException { + super.beginObject(); + Frame parent = top(); + Kind kind; + if (parent == null) { + kind = mode == Mode.MANIFEST ? Kind.ROOT : mode == Mode.ASSERTION ? Kind.ASSERTION : Kind.OTHER; + } else { + kind = objectKind(parent.kind, parent.lastName); + } + push(kind, kind == Kind.ASSERTION || (parent != null && parent.inAssertion)); + if (kind == Kind.ASSERTION) { + assertionDepth = depth - 1; + } + } + + @Override + public void endObject() throws IOException { + super.endObject(); + Frame frame = pop(); + if (frame.kind == Kind.ASSERTION) { + assertionDepth = -1; + if (frame.pendingReason != null) { + throw new SDK.AssertionFailureException(frame.exactId(), frame.pendingReason, frame.pendingDetail); + } + } + } + + @Override + public void beginArray() throws IOException { + super.beginArray(); + Frame parent = top(); + push(parent == null ? Kind.OTHER : arrayKind(parent.kind, parent.lastName), + parent != null && parent.inAssertion); + } + + @Override + public void endArray() throws IOException { + super.endArray(); + pop(); + } + + @Override + public String nextName() throws IOException { + String name = super.nextName(); + Frame frame = top(); + String folded = frame.kind.checked() ? fold(name) : name; + String earlier = frame.addName(folded, name); + if (earlier != null) { + if (earlier.equals(name)) { + fail(AssertionFailureReason.DUPLICATE_MEMBER, "duplicate member name \"" + name + "\" at " + getPath()); + } else { + // rule 2 + fail(AssertionFailureReason.AMBIGUOUS_MEMBER, "member names \"" + earlier + "\" and \"" + name + + "\" are case variants of each other at " + getPath()); + } + } + if (frame.kind.checked()) { + // rule 1 + String listed = frame.kind.listedByFold.get(folded); + if (listed != null && !listed.equals(name) + && !CASE_VARIANT_EXCEPTIONS.getOrDefault(frame.kind.level, List.of()).contains(name)) { + fail(AssertionFailureReason.AMBIGUOUS_MEMBER, + "member name \"" + name + "\" is a case variant of \"" + listed + "\" at " + getPath()); + } + } + if (frame.inAssertion) { + String problem = iJsonStringProblem(name); + if (problem != null) { + fail(AssertionFailureReason.INVALID_JSON, "member name with " + problem + " at " + getPath()); + } + } + frame.lastName = name; + if (frame.kind == Kind.ASSERTION && "id".equals(name)) { + frame.idMembers++; + } + return name; + } + + @Override + public String nextString() throws IOException { + JsonToken token = peek(); + String value = super.nextString(); + Frame frame = top(); + if (frame != null) { + if (frame.inAssertion) { + if (token == JsonToken.NUMBER) { + if (isOutOfDoubleRange(value)) { + fail(AssertionFailureReason.INVALID_JSON, + "number outside the IEEE 754 double range at " + getPreviousPath()); + } + } else { + String problem = iJsonStringProblem(value); + if (problem != null) { + fail(AssertionFailureReason.INVALID_JSON, "string with " + problem + " at " + getPreviousPath()); + } + } + } + if (frame.kind == Kind.ASSERTION && token == JsonToken.STRING && "id".equals(frame.lastName)) { + frame.idValue = value; + } + } + return value; + } + + /** + * Walks the value through the checking methods above rather than skipping it, so content + * the typed model ignores is checked too. Nesting is bounded by the reader's nesting limit. + */ + @Override + public void skipValue() throws IOException { + switch (peek()) { + case BEGIN_ARRAY: + beginArray(); + while (hasNext()) { + skipValue(); + } + endArray(); + break; + case BEGIN_OBJECT: + beginObject(); + while (hasNext()) { + nextName(); + skipValue(); + } + endObject(); + break; + case END_ARRAY: + endArray(); + break; + case END_OBJECT: + endObject(); + break; + case NAME: + nextName(); + break; + case STRING: + case NUMBER: + nextString(); + break; + case BOOLEAN: + nextBoolean(); + break; + case NULL: + nextNull(); + break; + case END_DOCUMENT: + default: + // nothing to skip; JsonReader leaves the end of the document in place too + break; + } + } + + /** True while the reader is inside an assertion object, as when a syntax error stopped it there. */ + boolean inAssertion() { + return assertionDepth >= 0; + } + + /** The exact {@code id} of the open assertion object, if one has been read; otherwise null. */ + String openAssertionId() { + return assertionDepth >= 0 ? frames[assertionDepth].exactId() : null; + } + + private void fail(AssertionFailureReason reason, String detail) { + if (assertionDepth >= 0) { + Frame assertion = frames[assertionDepth]; + if (assertion.pendingReason == null) { + assertion.pendingReason = reason; + assertion.pendingDetail = detail; + } + return; + } + throw new SDK.AssertionFailureException(null, reason, detail); + } + + private Frame top() { + return depth == 0 ? null : frames[depth - 1]; + } + + private void push(Kind kind, boolean inAssertion) { + if (depth == frames.length) { + frames = Arrays.copyOf(frames, depth * 2); + } + Frame frame = frames[depth]; + if (frame == null) { + frame = new Frame(); + frames[depth] = frame; + } + frame.reset(kind, inAssertion); + depth++; + } + + private Frame pop() { + return frames[--depth]; + } + + /** The kind of an object that is the value of {@code name} in, or an element of, {@code parent}. */ + private static Kind objectKind(Kind parent, String name) { + switch (parent) { + case ROOT: + return "payload".equals(name) ? Kind.PAYLOAD + : "encryptionInformation".equals(name) ? Kind.ENCRYPTION_INFORMATION : Kind.OTHER; + case ENCRYPTION_INFORMATION: + return "method".equals(name) ? Kind.METHOD + : "integrityInformation".equals(name) ? Kind.INTEGRITY_INFORMATION : Kind.OTHER; + case KEY_ACCESS_ARRAY: + return Kind.KEY_ACCESS; + case KEY_ACCESS: + return "policyBinding".equals(name) ? Kind.POLICY_BINDING : Kind.OTHER; + case INTEGRITY_INFORMATION: + return "rootSignature".equals(name) ? Kind.ROOT_SIGNATURE : Kind.OTHER; + case SEGMENTS_ARRAY: + return Kind.SEGMENT; + case ASSERTIONS: + return Kind.ASSERTION; + case ASSERTION: + return "statement".equals(name) ? Kind.STATEMENT : "binding".equals(name) ? Kind.BINDING : Kind.OTHER; + default: + return Kind.OTHER; + } + } + + /** The kind of an array that is the value of {@code name} in {@code parent}. */ + private static Kind arrayKind(Kind parent, String name) { + if (parent == Kind.ROOT && "assertions".equals(name)) { + return Kind.ASSERTIONS; + } + if (parent == Kind.ENCRYPTION_INFORMATION && "keyAccess".equals(name)) { + return Kind.KEY_ACCESS_ARRAY; + } + if (parent == Kind.INTEGRITY_INFORMATION && "segments".equals(name)) { + return Kind.SEGMENTS_ARRAY; + } + return Kind.OTHER; + } + + /** + * {@code name} folded for case-variant comparison: ASCII letters to lower case, U+017F LATIN + * SMALL LETTER LONG S to {@code s}, U+212A KELVIN SIGN to {@code k}, and every other code + * point unchanged. Two different names are case variants exactly when their folded forms are + * equal, which is Go's {@code strings.EqualFold} against an ASCII name, how Go's struct + * decoder matches members. {@link String#equalsIgnoreCase} folds differently and must not be + * used here. + */ + static String fold(String name) { + StringBuilder folded = null; + for (int i = 0; i < name.length(); ) { + int codePoint = name.codePointAt(i); + int mapped; + if (codePoint >= 'A' && codePoint <= 'Z') { + mapped = codePoint + ('a' - 'A'); + } else if (codePoint == 0x017F) { + mapped = 's'; + } else if (codePoint == 0x212A) { + mapped = 'k'; + } else { + mapped = codePoint; + } + if (mapped != codePoint && folded == null) { + folded = new StringBuilder(name.length()).append(name, 0, i); + } + if (folded != null) { + folded.appendCodePoint(mapped); + } + i += Character.charCount(codePoint); + } + return folded == null ? name : folded.toString(); + } + + /** + * What makes {@code text} invalid in I-JSON (RFC 7493 Section 2.1), or null if nothing does: + * a lone surrogate, or a Unicode noncharacter (U+FDD0 to U+FDEF, and the last two code points + * of every plane). + */ + static String iJsonStringProblem(String text) { + for (int i = 0; i < text.length(); ) { + char c = text.charAt(i); + int codePoint; + if (Character.isHighSurrogate(c)) { + if (i + 1 >= text.length() || !Character.isLowSurrogate(text.charAt(i + 1))) { + return "a lone surrogate"; + } + codePoint = Character.toCodePoint(c, text.charAt(i + 1)); + i += 2; + } else if (Character.isLowSurrogate(c)) { + return "a lone surrogate"; + } else { + codePoint = c; + i++; + } + if ((codePoint >= 0xFDD0 && codePoint <= 0xFDEF) || (codePoint & 0xFFFE) == 0xFFFE) { + return "a Unicode noncharacter"; + } + } + return null; + } + + /** True if the JSON number text parses to an infinite double, such as {@code 1e400}. */ + static boolean isOutOfDoubleRange(String numberText) { + try { + return Double.isInfinite(Double.parseDouble(numberText)); + } catch (NumberFormatException e) { + // the reader only hands over well-formed JSON numbers, which always parse + return true; + } + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index dd000a8d..abf60bcf 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -26,7 +26,6 @@ import java.security.*; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; -import java.text.ParseException; import java.util.*; /** @@ -654,7 +653,9 @@ private static Config.IntegrityAlgorithm rootIntegrityAlgorithmFromManifest(Stri /** * @throws IllegalArgumentException if {@code tdfConfig} selects an integrity algorithm - * that cannot be written. Unchecked and not an + * that cannot be written, or holds an assertion that a + * reader would reject while parsing (its wire form is not + * I-JSON). Unchecked and not an * {@link SDKException}, matching how the config layer * already reports out-of-range values (see * {@link Config#withSegmentSize}): this is a caller @@ -772,7 +773,8 @@ TDFObject createTDF(InputStream payload, OutputStream outputStream, Config.TDFCo assertion.statement = assertionConfig.statement; assertion.appliesToState = assertionConfig.appliesToState.toString(); - var assertionHashAsHex = assertion.hash(); + // the wire form written below is exactly what is hashed here, minus the binding + String assertionHashAsHex = assertion.hashForWriting(); byte[] assertionHash; if (tdfConfig.hexEncodeRootAndSegmentHashes) { assertionHash = assertionHashAsHex.getBytes(StandardCharsets.UTF_8); @@ -850,6 +852,12 @@ Reader loadTDF(SeekableByteChannel tdf, Config.TDFReaderConfig tdfReaderConfig) TDFReader tdfReader = TDFReader.open(tdf); Manifest manifest = tdfReader.readManifest(); + // The payload key is assembled only from key access objects; without one there is no key, + // so refuse before any KAS, integrity or assertion step. + if (manifest.encryptionInformation.keyAccessObj.isEmpty()) { + throw new SDK.MalformedTDFException( + "tdf manifest is invalid: encryptionInformation.keyAccess has no key access objects"); + } byte[] payloadKey = new byte[GCM_KEY_SIZE]; String unencryptedMetadata = null; @@ -928,6 +936,10 @@ Reader loadTDF(SeekableByteChannel tdf, Config.TDFReaderConfig tdfReaderConfig) throw new SDK.SplitKeyException(combinedMessage.toString()); } + // never continue with the all-zero starting value of the payload key + if (foundSplits.isEmpty()) { + throw new SDK.SplitKeyException("unable to reconstruct split key: no key access objects yielded a split"); + } } // Validate root signature @@ -979,52 +991,12 @@ Reader loadTDF(SeekableByteChannel tdf, Config.TDFReaderConfig tdfReaderConfig) "segment size mismatch. encrypted segment size differs from plaintext segment size. the TDF is invalid"); } - // Validate assertions - for (var assertion : manifest.assertions) { - // Skip assertion verification if disabled - if (tdfReaderConfig.disableAssertionVerification) { - break; - } - - // Set default to HS256 - var assertionKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, payloadKey); - Config.AssertionVerificationKeys assertionVerificationKeys = tdfReaderConfig.assertionVerificationKeys; - if (!assertionVerificationKeys.isEmpty()) { - var keyForAssertion = assertionVerificationKeys.getKey(assertion.id); - if (keyForAssertion != null) { - assertionKey = keyForAssertion; - } - } - - Manifest.Assertion.HashValues hashValues; - try { - hashValues = assertion.verify(assertionKey); - } catch (ParseException | JOSEException | java.security.cert.CertificateException e) { - throw new SDKException("error validating assertion hash", e); - } - var hashOfAssertionAsHex = assertion.hash(); - - if (!Objects.equals(hashOfAssertionAsHex, hashValues.getAssertionHash())) { - throw new SDK.AssertionException("assertion hash mismatch", assertion.id); - } - - byte[] hashOfAssertion; - if (isLegacyTdf) { - hashOfAssertion = hashOfAssertionAsHex.getBytes(StandardCharsets.UTF_8); - } else { - try { - hashOfAssertion = Hex.decodeHex(hashOfAssertionAsHex); - } catch (DecoderException e) { - throw new SDKException("error decoding assertion hash", e); - } - } - var signature = new byte[aggregateHash.length + hashOfAssertion.length]; - System.arraycopy(aggregateHash, 0, signature, 0, aggregateHash.length); - System.arraycopy(hashOfAssertion, 0, signature, aggregateHash.length, hashOfAssertion.length); - var encodeSignature = Base64.getEncoder().encodeToString(signature); - - if (!Objects.equals(encodeSignature, hashValues.getSignature())) { - throw new SDK.AssertionException("failed integrity check on assertion signature", assertion.id); + // Verify assertions (assertions spec, Section 8). Manifest parsing already applied the + // JSON profile of Section 3, which holds even when verification is disabled. + if (!tdfReaderConfig.disableAssertionVerification) { + for (var assertion : manifest.assertions) { + AssertionVerifier.verify(assertion, tdfReaderConfig.assertionVerificationKeys, payloadKey, + aggregateHash, isLegacyTdf); } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java index bb92fe38..bcdf4c25 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java @@ -8,6 +8,8 @@ import java.io.InputStreamReader; import java.io.Reader; import java.nio.channels.SeekableByteChannel; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.stream.Collectors; @@ -67,7 +69,10 @@ static TDFReader open(SeekableByteChannel tdf) throws IOException { /** * Reads and validates the manifest. Malformed JSON, and JSON that does not have the shape of - * a manifest, is reported as a {@link SDK.MalformedTDFException}. + * a manifest, is reported as a {@link SDK.MalformedTDFException}. A manifest that is not valid + * UTF-8, or that breaks the JSON profile of the assertions specification (duplicate member + * names, case variants of modelled member names, non-I-JSON assertion content), is reported + * as a {@link SDK.AssertionFailureException}. */ Manifest readManifest() { try (Reader manifestJson = manifest()) { @@ -96,8 +101,12 @@ Reader manifest() { // no fixed size cap: manifests for very large payloads exceed 1 GiB. a hostile declared // size is still bounded by the archive, since the entry's stream throws where it ends try { - return new BufferedReader( - new InputStreamReader(manifestEntry.getData(), StandardCharsets.UTF_8), MANIFEST_BUFFER_SIZE); + // REPORT, not the default REPLACE: invalid UTF-8 must fail rather than turn silently + // into U+FFFD, which would hash and verify as if the writer had written it + CharsetDecoder utf8 = StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + return new BufferedReader(new InputStreamReader(manifestEntry.getData(), utf8), MANIFEST_BUFFER_SIZE); } catch (InvalidZipException e) { throw malformed("error retrieving manifest from zip file", e); } catch (IOException e) { diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AssertionHashTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionHashTest.java new file mode 100644 index 00000000..59d1fb63 --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionHashTest.java @@ -0,0 +1,278 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * {@link Manifest.Assertion#computeAssertionHash}: the hash input is the assertion as received, + * minus only its top-level {@code binding} (spec Section 4), after the JSON profile checks of + * Section 3. + */ +class AssertionHashTest { + + /** Single quotes stand in for double quotes, to keep the fixtures readable. */ + private static String json(String singleQuoted) { + return singleQuoted.replace('\'', '"'); + } + + private static String sha256Hex(String text) throws Exception { + return Hex.encodeHexString(MessageDigest.getInstance("SHA-256").digest(text.getBytes(StandardCharsets.UTF_8))); + } + + /** The classification example of spec Section 4.4. */ + static JsonObject classificationAssertion() { + var statement = new JsonObject(); + statement.addProperty("format", "json+stanag5636"); + statement.addProperty("schema", "urn:nato:stanag:5636:A:1:elements:json"); + statement.addProperty("value", + "{\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"SECRET\",\"dcr\":\"2024-10-21T20:47:36Z\"}}"); + var assertion = new JsonObject(); + assertion.addProperty("id", "classification"); + assertion.addProperty("type", "handling"); + assertion.addProperty("scope", "payload"); + assertion.addProperty("appliesToState", "unencrypted"); + assertion.add("statement", statement); + return assertion; + } + + /** The ACP 240-style example of spec Sections 2.2 and 4.4, with an object-valued label. */ + static JsonObject acp240Assertion() { + var catl = new JsonObject(); + catl.addProperty("type", "P"); + catl.addProperty("name", "Releasable To"); + var vals = new JsonArray(); + vals.add("USA"); + catl.add("vals", vals); + var catls = new JsonArray(); + catls.add(catl); + var ocl = new JsonObject(); + ocl.addProperty("pol", "62c76c68-d73d-4628-8ccc-4c1e18118c22"); + ocl.addProperty("cls", "CONFIDENTIAL"); + ocl.add("catl", catls); + ocl.addProperty("dcr", "2026-11-06T12:00:00Z"); + var context = new JsonObject(); + context.addProperty("@base", "urn:nato:stanag:5636:A:1:elements:json"); + var value = new JsonObject(); + value.add("@context", context); + value.add("ocl", ocl); + var statement = new JsonObject(); + statement.addProperty("format", "json-structured"); + statement.addProperty("schema", "urn:nato:stanag:5636:A:1:elements:json"); + statement.add("value", value); + var assertion = new JsonObject(); + assertion.addProperty("id", "3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f"); + assertion.addProperty("type", "handling"); + assertion.addProperty("scope", "payload"); + assertion.addProperty("appliesToState", "encrypted"); + assertion.add("statement", statement); + return assertion; + } + + @Test + void matchesTheSpecWorkedExamples() { + assertThat(Manifest.Assertion.computeAssertionHash(classificationAssertion().toString())) + .isEqualTo("456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495"); + + var withExtension = classificationAssertion(); + withExtension.add("extensions", JsonParser.parseString(json("{'hash':'deadbeef','note':'coi'}"))); + assertThat(Manifest.Assertion.computeAssertionHash(withExtension.toString())) + .as("an unmodelled member is part of the hash") + .isEqualTo("a7347ff1c718c0ee7e88558de64389d5e7194990eb6c78662d0bcd9cfb19c7ba"); + + assertThat(Manifest.Assertion.computeAssertionHash(acp240Assertion().toString())) + .as("an object-valued statement is hashed as an object") + .isEqualTo("46853cb52912fd76f3ff90df3936158ee220b8e5973c5d02bb6e50f468596fba"); + } + + @Test + void ignoresOnlyTheTopLevelBindingAndTheLayout() { + var expected = Manifest.Assertion.computeAssertionHash(classificationAssertion().toString()); + + var withBinding = classificationAssertion(); + withBinding.add("binding", JsonParser.parseString(json("{'method':'jws','signature':'a.b.c'}"))); + assertThat(Manifest.Assertion.computeAssertionHash(withBinding.toString())).isEqualTo(expected); + + var reordered = new JsonObject(); + reordered.add("statement", classificationAssertion().get("statement")); + reordered.addProperty("type", "handling"); + reordered.addProperty("appliesToState", "unencrypted"); + reordered.addProperty("scope", "payload"); + reordered.addProperty("id", "classification"); + var spaced = new com.google.gson.GsonBuilder().setPrettyPrinting().create().toJson(reordered); + assertThat(spaced).contains("\n"); + assertThat(Manifest.Assertion.computeAssertionHash(spaced)).isEqualTo(expected); + } + + @Test + void hashesNestedMembersNamedLikeExcludedOnes() { + var plain = classificationAssertion(); + var nested = classificationAssertion(); + nested.getAsJsonObject("statement").addProperty("hash", "x"); + nested.getAsJsonObject("statement").add("binding", JsonParser.parseString("{\"signingKey\":\"y\"}")); + assertThat(Manifest.Assertion.computeAssertionHash(nested.toString())) + .isNotEqualTo(Manifest.Assertion.computeAssertionHash(plain.toString())); + } + + @Test + void anExplicitNullIsHashed() { + var withNull = classificationAssertion(); + withNull.add("appliesToState", com.google.gson.JsonNull.INSTANCE); + var without = classificationAssertion(); + without.remove("appliesToState"); + assertThat(Manifest.Assertion.computeAssertionHash(withNull.toString())) + .isNotEqualTo(Manifest.Assertion.computeAssertionHash(without.toString())); + } + + static Stream canonicalForms() { + return Stream.of( + // numbers are IEEE 754 doubles in RFC 8785, printed the way ECMAScript prints them + Arguments.of("{'id':'a','n':1E21}", "{'id':'a','n':1e+21}"), + Arguments.of("{'id':'a','n':-0}", "{'id':'a','n':0}"), + Arguments.of("{'id':'a','n':9007199254740993}", "{'id':'a','n':9007199254740992}"), + Arguments.of("{'id':'a','n':1.0}", "{'id':'a','n':1}"), + Arguments.of("{'id':'a','n':0.0000001}", "{'id':'a','n':1e-7}"), + Arguments.of("{'id':'a','n':1e-400}", "{'id':'a','n':0}"), + Arguments.of("{'id':'a','n':123456789012345680000}", "{'id':'a','n':123456789012345680000}"), + // escapes are decoded, and only the RFC 8785 set is re-escaped + Arguments.of("{'id':'\\u0061','s':'\\u00e9\\/\\u2028'}", "{'id':'a','s':'é/
'}"), + Arguments.of("{'id':'a','s':'\\u001f\\b'}", "{'id':'a','s':'\\u001f\\b'}"), + // keys sort by UTF-16 code unit, not by code point + Arguments.of("{'fi':2,'😀':1,'id':'a'}", "{'id':'a','😀':1,'fi':2}")); + } + + @ParameterizedTest + @MethodSource("canonicalForms") + void canonicalizesUnderRfc8785(String input, String canonical) throws Exception { + assertThat(Manifest.Assertion.computeAssertionHash(json(input))).isEqualTo(sha256Hex(json(canonical))); + } + + static Stream rejected() { + return Stream.of( + Arguments.of("{'id':'a','id':'b'}", AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of("{'id':'a','\\u0069d':'b'}", AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of("{'id':'a','x':1,'x':1}", AssertionFailureReason.DUPLICATE_MEMBER, "a"), + Arguments.of("{'id':'a','statement':{'value':{'x':1,'x':2}}}", AssertionFailureReason.DUPLICATE_MEMBER, "a"), + Arguments.of("{'id':'a','ID':'b'}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'Id':'b'}", AssertionFailureReason.AMBIGUOUS_MEMBER, null), + Arguments.of("{'id':'a','Statement':{}}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'id':'a','APPLIESTOSTATE':'x'}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'id':'a','ſtatement':{}}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'id':'a','statement':{'Value':'x'}}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'id':'a','binding':{'Method':'jws','signature':'x'}}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'id':'a','binding':{'BDO':'x'}}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'id':'a','x':'\\uDEAD'}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','x':'\\uD83D'}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','\\uDEAD':1}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','x':'\\uFFFE'}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','x':'\\uFDD0'}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','x':'\\uD83F\\uDFFF'}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','statement':{'value':{'n':[1e400]}}}", AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of("{'id':'a','n':-1E400}", AssertionFailureReason.INVALID_JSON, "a"), + // a bad id is never reported as the assertion's id + Arguments.of("{'id':'\\uDEAD'}", AssertionFailureReason.INVALID_JSON, null), + // the first failure in document order wins + Arguments.of("{'id':'a','ID':1,'x':'\\uDEAD'}", AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + Arguments.of("{'x':'\\uDEAD','id':'a','id':'b'}", AssertionFailureReason.INVALID_JSON, null)); + } + + @ParameterizedTest + @MethodSource("rejected") + void rejectsWhatSection3Forbids(String input, AssertionFailureReason reason, String id) { + assertThatThrownBy(() -> Manifest.Assertion.computeAssertionHash(json(input))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> { + assertThat(e.getReason()).isEqualTo(reason); + assertThat(e.getAssertionId()).isEqualTo(id); + assertThat(e).hasMessageContaining("reason=" + reason.code()); + if (id != null) { + assertThat(e).hasMessageContaining("id: " + id); + } + }) + .isInstanceOf(SDK.TamperException.class); + } + + @ParameterizedTest + @ValueSource(strings = { + // not a case variant under Go's EqualFold, so an ordinary unmodelled member + "{'id':'a','ıd':'x'}", + // the rule does not reach into statement values or unmodelled members + "{'id':'a','statement':{'value':{'ID':1,'Value':2,'Binding':3}}}", + "{'id':'a','extensions':{'Binding':1,'Statement':2}}", + // a valid surrogate pair and a noncharacter-free astral code point are fine + "{'id':'a','x':'\\uD83D\\uDE00'}", + // no id at all is still hashable; the id is checked when the assertion is verified + "{'type':'handling'}", + "{}" + }) + void acceptsWhatSection3Allows(String input) { + assertThat(Manifest.Assertion.computeAssertionHash(json(input))).matches("[0-9a-f]{64}"); + } + + @ParameterizedTest + @ValueSource(strings = {"", "[]", "\"x\"", "{", "{} {}", "{'id':'a',}", "{id:'a'}", "/*c*/{}", "{'id':'a'} x"}) + void rejectsTextThatIsNotOneStrictJsonObject(String input) { + assertThatThrownBy(() -> Manifest.Assertion.computeAssertionHash(json(input))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON)); + } + + @Test + void anUnmodelledMemberChangesTheHash() { + var original = classificationAssertion(); + original.addProperty("note", "approved"); + var tampered = classificationAssertion(); + tampered.addProperty("note", "rejected"); + assertThat(Manifest.Assertion.computeAssertionHash(original.toString())) + .isNotEqualTo(Manifest.Assertion.computeAssertionHash(tampered.toString())); + } + + private static String message(String id, String detail) { + return new SDK.AssertionFailureException(id, AssertionFailureReason.BINDING_MISMATCH, detail).getMessage(); + } + + @Test + void idsAreRenderedSafelyInMessages() { + var failure = new SDK.AssertionFailureException("a\nreason=verified", AssertionFailureReason.BINDING_MISMATCH, null); + // the typed field keeps the exact id + assertThat(failure.getAssertionId()).isEqualTo("a\nreason=verified"); + assertThat(failure.getMessage()) + .doesNotContain("\n") + .contains("id: a\\nreason=verified") + .endsWith(", reason=binding-mismatch"); + + // C1 controls such as NEL, DEL, and other C0 controls are escaped JSON-style + assertThat(message("a\u0085b\u007fc\u001bd\te", null)) + .contains("id: a\\u0085b\\u007fc\\u001bd\\te,"); + // a backslash is escaped, so escape-like text in an id cannot pass for an escape + assertThat(message("a\\u000ab", null)).contains("id: a\\\\u000ab,"); + // text that looks like a reason stays part of the id, before the real reason + assertThat(message("x, reason=signature-invalid", null)) + .endsWith("id: x, reason=signature-invalid, reason=binding-mismatch"); + } + + @Test + void longIdsAndDetailsAreCut() { + String id = "\uD83D\uDE00".repeat(100_000); + var failure = new SDK.AssertionFailureException(id, AssertionFailureReason.BINDING_MISMATCH, "x".repeat(1_000_000)); + assertThat(failure.getAssertionId()).isEqualTo(id); + String message = failure.getMessage(); + assertThat(message).contains("id: " + "\uD83D\uDE00".repeat(256) + "\u2026, reason=binding-mismatch: ") + .endsWith("x".repeat(512) + "\u2026") + .hasSizeLessThan(2_000); + // an id of exactly the limit is not cut + assertThat(message("y".repeat(256), null)).contains("id: " + "y".repeat(256) + ", reason="); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVectorsTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVectorsTest.java new file mode 100644 index 00000000..399b844b --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVectorsTest.java @@ -0,0 +1,211 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Runs the DSPX-5520 shared assertion conformance vectors, which the Go, Java and JavaScript + * SDKs all vendor byte for byte from opentdf/tests {@code xtest/vectors/assertions-phase0.json}. + * Do not edit the copy here; regenerate it there and copy it again. + *

+ * Every {@code required} vector must give exactly its expected result. {@code proposed} vectors + * describe rules the specification has not settled; they run, and a mismatch is only logged. + */ +class AssertionVectorsTest { + + private static final String RESOURCE = "/assertion-vectors-phase0.json"; + private static final JsonObject DOCUMENT = load(); + + private static JsonObject load() { + try (InputStream in = Objects.requireNonNull(AssertionVectorsTest.class.getResourceAsStream(RESOURCE), RESOURCE)) { + return JsonParser.parseString(new String(in.readAllBytes(), StandardCharsets.UTF_8)).getAsJsonObject(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + static Stream vectors() { + List vectors = new ArrayList<>(); + for (var vector : DOCUMENT.getAsJsonArray("vectors")) { + var object = vector.getAsJsonObject(); + vectors.add(Arguments.of(object.get("name").getAsString(), object)); + } + return vectors.stream(); + } + + @Test + void theFileIsTheSharedFormat() { + assertThat(DOCUMENT.get("format").getAsString()).isEqualTo("opentdf-assertion-vectors/1"); + assertThat(DOCUMENT.getAsJsonArray("vectors")).isNotEmpty(); + } + + /** The file names the root level {@code ""}; Table 3.2 calls it the manifest root. */ + private static String fileLevel(String level) { + return "manifest root".equals(level) ? "" : level; + } + + private static Map> stringLists(JsonObject json) { + Map> map = new LinkedHashMap<>(); + for (var entry : json.entrySet()) { + List names = new ArrayList<>(); + entry.getValue().getAsJsonArray().forEach(name -> names.add(name.getAsString())); + map.put(entry.getKey(), names); + } + return map; + } + + @Test + void theModelledNamesAreTheSharedTable() { + Map> java = new LinkedHashMap<>(); + StrictJsonReader.modelledNames().forEach((level, names) -> java.put(fileLevel(level), names)); + assertThat(java).isEqualTo(stringLists(DOCUMENT.getAsJsonObject("modelledNames"))); + + Map> exceptions = new LinkedHashMap<>(); + StrictJsonReader.caseVariantExceptions().forEach((level, names) -> exceptions.put(fileLevel(level), names)); + assertThat(exceptions).isEqualTo(stringLists(DOCUMENT.getAsJsonObject("caseVariantExceptions"))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("vectors") + void vector(String name, JsonObject vector) throws Exception { + JsonObject expected = vector.getAsJsonObject("expect"); + JsonObject actual = run(vector); + if ("required".equals(vector.get("status").getAsString())) { + assertThat(actual).as("%s: %s", name, vector.get("note").getAsString()).isEqualTo(expected); + } else if (!actual.equals(expected)) { + System.out.printf("proposed vector %s: expected %s, got %s (not enforced)%n", name, expected, actual); + } + } + + static Stream requiredAssertionVectors() { + return vectors().filter(arguments -> { + JsonObject vector = (JsonObject) arguments.get()[1]; + return "assertion".equals(vector.get("kind").getAsString()) + && "required".equals(vector.get("status").getAsString()) + // content after the object is not an assertion inside an array + && !"assertion-syntax-trailing-data".equals(vector.get("name").getAsString()); + }); + } + + /** + * The same assertion vectors, read the way a TDF is read: spliced into a manifest, zipped, + * and parsed by {@link SDK#readManifest}, with the hash and id taken from the result. + */ + @ParameterizedTest(name = "{0}") + @MethodSource("requiredAssertionVectors") + void assertionVectorThroughTheManifestReader(String name, JsonObject vector) throws Exception { + byte[] assertion = vector.has("input") + ? vector.get("input").getAsString().getBytes(StandardCharsets.UTF_8) + : Base64.getDecoder().decode(vector.get("inputBase64").getAsString()); + String wrapper = StrictManifestParsingTest.manifest(",\"assertions\":[@]"); + int at = wrapper.indexOf('@'); + var manifest = new java.io.ByteArrayOutputStream(); + manifest.write(wrapper.substring(0, at).getBytes(StandardCharsets.UTF_8)); + manifest.write(assertion); + manifest.write(wrapper.substring(at + 1).getBytes(StandardCharsets.UTF_8)); + byte[] tdf = StrictManifestParsingTest.tdfWithManifest(manifest.toByteArray()); + + var result = new JsonObject(); + try { + Manifest read = SDK.readManifest(new org.apache.commons.compress.utils.SeekableInMemoryByteChannel(tdf)); + JsonObject received = read.assertions.get(0).received(); + AssertionVerifier.exactId(received); + String hash = AssertionJson.hash(received); + // writing the manifest back out keeps the hash + Manifest again = Manifest.readManifest(Manifest.toJson(read)); + assertThat(AssertionJson.hash(again.assertions.get(0).received())).as("round trip").isEqualTo(hash); + result.addProperty("assertionHash", hash); + } catch (SDK.AssertionFailureException e) { + result.addProperty("reason", e.getReason().code()); + } + assertThat(result).as("%s: %s", name, vector.get("note").getAsString()).isEqualTo(vector.getAsJsonObject("expect")); + } + + /** What the reader does with one vector, as the vector's kind defines it. */ + private static JsonObject run(JsonObject vector) throws Exception { + var result = new JsonObject(); + try { + String input = vector.has("input") + ? vector.get("input").getAsString() + : AssertionJson.decodeUtf8(Base64.getDecoder().decode(vector.get("inputBase64").getAsString())); + switch (vector.get("kind").getAsString()) { + case "assertion": { + // Section 3 parsing, the 3.3 id check, then the public hash API + JsonObject assertion = AssertionJson.parseAssertion(input); + AssertionVerifier.exactId(assertion); + result.addProperty("assertionHash", Manifest.Assertion.computeAssertionHash(input)); + break; + } + case "manifest": + Manifest.checkManifestJson(input); + result.addProperty("ok", true); + break; + case "binding": + AssertionVerifier.jwsBinding(AssertionJson.parseAssertion(input), null); + result.addProperty("bindingForm", "jws"); + break; + case "verify": { + // Section 8, steps 3.3 to 3.9, with the key already selected + JsonObject assertion = AssertionJson.parseAssertion(input); + var key = key(vector.get("selectedKey").getAsString(), vector.get("keySource").getAsString()); + AssertionVerifier.verifyWithKey(assertion, null, key, + Base64.getDecoder().decode(vector.get("aggregateHashBase64").getAsString()), + vector.get("legacy").getAsBoolean()); + result.addProperty("ok", true); + break; + } + default: + throw new IllegalArgumentException("unknown vector kind " + vector.get("kind")); + } + } catch (SDK.AssertionFailureException e) { + result = new JsonObject(); + result.addProperty("reason", e.getReason().code()); + } + return result; + } + + /** + * The selected key, prepared the way the reader prepares it: a configured key goes through + * the same checks as a configured {@link AssertionConfig.AssertionKey}, so the deliberately + * unusable entries fail with {@code configuration-error}; the payload key is HS256. + */ + private static AssertionVerifier.SelectedKey key(String name, String source) throws Exception { + JsonObject entry = DOCUMENT.getAsJsonObject("keys").getAsJsonObject(name); + Object material; + if (entry.has("publicKeyPem")) { + String pem = entry.get("publicKeyPem").getAsString() + .replaceAll("-----(BEGIN|END) PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + material = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(pem))); + } else { + material = Base64.getDecoder().decode(entry.get("secretBase64").getAsString()); + } + if ("payload".equals(source)) { + return AssertionVerifier.SelectedKey.hmac((byte[]) material); + } + String alg = entry.get("alg").getAsString(); + // the enum holds only the supported names; anything else arrives as no algorithm + AssertionConfig.AssertionKeyAlg keyAlg = "RS256".equals(alg) ? AssertionConfig.AssertionKeyAlg.RS256 + : "HS256".equals(alg) ? AssertionConfig.AssertionKeyAlg.HS256 : null; + return AssertionVerifier.configuredKey(new AssertionConfig.AssertionKey(keyAlg, material), null, "the key " + name); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVerificationTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVerificationTest.java new file mode 100644 index 00000000..6162d81c --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVerificationTest.java @@ -0,0 +1,668 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Assertion verification on read: the key is selected once from the reader's configuration + * (per-ID key, then default key, then the payload key with HS256), keys embedded in the JWS + * header never add trust, and the hash covers the assertion as received. + *

+ * Every negative case has a positive control showing that the same reader configuration accepts + * the authorized signer. + */ +class AssertionVerificationTest { + + private static final String KAS_URL = "https://example.com/kas0"; + private static final String ID = "assertion1"; + private static final byte[] PLAINTEXT = "assertion verification".getBytes(StandardCharsets.UTF_8); + + private static KeyPair kasKeyPair; + /** The signer the reader is configured with. */ + private static KeyPair issuer; + /** A key the reader is not configured with. */ + private static KeyPair unrelated; + + private static final SDK.KAS KAS = new SDK.KAS() { + @Override + public void close() { + // nothing to release + } + + @Override + public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { + var resolved = new Config.KASInfo(); + resolved.URL = kasInfo.URL; + resolved.KID = "r1"; + resolved.PublicKey = CryptoUtils.getPublicKeyPEM(kasKeyPair.getPublic()); + return resolved; + } + + @Override + public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessionKeyType) { + return new AsymDecryption(kasKeyPair.getPrivate()).decrypt(Base64.getDecoder().decode(keyAccess.wrappedKey)); + } + + @Override + public KASKeyCache getKeyCache() { + return new KASKeyCache(); + } + }; + + @BeforeAll + static void generateKeys() { + kasKeyPair = CryptoUtils.generateRSAKeypair(); + issuer = CryptoUtils.generateRSAKeypair(); + unrelated = CryptoUtils.generateRSAKeypair(); + } + + // ------------------------------------------------------------------ fixtures + + private static AssertionConfig assertionConfig(String id, AssertionConfig.AssertionKey signingKey) { + var config = new AssertionConfig(); + config.id = id; + config.type = AssertionConfig.Type.HandlingAssertion; + config.scope = AssertionConfig.Scope.Payload; + config.appliesToState = AssertionConfig.AppliesToState.Unencrypted; + config.statement = new AssertionConfig.Statement(); + config.statement.format = "json+stanag5636"; + config.statement.schema = "urn:nato:stanag:5636:A:1:elements:json"; + config.statement.value = "{\"ocl\":{\"cls\":\"SECRET\"}}"; + config.signingKey = signingKey; + return config; + } + + private static AssertionConfig.AssertionKey issuerSigningKey() { + return new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, issuer.getPrivate()); + } + + private static byte[] createTdf(AssertionConfig... assertions) throws IOException { + var kasInfo = new Config.KASInfo(); + kasInfo.URL = KAS_URL; + var out = new ByteArrayOutputStream(); + new TDF(new FakeServicesBuilder().setKas(KAS).build()).createTDF(new ByteArrayInputStream(PLAINTEXT), out, + Config.newTDFConfig(Config.withAutoconfigure(false), Config.withKasInformation(kasInfo), + Config.withAssertionConfig(assertions))); + return out.toByteArray(); + } + + private static TDF.Reader load(byte[] tdf, Config.AssertionVerificationKeys keys) throws IOException { + var options = new ArrayList>(); + options.add(Config.WithIgnoreKasAllowlist(true)); + if (keys != null) { + options.add(Config.withAssertionVerificationKeys(keys)); + } + @SuppressWarnings("unchecked") + Consumer[] asArray = options.toArray(new Consumer[0]); + return new TDF(new FakeServicesBuilder().setKas(KAS).build()) + .loadTDF(new SeekableInMemoryByteChannel(tdf), Config.newTDFReaderConfig(asArray)); + } + + private static void assertDecrypts(byte[] tdf, Config.AssertionVerificationKeys keys) throws IOException { + var reader = load(tdf, keys); + var out = new ByteArrayOutputStream(); + reader.readPayload(out); + assertThat(out.toByteArray()).isEqualTo(PLAINTEXT); + } + + private static void assertFails(byte[] tdf, Config.AssertionVerificationKeys keys, AssertionFailureReason reason, + String id) { + assertThatThrownBy(() -> load(tdf, keys)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> { + assertThat(e.getReason()).isEqualTo(reason); + assertThat(e.getAssertionId()).isEqualTo(id); + assertThat(e.getMessage()).contains("reason=" + reason.code()).contains("assertion") + .contains("tamper"); + if (id != null) { + assertThat(e.getMessage()).contains("id: " + id); + } + }) + .isInstanceOf(SDK.AssertionException.class); + } + + private static Config.AssertionVerificationKeys perId(String id, AssertionConfig.AssertionKey key) { + var keys = new Config.AssertionVerificationKeys(); + keys.keys.put(id, key); + return keys; + } + + private static Config.AssertionVerificationKeys defaultKey(AssertionConfig.AssertionKey key) { + var keys = new Config.AssertionVerificationKeys(); + keys.defaultKey = key; + return keys; + } + + private static AssertionConfig.AssertionKey rs256(KeyPair keyPair) { + return new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, keyPair.getPublic()); + } + + private static AssertionConfig.AssertionKey hs256(byte[] secret) { + return new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, secret); + } + + // ------------------------------------------------------------ manifest surgery + + private static JsonObject manifestOf(byte[] tdf) throws IOException { + return JsonParser.parseString(new String(entry(tdf, TDFWriter.TDF_MANIFEST_FILE_NAME), StandardCharsets.UTF_8)) + .getAsJsonObject(); + } + + private static byte[] entry(byte[] tdf, String name) throws IOException { + for (var entry : new ZipReader(new SeekableInMemoryByteChannel(tdf)).getEntries()) { + if (entry.getName().equals(name)) { + try (InputStream data = entry.getData()) { + return data.readAllBytes(); + } + } + } + throw new IllegalArgumentException("no entry named " + name); + } + + /** A copy of the TDF with its manifest JSON edited. */ + private static byte[] rewrite(byte[] tdf, Consumer edit) throws IOException { + var manifest = manifestOf(tdf); + edit.accept(manifest); + var out = new ByteArrayOutputStream(); + var writer = new TDFWriter(out); + try (var payload = writer.payload()) { + payload.write(entry(tdf, TDFWriter.TDF_PAYLOAD_FILE_NAME)); + } + try (var manifestOut = writer.manifest()) { + manifestOut.write(new Gson().toJson(manifest).getBytes(StandardCharsets.UTF_8)); + } + writer.finish(); + return out.toByteArray(); + } + + private static JsonArray assertions(JsonObject manifest) { + return manifest.getAsJsonArray("assertions"); + } + + private static byte[] aggregate(JsonObject manifest) { + var aggregate = new ByteArrayOutputStream(); + for (var segment : manifest.getAsJsonObject("encryptionInformation").getAsJsonObject("integrityInformation") + .getAsJsonArray("segments")) { + aggregate.writeBytes(Base64.getDecoder().decode(segment.getAsJsonObject().get("hash").getAsString())); + } + return aggregate.toByteArray(); + } + + enum Embedded { NONE, JWK, X5C, BOTH } + + /** The wire form of an assertion, without its binding. */ + private static JsonObject wire(String id, String classification) { + var statement = new JsonObject(); + statement.addProperty("format", "json+stanag5636"); + statement.addProperty("schema", "urn:nato:stanag:5636:A:1:elements:json"); + statement.addProperty("value", "{\"ocl\":{\"cls\":\"" + classification + "\"}}"); + var assertion = new JsonObject(); + assertion.addProperty("id", id); + assertion.addProperty("type", "handling"); + assertion.addProperty("scope", "payload"); + assertion.addProperty("appliesToState", "unencrypted"); + assertion.add("statement", statement); + return assertion; + } + + /** + * Binds {@code assertion} to {@code manifest}'s payload exactly as a writer would, signing + * with {@code signer} (RS256) and embedding {@code embedded} in the JWS header. + */ + private static JsonObject bind(JsonObject manifest, JsonObject assertion, KeyPair signer, KeyPair embedded, + Embedded what) throws Exception { + var header = new JWSHeader.Builder(JWSAlgorithm.RS256); + if (what == Embedded.JWK || what == Embedded.BOTH) { + header.jwk(new RSAKey.Builder((RSAPublicKey) embedded.getPublic()).build()); + } + if (what == Embedded.X5C || what == Embedded.BOTH) { + var certificate = TestUtil.createTestCertificate(embedded.getPublic(), embedded.getPrivate()); + header.x509CertChain(List.of(com.nimbusds.jose.util.Base64.encode(certificate.getEncoded()))); + } + var jwt = new SignedJWT(header.build(), claims(manifest, assertion)); + jwt.sign(new RSASSASigner(signer.getPrivate())); + return withBinding(assertion, jwt.serialize()); + } + + private static JsonObject bindHs256(JsonObject manifest, JsonObject assertion, byte[] secret, + Consumer headerEdit) throws Exception { + var header = new JWSHeader.Builder(JWSAlgorithm.HS256); + headerEdit.accept(header); + var jwt = new SignedJWT(header.build(), claims(manifest, assertion)); + jwt.sign(new MACSigner(secret)); + return withBinding(assertion, jwt.serialize()); + } + + private static JWTClaimsSet claims(JsonObject manifest, JsonObject assertion) throws Exception { + String hash = Manifest.Assertion.computeAssertionHash(assertion.toString()); + byte[] aggregate = aggregate(manifest); + byte[] hashBytes = Hex.decodeHex(hash); + byte[] bound = new byte[aggregate.length + hashBytes.length]; + System.arraycopy(aggregate, 0, bound, 0, aggregate.length); + System.arraycopy(hashBytes, 0, bound, aggregate.length, hashBytes.length); + return new JWTClaimsSet.Builder() + .claim("assertionHash", hash) + .claim("assertionSig", Base64.getEncoder().encodeToString(bound)) + .build(); + } + + private static JsonObject withBinding(JsonObject assertion, String signature) { + var bound = assertion.deepCopy(); + var binding = new JsonObject(); + binding.addProperty("method", "jws"); + binding.addProperty("signature", signature); + bound.add("binding", binding); + return bound; + } + + // ---------------------------------------------------------------- controls + + @Test + void theConfiguredSignerVerifiesPerIdAndAsTheDefault() throws Exception { + var tdf = createTdf(assertionConfig(ID, issuerSigningKey())); + assertDecrypts(tdf, perId(ID, rs256(issuer))); + assertDecrypts(tdf, defaultKey(rs256(issuer))); + // an RSA JWK is accepted as the configured key too + assertDecrypts(tdf, perId(ID, new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, + new RSAKey.Builder((RSAPublicKey) issuer.getPublic()).build()))); + } + + @Test + void anUnkeyedAssertionVerifiesWithThePayloadKey() throws Exception { + var tdf = createTdf(assertionConfig(ID, null)); + assertDecrypts(tdf, null); + assertDecrypts(tdf, new Config.AssertionVerificationKeys()); + // a zero-value default key is the same as none + assertDecrypts(tdf, defaultKey(new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.NotDefined, null))); + } + + /** The Java writer may embed its key; that still verifies when the reader configured the same key. */ + @ParameterizedTest + @EnumSource(value = Embedded.class, names = {"JWK", "X5C", "BOTH"}) + void javaWriterEmbeddedKeysVerifyWithThatKeyConfigured(Embedded embedded) throws Exception { + var signingKey = issuerSigningKey(); + if (embedded == Embedded.JWK || embedded == Embedded.BOTH) { + signingKey.withJwk(new RSAKey.Builder((RSAPublicKey) issuer.getPublic()).build()); + } + if (embedded == Embedded.X5C || embedded == Embedded.BOTH) { + var certificate = TestUtil.createTestCertificate(issuer.getPublic(), issuer.getPrivate()); + signingKey.withX5c(List.of(com.nimbusds.jose.util.Base64.encode(certificate.getEncoded()))); + } + var tdf = createTdf(assertionConfig(ID, signingKey)); + assertDecrypts(tdf, perId(ID, rs256(issuer))); + assertDecrypts(tdf, defaultKey(rs256(issuer))); + // the embedded key alone grants nothing + assertFails(tdf, null, AssertionFailureReason.ALGORITHM_MISMATCH, ID); + assertFails(tdf, defaultKey(rs256(unrelated)), AssertionFailureReason.SIGNER_NOT_PERMITTED, ID); + } + + // ------------------------------------------------- embedded keys of other signers + + static Stream unrelatedSignerCases() { + var arguments = new ArrayList(); + for (var embedded : List.of(Embedded.JWK, Embedded.X5C, Embedded.BOTH)) { + for (var replace : List.of(false, true)) { + for (var asDefault : List.of(false, true)) { + arguments.add(Arguments.of(embedded, replace, asDefault)); + } + } + } + return arguments.stream(); + } + + /** + * A binding signed by an unrelated key, with that key embedded as jwk, x5c or both, fails + * against the configured key, per ID or default, whether its assertion is added alongside + * the original or replaces it. + */ + @ParameterizedTest(name = "embedded={0} replace={1} defaultKey={2}") + @MethodSource("unrelatedSignerCases") + void anUnrelatedSignerWithAnEmbeddedKeyFailsAgainstTheConfiguredKey(Embedded embedded, boolean replace, + boolean asDefault) + throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var keys = asDefault ? defaultKey(rs256(issuer)) : perId(ID, rs256(issuer)); + assertDecrypts(original, keys); + + var edited = rewrite(original, manifest -> { + try { + var other = bind(manifest, wire(ID, "UNCLASSIFIED"), unrelated, unrelated, embedded); + if (replace) { + assertions(manifest).set(0, other); + } else { + assertions(manifest).add(other); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertFails(edited, keys, AssertionFailureReason.SIGNER_NOT_PERMITTED, ID); + } + + @Test + void anAssertionUnderAnotherIdGetsTheDefaultOrPayloadKeyNotItsHeaderKey() throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var edited = rewrite(original, manifest -> { + try { + assertions(manifest).add(bind(manifest, wire("other", "UNCLASSIFIED"), unrelated, unrelated, Embedded.JWK)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + // with a default pin, the new id gets the default key + assertDecrypts(original, defaultKey(rs256(issuer))); + assertFails(edited, defaultKey(rs256(issuer)), AssertionFailureReason.SIGNER_NOT_PERMITTED, "other"); + // with only a per-ID pin, the new id gets the payload key, never the header's key + assertDecrypts(original, perId(ID, rs256(issuer))); + assertFails(edited, perId(ID, rs256(issuer)), AssertionFailureReason.ALGORITHM_MISMATCH, "other"); + } + + @Test + void bindingsFromOtherKeysFailWhenThePayloadKeyIsSelected() throws Exception { + var original = createTdf(assertionConfig(ID, null)); + assertDecrypts(original, null); + + for (var embedded : List.of(Embedded.JWK, Embedded.X5C)) { + var rsaBinding = rewrite(original, manifest -> { + try { + assertions(manifest).set(0, bind(manifest, wire(ID, "UNCLASSIFIED"), unrelated, unrelated, embedded)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertFails(rsaBinding, null, AssertionFailureReason.ALGORITHM_MISMATCH, ID); + } + + byte[] otherSecret = AesGcm.generateKey(); + var hmacWithJwk = rewrite(original, manifest -> { + try { + assertions(manifest).set(0, bindHs256(manifest, wire(ID, "UNCLASSIFIED"), otherSecret, + header -> header.jwk(new RSAKey.Builder((RSAPublicKey) unrelated.getPublic()).build()))); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertFails(hmacWithJwk, null, AssertionFailureReason.INVALID_EMBEDDED_KEY, ID); + + var hmacWithoutHeaderKey = rewrite(original, manifest -> { + try { + assertions(manifest).set(0, bindHs256(manifest, wire(ID, "UNCLASSIFIED"), otherSecret, header -> { })); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertFails(hmacWithoutHeaderKey, null, AssertionFailureReason.SIGNATURE_INVALID, ID); + } + + @Test + void theSelectedKeysOwnJwkSignedByAnotherKeyFailsTheSignature() throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var edited = rewrite(original, manifest -> { + try { + assertions(manifest).set(0, bind(manifest, wire(ID, "UNCLASSIFIED"), unrelated, issuer, Embedded.BOTH)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertFails(edited, perId(ID, rs256(issuer)), AssertionFailureReason.SIGNATURE_INVALID, ID); + } + + @Test + void contradictoryEmbeddedKeysAreRejected() throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var edited = rewrite(original, manifest -> { + try { + var assertion = wire(ID, "SECRET"); + var certificate = TestUtil.createTestCertificate(unrelated.getPublic(), unrelated.getPrivate()); + var header = new JWSHeader.Builder(JWSAlgorithm.RS256) + .jwk(new RSAKey.Builder((RSAPublicKey) issuer.getPublic()).build()) + .x509CertChain(List.of(com.nimbusds.jose.util.Base64.encode(certificate.getEncoded()))); + var jwt = new SignedJWT(header.build(), claims(manifest, assertion)); + jwt.sign(new RSASSASigner(issuer.getPrivate())); + assertions(manifest).set(0, withBinding(assertion, jwt.serialize())); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertFails(edited, perId(ID, rs256(issuer)), AssertionFailureReason.INVALID_EMBEDDED_KEY, ID); + } + + // ------------------------------------------------------------ selection order + + @Test + void aFailedPerIdKeyDoesNotFallBackToTheDefaultKey() throws Exception { + var tdf = createTdf(assertionConfig(ID, issuerSigningKey())); + var keys = perId(ID, rs256(unrelated)); + keys.defaultKey = rs256(issuer); + assertFails(tdf, keys, AssertionFailureReason.SIGNATURE_INVALID, ID); + // control: the same default verifies when there is no per-ID entry + assertDecrypts(tdf, defaultKey(rs256(issuer))); + } + + @Test + void aFailedConfiguredKeyDoesNotFallBackToThePayloadKey() throws Exception { + var tdf = createTdf(assertionConfig(ID, null)); + assertDecrypts(tdf, null); + assertFails(tdf, perId(ID, hs256(AesGcm.generateKey())), AssertionFailureReason.SIGNATURE_INVALID, ID); + assertFails(tdf, defaultKey(hs256(AesGcm.generateKey())), AssertionFailureReason.SIGNATURE_INVALID, ID); + assertFails(tdf, defaultKey(rs256(issuer)), AssertionFailureReason.ALGORITHM_MISMATCH, ID); + } + + @Test + void perIdKeysMatchTheExactId() throws Exception { + var tdf = createTdf(assertionConfig(ID, issuerSigningKey())); + // a differently cased entry does not apply, so the payload key is selected + assertFails(tdf, perId("Assertion1", rs256(issuer)), AssertionFailureReason.ALGORITHM_MISMATCH, ID); + assertDecrypts(tdf, perId(ID, rs256(issuer))); + } + + @Test + void theAlgorithmComesFromTheConfiguredKey() throws Exception { + var tdf = createTdf(assertionConfig(ID, issuerSigningKey())); + assertFails(tdf, perId(ID, hs256(AesGcm.generateKey())), AssertionFailureReason.ALGORITHM_MISMATCH, ID); + } + + static Stream invalidConfiguredKeys() { + var rsaPublic = CryptoUtils.generateRSAKeypair().getPublic(); + return Stream.of( + Arguments.of("empty per-ID entry", perId(ID, null)), + Arguments.of("RS256 with a secret", perId(ID, + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, AesGcm.generateKey()))), + Arguments.of("HS256 with public-key material", perId(ID, + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, rsaPublic))), + Arguments.of("HS256 with an empty secret", perId(ID, hs256(new byte[0]))), + Arguments.of("HS256 secret shorter than 256 bits", perId(ID, hs256(new byte[16]))), + Arguments.of("no algorithm", perId(ID, + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.NotDefined, rsaPublic))), + Arguments.of("default key with a key but no algorithm", defaultKey( + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.NotDefined, rsaPublic))), + Arguments.of("default key with an algorithm but no key", defaultKey( + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, null)))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("invalidConfiguredKeys") + void anInvalidConfiguredKeyIsAConfigurationError(String name, Config.AssertionVerificationKeys keys) + throws Exception { + // signed with the payload key, which would verify if the reader fell through to it + var tdf = createTdf(assertionConfig(ID, null)); + assertDecrypts(tdf, null); + assertFails(tdf, keys, AssertionFailureReason.CONFIGURATION_ERROR, ID); + } + + // --------------------------------------------------------- hash as received + + @Test + void unmodelledMembersAreAuthenticated() throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var signed = rewrite(original, manifest -> { + try { + var assertion = wire(ID, "SECRET"); + assertion.add("extensions", JsonParser.parseString("{\"releasableTo\":[\"USA\"]}")); + assertion.getAsJsonObject("statement").addProperty("hash", "part of the statement"); + assertions(manifest).set(0, bind(manifest, assertion, issuer, issuer, Embedded.NONE)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + assertDecrypts(signed, perId(ID, rs256(issuer))); + + var tampered = rewrite(signed, manifest -> assertions(manifest).get(0).getAsJsonObject() + .getAsJsonObject("extensions").getAsJsonArray("releasableTo").add("XXX")); + assertFails(tampered, perId(ID, rs256(issuer)), AssertionFailureReason.BINDING_MISMATCH, ID); + + var nestedTamper = rewrite(signed, manifest -> assertions(manifest).get(0).getAsJsonObject() + .getAsJsonObject("statement").addProperty("hash", "changed")); + assertFails(nestedTamper, perId(ID, rs256(issuer)), AssertionFailureReason.BINDING_MISMATCH, ID); + + var added = rewrite(original, manifest -> + assertions(manifest).get(0).getAsJsonObject().addProperty("note", "added after signing")); + assertFails(added, perId(ID, rs256(issuer)), AssertionFailureReason.BINDING_MISMATCH, ID); + } + + @Test + void anObjectValuedStatementIsHashedAsAnObject() throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var acp240 = AssertionHashTest.acp240Assertion(); + acp240.addProperty("id", ID); + var signed = rewrite(original, manifest -> { + try { + assertions(manifest).set(0, bind(manifest, acp240, issuer, issuer, Embedded.NONE)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + var reader = load(signed, perId(ID, rs256(issuer))); + var read = reader.getManifest().assertions.get(0); + assertThat(AssertionJson.hash(read.received())).isEqualTo(Manifest.Assertion.computeAssertionHash(acp240.toString())); + assertThat(read.received().getAsJsonObject("statement").get("value").isJsonObject()).isTrue(); + + var changed = rewrite(signed, manifest -> assertions(manifest).get(0).getAsJsonObject() + .getAsJsonObject("statement").getAsJsonObject("value").getAsJsonObject("ocl") + .addProperty("cls", "UNCLASSIFIED")); + assertFails(changed, perId(ID, rs256(issuer)), AssertionFailureReason.BINDING_MISMATCH, ID); + } + + // ----------------------------------------------------- ids and binding forms + + @Test + void anInvalidIdFailsVerificationButNotParsing() throws Exception { + var original = createTdf(assertionConfig(ID, null)); + for (JsonElement badId : List.of(new com.google.gson.JsonPrimitive(5), new com.google.gson.JsonPrimitive(""))) { + var edited = rewrite(original, manifest -> assertions(manifest).get(0).getAsJsonObject().add("id", badId)); + assertFails(edited, null, AssertionFailureReason.INVALID_ID, null); + var disabled = new TDF(new FakeServicesBuilder().setKas(KAS).build()).loadTDF( + new SeekableInMemoryByteChannel(edited), + Config.newTDFReaderConfig(Config.WithIgnoreKasAllowlist(true), + Config.withDisableAssertionVerification(true))); + // the typed view never coerces a non-string id + assertThat(disabled.getManifest().assertions.get(0).id).isEqualTo(badId.getAsJsonPrimitive().isString() ? "" : null); + } + var missing = rewrite(original, manifest -> assertions(manifest).get(0).getAsJsonObject().remove("id")); + assertFails(missing, null, AssertionFailureReason.INVALID_ID, null); + } + + @Test + void bindingFormsFollowTheSpec() throws Exception { + var original = createTdf(assertionConfig(ID, issuerSigningKey())); + var keys = perId(ID, rs256(issuer)); + + var upperCase = rewrite(original, manifest -> binding(manifest).addProperty("method", "JWS")); + assertDecrypts(upperCase, keys); + var noMethod = rewrite(original, manifest -> binding(manifest).remove("method")); + assertDecrypts(noMethod, keys); + + assertFails(rewrite(original, manifest -> binding(manifest).addProperty("method", "jwt")), keys, + AssertionFailureReason.UNSUPPORTED_BINDING, ID); + assertFails(rewrite(original, manifest -> { + var binding = binding(manifest); + binding.remove("signature"); + binding.addProperty("bdo", "PGJkbz4="); + }), keys, AssertionFailureReason.UNSUPPORTED_BINDING, ID); + assertFails(rewrite(original, manifest -> binding(manifest).addProperty("bdo", "PGJkbz4=")), keys, + AssertionFailureReason.INVALID_BINDING, ID); + assertFails(rewrite(original, manifest -> binding(manifest).remove("signature")), keys, + AssertionFailureReason.INVALID_BINDING, ID); + assertFails(rewrite(original, manifest -> assertions(manifest).get(0).getAsJsonObject().remove("binding")), + keys, AssertionFailureReason.INVALID_BINDING, ID); + assertFails(rewrite(original, manifest -> assertions(manifest).get(0).getAsJsonObject() + .addProperty("binding", "a.b.c")), keys, AssertionFailureReason.INVALID_BINDING, ID); + assertFails(rewrite(original, manifest -> binding(manifest).addProperty("signature", "a.b")), keys, + AssertionFailureReason.INVALID_BINDING, ID); + } + + private static JsonObject binding(JsonObject manifest) { + return assertions(manifest).get(0).getAsJsonObject().getAsJsonObject("binding"); + } + + @Test + void verifyingLeavesTheParsedManifestUntouched() throws Exception { + var tdf = createTdf(assertionConfig(ID, issuerSigningKey())); + var reader = load(tdf, perId(ID, rs256(issuer))); + var assertion = reader.getManifest().assertions.get(0); + assertThat(assertion.binding).isNotNull(); + assertThat(assertion.binding.method).isEqualTo("jws"); + assertThat(assertion.binding.signature).isNotEmpty(); + } + + @Test + void verifyChecksTheBindingWithExactlyTheGivenKey() throws Exception { + var tdf = createTdf(assertionConfig(ID, issuerSigningKey().withJwk( + new RSAKey.Builder((RSAPublicKey) issuer.getPublic()).build()))); + var assertion = SDK.readManifest(new SeekableInMemoryByteChannel(tdf)).assertions.get(0); + + var claims = assertion.verify(rs256(issuer)); + assertThat(claims.getAssertionHash()).isEqualTo(assertion.hash()); + + assertThatThrownBy(() -> assertion.verify(rs256(unrelated))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.SIGNER_NOT_PERMITTED)); + assertThatThrownBy(() -> assertion.verify(null)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.CONFIGURATION_ERROR)); + // and has no side effects + assertThat(assertion.binding).isNotNull(); + + // verify() and hash() read the same form: after a typed edit, the signed claims no + // longer match the hash, and a changed binding is the binding checked + assertion.statement.value = "changed"; + assertThat(assertion.verify(rs256(issuer)).getAssertionHash()).isNotEqualTo(assertion.hash()); + assertion.binding.signature = "not.a.jws.binding"; + assertThatThrownBy(() -> assertion.verify(rs256(issuer))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_BINDING)); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVerifierTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVerifierTest.java new file mode 100644 index 00000000..725cfdd0 --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AssertionVerifierTest.java @@ -0,0 +1,321 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.codec.binary.Hex; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Arrays; +import java.util.Base64; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Section 8 steps 3.3 to 3.9 on hand-built JWS bindings, for the cases the shared vectors do not + * cover yet: duplicate JOSE members, signature encoding, embedded keys of other key types, and + * claims other than the two the binding uses. + */ +class AssertionVerifierTest { + + private static final byte[] AGGREGATE = "segment hashes".getBytes(StandardCharsets.US_ASCII); + private static KeyPair signer; + private static KeyPair ecKey; + private static AssertionVerifier.SelectedKey selected; + + @BeforeAll + static void keys() { + signer = CryptoUtils.generateRSAKeypair(); + ecKey = CryptoUtils.generateECKeypair(KeyType.EC256Key.getECCurve().getCurveName()); + selected = AssertionVerifier.SelectedKey.rsa((RSAPublicKey) signer.getPublic()); + } + + private static String b64url(byte[] bytes) { + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } + + private static String b64url(String text) { + return b64url(text.getBytes(StandardCharsets.UTF_8)); + } + + private static JsonObject assertion() { + return JsonParser.parseString("{\"id\":\"a\",\"type\":\"handling\",\"scope\":\"payload\"," + + "\"statement\":{\"format\":\"string\",\"value\":\"SECRET\"}}").getAsJsonObject(); + } + + /** The claims a writer signs for {@link #assertion()}, plus {@code extra} members. */ + private static String claims(String extra) throws Exception { + String hash = Manifest.Assertion.computeAssertionHash(assertion().toString()); + byte[] hashBytes = Hex.decodeHex(hash); + byte[] bound = Arrays.copyOf(AGGREGATE, AGGREGATE.length + hashBytes.length); + System.arraycopy(hashBytes, 0, bound, AGGREGATE.length, hashBytes.length); + return "{\"assertionHash\":\"" + hash + "\",\"assertionSig\":\"" + Base64.getEncoder().encodeToString(bound) + + "\"" + extra + "}"; + } + + private static String sign(String headerJson, String claimsJson, PrivateKey key) throws Exception { + String signingInput = b64url(headerJson) + "." + b64url(claimsJson); + var signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(key); + signature.update(signingInput.getBytes(StandardCharsets.US_ASCII)); + return signingInput + "." + b64url(signature.sign()); + } + + private static JsonObject bound(String compactJws) { + var bound = assertion(); + var binding = new JsonObject(); + binding.addProperty("method", "jws"); + binding.addProperty("signature", compactJws); + bound.add("binding", binding); + return bound; + } + + private static void verify(String compactJws) { + AssertionVerifier.verifyWithKey(bound(compactJws), "a", selected, AGGREGATE, false); + } + + private static void assertReason(String compactJws, AssertionFailureReason reason) { + assertThatThrownBy(() -> verify(compactJws)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> { + assertThat(e.getReason()).isEqualTo(reason); + assertThat(e.getAssertionId()).isEqualTo("a"); + }); + } + + private static String headerWith(String members) { + return "{\"alg\":\"RS256\"" + members + "}"; + } + + @Test + void aWellFormedBindingVerifies() throws Exception { + assertThatCode(() -> verify(sign(headerWith(""), claims(""), signer.getPrivate()))).doesNotThrowAnyException(); + } + + @Test + void duplicateJoseMembersAreInvalidBindings() throws Exception { + assertReason(sign("{\"alg\":\"RS256\",\"alg\":\"RS256\"}", claims(""), signer.getPrivate()), + AssertionFailureReason.INVALID_BINDING); + assertReason(sign("{\"alg\":\"RS256\",\"\\u0061lg\":\"HS256\"}", claims(""), signer.getPrivate()), + AssertionFailureReason.INVALID_BINDING); + String claims = claims(""); + String duplicated = claims.substring(0, claims.length() - 1) + ",\"assertionHash\":\"0\"}"; + assertReason(sign(headerWith(""), duplicated, signer.getPrivate()), AssertionFailureReason.INVALID_BINDING); + } + + @Test + void theSignatureSegmentMustBeUnpaddedBase64Url() throws Exception { + String jws = sign(headerWith(""), claims(""), signer.getPrivate()); + String signature = jws.substring(jws.lastIndexOf('.') + 1); + String signingInput = jws.substring(0, jws.lastIndexOf('.') + 1); + byte[] raw = Base64.getUrlDecoder().decode(signature); + assertReason(signingInput + Base64.getUrlEncoder().encodeToString(raw) + (raw.length % 3 == 0 ? "=" : ""), + AssertionFailureReason.SIGNATURE_INVALID); + assertReason(signingInput + Base64.getEncoder().withoutPadding().encodeToString(raw).replace('-', '+') + "+", + AssertionFailureReason.SIGNATURE_INVALID); + assertReason(signingInput, AssertionFailureReason.SIGNATURE_INVALID); + } + + @ParameterizedTest + @ValueSource(strings = { + ",\"exp\":1", + ",\"nbf\":99999999999", + ",\"iat\":99999999999", + ",\"aud\":\"someone else\",\"iss\":\"anyone\",\"sub\":5" + }) + void claimsOtherThanTheBindingsAreIgnored(String extra) throws Exception { + assertThatCode(() -> verify(sign(headerWith(""), claims(extra), signer.getPrivate()))).doesNotThrowAnyException(); + } + + @Test + void headerMembersOtherThanTheKeysDoNotMatter() throws Exception { + assertThatCode(() -> verify(sign(headerWith(",\"kid\":5,\"typ\":{},\"x5u\":\"https://example.com/x\"," + + "\"jku\":\"https://example.com/k\""), claims(""), signer.getPrivate()))).doesNotThrowAnyException(); + } + + private static String ecJwk(ECPublicKey key, String crv) { + return "{\"kty\":\"EC\",\"crv\":\"" + crv + "\",\"x\":\"" + b64url(coordinate(key.getW().getAffineX())) + + "\",\"y\":\"" + b64url(coordinate(key.getW().getAffineY())) + "\"}"; + } + + private static byte[] coordinate(BigInteger value) { + byte[] bytes = value.toByteArray(); + byte[] fixed = new byte[32]; + int copy = Math.min(bytes.length, 32); + System.arraycopy(bytes, bytes.length - copy, fixed, 32 - copy, copy); + return fixed; + } + + private static String ecCertificate() throws Exception { + var owner = new X500Name("CN=EC"); + var builder = new JcaX509v3CertificateBuilder(owner, BigInteger.ONE, + new Date(System.currentTimeMillis() - 86_400_000L), new Date(System.currentTimeMillis() + 86_400_000L), + owner, ecKey.getPublic()); + var certificate = new JcaX509CertificateConverter() + .getCertificate(builder.build(new JcaContentSignerBuilder("SHA256withECDSA").build(ecKey.getPrivate()))); + return Base64.getEncoder().encodeToString(certificate.getEncoded()); + } + + @Test + void aWellFormedKeyOfAnotherTypeIsNotPermitted() throws Exception { + var ec = (ECPublicKey) ecKey.getPublic(); + assertReason(sign(headerWith(",\"jwk\":" + ecJwk(ec, "P-256")), claims(""), signer.getPrivate()), + AssertionFailureReason.SIGNER_NOT_PERMITTED); + assertReason(sign(headerWith(",\"x5c\":[\"" + ecCertificate() + "\"]"), claims(""), signer.getPrivate()), + AssertionFailureReason.SIGNER_NOT_PERMITTED); + // a jwk and an x5c of the same EC key agree with each other + assertReason(sign(headerWith(",\"jwk\":" + ecJwk(ec, "P-256") + ",\"x5c\":[\"" + ecCertificate() + "\"]"), + claims(""), signer.getPrivate()), AssertionFailureReason.SIGNER_NOT_PERMITTED); + String okp = "{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"" + b64url(new byte[32]) + "\"}"; + assertReason(sign(headerWith(",\"jwk\":" + okp), claims(""), signer.getPrivate()), + AssertionFailureReason.SIGNER_NOT_PERMITTED); + } + + @Test + void anEcJwkThatDisagreesWithTheX5cIsContradictory() throws Exception { + var other = (ECPublicKey) CryptoUtils.generateECKeypair(KeyType.EC256Key.getECCurve().getCurveName()).getPublic(); + assertReason(sign(headerWith(",\"jwk\":" + ecJwk(other, "P-256") + ",\"x5c\":[\"" + ecCertificate() + "\"]"), + claims(""), signer.getPrivate()), AssertionFailureReason.INVALID_EMBEDDED_KEY); + } + + @Test + void aJwkMalformedForItsKeyTypeIsInvalid() throws Exception { + var ec = (ECPublicKey) ecKey.getPublic(); + String x = b64url(coordinate(ec.getW().getAffineX())); + String y = b64url(coordinate(ec.getW().getAffineY())); + String offCurveY = b64url(coordinate(ec.getW().getAffineY().add(BigInteger.ONE))); + for (String jwk : new String[] { + "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"" + x + "\"}", + "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"" + x + "\",\"y\":\"" + offCurveY + "\"}", + "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"" + x + "\",\"y\":\"" + y + "=\"}", + "{\"kty\":\"EC\",\"crv\":\"P-384\",\"x\":\"" + x + "\",\"y\":\"" + y + "\"}", + "{\"kty\":\"EC\",\"crv\":\"P-257\",\"x\":\"" + x + "\",\"y\":\"" + y + "\"}", + "{\"kty\":\"EC\",\"x\":\"" + x + "\",\"y\":\"" + y + "\"}", + "{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"" + b64url(new byte[31]) + "\"}", + "{\"kty\":\"OKP\",\"crv\":\"Ed25519\"}", + "{\"kty\":\"OKP\",\"crv\":\"P-256\",\"x\":\"" + b64url(new byte[32]) + "\"}", + "{\"kty\":\"OKP\",\"crv\":\"X25519\",\"x\":\"" + b64url(new byte[32]) + "\"}", + "{\"kty\":\"XYZ\",\"x\":\"" + x + "\"}", + "{\"kty\":\"RSA\",\"n\":\"" + b64url(new byte[256]).substring(1) + "+\",\"e\":\"AQAB\"}", + "{\"kty\":\"RSA\",\"n\":\"\",\"e\":\"AQAB\"}", + "{\"kty\":5}", + "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"" + x + "\",\"y\":\"" + y + "\",\"d\":\"" + x + "\"}", + }) { + assertThat(jwk).as("fixture is JSON").satisfies(json -> JsonParser.parseString(json)); + assertThatThrownBy(() -> verify(sign(headerWith(",\"jwk\":" + jwk), claims(""), signer.getPrivate()))) + .as(jwk) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_EMBEDDED_KEY)); + } + } + + private static String rsaJwk(byte[] n, byte[] e) { + return "{\"kty\":\"RSA\",\"n\":\"" + b64url(n) + "\",\"e\":\"" + b64url(e) + "\"}"; + } + + private static byte[] unsigned(BigInteger value) { + byte[] bytes = value.toByteArray(); + return bytes[0] == 0 ? Arrays.copyOfRange(bytes, 1, bytes.length) : bytes; + } + + private static byte[] withLeadingZero(byte[] bytes) { + byte[] padded = new byte[bytes.length + 1]; + System.arraycopy(bytes, 0, padded, 1, bytes.length); + return padded; + } + + @Test + void theSelectedKeysJwkMustUseMinimalIntegers() throws Exception { + var rsa = (RSAPublicKey) signer.getPublic(); + byte[] n = unsigned(rsa.getModulus()); + byte[] e = unsigned(rsa.getPublicExponent()); + String minimal = rsaJwk(n, e); + assertThatCode(() -> verify(sign(headerWith(",\"jwk\":" + minimal + ",\"kid\":\"anything\""), claims(""), + signer.getPrivate()))).doesNotThrowAnyException(); + // the same key with a leading zero octet is malformed, never equal to the selected key + assertReason(sign(headerWith(",\"jwk\":" + rsaJwk(withLeadingZero(n), e)), claims(""), signer.getPrivate()), + AssertionFailureReason.INVALID_EMBEDDED_KEY); + assertReason(sign(headerWith(",\"jwk\":" + rsaJwk(n, withLeadingZero(e))), claims(""), signer.getPrivate()), + AssertionFailureReason.INVALID_EMBEDDED_KEY); + } + + /** The same bytes, encoded with a non-zero bit after the last whole byte. */ + private static String withTrailingBits(String base64Url) { + assertThat(base64Url.length() % 4).as("an encoding with spare bits").isNotZero(); + String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + char last = base64Url.charAt(base64Url.length() - 1); + String changed = base64Url.substring(0, base64Url.length() - 1) + alphabet.charAt(alphabet.indexOf(last) | 1); + assertThat(Base64.getUrlDecoder().decode(changed)).isEqualTo(Base64.getUrlDecoder().decode(base64Url)); + return changed; + } + + @Test + void base64UrlWithNonZeroTrailingBitsIsRejected() throws Exception { + // a header whose encoding has spare bits + String header = headerWith(",\"x\":12"); + String jws = sign(header, claims(""), signer.getPrivate()); + String[] parts = jws.split("\\."); + assertThatCode(() -> verify(jws)).doesNotThrowAnyException(); + assertReason(withTrailingBits(parts[0]) + "." + parts[1] + "." + parts[2], AssertionFailureReason.INVALID_BINDING); + assertReason(parts[0] + "." + parts[1] + "." + withTrailingBits(parts[2]), AssertionFailureReason.SIGNATURE_INVALID); + + var rsa = (RSAPublicKey) signer.getPublic(); + String n = b64url(unsigned(rsa.getModulus())); + String jwk = "{\"kty\":\"RSA\",\"n\":\"" + withTrailingBits(n) + "\",\"e\":\"AQAB\"}"; + assertReason(sign(headerWith(",\"jwk\":" + jwk), claims(""), signer.getPrivate()), + AssertionFailureReason.INVALID_EMBEDDED_KEY); + } + + @Test + void x5cMustBeExactlyOneDerCertificateInStrictBase64() throws Exception { + byte[] der = TestUtil.createTestCertificate(signer.getPublic(), signer.getPrivate()).getEncoded(); + String leaf = Base64.getEncoder().encodeToString(der); + assertThatCode(() -> verify(sign(headerWith(",\"x5c\":[\"" + leaf + "\"]"), claims(""), signer.getPrivate()))) + .doesNotThrowAnyException(); + // later elements must decode, but are never parsed + String notACertificate = Base64.getEncoder().encodeToString("not a certificate".getBytes(StandardCharsets.US_ASCII)); + assertThatCode(() -> verify(sign(headerWith(",\"x5c\":[\"" + leaf + "\",\"" + notACertificate + "\"]"), + claims(""), signer.getPrivate()))).doesNotThrowAnyException(); + + String pem = "-----BEGIN CERTIFICATE-----\n" + Base64.getMimeEncoder().encodeToString(der) + + "\n-----END CERTIFICATE-----\n"; + byte[] trailing = Arrays.copyOf(der, der.length + 2); + for (String x5c : new String[] { + "[\"" + Base64.getEncoder().encodeToString(pem.getBytes(StandardCharsets.US_ASCII)) + "\"]", + "[\"" + Base64.getEncoder().encodeToString(trailing) + "\"]", + "[\"" + leaf + "\",\"not base64!\"]", + "[\"" + leaf + "\",5]", + "[\"" + leaf.replace("=", "") + "\"]", + "[\"" + Base64.getUrlEncoder().encodeToString(der) + "\"]", + }) { + assertThatThrownBy(() -> verify(sign(headerWith(",\"x5c\":" + x5c), claims(""), signer.getPrivate()))) + .as(x5c.length() > 80 ? x5c.substring(0, 80) : x5c) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_EMBEDDED_KEY)); + } + } + + @Test + void pemTextIsNeverAnHs256Secret() { + byte[] pem = CryptoUtils.getPublicKeyPEM(signer.getPublic()).getBytes(StandardCharsets.US_ASCII); + assertThatThrownBy(() -> AssertionVerifier.configuredKey( + new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.HS256, pem), "a", "the key")) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.CONFIGURATION_ERROR)); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/StrictManifestParsingTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/StrictManifestParsingTest.java new file mode 100644 index 00000000..f17198d1 --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/StrictManifestParsingTest.java @@ -0,0 +1,327 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * The manifest reader applies the JSON profile of spec Section 3 to the whole manifest, before + * any typed field or JSON tree can drop one of two duplicates, and whether or not assertions are + * verified later. + */ +class StrictManifestParsingTest { + + private static String json(String singleQuoted) { + return singleQuoted.replace('\'', '"'); + } + + /** A minimal valid manifest with {@code rootExtra} (which must start with a comma) spliced in. */ + static String manifest(String rootExtra) { + return json("{'encryptionInformation':{'integrityInformation':{'encryptedSegmentSizeDefault':1048604," + + "'segmentSizeDefault':1048576,'rootSignature':{'alg':'HS256','sig':'c2ln'},'segmentHashAlg':'GMAC'," + + "'segments':[{'hash':'aGFzaDA=','segmentSize':4,'encryptedSegmentSize':32}]}," + + "'keyAccess':[{'protocol':'kas','type':'wrapped','url':'http://localhost:65432/kas','wrappedKey':'a2V5'}]," + + "'method':{'algorithm':'AES-256-GCM','isStreamable':true,'iv':'aXY='},'policy':'cG9saWN5','type':'split'}," + + "'payload':{'isEncrypted':true,'protocol':'zip','type':'reference','url':'0.payload'}" + + rootExtra + "}"); + } + + static String withAssertions(String assertions) { + return manifest(",'assertions':" + assertions); + } + + static Stream rejectedManifests() { + return Stream.of( + // duplicates anywhere, including outside the assertions, and escaped spellings + Arguments.of(manifest(",'payload':{}"), AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of(manifest(",'\\u0070ayload':{}"), AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of(manifest(",'extra':{'a':[{'b':1,'b':2}]}"), AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of(withAssertions("[]").replace("\"url\":\"0.payload\"", "\"url\":\"0.payload\",\"url\":\"x\""), + AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of(manifest(",'assertions':[],'assertions':[]"), AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of(withAssertions("[{'id':'a','id':'b'}]"), AssertionFailureReason.DUPLICATE_MEMBER, null), + Arguments.of(withAssertions("[{'id':'a','statement':{'format':'x','format':'y'}}]"), + AssertionFailureReason.DUPLICATE_MEMBER, "a"), + // case variants of modelled names, even with the exact name absent + Arguments.of(manifest(",'assertions':[],'Assertions':[]"), AssertionFailureReason.AMBIGUOUS_MEMBER, null), + Arguments.of(manifest(",'ASSERTIONS':[]"), AssertionFailureReason.AMBIGUOUS_MEMBER, null), + Arguments.of(withAssertions("[{'id':'classification','ID':'system-metadata'}]"), + AssertionFailureReason.AMBIGUOUS_MEMBER, "classification"), + Arguments.of(withAssertions("[{'ID':'system-metadata'}]"), AssertionFailureReason.AMBIGUOUS_MEMBER, null), + Arguments.of(withAssertions("[{'id':'a','binding':{'Signature':'x'}}]"), + AssertionFailureReason.AMBIGUOUS_MEMBER, "a"), + // non-I-JSON content inside an assertion + Arguments.of(withAssertions("[{'id':'a','statement':{'value':'\\uDEAD'}}]"), + AssertionFailureReason.INVALID_JSON, "a"), + Arguments.of(withAssertions("[{'id':'a','n':1e400}]"), AssertionFailureReason.INVALID_JSON, "a"), + // later assertions are checked too, and report their own id + Arguments.of(withAssertions("[{'id':'a'},{'id':'b','Id':'c'}]"), AssertionFailureReason.AMBIGUOUS_MEMBER, "b")); + } + + @ParameterizedTest + @MethodSource("rejectedManifests") + void rejects(String manifest, AssertionFailureReason reason, String id) { + assertThatThrownBy(() -> Manifest.readManifest(manifest)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> { + assertThat(e.getReason()).isEqualTo(reason); + assertThat(e.getAssertionId()).isEqualTo(id); + assertThat(e).hasMessageContaining("reason=" + reason.code()); + }); + // the manifest-only check used by the shared vectors agrees + assertThatThrownBy(() -> Manifest.checkManifestJson(manifest)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(reason)); + } + + @ParameterizedTest + @ValueSource(strings = { + "", + // the I-JSON checks are required only inside assertions + ",'extra':{'n':1e400,'s':'\\uDEAD'}", + // case variants are only checked at the four modelled levels + ",'payload2':{'Assertions':1},'assertions':[{'id':'a','statement':{'value':{'ID':1}},'extensions':{'Binding':1}}]", + // values the typed model coerces or ignores still parse + ",'assertions':[{'id':5,'type':{},'binding':'not an object'}]" + }) + void accepts(String rootExtra) { + var manifest = manifest(rootExtra); + assertThatCode(() -> Manifest.readManifest(manifest)).doesNotThrowAnyException(); + assertThatCode(() -> Manifest.checkManifestJson(manifest)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @ValueSource(strings = {",'a':NaN", ",'a':'x'//", ",'a':'x',", ",a:1", ",'a':'\\x'", "} {"}) + void readsOnlyStrictJson(String rootExtra) { + var manifest = manifest(rootExtra); + assertThatThrownBy(() -> Manifest.readManifest(manifest)).isInstanceOf(com.google.gson.JsonParseException.class); + } + + @Test + void reportsInvalidUtf8AsInvalidJson() throws IOException { + var valid = withAssertions("[{'id':'a','statement':{'value':'XY'}}]").getBytes(StandardCharsets.UTF_8); + var invalid = valid.clone(); + int at = new String(valid, StandardCharsets.UTF_8).indexOf("XY"); + invalid[at] = (byte) 0xC3; // a lead byte followed by an ASCII byte is malformed + assertThatCode(() -> SDK.readManifest(new SeekableInMemoryByteChannel(tdfWithManifest(valid)))) + .doesNotThrowAnyException(); + assertThatThrownBy(() -> SDK.readManifest(new SeekableInMemoryByteChannel(tdfWithManifest(invalid)))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON)); + } + + @Test + void reportsInvalidUtf8OutsideTheAssertionsAsInvalidJsonToo() throws IOException { + var valid = manifest(",'extra':'XY'").getBytes(StandardCharsets.UTF_8); + var invalid = valid.clone(); + invalid[new String(valid, StandardCharsets.UTF_8).indexOf("XY")] = (byte) 0xFF; + assertThatThrownBy(() -> SDK.readManifest(new SeekableInMemoryByteChannel(tdfWithManifest(invalid)))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> { + assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON); + assertThat(e).hasMessageContaining("reason=invalid-json"); + }); + } + + static byte[] tdfWithManifest(byte[] manifest) throws IOException { + var out = new ByteArrayOutputStream(); + var writer = new TDFWriter(out); + try (var payload = writer.payload()) { + payload.write(new byte[32]); + } + try (var manifestOut = writer.manifest()) { + manifestOut.write(manifest); + } + writer.finish(); + return out.toByteArray(); + } + + @Test + void keepsEachAssertionAsReceived() { + var assertion = "{'id':'a','type':'handling','scope':'payload','appliesToState':null," + + "'statement':{'format':'json-structured','schema':'s','value':{'n':1.50,'ocl':{'cls':'SECRET'}},'extra':[1]}," + + "'extensions':{'note':'coi'},'binding':{'method':'JWS','signature':'a.b.c'}}"; + var manifest = Manifest.readManifest(withAssertions("[" + assertion + "]")); + var read = manifest.assertions.get(0); + assertThat(read.id).isEqualTo("a"); + assertThat(read.statement.value).isEqualTo("{\"n\":1.50,\"ocl\":{\"cls\":\"SECRET\"}}"); + assertThat(read.binding.method).isEqualTo("JWS"); + + String expectedHash = Manifest.Assertion.computeAssertionHash(json(assertion)); + assertThat(AssertionJson.hash(read.received())).isEqualTo(expectedHash); + + // serializing writes the assertion back as received, unmodelled members and nulls included + var written = JsonParser.parseString(Manifest.toJson(manifest)).getAsJsonObject() + .getAsJsonArray("assertions").get(0); + assertThat(written).isEqualTo(JsonParser.parseString(json(assertion))); + assertThat(Manifest.Assertion.computeAssertionHash(written.toString())).isEqualTo(expectedHash); + + // and so does the plain Gson the TDF writer uses + var plain = JsonParser.parseString(new com.google.gson.Gson().toJson(manifest)).getAsJsonObject() + .getAsJsonArray("assertions").get(0); + assertThat(plain).isEqualTo(JsonParser.parseString(json(assertion))); + } + + @Test + void aChangedTypedFieldReplacesOnlyItsMember() throws IOException { + var assertion = "{'id':'a','statement':{'format':'f','value':{'x':1},'extra':true},'extensions':{'note':'coi'}}"; + var manifest = Manifest.readManifest(withAssertions("[" + assertion + "]")); + var read = manifest.assertions.get(0); + read.statement.format = "g"; + read.scope = "tdo"; + + JsonObject written = JsonParser.parseString(Manifest.toJson(manifest)).getAsJsonObject() + .getAsJsonArray("assertions").get(0).getAsJsonObject(); + assertThat(written).isEqualTo(JsonParser.parseString( + json("{'id':'a','statement':{'format':'g','value':{'x':1},'extra':true},'extensions':{'note':'coi'},'scope':'tdo'}"))); + assertThat(read.hash()).isEqualTo(Manifest.Assertion.computeAssertionHash(written.toString())); + // the received object itself is never modified + assertThat(AssertionJson.hash(read.received())).isEqualTo(Manifest.Assertion.computeAssertionHash(json(assertion))); + } + + @Test + void anAssertionBuiltInCodeSerializesItsNonNullFields() throws IOException { + var assertion = new Manifest.Assertion(); + assertion.id = "a"; + assertion.type = "handling"; + assertion.statement = new AssertionConfig.Statement(); + assertion.statement.value = "{\"x\":1}"; + assertion.binding = new Manifest.Binding(); + assertion.binding.method = "jws"; + assertion.binding.signature = "a.b.c"; + + var written = JsonParser.parseString(new com.google.gson.Gson().toJson(assertion)); + assertThat(written).isEqualTo(JsonParser.parseString( + json("{'id':'a','type':'handling','statement':{'value':'{\\'x\\':1}'},'binding':{'method':'jws','signature':'a.b.c'}}"))); + assertThat(assertion.hash()).isEqualTo(Manifest.Assertion.computeAssertionHash(written.toString())); + } + + @Test + void nullAssertionsAreMalformed() { + assertThatThrownBy(() -> Manifest.readManifest(withAssertions("[null]"))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void rejectsAByteOrderMark() throws IOException { + var withBom = ("\uFEFF" + manifest("")).getBytes(StandardCharsets.UTF_8); + assertThatThrownBy(() -> SDK.readManifest(new SeekableInMemoryByteChannel(tdfWithManifest(withBom)))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON)); + assertThatThrownBy(() -> Manifest.checkManifestJson("\uFEFF" + manifest(""))) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON)); + assertThatThrownBy(() -> Manifest.Assertion.computeAssertionHash("\uFEFF{\"id\":\"a\"}")) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON)); + // a byte order mark is ordinary content anywhere else + assertThatCode(() -> Manifest.readManifest(manifest(",'extra':'\uFEFF'"))).doesNotThrowAnyException(); + } + + static Stream wholeManifestCaseVariants() { + String twoSegments = "[{\"hash\":\"aGFzaDA=\",\"segmentSize\":4,\"encryptedSegmentSize\":32}," + + "{\"Hash\":\"aGFzaDE=\",\"segmentSize\":4,\"encryptedSegmentSize\":32}]"; + String manifest = manifest(""); + return Stream.of( + // rule 1: a variant of a listed name, with the listed member absent + Arguments.of("Payload at the root", manifest.replace("\"payload\":", "\"Payload\":")), + Arguments.of("URL in a key access object", manifest.replace("\"url\":\"http", "\"URL\":\"http")), + Arguments.of("SchemaVersion at the root", manifest(",'SchemaVersion':'4.3.0'")), + Arguments.of("long s in segmentHashAlg", manifest.replace("\"segmentHashAlg\"", "\"\u017fegmentHashAlg\"")), + Arguments.of("Hash in the second segment", manifest.replace( + "[{\"hash\":\"aGFzaDA=\",\"segmentSize\":4,\"encryptedSegmentSize\":32}]", twoSegments)), + Arguments.of("ALG in a policy binding", manifest.replace("\"wrappedKey\":\"a2V5\"", + "\"wrappedKey\":\"a2V5\",\"policyBinding\":{\"ALG\":\"HS256\",\"hash\":\"aA==\"}")), + Arguments.of("IV in the method", manifest.replace("\"iv\":", "\"IV\":")), + Arguments.of("Sig in the root signature", manifest.replace("\"sig\":", "\"Sig\":")), + Arguments.of("MimeType in the payload", manifest.replace("\"url\":\"0.payload\"", + "\"url\":\"0.payload\",\"MimeType\":\"text/plain\"")), + Arguments.of("Policy in encryptionInformation", manifest.replace("\"policy\":", "\"Policy\":")), + Arguments.of("KID in a key access object", manifest.replace("\"wrappedKey\"", "\"KID\":\"k\",\"wrappedKey\"")), + Arguments.of("tdf_spec_Version in the payload", manifest.replace("\"url\":\"0.payload\"", + "\"url\":\"0.payload\",\"tdf_spec_Version\":\"4.3.0\"")), + // rule 2: two names that are case variants of each other, neither listed + Arguments.of("foo and Foo at the root", manifest(",'foo':1,'Foo':2")), + Arguments.of("Extra and extra in a segment", manifest.replace("\"segmentSize\":4", + "\"segmentSize\":4,\"Extra\":1,\"extra\":2")), + Arguments.of("IsStreamable with isStreamable", + manifest.replace("\"isStreamable\":true", "\"isStreamable\":true,\"IsStreamable\":true"))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("wholeManifestCaseVariants") + void rejectsCaseVariantsAtEveryModelledLevel(String name, String manifest) { + assertThatThrownBy(() -> Manifest.readManifest(manifest)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.AMBIGUOUS_MEMBER)); + assertThatThrownBy(() -> Manifest.checkManifestJson(manifest)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, + e -> assertThat(e.getReason()).isEqualTo(AssertionFailureReason.AMBIGUOUS_MEMBER)); + } + + static Stream caseVariantsOutsideTheModelledLevels() { + String manifest = manifest(""); + return Stream.of( + // the one legacy exception to rule 1 + Arguments.of("IsStreamable alone", + manifest.replace("\"isStreamable\":true", "\"IsStreamable\":true")), + Arguments.of("case pairs inside statement.value", + withAssertions("[{'id':'a','statement':{'value':{'a':1,'A':2,'ID':3,'id':4}}}]")), + Arguments.of("case pairs inside an unlisted member", manifest(",'extra':{'foo':1,'Foo':2,'Payload':3}")), + Arguments.of("case pairs inside an unlisted member of a key access object", + manifest.replace("\"wrappedKey\"", "\"extra\":{\"a\":1,\"A\":2},\"wrappedKey\"")), + Arguments.of("a dotless i is not a variant of id", withAssertions("[{'id':'a','\u0131d':'x'}]")), + Arguments.of("a different length is not a variant", manifest(",'payloads':1,'Payloads2':2"))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("caseVariantsOutsideTheModelledLevels") + void acceptsCaseVariantsOutsideTheModelledLevels(String name, String manifest) { + assertThatCode(() -> Manifest.readManifest(manifest)).doesNotThrowAnyException(); + assertThatCode(() -> Manifest.checkManifestJson(manifest)).doesNotThrowAnyException(); + } + + @Test + void readsTheJava075GoldenTdfWithIsStreamable() throws IOException { + // the xtest golden file, when this checkout sits next to opentdf/tests + var golden = java.nio.file.Path.of("..", "..", "tests", "xtest", "golden", + "xstext-java-v0.7.5-94b161d53-DSP2.0.2_and_2.0.3.tdf"); + org.junit.jupiter.api.Assumptions.assumeTrue(java.nio.file.Files.isRegularFile(golden), + "golden TDF not available at " + golden.toAbsolutePath()); + try (var channel = java.nio.channels.FileChannel.open(golden)) { + var manifest = SDK.readManifest(channel); + assertThat(manifest.encryptionInformation.method.algorithm).isNotNull(); + } + try (var reader = TDFReader.open(java.nio.channels.FileChannel.open(golden)).manifest()) { + var text = new java.io.StringWriter(); + reader.transferTo(text); + assertThat(text.toString()).contains("\"IsStreamable\""); + } + } + + @Test + void longSegmentListsStayLinear() { + var segments = new StringBuilder("["); + for (int i = 0; i < 200_000; i++) { + segments.append(i == 0 ? "" : ",").append("{\"hash\":\"aGFzaDA=\",\"segmentSize\":4,\"encryptedSegmentSize\":32}"); + } + String manifest = manifest("").replace( + "[{\"hash\":\"aGFzaDA=\",\"segmentSize\":4,\"encryptedSegmentSize\":32}]", segments.append("]")); + long start = System.nanoTime(); + assertThat(Manifest.readManifest(manifest).encryptionInformation.integrityInformation.segments).hasSize(200_000); + // generous: a quadratic check would take minutes here + assertThat(java.time.Duration.ofNanos(System.nanoTime() - start)).isLessThan(java.time.Duration.ofSeconds(20)); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFKeyAccessTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFKeyAccessTest.java new file mode 100644 index 00000000..a83b302a --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFKeyAccessTest.java @@ -0,0 +1,165 @@ +package io.opentdf.platform.sdk; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.Base64; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * The payload key is built only from key access objects. A manifest without any is refused as + * malformed when the TDF is loaded, before any KAS, integrity or assertion step. + */ +class TDFKeyAccessTest { + + private static final String KAS_URL = "https://example.com/kas0"; + private static final byte[] PLAINTEXT = "key access objects".getBytes(StandardCharsets.UTF_8); + private static final byte[] ZERO_KEY = new byte[32]; + + private static KeyPair kasKeyPair; + private static final AtomicInteger unwraps = new AtomicInteger(); + + private static final SDK.KAS KAS = new SDK.KAS() { + @Override + public void close() { + // nothing to release + } + + @Override + public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { + var resolved = new Config.KASInfo(); + resolved.URL = kasInfo.URL; + resolved.KID = "r1"; + resolved.PublicKey = CryptoUtils.getPublicKeyPEM(kasKeyPair.getPublic()); + return resolved; + } + + @Override + public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessionKeyType) { + unwraps.incrementAndGet(); + return new AsymDecryption(kasKeyPair.getPrivate()).decrypt(Base64.getDecoder().decode(keyAccess.wrappedKey)); + } + + @Override + public KASKeyCache getKeyCache() { + return new KASKeyCache(); + } + }; + + @BeforeAll + static void generateKasKey() { + kasKeyPair = CryptoUtils.generateRSAKeypair(); + } + + private static TDF tdf() { + return new TDF(new FakeServicesBuilder().setKas(KAS).build()); + } + + private static byte[] createTdf() throws IOException { + var kasInfo = new Config.KASInfo(); + kasInfo.URL = KAS_URL; + var out = new ByteArrayOutputStream(); + tdf().createTDF(new ByteArrayInputStream(PLAINTEXT), out, + Config.newTDFConfig(Config.withAutoconfigure(false), Config.withKasInformation(kasInfo))); + return out.toByteArray(); + } + + private static TDF.Reader load(byte[] tdfBytes) throws IOException { + return tdf().loadTDF(new SeekableInMemoryByteChannel(tdfBytes), + Config.newTDFReaderConfig(Config.WithIgnoreKasAllowlist(true))); + } + + private static byte[] entry(byte[] tdfBytes, String name) throws IOException { + for (var entry : new ZipReader(new SeekableInMemoryByteChannel(tdfBytes)).getEntries()) { + if (entry.getName().equals(name)) { + try (InputStream data = entry.getData()) { + return data.readAllBytes(); + } + } + } + throw new IllegalArgumentException("no entry named " + name); + } + + private static byte[] zip(JsonObject manifest, byte[] payload) throws IOException { + var out = new ByteArrayOutputStream(); + var writer = new TDFWriter(out); + try (var payloadOut = writer.payload()) { + payloadOut.write(payload); + } + try (var manifestOut = writer.manifest()) { + manifestOut.write(new Gson().toJson(manifest).getBytes(StandardCharsets.UTF_8)); + } + writer.finish(); + return out.toByteArray(); + } + + /** + * The TDF with its manifest edited by {@code edit}, and its payload, segment hash and root + * signature redone under the all-zero key, the value the payload key starts from before any + * split is folded in. Only the missing key access objects make it unreadable. + */ + private static byte[] underTheZeroKey(byte[] tdfBytes, Consumer edit) throws IOException { + var manifest = JsonParser.parseString(new String(entry(tdfBytes, TDFWriter.TDF_MANIFEST_FILE_NAME), + StandardCharsets.UTF_8)).getAsJsonObject(); + var integrity = manifest.getAsJsonObject("encryptionInformation").getAsJsonObject("integrityInformation"); + assertThat(integrity.get("segmentHashAlg").getAsString()).isEqualTo("GMAC"); + JsonArray segments = integrity.getAsJsonArray("segments"); + assertThat(segments).hasSize(1); + + byte[] encrypted = new AesGcm(ZERO_KEY).encrypt(PLAINTEXT, 0, PLAINTEXT.length).asBytes(); + byte[] tag = Arrays.copyOfRange(encrypted, encrypted.length - AesGcm.GCM_TAG_LENGTH, encrypted.length); + segments.get(0).getAsJsonObject().addProperty("hash", Base64.getEncoder().encodeToString(tag)); + integrity.getAsJsonObject("rootSignature").addProperty("sig", + Base64.getEncoder().encodeToString(CryptoUtils.CalculateSHA256Hmac(ZERO_KEY, tag))); + edit.accept(manifest); + return zip(manifest, encrypted); + } + + @Test + void aTdfWithKeyAccessObjectsLoads() throws IOException { + unwraps.set(0); + var reader = load(createTdf()); + var out = new ByteArrayOutputStream(); + reader.readPayload(out); + assertThat(out.toByteArray()).isEqualTo(PLAINTEXT); + assertThat(unwraps).hasValue(1); + } + + @Test + void anEmptyKeyAccessListIsMalformed() throws IOException { + var withoutKeyAccess = underTheZeroKey(createTdf(), manifest -> + manifest.getAsJsonObject("encryptionInformation").add("keyAccess", new JsonArray())); + unwraps.set(0); + assertThatThrownBy(() -> load(withoutKeyAccess)) + .isInstanceOf(SDK.MalformedTDFException.class) + .hasMessageContaining("no key access objects"); + assertThat(unwraps).hasValue(0); + } + + @Test + void anAbsentKeyAccessListIsMalformed() throws IOException { + var withoutKeyAccess = underTheZeroKey(createTdf(), manifest -> + manifest.getAsJsonObject("encryptionInformation").remove("keyAccess")); + unwraps.set(0); + assertThatThrownBy(() -> load(withoutKeyAccess)) + .isInstanceOf(SDK.MalformedTDFException.class) + .hasMessageContaining("no key access objects"); + assertThat(unwraps).hasValue(0); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index cbc497de..418f872e 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -3,7 +3,6 @@ import com.connectrpc.ResponseMessage; import com.connectrpc.UnaryBlockingCall; import com.google.gson.reflect.TypeToken; -import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.JWK; import com.google.gson.Gson; import com.google.gson.JsonObject; @@ -26,6 +25,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.security.KeyPair; @@ -455,11 +455,39 @@ void testVerifyAssertionWithJwkHeaderInJWT() throws Exception { .setKeyAccessServerRegistryService(kasRegistryService).build()); tdf.createTDF(plainTextInputStream, tdfOutputStream, config); + assertEmbeddedKeyAddsNoTrust(tdf, tdfOutputStream.toByteArray(), keypair, plainText); + } + + /** + * A TDF whose assertion was signed by {@code signer} and carries the signer's public key in + * its JWS header. The embedded key grants nothing by itself: with no key configured the + * payload key with HS256 is selected and the RS256 binding fails; configuring the signer's + * key, per ID or as the default, is what makes it verify. + */ + private void assertEmbeddedKeyAddsNoTrust(TDF tdf, byte[] tdfBytes, KeyPair signer, String plainText) + throws Exception { + assertThatThrownBy(() -> tdf.loadTDF(new SeekableInMemoryByteChannel(tdfBytes), Config.newTDFReaderConfig(), + platformUrl)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> { + assertThat(e.getReason()).isEqualTo(AssertionFailureReason.ALGORITHM_MISMATCH); + assertThat(e).hasMessageContaining("reason=algorithm-mismatch"); + }); + + var unrelated = new Config.AssertionVerificationKeys(); + unrelated.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, + CryptoUtils.generateRSAKeypair().getPublic()); + assertThatThrownBy(() -> tdf.loadTDF(new SeekableInMemoryByteChannel(tdfBytes), + Config.newTDFReaderConfig(Config.withAssertionVerificationKeys(unrelated)), platformUrl)) + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> + assertThat(e.getReason()).isEqualTo(AssertionFailureReason.SIGNER_NOT_PERMITTED)); + + var configured = new Config.AssertionVerificationKeys(); + configured.defaultKey = new AssertionConfig.AssertionKey(AssertionConfig.AssertionKeyAlg.RS256, + signer.getPublic()); var unwrappedData = new ByteArrayOutputStream(); - var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), - Config.newTDFReaderConfig(), platformUrl); + var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfBytes), + Config.newTDFReaderConfig(Config.withAssertionVerificationKeys(configured)), platformUrl); reader.readPayload(unwrappedData); - assertThat(unwrappedData.toString(StandardCharsets.UTF_8)) .withFailMessage("extracted data does not match") .isEqualTo(plainText); @@ -503,14 +531,7 @@ void testVerifyAssertionWithX5cHeaderInJWT() throws Exception { .setKeyAccessServerRegistryService(kasRegistryService).build()); tdf.createTDF(plainTextInputStream, tdfOutputStream, config); - var unwrappedData = new ByteArrayOutputStream(); - var reader = tdf.loadTDF(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()), - Config.newTDFReaderConfig(), platformUrl); - reader.readPayload(unwrappedData); - - assertThat(unwrappedData.toString(StandardCharsets.UTF_8)) - .withFailMessage("extracted data does not match") - .isEqualTo(plainText); + assertEmbeddedKeyAddsNoTrust(tdf, tdfOutputStream.toByteArray(), keypair, plainText); } @Test @@ -551,10 +572,12 @@ void testWithAssertionVerificationDisabled() throws Exception { var unwrappedData = new ByteArrayOutputStream(); var dataToUnwrap = new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray()); var emptyConfig = Config.newTDFReaderConfig(); - var thrown = assertThrows(SDKException.class, () -> { + var thrown = assertThrows(SDK.AssertionFailureException.class, () -> { tdf.loadTDF(dataToUnwrap, emptyConfig, platformUrl); }); - assertThat(thrown.getCause()).isInstanceOf(JOSEException.class); + // no key configured, so the payload key with HS256 is selected, and the RS256 binding fails + assertThat(thrown.getReason()).isEqualTo(AssertionFailureReason.ALGORITHM_MISMATCH); + assertThat(thrown.getAssertionId()).isEqualTo(assertion1Id); // try with assertion verification disabled and not passing the assertion // verification keys @@ -641,6 +664,74 @@ void testSimpleTDFWithAssertionWithHS256() throws Exception { } } + /** + * The writer hashes the wire form it then writes, emits {@code "method": "jws"}, and keeps + * statement values as strings. + */ + @Test + void writerEmitsJwsBindingsOverTheWrittenAssertion() throws Exception { + var assertionConfig = new AssertionConfig(); + assertionConfig.id = "assertion1"; + assertionConfig.type = AssertionConfig.Type.HandlingAssertion; + assertionConfig.scope = AssertionConfig.Scope.Payload; + assertionConfig.appliesToState = AssertionConfig.AppliesToState.Encrypted; + assertionConfig.statement = new AssertionConfig.Statement(); + assertionConfig.statement.format = "json"; + assertionConfig.statement.schema = "urn:nato:stanag:5636:A:1:elements:json"; + assertionConfig.statement.value = "{\"ocl\":{\"cls\":\"SECRET\"},\"n\":1e21}"; + + var rsaKasInfo = new Config.KASInfo(); + rsaKasInfo.URL = "https://example.com/kas" + 0; + var tdfOutputStream = new ByteArrayOutputStream(); + new TDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()) + .createTDF(new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)), tdfOutputStream, + Config.newTDFConfig(Config.withAutoconfigure(false), Config.withKasInformation(rsaKasInfo), + Config.withSystemMetadataAssertion(), Config.withAssertionConfig(assertionConfig))); + + var manifestJson = new StringWriter(); + try (var manifestReader = TDFReader.open(new SeekableInMemoryByteChannel(tdfOutputStream.toByteArray())).manifest()) { + manifestReader.transferTo(manifestJson); + } + var assertions = JsonParser.parseString(manifestJson.toString()).getAsJsonObject().getAsJsonArray("assertions"); + assertThat(assertions).hasSize(2); + for (var written : assertions) { + var binding = written.getAsJsonObject().getAsJsonObject("binding"); + assertThat(binding.get("method").getAsString()).isEqualTo("jws"); + assertThat(written.getAsJsonObject().getAsJsonObject("statement").get("value").getAsJsonPrimitive().isString()) + .isTrue(); + var claims = com.nimbusds.jwt.SignedJWT.parse(binding.get("signature").getAsString()).getJWTClaimsSet(); + assertThat(claims.getStringClaim("assertionHash")) + .isEqualTo(Manifest.Assertion.computeAssertionHash(written.toString())); + } + } + + /** The writer refuses an assertion a reader would reject while parsing, as a configuration error. */ + @Test + void writerRefusesAnAssertionThatIsNotIJson() { + var rsaKasInfo = new Config.KASInfo(); + rsaKasInfo.URL = "https://example.com/kas" + 0; + for (var bad : List.of("value", "id")) { + var assertionConfig = new AssertionConfig(); + assertionConfig.id = "id".equals(bad) ? "assertion\uD800" : "assertion1"; + assertionConfig.type = AssertionConfig.Type.BaseAssertion; + assertionConfig.scope = AssertionConfig.Scope.Payload; + assertionConfig.appliesToState = AssertionConfig.AppliesToState.Unencrypted; + assertionConfig.statement = new AssertionConfig.Statement(); + assertionConfig.statement.format = "string"; + assertionConfig.statement.value = "value".equals(bad) ? "lone \uDC00 surrogate" : "fine"; + var config = Config.newTDFConfig(Config.withAutoconfigure(false), Config.withKasInformation(rsaKasInfo), + Config.withAssertionConfig(assertionConfig)); + var tdf = new TDF(new FakeServicesBuilder().setKas(kas).setKeyAccessServerRegistryService(kasRegistryService).build()); + assertThatThrownBy(() -> tdf.createTDF(new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)), + new ByteArrayOutputStream(), config)) + .as(bad) + .isInstanceOf(IllegalArgumentException.class) + .isNotInstanceOf(SDK.TamperException.class) + .hasNoCause() + .hasMessageContaining("reason=invalid-json"); + } + } + @Test void testSimpleTDFWithAssertionWithHS256Failure() throws Exception { // var keypair = CryptoUtils.generateRSAKeypair(); @@ -1607,14 +1698,21 @@ void loadTDFReportsHostileArchivesAsMalformed() { * size fails when the manifest is read, an oversized compressed size when the archive is opened. */ @Test - void loadTDFReportsAManifestRunningPastTheArchiveAsMalformed() { - // the manifest opens a JSON comment, so the parser reads on to the end of the archive - // looking for its close, rather than failing at the first byte past the entry - var uncompressedPastTheEnd = HostileZips.tdfWith("/*", e -> e.uncompressedSize = 0x7FFFFFFFL); + void loadTDFReportsAManifestRunningPastTheArchiveAsMalformed() throws IOException { + // the entry runs on through the central directory, whose binary fields are not UTF-8, + // and the manifest decoder refuses them rather than substituting U+FFFD + var uncompressedPastTheEnd = HostileZips.tdfWith("{\"a\":\"", e -> e.uncompressedSize = 0x7FFFFFFFL); assertThatThrownBy(() -> loadTDFFrom(uncompressedPastTheEnd)) - .isInstanceOf(SDK.MalformedTDFException.class) - .hasCauseInstanceOf(InvalidZipException.class) - .hasMessageContaining("Archive ended"); + .isInstanceOfSatisfying(SDK.AssertionFailureException.class, e -> + assertThat(e.getReason()).isEqualTo(AssertionFailureReason.INVALID_JSON)); + // and the entry itself still ends in an error at the end of the archive, not a short read + var manifestEntry = new ZipReader(new SeekableInMemoryByteChannel(uncompressedPastTheEnd)).getEntries() + .stream().filter(e -> e.getName().equals(TDFWriter.TDF_MANIFEST_FILE_NAME)).findFirst().orElseThrow(); + assertThatThrownBy(() -> { + try (var data = manifestEntry.getData()) { + data.transferTo(OutputStream.nullOutputStream()); + } + }).isInstanceOf(InvalidZipException.class).hasMessageContaining("Archive ended"); var storedPastTheEnd = HostileZips.tdfWith("{}", e -> e.compressedSize = 0x7FFFFFFFL); assertThatThrownBy(() -> loadTDFFrom(storedPastTheEnd)) diff --git a/sdk/src/test/resources/assertion-vectors-phase0.json b/sdk/src/test/resources/assertion-vectors-phase0.json new file mode 100644 index 00000000..adfad432 --- /dev/null +++ b/sdk/src/test/resources/assertion-vectors-phase0.json @@ -0,0 +1,3417 @@ +{ + "format": "opentdf-assertion-vectors/1", + "source": "opentdf/tests xtest/vectors/assertions-phase0.json (DSPX-5520)", + "description": "Shared conformance vectors for Phase 0 of the ZTDF assertions contract (DSPX-5519). modelledNames maps each level of the manifest that the case-variant rules check (spec 3.2, Table 3.2) to the member names listed for it. A level path is the member names from the manifest root joined by dots: the empty path is the root, and a name followed by [] means each element of that member's array. A level applies only where its value is a JSON object. caseVariantExceptions maps a level path to names that do not violate rule R1 there; rule R2 still applies to them. Manifest vectors apply the rules to the whole manifest; assertion objects in the other kinds are checked from the assertions[] level down. Each keys entry has an alg and one material member, publicKeyPem (a PEM public key) or secretBase64 (raw secret bytes). The material reflects the key's form, not its alg, and some entries are deliberately unusable: a verify harness passes the selected entry unchanged to the SDK's configured-key preparation, which must refuse those with configuration-error.", + "modelledNames": { + "": [ + "assertions", + "encryptionInformation", + "payload", + "schemaVersion", + "tdf_spec_version" + ], + "payload": [ + "type", + "url", + "protocol", + "mimeType", + "isEncrypted", + "tdf_spec_version" + ], + "encryptionInformation": [ + "type", + "policy", + "keyAccess", + "method", + "integrityInformation" + ], + "encryptionInformation.keyAccess[]": [ + "type", + "url", + "protocol", + "wrappedKey", + "policyBinding", + "encryptedMetadata", + "kid", + "sid", + "schemaVersion", + "ephemeralPublicKey" + ], + "encryptionInformation.keyAccess[].policyBinding": [ + "alg", + "hash" + ], + "encryptionInformation.method": [ + "algorithm", + "iv", + "isStreamable" + ], + "encryptionInformation.integrityInformation": [ + "rootSignature", + "segmentHashAlg", + "segmentSizeDefault", + "encryptedSegmentSizeDefault", + "segments" + ], + "encryptionInformation.integrityInformation.rootSignature": [ + "alg", + "sig" + ], + "encryptionInformation.integrityInformation.segments[]": [ + "hash", + "segmentSize", + "encryptedSegmentSize" + ], + "assertions[]": [ + "id", + "type", + "scope", + "appliesToState", + "statement", + "binding" + ], + "assertions[].statement": [ + "format", + "schema", + "value" + ], + "assertions[].binding": [ + "method", + "signature", + "bdo" + ] + }, + "caseVariantExceptions": { + "encryptionInformation.method": [ + "IsStreamable" + ] + }, + "keys": { + "rsa-signer": { + "alg": "RS256", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk+jyVUP1fM2Ox+Pet3tI\nLE8IHBw+NnT+OHX/ZZcHNyYNWstnqAN2V1oRhueSixnZKWuPAjtWAzuqDAwBRDqm\njqScwGIyW6fryW3qnNfXSGQhTbv1vzzREeiwWAFquEsEZrceKAQGhkCis8aVe9CB\nYwAjiHdWSiQFVOWRqbNqGMnY5I9enbu9uhWTInlKq2IfM2ohLFybZkgQOt0RdEi/\nR4QtfH5bkC85uoLAfd97abLl/ZNdnlnLcEiVaqSQWJq/fdyfzLNOqXdSVpkD58bP\nNEslicjlR7Z6H/YNdnjlqu5DHkohvezO8M1uBZJL1uFxRwAMoUn1/tt8dqULF0i5\n7QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "rsa-other": { + "alg": "RS256", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyz+dDj8fEg32NQ7aYbHU\nkZsQ1TbMeZVH6IyaSxEeEPEo6Dk643HkljmZxr7uOxl1SU6aPnUbV0SiLisyhjAv\nDOvWRHX1q7R+pCrhPp3lofIeKhde8u6ae38v1tKqCQNX6sTJLRMZah3MldTCrc7M\npq56iu7+NKFHDWiPdRrNkCdA0SCjFZtf2kVfYrav5xL/As9t9OFmgkjq3OvfOz6R\nuFNrpNi6wcku0iMw0+kgxa9ojYBQYzZJfEI/b8VGw4C05V7nL1SOoZ1/S8TFMFcp\nGoQmaFyu1UTtGK4ONliygJumGWKfukKPN4oF/kbjR7X0UONT0JGZBtpwnv8J+x/B\nGwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "hs256-configured": { + "alg": "HS256", + "secretBase64": "ZKQTFVvVexWaYOSclZVV8Ai9H0lUpY5m4Lej8P3dCwc=" + }, + "hs256-payload-key": { + "alg": "HS256", + "secretBase64": "+6JqBJ+febI0bll1y9XfkMkGrMeXh17LjwzRcasilWU=" + }, + "hs256-pem-secret": { + "alg": "HS256", + "secretBase64": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SQpMRThJSEJ3K05uVCtPSFgvWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtCmpxU2N3R0l5VzZmcnlXM3FuTmZYU0dRaFRidjF2enpSRWVpd1dBRnF1RXNFWnJjZUtBUUdoa0NpczhhVmU5Q0IKWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpLwpSNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGwvWk5kbmxuTGNFaVZhcVNRV0pxL2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQCk5Fc2xpY2psUjdaNkgvWU5kbmpscXU1REhrb2h2ZXpPOE0xdUJaSkwxdUZ4UndBTW9VbjEvdHQ4ZHFVTEYwaTUKN1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" + }, + "rs256-lowercase-alg": { + "alg": "rs256", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk+jyVUP1fM2Ox+Pet3tI\nLE8IHBw+NnT+OHX/ZZcHNyYNWstnqAN2V1oRhueSixnZKWuPAjtWAzuqDAwBRDqm\njqScwGIyW6fryW3qnNfXSGQhTbv1vzzREeiwWAFquEsEZrceKAQGhkCis8aVe9CB\nYwAjiHdWSiQFVOWRqbNqGMnY5I9enbu9uhWTInlKq2IfM2ohLFybZkgQOt0RdEi/\nR4QtfH5bkC85uoLAfd97abLl/ZNdnlnLcEiVaqSQWJq/fdyfzLNOqXdSVpkD58bP\nNEslicjlR7Z6H/YNdnjlqu5DHkohvezO8M1uBZJL1uFxRwAMoUn1/tt8dqULF0i5\n7QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + "unknown-alg": { + "alg": "XS256", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk+jyVUP1fM2Ox+Pet3tI\nLE8IHBw+NnT+OHX/ZZcHNyYNWstnqAN2V1oRhueSixnZKWuPAjtWAzuqDAwBRDqm\njqScwGIyW6fryW3qnNfXSGQhTbv1vzzREeiwWAFquEsEZrceKAQGhkCis8aVe9CB\nYwAjiHdWSiQFVOWRqbNqGMnY5I9enbu9uhWTInlKq2IfM2ohLFybZkgQOt0RdEi/\nR4QtfH5bkC85uoLAfd97abLl/ZNdnlnLcEiVaqSQWJq/fdyfzLNOqXdSVpkD58bP\nNEslicjlR7Z6H/YNdnjlqu5DHkohvezO8M1uBZJL1uFxRwAMoUn1/tt8dqULF0i5\n7QIDAQAB\n-----END PUBLIC KEY-----\n" + } + }, + "vectors": [ + { + "name": "assertion-worked-example-string-value", + "kind": "assertion", + "status": "required", + "note": "Spec 4.4 first example as a writer emits it, with its binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "assertionHash": "456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495" + } + }, + { + "name": "assertion-worked-example-unmodelled-extension", + "kind": "assertion", + "status": "required", + "note": "Spec 4.4 second example: an unmodelled member is hashed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"hash\":\"deadbeef\",\"note\":\"coi\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiYTczNDdmZjFjNzE4YzBlZTdlODg1NThkZTY0Mzg5ZDVlNzE5NDk5MGViNmM3ODY2MmQwYmNkOWNmYjE5YzdiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtuTkgveHh4akE3bjZJVlkzbVE0blY1eGxKa090c2VHWXRDODJjK3huSHVnPT0ifQ.SV8GD6VR7SFrNMClVgDmNSSZ3-z8B2dv8smJYU8Zc1jvhKpAVDjKkaRsf3nxAKLeQJIIgwy-tosRidfQ-qiTvJyB9emXyiQwGud-46lO02eu39A4PJJ19VfAn4Um8Rqvk_QhcHqsrMUHkiqCttIfqo6Dofl8Y9do-jKawHjGIyswukfS4COj87je0GlrZmS38W0fgBURbFc_5AxBCh7C7rIywWjlk4xbaZqB79yHeJv_HfDzR9AAsNZgi3syrX0id2Y5AvQm8-N0ZplIDXa0aTMByrQVzDZt1eDIXZn3SW7SqcVw6TSiO4lXJT7ZlPv3AQk9BNEUODnwlyiU6bngLg\"}}", + "expect": { + "assertionHash": "a7347ff1c718c0ee7e88558de64389d5e7194990eb6c78662d0bcd9cfb19c7ba" + } + }, + { + "name": "assertion-worked-example-acp240-object-value", + "kind": "assertion", + "status": "required", + "note": "Spec 4.4 third example: ACP 240 label as an object value, JWS binding", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}", + "expect": { + "assertionHash": "46853cb52912fd76f3ff90df3936158ee220b8e5973c5d02bb6e50f468596fba" + } + }, + { + "name": "assertion-no-binding-member", + "kind": "assertion", + "status": "required", + "note": "The hash input never includes a binding, so none is needed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "assertionHash": "456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495" + } + }, + { + "name": "assertion-binding-not-an-object", + "kind": "assertion", + "status": "required", + "note": "Only the top-level binding is removed, whatever its type", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":\"not-an-object\"}", + "expect": { + "assertionHash": "456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495" + } + }, + { + "name": "assertion-whitespace-and-member-order", + "kind": "assertion", + "status": "required", + "note": "Insignificant whitespace and member order do not change the hash", + "input": "\r\n {\r\n\t\"binding\": {\r\n\t\t\"signature\": \"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\r\n\t\t\"method\": \"jws\"\r\n\t},\r\n\t\"statement\": {\r\n\t\t\"value\": \"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\r\n\t\t\"schema\": \"urn:nato:stanag:5636:A:1:elements:json\",\r\n\t\t\"format\": \"json+stanag5636\"\r\n\t},\r\n\t\"appliesToState\": \"unencrypted\",\r\n\t\"scope\": \"payload\",\r\n\t\"type\": \"handling\",\r\n\t\"id\": \"classification\"\r\n} \n", + "expect": { + "assertionHash": "456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495" + } + }, + { + "name": "assertion-escaped-id", + "kind": "assertion", + "status": "required", + "note": "An id spelled with an escape is the decoded string classification", + "input": "{\"id\":\"classif\\u0069cation\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "assertionHash": "456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495" + } + }, + { + "name": "assertion-escaped-names-and-values", + "kind": "assertion", + "status": "required", + "note": "Escaped member names and strings hash as their decoded text", + "input": "{\"\\u0069d\":\"classification\",\"type\":\"handl\\u0069ng\",\"sc\\u006fpe\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json\\u002bstanag5636\",\"schema\":\"urn\\u003anato\\u003astanag\\u003a5636\\u003aA\\u003a1\\u003aelements\\u003ajson\",\"value\":\"{\\u0022ocl\\u0022:{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "assertionHash": "456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495" + } + }, + { + "name": "assertion-unmodelled-top-level-member", + "kind": "assertion", + "status": "required", + "note": "An unmodelled top-level member is part of the hash", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"handlingCaveat\":\"TEST-CAVEAT\",\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiYWE5NTdjYmE2YjU5ZDhmMzgzNGI5OGRkNmQyNzZmNmJhMjI3MDdiMWE4OTIzNGY0MGFkZmRhZjZmN2M0YzkxZiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtxbFh5NmExblk4NE5MbU4xdEoyOXJvaWNIc2FpU05QUUszOXIyOThUSkh3PT0ifQ.D9dWwt1YJNBexrx6a1HoVvSNm8HpRDnSSyKvHCOZOAPCa3JZzrYRnDppGJWgqpXHstCMBtvBhzM5LFMG8blQQlZykFeqjAj84YO4cSqalG_u0_6hZBuv03mrg_yVU_zIjS9d4SdvbGKQN2O45Z8zbdOBoge_53wTnQvSuk_qDji7Gj5JA2tBga-b3ix3l4-FUUIQZYXGH9IMaCFFd_5UIO-1uozAUhqo4hpVc8B0RuCFOlkK_HYxMzP8YyKQ-nJu5SAgmyKmuzLd3Hb706OojZRyPOmrSFAoEur-5dbNVFhjNxmLPvYQorxHUDU8_6y68Z2qEOyktAsg7dDMKcu6jA\"}}", + "expect": { + "assertionHash": "aa957cba6b59d8f3834b98dd6d276f6ba22707b1a89234f40adfdaf6f7c4c91f" + } + }, + { + "name": "assertion-unmodelled-statement-member", + "kind": "assertion", + "status": "required", + "note": "An unmodelled statement member is part of the hash", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"encoding\":\"utf-8\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiOTcxMDFjNTI3ZDU1NjYyMmJiZGU5ODNhOGRlZWJmZDIwOTgxNDRlMzhlNTg4ZmQwMWU3MTk1NmRjM2M0MDJhNSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtYRUJ4U2ZWVm1JcnZlbURxTjdyL1NDWUZFNDQ1WWo5QWVjWlZ0dzhRQ3BRPT0ifQ.j-l5A2W_X3061omsmWIu_J6en39SwHfV13bGFyAGBijaGIMf0NeaXUBYBThpkNY9FsT2DEFpeoKJR3LwaeUuuX7D6MmueMGwnux5KV1sEZwfXImdUFkSGI7sdcYL4sCrigyYF4821rYwWepY9y-Nxv7WiBQgvvgFkdiQlu2YZwPNsLpvNeqWJ8_xUXldTYK7-UgFGtc3PIl4lbfCPKKDEHbv5jkWt5zV7MdE8wRRuyJ14Rrrz71G9bINNA2PIxmoxBBL7Vjq4UhXACiDdpZpXMmc9gn6ZpF_9DJimX3vX2nGzxDbm_TKbmfZTfm9tPZzZ_gJi0-UpgtjWCtj2IaXyQ\"}}", + "expect": { + "assertionHash": "97101c527d556622bbde983a8deebfd2098144e38e588fd01e71956dc3c402a5" + } + }, + { + "name": "assertion-reserved-names-in-statement", + "kind": "assertion", + "status": "required", + "note": "hash, binding, signingKey, sign, and verify inside statement are hashed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"hash\":\"456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495\",\"binding\":{\"method\":\"jws\",\"signature\":\"e30.e30.c2ln\"},\"signingKey\":{\"alg\":\"HS256\",\"key\":\"placeholder\"},\"sign\":\"yes\",\"verify\":false},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMzJhNDY4ZDA3NmQ0YjQxNDNmNjlmZTEwMzYzYzYxZjkxZTY2MWE1M2Q1MWY5YTcxYWM4ZmZiYTBlMmUxMmYwYiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREl5cEdqUWR0UzBGRDlwL2hBMlBHSDVIbVlhVTlVZm1uR3NqL3VnNHVFdkN3PT0ifQ.dzik7xUD3kfo0C0GAplTMl68t8pgU3VG64rlEgqnK1tGGa81cqtUo9MvKPfv2aguT5FlIINh7KSOYW7R45rfyWO3kCwcZKTFOYFrDumtS8Ioi065w-aFm9TaLiVwdyrXiqy3JaJ7sso9vOd5HCMiaIK7QMYpBJ6f_J6xPuwsUfVu4MVET0ddbPsp1EKxQAtDVg5MBaKOq6xhonVFvdGc94H98PKVA36c_p14ot7_LdOpRxMCbcJt5yMEz-JbDQJv6pU15s0DD2uQb-7hbi0vhGVpxM-Ojr84LbhpGY85CILjNOaoliXNGiU8-LG05eunNvVWtm5EqEQayWO0Fdlygg\"}}", + "expect": { + "assertionHash": "32a468d076d4b4143f69fe10363c61f91e661a53d51f9a71ac8ffba0e2e12f0b" + } + }, + { + "name": "assertion-reserved-names-in-object-value", + "kind": "assertion", + "status": "required", + "note": "hash, binding, signingKey, sign, and verify inside statement.value are hashed", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"},\"hash\":\"deadbeef\",\"binding\":{\"signature\":\"e30.e30.c2ln\"},\"meta\":{\"signingKey\":\"k\",\"sign\":1,\"verify\":[true]}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiY2IyNWJhYjBiNjYxZjMxN2Y0MzE1ZTlmODQxMDU0ZTQ5MmIzMTg1OWE4ODVkYzdmMmYyYTYxZWE2MDE4MGE0YiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExMSmJxd3RtSHpGL1F4WHArRUVGVGtrck1ZV2FpRjNIOHZLbUhxWUJnS1N3PT0ifQ.Udh7E7feSHy5qbgULmv2ndT8OVqZawZ0gSz1VCIOUNvLtjeNUMKGp4gzaxAkGf9Y1ZnITS36l3R1KIMOuSxEgUkfY5msS3nlOTjaKqwRQr9uUfXemb1kwJSEuPsPvXfIjP8rbb8FuHc67rZmRHvMA1c8NGMOQ-jUEMSwiuhFVUIpjL-Gs0gjKE6RS3qDun57LFvAqgMzm1tfQmckozrb0aTuCNP2WWktQbiKBCmL8p9_ZNxr0vW2_DU-qxIIn3ymCX-uoQ4E3pJjEP_TOsH7EkZHD_RKQrJGCwYJEWV7P0NNtWgVbnXuSgaQ5QKGfsZUGQNrS5wUTccVRoxfQlxLHw\"}}", + "expect": { + "assertionHash": "cb25bab0b661f317f4315e9f841054e492b31859a885dc7f2f2a61ea60180a4b" + } + }, + { + "name": "assertion-reserved-names-in-extension", + "kind": "assertion", + "status": "required", + "note": "hash, binding, signingKey, sign, and verify inside an extension are hashed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"binding\":{\"hash\":\"deadbeef\"},\"signingKey\":{\"alg\":\"RS256\",\"key\":\"placeholder\"},\"sign\":{\"verify\":\"x\"}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMjQ4Y2IyOWY3OTI4NzVjZTViZTBhNjg5NWFkZmZmNjEwOWUwYmRkZjZhZmU2YTA5YjdhNWYyYjA2N2FhMDM3MSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRElrakxLZmVTaDF6bHZncG9sYTMvOWhDZUM5MzJyK2FnbTNwZkt3WjZvRGNRPT0ifQ.DQ1HqLdMtcaaJAW-PmOiqfHVdmZy2FBEtaYG2bpw8-ZxWpNcVPCGTuedQnHblrFv9g4fm8DujHARVFkb59DKKOn58s87vKfMUNvMyORXYBT4qOZKvnSl1gV1m8x2Qy5TlCwtQUtTMgdWf5aX2MFBGA-2W7exydUGPkn9J5RpmenXDhisPnMMBN0HdBG0L6dfd6CmJghdDKm3WGhWWe5vDuHbkuhTxN7uwL2WwxHYdeXGDeFyNfCEYQdbArT4rUDqUlufDuyd8XQUwOot7NqoR7kLVg2ERFHofsY2XQCirUy9FIOJzRVX4hUO8QNLHPnykXMjJdf1Sz8hDaBE8dDamw\"}}", + "expect": { + "assertionHash": "248cb29f792875ce5be0a6895adfff6109e0bddf6afe6a09b7a5f2b067aa0371" + } + }, + { + "name": "assertion-top-level-signing-key-member", + "kind": "assertion", + "status": "required", + "note": "A top-level signingKey is hashed; only binding is removed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"signingKey\":{\"alg\":\"HS256\",\"key\":\"placeholder\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMDA5YjM0OWY5ZGQwM2Q5ZGFlMzc2YTUxMDAxYjU3ZjEyNzhmYjEzY2U4YjUwNWZmNGI4Zjk3YzE0OTNmNzkzZiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRElBbXpTZm5kQTluYTQzYWxFQUcxZnhKNCt4UE9pMUJmOUxqNWZCU1Q5NVB3PT0ifQ.ZsjHxY0p7i-w8-z9-zLkhDTAcLT8A_2t9Adbdh4yFE4VNXwp3XgG0nE27-jm-dxIXl_iOmNlURbHkyxHKUXdHzubDMdF7tCCz4aopUCaFJ7mtpYqDVXqI6UvrbBnjbfSSIzDeuBnwntXBcPwy3XDST-K6s_YLZIM86IiK697G1z5_4BGzoNak5FGZNzrEZ1nMALUH0htWCHle3AQpnZBL70h0sv70T3q7vtZWv6TjsPx-u1UDst6qU37B3BISiwLJikaafiWnwocs8zyb4PfS5LF8uC5FBmM93oEl_5Qg4PKWyKpy9jLc6ZxYHTQVM49FOBjA3U3cVx1TMlgSbwTjA\"}}", + "expect": { + "assertionHash": "009b349f9dd03d9dae376a51001b57f1278fb13ce8b505ff4b8f97c1493f793f" + } + }, + { + "name": "assertion-top-level-hash-member", + "kind": "assertion", + "status": "required", + "note": "A top-level hash member is hashed; only binding is removed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"hash\":\"456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495\",\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMTQzMDA2M2JlNmRhOTQ3OWJmODQ0NjgwNmFjZDVjM2MwNTJjNzlhYmYzYjQ3YTEwYzA1NGZiODlkOTliZDYxNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRElVTUFZNzV0cVVlYitFUm9CcXpWdzhCU3g1cS9PMGVoREFWUHVKMlp2V0Z3PT0ifQ.Gs3q-BV5It7es7KJ4hTEnSbvv8mFJ5bIwQwenFlZo71-qu1uyibNmJExq2Sv9YqeW_M74ZdRlDyPDI7o9rP1smOOiOkwZheMAtkrLjWMNhXUCoMlY_Uqu0Nf-qxKXjwRQ3G3L3WhChHHoRyN8_itG2NX4Y6F3gPordtXA0pAQTRLit7beCJ11HUa2IiSWx3WCvWKXIaZGsyh1UL0PINvoaDkcjRIUh9o1RWXYD7aOqiHf5z4hQtYi7HESQIP_RJhpy4KhctrW8T712VEn4lNCtS0TB4fCbW-E0aSOrIvASjJhCN9KZ4y6ResyoQ-xjMiuIKEeXsrD0IxnuTelQkQVg\"}}", + "expect": { + "assertionHash": "1430063be6da9479bf8446806acd5c3c052c79abf3b47a10c054fb89d99bd617" + } + }, + { + "name": "assertion-stanag-label-string-value", + "kind": "assertion", + "status": "required", + "note": "The xtest STANAG 5636 label as a string value", + "input": "{\"id\":\"424ff3a3-50ca-4f01-a2ae-ef851cd3cac0\",\"type\":\"handling\",\"scope\":\"tdo\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"catl\\\":[{\\\"type\\\":\\\"P\\\",\\\"name\\\":\\\"Releasable To\\\",\\\"vals\\\":[\\\"usa\\\"]}],\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"},\\\"context\\\":{\\\"@base\\\":\\\"urn:nato:stanag:5636:A:1:elements:json\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNGE0NDdhMTNjNWEzMjczMGQyMGJkZjdmZWVjYjlmZmUxNjY0OWJjNzMxOTE0YjU3NGQ4MDAzNWEzOTI3Zjg2MCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpLUkhvVHhhTW5NTklMMzMvdXk1LytGbVNieHpHUlMxZE5nQU5hT1NmNFlBPT0ifQ.SSLcKsBoSP2dIj1L-10yiLiCyePKNGWg64ycWbWR5z2iamLZpxVFFHF0eNLs_aW9U2M3TLVQTE9gcQPPnQVkvYESaOxrIh6_4YBConjNh1dbAKQ2rGLQdZZjC7f2FjQ2GU7Y1tsBO1VuZbS9hAacesDylc3MdCtvF-XoM-spCPCiYkXhlTIRymP0MUBNI5DQwEvITmDNYnhcDt78wJrcpyR52lhoa8Sex9bPHNoYeFYQPd_c8SutvgirQ3LcBjrL5Znzl1q8OdbCeJeiddicl5wc34okT4TUA_958sVTxY1P7Xu2nHUsErAaUGKmRFlbbk-Hkif3TYFEtMScKhM_sQ\"}}", + "expect": { + "assertionHash": "4a447a13c5a32730d20bdf7feecb9ffe16649bc731914b574d80035a3927f860" + } + }, + { + "name": "assertion-stanag-label-object-value", + "kind": "assertion", + "status": "required", + "note": "The same STANAG 5636 label as an object value, which hashes differently", + "input": "{\"id\":\"424ff3a3-50ca-4f01-a2ae-ef851cd3cac0\",\"type\":\"handling\",\"scope\":\"tdo\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"SECRET\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"usa\"]}],\"dcr\":\"2024-10-21T20:47:36Z\"},\"context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiY2Y3M2Q1ZGY5MDFiYzgxZmM2OTc1OTRjNGFmMGU1MjhiODU5Njc0ZmZjZDM0ZGY0YmEzODVmMTNhMzU3OTY1MCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExQYzlYZmtCdklIOGFYV1V4SzhPVW91RmxuVC96VFRmUzZPRjhUbzFlV1VBPT0ifQ.UTpVEugs7vmmf8_EcZG7yKBZXXGF6Pj24mcbKc6NlQpkAO3daJxkADWOJAFzhT5mzKPnI-0CBT7DUiTdnpm1XTXy5AmKyYWv8pGQa2KaQMVC-tbrNRObscgYTFDkL5pMPbQYByOFub_XkkkfNmy-jWoZvH6WRjmuFLog4jHvGJ1haP6on6HnDncdw1qZhYTaoiq9zeGzvRdQERJL8Ac7bBfeAByAAOZvjnq-yMGSC1mj-Yx8hWxyC9WKZN8Szu8sJC4IH_Q-pl3zK2FiR8Bjcbe1zX8hw1ASebRedB6VVUc3--Nx4-3eqgnFJ3JTFnEN8JjTcqCAHleorRIZrUdQ2g\"}}", + "expect": { + "assertionHash": "cf73d5df901bc81fc697594c4af0e528b859674ffcd34df4ba385f13a3579650" + } + }, + { + "name": "assertion-numbers-integers", + "kind": "assertion", + "status": "required", + "note": "JCS number form in an extension: integers and negative zero", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":[0,-0,1,-1,42,-42,1000000,9007199254740991,-9007199254740991]},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiZDEzMDg1ZmE4NmZjMzYxZjhjMDg1MjcxYzcwOTQ0YjRhYzhlNDcwZjMyNGI5MzQ0OTJiNTFiODM1MTE0MjQ1NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExSTUlYNmh2dzJINHdJVW5ISENVUzBySTVIRHpKTGswU1N0UnVEVVJRa1ZRPT0ifQ.dfPlOsg5g9z9V9lF6nhb7QMkLXKZ3uYrk6uP0dCLdxOTwtmriV1oGVghrKMxMu7oTqnCe2PHVu7t58hHfLntqOARlkKQNZB9PPvWF0_JNpmfSTilU0fuo7B-5DWloWrJvNoCnBUi23xL4MIHQPZ9gkEB-joR7QKAsfxPuQkvH3RAvLJ7zrpOOWxh-gvPPZhMd1PLWnTio-qWR-PIyq2sNDw6sMgrs1qWrMp91ENXYOOBZ7stZ7gmnt_wcyipCenf4ljxohfnre4-HZQ7Ay78ic4wmBVOGqnJjtkqDYipviqylA9xgLhEGFiJXptC5nSq07Lgr2RqegHO15wvDPY0aA\"}}", + "expect": { + "assertionHash": "d13085fa86fc361f8c085271c70944b4ac8e470f324b934492b51b8351142455" + } + }, + { + "name": "assertion-numbers-decimals", + "kind": "assertion", + "status": "required", + "note": "JCS number form in an extension: decimals, trailing zeros, and small fractions", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":[0.0,-0.0,0.1,0.5,1.5,-2.25,100.0,4.50,123.456,0.000001,0.0000001,333333333.33333329]},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMzQwNTA4MGQwODdlZDQ2ZjdlMDYwM2U2MWEyNGY2MDQzOTRkYTUzOTc3N2NkMTBiM2FiNjBhYmZkMGE1MmJjNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREkwQlFnTkNIN1ViMzRHQStZYUpQWUVPVTJsT1hkODBRczZ0Z3EvMEtVcnh3PT0ifQ.kfWJ02h1M64Dc1QGvjiTc6BqmqHbMnCL4FtOXVddM-Zsbd5Vbk5kM9vOm5GOFhPYzFok-1ixLneU77NVSL1f3fmdlzp1H7fsPyj563M8mVke9oJ_MxnjRvZHvI_Gzu8QP0Z39KOj0k2E7kNAlyA6_PZNNwQ5Dxgl_mki_XxHO_KPgEaZoVjCFu5k9AYL3vdzUpwnT9ZsifOJD-PTFhVnP2zhJFWoZJu45Q-1NWEe2V2mB6UbrhX6IxPZCNx7-KYgMldNrAC7Z71GlFG1-IP30eprnsDRSVX0SmFqskMg5W9cUzogThfWpZCzJ0yVJNyY045rKe5U-z9RwXidpp8Y8g\"}}", + "expect": { + "assertionHash": "3405080d087ed46f7e0603e61a24f604394da539777cd10b3ab60abfd0a52bc7" + } + }, + { + "name": "assertion-numbers-exponents", + "kind": "assertion", + "status": "required", + "note": "JCS number form in an extension: exponent spellings and the 1e21 and 1e-7 switch points", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":[1e0,1E3,1e+3,1E-3,2e-3,1e20,1e21,1E30,1e-6,1e-7,1.5e300,-1.5e-300,5e-324,1.7976931348623157e308,0.000000000000000000000000001]},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU4ODY1NGExOWE3NTQxZjdmZWY0NDFjNzA3YzRjNTY5MjZkNDIyNzZjMjJjMjVlZWY5ZmM5ZGFiMmZhYmM3MyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGaUdWS0dhZFVIMy92UkJ4d2ZFeFdrbTFDSjJ3aXdsN3ZuOG5hc3ZxOGN3PT0ifQ.FIqlTfSbGrIjkuomwRcUbNVY0Rm_0n4nkqOxWeV3LteQJvujGNTjrMkyaNB3d9FPmFoxZAnHp0FDZhm2pN5GCBptWiIQcO2jFHu1gQnRxuiU2Nf_S6O8cgTub8jz0oFGYVCGRyIpKq3PILke7o5GnbcXuwKF1VbYJ_s9Zyo87eBikhP7Xeh3UGuPcVNxunav3OTaSP-kOLpSzFf_Zy-FXu5RxOyVDL55Tng9-kqCoone-nO8LZh8RUoE3WGbNo8_pxiVvZxV-1IJI_6xlSL-5hW7KuJCSq9KJWMzHjNBsfKAdGIqMdUNda9l8qHEYxoTd9vPGaavgq7Cn4_MvCfpiw\"}}", + "expect": { + "assertionHash": "4588654a19a7541f7fef441c707c4c56926d42276c22c25eef9fc9dab2fabc73" + } + }, + { + "name": "assertion-numbers-beyond-double-precision", + "kind": "assertion", + "status": "required", + "note": "JCS number form in an extension: digits beyond double precision round to the nearest double", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":[9007199254740992,9007199254740993,9007199254740994,123456789012345678901234567890,0.1000000000000000055511151231257827,0.30000000000000004,1e23]},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiM2FjNzZiNzc4MzI5YjkwNGYwYmQ3YTI2MzVhMjc3NTQyMjdjMTc0NDJlYTVmZDIyOTViMmY5MGI3N2QyZmYxNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREk2eDJ0M2d5bTVCUEM5ZWlZMW9uZFVJbndYUkM2bC9TS1ZzdmtMZDlML0Z3PT0ifQ.ErGSZEHJL3olS-_r1PnACaMQGGmIAANQIq51Hq-a0PnyKVVyxquhYdlN7DHB1jpS5fu9FKcX8FvWJu7mM_el3gdGoOffi41M4VcYwOxHdYN3fGCzVpkKyTK0IJWc3Oj5r2r3ujO3i0XiGeSlYNHgJtYBWy2_KryVwRgunIZzfJnI5BlJ7OwtoAV0hKjkqg5bjdtGiRtL8Zth-h26YZjvQkKLGBmGTNJ5Lf84VwVzohRCyqa4AApSFLUhXLVPNa2KtfFo9s7tA9aDViFCcRoF9E7AO2RJchNSxnIU5JxXQfT6L3hGNceXXWKg60ZhhJo6bdpy3Oe_yn-ExWbrWNhHbA\"}}", + "expect": { + "assertionHash": "3ac76b778329b904f0bd7a2635a27754227c17442ea5fd2295b2f90b77d2ff17" + } + }, + { + "name": "assertion-numbers-in-object-value", + "kind": "assertion", + "status": "required", + "note": "JCS number form inside an object statement.value", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"},\"version\":1.0,\"weights\":[1E2,-0,0.25e1,1e-7]}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNGI5ZGQwZmJmNThiMGVhZTM0NjNmN2VkYWYyY2IzMTFjZTBjYmFmYWZiODhhY2FjMmRkM2E1NmYyYTBhZjJkZiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpMbmRENzlZc09yalJqOSsydkxMTVJ6Z3k2K3Z1SXJLd3QwNlZ2S2dyeTN3PT0ifQ.PmagRlH0mXJ8-LhvS44FjWi00NLcebMhUkSTDZ2mQAIchG1v20aMXmsdAL1Fel2-1e_8WhfXRSawO8fbmhDDKKvcyI4Ur5zZ4XCIgy951ImQ22hg6gZR6pHPVH8uTfT-hqN06lfjsg3acDEWtWuS-zYfnjP-eUGrLU8N0lcdgMwMNTxL5TSF_EQtIRODqsFJmxrWqbKCws8wzr_ASOhVewXckGKZrTnpZpH_B1IKR5A9h6-_8iLX7AqeBGU_Eh9M6wk3R1nOkcTLnTBqg4Ps4_RE0t5n3_gsAMR3gqFz49vYRc4BvvPXp4qISbDQynz86OOpehfJXHLiBohO5iaLIQ\"}}", + "expect": { + "assertionHash": "4b9dd0fbf58b0eae3463f7edaf2cb311ce0cbafafb88acac2dd3a56f2a0af2df" + } + }, + { + "name": "assertion-rfc8785-sample-object", + "kind": "assertion", + "status": "required", + "note": "RFC 8785 Section 3.2.2 sample: numbers, escapes, and literals", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"numbers\":[333333333.33333329,1E30,4.50,2e-3,0.000000000000000000000000001],\"string\":\"\\u20ac$\\u000f\\u000aA'\\u0042\\u0022\\u005c\\\\\\\"\\/\",\"literals\":[null,true,false]},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiOTZkNWZkMTJmZmE1M2M3OGI5MDA3NGM0MjgyZGIwNmIxMzM4OGViNzg0ZTdlZmEyYmI0ODNiMDg5NjAzMTA0MyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtXMWYwUy82VThlTGtBZE1Rb0xiQnJFemlPdDRUbjc2SzdTRHNJbGdNUVF3PT0ifQ.AtJRHQqMlvAFOaJ3XZilHSrZHF0zeFq02RYDMt1Fnu-1EwX6QfswG97F7ZCkwt_3Sk-dMPgxKIvmrN_QNI1Lvp55zJjpDc2HPJ6Na4Q5Xy0EnKv-tV8C7MupjyxYuyrJ5VEa4Z3nLR9eWBbRcpc2E_KtDkDkSu_uPgRNySGpEHeFwnTV-fmZE4NWCKVBWfPlBXUz_5xzubOUF8kbOAppKmf3vWOQ0sD7zqQv3-ALIRVOPMygegrJb0SxAy2avus2uhTp7CHT4n45vflOM5qL4Ab7yzqlU1p9OIedNkqEGYtoZfAj5vSNQ2b8Ta9WON73qQWWuMM9ue9pAQgHDn4c9w\"}}", + "expect": { + "assertionHash": "96d5fd12ffa53c78b90074c4282db06b13388eb784e7efa2bb483b0896031043" + } + }, + { + "name": "assertion-non-ascii-literal", + "kind": "assertion", + "status": "required", + "note": "Literal non-ASCII text, including a character outside the BMP", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\u00dcn\u00efc\u00f6d\u00e9 \u4e2d\u6587 \ud83d\ude00\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiZWJhNTZiM2IzMmU3NGM5MzdiNDg1ZTJkODY0MWQ4MmM5ZDE1MmIxNzRjMmY0NzFmNzJhZGIzZjcyMjdjMjdlNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExycFdzN011ZE1rM3RJWGkyR1FkZ3NuUlVyRjB3dlJ4OXlyYlAzSW53bjV3PT0ifQ.UW0q6A_6zg6fIN8Bt_vRIHDS--3B3VMDkyCclLSMa9A-82sg4Jot-y5xI1ND2pyoDGaMr5w29Jmr7bmmE2RY7MFrz7b6tQKwpdXbrTVff29p1w0y8nfGeIBZZAa8EonBvXQilQo1J0Tp4zN9ADhY5pET3Zx5BSbuIyJ6jLwwvy1t09sEeIsfCV9CPsafWZ08JB1z6BjfNB1GqeKJFj-7wxqObUrvnI-6oEdtJW4hQZjzCl2yO5sUXUJf4Bm7nwidrRNG5ZeQX2jDOF83xE5CJAX_Dyqa3QeqIOiB6R-l-ATVCm-iC8sT1qGVHyxyIquQCJPKrYvrjd4s3JrdNPaCPg\"}}", + "expect": { + "assertionHash": "eba56b3b32e74c937b485e2d8641d82c9d152b174c2f471f72adb3f7227c27e7" + } + }, + { + "name": "assertion-non-ascii-escaped", + "kind": "assertion", + "status": "required", + "note": "The same text with escapes, including a surrogate pair, hashes the same", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\u00dcn\\u00efc\\u00f6d\\u00e9 \\u4e2d\\u6587 \\ud83d\\ude00\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiZWJhNTZiM2IzMmU3NGM5MzdiNDg1ZTJkODY0MWQ4MmM5ZDE1MmIxNzRjMmY0NzFmNzJhZGIzZjcyMjdjMjdlNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExycFdzN011ZE1rM3RJWGkyR1FkZ3NuUlVyRjB3dlJ4OXlyYlAzSW53bjV3PT0ifQ.UW0q6A_6zg6fIN8Bt_vRIHDS--3B3VMDkyCclLSMa9A-82sg4Jot-y5xI1ND2pyoDGaMr5w29Jmr7bmmE2RY7MFrz7b6tQKwpdXbrTVff29p1w0y8nfGeIBZZAa8EonBvXQilQo1J0Tp4zN9ADhY5pET3Zx5BSbuIyJ6jLwwvy1t09sEeIsfCV9CPsafWZ08JB1z6BjfNB1GqeKJFj-7wxqObUrvnI-6oEdtJW4hQZjzCl2yO5sUXUJf4Bm7nwidrRNG5ZeQX2jDOF83xE5CJAX_Dyqa3QeqIOiB6R-l-ATVCm-iC8sT1qGVHyxyIquQCJPKrYvrjd4s3JrdNPaCPg\"}}", + "expect": { + "assertionHash": "eba56b3b32e74c937b485e2d8641d82c9d152b174c2f471f72adb3f7227c27e7" + } + }, + { + "name": "assertion-string-escapes", + "kind": "assertion", + "status": "required", + "note": "JCS escapes only control characters, quote, and backslash", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\u0000\\b\\f\\n\\r\\t\\\"\\\\\\/\\u001f\\u007f\\u2028\\u2029\\u00e9\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNjJhODI2ODJiYTlkZTVmYmM2ZDc2YTUzMGI2MWM0MGJiNzBhOTEwNWM2ODJkM2M0NWYwODIwZTE4YjY2MDk0NyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREppcUNhQ3VwM2wrOGJYYWxNTFljUUx0d3FSQmNhQzA4UmZDQ0RoaTJZSlJ3PT0ifQ.S2--eGIT5EYuVpvHEQeYPIAaJ6F7SXnqDm7KJwe4-WLfuxAPQiSVQDzkDiDFeK0uPBp3FiuAJGGEgh2mMEvDR2Rv_uhTZdNMrR7bgFOuEfUX1CG3eJA7twZFMtzBOHYKKJZgvvgsngHo-SP4rflehJVc13UFx5uQ13fobnLakrkHQ6yVXEukWXDW9V-jzp7d0Iy2BFJD9Cytaf21_trS1K2iTaieXRDHf_ZxhGcqdfu9Yads2SMI3noc7jXNiSMkanS8HKS7D2-dNXz0jvFRfUa7TGbKixZW8a4kICNPWlicwUMCDQ2AH-NqmNIleV2VfM0Fw-DzWTBbOwVmhYPjdw\"}}", + "expect": { + "assertionHash": "62a82682ba9de5fbc6d76a530b61c40bb70a9105c682d3c45f0820e18b660947" + } + }, + { + "name": "assertion-keys-sorted-by-utf16-code-units", + "kind": "assertion", + "status": "required", + "note": "RFC 8785 3.2.3: keys sort by UTF-16 code units, so U+1F600 precedes U+FB33", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\\u20ac\":\"Euro Sign\",\"\\r\":\"Carriage Return\",\"\\ufb33\":\"Hebrew Letter Dalet With Dagesh\",\"1\":\"One\",\"\\ud83d\\ude00\":\"Emoji: Grinning Face\",\"\\u0080\":\"Control\",\"\\u00f6\":\"Latin Small Letter O With Diaeresis\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMWM2Zjk3MGFmMTE3ZTJkNjZlMDVmYzIzZWI5MmViZGFmOTNlYzYzZGU4NTU5MWNlNDIwZmJjMTc4ZDUyYzdhMCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREljYjVjSzhSZmkxbTRGL0NQcmt1dmErVDdHUGVoVmtjNUNEN3dYalZMSG9BPT0ifQ.HWzQ_p167LWfQE-F5M7mssj9BZBbMiUSPIwb2TsEeh_95rFAdYpzsAFFsa6l_NQ3DYaQSz3kTVXTCU-ENrNEMo3ONLCLAHBMBEAgYK1YJhwwnMWcxtI8IWtgj3DPnEY_xErY4yHWLyVhXwWCtqNSq36L8dSpCR-7TOLl_leppDAV2wpteRMSFKnlZsc6Ksb-kwpvjqOp7eW7buNKa2mC3BxC2EfHS8wGKmu6PlJyve6TLAhc0Y1Rm47rNxPQzxx87SnBNAeGLq4ckoRl6cFM08f795mkHu3X0qP5js_-BDajnVJouTka9IAX83ucmpLxg9Hrco5j_lk58M0IdvxapQ\"}}", + "expect": { + "assertionHash": "1c6f970af117e2d66e05fc23eb92ebdaf93ec63de85591ce420fbc178d52c7a0" + } + }, + { + "name": "assertion-surrogate-pair-in-member-name", + "kind": "assertion", + "status": "required", + "note": "An escaped surrogate pair is one valid code point, in a name or a value", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\\ud83d\\ude00\":\"\\ud83d\\ude00\",\"\ud83d\ude00x\":\"literal\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiZDUzYzkwMDFiNDI3MWNjNWY2YzUyOWMxZGU3ODdiNThkZTExY2ZkODM0YWNlN2Y4ZWUzMjczZWZlOWJlNWMxZCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExWUEpBQnRDY2N4ZmJGS2NIZWVIdFkzaEhQMkRTczUvanVNblB2NmI1Y0hRPT0ifQ.VcG-5dIGsgcn1BSsifmy1__qwDnt0CislFPtrR7UXJmNAkncbQ-IiKT6hXZ1Q0mCrFhqgNfLdOCC5aHVXpWOszfQHvNW-URGkHTwYDW50pAHZshzELjJ4-6F0zyJ9sRyZm0SrGNv8OuIL0l67WWlE6aDy6HHTqkccMx_-HygFO1kq_3h4TvWuaVBFGzipTBSASwdtrZtSak9OweNUEYp-BtVZrn3baLxJ_RQC1WWBaWs5qcaqfAX48p9itbvhgJcp616gh5XixpKi6u2741mSNo3Nlmj2cV0VIhQUhTN3mJ0dN9qHUErmA29457rM_KSkoiMuFUnbKJhZbviIrmQ7Q\"}}", + "expect": { + "assertionHash": "d53c9001b4271cc5f6c529c1de787b58de11cfd834ace7f8ee3273efe9be5c1d" + } + }, + { + "name": "assertion-id-surrounding-whitespace", + "kind": "assertion", + "status": "required", + "note": "id is not trimmed: surrounding whitespace is part of the id and the hash", + "input": "{\"id\":\" classification \",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiYjI3YjhlNDBmZGY0MzQ3Zjg2NmJlYWUyYTEwMTRiZTU4MTA0YjdhZjVjNDZkOTkyNWViMzZlOWRlZjBmNzRkNCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREt5ZTQ1QS9mUTBmNFpyNnVLaEFVdmxnUVMzcjF4RzJaSmVzMjZkN3c5MDFBPT0ifQ.XEUHqDlzJu-75j7GgsfWKKR_zj1M6VlN8Co11WxgQ0cTsn1zfCReBm2ggoQMXb0A3qf0zuS2jvGSEgB_WifGoxJAKUT_GijN72CWYBjw4Na_2RtbCN5orfD_bq5lfOLEvmQzwvsElmQUINe_UnYGDFkAX5R4mgpVJbEhEoj2g4uV9iivZWHcsUKjrzf4pm9qo70xOUJ6hQnP2a6XfgpBVOSOvXNrCb8TZk-Ol9Gpqd8R-Ly7XnciF4TLOe0BZ8LHJEu2uCe8vVHZ4pKqw-mRhdnIOSHcvZ18WLaGfOv5gJClx1COmznn4a11thq665zcxmH8S2Kzp5RCzxvGNkctkQ\"}}", + "expect": { + "assertionHash": "b27b8e40fdf4347f866beae2a1014be58104b7af5c46d9925eb36e9def0f74d4" + } + }, + { + "name": "assertion-id-non-ascii", + "kind": "assertion", + "status": "required", + "note": "A non-ASCII id is hashed as its code points", + "input": "{\"id\":\"classification-\u5206\u7c7b-\u00e9\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMGM2ODQxZGZlMTkxZWIyYjI3YzAzN2ZiNjk4NmExZDFiMjQyZWY5MWJiMDVhY2I4NzVmNjM4MDNkOGRjZjc3OSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRElNYUVIZjRaSHJLeWZBTi90cGhxSFJza0x2a2JzRnJMaDE5amdEMk56M2VRPT0ifQ.hFxwgPnStnYBxHl3KBQilyOHGfdujL3z0y2_HfSBr-vUgIrFCYokPmcFFsSNRLcIlhSkKmIXndblGrFrLIT_0pfUPC7YjQrv9j21HDWyTqCEq6kMeAJ4elJBvcCA403tCM5SfI51gfzyN2LuDATh8M4AhPZ-M9J_-5ZwDmokEvDDgTGMJ79jV9izX2MZyJfBrVD2ydaQiszWYW0jRyd3L9LHJRXjxpouRDBMBg_kV64wVSY09t_xmcVoMinH74e0YxZHPZlu-0xGwqyk3_o8zk3k7EB8wyb4xcHcXjpmMIAxwPoGXHC69wKs7zvv4LBYzjmr9gvVuYsOGv4nkhFUIg\"}}", + "expect": { + "assertionHash": "0c6841dfe191eb2b27c037fb6986a1d1b242ef91bb05acb875f63803d8dcf779" + } + }, + { + "name": "assertion-case-variants-inside-object-value-allowed", + "kind": "assertion", + "status": "required", + "note": "Names inside statement.value are application content, not checked", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"},\"id\":\"a\",\"ID\":\"b\",\"Statement\":\"c\",\"Binding\":{\"Signature\":\"d\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiODkxZGZjNDE1MmRlZjkzNzg5OTkyOGI0MzE4NWE0YjhkYmIxMzI3MzJlZDhhNzdmMzI4OWFiZDkyYzdhYmI5MSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtKSGZ4QlV0NzVONG1aS0xReGhhUzQyN0V5Y3k3WXAzOHlpYXZaTEhxN2tRPT0ifQ.jtuYR91fVwRfv6WMZkreQapWFAaxLknKKmD9TER39ey17a32Tbqet2lvSbjqqGlOGaZ0RdWEcKok3WnL6sABIA4KAyBMmzauVD8nhffoUT_4dn_RSYgQzlJAfH0L7MS5EgYqJMmsow9QjzqXKn9gNR_4kxvnUltUglD9jvr4Ng2OUwVqy2aj3Vh96MiOVfK6XWVULlk0MlVhzSptlKZp_n1jXzaXHqinkgJjX2CfKO3fj2gg5DRrKpRnfA4j3uEH6sarneb9Uxijq7b_iicR5Dma3JNuDq52MGdIc8Ihr3rWkOQISeg5r6zFIZZpWtfoY6Bpe51HdJwUeFQkvdrQxA\"}}", + "expect": { + "assertionHash": "891dfc4152def937899928b43185a4b8dbb132732ed8a77f3289abd92c7abb91" + } + }, + { + "name": "assertion-case-variants-inside-extension-allowed", + "kind": "assertion", + "status": "required", + "note": "Names inside unmodelled members are not checked", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"id\":1,\"ID\":2,\"Type\":\"x\",\"BINDING\":true,\"Assertions\":[]},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMmYxODMxYjg2ZDY5YTEzNmI1YmE5YjQ0NWQ4YjY0ZWU3ZTE4ZWZkODcyYWY2NjlhNzUxNGQ0YWE4NTJlNDI4OCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREl2R0RHNGJXbWhOclc2bTBSZGkyVHVmaGp2MkhLdlpwcDFGTlNxaFM1Q2lBPT0ifQ.bYPrYahKdIICCKjWOM8gL3BVoRlIDavjCyRJiCv2b1_L0MvfOl6esh44jkTLfdnFq3n6LWIXMgU8DKKPIZvBEItg9ecqVj5FetC5trJONUFHdxgv0I3eIB6g9aL7ehrbG_SZrdXqIyw3YE0YY0Rt7OYjCvzbVYXuY8f0xxE__bCUqIXC-xqSxnwv4RJZW6lQFV3s5lGfT2nITZK1NjyW8K5vFzalRqZpcWTQ14IesSRu5rMJ89v0a2Un46vyTBQZIgueyIqUPC1aC5pdILKClIseEkroTna1kWJzXio5KhpfMwCNSJRk68dl6corSE8xFkR12owuGuRTB52vfv8YKw\"}}", + "expect": { + "assertionHash": "2f1831b86d69a136b5ba9b445d8b64ee7e18efd872af669a7514d4aa852e4288" + } + }, + { + "name": "assertion-unlisted-case-pair", + "kind": "assertion", + "status": "required", + "note": "extensions and Extensions are case variants of each other (rule R2)", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"a\":1},\"Extensions\":{\"a\":2}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-statement-names-at-assertion-level-allowed", + "kind": "assertion", + "status": "required", + "note": "Format, Schema, and Value are modelled only inside statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"Format\":\"x\",\"Schema\":\"y\",\"Value\":\"z\",\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNzg5ZTBkNjY0NDdjMGE4MTdhMGUxYWM2NWNmMDNkZTk3ZmM1ODIxNTI5NjY0MzVkYjZkMjJjZjQzMTA0ZGRiMyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREo0bmcxbVJId0tnWG9PR3NaYzhEM3BmOFdDRlNsbVExMjIwaXowTVFUZHN3PT0ifQ.P8NF-IOngAhizJa1NPYsP-MKnv2WzFbKbkE4HAWE1MrDqfphIqLljbefNfCUehDJc8wYrxN3d0uwpYHwryIIk7l7tlcgksQJ-mCespz8q5wHbd-on42gZ1xSm8K01jTBhE4QX--7TxyEShBpdaOYCafO8pYdi7ImVTexoGMtRsmzcq7Hy7O1H48n-7xuydCq8AM8C2oRNPjpoZH6UpsKouW5CLTOQaFX22XpDgLdebrbQpvaNRw2ITx-qUopdNaw2qTMaRTtrFpNUWjElpfJns6V5ll66tVdId0oNaFliz12J5FgCXP4Pw0eSlt5EarJcoYSibkcl3Tsi5FqYpa4jA\"}}", + "expect": { + "assertionHash": "789e0d66447c0a817a0e1ac65cf03de97fc582152966435db6d22cf43104ddb3" + } + }, + { + "name": "assertion-assertion-names-in-statement-allowed", + "kind": "assertion", + "status": "required", + "note": "ID, Type, and Binding are modelled only at the assertion level", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"ID\":\"x\",\"Type\":\"y\",\"Binding\":\"z\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiOWY3Zjk5ZTQzMjkwZjg2MmIyYjljMmI4ODYwOGJiODhiMzExN2Y0ZDljZmY1ZmVhOTdkNTE0YzY3MDNjNDAzZCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtmZjVua01wRDRZcks1d3JpR0NMdUlzeEYvVFp6L1grcVgxUlRHY0R4QVBRPT0ifQ.QlZkpxLtpRlLMYm-_1hQzntEDeKWYKerXQ0fopjSF0iWY85qmC100HLd96qFkKLK24t81s9qCIV4m8HLNPblQZx-tQC2BOHgFw4_vHXEC8ppK-rXGvvuGfgD_yfwFvP7l-yRFMT8j16BWsFVEZqYMJLqzofXHcpKNFIp8qDSmteGfZFo-qsYDqATq2aOe-VcYxh35W8S-lenQa67qh8-e6dMchTNVx-UEOD9GhoLUiwwZWBoUaXL8ht_ke2QtKC0U0-LvpLCupUJ-KBOaK76d-IyHeEfslT6zrZtyNBbrbAeuryprPwYm9eOVnn19sTx8v8IQwzM06FNi-qVVuGIOg\"}}", + "expect": { + "assertionHash": "9f7f99e43290f862b2b9c2b88608bb88b3117f4d9cff5fea97d514c6703c403d" + } + }, + { + "name": "assertion-dotless-i-not-a-case-variant", + "kind": "assertion", + "status": "required", + "note": "U+0131 does not fold to i under the contract rule, so this name is unmodelled", + "input": "{\"id\":\"classification\",\"\u0131d\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiMmE5NjM0OWJiMDQ3YmI4ZGNjMTMyNGYyMWU3NDhiOWMxNDM4MjcwNGFjNDA2YzlhOWQ1MTVkZmRjYjQ0MmUxNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRElxbGpTYnNFZTdqY3dUSlBJZWRJdWNGRGduQkt4QWJKcWRVVjM5eTBRdUZ3PT0ifQ.eKEuD5GA8cMIzk2oVsT2MAW8XCYMv4iUNpe6wh3pamBT5AfWQYYZqpKcVvfMSoqzH9cs8IderkfyUUITg5RpA9Ie8dkdnpItyLGxT5ezmXeH7B4rbr3XOOtIJHGqHLRFNgypmzna778zV3hHJj7T0P2sNdCYteTe2EL45054zWJW2IHXJQTGzdn3j6lqBIi1sJNYDGHoJYMUGMuWdtS2N_cJ701dnq8KS4zX-U4KQGYLqfQVzI1hZDDZve71QG3U3HwOcGNjgjKSMZkQlprwi73K6dbSrL3_TGdrIUUsSv8MwC9xuZd5mVlIvAt4-mw0DKr6V0M56WwxGKdhmVVaYg\"}}", + "expect": { + "assertionHash": "2a96349bb047bb8dcc1324f21e748b9c14382704ac406c9a9d515dfdcb442e17" + } + }, + { + "name": "assertion-dotted-capital-i-not-a-case-variant", + "kind": "assertion", + "status": "required", + "note": "U+0130 does not fold to i under the contract rule, so this name is unmodelled", + "input": "{\"id\":\"classification\",\"\u0130d\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiYWVkNTYwNzM1YWYyZDVlNTI3ZGVlZWI3ODYxMGI5YThmMmNjYzU1NTYyZWI1NWQ0YjcxMWFhMjI0NjNlNGE3NCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREt1MVdCeld2TFY1U2ZlN3JlR0VMbW84c3pGVldMclZkUzNFYW9pUmo1S2RBPT0ifQ.R8fC3K1AJGNF6smFF5aqHMUYMYm8meboRFO7c_ey4g5rndfob_njvEo1rrklNUCSwiawKiOzqeL4OIvPCzMfIMHj9bD3Hd4KB2GHVhXpbJmOJqkTOlk-WvMW3QqcMI2uZ-SStyyRstunEZl7NGoZ3531rnkSvDhl3tkBjrSxdKAtgKcMTNmDHlf57gduFMK4Ut6WH63K2jWHSxq67PlcQx0DEBgboew62DwwcE-XDLwlNaEdwODyX8jF0yHhpkAsZwKY_mbabUVA7rJtX3vSmA9MdImK9VMNaSPK6LWZt3BL2IWgYOUx-ifv1nQSKAhkoL8hUhgDghVuzrgg3TZbhQ\"}}", + "expect": { + "assertionHash": "aed560735af2d5e527deeeb78610b9a8f2ccc55562eb55d4b711aa22463e4a74" + } + }, + { + "name": "assertion-ligature-st-not-a-case-variant", + "kind": "assertion", + "status": "required", + "note": "U+FB06 is one code point, so this name is not a variant of statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"\ufb06atement\":\"x\",\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDAzNzFmNTIwMjBmN2FjMTBiY2QyYmVhZmJkZGEwYjJmZGUzMDMyYTkzM2VkMzM2OTkwMjE1YmNkYzIyMWVmOSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpBTng5U0FnOTZ3UXZOSytyNzNhQ3kvZU1ES3BNKzB6YVpBaFc4M0NJZStRPT0ifQ.QNtLbnYCrQyscFr0ZQcYhmiEB7k74leO0oT-nzaR98fqjQIr2hFkTHRNXo9LfD8eEN6CG3V6xSsxHi9JJx8XUyelaqWKUMxYKWMe6hlsiBcKbAHVmC8jCJL6-zwnlJnZ9lqP9OKnneV1xLMi3QqdOFw8Pr1sIof_3taKVU6du2ecArGhFUOjf_uditr_u69Peq83WlVm4uFfvwdsWjfZtW7C8sucR2MBmxIabZkB6wq6b1SK64cykA_zEY0WKdm8HAx120hiZ8pk8n-LToJTtWORsTFI25gDZgV6t6ye9V87298IMv5d0pgBdoG7wf8gwkwAGMcLmXzTu7wSHpBSOQ\"}}", + "expect": { + "assertionHash": "40371f52020f7ac10bcd2beafbdda0b2fde3032a933ed336990215bcdc221ef9" + } + }, + { + "name": "assertion-unicode-normalization-distinct-names", + "kind": "assertion", + "status": "required", + "note": "Names are not normalized: precomposed and decomposed e-acute differ", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\u00e9\":1,\"e\u0301\":2},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiZTVhZmNjYWQwNzEwMzEwMjY2YzU0YjZjNmQ4NDFkYzlmZmE4NzUwZjc5ZGYyMTc2M2NkYjM1ZmM0YjY5OTdjNyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCRExscjh5dEJ4QXhBbWJGUzJ4dGhCM0ovNmgxRDNuZklYWTgyelg4UzJtWHh3PT0ifQ.eaL_5HI3jrw6rrnQ3jj-cmi6C3lmzdREwbyrhPirqOliVozi3JCiD8D8iQVG33GO6rYT-W-ZBx-sGUchpe3FmB1tpIRvSJCZ0ckOdW6JWyCHTwE46kc3feai3A7Zfhr7WQ--jhCnoEgga-4foAPpMMuWJHC3Xh27pcti-hv_zfRGrvgEc82CP3NCA5YQwSEbKt2JeicdU6akcCcSedmGUTLOrEvMaj8NzEt7lGqqLe8XQ4swFb4AKSFkvzuzHo_k0IYwxxLk_fShIORZ7Wt2fAN5ITw8rAhheL9BrIC6CP2SR1RkbgVoX8KkNPMyuHtRmRqZ4I0OAv8XzA2y8BawVw\"}}", + "expect": { + "assertionHash": "e5afccad0710310266c54b6c6d841dc9ffa8750f79df21763cdb35fc4b6997c7" + } + }, + { + "name": "assertion-duplicate-id", + "kind": "assertion", + "status": "required", + "note": "Two id members with different values", + "input": "{\"id\":\"classification\",\"id\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-id-identical-values", + "kind": "assertion", + "status": "required", + "note": "Two id members with the same value", + "input": "{\"id\":\"classification\",\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-in-statement", + "kind": "assertion", + "status": "required", + "note": "Two format members inside statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"format\":\"json\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-deep-in-object-value", + "kind": "assertion", + "status": "required", + "note": "Two cls members deep inside an object statement.value", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"cls\":\"SECRET\",\"cls\":\"UNCLASSIFIED\"}}}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-in-value-array-element", + "kind": "assertion", + "status": "required", + "note": "Two type members in an object inside an array in statement.value", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"},\"catl\":[{\"type\":\"P\",\"type\":\"R\"}]}}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-in-extension", + "kind": "assertion", + "status": "required", + "note": "Two note members inside an unmodelled member", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"note\":\"coi\",\"note\":\"other\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-in-binding", + "kind": "assertion", + "status": "required", + "note": "Two signature members inside binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\"signature\":\"x.y.z\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-duplicate-binding-member", + "kind": "assertion", + "status": "required", + "note": "Two top-level binding members, though binding is not hashed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"},\"binding\":{\"method\":\"jws\",\"signature\":\"x.y.z\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-escaped-duplicate-id", + "kind": "assertion", + "status": "required", + "note": "id and the escaped spelling of id are duplicates", + "input": "{\"id\":\"classification\",\"\\u0069d\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-escaped-duplicate-solidus", + "kind": "assertion", + "status": "required", + "note": "a/b and its escaped-solidus spelling are duplicates", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"a/b\":1,\"a\\/b\":2}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-escaped-duplicate-surrogate-pair", + "kind": "assertion", + "status": "required", + "note": "A literal U+1F600 name and its escaped surrogate pair are duplicates", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\ud83d\ude00\":1,\"\\ud83d\\ude00\":2}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "assertion-case-variant-ID", + "kind": "assertion", + "status": "required", + "note": "ID alongside id", + "input": "{\"id\":\"classification\",\"ID\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-Id-alias-only", + "kind": "assertion", + "status": "required", + "note": "Id without id is ambiguous, not a missing id", + "input": "{\"Id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-escaped-ID", + "kind": "assertion", + "status": "required", + "note": "The escaped spelling of ID alongside id", + "input": "{\"id\":\"classification\",\"\\u0049D\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-TYPE", + "kind": "assertion", + "status": "required", + "note": "TYPE alongside type", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"TYPE\":\"other\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-Scope-alias-only", + "kind": "assertion", + "status": "required", + "note": "Scope without scope", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"Scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-appliestostate", + "kind": "assertion", + "status": "required", + "note": "appliestostate alongside appliesToState", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"appliestostate\":\"encrypted\"}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-Statement", + "kind": "assertion", + "status": "required", + "note": "Statement alongside statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"Statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-Binding-alias-only", + "kind": "assertion", + "status": "required", + "note": "Binding without binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"Binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-long-s-scope", + "kind": "assertion", + "status": "required", + "note": "U+017F folds to s, so this name is a variant of scope", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"\u017fcope\":\"tdo\"}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-case-variant-long-s-appliesToState", + "kind": "assertion", + "status": "required", + "note": "U+017F against the uppercase S of appliesToState", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"appliesTo\u017ftate\":\"encrypted\"}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-statement-case-variant-Value", + "kind": "assertion", + "status": "required", + "note": "Value alongside value in statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"Value\":\"x\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-statement-case-variant-SCHEMA-alias-only", + "kind": "assertion", + "status": "required", + "note": "SCHEMA without schema in statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"SCHEMA\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-statement-case-variant-long-s-schema", + "kind": "assertion", + "status": "required", + "note": "U+017F variant of schema in statement", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"\u017fchema\":\"urn:nato:stanag:5636:A:1:elements:json\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-binding-case-variant-Signature", + "kind": "assertion", + "status": "required", + "note": "Signature alongside signature in binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\"Signature\":\"x.y.z\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-binding-case-variant-Method-alias-only", + "kind": "assertion", + "status": "required", + "note": "Method without method in binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"Method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-binding-case-variant-BDO", + "kind": "assertion", + "status": "required", + "note": "BDO in a JWS binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\"BDO\":\"x\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-binding-case-variant-long-s-signature", + "kind": "assertion", + "status": "required", + "note": "U+017F variant of signature in binding", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"\u017fignature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "assertion-lone-high-surrogate-in-value", + "kind": "assertion", + "status": "required", + "note": "A lone high surrogate in statement.value", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\ud800\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-reversed-surrogate-pair", + "kind": "assertion", + "status": "required", + "note": "A low surrogate before a high surrogate", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\ude00\\ud83d\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-high-surrogate-before-non-surrogate", + "kind": "assertion", + "status": "required", + "note": "A high surrogate followed by an escaped A", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\ud800\\u0041\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-fffe-escaped", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+FFFE, escaped", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\ufffe\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-ffff-escaped", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+FFFF, escaped", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\uffff\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-fdd0-escaped", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+FDD0, escaped", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\ufdd0\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-1ffff-escaped-pair", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+1FFFF as an escaped surrogate pair", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\ud83f\\udfff\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-10ffff-escaped-pair", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+10FFFF as an escaped surrogate pair", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\udbff\\udfff\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-fffe-literal", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+FFFE as literal UTF-8", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"x\ufffe\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-1fffe-literal", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+1FFFE as literal UTF-8", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\ud83f\udffe\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-lone-low-surrogate-in-extension", + "kind": "assertion", + "status": "required", + "note": "A lone low surrogate in an unmodelled member", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"note\":\"coi\\udc00\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-lone-surrogate-in-member-name", + "kind": "assertion", + "status": "required", + "note": "A lone surrogate in a member name inside an unmodelled member", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\\udead\":1}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-lone-surrogate-in-top-level-member-name", + "kind": "assertion", + "status": "required", + "note": "A lone surrogate in an unmodelled top-level member name", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"x\\ud800\":1}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-lone-surrogate-in-binding", + "kind": "assertion", + "status": "required", + "note": "A lone surrogate inside binding, which is parsed though not hashed", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"\\udead\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-lone-surrogate-in-id", + "kind": "assertion", + "status": "required", + "note": "A lone surrogate in id fails parsing before the id check", + "input": "{\"id\":\"classification\\ud800\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-noncharacter-fdef-literal-in-member-name", + "kind": "assertion", + "status": "required", + "note": "The noncharacter U+FDEF as a literal member name", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\ufdef\":1}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-number-1e400", + "kind": "assertion", + "status": "required", + "note": "1e400 is outside the double range", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":1e400}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-number-minus-1e400", + "kind": "assertion", + "status": "required", + "note": "-1e400 is outside the double range", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":-1e400}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-number-integer-beyond-double-range", + "kind": "assertion", + "status": "required", + "note": "A 401-digit integer is outside the double range", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-number-1e400-in-object-value", + "kind": "assertion", + "status": "required", + "note": "1e400 inside an object statement.value", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"},\"n\":1e400}}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-invalid-utf8-byte-ff", + "kind": "assertion", + "status": "required", + "note": "The byte 0xFF in statement.value", + "inputBase64": "eyJpZCI6ImNsYXNzaWZpY2F0aW9uIiwidHlwZSI6ImhhbmRsaW5nIiwic2NvcGUiOiJwYXlsb2FkIiwiYXBwbGllc1RvU3RhdGUiOiJ1bmVuY3J5cHRlZCIsInN0YXRlbWVudCI6eyJmb3JtYXQiOiJqc29uK3N0YW5hZzU2MzYiLCJzY2hlbWEiOiJ1cm46bmF0bzpzdGFuYWc6NTYzNjpBOjE6ZWxlbWVudHM6anNvbiIsInZhbHVlIjoibGFiZWz/In19", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-invalid-utf8-overlong", + "kind": "assertion", + "status": "required", + "note": "The overlong encoding C0 AF of a solidus", + "inputBase64": "eyJpZCI6ImNsYXNzaWZpY2F0aW9uIiwidHlwZSI6ImhhbmRsaW5nIiwic2NvcGUiOiJwYXlsb2FkIiwiYXBwbGllc1RvU3RhdGUiOiJ1bmVuY3J5cHRlZCIsInN0YXRlbWVudCI6eyJmb3JtYXQiOiJqc29uK3N0YW5hZzU2MzYiLCJzY2hlbWEiOiJ1cm46bmF0bzpzdGFuYWc6NTYzNjpBOjE6ZWxlbWVudHM6anNvbiIsInZhbHVlIjoiwK8ifX0=", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-invalid-utf8-encoded-surrogate", + "kind": "assertion", + "status": "required", + "note": "U+D800 encoded as UTF-8 bytes ED A0 80", + "inputBase64": "eyJpZCI6ImNsYXNzaWZpY2F0aW9uIiwidHlwZSI6ImhhbmRsaW5nIiwic2NvcGUiOiJwYXlsb2FkIiwiYXBwbGllc1RvU3RhdGUiOiJ1bmVuY3J5cHRlZCIsInN0YXRlbWVudCI6eyJmb3JtYXQiOiJqc29uK3N0YW5hZzU2MzYiLCJzY2hlbWEiOiJ1cm46bmF0bzpzdGFuYWc6NTYzNjpBOjE6ZWxlbWVudHM6anNvbiIsInZhbHVlIjoi7aCAIn19", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-invalid-utf8-truncated-sequence", + "kind": "assertion", + "status": "required", + "note": "A three-byte sequence cut short", + "inputBase64": "eyJpZCI6ImNsYXNzaWZpY2F0aW9uIiwidHlwZSI6ImhhbmRsaW5nIiwic2NvcGUiOiJwYXlsb2FkIiwiYXBwbGllc1RvU3RhdGUiOiJ1bmVuY3J5cHRlZCIsInN0YXRlbWVudCI6eyJmb3JtYXQiOiJqc29uK3N0YW5hZzU2MzYiLCJzY2hlbWEiOiJ1cm46bmF0bzpzdGFuYWc6NTYzNjpBOjE6ZWxlbWVudHM6anNvbiIsInZhbHVlIjoi4oIifX0=", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-invalid-utf8-in-member-name", + "kind": "assertion", + "status": "required", + "note": "The byte 0xFE in an unmodelled member name", + "inputBase64": "eyJpZCI6ImNsYXNzaWZpY2F0aW9uIiwidHlwZSI6ImhhbmRsaW5nIiwic2NvcGUiOiJwYXlsb2FkIiwiYXBwbGllc1RvU3RhdGUiOiJ1bmVuY3J5cHRlZCIsInN0YXRlbWVudCI6eyJmb3JtYXQiOiJqc29uK3N0YW5hZzU2MzYiLCJzY2hlbWEiOiJ1cm46bmF0bzpzdGFuYWc6NTYzNjpBOjE6ZWxlbWVudHM6anNvbiIsInZhbHVlIjoie1wib2NsXCI6e1wicG9sXCI6XCI2MmM3NmM2OC1kNzNkLTQ2MjgtOGNjYy00YzFlMTgxMThjMjJcIixcImNsc1wiOlwiU0VDUkVUXCIsXCJkY3JcIjpcIjIwMjQtMTAtMjFUMjA6NDc6MzZaXCJ9fSJ9LCJ4/iI6MX0=", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-trailing-comma", + "kind": "assertion", + "status": "required", + "note": "A trailing comma in the assertion object", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-nan-literal", + "kind": "assertion", + "status": "required", + "note": "NaN is not a JSON value", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":NaN}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-single-quoted-string", + "kind": "assertion", + "status": "required", + "note": "Single-quoted strings are not JSON", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":'coi'}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-leading-zero", + "kind": "assertion", + "status": "required", + "note": "Numbers cannot have leading zeros", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":01}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-raw-control-character", + "kind": "assertion", + "status": "required", + "note": "A literal tab inside a string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"a\tb\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-comment", + "kind": "assertion", + "status": "required", + "note": "Comments are not JSON", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}/* note */}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-trailing-data", + "kind": "assertion", + "status": "required", + "note": "Data after the assertion object", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}} {}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-syntax-invalid-escape", + "kind": "assertion", + "status": "required", + "note": "\\x is not a JSON escape", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\x41\"}}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "assertion-id-missing", + "kind": "assertion", + "status": "required", + "note": "No id member", + "input": "{\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-id-null", + "kind": "assertion", + "status": "required", + "note": "id is null", + "input": "{\"id\":null,\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-id-number", + "kind": "assertion", + "status": "required", + "note": "id is a number", + "input": "{\"id\":42,\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-id-empty", + "kind": "assertion", + "status": "required", + "note": "id is the empty string", + "input": "{\"id\":\"\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-id-array", + "kind": "assertion", + "status": "required", + "note": "id is an array", + "input": "{\"id\":[\"classification\"],\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-id-object", + "kind": "assertion", + "status": "required", + "note": "id is an object", + "input": "{\"id\":{\"value\":\"classification\"},\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-id-boolean", + "kind": "assertion", + "status": "required", + "note": "id is a boolean", + "input": "{\"id\":true,\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-id" + } + }, + { + "name": "assertion-statement-value-number", + "kind": "assertion", + "status": "proposed", + "note": "Proposed: statement.value is a number", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":42}}", + "expect": { + "reason": "invalid-statement" + } + }, + { + "name": "assertion-statement-value-boolean", + "kind": "assertion", + "status": "proposed", + "note": "Proposed: statement.value is a boolean", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":true}}", + "expect": { + "reason": "invalid-statement" + } + }, + { + "name": "assertion-statement-value-null", + "kind": "assertion", + "status": "proposed", + "note": "Proposed: statement.value is null", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":null}}", + "expect": { + "reason": "invalid-statement" + } + }, + { + "name": "assertion-statement-value-array", + "kind": "assertion", + "status": "proposed", + "note": "Proposed: statement.value is an array", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":[\"SECRET\"]}}", + "expect": { + "reason": "invalid-statement" + } + }, + { + "name": "assertion-statement-string", + "kind": "assertion", + "status": "proposed", + "note": "Proposed: statement is a string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":\"SECRET\"}", + "expect": { + "reason": "invalid-statement" + } + }, + { + "name": "assertion-statement-null", + "kind": "assertion", + "status": "proposed", + "note": "Proposed: statement is null", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":null}", + "expect": { + "reason": "invalid-statement" + } + }, + { + "name": "manifest-clean", + "kind": "manifest", + "status": "required", + "note": "A manifest with two well-formed assertions", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-no-assertions-member", + "kind": "manifest", + "status": "required", + "note": "A manifest without assertions", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\"}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-empty-assertions", + "kind": "manifest", + "status": "required", + "note": "A manifest with an empty assertions array", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-Payload-alongside-payload", + "kind": "manifest", + "status": "required", + "note": "payload and Payload at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}],\"Payload\":{\"type\":\"reference\"}}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-URL-alongside-url-in-key-access", + "kind": "manifest", + "status": "required", + "note": "url and URL in a key access object", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"URL\":\"https://other.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-case-variants-in-object-value", + "kind": "manifest", + "status": "required", + "note": "ID and Statement inside an assertion's statement.value are not checked", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"},\"ID\":\"x\",\"Statement\":\"y\"}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDI2OTZlYWQ5MTNkMTEyNzVjMmE3ZjBjMTg3OGQ0NWFhMzI5NTZkOTE2MWMzODRlYmFhNzA3ZDA0MGIyZmRkZCIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpDYVc2dGtUMFJKMXdxZnd3WWVOUmFveWxXMlJZY09FNjZwd2ZRUUxMOTNRPT0ifQ.B37o6QaUZzH3gDlYCC2vE-RFBDuv6v2qB9rl-H-l-YgMS5_F1cxFDWkCo8bNPiL2LBlat_kHazH0iq5ULd5lVqjnvLTslL632XYBPA8cOHfqyCkS7olpeThcZxOZA-5YXBtdX0LZwTbOp_QKO0MSu-suv0VDMVlyg9J3dq-AutzAQgB6nb7G_zoL223igwot5jjvmD8jnBqgqyxlEvWxxKAhb7ye-RXpdEh58HKeSxQ7_Eio3KM_UNB6wdnnsPUtJJ_zpMWvBpwcg1ZcsAEKIVwgkH4U7pdzpPn8GeTmxo-KMKN07dETjV87n0SVsXZ1gOwSJrLZRn2EkKbDDzIaPw\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-duplicate-root-member", + "kind": "manifest", + "status": "required", + "note": "Two schemaVersion members at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}],\"schemaVersion\":\"4.3.0\"}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "manifest-duplicate-assertions-member", + "kind": "manifest", + "status": "required", + "note": "Two assertions members at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}],\"assertions\":[]}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "manifest-duplicate-deep-in-key-access", + "kind": "manifest", + "status": "required", + "note": "Two url members inside a key access object", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"url\":\"https://other.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}]}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "manifest-escaped-duplicate-root-member", + "kind": "manifest", + "status": "required", + "note": "payload and its escaped spelling at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}],\"p\\u0061yload\":{\"type\":\"reference\"}}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "manifest-duplicate-in-assertion", + "kind": "manifest", + "status": "required", + "note": "Two id members in one assertion", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "duplicate-member" + } + }, + { + "name": "manifest-assertions-and-Assertions", + "kind": "manifest", + "status": "required", + "note": "Both assertions and Assertions at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}],\"Assertions\":[]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-Assertions-alone", + "kind": "manifest", + "status": "required", + "note": "Assertions without assertions at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"Assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-ASSERTIONS-alone", + "kind": "manifest", + "status": "required", + "note": "ASSERTIONS without assertions at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"ASSERTIONS\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-long-s-assertions-alone", + "kind": "manifest", + "status": "required", + "note": "A U+017F variant of assertions without assertions at the root", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"a\u017f\u017fertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-ID-variant-in-assertion", + "kind": "manifest", + "status": "required", + "note": "ID alongside id in one assertion", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"ID\":\"system-metadata\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-Id-alias-only-in-assertion", + "kind": "manifest", + "status": "required", + "note": "Id without id in one assertion", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"Id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-statement-variant-in-assertion", + "kind": "manifest", + "status": "required", + "note": "Value alongside value in an assertion's statement", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"Value\":\"x\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-binding-variant-in-assertion", + "kind": "manifest", + "status": "required", + "note": "Signature alongside signature in an assertion's binding", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\"Signature\":\"x.y.z\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-lone-surrogate-in-assertion", + "kind": "manifest", + "status": "required", + "note": "A lone surrogate in an assertion's statement.value", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"\\udead\"}}]}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "manifest-noncharacter-in-assertion", + "kind": "manifest", + "status": "required", + "note": "The noncharacter U+FFFF in an assertion member name", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"\\uffff\":1}}]}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "manifest-out-of-range-number-in-assertion", + "kind": "manifest", + "status": "required", + "note": "1e400 in an assertion's unmodelled member", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}},{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"n\":1e400}}]}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "manifest-invalid-utf8-in-assertion", + "kind": "manifest", + "status": "required", + "note": "The byte 0xFF in an assertion's statement.value", + "inputBase64": "eyJlbmNyeXB0aW9uSW5mb3JtYXRpb24iOnsidHlwZSI6InNwbGl0IiwicG9saWN5IjoiZXlKMWRXbGtJam9pTURBd01EQXdNREF0TURBd01DMDBNREF3TFRnd01EQXRNREF3TURBd01EQXdNREF3SWl3aVltOWtlU0k2ZXlKa1lYUmhRWFIwY21saWRYUmxjeUk2VzEwc0ltUnBjM05sYlNJNlcxMTlmUT09Iiwia2V5QWNjZXNzIjpbeyJ0eXBlIjoid3JhcHBlZCIsInVybCI6Imh0dHBzOi8va2FzLmV4YW1wbGUuY29tIiwicHJvdG9jb2wiOiJrYXMiLCJ3cmFwcGVkS2V5IjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBPT0iLCJwb2xpY3lCaW5kaW5nIjp7ImFsZyI6IkhTMjU2IiwiaGFzaCI6IkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9In0sImtpZCI6InIxIn1dLCJtZXRob2QiOnsiYWxnb3JpdGhtIjoiQUVTLTI1Ni1HQ00iLCJpdiI6IiIsImlzU3RyZWFtYWJsZSI6dHJ1ZX0sImludGVncml0eUluZm9ybWF0aW9uIjp7InJvb3RTaWduYXR1cmUiOnsiYWxnIjoiSFMyNTYiLCJzaWciOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBPSJ9LCJzZWdtZW50SGFzaEFsZyI6IkdNQUMiLCJzZWdtZW50U2l6ZURlZmF1bHQiOjIwOTcxNTIsImVuY3J5cHRlZFNlZ21lbnRTaXplRGVmYXVsdCI6MjA5NzE4MCwic2VnbWVudHMiOlt7Imhhc2giOiJ3TnI4SDJmdVZhVWNTU0FoVzVMTklBPT0iLCJzZWdtZW50U2l6ZSI6MTQsImVuY3J5cHRlZFNlZ21lbnRTaXplIjo0Mn1dfX0sInBheWxvYWQiOnsidHlwZSI6InJlZmVyZW5jZSIsInVybCI6IjAucGF5bG9hZCIsInByb3RvY29sIjoiemlwIiwibWltZVR5cGUiOiJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iLCJpc0VuY3J5cHRlZCI6dHJ1ZX0sInNjaGVtYVZlcnNpb24iOiI0LjMuMCIsImFzc2VydGlvbnMiOlt7ImlkIjoiY2xhc3NpZmljYXRpb24iLCJ0eXBlIjoiaGFuZGxpbmciLCJzY29wZSI6InBheWxvYWQiLCJhcHBsaWVzVG9TdGF0ZSI6InVuZW5jcnlwdGVkIiwic3RhdGVtZW50Ijp7ImZvcm1hdCI6Impzb24rc3RhbmFnNTYzNiIsInNjaGVtYSI6InVybjpuYXRvOnN0YW5hZzo1NjM2OkE6MTplbGVtZW50czpqc29uIiwidmFsdWUiOiJ7XCJvY2xcIjp7XCJwb2xcIjpcIjYyYzc2YzY4LWQ3M2QtNDYyOC04Y2NjLTRjMWUxODExOGMyMlwiLFwiY2xzXCI6XCJTRUNSRVRcIixcImRjclwiOlwiMjAyNC0xMC0yMVQyMDo0NzozNlpcIn19In0sImJpbmRpbmciOnsibWV0aG9kIjoiandzIiwic2lnbmF0dXJlIjoiZXlKaGJHY2lPaUpTVXpJMU5pSjkuZXlKaGMzTmxjblJwYjI1SVlYTm9Jam9pTkRVMlpEQTFaRFpoTTJZME9HRXhOR0ZsWldVeU1HTTRaRFZtTURReU9XUXdOMlUxWW1VelpqRTJPVFZtWTJWak1tUmlZemMxTldOa09XUTJORFE1TlNJc0ltRnpjMlZ5ZEdsdmJsTnBaeUk2SW5kT2NqaElNbVoxVm1GVlkxTlRRV2hYTlV4T1NVUkhPV1pFVm5ZNE9YbHdZVk5GVEhSdGMxVkNSRXBHWWxGWVYyOHZVMHRHU3pkMVNVMXFWamhGUzJSQ0sxY3JVSGhoVmk5UGQzUjJTRlpqTW1SYVJXeFJQVDBpZlEuWklHdDNva19lRmdjVnNwblVQWktLLWxfeVFHMTYxU3FjLUw5Yl91MXg2X2ljU01xeWxGbVMxZXRYWk9Kai1IVnVsRGFzcU1Da2dLMVFMOVI4VlVZQktjSWdFQlRaR0VBMFA3eHNXcGRON1ZNc0VVcjdaaVRCdThISkRpbGZtdmRzNXlUQ0pmLWZ0Z2FrbFBKNmJtVWRDN2ptZDRwaEJGWkNmdEY4QnpaSVdEaVp6eXQ3dk5SWnNrcnEtazJkdVhOTDlNR2ZseEJVbC0xWmk3U2VISlNUNmtNS0V1bXh1R015ZkRzeUJSdkthZlVyWWsxTEZ0QklmcEN1LXJiM2ZLbTNPa1kzSXBVVFZwd3hWNHkwWGlIZ0xtZ1BYZ3VQaV9uRGdVdzhwY2M4STVBVTVpRm5JcXItVzNjeWY5OE01NGZqTXZqSUhJWjB6dzNGS1VhOW1zdThRIn19LHsiaWQiOiJjbGFzc2lmaWNhdGlvbiIsInR5cGUiOiJoYW5kbGluZyIsInNjb3BlIjoicGF5bG9hZCIsImFwcGxpZXNUb1N0YXRlIjoidW5lbmNyeXB0ZWQiLCJzdGF0ZW1lbnQiOnsiZm9ybWF0IjoianNvbitzdGFuYWc1NjM2Iiwic2NoZW1hIjoidXJuOm5hdG86c3RhbmFnOjU2MzY6QToxOmVsZW1lbnRzOmpzb24iLCJ2YWx1ZSI6Iv8ifX1dfQ==", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "manifest-root-Payload-alias-only", + "kind": "manifest", + "status": "required", + "note": "Payload without payload", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"Payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-root-EncryptionInformation-alias-only", + "kind": "manifest", + "status": "required", + "note": "EncryptionInformation without encryptionInformation", + "input": "{\"EncryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-root-SchemaVersion-alias-only", + "kind": "manifest", + "status": "required", + "note": "SchemaVersion without schemaVersion", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"SchemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-root-uppercase-tdf-spec-version-alias-only", + "kind": "manifest", + "status": "required", + "note": "TDF_SPEC_VERSION, a variant of tdf_spec_version", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"TDF_SPEC_VERSION\":\"4.2.2\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-payload-IsEncrypted-alias-only", + "kind": "manifest", + "status": "required", + "note": "IsEncrypted without isEncrypted in payload", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"IsEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-payload-mixed-case-tdf-spec-version-alias-only", + "kind": "manifest", + "status": "required", + "note": "Tdf_Spec_Version, a variant of tdf_spec_version, in payload", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true,\"Tdf_Spec_Version\":\"4.3.0\"},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-encryption-information-Policy-alias-only", + "kind": "manifest", + "status": "required", + "note": "Policy without policy in encryptionInformation", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"Policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-encryption-information-KeyAccess-alias-only", + "kind": "manifest", + "status": "required", + "note": "KeyAccess without keyAccess in encryptionInformation", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"KeyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-key-access-URL-alias-only", + "kind": "manifest", + "status": "required", + "note": "URL without url in a key access object", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"URL\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-key-access-SID-alias-only", + "kind": "manifest", + "status": "required", + "note": "SID, a variant of sid, in a key access object", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\",\"SID\":\"s1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-key-access-kelvin-Kid-alias-only", + "kind": "manifest", + "status": "required", + "note": "Kid spelled with U+212A KELVIN SIGN, which folds to k", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"\u212aid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-policy-binding-Hash-alias-only", + "kind": "manifest", + "status": "required", + "note": "Hash without hash in a policy binding", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"Hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-method-ALGORITHM-alias-only", + "kind": "manifest", + "status": "required", + "note": "ALGORITHM without algorithm in method", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"ALGORITHM\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-integrity-Segments-alias-only", + "kind": "manifest", + "status": "required", + "note": "Segments without segments in integrityInformation", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"Segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-integrity-long-s-segmentHashAlg-alias-only", + "kind": "manifest", + "status": "required", + "note": "segmentHashAlg spelled with U+017F, which folds to s", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"\u017fegmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-root-signature-Sig-alias-only", + "kind": "manifest", + "status": "required", + "note": "Sig without sig in rootSignature", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"Sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-second-segment-Hash-alias-only", + "kind": "manifest", + "status": "required", + "note": "Hash without hash in the second element of segments", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42},{\"Hash\":\"Mb18NW/z3KlpIQu2axQEMg==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-root-unlisted-case-pair", + "kind": "manifest", + "status": "required", + "note": "foo and Foo at the root: unlisted names that are case variants (R2)", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"foo\":1,\"Foo\":2,\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-method-IsStreamable-alone", + "kind": "manifest", + "status": "required", + "note": "IsStreamable, which java-sdk up to v0.7.5 wrote, is exempt from R1 (R4)", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"IsStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-method-isStreamable-and-IsStreamable", + "kind": "manifest", + "status": "required", + "note": "isStreamable and IsStreamable together: R2 still applies", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true,\"IsStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "ambiguous-member" + } + }, + { + "name": "manifest-unlisted-keyAccesses-ok", + "kind": "manifest", + "status": "required", + "note": "keyAccesses is unlisted and not a case variant of keyAccess", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccesses\":[],\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-unlisted-segmentHash-ok", + "kind": "manifest", + "status": "required", + "note": "segmentHash is unlisted and not a case variant of segmentHashAlg", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHash\":\"x\",\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-payload-schemaVersion-ok", + "kind": "manifest", + "status": "required", + "note": "schemaVersion is listed at the root, not in payload, so it is unlisted there", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true,\"schemaVersion\":\"4.3.0\"},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-string-policyBinding-ok", + "kind": "manifest", + "status": "required", + "note": "A string policyBinding, as older writers emitted, is not a checked level", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-case-pair-inside-unlisted-member-ok", + "kind": "manifest", + "status": "required", + "note": "Foo and foo inside an unlisted member's object value are not checked (R3)", + "input": "{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"meta\":{\"Foo\":1,\"foo\":2},\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "ok": true + } + }, + { + "name": "manifest-invalid-utf8-outside-assertions", + "kind": "manifest", + "status": "required", + "note": "The byte 0xFF in payload.mimeType, outside any assertion", + "inputBase64": "eyJlbmNyeXB0aW9uSW5mb3JtYXRpb24iOnsidHlwZSI6InNwbGl0IiwicG9saWN5IjoiZXlKMWRXbGtJam9pTURBd01EQXdNREF0TURBd01DMDBNREF3TFRnd01EQXRNREF3TURBd01EQXdNREF3SWl3aVltOWtlU0k2ZXlKa1lYUmhRWFIwY21saWRYUmxjeUk2VzEwc0ltUnBjM05sYlNJNlcxMTlmUT09Iiwia2V5QWNjZXNzIjpbeyJ0eXBlIjoid3JhcHBlZCIsInVybCI6Imh0dHBzOi8va2FzLmV4YW1wbGUuY29tIiwicHJvdG9jb2wiOiJrYXMiLCJ3cmFwcGVkS2V5IjoiQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBPT0iLCJwb2xpY3lCaW5kaW5nIjp7ImFsZyI6IkhTMjU2IiwiaGFzaCI6IkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9In0sImtpZCI6InIxIn1dLCJtZXRob2QiOnsiYWxnb3JpdGhtIjoiQUVTLTI1Ni1HQ00iLCJpdiI6IiIsImlzU3RyZWFtYWJsZSI6dHJ1ZX0sImludGVncml0eUluZm9ybWF0aW9uIjp7InJvb3RTaWduYXR1cmUiOnsiYWxnIjoiSFMyNTYiLCJzaWciOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBPSJ9LCJzZWdtZW50SGFzaEFsZyI6IkdNQUMiLCJzZWdtZW50U2l6ZURlZmF1bHQiOjIwOTcxNTIsImVuY3J5cHRlZFNlZ21lbnRTaXplRGVmYXVsdCI6MjA5NzE4MCwic2VnbWVudHMiOlt7Imhhc2giOiJ3TnI4SDJmdVZhVWNTU0FoVzVMTklBPT0iLCJzZWdtZW50U2l6ZSI6MTQsImVuY3J5cHRlZFNlZ21lbnRTaXplIjo0Mn1dfX0sInBheWxvYWQiOnsidHlwZSI6InJlZmVyZW5jZSIsInVybCI6IjAucGF5bG9hZCIsInByb3RvY29sIjoiemlwIiwibWltZVR5cGUiOiJ0ZXh0L/9wbGFpbiIsImlzRW5jcnlwdGVkIjp0cnVlfSwic2NoZW1hVmVyc2lvbiI6IjQuMy4wIiwiYXNzZXJ0aW9ucyI6W3siaWQiOiJjbGFzc2lmaWNhdGlvbiIsInR5cGUiOiJoYW5kbGluZyIsInNjb3BlIjoicGF5bG9hZCIsImFwcGxpZXNUb1N0YXRlIjoidW5lbmNyeXB0ZWQiLCJzdGF0ZW1lbnQiOnsiZm9ybWF0IjoianNvbitzdGFuYWc1NjM2Iiwic2NoZW1hIjoidXJuOm5hdG86c3RhbmFnOjU2MzY6QToxOmVsZW1lbnRzOmpzb24iLCJ2YWx1ZSI6IntcIm9jbFwiOntcInBvbFwiOlwiNjJjNzZjNjgtZDczZC00NjI4LThjY2MtNGMxZTE4MTE4YzIyXCIsXCJjbHNcIjpcIlNFQ1JFVFwiLFwiZGNyXCI6XCIyMDI0LTEwLTIxVDIwOjQ3OjM2WlwifX0ifSwiYmluZGluZyI6eyJtZXRob2QiOiJqd3MiLCJzaWduYXR1cmUiOiJleUpoYkdjaU9pSlNVekkxTmlKOS5leUpoYzNObGNuUnBiMjVJWVhOb0lqb2lORFUyWkRBMVpEWmhNMlkwT0dFeE5HRmxaV1V5TUdNNFpEVm1NRFF5T1dRd04yVTFZbVV6WmpFMk9UVm1ZMlZqTW1SaVl6YzFOV05rT1dRMk5EUTVOU0lzSW1GemMyVnlkR2x2YmxOcFp5STZJbmRPY2poSU1tWjFWbUZWWTFOVFFXaFhOVXhPU1VSSE9XWkVWblk0T1hsd1lWTkZUSFJ0YzFWQ1JFcEdZbEZZVjI4dlUwdEdTemQxU1UxcVZqaEZTMlJDSzFjclVIaGhWaTlQZDNSMlNGWmpNbVJhUld4UlBUMGlmUS5aSUd0M29rX2VGZ2NWc3BuVVBaS0stbF95UUcxNjFTcWMtTDliX3UxeDZfaWNTTXF5bEZtUzFldFhaT0pqLUhWdWxEYXNxTUNrZ0sxUUw5UjhWVVlCS2NJZ0VCVFpHRUEwUDd4c1dwZE43Vk1zRVVyN1ppVEJ1OEhKRGlsZm12ZHM1eVRDSmYtZnRnYWtsUEo2Ym1VZEM3am1kNHBoQkZaQ2Z0RjhCelpJV0RpWnp5dDd2TlJac2tycS1rMmR1WE5MOU1HZmx4QlVsLTFaaTdTZUhKU1Q2a01LRXVteHVHTXlmRHN5QlJ2S2FmVXJZazFMRnRCSWZwQ3UtcmIzZkttM09rWTNJcFVUVnB3eFY0eTBYaUhnTG1nUFhndVBpX25EZ1V3OHBjYzhJNUFVNWlGbklxci1XM2N5Zjk4TTU0ZmpNdmpJSElaMHp3M0ZLVWE5bXN1OFEifX1dfQ==", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "manifest-leading-byte-order-mark", + "kind": "manifest", + "status": "required", + "note": "Manifest text starting with U+FEFF", + "input": "\ufeff{\"encryptionInformation\":{\"type\":\"split\",\"policy\":\"eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAwIiwiYm9keSI6eyJkYXRhQXR0cmlidXRlcyI6W10sImRpc3NlbSI6W119fQ==\",\"keyAccess\":[{\"type\":\"wrapped\",\"url\":\"https://kas.example.com\",\"protocol\":\"kas\",\"wrappedKey\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"policyBinding\":{\"alg\":\"HS256\",\"hash\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"kid\":\"r1\"}],\"method\":{\"algorithm\":\"AES-256-GCM\",\"iv\":\"\",\"isStreamable\":true},\"integrityInformation\":{\"rootSignature\":{\"alg\":\"HS256\",\"sig\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"},\"segmentHashAlg\":\"GMAC\",\"segmentSizeDefault\":2097152,\"encryptedSegmentSizeDefault\":2097180,\"segments\":[{\"hash\":\"wNr8H2fuVaUcSSAhW5LNIA==\",\"segmentSize\":14,\"encryptedSegmentSize\":42}]}},\"payload\":{\"type\":\"reference\",\"url\":\"0.payload\",\"protocol\":\"zip\",\"mimeType\":\"application/octet-stream\",\"isEncrypted\":true},\"schemaVersion\":\"4.3.0\",\"assertions\":[{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}]}", + "expect": { + "reason": "invalid-json" + } + }, + { + "name": "binding-jws", + "kind": "binding", + "status": "required", + "note": "method jws with a signature", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "bindingForm": "jws" + } + }, + { + "name": "binding-JWS-uppercase", + "kind": "binding", + "status": "required", + "note": "method JWS, which Java writers emit", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"JWS\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "bindingForm": "jws" + } + }, + { + "name": "binding-method-missing", + "kind": "binding", + "status": "required", + "note": "A missing method means jws", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "bindingForm": "jws" + } + }, + { + "name": "binding-method-mixed-case", + "kind": "binding", + "status": "required", + "note": "Jws is neither jws nor JWS", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"Jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "binding-method-unknown", + "kind": "binding", + "status": "required", + "note": "An unknown method", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"xmldsig\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "binding-method-empty-string", + "kind": "binding", + "status": "required", + "note": "An empty method string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "binding-bdo-object-alone", + "kind": "binding", + "status": "required", + "note": "A STANAG 4778 binding object as an object", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"bdo\":{\"BindingInformation\":{}}}}", + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "binding-bdo-string-alone", + "kind": "binding", + "status": "required", + "note": "A STANAG 4778 binding object as a string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"bdo\":\"PEJpbmRpbmdJbmZvcm1hdGlvbi8+\"}}", + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "binding-bdo-with-method-jws", + "kind": "binding", + "status": "required", + "note": "bdo without signature is a binding object whatever method says", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"bdo\":\"PEJpbmRpbmdJbmZvcm1hdGlvbi8+\"}}", + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "binding-both-forms", + "kind": "binding", + "status": "required", + "note": "signature and bdo together", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\"bdo\":\"PEJpbmRpbmdJbmZvcm1hdGlvbi8+\"}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-empty-object", + "kind": "binding", + "status": "required", + "note": "binding is an empty object", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-method-only", + "kind": "binding", + "status": "required", + "note": "Neither signature nor bdo", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\"}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-missing", + "kind": "binding", + "status": "required", + "note": "No binding member", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-string", + "kind": "binding", + "status": "required", + "note": "binding is a string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-null", + "kind": "binding", + "status": "required", + "note": "binding is null", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":null}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-array", + "kind": "binding", + "status": "required", + "note": "binding is an array", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":[{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}]}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-method-number", + "kind": "binding", + "status": "required", + "note": "method is a number", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":1,\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-method-null", + "kind": "binding", + "status": "required", + "note": "method is null", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":null,\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-signature-number", + "kind": "binding", + "status": "required", + "note": "signature is a number", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":1}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-signature-null", + "kind": "binding", + "status": "required", + "note": "signature is null", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":null}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "binding-signature-json-serialization-object", + "kind": "binding", + "status": "required", + "note": "signature is a JWS JSON serialization object", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":{\"payload\":\"eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ\",\"protected\":\"eyJhbGciOiJSUzI1NiJ9\",\"signature\":\"ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}}", + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-rs256", + "kind": "verify", + "status": "required", + "note": "RS256 binding from the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-rs256-legacy", + "kind": "verify", + "status": "required", + "note": "Legacy encoding: assertionSig carries the hash as hex text", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6Ill6QmtZV1pqTVdZMk4yVmxOVFZoTlRGak5Ea3lNREl4TldJNU1tTmtNakE1WVdKaU5qSTJNVFZpWm1ZeFlXVTRNMlprTmpKbU9HUXpNVEJrTVRJNE9UTXhZbVEzWXpNMU5tWm1NMlJqWVRrMk9USXhNR0ppTmpaaU1UUXdORE15WlRFeFlUSTBPR0l5TXpFNE4ySmlZak0wTWpsbU5XWmtNVGhqTkRkbVpXWTBOVFprTURWa05tRXpaalE0WVRFMFlXVmxaVEl3WXpoa05XWXdOREk1WkRBM1pUVmlaVE5tTVRZNU5XWmpaV015WkdKak56VTFZMlE1WkRZME5EazEifQ.KpOgoTozWGv9epsE4hfmdoax2hq6QUtsfmqJS41KJdEyPoiWMco1fWAojKieIroXHVoIHjV5CtuqllaBGn-G4N9tON5NSZSBqTlSiAlLPFda9Ry_zV1kWi7GxJTg9FS0ANp--2_7mLwv_mxWjqVk02P_U062UciPxog1wF1UGI-qEhJJDyXITpljRpSQoSfCuyy_6DKlIK6aqkiCRT1hys1SoZNBzkfDCroO2d-MkTF3a8b0VHh0eMfn2Nnaz-VEmOZCIpurykYas5qVLFekN6c7Pe2bwt0-wsepVBO-YNl4jFN2eGNXWUAdu2d-B0hAh5Of9mLIolsbKQ7JwuW4xw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "YzBkYWZjMWY2N2VlNTVhNTFjNDkyMDIxNWI5MmNkMjA5YWJiNjI2MTViZmYxYWU4M2ZkNjJmOGQzMTBkMTI4OTMxYmQ3YzM1NmZmM2RjYTk2OTIxMGJiNjZiMTQwNDMyZTExYTI0OGIyMzE4N2JiYjM0MjlmNWZkMThjNDdmZWY=", + "legacy": true, + "expect": { + "ok": true + } + }, + { + "name": "verify-rs256-method-JWS", + "kind": "verify", + "status": "required", + "note": "method JWS, which Java writers emit", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"JWS\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-rs256-method-missing", + "kind": "verify", + "status": "required", + "note": "A missing method means jws", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-hs256-configured", + "kind": "verify", + "status": "required", + "note": "HS256 binding from a configured shared secret", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.aVPkGyrHe5U-2tzQoNI-IJMFxWkPFEWQY4aljyOFXZ8\"}}", + "selectedKey": "hs256-configured", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-hs256-payload-key", + "kind": "verify", + "status": "required", + "note": "HS256 binding from the payload key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Gpdu2Q1de4ga4DEVRvXS2fhwFCk-SasHnrfNMiwtpxg\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-hs256-payload-key-legacy", + "kind": "verify", + "status": "required", + "note": "Legacy-encoded HS256 binding from the payload key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6Ill6QmtZV1pqTVdZMk4yVmxOVFZoTlRGak5Ea3lNREl4TldJNU1tTmtNakE1WVdKaU5qSTJNVFZpWm1ZeFlXVTRNMlprTmpKbU9HUXpNVEJrTVRJNE9UTXhZbVEzWXpNMU5tWm1NMlJqWVRrMk9USXhNR0ppTmpaaU1UUXdORE15WlRFeFlUSTBPR0l5TXpFNE4ySmlZak0wTWpsbU5XWmtNVGhqTkRkbVpXWTBOVFprTURWa05tRXpaalE0WVRFMFlXVmxaVEl3WXpoa05XWXdOREk1WkRBM1pUVmlaVE5tTVRZNU5XWmpaV015WkdKak56VTFZMlE1WkRZME5EazEifQ.E3HxtlmdDnAKWwmdvqPgUv6Xds-SRXahDqQEFq_o4wQ\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "YzBkYWZjMWY2N2VlNTVhNTFjNDkyMDIxNWI5MmNkMjA5YWJiNjI2MTViZmYxYWU4M2ZkNjJmOGQzMTBkMTI4OTMxYmQ3YzM1NmZmM2RjYTk2OTIxMGJiNjZiMTQwNDMyZTExYTI0OGIyMzE4N2JiYjM0MjlmNWZkMThjNDdmZWY=", + "legacy": true, + "expect": { + "ok": true + } + }, + { + "name": "verify-header-jwk-selected-key", + "kind": "verify", + "status": "required", + "note": "Header jwk of the selected key, with kid, use, and alg members", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdRIiwiZSI6IkFRQUIiLCJraWQiOiJzaWduZXItMSIsInVzZSI6InNpZyIsImFsZyI6IlJTMjU2In19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.k9vsCwN6F002GvuroKvaQOtH8iejF3lXTx6OhwCixoQ5NTfT92sdtoh2_I_6yZLsv91q0zoV_PbQSGkDfTkIhLF55c6wc37V8ZDZX0zkaf3BXKtXhKY5Bw0IC4Lptxqv619Z4TRssRCbNZLgiYoVBQdz5QFcWo5apLdiucUGH3cQcUi9RI3k1MLfXWTsMC3OpqDptQ3kcgTGmP-cYtqwLWyrVQdNh1RCNbrccQxJ6LW4bOdHfqua31DfYsnrp76SZgC5iNnM316LU9h97XAiMwKZQscaKln1zOM7po5vy64WxnRiSF3Gl8MbYBnr7XRu94KwOIZSKppF2nq95DTtqg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-header-x5c-selected-key", + "kind": "verify", + "status": "required", + "note": "Self-signed x5c whose leaf is the selected key; no chain is validated", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMGpDQ0FicWdBd0lCQWdJRVZTQUFBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWlNU0F3SGdZRFZRUUREQmRFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNqQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNJeElEQWVCZ05WQkFNTUYwUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2MybG5ibVZ5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SUxFOElIQncrTm5UK09IWC9aWmNITnlZTldzdG5xQU4yVjFvUmh1ZVNpeG5aS1d1UEFqdFdBenVxREF3QlJEcW1qcVNjd0dJeVc2ZnJ5VzNxbk5mWFNHUWhUYnYxdnp6UkVlaXdXQUZxdUVzRVpyY2VLQVFHaGtDaXM4YVZlOUNCWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpL1I0UXRmSDVia0M4NXVvTEFmZDk3YWJMbC9aTmRubG5MY0VpVmFxU1FXSnEvZmR5ZnpMTk9xWGRTVnBrRDU4YlBORXNsaWNqbFI3WjZIL1lOZG5qbHF1NURIa29odmV6TzhNMXVCWkpMMXVGeFJ3QU1vVW4xL3R0OGRxVUxGMGk1N1FJREFRQUJveEF3RGpBTUJnTlZIUk1CQWY4RUFqQUFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJORUNNLzBnTWFUbGhyMmIyNVczaG11dUttZGtYTkwzYW9pMm8wQ2Z3cmhudFhnNjFtVFNubW9hams1enNTVmsxcHF0b2pxbmpwVkY2dzQrSXNuWW40VW5CdlZFMHZwK0VJdStDa2lSRC8zTGwxZUF4QnNTMG9CRlNJQkQ1cm9sN0NNZHF6YTlGY0FMbnREazFzWWRxdTJxb3JmNzltc09zYzdJZncrQ1V0ZVE1QVU2cExEMlhyd1FGWGRTNjlTNHg4Rm9uS3Y1aDcwSFdwVTI4bjQranRRVEhRNVE1UEYxWWVTS3ljLzdZZUM4b2hJbDZqaEtIdllFd3ZUNVJYSG1HeWw4Z25uQXRydFFDL2lMRHRwRmE1NnRmYkNmU1JnZTIxMWpCRExjdWJxeHFpbTVHd3o5Z1dvZTJVdXI2QzJlWjEwQXVGRUp6VlhFUEQ3Qkl4SGdlbSJdfQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Ur4OcmsxCCiNFd3Tu01HbomnpL27dcHcY6_ccAQ2FEFJIVB-l6_Vd8ziupqEubdJaOXpJKAbL49E7S_JQtnFJTGOu7pKPZgUJJ_V8U0wNmoNv9SCFU0PJuB0xgdynCmQhwkUZCfAo181hj2Tng2Qk47nlQazGtoVLxau7DI4ljEsF0Z9714NhXRgchAQ706_A07fOfaBW3W2srz4tFnCkmwPNIpDvBXWx9aosRLlsRwcrIP_NUZ_E1ev8XURYiPW1stgQLVfOjgWzlvgXe0hK4YfX7BtbCB09SWbS5b1-2avmVVjnTwm2kusYpiq6bQ9sTLv5hELBOlh4oZYipM04g\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-header-jwk-and-x5c-selected-key", + "kind": "verify", + "status": "required", + "note": "jwk and x5c of the selected key with method JWS, as Java writers emit", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"JWS\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdRIiwiZSI6IkFRQUIifSwieDVjIjpbIk1JSUMwakNDQWJxZ0F3SUJBZ0lFVlNBQUFUQU5CZ2txaGtpRzl3MEJBUXNGQURBaU1TQXdIZ1lEVlFRRERCZEVVMUJZTFRVMU1qQWdkbVZqZEc5eUlITnBaMjVsY2pBZUZ3MHlOakF4TURFd01EQXdNREJhRncwek5qQXhNREV3TURBd01EQmFNQ0l4SURBZUJnTlZCQU1NRjBSVFVGZ3ROVFV5TUNCMlpXTjBiM0lnYzJsbmJtVnlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWsranlWVVAxZk0yT3grUGV0M3RJTEU4SUhCdytOblQrT0hYL1paY0hOeVlOV3N0bnFBTjJWMW9SaHVlU2l4blpLV3VQQWp0V0F6dXFEQXdCUkRxbWpxU2N3R0l5VzZmcnlXM3FuTmZYU0dRaFRidjF2enpSRWVpd1dBRnF1RXNFWnJjZUtBUUdoa0NpczhhVmU5Q0JZd0FqaUhkV1NpUUZWT1dScWJOcUdNblk1STllbmJ1OXVoV1RJbmxLcTJJZk0yb2hMRnliWmtnUU90MFJkRWkvUjRRdGZINWJrQzg1dW9MQWZkOTdhYkxsL1pOZG5sbkxjRWlWYXFTUVdKcS9mZHlmekxOT3FYZFNWcGtENThiUE5Fc2xpY2psUjdaNkgvWU5kbmpscXU1REhrb2h2ZXpPOE0xdUJaSkwxdUZ4UndBTW9VbjEvdHQ4ZHFVTEYwaTU3UUlEQVFBQm94QXdEakFNQmdOVkhSTUJBZjhFQWpBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQk5FQ00vMGdNYVRsaHIyYjI1VzNobXV1S21ka1hOTDNhb2kybzBDZndyaG50WGc2MW1UU25tb2FqazV6c1NWazFwcXRvanFuanBWRjZ3NCtJc25ZbjRVbkJ2VkUwdnArRUl1K0NraVJELzNMbDFlQXhCc1Mwb0JGU0lCRDVyb2w3Q01kcXphOUZjQUxudERrMXNZZHF1MnFvcmY3OW1zT3NjN0lmdytDVXRlUTVBVTZwTEQyWHJ3UUZYZFM2OVM0eDhGb25LdjVoNzBIV3BVMjhuNCtqdFFUSFE1UTVQRjFZZVNLeWMvN1llQzhvaElsNmpoS0h2WUV3dlQ1UlhIbUd5bDhnbm5BdHJ0UUMvaUxEdHBGYTU2dGZiQ2ZTUmdlMjExakJETGN1YnF4cWltNUd3ejlnV29lMlV1cjZDMmVaMTBBdUZFSnpWWEVQRDdCSXhIZ2VtIl19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.UBtOzL65KfQBC_YqAFml5h9THG5m9-Bvj5EyvgSrVQt2RKmQkUcBTwEJQGzASSUyGTi8-3uEDTd1sWwzvpHIIvsTBuhGdGW1pG1Qnzj5OLt6KRJ_rHKu65Zb2uYCBd27G5t04hmD9nSvIEtMAMwgXmZaNuSsuMR_6nl5L9krVL3IHeAWEbcI5I_L0olhdXkPcS7_EF0esvtW6FszenbohWZUiL8S1z8ZsT6Hn54Uu9l5UcpEkdSOanBdxUVHittK8Hhkj0kz_Ghi4mDjZMdyM1kTczexWgTrIe1mSdycTOsw7kfzp2vakyl96sBjR08MdcWwnR5-KXu231iQe_Koww\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-header-x5c-extra-certificate-ignored", + "kind": "verify", + "status": "required", + "note": "Certificates after the x5c leaf are ignored", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMGpDQ0FicWdBd0lCQWdJRVZTQUFBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWlNU0F3SGdZRFZRUUREQmRFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNqQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNJeElEQWVCZ05WQkFNTUYwUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2MybG5ibVZ5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SUxFOElIQncrTm5UK09IWC9aWmNITnlZTldzdG5xQU4yVjFvUmh1ZVNpeG5aS1d1UEFqdFdBenVxREF3QlJEcW1qcVNjd0dJeVc2ZnJ5VzNxbk5mWFNHUWhUYnYxdnp6UkVlaXdXQUZxdUVzRVpyY2VLQVFHaGtDaXM4YVZlOUNCWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpL1I0UXRmSDVia0M4NXVvTEFmZDk3YWJMbC9aTmRubG5MY0VpVmFxU1FXSnEvZmR5ZnpMTk9xWGRTVnBrRDU4YlBORXNsaWNqbFI3WjZIL1lOZG5qbHF1NURIa29odmV6TzhNMXVCWkpMMXVGeFJ3QU1vVW4xL3R0OGRxVUxGMGk1N1FJREFRQUJveEF3RGpBTUJnTlZIUk1CQWY4RUFqQUFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJORUNNLzBnTWFUbGhyMmIyNVczaG11dUttZGtYTkwzYW9pMm8wQ2Z3cmhudFhnNjFtVFNubW9hams1enNTVmsxcHF0b2pxbmpwVkY2dzQrSXNuWW40VW5CdlZFMHZwK0VJdStDa2lSRC8zTGwxZUF4QnNTMG9CRlNJQkQ1cm9sN0NNZHF6YTlGY0FMbnREazFzWWRxdTJxb3JmNzltc09zYzdJZncrQ1V0ZVE1QVU2cExEMlhyd1FGWGRTNjlTNHg4Rm9uS3Y1aDcwSFdwVTI4bjQranRRVEhRNVE1UEYxWWVTS3ljLzdZZUM4b2hJbDZqaEtIdllFd3ZUNVJYSG1HeWw4Z25uQXRydFFDL2lMRHRwRmE1NnRmYkNmU1JnZTIxMWpCRExjdWJxeHFpbTVHd3o5Z1dvZTJVdXI2QzJlWjEwQXVGRUp6VlhFUEQ3Qkl4SGdlbSIsIk1JSUMyRENDQWNDZ0F3SUJBZ0lFVlNBQUFqQU5CZ2txaGtpRzl3MEJBUXNGQURBbE1TTXdJUVlEVlFRRERCcEVVMUJZTFRVMU1qQWdkbVZqZEc5eUlHOTBhR1Z5SUd0bGVUQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNVeEl6QWhCZ05WQkFNTUdrUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2IzUm9aWElnYTJWNU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBeXorZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1IrcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTcrTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eEwvQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MCtrZ3hhOW9qWUJRWXpaSmZFSS9iOFZHdzRDMDVWN25MMVNPb1oxL1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GL2tialI3WDBVT05UMEpHWkJ0cHdudjhKK3gvQkd3SURBUUFCb3hBd0RqQU1CZ05WSFJNQkFmOEVBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDaEUxYVcycUt3OVRGVllrdnpVVjJpTndudHhodjVCVDlZRlByRENTS0xuZDRYYVJIWHovNHd2YVRvYUs4WHp4YktwSlBEN1h3ZFJzWjhDN2EzTDFDd1NEQjRnbWpsdjAwbktTTXZ6UnNhNzJSbXhoaHRhckt5WkQxYXUxWGZiZUwzdVJXdVB1RENkK2VjSFZ2dEhDZ0JaZjc5WFpSLzBNeW0vTkxsbzdvWDFtS25FdzJkVzFQZVlHQ0ZVN3dWaFVzaTVlSzBzUEFRMW9nd25DUEl5REZsbld4RWdVQ1VkQ3psa2FBMitGRUt1QmlzQ3J0SkRMYXcyYWlUUjJnMk1WbjVQMlZVQ21ucVFZa2gxN240RG1MbzhNTVR1VWlSOUVrVjI2d1AwdEMxaG90Tm1URndNeTdOanBVT0c2T0ZCeTd1RTBlUVhKdUJJYmE3SHZXZFl3YmYiXX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.JNqqZhs_Y5mOcpKxT0MiRIY-jNuaCRFA94Z5f4BmrSJWcUO0iyd8eCFazbFvybrbR_LWocMqNQjS4b-j9gtObcRuWdtsS3NwBnCk0qoCVaJilDPFT2giWpFRBi6zo6vhvNsCqA3WzKJQCyZUFBRvBOBbxR97UP01fHpAWJ3F2zuPW6P6zmn5WJ9ZnT01s5xqTOGH6yJzptoRWSn-IR9oBhDF5QipMNkd6NpsIELZDZLlgsVvzjMgMBHLGPuwC_j6nr3czMXv37PSZuqoXNhsX4k8c7c3MSP2b3jftCrKFxJugkOJaIw9ob-aBfo770n1QntDj9w_wKydVXxoeVpzmQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-header-kid-ignored", + "kind": "verify", + "status": "required", + "note": "A kid naming some other key does not affect verification", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6Im81WWV5NHBYWXI3WnNkQ2E4STlKTy1ZWWdvRWt1V1BWVDZvc1BQMno0M0kifQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.d5mgxO73rlgsLs2Cu1yUPyCixJ0JTn17JEfIx7rtdagACI0a1FIi9mxS0wWL9KRH8ST_4qJ0fw3b2_ABnVVnWnp8rpxOJoVcBPMaJJZIqjqU6W5uMaSx8cdCByPtK_zO332ugDogeEUPUYpZKmSfPdxVo8kZJvUWAUxR3odcZLYE57CnHkj_Jb9KsUDSoUBrrXmb9lJwICGMjFRgE1HEdcIII7y35Lbxr-1z44SxGl0V-3xKO8aXMZ4dd74Qb0mu-N-Gt45Rw53OMuspZf48m7OrsE4kasq9XKcER0esqd9wkZpo4WqY8nvWUcI9TeN44eFIPQdYC7MCkt1m9SmJtw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-header-jku-x5u-not-fetched", + "kind": "verify", + "status": "required", + "note": "jku and x5u point at unresolvable URLs and must not be fetched", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vandrcy5pbnZhbGlkL2tleXMuanNvbiIsIng1dSI6Imh0dHBzOi8vY2VydGlmaWNhdGVzLmludmFsaWQvc2lnbmVyLnBlbSJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.jhHlkXpmPy3SXW-1WLNEGMB8PVAK4xqXbv4dfrWnBfEl-qbXoMLWxuBqmI1MafR1oEJYNUrSNyuXf28IXcrekF4EZmt6X5D51p4Xjjc6oK3IyHcqa9dVkJrWRnMfij1O-2lUhOEqGTaIaTr9lAJdD4BzLcUJNiX7rqyU3aA9lSoLIKlyyyEbPsado7N3F5bxDV3umSUZcjHy7SNjeuyoDBFrrybPd-N8AaN69WfSGPL41F3EYtRazxliSTxa5r053zOQ9R5_oIPNQ2VQGGahZIGbcnKuhcxlM2endWCO3LWiXji07Z4bN9HdnUIdEbmgtFwA-D71DaOGNcytUWqfxA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-extra-claim-ignored", + "kind": "verify", + "status": "required", + "note": "Claims other than assertionHash and assertionSig are ignored", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0iLCJpYXQiOjE3NjcyMjU2MDB9.YbSV_-m64jr-Ktzywkf-giBXRFAASJ7RdvnLUV4tQBw9LqPJXchqlUWtpM8nwuc8mqEZmOBEDHyf_w8ueDFYgCRkpGsY1Nh3Tbk_3IRlaFN8EwidFxGpkS4Q_KW-9B5x8Pk_J1sQR0lu43Ehk4XKWEfo0uLa-rGbGvTTYH2h7juuQyOB65FwY0I5PwyIyXDDWhxzSAzgrKylaSrD3NIUGLLAuOapweNiInoJaXXu_ao93I_I-TrVg4BDh3zX4yJQe6o2LD3UFixFvpMFc-erJfr4Zor-hCDF8Y7XbvsL0nEUsBbx077QVchES7Wel4WX7QPgR-vYXMnJrSTEoMEffw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-unmodelled-members-signed", + "kind": "verify", + "status": "required", + "note": "Unmodelled top-level and statement members, hashed as received", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"encoding\":\"utf-8\"},\"extensions\":{\"hash\":\"deadbeef\",\"note\":\"coi\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiYWUxNjRiM2UzMjk1NjNlODg3NDljNmJjYzg4Y2RjZmVmNDI0ZGFkNWVlNWY5Mjg1OWI4MDdmZTZjNDVhZDMwYyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREt1RmtzK01wVmo2SWRKeHJ6SWpOeis5Q1RhMWU1ZmtvV2JnSC9teEZyVERBPT0ifQ.DCCi8IVN5LcDgANVkeAptyL3_rlnc6wi-9ashp_do4i4t-8ZbGPTWt8Ddc3sgrAn8nCK2OZrb3_mszo1ms9gP2A1tHlbNvAM6Vtnk-bj0kReVAe6eBjcHaMQADB4dZ-eB8O-7z2nTQ6zfTTLPWOkdjYolePQIaZdvJvBF1gv23CdwkV_fBVIP4zKFxofhLPBeYN7tmwPyafMuyJqgN9jLsdHcEY4OQG-S_fzABQxZmifEd-4QAJEDHKYwpD6jl16KclQ8Wx9FzJTqhq2Nd4v3E3RfheeyLACgOAlXp1Sqna_V2KtVMZ6TUqMK2QCi-6TBKvuY9_p0vhRCWMDElVaKg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-acp240-object-value", + "kind": "verify", + "status": "required", + "note": "ACP 240 object-valued label with a JWS binding", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-statement-hash-member-signed", + "kind": "verify", + "status": "required", + "note": "statement.hash covered by the hash, as corrected writers sign it", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"hash\":\"456d05d6a3f48a14aeee20c8d5f0429d07e5be3f1695fcec2dbc755cd9d64495\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNTM4MmU1NWM0ZmY5YzlmMmNhZGY3NDhiNGQwYjRjMjJmMmYzM2VmMTFlY2YyY2VjZjVlODhhM2ZhMTFlMjk5ZiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpUZ3VWY1Qvbko4c3JmZEl0TkMwd2k4dk0rOFI3UExPejE2SW8vb1I0cG53PT0ifQ.XR_fvNo2Zwepzk__dcEHYavkV9TkSTxtBr07BCvASkZD53MyEkcBqj-0A6oldNXGdgHVDbGSJ_oCuwovO1iCELUDNge1mFtrrKyd5xcI-rwn64Pu4LlArTuT5aI1SFyoXq45Cxhk_YWkYh_c2d_SN8JlyLBgRDsaqbeEfdmawbSgor12yrgUCnrP0p38wUv9fEVPNQ40ZrM7WzCavVDndbZG6nice9_pHicZtsADQsaohAJDIULRxjoNEJk1at_918k4ocJ2ehQhm12ndkAQ44IAGJw3iaCPwAsP5P3npDGVhggY1fyabo3-Nxm9vj6rPOOA613fzcBSNB9ZbWWHQw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-binding-bdo", + "kind": "verify", + "status": "required", + "note": "A STANAG 4778 binding object", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"bdo\":\"PEJpbmRpbmdJbmZvcm1hdGlvbi8+\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "verify-binding-unknown-method", + "kind": "verify", + "status": "required", + "note": "An unknown method fails even with a valid signature", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"Jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-binding" + } + }, + { + "name": "verify-binding-missing", + "kind": "verify", + "status": "required", + "note": "No binding member", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-binding-both-forms", + "kind": "verify", + "status": "required", + "note": "signature and bdo together", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\",\"bdo\":\"PEJpbmRpbmdJbmZvcm1hdGlvbi8+\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-empty", + "kind": "verify", + "status": "required", + "note": "An empty signature string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-two-parts", + "kind": "verify", + "status": "required", + "note": "A compact JWS without its signature part", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-four-parts", + "kind": "verify", + "status": "required", + "note": "A compact JWS with an extra part", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-json-serialization", + "kind": "verify", + "status": "required", + "note": "The JWS JSON serialization in a string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"{\\\"payload\\\": \\\"eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ\\\", \\\"protected\\\": \\\"eyJhbGciOiJSUzI1NiJ9\\\", \\\"signature\\\": \\\"ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\\\"}\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-header-not-json", + "kind": "verify", + "status": "required", + "note": "A protected header that is not JSON", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"UlMyNTY.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.h8IIZ7KOQNyvIANABEzSZKqLUA5YWr2dYHHakzCMpXKOrVrupFsUJdMwLqvTCiRlcG6YUTd1w9g1osfCyjFamiCoGxe-R4WAWEduOH91lsNiAV050Bnb-89jNKslBRcCmrgymcomLnzVKMMNCl2CtXtSjlhIW5N5l_galSToF5QFyt7I37T7OGWlP7y8NtrFh8fL1N7lXyT5YDu74s4cyi_sy5hjwfQdPdfzOp2l7XNSL3rmGo8nNWyUmsTxmP4vSPf-4XQgdafRDvWbxcpX4eT6-hhZzm1899Olt9w6VXscXWQXH1ZcM_uwzLqE1olF3kgXVK4E4IxOgT1v5uNSww\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-header-json-array", + "kind": "verify", + "status": "required", + "note": "A protected header that is a JSON array", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"WyJSUzI1NiJd.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.NgykyYevK2V4gKooVd690a1uuIKdYKA64XyFhnQiqn7IiU6AKNqNcr8zJ-EYVZ_PYGfmUvnvaSxhB9KgDQWqX7Wu51BbpBEQkmHViIe7JoRqiRu6useyq7AlwVWaPTY34CKvsPcWrDxcdDVuL56hCrY5gOaDwpVjz9-vJUtITNnOWZQc7YbWk8fmQ_G8HnZqFfjRGjTUsRLG5Kvd2A1455EPkeDuUzKDMRdSzBBh7zaOFC6gw8qxVAblOubhH1YYpk2DVhQdZJIHz6_Dlvre6Y2mEiBpn18x8BTKBTodsmL7fOUiBna5yzkeRjoO-L6BvDXKGWsHkayKX5G3ddvRVA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-payload-not-json", + "kind": "verify", + "status": "required", + "note": "A validly signed payload that is not JSON", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.bm90IGpzb24.ZVXgX31Z40RLfWIDzquE0XK_S49LNVNB0B3ehBRU0HRQkuS16-iK5zmPPNvoFDzXqMFswpogH0nRcWZN8PqP2YfajOe5wjdiCJCguw6K7fPlsQmllsms0ElI0ZjqNRzpVRTGcScqQCZD_y7-wcX6gyoDKQIWNAPzxBcBNL61Td3dFrptT4wokVML8Rh7Wb1yGxC33gNAEYUWNynfJuGOr7o5DFhQpvyyZriS7Co6gatA3SuEl75yp2uQS4B-dIGVgnFduBcccpyxRDiyDJh0Y-FHDnF9urep36URNBkyDsHlx9Bd86PtHsPJLdsB-QdWtKnTkzn2DF-bcpS4I7lBAA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-payload-json-string", + "kind": "verify", + "status": "required", + "note": "A validly signed payload that is a JSON string", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.ImNsYWltcyI.b5YoOd4gpJWXIw0HF5gWqD_DQjzwXZv7r9-NlgDCHCRNr3MJlWlPvdIYUB-yG8Fpzcus12Abp7Ka3svLEaznvwB6f8FoJce0tB2xr6oF-XUhEBCwuLN6CQ_ONeZqB5BOfzNHaORmfeUZamnIy5xoZsKWZ0yHiLbrThhe8_z9m5DbgXdzZH0n8wjTg6dU1-Y5XRfyaSF6u6R3u7fJD_U-MZ0h7TmgJR9mw7a6ysCuSHaqKf465pwVXvI0OxuVtIaDcAjY9zJdvJLOn6dYoHl0bwiUlThjhtlpQi8tJf1-wVBbCF2qW6RhmE7QRP0KDDl9EfS1OpKHgOGXHCbOcQYF8w\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-crit-header", + "kind": "verify", + "status": "required", + "note": "A crit header, which this version never accepts", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImNyaXQiOlsieC12ZWN0b3IiXSwieC12ZWN0b3IiOnRydWV9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.V7miNpKNtvwAZZZotlJAC8JOrNpYYHqynn8pmYeJfMpGuPcHDdM5GlPP8HovrIj8bzcMaeSfZiRqZPOB0Sh_eVR6uQfJMrMn62Z12bJE7mzkXR5WRECgZzUmZrRxZY5fa-bnRoXOIPdLeDDvu0CSMYUTEufjN_8gm2xwNMeOEEKkPPW0es_A6q8uYiE-zvZjNoROgXPfYnpQ7yNkT7Mz3p4coh6GFcz7WWk1fqWHIqEBoSBedyBgRkYS5Bn2GQ5ej5BatUxLXxj8ECqQ3rkE04WNdUQpY7u1iHGb59SVyGOK9B0BsQwfjqT4ttarmgR_R2TYLg5t3i8Rux_VvzgRgQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-alg-none", + "kind": "verify", + "status": "required", + "note": "alg none against an RS256 key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJub25lIn0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-algorithm" + } + }, + { + "name": "verify-alg-none-payload-key", + "kind": "verify", + "status": "required", + "note": "alg none against the payload key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJub25lIn0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-algorithm" + } + }, + { + "name": "verify-alg-missing", + "kind": "verify", + "status": "required", + "note": "No alg header", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJ0eXAiOiJKV1QifQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.FeCtcVqJP38vDWdoo-baIMNZn-2r6wxdCxiWeySBEeLe8JzIriadmYVn8oNUjMb4mK_kDKEm2IIWHqKuzAfHirGNcra13nUY_BqT478tklO9g3wRbISVpxCS3ypZY51z_hEZfpneZlh2Sm2LhRNJWKyKjwRi_N8fO0HVfNCbwEvFBTfdK8gm0KnqR-ozwvhwDrpyZFtu9wx7JxiWq6lUAe5inzwYuQMy5x9dj5DDZUZ7olLkU9-YeUhRh-jD9_nGZpFk_8y4h_FTzCQjmOqajxpN4dlaZBszHE5QQAYU3ha17na7QDZYU84dhPRLh2SLfHr8Gjb0LqfbR8TYkINb4w\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-algorithm" + } + }, + { + "name": "verify-alg-not-string", + "kind": "verify", + "status": "required", + "note": "alg is a number", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOjI1Nn0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.cg73xqqzIoLLsBnwMRNzVigWNKBTL79qB1weYF74y7Vyt3SqZTTsrzXvTdI7NJXYpl4h1eyedUT84U0-rZWchwZI-p6Zbq8Xm7PnRmfRb76Y6JB3MMatfUXwDyuU_v5VzrMuS2i9lTFHIsrOoEHR8L98Gt5a0hGieHfPzBvPlh7wILZuf-0LsXfYFz2OZAiv2ftQdYOKefAXP2p2rfhRNbNo0w6t7xuJAYjxU_V1uSxCIol2SxUnRw-fh5mgBno2X1FI7FhJo6n5iVx30qU0VAlGz9-6MnOILIoC2jmHK986JUwswuRKUafFxFz7xtBfbBN16rgQEiCxbw54bx9Fmg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-algorithm" + } + }, + { + "name": "verify-alg-HS512", + "kind": "verify", + "status": "required", + "note": "HS512 is not a supported algorithm", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzUxMiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ._F806Au944fbG5avxuqnu8YDdHnJDxBVtJWaN5V-ft8Mcy1FFk2zqTQX7ou2aS9D0uSg91tVMJFCkFcfRJqrFQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-algorithm" + } + }, + { + "name": "verify-alg-lowercase-rs256", + "kind": "verify", + "status": "required", + "note": "Algorithm names are case-sensitive: rs256 is not RS256", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJyczI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.azSlRB9fi822PwBslk5nD7nzuGB_UyQSYXqrX0IwX6LsvkenXfaMb9ys0L_oiOOpWiv2OeikehVnSkbx-Q8_H2E4o34aqlKizWexeEYTLeUYoxcDmuuKwR9B-EtO9lvZWpUghcJyU38rgVSBitDLa43wF9z-OCIdhzpPoUD2S_ti1CzSXe6WyknwUWzKQTh_MdVItgElLf1uE5SORdbe2BRVGXam9bLCfoDP6a6waZ9yOihX81LcrWTkeJNqOxn8CHwIiwlr24gS7eQTw_vJxZtsAF2RLWcXnFY2ej5gNlqmcfCRZvp_9FF52xspTgP_st3esyZb8-xwslpwKjEFeA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "unsupported-algorithm" + } + }, + { + "name": "verify-hs256-mac-with-rsa-public-key-pem", + "kind": "verify", + "status": "required", + "note": "HS256 keyed with the selected RSA public key PEM, against that RS256 key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.RszhBRIX0Hg8XgKc78Tq0XqDUSZXXwkdvTfAMUliPWg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "algorithm-mismatch" + } + }, + { + "name": "verify-rs256-against-hs256-configured", + "kind": "verify", + "status": "required", + "note": "An RS256 binding against a configured HS256 secret", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "hs256-configured", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "algorithm-mismatch" + } + }, + { + "name": "verify-rs256-jwk-against-payload-key", + "kind": "verify", + "status": "required", + "note": "An RS256 binding with a header jwk of its own key, against the payload key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoieXotZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1ItcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTctTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eExfQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MC1rZ3hhOW9qWUJRWXpaSmZFSV9iOFZHdzRDMDVWN25MMVNPb1oxX1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GX2tialI3WDBVT05UMEpHWkJ0cHdudjhKLXhfQkd3IiwiZSI6IkFRQUIifX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Q6ikRt2uZdbVXQJMVn0W1qQJTdOwRr5lxb0A2nDPkzosLEa1DauzST0eMmv5XT6cZkNZlWcO9dkMDEe3M9uKeikuj0jVqdjxjzWbm5qoD6aLY57A3hXLcZfN1F5_UouVHe8dYFnphqShBOXQwSAgJ69bFcrCp2dk61su8Oxlz_fVvIJDE9nui6c_zbBp09ORF8JSRiQF7Q99jvQcsMAY6fBAeXFr64tT9Gtv8lZuCX7XveDIsOXQYbsyrzMxA24MqwA1mV4LzA_kHayDJ1WaFjDzHOAgUjzt1HltVD4iYltz6k05v3yT-iZR0X8eqX9yxC78tVa80-pFw4VKDGGGQA\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "algorithm-mismatch" + } + }, + { + "name": "verify-jwk-other-key", + "kind": "verify", + "status": "required", + "note": "Signed by another key whose jwk is in the header", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoieXotZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1ItcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTctTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eExfQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MC1rZ3hhOW9qWUJRWXpaSmZFSV9iOFZHdzRDMDVWN25MMVNPb1oxX1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GX2tialI3WDBVT05UMEpHWkJ0cHdudjhKLXhfQkd3IiwiZSI6IkFRQUIifX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Q6ikRt2uZdbVXQJMVn0W1qQJTdOwRr5lxb0A2nDPkzosLEa1DauzST0eMmv5XT6cZkNZlWcO9dkMDEe3M9uKeikuj0jVqdjxjzWbm5qoD6aLY57A3hXLcZfN1F5_UouVHe8dYFnphqShBOXQwSAgJ69bFcrCp2dk61su8Oxlz_fVvIJDE9nui6c_zbBp09ORF8JSRiQF7Q99jvQcsMAY6fBAeXFr64tT9Gtv8lZuCX7XveDIsOXQYbsyrzMxA24MqwA1mV4LzA_kHayDJ1WaFjDzHOAgUjzt1HltVD4iYltz6k05v3yT-iZR0X8eqX9yxC78tVa80-pFw4VKDGGGQA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signer-not-permitted" + } + }, + { + "name": "verify-x5c-other-key", + "kind": "verify", + "status": "required", + "note": "Signed by another key whose self-signed x5c is in the header", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMkRDQ0FjQ2dBd0lCQWdJRVZTQUFBakFOQmdrcWhraUc5dzBCQVFzRkFEQWxNU013SVFZRFZRUUREQnBFVTFCWUxUVTFNakFnZG1WamRHOXlJRzkwYUdWeUlHdGxlVEFlRncweU5qQXhNREV3TURBd01EQmFGdzB6TmpBeE1ERXdNREF3TURCYU1DVXhJekFoQmdOVkJBTU1Ha1JUVUZndE5UVXlNQ0IyWldOMGIzSWdiM1JvWlhJZ2EyVjVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXl6K2REajhmRWczMk5RN2FZYkhVa1pzUTFUYk1lWlZINkl5YVN4RWVFUEVvNkRrNjQzSGtsam1aeHI3dU94bDFTVTZhUG5VYlYwU2lMaXN5aGpBdkRPdldSSFgxcTdSK3BDcmhQcDNsb2ZJZUtoZGU4dTZhZTM4djF0S3FDUU5YNnNUSkxSTVphaDNNbGRUQ3JjN01wcTU2aXU3K05LRkhEV2lQZFJyTmtDZEEwU0NqRlp0ZjJrVmZZcmF2NXhML0FzOXQ5T0ZtZ2tqcTNPdmZPejZSdUZOcnBOaTZ3Y2t1MGlNdzAra2d4YTlvallCUVl6WkpmRUkvYjhWR3c0QzA1VjduTDFTT29aMS9TOFRGTUZjcEdvUW1hRnl1MVVUdEdLNE9ObGl5Z0p1bUdXS2Z1a0tQTjRvRi9rYmpSN1gwVU9OVDBKR1pCdHB3bnY4Sit4L0JHd0lEQVFBQm94QXdEakFNQmdOVkhSTUJBZjhFQWpBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ2hFMWFXMnFLdzlURlZZa3Z6VVYyaU53bnR4aHY1QlQ5WUZQckRDU0tMbmQ0WGFSSFh6LzR3dmFUb2FLOFh6eGJLcEpQRDdYd2RSc1o4QzdhM0wxQ3dTREI0Z21qbHYwMG5LU012elJzYTcyUm14aGh0YXJLeVpEMWF1MVhmYmVMM3VSV3VQdURDZCtlY0hWdnRIQ2dCWmY3OVhaUi8wTXltL05MbG83b1gxbUtuRXcyZFcxUGVZR0NGVTd3VmhVc2k1ZUswc1BBUTFvZ3duQ1BJeURGbG5XeEVnVUNVZEN6bGthQTIrRkVLdUJpc0NydEpETGF3MmFpVFIyZzJNVm41UDJWVUNtbnFRWWtoMTduNERtTG84TU1UdVVpUjlFa1YyNndQMHRDMWhvdE5tVEZ3TXk3TmpwVU9HNk9GQnk3dUUwZVFYSnVCSWJhN0h2V2RZd2JmIl19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.hVDa0ObIZvM_q1o9UUUsNTuL8hffbKRvV5w5OigywpUAqn3Z6OQI3jxXWFBDQRN0lEPsz21Ua1X9cF0bRxpYWIOfBkBdY2rUaL-1OXFosdRz-jP8pOf_Hv_tAz7R0QXzN-Nsso4buozqKaK50KhwqKCvgHzmEf7UP01oKeoJgVM3wi6u2RjYB1Fzki9YCB7h1jFhlRIqANwMVHxAD5_eHk_3vulQ7CkPoIDkDYCTXRkBwjR19sgaNSF_JiWZZiegtVEsdGrkUGmeL5FcAmH5yhPC-AbNTkRT5tHzaEMZ3Qw4BAGyiaStzDbvGWo_RDdMr2sJa0Bh2P5cj0Nb1mc29g\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signer-not-permitted" + } + }, + { + "name": "verify-jwk-and-x5c-other-key", + "kind": "verify", + "status": "required", + "note": "Signed by another key whose jwk and x5c agree with each other", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoieXotZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1ItcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTctTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eExfQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MC1rZ3hhOW9qWUJRWXpaSmZFSV9iOFZHdzRDMDVWN25MMVNPb1oxX1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GX2tialI3WDBVT05UMEpHWkJ0cHdudjhKLXhfQkd3IiwiZSI6IkFRQUIifSwieDVjIjpbIk1JSUMyRENDQWNDZ0F3SUJBZ0lFVlNBQUFqQU5CZ2txaGtpRzl3MEJBUXNGQURBbE1TTXdJUVlEVlFRRERCcEVVMUJZTFRVMU1qQWdkbVZqZEc5eUlHOTBhR1Z5SUd0bGVUQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNVeEl6QWhCZ05WQkFNTUdrUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2IzUm9aWElnYTJWNU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBeXorZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1IrcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTcrTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eEwvQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MCtrZ3hhOW9qWUJRWXpaSmZFSS9iOFZHdzRDMDVWN25MMVNPb1oxL1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GL2tialI3WDBVT05UMEpHWkJ0cHdudjhKK3gvQkd3SURBUUFCb3hBd0RqQU1CZ05WSFJNQkFmOEVBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDaEUxYVcycUt3OVRGVllrdnpVVjJpTndudHhodjVCVDlZRlByRENTS0xuZDRYYVJIWHovNHd2YVRvYUs4WHp4YktwSlBEN1h3ZFJzWjhDN2EzTDFDd1NEQjRnbWpsdjAwbktTTXZ6UnNhNzJSbXhoaHRhckt5WkQxYXUxWGZiZUwzdVJXdVB1RENkK2VjSFZ2dEhDZ0JaZjc5WFpSLzBNeW0vTkxsbzdvWDFtS25FdzJkVzFQZVlHQ0ZVN3dWaFVzaTVlSzBzUEFRMW9nd25DUEl5REZsbld4RWdVQ1VkQ3psa2FBMitGRUt1QmlzQ3J0SkRMYXcyYWlUUjJnMk1WbjVQMlZVQ21ucVFZa2gxN240RG1MbzhNTVR1VWlSOUVrVjI2d1AwdEMxaG90Tm1URndNeTdOanBVT0c2T0ZCeTd1RTBlUVhKdUJJYmE3SHZXZFl3YmYiXX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.sPZAfYK-YZhRqYD7fMu6ZmbQHO04beIfJAmspccoBSD4byfHBK-KBgjpcefN4BzxQULEzEN20to7oJB3HFrkMhxkweajQvSEbB4-oHIC4epreJ-jV3ea5MoqHgx8hxyag6or0EceK4QQIHLA_iJNObYyT06nhZQA1BMJ_0tGi89LaB4avMKDMXWKSFowkOXJnt-w4twjhcDtTgLRTvRdRJAY-bkc5JbbrCUH9gHRrGkRLEVhI_cZaLkEL6z7sdYYvtEbrExb4EdxbQJcJpHpTxqPUuKLIw-0TDOaTAQI3oDdT5KZhWKcgvin-iV1oJx_EUhemVoOmAJTH7DrLDCqIA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signer-not-permitted" + } + }, + { + "name": "verify-x5c-other-leaf-selected-key-second", + "kind": "verify", + "status": "required", + "note": "Another key's x5c leaf, with the selected key's certificate after it", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMkRDQ0FjQ2dBd0lCQWdJRVZTQUFBakFOQmdrcWhraUc5dzBCQVFzRkFEQWxNU013SVFZRFZRUUREQnBFVTFCWUxUVTFNakFnZG1WamRHOXlJRzkwYUdWeUlHdGxlVEFlRncweU5qQXhNREV3TURBd01EQmFGdzB6TmpBeE1ERXdNREF3TURCYU1DVXhJekFoQmdOVkJBTU1Ha1JUVUZndE5UVXlNQ0IyWldOMGIzSWdiM1JvWlhJZ2EyVjVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXl6K2REajhmRWczMk5RN2FZYkhVa1pzUTFUYk1lWlZINkl5YVN4RWVFUEVvNkRrNjQzSGtsam1aeHI3dU94bDFTVTZhUG5VYlYwU2lMaXN5aGpBdkRPdldSSFgxcTdSK3BDcmhQcDNsb2ZJZUtoZGU4dTZhZTM4djF0S3FDUU5YNnNUSkxSTVphaDNNbGRUQ3JjN01wcTU2aXU3K05LRkhEV2lQZFJyTmtDZEEwU0NqRlp0ZjJrVmZZcmF2NXhML0FzOXQ5T0ZtZ2tqcTNPdmZPejZSdUZOcnBOaTZ3Y2t1MGlNdzAra2d4YTlvallCUVl6WkpmRUkvYjhWR3c0QzA1VjduTDFTT29aMS9TOFRGTUZjcEdvUW1hRnl1MVVUdEdLNE9ObGl5Z0p1bUdXS2Z1a0tQTjRvRi9rYmpSN1gwVU9OVDBKR1pCdHB3bnY4Sit4L0JHd0lEQVFBQm94QXdEakFNQmdOVkhSTUJBZjhFQWpBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ2hFMWFXMnFLdzlURlZZa3Z6VVYyaU53bnR4aHY1QlQ5WUZQckRDU0tMbmQ0WGFSSFh6LzR3dmFUb2FLOFh6eGJLcEpQRDdYd2RSc1o4QzdhM0wxQ3dTREI0Z21qbHYwMG5LU012elJzYTcyUm14aGh0YXJLeVpEMWF1MVhmYmVMM3VSV3VQdURDZCtlY0hWdnRIQ2dCWmY3OVhaUi8wTXltL05MbG83b1gxbUtuRXcyZFcxUGVZR0NGVTd3VmhVc2k1ZUswc1BBUTFvZ3duQ1BJeURGbG5XeEVnVUNVZEN6bGthQTIrRkVLdUJpc0NydEpETGF3MmFpVFIyZzJNVm41UDJWVUNtbnFRWWtoMTduNERtTG84TU1UdVVpUjlFa1YyNndQMHRDMWhvdE5tVEZ3TXk3TmpwVU9HNk9GQnk3dUUwZVFYSnVCSWJhN0h2V2RZd2JmIiwiTUlJQzBqQ0NBYnFnQXdJQkFnSUVWU0FBQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFpTVNBd0hnWURWUVFEREJkRVUxQllMVFUxTWpBZ2RtVmpkRzl5SUhOcFoyNWxjakFlRncweU5qQXhNREV3TURBd01EQmFGdzB6TmpBeE1ERXdNREF3TURCYU1DSXhJREFlQmdOVkJBTU1GMFJUVUZndE5UVXlNQ0IyWldOMGIzSWdjMmxuYm1WeU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBaytqeVZVUDFmTTJPeCtQZXQzdElMRThJSEJ3K05uVCtPSFgvWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaS9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGwvWk5kbmxuTGNFaVZhcVNRV0pxL2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SC9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMS90dDhkcVVMRjBpNTdRSURBUUFCb3hBd0RqQU1CZ05WSFJNQkFmOEVBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFCTkVDTS8wZ01hVGxocjJiMjVXM2htdXVLbWRrWE5MM2FvaTJvMENmd3JobnRYZzYxbVRTbm1vYWprNXpzU1ZrMXBxdG9qcW5qcFZGNnc0K0lzblluNFVuQnZWRTB2cCtFSXUrQ2tpUkQvM0xsMWVBeEJzUzBvQkZTSUJENXJvbDdDTWRxemE5RmNBTG50RGsxc1lkcXUycW9yZjc5bXNPc2M3SWZ3K0NVdGVRNUFVNnBMRDJYcndRRlhkUzY5UzR4OEZvbkt2NWg3MEhXcFUyOG40K2p0UVRIUTVRNVBGMVllU0t5Yy83WWVDOG9oSWw2amhLSHZZRXd2VDVSWEhtR3lsOGdubkF0cnRRQy9pTER0cEZhNTZ0ZmJDZlNSZ2UyMTFqQkRMY3VicXhxaW01R3d6OWdXb2UyVXVyNkMyZVoxMEF1RkVKelZYRVBEN0JJeEhnZW0iXX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.LpvxlXwUAvNX5XdWIqApsCxeJn5a0YwG9un9UAOsunJbX2XOrw5fkqZRgfpRMPRZ_0TjipWP6Ln49fYCy-j0WKSmFi2CXhhlWAgWKo2JfbFMxxF2bckJryiiaXlxQw1brQGJyOkCJJuQaqaGbrcf7EgOWXkxf_ve95CeHF16e3cAIXN7eOvzWw8PL94C1F3_WCy_pRj19W2P92zZ3B6tn9gGJ3Frk4abMYUyOIb7BGdhV2IJuCa6QQ0jVTCRj2tiJ_lKDWiPL4eg3eSKgJJfbLgKGEFUE7AgG9LeAa-dibceGDXcQATDnijnP8GfoECYGRjBaMTj4GLzerB3N-zO7Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signer-not-permitted" + } + }, + { + "name": "verify-jwk-other-x5c-selected", + "kind": "verify", + "status": "required", + "note": "jwk of another key with x5c of the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoieXotZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1ItcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTctTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eExfQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MC1rZ3hhOW9qWUJRWXpaSmZFSV9iOFZHdzRDMDVWN25MMVNPb1oxX1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GX2tialI3WDBVT05UMEpHWkJ0cHdudjhKLXhfQkd3IiwiZSI6IkFRQUIifSwieDVjIjpbIk1JSUMwakNDQWJxZ0F3SUJBZ0lFVlNBQUFUQU5CZ2txaGtpRzl3MEJBUXNGQURBaU1TQXdIZ1lEVlFRRERCZEVVMUJZTFRVMU1qQWdkbVZqZEc5eUlITnBaMjVsY2pBZUZ3MHlOakF4TURFd01EQXdNREJhRncwek5qQXhNREV3TURBd01EQmFNQ0l4SURBZUJnTlZCQU1NRjBSVFVGZ3ROVFV5TUNCMlpXTjBiM0lnYzJsbmJtVnlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWsranlWVVAxZk0yT3grUGV0M3RJTEU4SUhCdytOblQrT0hYL1paY0hOeVlOV3N0bnFBTjJWMW9SaHVlU2l4blpLV3VQQWp0V0F6dXFEQXdCUkRxbWpxU2N3R0l5VzZmcnlXM3FuTmZYU0dRaFRidjF2enpSRWVpd1dBRnF1RXNFWnJjZUtBUUdoa0NpczhhVmU5Q0JZd0FqaUhkV1NpUUZWT1dScWJOcUdNblk1STllbmJ1OXVoV1RJbmxLcTJJZk0yb2hMRnliWmtnUU90MFJkRWkvUjRRdGZINWJrQzg1dW9MQWZkOTdhYkxsL1pOZG5sbkxjRWlWYXFTUVdKcS9mZHlmekxOT3FYZFNWcGtENThiUE5Fc2xpY2psUjdaNkgvWU5kbmpscXU1REhrb2h2ZXpPOE0xdUJaSkwxdUZ4UndBTW9VbjEvdHQ4ZHFVTEYwaTU3UUlEQVFBQm94QXdEakFNQmdOVkhSTUJBZjhFQWpBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQk5FQ00vMGdNYVRsaHIyYjI1VzNobXV1S21ka1hOTDNhb2kybzBDZndyaG50WGc2MW1UU25tb2FqazV6c1NWazFwcXRvanFuanBWRjZ3NCtJc25ZbjRVbkJ2VkUwdnArRUl1K0NraVJELzNMbDFlQXhCc1Mwb0JGU0lCRDVyb2w3Q01kcXphOUZjQUxudERrMXNZZHF1MnFvcmY3OW1zT3NjN0lmdytDVXRlUTVBVTZwTEQyWHJ3UUZYZFM2OVM0eDhGb25LdjVoNzBIV3BVMjhuNCtqdFFUSFE1UTVQRjFZZVNLeWMvN1llQzhvaElsNmpoS0h2WUV3dlQ1UlhIbUd5bDhnbm5BdHJ0UUMvaUxEdHBGYTU2dGZiQ2ZTUmdlMjExakJETGN1YnF4cWltNUd3ejlnV29lMlV1cjZDMmVaMTBBdUZFSnpWWEVQRDdCSXhIZ2VtIl19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.aOi6fLzqiaASIrAGXGmR99JluzTqkHHyA3BtD9XIzit42EY3wkwz0qLpHCPhfH7TSWvXpNhAEdLuaj87PaqDW8PCF-RBmvVEUvVe1qzE9Itnvx-6ipGiYDVskxSywGTV9-bXUaEertMobBoHJEalOKBw_7pJ2ITdMtJ2EuMevhR60lQLrXKPHNplrTFrqDtiEVC6uEeBEUC3WB-hs2_279AGgMWvQ5GMWjFvD4iMzWHXLvFCxB_Y1HZnKbOdHT_0MBqKT_fvZtVLGsJoU1nOwsrSLiBVYb11xldIkc213pAeSn_L5U29bkl0OQej3q0cNRhKdV-4OM-RhamnRS1pUg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-selected-x5c-other", + "kind": "verify", + "status": "required", + "note": "jwk of the selected key with x5c of another key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdRIiwiZSI6IkFRQUIifSwieDVjIjpbIk1JSUMyRENDQWNDZ0F3SUJBZ0lFVlNBQUFqQU5CZ2txaGtpRzl3MEJBUXNGQURBbE1TTXdJUVlEVlFRRERCcEVVMUJZTFRVMU1qQWdkbVZqZEc5eUlHOTBhR1Z5SUd0bGVUQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNVeEl6QWhCZ05WQkFNTUdrUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2IzUm9aWElnYTJWNU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBeXorZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1IrcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTcrTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eEwvQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MCtrZ3hhOW9qWUJRWXpaSmZFSS9iOFZHdzRDMDVWN25MMVNPb1oxL1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GL2tialI3WDBVT05UMEpHWkJ0cHdudjhKK3gvQkd3SURBUUFCb3hBd0RqQU1CZ05WSFJNQkFmOEVBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDaEUxYVcycUt3OVRGVllrdnpVVjJpTndudHhodjVCVDlZRlByRENTS0xuZDRYYVJIWHovNHd2YVRvYUs4WHp4YktwSlBEN1h3ZFJzWjhDN2EzTDFDd1NEQjRnbWpsdjAwbktTTXZ6UnNhNzJSbXhoaHRhckt5WkQxYXUxWGZiZUwzdVJXdVB1RENkK2VjSFZ2dEhDZ0JaZjc5WFpSLzBNeW0vTkxsbzdvWDFtS25FdzJkVzFQZVlHQ0ZVN3dWaFVzaTVlSzBzUEFRMW9nd25DUEl5REZsbld4RWdVQ1VkQ3psa2FBMitGRUt1QmlzQ3J0SkRMYXcyYWlUUjJnMk1WbjVQMlZVQ21ucVFZa2gxN240RG1MbzhNTVR1VWlSOUVrVjI2d1AwdEMxaG90Tm1URndNeTdOanBVT0c2T0ZCeTd1RTBlUVhKdUJJYmE3SHZXZFl3YmYiXX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.sEjF3Bi1xIHUPXWIFd3WRJonnFBilsF9xOnpPnKu-h3AhuctLZ9a0YNSySj9GkGN8JDRGsIL54-t1IIZI5Jnpeh_knojEKqy5iiCQnn7FxmxKQNYH6IAc1Omj51qYMJvECABR8njXkmkG-JJQ45NgXPm9d5RMtRrU_vzuQU7DSYoIkcfPzJK9uljxnfEPQosP3VdK0xnPGIxUxVRG5QW4wnefPhXDwTVWHtqJ7VQZp99hMz7ZXPX0gKTyqUw7g02R8JR4lZieEFCgIWA0CB0cyoM7nVRJ4g9IfCjysLt9dFVD5aekCbNNdyyOJr7Ba7EyIpHzGSX6dYC9LYLg8LFyQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-oct", + "kind": "verify", + "status": "required", + "note": "A symmetric jwk; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJvY3QiLCJrIjoiWktRVEZWdlZleFdhWU9TY2xaVlY4QWk5SDBsVXBZNW00TGVqOFAzZEN3YyJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.hPMAwoz6DqJ-PsOOAa01G-J6YVbDxRe_f7Ms19gW2qrgNw09gB4gAvFXscDv-_RrzHyMWuIYy1T_HKksKC83LFRIfWWnsmrFifxFI3n7aUrv-xWuEHBdwVdPB8sfT0vMUnKG2un4F0c_QNc3mQdgduPMwpvf3SNd53-7Y9lD08ozt9VUegdWKPSS6-c-pgMmP4pCSMzKzFjzG112loH8YdYqxKG2T9BxnqlkWXwNNCZyOgRaLinZHJEmGrkSfdsScJjmYaWO28pq6ng9MAqbFc5lOJ-ctcS8dMQqREIO2za0kS5hOeoWQLIUHO2VpA-e8IS4iXNpXm90G_f1EReIxg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-private-member-d", + "kind": "verify", + "status": "required", + "note": "An RSA jwk with a d member; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdRIiwiZSI6IkFRQUIiLCJkIjoicXJFaEZfb18wd2JiYXJWbVBBUVUxbVRla0dVVU01WmRQdVVjbTNQdDRGeXFzU0VYLWpfVEJ0dHF0V1k4QkJUV1pONlFaUlF6bGwwLTVSeWJjLTNnWEtxeElSZjZQOU1HMjJxMVpqd0VGTlprM3BCbEZET1dYVDdsSEp0ejdlQmNxckVoRl9vXzB3YmJhclZtUEFRVTFtVGVrR1VVTTVaZFB1VWNtM1B0NEZ5cXNTRVgtal9UQnR0cXRXWThCQlRXWk42UVpSUXpsbDAtNVJ5YmMtM2dYS3F4SVJmNlA5TUcyMnExWmp3RUZOWmszcEJsRkRPV1hUN2xISnR6N2VCY3FyRWhGX29fMHdiYmFyVm1QQVFVMW1UZWtHVVVNNVpkUHVVY20zUHQ0Rnlxc1NFWC1qX1RCdHRxdFdZOEJCVFdaTjZRWlJRemxsMC01UnliYy0zZ1hBIn19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.INWthLqlHIvdpg_bmT2J7dqkI9TQG2Oy7s05G_pAk58AKtF73Vt9huQFOeszsbcrsMA8cuUhVyusfNM81ETvT79Uk-KyWBrVjtCuLzGlCXTZhb7IcUXJCC4IOP3iONu9W-12c1IEj0FwAzIK9mgU6ZlnPGqcm6Yj15RnC2mkT4aMCuoa9_OXQJtJBPXDoFDE1twsW2kzBJNQzXB9oFbY3K4fYM7mTjxHHgdvXfwcJ4L4-CaoIwddaTkIxpihzE6CYWOBX4MtGzeM4RFrXeGsaC19aZeRaTB4M-jX8q0qow3Alq4TeaeBxOtjkVr-lBTYK9H6AA88_1Ds1F3cofGkaw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-private-member-qi", + "kind": "verify", + "status": "required", + "note": "An RSA jwk whose only private member is qi; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdRIiwiZSI6IkFRQUIiLCJxaSI6InFyRWhGX29fMHdiYmFyVm1QQVFVMW1UZWtHVVVNNVpkUHVVY20zUHQ0Rnlxc1NFWC1qX1RCdHRxdFdZOEJCVFdaTjZRWlJRemxsMC01UnliYy0zZ1hLcXhJUmY2UDlNRzIycTFaandFRk5aazNwQmxGRE9XWFQ3bEhKdHo3ZUJjcXJFaEZfb18wd2JiYXJWbVBBUVUxbVRla0dVVU01WmRQdVVjbTNQdDRGeXFzU0VYLWpfVEJ0dHF0V1k4QkJUV1pONlFaUlF6bGwwLTVSeWJjLTNnWEtxeElSZjZQOU1HMjJxMVpqd0VGTlprM3BCbEZET1dYVDdsSEp0ejdlQmNxckVoRl9vXzB3YmJhclZtUEFRVTFtVGVrR1VVTTVaZFB1VWNtM1B0NEZ5cXNTRVgtal9UQnR0cXRXWThCQlRXWk42UVpSUXpsbDAtNVJ5YmMtM2dYQSJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.LETfn6seQ26f1rja7OTxe72WtiZxmgwVOyOXPCwKU7O_ruiN1XNwVzpm6ly-9DoYZjv5GRAfaUFLAfcvEPYYK40wMaJZwaE-IDl79XSlue5pDQMkQ4wfwwFV3XzAHivqDEsp_qlKCy0l2Rx6Z5MJCpvdipwCJ8gKaZuDpvkc-4S3ei7z1d7EKVtPQTWYXH3p7daF7IvNVzGbQ6vc_3_HbZYz2NTIrrA2goprgLpfgAKH3b7lUntwB7wIYHtEFSRsoIftbCXthkL9GM8cPtouNjBqPywrc4TQ-QkOnnITTQ73DVmekYQMrtJNHL7Pm4FDrUYOomUrdaEWyyfH_u8T7w\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-not-object", + "kind": "verify", + "status": "required", + "note": "jwk is a string; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6Im5vdC1hLWp3ayJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.OZbw9U-6AWpaSuvaPMSBx0xPBlwLsccUiobFtFg-J89gz_7C2284MWMXO99nkTEakQYifehqKGh_l3WYgOjp-cdQbMEGtcsE457RGeHYPNpZNaZuSruB5i6MBHccpQnq78ZrUxgRGaKUTsa78bz6ofSRsL8o0dxDx6Y5zi2ljGZmojnxLj_ABGbfEWxQa3aRnRv5Gk-7UWYqT_XUdJu1Ue3ypxC2wIvxx0wmP4jsTIFoqV97fKJVluvAL5hmggP2deCd2ZKQ9ewOxrgYepz5Wz2212Z1fNjn-QKWt4hQW4mtd-Pzl9ow4X0vkkOjFKiE2z3_fl6z9c7kYu_j4g7hpg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-missing-n", + "kind": "verify", + "status": "required", + "note": "An RSA jwk without n; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.JVeVwldMPZZ9t2IOQTMlbQhwOozhJAXqQSok5VzHGCsuN4GOjfxg29ty9l1kVjpcwZyyjSt2uqO1SEmdjEeZ1YkaXtb0AuD1tp4W-78GJohLcL_2FZZ6kFPuqYTpB5eDOgX3B5tvIG779km71obIqkx2uRsRjk6SbZ_yOB6zhrX1MkIdaEGOFIaLOc3o4bjSKrB6o0LXYopBXGgpgfZeiDiMQjbpzDa76PaJt_F7WVWR8_BaHoXb6HQupl4yxMwIuI-_bNA_Fq_Cte9dn8qmC9uESjfPPkgdnMmaeARdZypR31OwoVBhzYEX56PoJQrDCya5OaS6PMhgxJxuI0xTTg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-n-not-base64url", + "kind": "verify", + "status": "required", + "note": "An RSA jwk whose n is not base64url; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoibm90IGJhc2U2NHVybCEiLCJlIjoiQVFBQiJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.PFMuMDjmJZHpUG9K2XRG-RhG8zq0A7hObWDhp_LyUez8dUzTVFu-gn5w7oXjTvUiWfja1cFP1Di77bQxiFXHA7p0pRrCZFsugPbuKzMhxB76JEvrJyYhtBP8pKlowOazD5IgDD69OkGGygRaRAqfyOkUkLDgjCnle2N2Jl_9wUCguEuYXf1CMG_HjMV1-cnaQ3Hibo7hsng4yiGF2NdlsxdyptRgLTsJZgbyBEeb8F_rhWg-3VROB70R0KWnXNv-3Wx3xxsVKds_noZTZBuWd_2tf7rx72oZDcUxseBh0RU119AoE2rHmOfMrHrQD71jVdcbstBEIaEsdL3DCZa4mQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-empty", + "kind": "verify", + "status": "required", + "note": "x5c is an empty array; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6W119.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.QgtfOtyU9cO9iEt8PlzZz7Gyt5UEsqhQbKSPjpTDrGcaKB4ASxb4lGWBYiZcJiW1Aq4Cf4-vrkiOxM6-3sCxMC1HeRoXExmmnZ7Q0uloWWKPeHzAzKFF6OJbh2HAT_vIBGCwEuDivQyOo7KTw1X_sDbOnX3J3oNBmmMp_YP_0QEiG9xATNJ7iZym8T_uwytcn_rxBLp0NrbhavJm5swIUAn2vG_MJviyZTJTLTnY7R0DL_R7mm4AzVlXY5oZKEafL7-B2gzMpfom-SGrifgz5H8Z3txQd2RiqlV1fkH4OxxJ9-CB7sJtKdibTpQ0Z6jh-LwnxAZizlsfuA7atiJUZA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-not-array", + "kind": "verify", + "status": "required", + "note": "x5c is a string, not an array; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6Ik1JSUMwakNDQWJxZ0F3SUJBZ0lFVlNBQUFUQU5CZ2txaGtpRzl3MEJBUXNGQURBaU1TQXdIZ1lEVlFRRERCZEVVMUJZTFRVMU1qQWdkbVZqZEc5eUlITnBaMjVsY2pBZUZ3MHlOakF4TURFd01EQXdNREJhRncwek5qQXhNREV3TURBd01EQmFNQ0l4SURBZUJnTlZCQU1NRjBSVFVGZ3ROVFV5TUNCMlpXTjBiM0lnYzJsbmJtVnlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWsranlWVVAxZk0yT3grUGV0M3RJTEU4SUhCdytOblQrT0hYL1paY0hOeVlOV3N0bnFBTjJWMW9SaHVlU2l4blpLV3VQQWp0V0F6dXFEQXdCUkRxbWpxU2N3R0l5VzZmcnlXM3FuTmZYU0dRaFRidjF2enpSRWVpd1dBRnF1RXNFWnJjZUtBUUdoa0NpczhhVmU5Q0JZd0FqaUhkV1NpUUZWT1dScWJOcUdNblk1STllbmJ1OXVoV1RJbmxLcTJJZk0yb2hMRnliWmtnUU90MFJkRWkvUjRRdGZINWJrQzg1dW9MQWZkOTdhYkxsL1pOZG5sbkxjRWlWYXFTUVdKcS9mZHlmekxOT3FYZFNWcGtENThiUE5Fc2xpY2psUjdaNkgvWU5kbmpscXU1REhrb2h2ZXpPOE0xdUJaSkwxdUZ4UndBTW9VbjEvdHQ4ZHFVTEYwaTU3UUlEQVFBQm94QXdEakFNQmdOVkhSTUJBZjhFQWpBQU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQk5FQ00vMGdNYVRsaHIyYjI1VzNobXV1S21ka1hOTDNhb2kybzBDZndyaG50WGc2MW1UU25tb2FqazV6c1NWazFwcXRvanFuanBWRjZ3NCtJc25ZbjRVbkJ2VkUwdnArRUl1K0NraVJELzNMbDFlQXhCc1Mwb0JGU0lCRDVyb2w3Q01kcXphOUZjQUxudERrMXNZZHF1MnFvcmY3OW1zT3NjN0lmdytDVXRlUTVBVTZwTEQyWHJ3UUZYZFM2OVM0eDhGb25LdjVoNzBIV3BVMjhuNCtqdFFUSFE1UTVQRjFZZVNLeWMvN1llQzhvaElsNmpoS0h2WUV3dlQ1UlhIbUd5bDhnbm5BdHJ0UUMvaUxEdHBGYTU2dGZiQ2ZTUmdlMjExakJETGN1YnF4cWltNUd3ejlnV29lMlV1cjZDMmVaMTBBdUZFSnpWWEVQRDdCSXhIZ2VtIn0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.jWe00EAzrF4n4V_Lgb7wl36QGfcEkK9_R8JP05jLrggcQhrITcXFD4qz389usacWmlfvCfOtMVpkdlL2Kl92Tg0uttCjd9v7-tEhanuwa-AC8t-aTwvUmhhpoBok9MukDJl_Wr3dCJCL_dwNmMv5VWZijCCXzS7XRNAD0jYS_vVDP9qPy09DBe_caVg7OU83RzF_icV8cg_D1ww6GU0_yPAfaL3ZWcnMkTDNVPj8aIn_nwqdaBAPis89gCsTDd5Q2aH-BrBhWY94pcT-MHol8RXgrPvYM6oDlGzzuoDEnFqTSJQgTgASKhpzR3EvINn_e34ZYWfSTNf78Ml1aeWhNg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-not-base64", + "kind": "verify", + "status": "required", + "note": "The x5c leaf is not base64; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJub3QgYmFzZTY0ISJdfQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.a5wBLm3ke_n4lZtoh_s_tGEIgckSL9a2eTifSPrtkegLQU7Kuttnz0ykpRmj8ZexurO8FnSbSgUfa-RhB1NZ3lHVjRVXz5AlQee0_YFrL-iHLkE0tfpXDZrrfk0xEWLR2U99ewHs50yidey2nrOGborU-6MOjISTe6tfHR9UFd5Viox_swh6NFpYcDLgUyV452jlvFewLhQODUllrhrRJVXwi0JbaBF9FraX508hRvmJJVzVDt4e-cXANdtwJuJ8S1R2n5-dBGIsniqupZT6IKoY5Hv_XVSlgkl29IL4qJUa544zMUPBBOObF1idBonw7P_WqhwJC7QxdMPp2-pZRw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-not-certificate", + "kind": "verify", + "status": "required", + "note": "The x5c leaf is not a DER certificate; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJibTkwSUdFZ1kyVnlkR2xtYVdOaGRHVT0iXX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.eq2Uf8ONxmCEVWh3UimJRV_NZBEwSXylPytPE_mj32n7J5LZlNi37TGowJgI5T7HQCUnvheJuyJ1li0NozTF4-8OzMq3FaJAFYaKn7L_ubgYlVpZ8Awqp_nMNsiOy41-wrxFs1JkAGR7Pvq-KQW9AsYcK8vqpM_6RqgztHDvQsDxKb3ZVzWNU2jZBlnsOLUgw9tjPPC2BM7CXbXFOzxz96pZss36QM1437r9VXHaCWi4CkM1o3U7qYfNn2Us9rvLKZoIrQMSCEgTGwfy4Qi0R-SexPJYQ_ecD__xyAq7ZVai9yHMXqMusBRdPISzSUylMigUKDaJGt-BJo3Ho6M6bg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-hs256-configured-x5c", + "kind": "verify", + "status": "required", + "note": "x5c with a configured HS256 secret", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiIsIng1YyI6WyJNSUlDMGpDQ0FicWdBd0lCQWdJRVZTQUFBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWlNU0F3SGdZRFZRUUREQmRFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNqQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNJeElEQWVCZ05WQkFNTUYwUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2MybG5ibVZ5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SUxFOElIQncrTm5UK09IWC9aWmNITnlZTldzdG5xQU4yVjFvUmh1ZVNpeG5aS1d1UEFqdFdBenVxREF3QlJEcW1qcVNjd0dJeVc2ZnJ5VzNxbk5mWFNHUWhUYnYxdnp6UkVlaXdXQUZxdUVzRVpyY2VLQVFHaGtDaXM4YVZlOUNCWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpL1I0UXRmSDVia0M4NXVvTEFmZDk3YWJMbC9aTmRubG5MY0VpVmFxU1FXSnEvZmR5ZnpMTk9xWGRTVnBrRDU4YlBORXNsaWNqbFI3WjZIL1lOZG5qbHF1NURIa29odmV6TzhNMXVCWkpMMXVGeFJ3QU1vVW4xL3R0OGRxVUxGMGk1N1FJREFRQUJveEF3RGpBTUJnTlZIUk1CQWY4RUFqQUFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJORUNNLzBnTWFUbGhyMmIyNVczaG11dUttZGtYTkwzYW9pMm8wQ2Z3cmhudFhnNjFtVFNubW9hams1enNTVmsxcHF0b2pxbmpwVkY2dzQrSXNuWW40VW5CdlZFMHZwK0VJdStDa2lSRC8zTGwxZUF4QnNTMG9CRlNJQkQ1cm9sN0NNZHF6YTlGY0FMbnREazFzWWRxdTJxb3JmNzltc09zYzdJZncrQ1V0ZVE1QVU2cExEMlhyd1FGWGRTNjlTNHg4Rm9uS3Y1aDcwSFdwVTI4bjQranRRVEhRNVE1UEYxWWVTS3ljLzdZZUM4b2hJbDZqaEtIdllFd3ZUNVJYSG1HeWw4Z25uQXRydFFDL2lMRHRwRmE1NnRmYkNmU1JnZTIxMWpCRExjdWJxeHFpbTVHd3o5Z1dvZTJVdXI2QzJlWjEwQXVGRUp6VlhFUEQ3Qkl4SGdlbSJdfQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.-wlZiXW3nVueLOiUQNCPsXw_oY9bGO6-FPNFaQspgpw\"}}", + "selectedKey": "hs256-configured", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-hs256-configured-oct-jwk", + "kind": "verify", + "status": "required", + "note": "A symmetric jwk carrying the configured HS256 secret itself", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiIsImp3ayI6eyJrdHkiOiJvY3QiLCJrIjoiWktRVEZWdlZleFdhWU9TY2xaVlY4QWk5SDBsVXBZNW00TGVqOFAzZEN3YyJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.C-tdQ8cvlMopvOQgqfC-B5JrV8xs4oak-LppyCdZx5w\"}}", + "selectedKey": "hs256-configured", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-hs256-payload-key-jwk", + "kind": "verify", + "status": "required", + "note": "An RSA jwk with an HS256 binding from the payload key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoieXotZERqOGZFZzMyTlE3YVliSFVrWnNRMVRiTWVaVkg2SXlhU3hFZUVQRW82RGs2NDNIa2xqbVp4cjd1T3hsMVNVNmFQblViVjBTaUxpc3loakF2RE92V1JIWDFxN1ItcENyaFBwM2xvZkllS2hkZTh1NmFlMzh2MXRLcUNRTlg2c1RKTFJNWmFoM01sZFRDcmM3TXBxNTZpdTctTktGSERXaVBkUnJOa0NkQTBTQ2pGWnRmMmtWZllyYXY1eExfQXM5dDlPRm1na2pxM092Zk96NlJ1Rk5ycE5pNndja3UwaU13MC1rZ3hhOW9qWUJRWXpaSmZFSV9iOFZHdzRDMDVWN25MMVNPb1oxX1M4VEZNRmNwR29RbWFGeXUxVVR0R0s0T05saXlnSnVtR1dLZnVrS1BONG9GX2tialI3WDBVT05UMEpHWkJ0cHdudjhKLXhfQkd3IiwiZSI6IkFRQUIifX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.4XAYYljpETk3EOPZsioY7Y-ygSaqJDd5hfTw319pejQ\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-other-key-signature", + "kind": "verify", + "status": "required", + "note": "Signed by another key, no header key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.djfBq87hT88-GYydBtzwZrMhT1DLpAXLHiSPoF03L0fMEH0VADsumKZ8cV4LVGZUg7yiLIPbtT-RiMbPjo0x8LKvu6d9L_cMxxlcDAxLdODOB8QSRG3TTyCXjQtdq1vGmUzvi7uJxQ1aDHYUA2qLN0SC4E4vR2JeLsI-Cejliqrn7qbEXVowytZq33OMVrmIuQaM_TtwiAFARXF9SoiyqGCdkLaAsKSnLrrs61KoSF72iJGOeAoApBoGZjfzz6ZMrHEmKaVXTvJ4j8HA0C0MMtHHqPry0d5x74nTEfJCINhMKn1DKKepDQnqpYHGPdacoAmsFpicCwODC7PWgvfcCA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-other-key-signature-copied-kid", + "kind": "verify", + "status": "required", + "note": "Signed by another key with the selected key's thumbprint as kid", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6InhkNU5qT19CTnNWR1JYYW5BQlBtb2tMelQxcUhFUkxYa1ZhTDZiRExJakEifQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.TkRr48wEaMkO9eDvyW2sG9yFCE2xW9KXoBLtROfyG6kqDmH0qvLOU_bVXDABEm2GwEtXs_vUqjKL64Xli5YKPTYpHWfpAI72FI6aVUmHeCMujtbJAUoBuaRf1Z15XUKAt1gkv3v6RtBTieUNnLtg7BvQ7vyS_uRMc4ny5yr_5tveRUzuvU-08CHzCtwFRg_DrYfCZMEmD77pmFv7-RRAV0adkV7s5QfUSEMdLS9H7cYPliTUPaa7HVie-QKsOYuV58R0j0nF0O6-TnJfzPWXPbUPrZK2tLGRh4wclu-yTuDQ4ssjT-NlBsPHkhOF19p8FLACu7cb6hNW6rOBLlVEHQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-other-key-signature-copied-jwk", + "kind": "verify", + "status": "required", + "note": "Signed by another key with the selected key's jwk copied into the header", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdRIiwiZSI6IkFRQUIifX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.QaagmqMdYpicfRAwnE9uynIb0tMv5FueQ-0kQ8GsLR6kOJXP5Sbzcpy6YQ9r3uwno9BTQwYBCji5SlDofadikgkHEvEKNRcHGutVlLVcIW2zO50Z3h21K4298i141LbuoIM1tRqdxR1KxVW8KrmqWGKrMwS9-Vx4CzxQzRzCz-QXhZmUTMaQfAqLEza0jdUrAe82kolbIg91Ulpkg_WMBrUxaQlhsnilWjlnDQle3ykuZwZjTaUbVAssUx6V_JUxQPHJlECpAnpa2S0FG0ge8T2DQ1wyGK-TzeKGlQMxZ73IQZR-5FOSNrJyb5ZAQ2xI_K39WebPJYIWkHRjHUD92g\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-other-key-signature-copied-x5c", + "kind": "verify", + "status": "required", + "note": "Signed by another key with the selected key's certificate copied into x5c", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMGpDQ0FicWdBd0lCQWdJRVZTQUFBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWlNU0F3SGdZRFZRUUREQmRFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNqQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNJeElEQWVCZ05WQkFNTUYwUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2MybG5ibVZ5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SUxFOElIQncrTm5UK09IWC9aWmNITnlZTldzdG5xQU4yVjFvUmh1ZVNpeG5aS1d1UEFqdFdBenVxREF3QlJEcW1qcVNjd0dJeVc2ZnJ5VzNxbk5mWFNHUWhUYnYxdnp6UkVlaXdXQUZxdUVzRVpyY2VLQVFHaGtDaXM4YVZlOUNCWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpL1I0UXRmSDVia0M4NXVvTEFmZDk3YWJMbC9aTmRubG5MY0VpVmFxU1FXSnEvZmR5ZnpMTk9xWGRTVnBrRDU4YlBORXNsaWNqbFI3WjZIL1lOZG5qbHF1NURIa29odmV6TzhNMXVCWkpMMXVGeFJ3QU1vVW4xL3R0OGRxVUxGMGk1N1FJREFRQUJveEF3RGpBTUJnTlZIUk1CQWY4RUFqQUFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJORUNNLzBnTWFUbGhyMmIyNVczaG11dUttZGtYTkwzYW9pMm8wQ2Z3cmhudFhnNjFtVFNubW9hams1enNTVmsxcHF0b2pxbmpwVkY2dzQrSXNuWW40VW5CdlZFMHZwK0VJdStDa2lSRC8zTGwxZUF4QnNTMG9CRlNJQkQ1cm9sN0NNZHF6YTlGY0FMbnREazFzWWRxdTJxb3JmNzltc09zYzdJZncrQ1V0ZVE1QVU2cExEMlhyd1FGWGRTNjlTNHg4Rm9uS3Y1aDcwSFdwVTI4bjQranRRVEhRNVE1UEYxWWVTS3ljLzdZZUM4b2hJbDZqaEtIdllFd3ZUNVJYSG1HeWw4Z25uQXRydFFDL2lMRHRwRmE1NnRmYkNmU1JnZTIxMWpCRExjdWJxeHFpbTVHd3o5Z1dvZTJVdXI2QzJlWjEwQXVGRUp6VlhFUEQ3Qkl4SGdlbSJdfQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.nXWXjB01Nw7ZrS2UFMj4SPsilphHlC6nT8YLvYgaoTW1RF1I-VMqlUP5SLnhwvHSR_R3zlGGfK6QZMdYSYD60TYo_2vMUJMi6DdL-fyfpcvjObiefmjG3zqksayry9fN_uTwCKVrd65247CepndFtt8waW5BOpTSFArwuafQOH6W7P89LUjCoKg17U2WfWm7RKemz0F9IJCTEnX0DGS6i81lcqvdiweT6nnQXw9as90lyCXoLp2f2ja5a9Na8wfq84WN0UMBRXtUbPpr93bg-HgfgVOoKQGgUO24bGJ-OzSTnDnnD3sgOs_MV0HoWDvWyQVlydaglhJdKWN4Hzg1WQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-signature-bit-flip", + "kind": "verify", + "status": "required", + "note": "One bit of the selected key's signature flipped", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJb-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-payload-key-binding-against-configured-hs256", + "kind": "verify", + "status": "required", + "note": "Payload-key HS256 binding against a configured HS256 secret", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Gpdu2Q1de4ga4DEVRvXS2fhwFCk-SasHnrfNMiwtpxg\"}}", + "selectedKey": "hs256-configured", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-configured-hs256-binding-against-payload-key", + "kind": "verify", + "status": "required", + "note": "Configured-secret HS256 binding against the payload key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.aVPkGyrHe5U-2tzQoNI-IJMFxWkPFEWQY4aljyOFXZ8\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-claims-missing-assertionHash", + "kind": "verify", + "status": "required", + "note": "No assertionHash claim", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25TaWciOiJ3TnI4SDJmdVZhVWNTU0FoVzVMTklERzlmRFZ2ODl5cGFTRUx0bXNVQkRKRmJRWFdvL1NLRks3dUlNalY4RUtkQitXK1B4YVYvT3d0dkhWYzJkWkVsUT09In0.ihxEo9vBw7VJTHux-jJ4dc3ScuG-BPoPtzF71WiO9L1tJs38FuTkMbbXrvixYbDhf-ttILAklW2pmBqhIcYKffkv8Ffo7NKXPDQynRMWJF8brWqOaUkkv4bma50qJyDtPoYgVBGr6dzarkeB63xS3CS5kasBBGLoudYxnlGZ4uvBTBh5EDbWqvxPzC3ZUUtAUpNCS1kbZXQl6ywEi0gk5gjE_VRv8pzj4SrMXR_EEMPLG61PtXPZw5A4ZKdi-cX3t1V_TEFXVTcqp2P8GkFzQ8NwNXHMyMdtIQv798c8087caYMUodN9p2c9Am38nVM7jMkVIICzJ5fBsxfnXWKnZQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-claims-assertionHash-not-string", + "kind": "verify", + "status": "required", + "note": "assertionHash is a number", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoxLCJhc3NlcnRpb25TaWciOiJ3TnI4SDJmdVZhVWNTU0FoVzVMTklERzlmRFZ2ODl5cGFTRUx0bXNVQkRKRmJRWFdvL1NLRks3dUlNalY4RUtkQitXK1B4YVYvT3d0dkhWYzJkWkVsUT09In0.FdkzVDDqqVPaOSiYHccLTiiUNA-UEb0qedFp26iex03LvSxbuex3e0waydcvby4--sSOzi3dwG0xpTkBQ-JTq9wIRZpvnqKU2VOnhe9peby7-S9wuI8qab6EsG3vflcUKmauKHOH2IYIGmxHXzPz0qzZCxaXARN_tNF-xaLsLc__6jdcw5ulp6FRdvaWTPROywM4jLyyPHAdcM8hz9COMGySXJPZnaWlNu_GQ8egENWcY3WPHi0Vt9kV6oZKmgOXPmnr7d7KbB7JhWQo5SACWaJ7OuoHjF6_j-hZiA-bN4zcvLleLwnoe8994jARcPRlkEXD5m_tqotkeBo_TYgx7g\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-claims-missing-assertionSig", + "kind": "verify", + "status": "required", + "note": "No assertionSig claim", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSJ9.QBGEcZPuzjZeSfvNHo18LjkL1ZXz93LmP6Std6ymHfPsqIS0YWnSZxBgBxQIFZX6QSRsxm5U8DKrswW_g4dKLOD7GqNwLOVQClZp9KWex-ZCddYhSmFFzxuWkQcyZFKz2M22YIfNrJcdtdFNfP4q9TtzmjDdQP-yyiZAQmu-kA-oR7kES-zlYEPOeFpPbvIXSLp0vsILilMYjPi14ydOdf6xe4DTwbkpOGD0L2bFUvD8hPUW7uJTutNBYa8Sz5d8i2HwE4_iBlnFmF87ZAyBjClxGZao1DPBqcyA43RticP9sfDOSta5tnoV-3ToddVKOHaD2bjbhtKDhBUQxTXKwA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-claims-assertionSig-not-string", + "kind": "verify", + "status": "required", + "note": "assertionSig is an array", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6WyJ3TnI4SDJmdVZhVWNTU0FoVzVMTklERzlmRFZ2ODl5cGFTRUx0bXNVQkRKRmJRWFdvL1NLRks3dUlNalY4RUtkQitXK1B4YVYvT3d0dkhWYzJkWkVsUT09Il19.XQjFDvxvVEvG1ddfXlVAqZEvL4vdfAXWkHBQSh2J3pWouqk7Ix74EqUYp_RKA-VPA9VnqgqSv_uEK4zL4v8keuR5t7IBvFp5obRrWwhUv1pXBaiicAtfCndeuyoA8pzIkALB4iUVr8Ikd7CFHjKFtGHWOw_o1om47vMDJlxOMvv8QIVXxlV_kVrPEQDnrqFGBJpVfM37LEFXIZHZuK8jJAEvC04dL1zNGGmSV3-1nyCziiej-iOHutgSNhOeffaFhLGIYxHq2hkaQfXonodoP4BL3KorKbnQdTA5toD5ooOkg54nEy6JFWbrBMOYUD2Hoyj4E8i8L81Z9-l8pC1L8A\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-claims-assertionHash-uppercase", + "kind": "verify", + "status": "required", + "note": "assertionHash in uppercase hex; it is compared exactly", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2RDA1RDZBM0Y0OEExNEFFRUUyMEM4RDVGMDQyOUQwN0U1QkUzRjE2OTVGQ0VDMkRCQzc1NUNEOUQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.RBDDh-iHo1ClgmvZ5NEgeTJPDszZXG4XQmFbn_-qdFKsxcoRyvoeQb8dPU0aPA3fp9ZT17eX1zPbcRS-8l4HS7IodjxvCrI5Uj2ldwqE0nnoDEILAQMouRcOzkJG6DfqTVXPve9hXFw2FBWHTAQifT3ZNiWLXNCL0FPrugsz278eK9EBKB3jrIOaj4Y2ezV-03-1X2bk7cOxDnSuTF2X2uMPKseVsRcE1E_Xc_zVvXWuE1XhIOP62Zll-BYr7k2rGKNND8eRPZlmuIkH7gFaxV_f3A_w2mzbMWtM-UPCGKWapelRDRs9aYbDzA8R83hUpwSY-4F8wYm2pUqqWHVBBA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-statement-changed-after-signing", + "kind": "verify", + "status": "required", + "note": "statement.value changed after signing", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"UNCLASSIFIED\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-unmodelled-member-added-after-signing", + "kind": "verify", + "status": "required", + "note": "An unmodelled member added after signing", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"hash\":\"deadbeef\",\"note\":\"coi\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-unmodelled-member-changed-after-signing", + "kind": "verify", + "status": "required", + "note": "An unmodelled member changed after signing", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"hash\":\"deadbeef\",\"note\":\"changed\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiYTczNDdmZjFjNzE4YzBlZTdlODg1NThkZTY0Mzg5ZDVlNzE5NDk5MGViNmM3ODY2MmQwYmNkOWNmYjE5YzdiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtuTkgveHh4akE3bjZJVlkzbVE0blY1eGxKa090c2VHWXRDODJjK3huSHVnPT0ifQ.SV8GD6VR7SFrNMClVgDmNSSZ3-z8B2dv8smJYU8Zc1jvhKpAVDjKkaRsf3nxAKLeQJIIgwy-tosRidfQ-qiTvJyB9emXyiQwGud-46lO02eu39A4PJJ19VfAn4Um8Rqvk_QhcHqsrMUHkiqCttIfqo6Dofl8Y9do-jKawHjGIyswukfS4COj87je0GlrZmS38W0fgBURbFc_5AxBCh7C7rIywWjlk4xbaZqB79yHeJv_HfDzR9AAsNZgi3syrX0id2Y5AvQm8-N0ZplIDXa0aTMByrQVzDZt1eDIXZn3SW7SqcVw6TSiO4lXJT7ZlPv3AQk9BNEUODnwlyiU6bngLg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-object-value-changed-after-signing", + "kind": "verify", + "status": "required", + "note": "A member deep in an object statement.value changed after signing", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"UNCLASSIFIED\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-binding-from-another-assertion", + "kind": "verify", + "status": "required", + "note": "A valid binding of a different assertion with the same key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDY4NTNjYjUyOTEyZmQ3NmYzZmY5MGRmMzkzNjE1OGVlMjIwYjhlNTk3M2M1ZDAyYmI2ZTUwZjQ2ODU5NmZiYSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpHaFR5MUtSTDlkdlAva044NU5oV080aUM0NVpjOFhRSzdibEQwYUZsdnVnPT0ifQ.c-iW_TdRcnjNJY2gVHAW4HPKni1LDbaJ6lVtzJhipXwf_53QS_Rnutr6Hgdubxg8tQzDdWTNMIieBM_fi5DL_pkpiA6mOa5O_J0KPKIJ3HLF5z1yPAe3W4BHaSCrQlKlokypN07U_CxtNr5BJ2GboN2OdvxFkelvPBJQAEI2M1NdxEn65diD0KcI5522Xyq9_x9oKPbcTNIlbk6pBzZQDYovZ96yEc1Jcq9M6LPrGw3ghhHVL7y3aleWgZO2xOnnoE_-mKV_72FOkKzByTIgUxjEqqEfExnaUTjvqxe1GyrAnrKrpIeBsdfHnhe6jDpzdApL5jhsvvfZaVAPZWBpyQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-aggregate-mismatch", + "kind": "verify", + "status": "required", + "note": "Bound to a different aggregate hash", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "B+n+49Wh7ctIZho1+2Vv1TG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-legacy-binding-read-as-current", + "kind": "verify", + "status": "required", + "note": "Legacy assertionSig, read without the legacy encoding; never retried", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREkwTlRaa01EVmtObUV6WmpRNFlURTBZV1ZsWlRJd1l6aGtOV1l3TkRJNVpEQTNaVFZpWlRObU1UWTVOV1pqWldNeVpHSmpOelUxWTJRNVpEWTBORGsxIn0.f2JOSJUdlkekWVxIwj2miixPlMffQRS7b4Tj9FTOv1-RoNQd4PC454nFcWA9lGmB7PJck_8v5edFmm7rwLWayR4Ldg6yq-QrvX6_R7D7htpTm5IB52ToMIkqFFAqpfJOKUeefsGO7P9zJlF3Ma9jHMvFzlMZ44zJVKl7OsWDobgecQUYGWsil8l5fV__-eLd-JT5ZyvT_rbTDpZsi1omRViGvtNcu27TpkEhgqius2AXJdek_aV3zWZwmw-NDMMcRsfbd9Z3QZHSt9pLlh3ycATE1AfpA_mNllJzqgYPlERb584Qjggd1YFeketXD50rzW2Q8ma3pusIlGyPyv9tgg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-current-binding-read-as-legacy", + "kind": "verify", + "status": "required", + "note": "Current assertionSig, read with the legacy encoding; never retried", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": true, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-old-js-hash-of-extension", + "kind": "verify", + "status": "required", + "note": "Spec 4.4 extension example signed over the old JS hash that skips hash", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"extensions\":{\"hash\":\"deadbeef\",\"note\":\"coi\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiOWFiYmMzZTU3NGE5ZjkwZTQ1ZDQ1NTM3NjMxYzZhZWY4MjZlYTllNjc1NTE1ZWI2YTQwNDRhMWVlMjZlYTMwZiIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREthdThQbGRLbjVEa1hVVlRkakhHcnZnbTZwNW5WUlhyYWtCRW9lNG02akR3PT0ifQ.TFbfToUL2PCakJ-GvGTipETpI1ztbLYAYRr5k9wpKWmAjdUXHoD4ehElkxRY0nlEPpX9XsXK-c1tKx-HEh2-uLiwMeW-AMfKPN6PyhsCwlWmzNwC6XQGC5LhwN9nfZxLBCuSccI2egAa8UuIX04lT2S7W5eI7soNgXzNakQ5cdbkqWxXX8uhAukEQiOFKY6PabrYBsbioUzP2QQHRKA1LT6hramTF4CAjcAZ1dXlwCTo7g-4-uXnd6RSzK0JxzgIKlmoPXJixNLzf91zmFL92gnMk4wPadywmvw61mGFrO7mJLG3iYQJOJspYRrjvbFjUe_ruw3bjTAeEwm06cXDJw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-old-js-writer-statement-hash", + "kind": "verify", + "status": "required", + "note": "statement.hash on the wire, signed by the payload key over the old JS hash", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\",\"hash\":\"sha256:e3b0c44298fc1c149afbf4c8996fb924\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Gpdu2Q1de4ga4DEVRvXS2fhwFCk-SasHnrfNMiwtpxg\"}}", + "selectedKey": "hs256-payload-key", + "keySource": "payload", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-typed-reserialization-of-object-value", + "kind": "verify", + "status": "required", + "note": "ACP 240 label signed over a hash of the value stringified with sorted keys", + "input": "{\"id\":\"3f1c2d9e-8a4b-4c6d-9e0f-1a2b3c4d5e6f\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"encrypted\",\"statement\":{\"format\":\"json-structured\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":{\"@context\":{\"@base\":\"urn:nato:stanag:5636:A:1:elements:json\"},\"ocl\":{\"pol\":\"62c76c68-d73d-4628-8ccc-4c1e18118c22\",\"cls\":\"CONFIDENTIAL\",\"catl\":[{\"type\":\"P\",\"name\":\"Releasable To\",\"vals\":[\"USA\"]}],\"dcr\":\"2026-11-06T12:00:00Z\"}}},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiODg1MzkwZTFkM2JkMDI2MzQwOWE3ZDdjNjIwYzE2OWFmY2MyNzg2MTAxOTY3N2ViODA2Y2Q0ZGE0ODJkMmI5MyIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREtJVTVEaDA3MENZMENhZlh4aURCYWEvTUo0WVFHV2QrdUFiTlRhU0Mwcmt3PT0ifQ.jGxu0rJhqjYMwl1fcrOFIIvcEouigTumTh505oGOXPrgsV1eVPU0xNcjSSTWwEKtbFDiHVjcc-DiAkv6Kk4fhZDMaVlm6fDpA406gNXmXEGXDZoLqTgSA28H9XOTHdOBqy8YcZbkkmXoSmWsa0bMbuE0Yo2MF7o9D4EvF3Kmsrl5F8NeRa_gTcv7tVgGcFBEkawHtYscDdYebkl4VcPnyum7TnJbWQ4oWW2W43oBBfj9XoWNwQce0Sz5HNd0ZBMC9I2jJvHUUf7tlAFoJgGxuKXZx3LXZHQblUOgRKKP3mc1XnrF4iobt4sw9AuJjmQxyIiwTIjz3WDpYCqjzGu2MA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "binding-mismatch" + } + }, + { + "name": "verify-jws-header-nonzero-trailing-bits", + "kind": "verify", + "status": "required", + "note": "Header base64url with non-zero unused trailing bits", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9IB.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.FJy50H31jfIu5HWdNSMPAmBnYabJWxV7DiVfpYcEAyCMOdhhXgCYZRx03arwpnYqCAu41WrLIYvgK96DSvCQvQW1k2FJY7G6S2paFmZjKvNGt0-K24_jegqU4kkzifRl5nRv10OjoTuomZnKXnwlliYRNLenqYYzzPMdmdLPD4zQNG3AqipLYmB23x74vxJLzfOjAq3KIRakpKA8uMT2y5qreFTO_PlnVHoOUmXzg8sLkAy0WCcgYLMpVKptKJccJ0vc-m6c5HYMYPYNFOhBJNJ0DqcQuSleO_Xf0tDXUgKxeZmkCz7Bmq47k4f63un40ECLOpJf0pBpmzH4X0WyJg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-claims-nonzero-trailing-bits", + "kind": "verify", + "status": "required", + "note": "Claims base64url with non-zero unused trailing bits", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifR.eFL14k7xrGbVBAM9erNfBwnlPLav6MgznGyYc1_ZcVtfC7rXLYYkqzO46zGhqi7X3CPlaxjvKmlMlod7URhICYChszeffxCx5DDzEYe8QrbnEmI2dZDCvax-9UHoYpYwFW5wIZHR2s6zXjo2KN-Wwqyd3MNczmjwOVSacYr5l1N96EULmiMMrD2ED-m8nPxFoNeKZ5N8uqkNWZOEqCYUqvgEedEdHv7jpXLuVOxaizhQt3wNeB9ygJAe8v2gBOf9dZ9MwzctSZyAyvIO1rLUKjyKKgq3mWjliNUf1Qq2203Yt6_ZlKCUz0kj5pzjDBngrswV_fWQdz6CQ3pHc4NUqA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-header-duplicate-member", + "kind": "verify", + "status": "required", + "note": "Duplicate alg members in the protected header", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImFsZyI6IlJTMjU2In0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.fJywkAujZDJM_GkYnAtuRoXaMYyIPOT-jEhnsTCJ8ImN-bjI2P2GjVvqXeIRQNzYyYlWGvA75ZdSWWw3MEGHioJKU359NwNtEHBbBV_hsMD0LnI4dpeNwqzyUTMYPzQ_KVlynHc5LOQxD4FS67Dqrh7PcWLzP7GOikNP1tl7t1ri8l7v0b1_I10UgoDbLEAKgOcMXO9OjiIHIkZ5IxxPhu9f0dHgGPZAVqd5N3Y54YEzmSQgr7Q4GzqR1m165_LyNONC7-ffZ69j1b05MwJnOVQgu2uKMkZTMODw9M37JvkkFmTdkzGQtQ4ktC5WnW7rnB9JmZZa3PzKZtHmqufQfQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-claims-duplicate-member", + "kind": "verify", + "status": "required", + "note": "Duplicate assertionHash members in the claims", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvbkhhc2giOiI0NTZkMDVkNmEzZjQ4YTE0YWVlZTIwYzhkNWYwNDI5ZDA3ZTViZTNmMTY5NWZjZWMyZGJjNzU1Y2Q5ZDY0NDk1IiwiYXNzZXJ0aW9uU2lnIjoid05yOEgyZnVWYVVjU1NBaFc1TE5JREc5ZkRWdjg5eXBhU0VMdG1zVUJESkZiUVhXby9TS0ZLN3VJTWpWOEVLZEIrVytQeGFWL093dHZIVmMyZFpFbFE9PSJ9.Oq89Xrmat24Dz1kk2Odz2tiSYeS12rbG1JsVjFVUMNmPb8om8zFdbI_bQI48-gJ-89OQ8wRXMWPw6CL8FyWuWmjn7MVPPOYdeEv3hiZ2HuSjt2RiVKUlVXLQ6kT38jxTFVrIw_8eLZ42cw-PI_uLgpbF1TPWgB9xI58qbR_GZ0chFzfDYZCC9P4c8N4znOd9_KiQ8lCjQqJspDEfXk46HuQ88OXXPyeLRGwwg-L-zNcvgbeFbDoIfjnrm0bDTyTMFf20DYBl-23XqO4r3Wgj3D7RZtD7Qf3grEmhLXncdHZIBClT1DD9ivxlUqDnn2F0mFFhLg2M3_tWdIXWj4oazA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-binding" + } + }, + { + "name": "verify-jws-signature-nonzero-trailing-bits", + "kind": "verify", + "status": "required", + "note": "Signature base64url with non-zero unused trailing bits", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8R\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-jws-signature-not-base64url", + "kind": "verify", + "status": "required", + "note": "A signature segment outside the base64url alphabet", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9m+/==\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signature-invalid" + } + }, + { + "name": "verify-jwk-n-nonzero-trailing-bits", + "kind": "verify", + "status": "required", + "note": "The selected key's jwk with non-zero unused trailing bits in n; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiay1qeVZVUDFmTTJPeC1QZXQzdElMRThJSEJ3LU5uVC1PSFhfWlpjSE55WU5Xc3RucUFOMlYxb1JodWVTaXhuWktXdVBBanRXQXp1cURBd0JSRHFtanFTY3dHSXlXNmZyeVczcW5OZlhTR1FoVGJ2MXZ6elJFZWl3V0FGcXVFc0VacmNlS0FRR2hrQ2lzOGFWZTlDQll3QWppSGRXU2lRRlZPV1JxYk5xR01uWTVJOWVuYnU5dWhXVElubEtxMklmTTJvaExGeWJaa2dRT3QwUmRFaV9SNFF0Zkg1YmtDODV1b0xBZmQ5N2FiTGxfWk5kbmxuTGNFaVZhcVNRV0pxX2ZkeWZ6TE5PcVhkU1Zwa0Q1OGJQTkVzbGljamxSN1o2SF9ZTmRuamxxdTVESGtvaHZlek84TTF1QlpKTDF1RnhSd0FNb1VuMV90dDhkcVVMRjBpNTdSIiwiZSI6IkFRQUIifX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.f4MgLVNuogbo4goMZjzb4Oqu4TH87C7RzdX584w9kECx0Yhzf1S1tgRpJBf1ncl3k0THUOyJT-kvvkeedc1cfqbma6gx5mVfGe-7MpnCIe0o8K4Xpc4n8APX-3uLL6VmqCaIpM12bhcTK-hFM_Os3HyeR4UMvYtW0JjEEDP3UizowLwQJepRVkOltw7lJXo0fjX1MDzW61nU1qpTyT1RuN-3tW3IY_NYmQGlKp5Ce6CvIcymWn8DABg5n6l6lCem8nLjlznaykBNPrUQrYjeWp_wLRhwmcexKms2wmVZ68fLAohzkyljAVHEmleeRNCE0KEZT7RPAKMXXQt2DjNGuA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-n-leading-zero-octet", + "kind": "verify", + "status": "required", + "note": "The selected key's jwk with a leading zero octet in n; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoiQUpQbzhsVkQ5WHpOanNmajNyZDdTQ3hQQ0J3Y1BqWjBfamgxXzJXWEJ6Y21EVnJMWjZnRGRsZGFFWWJua29zWjJTbHJqd0k3VmdNN3Fnd01BVVE2cG82a25NQmlNbHVuNjhsdDZwelgxMGhrSVUyNzliODgwUkhvc0ZnQmFyaExCR2EzSGlnRUJvWkFvclBHbFh2UWdXTUFJNGgzVmtva0JWVGxrYW16YWhqSjJPU1BYcDI3dmJvVmt5SjVTcXRpSHpOcUlTeGNtMlpJRURyZEVYUkl2MGVFTFh4LVc1QXZPYnFDd0gzZmUybXk1ZjJUWFo1WnkzQklsV3Fra0ZpYXYzM2NuOHl6VHFsM1VsYVpBLWZHenpSTEpZbkk1VWUyZWhfMkRYWjQ1YXJ1UXg1S0liM3N6dkROYmdXU1M5YmhjVWNBREtGSjlmN2JmSGFsQ3hkSXVlMCIsImUiOiJBUUFCIn19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.SZ9U5VtjZFXmfC-X_vk9kG3qibuiDT_1XBCn6AP_u_9AxtELJSr6MJ2tqwAHcZBvLxUjYvO5ABSM_RBJ4msOeww2TR67oFg0cDZaA_P1uPzTnDHOhFmnbPCKiAvczQ7BxKCIfeGoQgCY3z9CoenhFjWOQtgOV4kzUfR6q1-HBx8RmSUyJwwbnb_vOoyUC5Dx_bVMr-FqEDF_WYbuCX74fswl9hlS_IYBTyC5rHkf1vya5SXbHhhyAhDE7LpJ1sJXuXKT69vX6ZiqI7MFMyXD9Lw7fJSslJaJdFeRzzqScE_OLpHb9zsOi1lReQ0izniITbPxSYV-N2A5iNHml-T0rg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-ec-short-x", + "kind": "verify", + "status": "required", + "note": "A P-256 jwk whose x is 31 octets; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjF6TGNXTzdnNGxvWjJYZ2RqZy13WWYyVXRvalZrVnJwbW8tQ2VSX3JQQSIsInkiOiJpcnRCQUwtenBKdXNxc01XQTZyU1NSY2RhV2Y3amplTjloeGdyeWt2RXRFIn19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.DCwIVfULZFc8495iZwBF-91b6K3Wrjj99pWt4WyT1kZ8oTy9OCRSrijsDlGBB0o4kdxsqLdFXbd2MVLZdsyS7LvG89l3m971yELYqjTIN_Cjfk38l5K0o1h0YkRyloCxT0TRx_862dBTvNBe7ZijT5E2bKrFWgAtLoOThB3C_gg4awmq_cxEpXJGPG0OUGKT-hZIvT_Y9qI3iS6AEMIQfc7eVQLIUzVK-e_i1U7m4-tVTe78K8jr4uaC2YZC6JvMMQJUha7sdNSl--clultc9VoDExK0v1xzXxS5msL-Hp0WIAZWqcX8Yp4R7FuMbZHIM0sRkTgMdSL1-PC3B2lDbg\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-ec-point-off-curve", + "kind": "verify", + "status": "required", + "note": "A P-256 jwk with exact-length coordinates that are not on the curve; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Inp0Y3kzRmp1NE9KYUdkbDRIWTRQc0dIOWxMYUkxWkZhNlpxUGdua2Y2enciLCJ5IjoiaXJ0QkFMLXpwSnVzcXNNV0E2clNTUmNkYVdmN2pqZU45aHhncnlrdkV0QSJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.dh7g6rTf9nZ3k596Efe57J1pBbU1fQlya6MYE6gvJK1eU9VWMk9eN4dhv4ZsZmatTrIlkLKWcxvTBye7ith5Z_ZSOzpuSB_RV-MtUDz3lyo7blqPcxSzm1JW7ecVd5RpOG7UPM-zLY8O8fgHjE7AR48NLIHCk9mFQtoRKynaQ5Pv__9y8ARhgHeBHCEzOzDZnhOhp8EOlr9beI9LU-_VZHsJGpK4W8CHMfybM4Im3uPBt6T9_C48YErhlrw52tTXd5FSngTcp06CocxDNcHiuZ-GXH-MbaBuElHCOfVAltN5NEcNtw0r837znRajkRXGoeWeDX_UAhrvYpk2EeWAIw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-jwk-ec-p256", + "kind": "verify", + "status": "required", + "note": "A well-formed P-256 jwk against the selected RSA key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Inp0Y3kzRmp1NE9KYUdkbDRIWTRQc0dIOWxMYUkxWkZhNlpxUGdua2Y2enciLCJ5IjoiaXJ0QkFMLXpwSnVzcXNNV0E2clNTUmNkYVdmN2pqZU45aHhncnlrdkV0RSJ9fQ.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.SSvxXEecbrcLKhx3BO0ImwaspkVIuQZD1tjfgKpb_nSbQ_YpLkxK07WGc-wtI9LVdNl9beg1aOrqJv_A54wfbGo-ExJ2a4XMm7IyjD3XVLEMifolgtMq-pFzNpce06qmwcqBSEOsnGhZ1_ItVdbDEAXuLd0GoVTvh7kpZZvfTZZuSMLw4btcVoPVDY0Kr6-KQeYFt5ya6jBnry-OTM7jmk3xRf0AM6R9nkN1QSNZbLMSXS0xi1bSLB7C79JdSFM3yar_-fDikG9HQ9s6a4Toi-zua_LQrxn2Utn1tAZdooiFrD9UUMUbHAk7gtLb1ps4C4kJHu7frQMdbEOqOdEiIA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signer-not-permitted" + } + }, + { + "name": "verify-jwk-okp-ed25519", + "kind": "verify", + "status": "required", + "note": "A well-formed Ed25519 jwk against the selected RSA key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6InlHS2Z5REJMdVk5eFJYSlNSUm9zOFdjT3BRdWZabFZIeklvbzNNRmNfZGMifX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ON2-TPdyiZJ1rMlB947ak3JdkuszyfjszO2eLCPzTvJo3Vj9uJGcWATcsMDZ-8O8HTdERQdoL-6Z5WQOLwUCv_fTv1WSt7NAYobL-0K95wHeqDnfRxu5SzbuphOCtaxRGBXxgbjlsgtlyERia3ej--Fvwjx48QCe7CJV5Hn59AqUIqGNryGanIIrGcKE_IzG9XGcu5w-YclJuRff_42-KbLDwJP1Pa_h3I7IAjmubvFdfUWETjUNo7ruVMlA3V-fIxAl_PToVxo32SF3tDNks7dxiVbC-kgn_oyWzmQgIoj2UHPCxju2LKpYdpub6wOFN9BaoITFrj0f_hG1yyGh7g\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "signer-not-permitted" + } + }, + { + "name": "verify-x5c-second-element-not-base64", + "kind": "verify", + "status": "required", + "note": "A valid leaf followed by an element that is not base64; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMGpDQ0FicWdBd0lCQWdJRVZTQUFBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWlNU0F3SGdZRFZRUUREQmRFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNqQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNJeElEQWVCZ05WQkFNTUYwUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2MybG5ibVZ5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SUxFOElIQncrTm5UK09IWC9aWmNITnlZTldzdG5xQU4yVjFvUmh1ZVNpeG5aS1d1UEFqdFdBenVxREF3QlJEcW1qcVNjd0dJeVc2ZnJ5VzNxbk5mWFNHUWhUYnYxdnp6UkVlaXdXQUZxdUVzRVpyY2VLQVFHaGtDaXM4YVZlOUNCWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpL1I0UXRmSDVia0M4NXVvTEFmZDk3YWJMbC9aTmRubG5MY0VpVmFxU1FXSnEvZmR5ZnpMTk9xWGRTVnBrRDU4YlBORXNsaWNqbFI3WjZIL1lOZG5qbHF1NURIa29odmV6TzhNMXVCWkpMMXVGeFJ3QU1vVW4xL3R0OGRxVUxGMGk1N1FJREFRQUJveEF3RGpBTUJnTlZIUk1CQWY4RUFqQUFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJORUNNLzBnTWFUbGhyMmIyNVczaG11dUttZGtYTkwzYW9pMm8wQ2Z3cmhudFhnNjFtVFNubW9hams1enNTVmsxcHF0b2pxbmpwVkY2dzQrSXNuWW40VW5CdlZFMHZwK0VJdStDa2lSRC8zTGwxZUF4QnNTMG9CRlNJQkQ1cm9sN0NNZHF6YTlGY0FMbnREazFzWWRxdTJxb3JmNzltc09zYzdJZncrQ1V0ZVE1QVU2cExEMlhyd1FGWGRTNjlTNHg4Rm9uS3Y1aDcwSFdwVTI4bjQranRRVEhRNVE1UEYxWWVTS3ljLzdZZUM4b2hJbDZqaEtIdllFd3ZUNVJYSG1HeWw4Z25uQXRydFFDL2lMRHRwRmE1NnRmYkNmU1JnZTIxMWpCRExjdWJxeHFpbTVHd3o5Z1dvZTJVdXI2QzJlWjEwQXVGRUp6VlhFUEQ3Qkl4SGdlbSIsIm5vdCBiYXNlNjQhIl19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.YXA_ree9whIkaLZHbPMdj5zg4ry6hJBEhD6OSwWjTmKvnKmq9l4x-IgyZDcaOMiP_9CL73GBYbyXO-w9W3CjGmOPXbODW-ml4O8Lt0_TXAOk7cqbtvk55iEZilQs3jOWuJzc_keHE0Qo7-bKpSrxo-a-23ar3l9wOLqulG0yofspjuB768G_rwfpblSjbiGJ0Ce8jtKE1JG2_tBBejZb60dY62sWyz-JKG2Zn_h7Rvfrr4RCGSSl9Ct8OJNOFBa90kAi1dBDwur-ZkOE8_DfJjgWSLvgpxGW2ffe2U4f_ukq7cHBIJ9M_3KrWhSx2vf336_H2om-UWt5YSDf60uZKA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-leaf-trailing-bytes", + "kind": "verify", + "status": "required", + "note": "The selected key's certificate with a byte after its DER; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMGpDQ0FicWdBd0lCQWdJRVZTQUFBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWlNU0F3SGdZRFZRUUREQmRFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNqQWVGdzB5TmpBeE1ERXdNREF3TURCYUZ3MHpOakF4TURFd01EQXdNREJhTUNJeElEQWVCZ05WQkFNTUYwUlRVRmd0TlRVeU1DQjJaV04wYjNJZ2MybG5ibVZ5TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFrK2p5VlVQMWZNMk94K1BldDN0SUxFOElIQncrTm5UK09IWC9aWmNITnlZTldzdG5xQU4yVjFvUmh1ZVNpeG5aS1d1UEFqdFdBenVxREF3QlJEcW1qcVNjd0dJeVc2ZnJ5VzNxbk5mWFNHUWhUYnYxdnp6UkVlaXdXQUZxdUVzRVpyY2VLQVFHaGtDaXM4YVZlOUNCWXdBamlIZFdTaVFGVk9XUnFiTnFHTW5ZNUk5ZW5idTl1aFdUSW5sS3EySWZNMm9oTEZ5YlprZ1FPdDBSZEVpL1I0UXRmSDVia0M4NXVvTEFmZDk3YWJMbC9aTmRubG5MY0VpVmFxU1FXSnEvZmR5ZnpMTk9xWGRTVnBrRDU4YlBORXNsaWNqbFI3WjZIL1lOZG5qbHF1NURIa29odmV6TzhNMXVCWkpMMXVGeFJ3QU1vVW4xL3R0OGRxVUxGMGk1N1FJREFRQUJveEF3RGpBTUJnTlZIUk1CQWY4RUFqQUFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJORUNNLzBnTWFUbGhyMmIyNVczaG11dUttZGtYTkwzYW9pMm8wQ2Z3cmhudFhnNjFtVFNubW9hams1enNTVmsxcHF0b2pxbmpwVkY2dzQrSXNuWW40VW5CdlZFMHZwK0VJdStDa2lSRC8zTGwxZUF4QnNTMG9CRlNJQkQ1cm9sN0NNZHF6YTlGY0FMbnREazFzWWRxdTJxb3JmNzltc09zYzdJZncrQ1V0ZVE1QVU2cExEMlhyd1FGWGRTNjlTNHg4Rm9uS3Y1aDcwSFdwVTI4bjQranRRVEhRNVE1UEYxWWVTS3ljLzdZZUM4b2hJbDZqaEtIdllFd3ZUNVJYSG1HeWw4Z25uQXRydFFDL2lMRHRwRmE1NnRmYkNmU1JnZTIxMWpCRExjdWJxeHFpbTVHd3o5Z1dvZTJVdXI2QzJlWjEwQXVGRUp6VlhFUEQ3Qkl4SGdlbUFBPT0iXX0.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.Xc6FD1Xr6cyEglxr8Cl4vKqtIsgNma4WYwDudkubYGYMA-SLlESJgYPJ4_PWCHKBAsPTOrvsblxCjYIfUImatcgkyoB19HuUS_jS8jrFcL8iPo-JgcpMNvCsOUVBAlvRsHCyVdJCzc-5zyh_AxnNRy__qq3wNHNNRNrJ_FjNT_FJ59af2G7drZdShJEqhyeudtX1QbcFHhsvjgnxFOOp_dfHRRagEKhg0HZckEmHLKq1peDcDuu6cdqlMugoD22I-9xj9Y98E9T5a_F7G17Nci2EuouKiz2i3Q2EWxxDsn47jaX_S_DHvcxt6OTVnYRYeF70Ty8gShkP3n3R7tCJkw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-leaf-pem-text", + "kind": "verify", + "status": "required", + "note": "base64 of the selected key's certificate as PEM text; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNd2FrTkRRV0p4WjBGM1NVSkJaMGxGVmxOQlFVRlVRVTVDWjJ0eGFHdHBSemwzTUVKQlVYTkdRVVJCYVUxVFFYZElaMWxFVmxGUlJFUkNaRVVLVlRGQ1dVeFVWVEZOYWtGblpHMVdhbVJIT1hsSlNFNXdXakkxYkdOcVFXVkdkekI1VG1wQmVFMUVSWGROUkVGM1RVUkNZVVozTUhwT2FrRjRUVVJGZHdwTlJFRjNUVVJDWVUxRFNYaEpSRUZsUW1kT1ZrSkJUVTFHTUZKVVZVWm5kRTVVVlhsTlEwSXlXbGRPTUdJelNXZGpNbXh1WW0xV2VVMUpTVUpKYWtGT0NrSm5hM0ZvYTJsSE9YY3dRa0ZSUlVaQlFVOURRVkU0UVUxSlNVSkRaMHREUVZGRlFXc3JhbmxXVlZBeFprMHlUM2dyVUdWME0zUkpURVU0U1VoQ2R5c0tUbTVVSzA5SVdDOWFXbU5JVG5sWlRsZHpkRzV4UVU0eVZqRnZVbWgxWlZOcGVHNWFTMWQxVUVGcWRGZEJlblZ4UkVGM1FsSkVjVzFxY1ZOamQwZEplUXBYTm1aeWVWY3pjVzVPWmxoVFIxRm9WR0oyTVhaNmVsSkZaV2wzVjBGR2NYVkZjMFZhY21ObFMwRlJSMmhyUTJsek9HRldaVGxEUWxsM1FXcHBTR1JYQ2xOcFVVWldUMWRTY1dKT2NVZE5ibGsxU1RsbGJtSjFPWFZvVjFSSmJteExjVEpKWmsweWIyaE1SbmxpV210blVVOTBNRkprUldrdlVqUlJkR1pJTldJS2EwTTROWFZ2VEVGbVpEazNZV0pNYkM5YVRtUnViRzVNWTBWcFZtRnhVMUZYU25FdlptUjVabnBNVGs5eFdHUlRWbkJyUkRVNFlsQk9SWE5zYVdOcWJBcFNOMW8yU0M5WlRtUnVhbXh4ZFRWRVNHdHZhSFpsZWs4NFRURjFRbHBLVERGMVJuaFNkMEZOYjFWdU1TOTBkRGhrY1ZWTVJqQnBOVGRSU1VSQlVVRkNDbTk0UVhkRWFrRk5RbWRPVmtoU1RVSkJaamhGUVdwQlFVMUJNRWREVTNGSFUwbGlNMFJSUlVKRGQxVkJRVFJKUWtGUlFrNUZRMDB2TUdkTllWUnNhSElLTW1JeU5WY3phRzExZFV0dFpHdFlUa3d6WVc5cE1tOHdRMlozY21odWRGaG5OakZ0VkZOdWJXOWhhbXMxZW5OVFZtc3hjSEYwYjJweGJtcHdWa1kyZHdvMEswbHpibGx1TkZWdVFuWldSVEIyY0N0RlNYVXJRMnRwVWtRdk0weHNNV1ZCZUVKelV6QnZRa1pUU1VKRU5YSnZiRGREVFdSeGVtRTVSbU5CVEc1MENrUnJNWE5aWkhGMU1uRnZjbVkzT1cxelQzTmpOMGxtZHl0RFZYUmxVVFZCVlRad1RFUXlXSEozVVVaWVpGTTJPVk0wZURoR2IyNUxkalZvTnpCSVYzQUtWVEk0YmpRcmFuUlJWRWhSTlZFMVVFWXhXV1ZUUzNsakx6ZFpaVU00YjJoSmJEWnFhRXRJZGxsRmQzWlVOVkpZU0cxSGVXdzRaMjV1UVhSeWRGRkRMd3BwVEVSMGNFWmhOVFowWm1KRFpsTlNaMlV5TVRGcVFrUk1ZM1ZpY1hoeGFXMDFSM2Q2T1dkWGIyVXlWWFZ5TmtNeVpWb3hNRUYxUmtWS2VsWllSVkJFQ2pkQ1NYaElaMlZ0Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIl19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.QHNsJ5LSfdMPOY2Y5u1zNX-IKco4RMqR31h1XeJtJpyUyL5TZ4gJoFD8yYt5NtILe8mEgcriXDi2Rtf-hWeBIQq6YskoJrJRLjAY8C6yJNafp8QiwgCG988sd-n_4UfOqbSR3eAaC9k4aBLmjqf9nfUlXCx8OEo9D_4hphBd1UnlWzqUb9OX_1nT5-sYxSqI4faUaq1rhfr-0Uf3IL7OzhVr38qDIxe_tEmvL_q8X3nnuNGiUVs4kRPS0DSx3Pgir8FwZksUo8YefLnYKZzabtcHQJAovyDn5wAEoeqz4oqxOlw2wkAlnvCH2esugOoQKVrsf_Nu3vFhKeeyGobupw\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-leaf-tlv-skeleton", + "kind": "verify", + "status": "required", + "note": "A leaf with only a certificate's TLV skeleton around the selected key; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlCT2pDQ0FURUNBUUV3QURBQU1BQXdBRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFKUG84bFZEOVh6TmpzZmozcmQ3U0N4UENCd2NQalowL2poMS8yV1hCemNtRFZyTFo2Z0RkbGRhRVlibmtvc1oyU2xyandJN1ZnTTdxZ3dNQVVRNnBvNmtuTUJpTWx1bjY4bHQ2cHpYMTBoa0lVMjc5Yjg4MFJIb3NGZ0JhcmhMQkdhM0hpZ0VCb1pBb3JQR2xYdlFnV01BSTRoM1Zrb2tCVlRsa2FtemFoakoyT1NQWHAyN3Zib1ZreUo1U3F0aUh6TnFJU3hjbTJaSUVEcmRFWFJJdjBlRUxYeCtXNUF2T2JxQ3dIM2ZlMm15NWYyVFhaNVp5M0JJbFdxa2tGaWF2MzNjbjh5elRxbDNVbGFaQStmR3p6UkxKWW5JNVVlMmVoLzJEWFo0NWFydVF4NUtJYjNzenZETmJnV1NTOWJoY1VjQURLRko5ZjdiZkhhbEN4ZEl1ZTBDQXdFQUFUQUFBd0VBIl19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.dHLbz6A96BY8cCaNyvtvM5bi0-6TjYSOX65GKDooubQUWYl5nYKm7HDx1i-KAo7y6zCUTszdF-6S9I2j92PVHjEc5KWlY2uk_-g3--aJf3zGgcuxHyk80XnhNh_0oUAusxR9DmG6PZNy5Y-qLwKTzpmiwBRb6sbSPqwwsPR5p_Lrqwzw_qtAsXMPTLsA5wQIThZsKRYdyhiZjC0QNyYDay0MEJVk9S0-7PJpXqY7NGuj-vqZy-k87BrhYSgG6goQJ4JI6KayZ7j8yQcJrAGBcN5PFLZLjf86tK_y6IGRnMWq-FhADHKk_GaWL79HNrFjoVEpY8m8qp4cvOcmILfDEA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-x5c-leaf-nonzero-trailing-bits", + "kind": "verify", + "status": "required", + "note": "A self-signed leaf of the selected key whose base64 sets a trailing bit; signed by the selected key", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlDMWpDQ0FiNmdBd0lCQWdJRVZTQUFBekFOQmdrcWhraUc5dzBCQVFzRkFEQWtNU0l3SUFZRFZRUUREQmxFVTFCWUxUVTFNakFnZG1WamRHOXlJSE5wWjI1bGNpQXlNQjRYRFRJMk1ERXdNVEF3TURBd01Gb1hEVE0yTURFd01UQXdNREF3TUZvd0pERWlNQ0FHQTFVRUF3d1pSRk5RV0MwMU5USXdJSFpsWTNSdmNpQnphV2R1WlhJZ01qQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUpQbzhsVkQ5WHpOanNmajNyZDdTQ3hQQ0J3Y1BqWjAvamgxLzJXWEJ6Y21EVnJMWjZnRGRsZGFFWWJua29zWjJTbHJqd0k3VmdNN3Fnd01BVVE2cG82a25NQmlNbHVuNjhsdDZwelgxMGhrSVUyNzliODgwUkhvc0ZnQmFyaExCR2EzSGlnRUJvWkFvclBHbFh2UWdXTUFJNGgzVmtva0JWVGxrYW16YWhqSjJPU1BYcDI3dmJvVmt5SjVTcXRpSHpOcUlTeGNtMlpJRURyZEVYUkl2MGVFTFh4K1c1QXZPYnFDd0gzZmUybXk1ZjJUWFo1WnkzQklsV3Fra0ZpYXYzM2NuOHl6VHFsM1VsYVpBK2ZHenpSTEpZbkk1VWUyZWgvMkRYWjQ1YXJ1UXg1S0liM3N6dkROYmdXU1M5YmhjVWNBREtGSjlmN2JmSGFsQ3hkSXVlMENBd0VBQWFNUU1BNHdEQVlEVlIwVEFRSC9CQUl3QURBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQUw1Ym5xdjFNc284bDArWGNQU04yQ1hwRVkxLys4MTdxRmF0cloycjBKRGVjcWxsZG16WG5kYm9RejZTZUViTnRFd0xUNzhtMHNEMzNMQjlYUWRzdkNLZXB5UW9xTDFvTGxJTW5lVVp6OWVoSEdwbmF3TTFzaDhzSjJ1dmhoQ1dkcGQrRTExV1c1Y3ZNNEsvL2ltaVhkZzA4VTE4THRYbGdMOGFXT0lyKzdWRFMyaXpKTUVDMFpaRnVqSVJLRVJ5aWE2MytRV0FPaEVRUEwwRTNYd2FONnlLR3pZWmlCRURwU0lNUDQ0eXBMaVQ2QmFjTElvL0FDc1h4WkVudjlzTkJBZHkzUURGclZ5U2w5Vkpmdll3ME9DekZwY1JDV2FSK3oyaFNaUk9Ec2QxWERTb0IvMmE0U2lCU0s3NXl6VTFKUFhnakdwNzBwL0hJMVJ2Mmc5QkVGaD09Il19.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.HMqUotNnqQf83EpoeKpbSM5Bsa-ZqPpQA7_0XIVu77kBwsYwmdYaARpJFK5rDsLHlwLiLzhs1-XUs0L-wiRpkrEpsfXNE5m-NEYWk3CoRz4m_WnKmNqRA1lhUuYVt83mRFHohWwhLpeH-B2f-h6e1GMlMIR6mndpxRnUasgWTHwxH27J_cte-E0FrlvfP8jXMHIce5FJRn_nS1otCkOciOH_3SE5RIpP7751y_FDPt-QwtR1Q8EVhMrrGks7UrFPD2I2x9pu6PTde8e20BPMLmIPSirHWvfzhcT6XyV48qfY5uSAV_vXJDtgd0695jIpQaL0_sWJSoDsjhmoh3ifXQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "invalid-embedded-key" + } + }, + { + "name": "verify-claim-exp-past-ignored", + "kind": "verify", + "status": "required", + "note": "An exp claim in the past is ignored", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0iLCJleHAiOjE1MDAwMDAwMDB9.WtcrahIcnPL7aPolHhew8ce-IqMBOGhel9ybmy8EXz47TbZttHJMRE5KCCa8j0yFaULrOtj1WhbS8FDY0vRST97Dt7hJfzjj8RZG4x4U0iD8-Nf2jPshdh2wk6kYYSOJZlVkn_YVyAGI8Kab4BDZJM14t0MCZN0S72W5VzTTYa-rxYbafogNW5PKKCirttjze8yOBkJtflhsODrftVYnYsBE36cuVgMz7xurTHzf613V-htO8jWV1iXiZnAh1xuDzRCYpVVTS9PuTY8WRMPSkvYUgzMtTe5G8bzVlT2TyG--ivLp--CFU9ZrrMXCx7yX6srqt9INn_nqk9_cwPvzDA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-claim-nbf-future-ignored", + "kind": "verify", + "status": "required", + "note": "An nbf claim in the future is ignored", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0iLCJuYmYiOjQxMDI0NDQ4MDB9.dqIfF75yC509sNcdCzPSIvZS529xjpc-INvnEWNmGwiq_2FWmDuDbFOYu13mRhzT-GH2CWGps1KyyOhxKwoDoGeZLU4v3_BcOmWOzX_S1QQSraHoeWmTFW6c7ZoiHDW3tWlP4cyKTgWZHYdfmbT_sp94tKpd09RJz6abMfh_AlyP_m-B5BHd0P19vS8otVOoXcDFjPKBduybNC7AGCcfreBkeL3rpFqCR7NP5jnrBqfHQo7GDSvBNgSk7j9H8RTh4a3r7FHDehoUXr96gM3GmHVhiqHwOKjfLQ0GOxXW83F77wmFGyuCrO2TtuVCtLAB8qVKX21bbx9TqWqkEOuWdQ\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-claim-aud-iss-ignored", + "kind": "verify", + "status": "required", + "note": "aud and iss claims naming some other party is ignored", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0iLCJhdWQiOiJodHRwczovL290aGVyLmV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9pc3N1ZXIuZXhhbXBsZS5jb20ifQ.GylUtTjIHq596ai6iIRErweeFlKoGkXV1z1LVQpFn6qh0mt3t9OMlM6yBW3-LUOBMue-TXONs4AAmQc-7Jf7eUvPuGKEYuylzGl2ZvKHko8urXNF29i4g6hL7viD_lFROKFOH0NPVBIopfs_feVO5bb9jUHec5Cpz5ch3EbGfNCObNc6ulpeahg3xScxn7yqWK1JlijqSV1emtWE4_JBPNYMsijKC-EHSiBKCQX-zLHrQFDzcz72YqGbDBNwVO8tz2NYM5_eq1GH8INVz1tlzFQVFuGLMLMYlfi1XNfiu081jmQOHIC8YG3fXL7wyPE85aXpwxV_10jYqkCmzk13QA\"}}", + "selectedKey": "rsa-signer", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "ok": true + } + }, + { + "name": "verify-configured-hs256-pem-secret", + "kind": "verify", + "status": "required", + "note": "A configured HS256 secret that is PEM text", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJIUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.RszhBRIX0Hg8XgKc78Tq0XqDUSZXXwkdvTfAMUliPWg\"}}", + "selectedKey": "hs256-pem-secret", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "configuration-error" + } + }, + { + "name": "verify-configured-alg-lowercase", + "kind": "verify", + "status": "required", + "note": "A configured key whose alg is rs256 rather than RS256", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "rs256-lowercase-alg", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "configuration-error" + } + }, + { + "name": "verify-configured-alg-unknown", + "kind": "verify", + "status": "required", + "note": "A configured key with an unknown alg", + "input": "{\"id\":\"classification\",\"type\":\"handling\",\"scope\":\"payload\",\"appliesToState\":\"unencrypted\",\"statement\":{\"format\":\"json+stanag5636\",\"schema\":\"urn:nato:stanag:5636:A:1:elements:json\",\"value\":\"{\\\"ocl\\\":{\\\"pol\\\":\\\"62c76c68-d73d-4628-8ccc-4c1e18118c22\\\",\\\"cls\\\":\\\"SECRET\\\",\\\"dcr\\\":\\\"2024-10-21T20:47:36Z\\\"}}\"},\"binding\":{\"method\":\"jws\",\"signature\":\"eyJhbGciOiJSUzI1NiJ9.eyJhc3NlcnRpb25IYXNoIjoiNDU2ZDA1ZDZhM2Y0OGExNGFlZWUyMGM4ZDVmMDQyOWQwN2U1YmUzZjE2OTVmY2VjMmRiYzc1NWNkOWQ2NDQ5NSIsImFzc2VydGlvblNpZyI6IndOcjhIMmZ1VmFVY1NTQWhXNUxOSURHOWZEVnY4OXlwYVNFTHRtc1VCREpGYlFYV28vU0tGSzd1SU1qVjhFS2RCK1crUHhhVi9Pd3R2SFZjMmRaRWxRPT0ifQ.ZIGt3ok_eFgcVspnUPZKK-l_yQG161Sqc-L9b_u1x6_icSMqylFmS1etXZOJj-HVulDasqMCkgK1QL9R8VUYBKcIgEBTZGEA0P7xsWpdN7VMsEUr7ZiTBu8HJDilfmvds5yTCJf-ftgaklPJ6bmUdC7jmd4phBFZCftF8BzZIWDiZzyt7vNRZskrq-k2duXNL9MGflxBUl-1Zi7SeHJST6kMKEumxuGMyfDsyBRvKafUrYk1LFtBIfpCu-rb3fKm3OkY3IpUTVpwxV4y0XiHgLmgPXguPi_nDgUw8pcc8I5AU5iFnIqr-W3cyf98M54fjMvjIHIZ0zw3FKUa9msu8Q\"}}", + "selectedKey": "unknown-alg", + "keySource": "configured", + "aggregateHashBase64": "wNr8H2fuVaUcSSAhW5LNIDG9fDVv89ypaSELtmsUBDI=", + "legacy": false, + "expect": { + "reason": "configuration-error" + } + } + ] +}