diff options
author | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-11-01 13:44:42 +0100 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahooinc.com> | 2022-11-01 14:43:54 +0100 |
commit | f59b56ae4b8fafc67ec1828f03ce3178afaf037d (patch) | |
tree | 37be6e743672efbd4816ad39cb05ab46cad66e0a | |
parent | 43803ae25a68b4708f5846b7021e1dc3b68a82c6 (diff) |
Let token key IDs be UTF-8 byte strings instead of just an integer
This makes key IDs vastly more expressive. Max size is 255 bytes,
and UTF-8 form is enforced by checking that the byte sequence can be
identity-transformed to and from a string with UTF-8 encoding.
In addition, we now protect the integrity of the key ID by supplying
it as the AAD parameter to the key sealing and opening operations.
Reduce v1 token max length of `enc` part to 255, since this is always
an X25519 public key, which is never bigger than 32 bytes (but may
be _less_ if the random `BigInteger` is small enough, so we still have
to encode the length).
7 files changed, 143 insertions, 53 deletions
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java index 3906c13c4a4..be4ee657292 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoredumpHandlerTest.java @@ -299,7 +299,7 @@ public class CoredumpHandlerTest { private static SecretSharedKey makeFixedSecretSharedKey() { byte[] keyBytes = bytesOf("very secret yes!"); // 128 bits var secretKey = new SecretKeySpec(keyBytes, "AES"); - int keyId = 123; + byte[] keyId = bytesOf("the shiniest key"); // We don't parse any of these fields in the test, so just use dummy contents. byte[] enc = bytesOf("hello world"); byte[] ciphertext = bytesOf("imaginary ciphertext"); diff --git a/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java index 9c379b67a23..fe1be85539c 100644 --- a/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java +++ b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java @@ -2,8 +2,12 @@ package com.yahoo.security; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Base64; +import static com.yahoo.security.ArrayUtils.fromUtf8Bytes; +import static com.yahoo.security.ArrayUtils.toUtf8Bytes; + /** * A SealedSharedKey represents the public part of a secure one-way ephemeral key exchange. * @@ -15,29 +19,37 @@ import java.util.Base64; * This token representation is expected to be used as a convenient serialization * form when communicating shared keys. */ -public record SealedSharedKey(int keyId, byte[] enc, byte[] ciphertext) { +public record SealedSharedKey(byte[] keyId, byte[] enc, byte[] ciphertext) { /** Current encoding version of opaque sealed key tokens. Must be less than 256. */ public static final int CURRENT_TOKEN_VERSION = 1; + public static final int MAX_KEY_ID_UTF8_LENGTH = 255; + /** Encryption context for v1 tokens is always a 32-byte X25519 public key */ + public static final int MAX_ENC_CONTEXT_LENGTH = 255; - private static final int MAX_ENC_CONTEXT_LENGTH = Short.MAX_VALUE; + public SealedSharedKey { + if (keyId.length > MAX_KEY_ID_UTF8_LENGTH) { + throw new IllegalArgumentException("Key ID is too large to be encoded (max is %d, got %d)" + .formatted(MAX_KEY_ID_UTF8_LENGTH, keyId.length)); + } + verifyByteStringRoundtripsAsValidUtf8(keyId); + if (enc.length > MAX_ENC_CONTEXT_LENGTH) { + throw new IllegalArgumentException("Encryption context is too large to be encoded (max is %d, got %d)" + .formatted(MAX_ENC_CONTEXT_LENGTH, enc.length)); + } + } /** * Creates an opaque URL-safe string token that contains enough information to losslessly * reconstruct the SealedSharedKey instance when passed verbatim to fromTokenString(). */ public String toTokenString() { - if (keyId >= (1 << 24)) { - throw new IllegalArgumentException("Key id is too large to be encoded"); - } - if (enc.length > MAX_ENC_CONTEXT_LENGTH) { - throw new IllegalArgumentException("Encryption context is too large to be encoded"); - } - - // i32 header || i16 length(enc) || enc || ciphertext - ByteBuffer encoded = ByteBuffer.allocate(4 + 2 + enc.length + ciphertext.length); - encoded.putInt((CURRENT_TOKEN_VERSION << 24) | keyId); - encoded.putShort((short)enc.length); + // u8 token version || u8 length(key id) || key id || u8 length(enc) || enc || ciphertext + ByteBuffer encoded = ByteBuffer.allocate(1 + 1 + keyId.length + 1 + enc.length + ciphertext.length); + encoded.put((byte)CURRENT_TOKEN_VERSION); + encoded.put((byte)keyId.length); + encoded.put(keyId); + encoded.put((byte)enc.length); encoded.put(enc); encoded.put(ciphertext); encoded.flip(); @@ -53,27 +65,37 @@ public record SealedSharedKey(int keyId, byte[] enc, byte[] ciphertext) { */ public static SealedSharedKey fromTokenString(String tokenString) { byte[] rawTokenBytes = Base64.getUrlDecoder().decode(tokenString); - if (rawTokenBytes.length < 4) { - throw new IllegalArgumentException("Decoded token too small to contain a header"); + if (rawTokenBytes.length < 1) { + throw new IllegalArgumentException("Decoded token too small to contain a version"); } ByteBuffer decoded = ByteBuffer.wrap(rawTokenBytes); - int versionAndKeyId = decoded.getInt(); - int version = versionAndKeyId >>> 24; + // u8 token version || u8 length(key id) || key id || u8 length(enc) || enc || ciphertext + int version = Byte.toUnsignedInt(decoded.get()); if (version != CURRENT_TOKEN_VERSION) { throw new IllegalArgumentException("Token had unexpected version. Expected %d, was %d" .formatted(CURRENT_TOKEN_VERSION, version)); } - short encLen = decoded.getShort(); - if (encLen <= 0) { - throw new IllegalArgumentException("Token encryption context does not have a valid length"); - } + int keyIdLen = Byte.toUnsignedInt(decoded.get()); + byte[] keyId = new byte[keyIdLen]; + decoded.get(keyId); + verifyByteStringRoundtripsAsValidUtf8(keyId); + int encLen = Byte.toUnsignedInt(decoded.get()); byte[] enc = new byte[encLen]; decoded.get(enc); byte[] ciphertext = new byte[decoded.remaining()]; decoded.get(ciphertext); - int keyId = versionAndKeyId & 0xffffff; return new SealedSharedKey(keyId, enc, ciphertext); } + private static void verifyByteStringRoundtripsAsValidUtf8(byte[] byteStr) { + String asStr = fromUtf8Bytes(byteStr); // Replaces bad chars with a placeholder + byte[] asBytes = toUtf8Bytes(asStr); + if (!Arrays.equals(byteStr, asBytes)) { + throw new IllegalArgumentException("Key ID is not valid normalized UTF-8"); + } + } + + public int tokenVersion() { return CURRENT_TOKEN_VERSION; } + } diff --git a/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java b/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java index f9a16a78013..47936dab114 100644 --- a/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java +++ b/security-utils/src/main/java/com/yahoo/security/SharedKeyGenerator.java @@ -60,18 +60,17 @@ public class SharedKeyGenerator { } } - public static SecretSharedKey generateForReceiverPublicKey(PublicKey receiverPublicKey, int keyId) { + public static SecretSharedKey generateForReceiverPublicKey(PublicKey receiverPublicKey, byte[] keyId) { var secretKey = generateRandomSecretAesKey(); - // TODO do we want to tie the key ID to the sealing via AAD? - var sealed = HPKE.sealBase((XECPublicKey) receiverPublicKey, EMPTY_BYTES, EMPTY_BYTES, secretKey.getEncoded()); + // We protect the integrity of the key ID by passing it as AAD. + var sealed = HPKE.sealBase((XECPublicKey) receiverPublicKey, EMPTY_BYTES, keyId, secretKey.getEncoded()); var sealedSharedKey = new SealedSharedKey(keyId, sealed.enc(), sealed.ciphertext()); return new SecretSharedKey(secretKey, sealedSharedKey); } public static SecretSharedKey fromSealedKey(SealedSharedKey sealedKey, PrivateKey receiverPrivateKey) { - // TODO do we want to tie the key ID to the opening via AAD? byte[] secretKeyBytes = HPKE.openBase(sealedKey.enc(), (XECPrivateKey) receiverPrivateKey, - EMPTY_BYTES, EMPTY_BYTES, sealedKey.ciphertext()); + EMPTY_BYTES, sealedKey.keyId(), sealedKey.ciphertext()); return new SecretSharedKey(new SecretKeySpec(secretKeyBytes, "AES"), sealedKey); } diff --git a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java index 3a1952da49d..f635121eda0 100644 --- a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java +++ b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java @@ -10,18 +10,23 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; import static com.yahoo.security.ArrayUtils.hex; +import static com.yahoo.security.ArrayUtils.toUtf8Bytes; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; public class SharedKeyTest { + private static final byte[] KEY_ID_1 = new byte[] {1}; + @Test void generated_secret_key_is_128_bit_aes() { var receiverKeyPair = KeyUtils.generateX25519KeyPair(); - var shared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), 1); + var shared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), KEY_ID_1); var secret = shared.secretKey(); assertEquals(secret.getAlgorithm(), "AES"); assertEquals(secret.getEncoded().length, 16); @@ -31,7 +36,7 @@ public class SharedKeyTest { void sealed_shared_key_can_be_exchanged_via_token_and_computes_identical_secret_key_at_receiver() { var receiverKeyPair = KeyUtils.generateX25519KeyPair(); - var myShared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), 1); + var myShared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), KEY_ID_1); var publicToken = myShared.sealedSharedKey().toTokenString(); var theirSealed = SealedSharedKey.fromTokenString(publicToken); @@ -44,15 +49,16 @@ public class SharedKeyTest { void token_v1_representation_is_stable() { var receiverPrivate = KeyUtils.fromBase64EncodedX25519PrivateKey("4qGcntygFn_a3uqeBa1PbDlygQ-cpOuNznTPIz9ftWE"); var receiverPublic = KeyUtils.fromBase64EncodedX25519PublicKey( "ROAH_S862tNMpbJ49lu1dPXFCPHFIXZK30pSrMZEmEg"); + byte[] keyId = toUtf8Bytes("my key ID"); // Token generated for the above receiver public key, with the below expected shared secret (in hex) - var publicToken = "AQAAAQAgwyxd7bFNQB_2LdL3bw-xFlvrxXhs7WWNVCKZ4EFeNVtu42JMwM74bMN4E46v6mYcfQNPzcMGaP22Wl2cTnji0A"; - var expectedSharedSecret = "85ac3c7c3a930a19334cb73e02779733"; + var publicToken = "AQlteSBrZXkgSUQgAtTxJJdmv3eUoW5Z3NJSdZ3poKPEkW0SJOGQXP6CaC5XfyAVoUlK_NyYIMsJKyNYKU6WmagZpVG2zQGFJoqiFA"; + var expectedSharedSecret = "1b33b4dcd6a94e5a4a1ee6d208197d01"; var theirSealed = SealedSharedKey.fromTokenString(publicToken); var theirShared = SharedKeyGenerator.fromSealedKey(theirSealed, receiverPrivate); - assertEquals(1, theirSealed.keyId()); + assertArrayEquals(keyId, theirSealed.keyId()); assertEquals(expectedSharedSecret, hex(theirShared.secretKey().getEncoded())); } @@ -60,19 +66,74 @@ public class SharedKeyTest { void unrelated_private_key_cannot_decrypt_shared_secret_key() { var aliceKeyPair = KeyUtils.generateX25519KeyPair(); var eveKeyPair = KeyUtils.generateX25519KeyPair(); - var bobShared = SharedKeyGenerator.generateForReceiverPublicKey(aliceKeyPair.getPublic(), 1); + var bobShared = SharedKeyGenerator.generateForReceiverPublicKey(aliceKeyPair.getPublic(), KEY_ID_1); assertThrows(RuntimeException.class, // TODO consider distinct exception class () -> SharedKeyGenerator.fromSealedKey(bobShared.sealedSharedKey(), eveKeyPair.getPrivate())); } @Test - void token_carries_key_id_as_metadata() { - int keyId = 12345; + void token_carries_opaque_key_id_bytes_as_metadata() { + byte[] keyId = toUtf8Bytes("hello key id world"); var keyPair = KeyUtils.generateX25519KeyPair(); var myShared = SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), keyId); var publicToken = myShared.sealedSharedKey().toTokenString(); var theirShared = SealedSharedKey.fromTokenString(publicToken); - assertEquals(theirShared.keyId(), keyId); + assertArrayEquals(theirShared.keyId(), keyId); + } + + @Test + void key_id_integrity_is_protected_by_aad() { + byte[] goodId = toUtf8Bytes("my key 1"); + var keyPair = KeyUtils.generateX25519KeyPair(); + var myShared = SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), goodId); + var mySealed = myShared.sealedSharedKey(); + byte[] badId = toUtf8Bytes("my key 2"); + + var tamperedShared = new SealedSharedKey(badId, mySealed.enc(), mySealed.ciphertext()); + // Should not be able to unseal the token since the AAD auth tag won't be correct + assertThrows(RuntimeException.class, // TODO consider distinct exception class + () -> SharedKeyGenerator.fromSealedKey(tamperedShared, keyPair.getPrivate())); + } + + @Test + void key_id_encoding_size_is_bounded() { + byte[] okId = new byte[SealedSharedKey.MAX_KEY_ID_UTF8_LENGTH]; + Arrays.fill(okId, (byte)'A'); + var keyPair = KeyUtils.generateX25519KeyPair(); + var myShared = SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), okId); + assertArrayEquals(okId, myShared.sealedSharedKey().keyId()); + + var asToken = myShared.sealedSharedKey().toTokenString(); + var decoded = SealedSharedKey.fromTokenString(asToken); + assertArrayEquals(okId, decoded.keyId()); + + byte[] tooBigId = new byte[SealedSharedKey.MAX_KEY_ID_UTF8_LENGTH + 1]; + Arrays.fill(tooBigId, (byte)'A'); + assertThrows(IllegalArgumentException.class, + () -> SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), tooBigId)); + } + + @Test + void malformed_utf8_key_id_is_rejected_on_construction() { + byte[] malformedId = new byte[]{ (byte)0xC0 }; // First part of a 2-byte continuation without trailing byte + var keyPair = KeyUtils.generateX25519KeyPair(); + assertThrows(IllegalArgumentException.class, + () -> SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), malformedId)); + } + + // TODO make this test less implementation specific if possible... + @Test + void malformed_utf8_key_id_is_rejected_on_parsing() { + byte[] goodId = new byte[] { (byte)'A' }; + var keyPair = KeyUtils.generateX25519KeyPair(); + var myShared = SharedKeyGenerator.generateForReceiverPublicKey(keyPair.getPublic(), goodId); + + // token header is u8 version || u8 key id length || key id bytes ... + // Since the key ID is only 1 bytes long, patch it with a bad UTF-8 value (see above test) + byte[] tokenBytes = Base64.getUrlDecoder().decode(myShared.sealedSharedKey().toTokenString()); + tokenBytes[2] = (byte)0xC0; + var tokenStr = Base64.getUrlEncoder().encodeToString(tokenBytes); + assertThrows(IllegalArgumentException.class, () -> SealedSharedKey.fromTokenString(tokenStr)); } static byte[] streamEncryptString(String data, SecretSharedKey secretSharedKey) throws IOException { @@ -108,7 +169,7 @@ public class SharedKeyTest { @Test void can_create_symmetric_ciphers_from_shared_secret_key_and_public_keys() throws Exception { var receiverKeyPair = KeyUtils.generateX25519KeyPair(); - var myShared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), 1); + var myShared = SharedKeyGenerator.generateForReceiverPublicKey(receiverKeyPair.getPublic(), KEY_ID_1); String terrifyingSecret = "birds are not real D:"; byte[] encrypted = streamEncryptString(terrifyingSecret, myShared); diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java index 23543486e1b..af59784bfe0 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java @@ -13,9 +13,12 @@ import org.apache.commons.cli.Option; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import static com.yahoo.security.ArrayUtils.toUtf8Bytes; + /** * Tooling for decrypting a file using a private key that corresponds to the public key used * to originally encrypt the file. @@ -85,16 +88,19 @@ public class DecryptTool implements Tool { } var inputArg = leftoverArgs[0]; var maybeKeyId = Optional.ofNullable(arguments.hasOption(KEY_ID_OPTION) - ? Integer.parseInt(arguments.getOptionValue(KEY_ID_OPTION)) + ? arguments.getOptionValue(KEY_ID_OPTION) : null); var outputArg = CliUtils.optionOrThrow(arguments, OUTPUT_FILE_OPTION); var privKeyPath = Paths.get(CliUtils.optionOrThrow(arguments, RECIPIENT_PRIVATE_KEY_FILE_OPTION)); var tokenString = CliUtils.optionOrThrow(arguments, TOKEN_OPTION); var sealedSharedKey = SealedSharedKey.fromTokenString(tokenString.strip()); - if (maybeKeyId.isPresent() && (maybeKeyId.get() != sealedSharedKey.keyId())) { - throw new IllegalArgumentException(("Key ID specified with --key-id (%d) does not match key ID " + - "used when generating the supplied token (%d)") - .formatted(maybeKeyId.get(), sealedSharedKey.keyId())); + if (maybeKeyId.isPresent()) { + byte[] myKeyIdBytes = toUtf8Bytes(maybeKeyId.get()); + if (!Arrays.equals(myKeyIdBytes, sealedSharedKey.keyId())) { + // Don't include raw key bytes array verbatim in message (may contain control chars etc). + throw new IllegalArgumentException("Key ID specified with --key-id does not match key ID " + + "used when generating the supplied token"); + } } var privateKey = KeyUtils.fromBase64EncodedX25519PrivateKey(Files.readString(privKeyPath).strip()); var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey); diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java index 5437e8cf9fe..cb16151c9b6 100644 --- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java +++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java @@ -14,6 +14,8 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; +import static com.yahoo.security.ArrayUtils.toUtf8Bytes; + /** * Tooling to encrypt a file using a public key, emitting a non-secret token that can be * passed on to a recipient holding the corresponding private key. @@ -77,7 +79,7 @@ public class EncryptTool implements Tool { var outputPath = Paths.get(CliUtils.optionOrThrow(arguments, OUTPUT_FILE_OPTION)); var recipientPubKey = KeyUtils.fromBase64EncodedX25519PublicKey(CliUtils.optionOrThrow(arguments, RECIPIENT_PUBLIC_KEY_OPTION).strip()); - int keyId = Integer.parseInt(CliUtils.optionOrThrow(arguments, KEY_ID_OPTION)); + var keyId = toUtf8Bytes(CliUtils.optionOrThrow(arguments, KEY_ID_OPTION)); var shared = SharedKeyGenerator.generateForReceiverPublicKey(recipientPubKey, keyId); var cipher = SharedKeyGenerator.makeAesGcmEncryptionCipher(shared); diff --git a/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java b/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java index 55d0a10e660..a3651888441 100644 --- a/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java +++ b/vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java @@ -144,10 +144,10 @@ public class CryptoToolsTest { private static final String TEST_PRIV_KEY = "4qGcntygFn_a3uqeBa1PbDlygQ-cpOuNznTPIz9ftWE"; private static final String TEST_PUB_KEY = "ROAH_S862tNMpbJ49lu1dPXFCPHFIXZK30pSrMZEmEg"; - // Token created for the above public key (matching the above private key), using key id 1 - private static final String TEST_TOKEN = "AQAAAQAgwyxd7bFNQB_2LdL3bw-xFlvrxXhs7WWNVCKZ4" + - "EFeNVtu42JMwM74bMN4E46v6mYcfQNPzcMGaP22Wl2cTnji0A"; - private static final int TEST_TOKEN_KEY_ID = 1; + // Token created for the above public key (matching the above private key), using key id "my key ID" + private static final String TEST_TOKEN = "AQlteSBrZXkgSUQgAtTxJJdmv3eUoW5Z3NJSdZ3poKPEkW0SJOG" + + "QXP6CaC5XfyAVoUlK_NyYIMsJKyNYKU6WmagZpVG2zQGFJoqiFA"; + private static final String TEST_TOKEN_KEY_ID = "my key ID"; @Test void encrypt_fails_with_error_message_if_no_input_file_is_given() throws IOException { @@ -177,7 +177,7 @@ public class CryptoToolsTest { "--output-file", "foo", "--recipient-private-key-file", absPathOf(privKeyFile), "--token", TEST_TOKEN, - "--key-id", Integer.toString(TEST_TOKEN_KEY_ID)), + "--key-id", TEST_TOKEN_KEY_ID), "Invalid command line arguments: Expected exactly 1 file argument to decrypt\n"); } @@ -191,7 +191,7 @@ public class CryptoToolsTest { "--output-file", "foo", "--recipient-private-key-file", absPathOf(privKeyFile), "--token", TEST_TOKEN, - "--key-id", Integer.toString(TEST_TOKEN_KEY_ID)), + "--key-id", TEST_TOKEN_KEY_ID), "Invalid command line arguments: Input file 'no-such-file' does not exist\n"); } @@ -208,9 +208,9 @@ public class CryptoToolsTest { "--output-file", "foo", "--recipient-private-key-file", absPathOf(privKeyFile), "--token", TEST_TOKEN, - "--key-id", Integer.toString(TEST_TOKEN_KEY_ID + 1)), - "Invalid command line arguments: Key ID specified with --key-id (2) does not match " + - "key ID used when generating the supplied token (1)\n"); + "--key-id", TEST_TOKEN_KEY_ID + "-wrong"), + "Invalid command line arguments: Key ID specified with --key-id does not " + + "match key ID used when generating the supplied token\n"); } @Test |