summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahooinc.com>2022-11-09 12:26:07 +0100
committerTor Brede Vekterli <vekterli@yahooinc.com>2022-11-09 12:50:23 +0100
commitc395f42fa83a3d6e57a76f0691b487b9382b5570 (patch)
treee092b89bb92d5e13bbd0910b810fa8397fc7a177
parent30a676fb89f28a618f0dc2c0752cde8c29bf320c (diff)
Use Base62 for tokens and Base58 for keys
* Base62 minimizes extra size overhead relative to Base64. * Base58 removes ambiguous characters from key encodings. Common for both bases is that they do not emit any characters that interfer with easily selecting them on web pages or in the CLI.
-rw-r--r--security-utils/src/main/java/com/yahoo/security/KeyUtils.java29
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java18
-rw-r--r--security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java12
-rw-r--r--security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java7
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/DecryptTool.java7
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/EncryptTool.java6
-rw-r--r--vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/KeygenTool.java6
-rw-r--r--vespaclient-java/src/test/java/com/yahoo/vespa/security/tool/CryptoToolsTest.java8
-rw-r--r--vespaclient-java/src/test/resources/expected-decrypt-help-output.txt3
-rw-r--r--vespaclient-java/src/test/resources/expected-encrypt-help-output.txt2
-rw-r--r--vespaclient-java/src/test/resources/expected-keygen-help-output.txt2
11 files changed, 70 insertions, 30 deletions
diff --git a/security-utils/src/main/java/com/yahoo/security/KeyUtils.java b/security-utils/src/main/java/com/yahoo/security/KeyUtils.java
index cef0dd9a62e..47055a65618 100644
--- a/security-utils/src/main/java/com/yahoo/security/KeyUtils.java
+++ b/security-utils/src/main/java/com/yahoo/security/KeyUtils.java
@@ -280,6 +280,25 @@ public class KeyUtils {
return Base64.getUrlEncoder().withoutPadding().encodeToString(toRawX25519PublicKeyBytes(publicKey));
}
+ // This sanity check is to avoid any DoS potential caused by passing in a very large key
+ // to a quadratic Base58 decoding routing. We don't do this for the encoding since we
+ // always control the input size for that case.
+ private static void verifyB58InputSmallEnoughToBeX25519Key(String key) {
+ if (key.length() > 64) { // a very wide margin...
+ throw new IllegalArgumentException("Input Base58 is too large to represent an X25519 key");
+ }
+ }
+
+ public static XECPublicKey fromBase58EncodedX25519PublicKey(String base58pk) {
+ verifyB58InputSmallEnoughToBeX25519Key(base58pk);
+ byte[] rawKeyBytes = Base58.codec().decode(base58pk);
+ return fromRawX25519PublicKey(rawKeyBytes);
+ }
+
+ public static String toBase58EncodedX25519PublicKey(XECPublicKey publicKey) {
+ return Base58.codec().encode(toRawX25519PublicKeyBytes(publicKey));
+ }
+
public static XECPrivateKey fromRawX25519PrivateKey(byte[] rawScalarBytes) {
try {
NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
@@ -309,6 +328,16 @@ public class KeyUtils {
return Base64.getUrlEncoder().withoutPadding().encodeToString(toRawX25519PrivateKeyBytes(privateKey));
}
+ public static XECPrivateKey fromBase58EncodedX25519PrivateKey(String base58pk) {
+ verifyB58InputSmallEnoughToBeX25519Key(base58pk);
+ byte[] rawKeyBytes = Base58.codec().decode(base58pk);
+ return fromRawX25519PrivateKey(rawKeyBytes);
+ }
+
+ public static String toBase58EncodedX25519PrivateKey(XECPrivateKey privateKey) {
+ return Base58.codec().encode(toRawX25519PrivateKeyBytes(privateKey));
+ }
+
// TODO unify with generateKeypair()?
public static KeyPair generateX25519KeyPair() {
try {
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 a921b3baf87..65f149579f4 100644
--- a/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java
+++ b/security-utils/src/main/java/com/yahoo/security/SealedSharedKey.java
@@ -2,11 +2,6 @@
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.
@@ -51,7 +46,7 @@ public record SealedSharedKey(KeyId keyId, byte[] enc, byte[] ciphertext) {
byte[] encBytes = new byte[encoded.remaining()];
encoded.get(encBytes);
- return Base64.getUrlEncoder().withoutPadding().encodeToString(encBytes);
+ return Base62.codec().encode(encBytes);
}
/**
@@ -59,7 +54,8 @@ public record SealedSharedKey(KeyId keyId, byte[] enc, byte[] ciphertext) {
* created by a call to toTokenString().
*/
public static SealedSharedKey fromTokenString(String tokenString) {
- byte[] rawTokenBytes = Base64.getUrlDecoder().decode(tokenString);
+ verifyInputTokenStringNotTooLarge(tokenString);
+ byte[] rawTokenBytes = Base62.codec().decode(tokenString);
if (rawTokenBytes.length < 1) {
throw new IllegalArgumentException("Decoded token too small to contain a version");
}
@@ -84,4 +80,12 @@ public record SealedSharedKey(KeyId keyId, byte[] enc, byte[] ciphertext) {
public int tokenVersion() { return CURRENT_TOKEN_VERSION; }
+ private static void verifyInputTokenStringNotTooLarge(String tokenString) {
+ // Expected max decoded size for v1 is 3 + 255 + 32 + 32 = 322. For simplicity, round this
+ // up to 512 to effectively not have to care about the overhead of any reasonably chosen encoding.
+ if (tokenString.length() > 512) {
+ throw new IllegalArgumentException("Token string is too long to possibly be a valid token");
+ }
+ }
+
}
diff --git a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java
index dc6078c58b7..f44eadc59d4 100644
--- a/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/KeyUtilsTest.java
@@ -155,9 +155,15 @@ public class KeyUtilsTest {
var priv = xecPrivFromHex(privHex);
assertEquals(privHex, xecHexFromPriv(priv));
+ // Base 64
var privB64 = KeyUtils.toBase64EncodedX25519PrivateKey(priv);
var priv2 = KeyUtils.fromBase64EncodedX25519PrivateKey(privB64);
assertEquals(privHex, xecHexFromPriv(priv2));
+
+ // Base 58
+ var privB58 = KeyUtils.toBase58EncodedX25519PrivateKey(priv);
+ var priv3 = KeyUtils.fromBase58EncodedX25519PrivateKey(privB58);
+ assertEquals(privHex, xecHexFromPriv(priv3));
}
@Test
@@ -166,9 +172,15 @@ public class KeyUtilsTest {
var pub = xecPubFromHex(pubHex);
assertEquals(pubHex, xecHexFromPub(pub));
+ // Base 64
var pubB64 = KeyUtils.toBase64EncodedX25519PublicKey(pub);
var pub2 = KeyUtils.fromBase64EncodedX25519PublicKey(pubB64);
assertEquals(pubHex, xecHexFromPub(pub2));
+
+ // Base 58
+ var pubB58 = KeyUtils.toBase58EncodedX25519PublicKey(pub);
+ var pub3 = KeyUtils.fromBase58EncodedX25519PublicKey(pubB58);
+ assertEquals(pubHex, xecHexFromPub(pub3));
}
@Test
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 74b4ca0854b..4e64bc3e9aa 100644
--- a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
@@ -14,7 +14,6 @@ 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;
@@ -47,12 +46,12 @@ public class SharedKeyTest {
@Test
void token_v1_representation_is_stable() {
- var receiverPrivate = KeyUtils.fromBase64EncodedX25519PrivateKey("4qGcntygFn_a3uqeBa1PbDlygQ-cpOuNznTPIz9ftWE");
- var receiverPublic = KeyUtils.fromBase64EncodedX25519PublicKey( "ROAH_S862tNMpbJ49lu1dPXFCPHFIXZK30pSrMZEmEg");
+ var receiverPrivate = KeyUtils.fromBase58EncodedX25519PrivateKey("GFg54SaGNCmcSGufZCx68SKLGuAFrASoDeMk3t5AjU6L");
+ var receiverPublic = KeyUtils.fromBase58EncodedX25519PublicKey( "5drrkakYLjYSBpr5Haknh13EiCYL36ndMzK4gTJo6pwh");
var keyId = KeyId.ofString("my key ID");
// Token generated for the above receiver public key, with the below expected shared secret (in hex)
- var publicToken = "AQlteSBrZXkgSUQgAtTxJJdmv3eUoW5Z3NJSdZ3poKPEkW0SJOGQXP6CaC5XfyAVoUlK_NyYIMsJKyNYKU6WmagZpVG2zQGFJoqiFA";
+ var publicToken = "OntP9gRVAjXeZIr4zkYqRJFcnA993v7ZEE7VbcNs1NcR3HdE7Mpwlwi3r3anF1kVa5fn7O1CyeHQpBWpdayUTKkrtyFepG6WJrZdE";
var expectedSharedSecret = "1b33b4dcd6a94e5a4a1ee6d208197d01";
var theirSealed = SealedSharedKey.fromTokenString(publicToken);
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 fc485eb92f2..f1c166ba934 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
@@ -14,12 +14,9 @@ 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.
@@ -47,7 +44,7 @@ public class DecryptTool implements Tool {
.longOpt(RECIPIENT_PRIVATE_KEY_FILE_OPTION)
.hasArg(true)
.required(false)
- .desc("Recipient private key file")
+ .desc("Recipient private key file in Base58 encoded format")
.build(),
Option.builder("i")
.longOpt(KEY_ID_OPTION)
@@ -103,7 +100,7 @@ public class DecryptTool implements Tool {
"used when generating the supplied token");
}
}
- var privateKey = KeyUtils.fromBase64EncodedX25519PrivateKey(Files.readString(privKeyPath).strip());
+ var privateKey = KeyUtils.fromBase58EncodedX25519PrivateKey(Files.readString(privKeyPath).strip());
var secretShared = SharedKeyGenerator.fromSealedKey(sealedSharedKey, privateKey);
var cipher = SharedKeyGenerator.makeAesGcmDecryptionCipher(secretShared);
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 737bade400f..886433f00f8 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
@@ -15,8 +15,6 @@ 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.
@@ -42,7 +40,7 @@ public class EncryptTool implements Tool {
.longOpt(RECIPIENT_PUBLIC_KEY_OPTION)
.hasArg(true)
.required(false)
- .desc("Recipient X25519 public key in Base64 encoded format")
+ .desc("Recipient X25519 public key in Base58 encoded format")
.build(),
Option.builder("i")
.longOpt(KEY_ID_OPTION)
@@ -79,7 +77,7 @@ public class EncryptTool implements Tool {
var inputArg = leftoverArgs[0];
var outputPath = Paths.get(CliUtils.optionOrThrow(arguments, OUTPUT_FILE_OPTION));
- var recipientPubKey = KeyUtils.fromBase64EncodedX25519PublicKey(CliUtils.optionOrThrow(arguments, RECIPIENT_PUBLIC_KEY_OPTION).strip());
+ var recipientPubKey = KeyUtils.fromBase58EncodedX25519PublicKey(CliUtils.optionOrThrow(arguments, RECIPIENT_PUBLIC_KEY_OPTION).strip());
var keyId = KeyId.ofString(CliUtils.optionOrThrow(arguments, KEY_ID_OPTION));
var shared = SharedKeyGenerator.generateForReceiverPublicKey(recipientPubKey, keyId);
var cipher = SharedKeyGenerator.makeAesGcmEncryptionCipher(shared);
diff --git a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/KeygenTool.java b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/KeygenTool.java
index d7885dc6455..3d5accde98f 100644
--- a/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/KeygenTool.java
+++ b/vespaclient-java/src/main/java/com/yahoo/vespa/security/tool/crypto/KeygenTool.java
@@ -59,7 +59,7 @@ public class KeygenTool implements Tool {
return new ToolDescription(
"<options>",
"Generates an X25519 key pair and stores its private/public parts in " +
- "separate files in Base64 encoded form.",
+ "separate files in Base58 encoded form.",
"Note: this is a BETA tool version; its interface may be changed at any time",
OPTIONS);
}
@@ -101,8 +101,8 @@ public class KeygenTool implements Tool {
var privFilePerms = PosixFilePermissions.fromString("rw-------");
Files.createFile( privOutPath, PosixFilePermissions.asFileAttribute(privFilePerms));
- Files.writeString(privOutPath, KeyUtils.toBase64EncodedX25519PrivateKey(privKey) + "\n");
- Files.writeString(pubOutPath, KeyUtils.toBase64EncodedX25519PublicKey(pubKey) + "\n");
+ Files.writeString(privOutPath, KeyUtils.toBase58EncodedX25519PrivateKey(privKey) + "\n");
+ Files.writeString(pubOutPath, KeyUtils.toBase58EncodedX25519PublicKey(pubKey) + "\n");
} catch (IOException e) {
throw new RuntimeException(e);
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 f529ed828ea..d4992e89802 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
@@ -168,11 +168,11 @@ public class CryptoToolsTest {
assertEquals(expectedPerms, privKeyPerms);
}
- private static final String TEST_PRIV_KEY = "4qGcntygFn_a3uqeBa1PbDlygQ-cpOuNznTPIz9ftWE";
- private static final String TEST_PUB_KEY = "ROAH_S862tNMpbJ49lu1dPXFCPHFIXZK30pSrMZEmEg";
+ private static final String TEST_PRIV_KEY = "GFg54SaGNCmcSGufZCx68SKLGuAFrASoDeMk3t5AjU6L";
+ private static final String TEST_PUB_KEY = "5drrkakYLjYSBpr5Haknh13EiCYL36ndMzK4gTJo6pwh";
// 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 = "OntP9gRVAjXeZIr4zkYqRJFcnA993v7ZEE7VbcNs1NcR3HdE7Mp" +
+ "wlwi3r3anF1kVa5fn7O1CyeHQpBWpdayUTKkrtyFepG6WJrZdE";
private static final String TEST_TOKEN_KEY_ID = "my key ID";
@Test
diff --git a/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt b/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
index ef59741cd30..ddf91c779e2 100644
--- a/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-decrypt-help-output.txt
@@ -10,7 +10,8 @@ the quotes).
this is not provided, the key ID
stored as part of the token is
not verified.
- -k,--recipient-private-key-file <arg> Recipient private key file
+ -k,--recipient-private-key-file <arg> Recipient private key file in
+ Base58 encoded format
-o,--output-file <arg> Output file for decrypted
plaintext. Specify '-' (without
the quotes) to write plaintext to
diff --git a/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt b/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt
index 5e1da32cbe7..beddc69855b 100644
--- a/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-encrypt-help-output.txt
@@ -10,7 +10,7 @@ the quotes).
-i,--key-id <arg> Numeric ID of recipient key
-o,--output-file <arg> Output file (will be truncated if it
already exists)
- -r,--recipient-public-key <arg> Recipient X25519 public key in Base64
+ -r,--recipient-public-key <arg> Recipient X25519 public key in Base58
encoded format
Note: this is a BETA tool version; its interface may be changed at any
time
diff --git a/vespaclient-java/src/test/resources/expected-keygen-help-output.txt b/vespaclient-java/src/test/resources/expected-keygen-help-output.txt
index 60629c4291f..f386f6d2e3a 100644
--- a/vespaclient-java/src/test/resources/expected-keygen-help-output.txt
+++ b/vespaclient-java/src/test/resources/expected-keygen-help-output.txt
@@ -1,6 +1,6 @@
usage: vespa-security keygen <options>
Generates an X25519 key pair and stores its private/public parts in
-separate files in Base64 encoded form.
+separate files in Base58 encoded form.
-h,--help Show help
-k,--private-out-file <arg> Output file for private (secret) key. Will
be created with restrictive file