diff --git a/at_client/pom.xml b/at_client/pom.xml
index a9e4c642..7b679b6c 100644
--- a/at_client/pom.xml
+++ b/at_client/pom.xml
@@ -19,12 +19,20 @@
../config/checkstyle.xml
- 0.77
- 0.7
+ 0.80
+ 0.80
+
+
+
+ src/main/resources
+ true
+
+
+
diff --git a/at_client/src/main/java/org/atsign/client/api/AtKeys.java b/at_client/src/main/java/org/atsign/client/api/AtKeys.java
index 53d5aea2..f8ddeb35 100644
--- a/at_client/src/main/java/org/atsign/client/api/AtKeys.java
+++ b/at_client/src/main/java/org/atsign/client/api/AtKeys.java
@@ -1,8 +1,6 @@
package org.atsign.client.api;
-import java.security.Key;
import java.security.KeyPair;
-import java.util.Base64;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -11,6 +9,8 @@
import lombok.Value;
import org.atsign.client.impl.common.EnrollmentId;
+import static org.atsign.client.impl.util.EncryptionUtils.toStringBase64;
+
/**
* An immutable class used to hold an {@link org.atsign.client.api.AtClient}s keys. These
* are use for authentication and encryption.
@@ -154,18 +154,14 @@ public Map getCache() {
public static class AtKeysBuilder {
public AtKeysBuilder encryptKeyPair(KeyPair keyPair) {
- return this.encryptPublicKey(createStringBase64(keyPair.getPublic()))
- .encryptPrivateKey(createStringBase64(keyPair.getPrivate()));
+ return this.encryptPublicKey(toStringBase64(keyPair.getPublic()))
+ .encryptPrivateKey(toStringBase64(keyPair.getPrivate()));
}
public AtKeysBuilder apkamKeyPair(KeyPair keyPair) {
- return this.apkamPublicKey(createStringBase64(keyPair.getPublic()))
- .apkamPrivateKey(createStringBase64(keyPair.getPrivate()));
+ return this.apkamPublicKey(toStringBase64(keyPair.getPublic()))
+ .apkamPrivateKey(toStringBase64(keyPair.getPrivate()));
}
}
- private static String createStringBase64(Key key) {
- return Base64.getEncoder().encodeToString(key.getEncoded());
- }
-
}
diff --git a/at_client/src/main/java/org/atsign/client/api/Metadata.java b/at_client/src/main/java/org/atsign/client/api/Metadata.java
index 117336b8..6012e16e 100644
--- a/at_client/src/main/java/org/atsign/client/api/Metadata.java
+++ b/at_client/src/main/java/org/atsign/client/api/Metadata.java
@@ -2,12 +2,13 @@
import java.time.OffsetDateTime;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import org.atsign.client.impl.util.JsonUtils;
import lombok.Builder;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.extern.jackson.Jacksonized;
-import org.atsign.client.impl.util.JsonUtils;
/**
* Value class which models key metadata in the Atsign Platform
@@ -41,8 +42,14 @@ public class Metadata {
Boolean isCached;
String sharedKeyEnc;
String pubKeyCS;
+ PublicKeyHash pubKeyHash;
String encoding;
+ String encKeyName;
+ String encAlgo;
String ivNonce;
+ String skeEncKeyName;
+ String skeEncAlgo;
+ Boolean immutable;
/**
* A builder for instantiating {@link Metadata} instances. Note: Metadata is immutable so if you
@@ -58,6 +65,7 @@ public static Metadata fromJson(String json) {
}
/**
+ * Ordering is crucial see at_commons\lib\src\verb\syntax.dart
*
* @return the encoded metadata fields as recognized by an At Server in an update command.
*/
@@ -70,12 +78,18 @@ public String toString() {
.append(ccd != null ? ":ccd:" + ccd : "")
.append(dataSignature != null ? ":dataSignature:" + dataSignature : "")
.append(sharedKeyStatus != null ? ":sharedKeyStatus:" + sharedKeyStatus : "")
- .append(sharedKeyEnc != null ? ":sharedKeyEnc:" + sharedKeyEnc : "")
- .append(pubKeyCS != null ? ":pubKeyCS:" + pubKeyCS : "")
.append(isBinary != null ? ":isBinary:" + isBinary : "")
.append(isEncrypted != null ? ":isEncrypted:" + isEncrypted : "")
+ .append(sharedKeyEnc != null ? ":sharedKeyEnc:" + sharedKeyEnc : "")
+ .append(pubKeyCS != null ? ":pubKeyCS:" + pubKeyCS : "")
+ .append(pubKeyHash != null ? ":pubKeyHash:" + pubKeyHash.hash + ":hashingAlgo:" + pubKeyHash.hashingAlgo : "")
.append(encoding != null ? ":encoding:" + encoding : "")
+ .append(encKeyName != null ? ":encKeyName:" + encKeyName : "")
+ .append(encAlgo != null ? ":encAlgo:" + encAlgo : "")
.append(ivNonce != null ? ":ivNonce:" + ivNonce : "")
+ .append(skeEncKeyName != null ? ":skeEncKeyName:" + skeEncKeyName : "")
+ .append(skeEncAlgo != null ? ":skeEncAlgo:" + skeEncAlgo : "")
+ .append(immutable != null ? ":immutable:" + immutable : "")
.toString();
}
@@ -158,12 +172,30 @@ public static MetadataBuilder toMergedBuilder(Metadata md1, Metadata md2) {
if (!setPubKeyCSIfNotNull(builder, md1.pubKeyCS)) {
setPubKeyCSIfNotNull(builder, md2.pubKeyCS);
}
+ if (!setPubKeyHashIfNotNull(builder, md1.pubKeyHash)) {
+ setPubKeyHashIfNotNull(builder, md2.pubKeyHash);
+ }
if (!setEncodingIfNotNull(builder, md1.encoding)) {
setEncodingIfNotNull(builder, md2.encoding);
}
+ if (!setEncKeyNameIfNotNull(builder, md1.encKeyName)) {
+ setEncKeyNameIfNotNull(builder, md2.encKeyName);
+ }
+ if (!setEncAlgoIfNotNull(builder, md1.encAlgo)) {
+ setEncAlgoIfNotNull(builder, md2.encAlgo);
+ }
if (!setIvNonceIfNotNull(builder, md1.ivNonce)) {
setIvNonceIfNotNull(builder, md2.ivNonce);
}
+ if (!setSkeEncKeyNameIfNotNull(builder, md1.skeEncKeyName)) {
+ setSkeEncKeyNameIfNotNull(builder, md2.skeEncKeyName);
+ }
+ if (!setSkeEncAlgoIfNotNull(builder, md1.skeEncAlgo)) {
+ setSkeEncAlgoIfNotNull(builder, md2.skeEncAlgo);
+ }
+ if (!setImmutableIfNotNull(builder, md1.immutable)) {
+ setImmutableIfNotNull(builder, md2.immutable);
+ }
return builder;
}
@@ -339,6 +371,15 @@ public static boolean setPubKeyCSIfNotNull(MetadataBuilder builder, String value
}
}
+ public static boolean setPubKeyHashIfNotNull(MetadataBuilder builder, PublicKeyHash value) {
+ if (value != null) {
+ builder.pubKeyHash(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public static boolean setEncodingIfNotNull(MetadataBuilder builder, String value) {
if (value != null) {
builder.encoding(value);
@@ -348,6 +389,24 @@ public static boolean setEncodingIfNotNull(MetadataBuilder builder, String value
}
}
+ public static boolean setEncKeyNameIfNotNull(MetadataBuilder builder, String value) {
+ if (value != null) {
+ builder.encKeyName(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean setEncAlgoIfNotNull(MetadataBuilder builder, String value) {
+ if (value != null) {
+ builder.encAlgo(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public static boolean setIvNonceIfNotNull(MetadataBuilder builder, String value) {
if (value != null) {
builder.ivNonce(value);
@@ -357,7 +416,46 @@ public static boolean setIvNonceIfNotNull(MetadataBuilder builder, String value)
}
}
+ public static boolean setSkeEncKeyNameIfNotNull(MetadataBuilder builder, String value) {
+ if (value != null) {
+ builder.skeEncKeyName(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean setSkeEncAlgoIfNotNull(MetadataBuilder builder, String value) {
+ if (value != null) {
+ builder.skeEncAlgo(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean setImmutableIfNotNull(MetadataBuilder builder, Boolean value) {
+ if (value != null) {
+ builder.immutable(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public static boolean isBinary(Metadata metadata) {
return metadata.isBinary() != null && metadata.isBinary();
}
+
+ /**
+ * Model a public key hash tuple, the hash value and the algorithm used to generate the digest.
+ */
+ @Value
+ @Jacksonized
+ @Builder
+ @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
+ public static class PublicKeyHash {
+ String hash;
+ String hashingAlgo;
+ }
}
diff --git a/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java b/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java
index f97eb263..8622489a 100644
--- a/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java
+++ b/at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java
@@ -52,6 +52,7 @@ public class AtClientImpl implements AtClient {
private final AtSign atSign;
private final AtKeys keys;
+ private final Map config;
private final AtCommandExecutor executor;
private final AtEventBus eventBus;
private final AtomicBoolean isMonitoring = new AtomicBoolean();
@@ -68,9 +69,14 @@ public AtCommandExecutor getCommandExecutor() {
}
@Builder
- public AtClientImpl(AtSign atSign, AtKeys keys, AtCommandExecutor executor, AtEventBus eventBus) {
+ public AtClientImpl(AtSign atSign,
+ AtKeys keys,
+ Map config,
+ AtCommandExecutor executor,
+ AtEventBus eventBus) {
this.atSign = checkNotNull(atSign, "atSign not set");
this.keys = checkNotNull(keys, "keys not set");
+ this.config = config;
this.executor = checkNotNull(executor, "executor not set");
this.eventBus = checkNotNull(eventBus, "eventBus not set");
this.eventBus.addEventListener(this::handleEvent, EnumSet.allOf(AtEventType.class));
@@ -106,13 +112,13 @@ public void close() throws Exception {
@Override
public void startMonitor() {
isMonitoring.compareAndSet(false, true);
- executor.onReady(Notifications.monitor(atSign, keys, eventBusBridge::accept));
+ executor.onReady(Notifications.monitor(atSign, keys, config, eventBusBridge::accept));
}
@Override
public void stopMonitor() {
isMonitoring.compareAndSet(true, false);
- executor.onReady(AuthenticationCommands.pkamAuthenticator(atSign, keys));
+ executor.onReady(AuthenticationCommands.pkamAuthenticator(atSign, keys, config));
}
@Override
@@ -268,12 +274,18 @@ private void onSharedKeyNotification(Map eventData) throws AtDec
private void onUpdateNotification(Map eventData) throws AtException {
// Let's see if we can decrypt it on the fly
if (eventData.get("value") != null) {
- String key = (String) eventData.get("key");
String encryptedValue = (String) eventData.get("value");
Map metadata = (Map) eventData.get("metadata");
String ivNonce = (String) metadata.get("ivNonce");
- SharedKey sk = org.atsign.client.api.Keys.sharedKeyBuilder().rawKey(key).build();
- String encryptKeySharedByOther = SharedKeyCommands.getEncryptKeySharedByOther(executor, keys, sk);
+ String encryptKeySharedByOther;
+ String sharedKeyEnc = (String) metadata.get("sharedKeyEnc");
+ if (sharedKeyEnc != null) {
+ encryptKeySharedByOther = rsaDecryptFromBase64(sharedKeyEnc, keys.getEncryptPrivateKey());
+ } else {
+ String key = (String) eventData.get("key");
+ SharedKey sk = org.atsign.client.api.Keys.sharedKeyBuilder().rawKey(key).build();
+ encryptKeySharedByOther = SharedKeyCommands.lookupEncryptKeySharedByOther(executor, keys, sk);
+ }
String decryptedValue = aesDecryptFromBase64(encryptedValue, encryptKeySharedByOther, ivNonce);
HashMap newEventData = new HashMap<>(eventData);
newEventData.put("decryptedValue", decryptedValue);
diff --git a/at_client/src/main/java/org/atsign/client/impl/AtClients.java b/at_client/src/main/java/org/atsign/client/impl/AtClients.java
index df86a059..3f0f5e97 100644
--- a/at_client/src/main/java/org/atsign/client/impl/AtClients.java
+++ b/at_client/src/main/java/org/atsign/client/impl/AtClients.java
@@ -2,13 +2,14 @@
import static org.atsign.client.impl.common.Preconditions.checkNotNull;
-import org.atsign.client.api.AtClient;
-import org.atsign.client.api.AtCommandExecutor;
-import org.atsign.client.api.AtKeys;
-import org.atsign.client.api.AtSign;
+import java.io.File;
+import java.util.Map;
+
+import org.atsign.client.api.*;
import org.atsign.client.impl.common.ReconnectStrategy;
import org.atsign.client.impl.common.SimpleAtEventBus;
import org.atsign.client.impl.common.SimpleReconnectStrategy;
+import org.atsign.client.impl.exceptions.AtClientConfigException;
import org.atsign.client.impl.exceptions.AtException;
import org.atsign.client.impl.util.KeysUtils;
@@ -34,6 +35,8 @@ public class AtClients {
public static AtClient createAtClient(String url,
AtSign atSign,
AtKeys keys,
+ String keysPath,
+ Map config,
Long timeoutMillis,
Long awaitReadyMillis,
ReconnectStrategy reconnect,
@@ -42,12 +45,13 @@ public static AtClient createAtClient(String url,
throws AtException {
checkNotNull(atSign, "atSign not set");
- keys = keys != null ? keys : KeysUtils.loadKeys(atSign);
+ keys = keys != null ? keys : loadKeys(keysPath, atSign);
AtCommandExecutor executor = AtCommandExecutors.builder()
.url(url)
.atSign(atSign)
.keys(keys)
+ .config(config)
.timeoutMillis(timeoutMillis)
.awaitReadyMillis(awaitReadyMillis)
.reconnect(reconnect)
@@ -60,11 +64,26 @@ public static AtClient createAtClient(String url,
return AtClientImpl.builder()
.atSign(atSign)
.keys(keys)
+ .config(config)
.executor(executor)
.eventBus(eventBus)
.build();
}
+ private static AtKeys loadKeys(String path, AtSign atSign) throws AtClientConfigException {
+ if (path == null) {
+ return KeysUtils.loadKeys(atSign);
+ }
+ File f = new File(path);
+ if (!f.exists()) {
+ throw new AtClientConfigException(path + " does not exist");
+ }
+ if (f.isDirectory()) {
+ return KeysUtils.loadKeys(KeysUtils.getKeysFile(atSign, path));
+ }
+ return KeysUtils.loadKeys(f);
+ }
+
/**
* A builder for instantiating {@link AtClient} implementations that are included in
* this library.
@@ -75,6 +94,8 @@ public static AtClient createAtClient(String url,
* .atSign(...) // the AtSign that this client will authenticate as
* .url(...) // the url for the root server or proxy (optional)
* .keys(...) // the AtKeys that this client will use (optional)
+ * .keysPath(...) // the location for the AtKeys that this client will use (optional)
+ * .config(...) // the config map that will be passed during authentication (optional)
* .timeoutMillis() // timeout after which commands will complete exceptionally (optional)
* .awaitReadyMillis() // how long to wait for executor to become ready during build() (optional)
* .reconnect() // a ReconnectStrategy (optional)
@@ -89,6 +110,9 @@ public static AtClient createAtClient(String url,
* If keys is not set then the builder will default to attempting to load the keys
* which correspond to the atSign field in ~/.atsign/keys (or the environment variable
* / system property {@link KeysUtils#ATSIGN_KEYS_DIR} if set).
+ * If keysPath is set (and keys is not set) then the builder will attempt to load keys
+ * from the path value. If the provided value is not a directory it will simply load the file,
+ * otherwise it will look for a atKeys file for the atSign in the path.
* If timeoutMillis is not set then builder will default to
* {@link AtCommandExecutors#DEFAULT_TIMEOUT_MILLIS}.
* If awaitReadyMillis is not set then the builder will default to
@@ -100,4 +124,5 @@ public static AtClient createAtClient(String url,
public static class AtClientBuilder {
// required for javadoc
}
+
}
diff --git a/at_client/src/main/java/org/atsign/client/impl/AtCommandExecutors.java b/at_client/src/main/java/org/atsign/client/impl/AtCommandExecutors.java
index 625feab6..6b5a94e6 100644
--- a/at_client/src/main/java/org/atsign/client/impl/AtCommandExecutors.java
+++ b/at_client/src/main/java/org/atsign/client/impl/AtCommandExecutors.java
@@ -3,6 +3,12 @@
import static org.atsign.client.impl.common.Preconditions.checkNotNull;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -16,6 +22,7 @@
import org.atsign.client.impl.netty.NettyAtCommandExecutor;
import lombok.Builder;
+import lombok.extern.slf4j.Slf4j;
/**
* Utility methods / builders for instantiating {@link AtCommandExecutor} implementations
@@ -40,6 +47,7 @@
* NOTE: If reconnect is not set then the builder will default to a
* {@link SimpleReconnectStrategy}
*/
+@Slf4j
public class AtCommandExecutors {
public static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
@@ -48,6 +56,7 @@ public class AtCommandExecutors {
public static AtCommandExecutor createCommandExecutor(String url,
AtSign atSign,
AtKeys keys,
+ Map config,
Long timeoutMillis,
Long awaitReadyMillis,
ReconnectStrategy reconnect,
@@ -66,7 +75,7 @@ public static AtCommandExecutor createCommandExecutor(String url,
.awaitReadyMillis(defaultIfNotSet(awaitReadyMillis, DEFAULT_TIMEOUT_MILLIS))
.reconnect(defaultIfNotSet(reconnect))
.queueLimit(queueLimit)
- .onReady(createOnReady(atSign, keys))
+ .onReady(createOnReady(atSign, keys, createClientConfig(config)))
.build();
}
@@ -101,10 +110,27 @@ public static class AtCommandExecutorBuilder {
// required for javadoc
}
- private static Consumer createOnReady(AtSign atSign, AtKeys keys) {
+ public static Map createClientConfig(Map config) {
+ Map result = new HashMap<>();
+ result.put("clientId", UUID.randomUUID());
+ Properties properties = new Properties();
+ URL resource = AtCommandExecutors.class.getClassLoader().getResource("client-config.properties");
+ try (InputStream in = resource.openStream()) {
+ properties.load(in);
+ properties.forEach((k, v) -> result.put(k.toString(), v.toString().replace("-SNAPSHOT", "")));
+ } catch (Exception e) {
+ log.warn("unable to load client-config.properties");
+ }
+ if (config != null) {
+ result.putAll(config);
+ }
+ return result;
+ }
+
+ private static Consumer createOnReady(AtSign atSign, AtKeys keys, Map config) {
Consumer onReady;
if (atSign != null && keys != null) {
- onReady = AuthenticationCommands.pkamAuthenticator(atSign, keys);
+ onReady = AuthenticationCommands.pkamAuthenticator(atSign, keys, config);
} else {
onReady = c -> {
};
@@ -119,5 +145,4 @@ private static ReconnectStrategy defaultIfNotSet(ReconnectStrategy reconnect) {
private static long defaultIfNotSet(Long l, long defaultValue) {
return l != null ? l : defaultValue;
}
-
}
diff --git a/at_client/src/main/java/org/atsign/client/impl/cli/AbstractCli.java b/at_client/src/main/java/org/atsign/client/impl/cli/AbstractCli.java
index 153eab1f..b29e3179 100644
--- a/at_client/src/main/java/org/atsign/client/impl/cli/AbstractCli.java
+++ b/at_client/src/main/java/org/atsign/client/impl/cli/AbstractCli.java
@@ -96,7 +96,7 @@ protected AtCommandExecutor createConnection(String rootUrl, AtSign atSign, int
protected AtCommandExecutor createAuthenticatedConnection(String rootUrl, AtSign atSign, int retries)
throws AtException {
return createCommandExecutorBuilder(rootUrl, atSign, retries, verbose)
- .onReady(AuthenticationCommands.pkamAuthenticator(atSign, getKeys()))
+ .onReady(AuthenticationCommands.pkamAuthenticator(atSign, getKeys(), null))
.build();
}
diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/AuthenticationCommands.java b/at_client/src/main/java/org/atsign/client/impl/commands/AuthenticationCommands.java
index b97b695b..b7d3e702 100644
--- a/at_client/src/main/java/org/atsign/client/impl/commands/AuthenticationCommands.java
+++ b/at_client/src/main/java/org/atsign/client/impl/commands/AuthenticationCommands.java
@@ -4,10 +4,12 @@
import static org.atsign.client.impl.commands.DataResponses.matchDataStringNoWhitespace;
import static org.atsign.client.impl.commands.DataResponses.matchDataSuccess;
import static org.atsign.client.impl.commands.ErrorResponses.throwExceptionIfError;
+import static org.atsign.client.impl.util.EncryptionUtils.bytesToHex;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -24,8 +26,8 @@
*/
public class AuthenticationCommands {
- public static Consumer pkamAuthenticator(AtSign atSign, AtKeys keys) {
- return throwOnReadyException(executor -> authenticateWithPkam(executor, atSign, keys));
+ public static Consumer pkamAuthenticator(AtSign atSign, AtKeys keys, Map config) {
+ return throwOnReadyException(executor -> authenticateWithPkam(executor, atSign, keys, config));
}
/**
@@ -38,10 +40,27 @@ public static Consumer pkamAuthenticator(AtSign atSign, AtKey
*/
public static void authenticateWithPkam(AtCommandExecutor executor, AtSign atSign, AtKeys keys)
throws AtException {
+ authenticateWithPkam(executor, atSign, keys, null);
+ }
+
+ /**
+ * Implements the protocol workflow / sequence for PKAM authentication.
+ *
+ * @param executor The executor with which to send the commands.
+ * @param atSign The asign to authenticate.
+ * @param keys The keys to use to authenticate.
+ * @param config The map of configuration values to send in the from command.
+ * @throws AtException If authentication fails.
+ */
+ public static void authenticateWithPkam(AtCommandExecutor executor,
+ AtSign atSign,
+ AtKeys keys,
+ Map config)
+ throws AtException {
try {
// send a from command and expect to receive a challenge
- String fromCommand = CommandBuilders.fromCommandBuilder().atSign(atSign).build();
+ String fromCommand = CommandBuilders.fromCommandBuilder().atSign(atSign).config(config).build();
String fromResponse = executor.sendSync(fromCommand);
String challenge = matchDataStringNoWhitespace(throwExceptionIfError(fromResponse));
@@ -105,15 +124,5 @@ private static String createDigest(String cramSecret, String challenge) throws A
}
}
- private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
- public static String bytesToHex(byte[] bytes) {
- char[] hexChars = new char[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = HEX_ARRAY[v >>> 4];
- hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
- }
- return new String(hexChars);
- }
}
diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/CommandBuilders.java b/at_client/src/main/java/org/atsign/client/impl/commands/CommandBuilders.java
index 64a860ce..b8f01d65 100644
--- a/at_client/src/main/java/org/atsign/client/impl/commands/CommandBuilders.java
+++ b/at_client/src/main/java/org/atsign/client/impl/commands/CommandBuilders.java
@@ -39,9 +39,13 @@ public class CommandBuilders {
* @throws IllegalArgumentException If mandatory fields are not set or if field values conflict.
*/
@Builder(builderMethodName = "fromCommandBuilder", builderClassName = "FromCommandBuilder")
- public static String from(AtSign atSign) {
+ public static String from(AtSign atSign, Map config) {
checkNotNull(atSign, "atSign not set");
- return "from:" + atSign;
+ StringBuilder builder = new StringBuilder("from:").append(atSign);
+ if (config != null && !config.isEmpty()) {
+ builder.append(":clientConfig:").append(JsonUtils.writeValueAsString(config));
+ }
+ return builder.toString();
}
/**
diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/ErrorResponses.java b/at_client/src/main/java/org/atsign/client/impl/commands/ErrorResponses.java
index 60b22e10..50a7c63e 100644
--- a/at_client/src/main/java/org/atsign/client/impl/commands/ErrorResponses.java
+++ b/at_client/src/main/java/org/atsign/client/impl/commands/ErrorResponses.java
@@ -3,6 +3,7 @@
import static org.atsign.client.impl.commands.AtExceptions.toTypedException;
import static org.atsign.client.impl.common.Preconditions.checkNotNull;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -20,6 +21,8 @@ public class ErrorResponses {
private static final Pattern ERROR_WITH_CODE = Pattern.compile("error:(AT\\d+)([^:]*):\\s*(.+)");
+ private static final Pattern ERROR_WITH_JSON = Pattern.compile("error:(\\{.+})");
+
/**
* Use this to verify a "error:xxxx" response.
*
@@ -48,7 +51,12 @@ public static String throwExceptionIfError(String response) throws AtException {
private static AtException getAtExceptionIfError(String response) throws AtException {
checkNotNull(response);
- Matcher matcher = ERROR_WITH_CODE.matcher(response);
+ Matcher matcher = ERROR_WITH_JSON.matcher(response);
+ if (matcher.matches()) {
+ Map map = Responses.decodeJsonMapOfObjects(matcher.group(1));
+ return toTypedException((String) map.get("errorCode"), (String) map.get("errorDescription"));
+ }
+ matcher = ERROR_WITH_CODE.matcher(response);
if (matcher.matches()) {
return toTypedException(matcher.group(1), matcher.group(3));
}
diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/Notifications.java b/at_client/src/main/java/org/atsign/client/impl/commands/Notifications.java
index 1ba03664..91f7f711 100644
--- a/at_client/src/main/java/org/atsign/client/impl/commands/Notifications.java
+++ b/at_client/src/main/java/org/atsign/client/impl/commands/Notifications.java
@@ -38,8 +38,11 @@ public class Notifications {
* @param keys The {@link AtKeys} to authenticate with.
* @param consumer A consumer that will be invoked with each notification.
*/
- public static Consumer monitor(AtSign atSign, AtKeys keys, Consumer consumer) {
- return throwOnReadyException(executor -> monitor(executor, atSign, keys, consumer));
+ public static Consumer monitor(AtSign atSign,
+ AtKeys keys,
+ Map config,
+ Consumer consumer) {
+ return throwOnReadyException(executor -> monitor(executor, atSign, keys, config, consumer));
}
/**
@@ -51,12 +54,34 @@ public static Consumer monitor(AtSign atSign, AtKeys keys, Co
* @param consumer A consumer that will be invoked with each notification.
* @throws AtException If any of the commands fail.
*/
- public static void monitor(AtCommandExecutor executor, AtSign atSign, AtKeys keys, Consumer consumer)
+ public static void monitor(AtCommandExecutor executor,
+ AtSign atSign,
+ AtKeys keys,
+ Consumer consumer)
+ throws AtException {
+ monitor(executor, atSign, keys, null, consumer);
+ }
+
+ /**
+ * Sends the commands to perform PKAM authentication followed by monitor command.
+ *
+ * @param executor The {@link AtCommandExecutor} to use.
+ * @param atSign The {@link AtSign} to authenticate.
+ * @param keys The {@link AtKeys} to authenticate with.
+ * @param config The map of configuration values to send with the from command.
+ * @param consumer A consumer that will be invoked with each notification.
+ * @throws AtException If any of the commands fail.
+ */
+ public static void monitor(AtCommandExecutor executor,
+ AtSign atSign,
+ AtKeys keys,
+ Map config,
+ Consumer consumer)
throws AtException {
try {
// authenticate
- authenticateWithPkam(executor, atSign, keys);
+ authenticateWithPkam(executor, atSign, keys, config);
// send monitor command
executor.sendSync("monitor", consumer);
diff --git a/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java b/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java
index a70a9590..72765a25 100644
--- a/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java
+++ b/at_client/src/main/java/org/atsign/client/impl/commands/SharedKeyCommands.java
@@ -13,8 +13,7 @@
import org.atsign.client.api.*;
import org.atsign.client.api.Keys.SharedKey;
-import org.atsign.client.impl.exceptions.AtException;
-import org.atsign.client.impl.exceptions.AtKeyNotFoundException;
+import org.atsign.client.impl.exceptions.*;
import org.atsign.client.impl.util.EncryptionUtils;
/**
@@ -81,7 +80,7 @@ public static void put(AtCommandExecutor executor, AtSign atSign, AtKeys keys, S
try {
// get or create key for sharedBy - sharedWith
- String aesKey = getEncryptKeySharedByMe(executor, keys, key);
+ String aesKey = lookupEncryptKeySharedByMe(executor, keys, key);
if (aesKey == null) {
aesKey = createEncryptKey(executor, keys, key);
}
@@ -115,8 +114,8 @@ private static String getSharedByMe(AtCommandExecutor executor, AtKeys keys, Sha
checkTrue(Metadata.isBinary(llookupResponse.metaData), "metadata.isBinary not set to true");
}
- // get my encrypt key for sharedBy sharedWith
- String aesKey = checkNotNull(getEncryptKeySharedByMe(executor, keys, key), key + " not found");
+ // get the encryption key that was previously created by "me"
+ String aesKey = checkNotNull(lookupEncryptKeySharedByMe(executor, keys, key), key + " not found");
// return decrypted value
return aesDecryptFromBase64(llookupResponse.data, aesKey, llookupResponse.metaData.ivNonce());
@@ -138,17 +137,35 @@ private static String getSharedByOther(AtCommandExecutor executor, AtKeys keys,
checkTrue(Metadata.isBinary(lookupResponse.metaData), "isBinary not set to true");
}
- // get my encrypt key for sharedBy sharedWith
- String shareEncryptionKey = getEncryptKeySharedByOther(executor, keys, key);
+ // get the encryption key that was created by the "other"
+ String sharedEncryptionKey;
+ if (lookupResponse.metaData.sharedKeyEnc() != null) {
+ sharedEncryptionKey = extractEncryptKeySharedByOther(lookupResponse, keys);
+ } else {
+ sharedEncryptionKey = lookupEncryptKeySharedByOther(executor, keys, key);
+ }
// return decrypted value
- return aesDecryptFromBase64(lookupResponse.data, shareEncryptionKey, lookupResponse.metaData.ivNonce());
+ return aesDecryptFromBase64(lookupResponse.data, sharedEncryptionKey, lookupResponse.metaData.ivNonce());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}
+ private static String extractEncryptKeySharedByOther(LookupResponse lookupResponse, AtKeys keys)
+ throws AtException {
+ String encryptedShareEncryptionKey = lookupResponse.metaData.sharedKeyEnc();
+ if (lookupResponse.metaData.pubKeyHash() != null) {
+ String algo = lookupResponse.metaData.pubKeyHash().hashingAlgo();
+ String hash = digest(keys.getEncryptPublicKey(), algo);
+ if (!hash.equals(lookupResponse.metaData.pubKeyHash().hash())) {
+ throw new AtPublicKeyChangeException("pubKeyHash mis-match");
+ }
+ }
+ return rsaDecryptFromBase64(encryptedShareEncryptionKey, keys.getEncryptPrivateKey());
+ }
+
/**
* Get the specific encryption key which needs to be used for a sharedBy - sharedWith relationship
* where the AtSign that the {@link AtCommandExecutor} has authenticated with is the sharedBy
@@ -160,7 +177,7 @@ private static String getSharedByOther(AtCommandExecutor executor, AtKeys keys,
* @return The symmetric encryption key (in base64).
* @throws AtException If any of the commands fail or the key does not exist.
*/
- public static String getEncryptKeySharedByMe(AtCommandExecutor executor, AtKeys keys, SharedKey key)
+ public static String lookupEncryptKeySharedByMe(AtCommandExecutor executor, AtKeys keys, SharedKey key)
throws AtException {
try {
@@ -205,7 +222,7 @@ public static String getEncryptKeySharedByMe(AtCommandExecutor executor, AtKeys
* @return The symmetric encryption key (in base64).
* @throws AtException If any of the commands fail or the key does not exist.
*/
- public static String getEncryptKeySharedByOther(AtCommandExecutor executor, AtKeys keys, SharedKey key)
+ public static String lookupEncryptKeySharedByOther(AtCommandExecutor executor, AtKeys keys, SharedKey key)
throws AtException {
try {
@@ -250,9 +267,15 @@ private static String createEncryptKey(AtCommandExecutor executor, AtKeys keys,
.sharedBy(key.sharedBy())
.value(encryptedForMe)
.build();
+ executor.sendSync(updateForUsCommand);
- // get the other (sharedWith) atsign's public key
+ // get the other (sharedWith) atsign's public key (and compute the hash of that key)
String otherPublicKey = getEncryptKey(executor, key.sharedWith());
+ Metadata.PublicKeyHash hash = Metadata.PublicKeyHash.builder()
+ .hash(EncryptionUtils.digest(otherPublicKey, HASHING_ALGO_SHA512))
+ .hashingAlgo(HASHING_ALGO_SHA512)
+ .build();
+ String checksum = digest(otherPublicKey, "MD5");
// compose an update command to store this key encrypted with the other (sharedWith) atsign's public key
String encryptedForOther = rsaEncryptToBase64(aesKey, otherPublicKey);
@@ -263,11 +286,18 @@ private static String createEncryptKey(AtCommandExecutor executor, AtKeys keys,
.ttr(TimeUnit.HOURS.toMillis(24))
.value(encryptedForOther)
.build();
-
- // send the update commands
- executor.sendSync(updateForUsCommand);
executor.sendSync(updateForOtherCommand);
+ // update the key metadata to include the shared encryption key encrypted with the other (sharedWith)
+ // atsign's public key plus the hash of that key to accommodate race conditions on public key changes
+ Metadata modifiedMetadata = key.metadata().toBuilder()
+ .sharedKeyEnc(encryptedForOther)
+ .pubKeyHash(hash)
+ .pubKeyCS(checksum)
+ .build();
+ key.overwriteMetadata(modifiedMetadata);
+
+ // store in my cache
keys.put(AtKeyNames.toSharedByMeKeyName(key.sharedWith()), aesKey);
// return the new
diff --git a/at_client/src/main/java/org/atsign/client/impl/exceptions/AtPublicKeyChangeException.java b/at_client/src/main/java/org/atsign/client/impl/exceptions/AtPublicKeyChangeException.java
new file mode 100644
index 00000000..9cd46e10
--- /dev/null
+++ b/at_client/src/main/java/org/atsign/client/impl/exceptions/AtPublicKeyChangeException.java
@@ -0,0 +1,14 @@
+package org.atsign.client.impl.exceptions;
+
+/**
+ * Occurs when sharedKeyEnc was encrypted with a public key that has now changed
+ */
+public class AtPublicKeyChangeException extends AtException {
+ public AtPublicKeyChangeException(String message) {
+ super(message);
+ }
+
+ public AtPublicKeyChangeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java b/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java
index 2dcca30c..aa2306a5 100644
--- a/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java
+++ b/at_client/src/main/java/org/atsign/client/impl/util/EncryptionUtils.java
@@ -1,5 +1,8 @@
package org.atsign.client.impl.util;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.atsign.client.impl.common.Preconditions.checkNotBlank;
+
import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
@@ -15,14 +18,14 @@
import org.atsign.client.impl.exceptions.AtEncryptionException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
/**
* Utility class which registers bouncycastle as a security {@link Provider} and provides
* static methods for the various encryption functions required by Atsign client APIs
*/
public class EncryptionUtils {
+ private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
+
/**
* The signing algo "label"
*/
@@ -33,10 +36,44 @@ public class EncryptionUtils {
*/
public static final String HASHING_ALGO_SHA256 = "sha256";
+ /**
+ * The hashing algo "label"
+ */
+ public static final String HASHING_ALGO_SHA512 = "sha512";
+
static {
Security.addProvider(new BouncyCastleProvider());
}
+ /**
+ * Creates a digest for the given input using the given algo.
+ *
+ * @param input The text to digest.
+ * @param algo The digest algorithm to use. e.g. MD5
+ * @return The digest as a hex string.
+ * @throws AtEncryptionException If algorithm cannot be found or any other error.
+ */
+ public static String digest(String input, String algo) throws AtEncryptionException {
+ try {
+ MessageDigest md = MessageDigest.getInstance(toMessageDigestAlgorithm(algo));
+ return bytesToHex(md.digest(checkNotBlank(input, "input blank").getBytes(UTF_8)));
+ } catch (IllegalArgumentException | NoSuchAlgorithmException e) {
+ throw new AtEncryptionException("failed to hash : " + e.getMessage(), e);
+ }
+ }
+
+ private static String toMessageDigestAlgorithm(String algo) {
+ checkNotBlank(algo, "algo blank");
+ switch (algo) {
+ case HASHING_ALGO_SHA256:
+ return "SHA-256";
+ case HASHING_ALGO_SHA512:
+ return "SHA-512";
+ default:
+ return algo;
+ }
+ }
+
/**
* Encrypts a String with the AES Cipher and encodes as Base 64.
*
@@ -50,11 +87,12 @@ public static String aesEncryptToBase64(String input, String key, String iv)
throws AtEncryptionException {
try {
Cipher cipher = createAesCipher(Cipher.ENCRYPT_MODE, key, iv);
- byte[] encrypted = cipher.doFinal(input.getBytes(UTF_8));
+ byte[] encrypted = cipher.doFinal(checkNotBlank(input, "input is blank").getBytes(UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
- } catch (NoSuchAlgorithmException | NoSuchProviderException | BadPaddingException | IllegalBlockSizeException
- | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
- throw new AtEncryptionException("AES encryption failed", e);
+ } catch (IllegalArgumentException | NoSuchAlgorithmException | NoSuchProviderException | BadPaddingException
+ | IllegalBlockSizeException | NoSuchPaddingException | InvalidKeyException
+ | InvalidAlgorithmParameterException e) {
+ throw new AtEncryptionException("AES encryption failed : " + e.getMessage(), e);
}
}
@@ -70,11 +108,12 @@ public static String aesEncryptToBase64(String input, String key, String iv)
public static String aesDecryptFromBase64(String input, String key, String iv) throws AtDecryptionException {
try {
Cipher cipher = createAesCipher(Cipher.DECRYPT_MODE, key, iv);
- byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(input));
+ byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(checkNotBlank(input, "input is blank")));
return new String(decrypted, UTF_8);
- } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
- throw new AtDecryptionException("AES decryption failed", e);
+ } catch (IllegalArgumentException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException
+ | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException e) {
+ throw new AtDecryptionException("AES decryption failed : " + e.getMessage(), e);
}
}
@@ -124,12 +163,12 @@ public static String rsaDecryptFromBase64(String input, String key) throws AtDec
PrivateKey privateKey = toPrivateKey(key);
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
- byte[] decoded = Base64.getDecoder().decode(input.getBytes(UTF_8));
+ byte[] decoded = Base64.getDecoder().decode(checkNotBlank(input, "input is blank").getBytes(UTF_8));
byte[] decryptedMessageBytes = decryptCipher.doFinal(decoded);
return new String(decryptedMessageBytes, UTF_8);
- } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException
- | IllegalBlockSizeException | BadPaddingException e) {
- throw new AtDecryptionException("RSA decryption failed", e);
+ } catch (IllegalArgumentException | NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException
+ | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
+ throw new AtDecryptionException("RSA decryption failed : " + e.getMessage(), e);
}
}
@@ -146,12 +185,12 @@ public static String rsaEncryptToBase64(String input, String key) throws AtEncry
PublicKey publicKey = toPublicKey(key);
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
- byte[] clearTextBytes = input.getBytes(UTF_8);
+ byte[] clearTextBytes = checkNotBlank(input, "input is blank").getBytes(UTF_8);
byte[] encryptedMessageBytes = encryptCipher.doFinal(clearTextBytes);
return Base64.getEncoder().encodeToString(encryptedMessageBytes);
- } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException
- | IllegalBlockSizeException | BadPaddingException e) {
- throw new AtEncryptionException("RSA encryption failed", e);
+ } catch (IllegalArgumentException | NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException
+ | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
+ throw new AtEncryptionException("RSA encryption failed : " + e.getMessage(), e);
}
}
@@ -168,35 +207,36 @@ public static String signSHA256RSA(String input, String key) throws AtEncryption
PrivateKey pk = toPrivateKey(key);
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(pk);
- privateSignature.update(input.getBytes(UTF_8));
+ privateSignature.update(checkNotBlank(input, "input is blank").getBytes(UTF_8));
byte[] signedBytes = privateSignature.sign();
return Base64.getEncoder().encodeToString(signedBytes);
- } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
- throw new AtEncryptionException("SHA256 sign failed", e);
+ } catch (IllegalArgumentException | NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+ | SignatureException e) {
+ throw new AtEncryptionException("SHA256 sign failed : " + e.getMessage(), e);
}
}
private static PublicKey toPublicKey(String s) throws NoSuchAlgorithmException, InvalidKeySpecException {
- byte[] keyBytes = Base64.getDecoder().decode(s.getBytes(UTF_8));
+ byte[] keyBytes = Base64.getDecoder().decode(checkNotBlank(s, "key is blank").getBytes(UTF_8));
EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
return rsaKeyFactory.generatePublic(keySpec);
}
private static PrivateKey toPrivateKey(String s) throws NoSuchAlgorithmException, InvalidKeySpecException {
- byte[] keyBytes = Base64.getDecoder().decode(s.getBytes(UTF_8));
+ byte[] keyBytes = Base64.getDecoder().decode(checkNotBlank(s, "key is blank").getBytes(UTF_8));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
return rsaKeyFactory.generatePrivate(keySpec);
}
private static SecretKey toSecretKey(String s) {
- byte[] keyBytes = Base64.getDecoder().decode(s.getBytes());
+ byte[] keyBytes = Base64.getDecoder().decode(checkNotBlank(s, "key is blank").getBytes());
return new SecretKeySpec(keyBytes, "AES");
}
private static IvParameterSpec toIvParameterSpec(String s) {
- byte[] ivBytes = Base64.getDecoder().decode(s.getBytes());
+ byte[] ivBytes = Base64.getDecoder().decode(checkNotBlank(s, "iv is blank").getBytes());
return new IvParameterSpec(ivBytes);
}
@@ -213,6 +253,20 @@ public static String generateRandomIvBase64(int length) {
return Base64.getEncoder().encodeToString(iv);
}
+ public static String bytesToHex(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = HEX_ARRAY[v >>> 4];
+ hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ public static String toStringBase64(Key key) {
+ return Base64.getEncoder().encodeToString(key.getEncoded());
+ }
+
private static Cipher createAesCipher(int mode, String keyBase64, String ivNonce) throws NoSuchAlgorithmException,
NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
SecretKey key = toSecretKey(keyBase64);
diff --git a/at_client/src/main/resources/client-config.properties b/at_client/src/main/resources/client-config.properties
new file mode 100644
index 00000000..a84f8c51
--- /dev/null
+++ b/at_client/src/main/resources/client-config.properties
@@ -0,0 +1,2 @@
+version=${project.version}
+platform=Java
diff --git a/at_client/src/test/java/org/atsign/client/api/MetadataTest.java b/at_client/src/test/java/org/atsign/client/api/MetadataTest.java
index fd8bb41e..2f5b52ca 100644
--- a/at_client/src/test/java/org/atsign/client/api/MetadataTest.java
+++ b/at_client/src/test/java/org/atsign/client/api/MetadataTest.java
@@ -1,14 +1,15 @@
package org.atsign.client.api;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.junit.jupiter.api.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.*;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
class MetadataTest {
@@ -130,6 +131,10 @@ void testMergeMd1FieldsTakePriorityOverMd2() {
.isEncrypted(true).isBinary(true).namespaceAware(true)
.dataSignature("ds1").sharedKeyStatus("sks1").sharedKeyEnc("ske1")
.pubKeyCS("pkcs1").encoding("utf8").ivNonce("iv1")
+ .pubKeyHash(Metadata.PublicKeyHash.builder().hash("hash1").hashingAlgo("algo1").build())
+ .encKeyName("encKeyName1").encAlgo("encAlgo1")
+ .skeEncKeyName("skeEncKeyName1").skeEncAlgo("skeEncAlgo1")
+ .immutable(true)
.build();
Metadata md2 = Metadata.builder()
@@ -138,13 +143,17 @@ void testMergeMd1FieldsTakePriorityOverMd2() {
.isEncrypted(false).isBinary(false).namespaceAware(false)
.dataSignature("ds2").sharedKeyStatus("sks2").sharedKeyEnc("ske2")
.pubKeyCS("pkcs2").encoding("ascii").ivNonce("iv2")
+ .pubKeyHash(Metadata.PublicKeyHash.builder().hash("hash2").hashingAlgo("algo2").build())
+ .encKeyName("encKeyName2").encAlgo("encAlgo2")
+ .skeEncKeyName("skeEncKeyName2").skeEncAlgo("skeEncAlgo2")
+ .immutable(false)
.build();
Metadata merged = Metadata.merge(md1, md2);
- assertThat(merged.ttl(), is(1L));
- assertThat(merged.ttb(), is(2L));
- assertThat(merged.ttr(), is(3L));
+ assertThat(merged.ttl(), equalTo(1L));
+ assertThat(merged.ttb(), equalTo(2L));
+ assertThat(merged.ttr(), equalTo(3L));
assertThat(merged.ccd(), is(true));
assertThat(merged.isPublic(), is(true));
assertThat(merged.isHidden(), is(true));
@@ -152,12 +161,19 @@ void testMergeMd1FieldsTakePriorityOverMd2() {
assertThat(merged.isEncrypted(), is(true));
assertThat(merged.isBinary(), is(true));
assertThat(merged.namespaceAware(), is(true));
- assertThat(merged.dataSignature(), is("ds1"));
- assertThat(merged.sharedKeyStatus(), is("sks1"));
- assertThat(merged.sharedKeyEnc(), is("ske1"));
- assertThat(merged.pubKeyCS(), is("pkcs1"));
- assertThat(merged.encoding(), is("utf8"));
- assertThat(merged.ivNonce(), is("iv1"));
+ assertThat(merged.dataSignature(), equalTo("ds1"));
+ assertThat(merged.sharedKeyStatus(), equalTo("sks1"));
+ assertThat(merged.sharedKeyEnc(), equalTo("ske1"));
+ assertThat(merged.pubKeyCS(), equalTo("pkcs1"));
+ assertThat(merged.encoding(), equalTo("utf8"));
+ assertThat(merged.ivNonce(), equalTo("iv1"));
+ assertThat(merged.pubKeyHash(),
+ equalTo(Metadata.PublicKeyHash.builder().hash("hash1").hashingAlgo("algo1").build()));
+ assertThat(merged.encKeyName(), equalTo("encKeyName1"));
+ assertThat(merged.encAlgo(), equalTo("encAlgo1"));
+ assertThat(merged.skeEncKeyName(), equalTo("skeEncKeyName1"));
+ assertThat(merged.skeEncAlgo(), equalTo("skeEncAlgo1"));
+ assertThat(merged.immutable(), is(true));
}
@Test
@@ -173,13 +189,17 @@ void testMergeMd2FieldsUsedWhenMd1FieldsAreNull() {
.pubKeyCS("pkcs2").encoding("ascii").ivNonce("iv2")
.availableAt(now).expiresAt(now).refreshAt(now)
.createdAt(now).updatedAt(now)
+ .pubKeyHash(Metadata.PublicKeyHash.builder().hash("hash2").hashingAlgo("algo2").build())
+ .encKeyName("encKeyName2").encAlgo("encAlgo2")
+ .skeEncKeyName("skeEncKeyName2").skeEncAlgo("skeEncAlgo2")
+ .immutable(false)
.build();
Metadata merged = Metadata.merge(md1, md2);
- assertThat(merged.ttl(), is(1L));
- assertThat(merged.ttb(), is(2L));
- assertThat(merged.ttr(), is(3L));
+ assertThat(merged.ttl(), equalTo(1L));
+ assertThat(merged.ttb(), equalTo(2L));
+ assertThat(merged.ttr(), equalTo(3L));
assertThat(merged.ccd(), is(true));
assertThat(merged.isPublic(), is(true));
assertThat(merged.isHidden(), is(true));
@@ -187,17 +207,24 @@ void testMergeMd2FieldsUsedWhenMd1FieldsAreNull() {
assertThat(merged.isEncrypted(), is(true));
assertThat(merged.isBinary(), is(true));
assertThat(merged.namespaceAware(), is(true));
- assertThat(merged.dataSignature(), is("ds2"));
- assertThat(merged.sharedKeyStatus(), is("sks2"));
- assertThat(merged.sharedKeyEnc(), is("ske2"));
- assertThat(merged.pubKeyCS(), is("pkcs2"));
- assertThat(merged.encoding(), is("ascii"));
- assertThat(merged.ivNonce(), is("iv2"));
- assertThat(merged.availableAt(), is(now));
- assertThat(merged.expiresAt(), is(now));
- assertThat(merged.refreshAt(), is(now));
- assertThat(merged.createdAt(), is(now));
- assertThat(merged.updatedAt(), is(now));
+ assertThat(merged.dataSignature(), equalTo("ds2"));
+ assertThat(merged.sharedKeyStatus(), equalTo("sks2"));
+ assertThat(merged.sharedKeyEnc(), equalTo("ske2"));
+ assertThat(merged.pubKeyCS(), equalTo("pkcs2"));
+ assertThat(merged.encoding(), equalTo("ascii"));
+ assertThat(merged.ivNonce(), equalTo("iv2"));
+ assertThat(merged.availableAt(), equalTo(now));
+ assertThat(merged.expiresAt(), equalTo(now));
+ assertThat(merged.refreshAt(), equalTo(now));
+ assertThat(merged.createdAt(), equalTo(now));
+ assertThat(merged.updatedAt(), equalTo(now));
+ assertThat(merged.pubKeyHash(),
+ equalTo(Metadata.PublicKeyHash.builder().hash("hash2").hashingAlgo("algo2").build()));
+ assertThat(merged.encKeyName(), equalTo("encKeyName2"));
+ assertThat(merged.encAlgo(), equalTo("encAlgo2"));
+ assertThat(merged.skeEncKeyName(), equalTo("skeEncKeyName2"));
+ assertThat(merged.skeEncAlgo(), equalTo("skeEncAlgo2"));
+ assertThat(merged.immutable(), is(false));
}
@Test
@@ -225,6 +252,12 @@ void testMergeFieldRemainsNullWhenBothMd1AndMd2AreNull() {
assertThat(merged.refreshAt(), is(nullValue()));
assertThat(merged.createdAt(), is(nullValue()));
assertThat(merged.updatedAt(), is(nullValue()));
+ assertThat(merged.pubKeyHash(), is(nullValue()));
+ assertThat(merged.encKeyName(), is(nullValue()));
+ assertThat(merged.encAlgo(), is(nullValue()));
+ assertThat(merged.skeEncKeyName(), is(nullValue()));
+ assertThat(merged.skeEncAlgo(), is(nullValue()));
+ assertThat(merged.immutable(), is(nullValue()));
}
@Test
@@ -245,28 +278,28 @@ void testSetTtlIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
void testSetTtbIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setTtbIfNotNull(b, 10L), is(true));
- assertThat(b.build().ttb(), is(10L));
+ assertThat(b.build().ttb(), equalTo(10L));
}
@Test
void testSetTtbIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().ttb(10L);
assertThat(Metadata.setTtbIfNotNull(b, null), is(false));
- assertThat(b.build().ttb(), is(10L));
+ assertThat(b.build().ttb(), equalTo(10L));
}
@Test
void testSetTtrIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setTtrIfNotNull(b, 5L), is(true));
- assertThat(b.build().ttr(), is(5L));
+ assertThat(b.build().ttr(), equalTo(5L));
}
@Test
void testSetTtrIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().ttr(5L);
assertThat(Metadata.setTtrIfNotNull(b, null), is(false));
- assertThat(b.build().ttr(), is(5L));
+ assertThat(b.build().ttr(), equalTo(5L));
}
@Test
@@ -371,42 +404,42 @@ void testSetNamespaceAwareIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueW
void testSetDataSignatureIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setDataSignatureIfNotNull(b, "sig"), is(true));
- assertThat(b.build().dataSignature(), is("sig"));
+ assertThat(b.build().dataSignature(), equalTo("sig"));
}
@Test
void testSetDataSignatureIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().dataSignature("sig");
assertThat(Metadata.setDataSignatureIfNotNull(b, null), is(false));
- assertThat(b.build().dataSignature(), is("sig"));
+ assertThat(b.build().dataSignature(), equalTo("sig"));
}
@Test
void testSetSharedKeyStatusIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setSharedKeyStatusIfNotNull(b, "ok"), is(true));
- assertThat(b.build().sharedKeyStatus(), is("ok"));
+ assertThat(b.build().sharedKeyStatus(), equalTo("ok"));
}
@Test
void testSetSharedKeyStatusIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().sharedKeyStatus("ok");
assertThat(Metadata.setSharedKeyStatusIfNotNull(b, null), is(false));
- assertThat(b.build().sharedKeyStatus(), is("ok"));
+ assertThat(b.build().sharedKeyStatus(), equalTo("ok"));
}
@Test
void testSetSharedKeyEncIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setSharedKeyEncIfNotNull(b, "encKey"), is(true));
- assertThat(b.build().sharedKeyEnc(), is("encKey"));
+ assertThat(b.build().sharedKeyEnc(), equalTo("encKey"));
}
@Test
void testSetSharedKeyEncIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().sharedKeyEnc("encKey");
assertThat(Metadata.setSharedKeyEncIfNotNull(b, null), is(false));
- assertThat(b.build().sharedKeyEnc(), is("encKey"));
+ assertThat(b.build().sharedKeyEnc(), equalTo("encKey"));
}
@Test
@@ -420,34 +453,122 @@ void testSetPubKeyCSIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
void testSetPubKeyCSIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().pubKeyCS("checksum");
assertThat(Metadata.setPubKeyCSIfNotNull(b, null), is(false));
- assertThat(b.build().pubKeyCS(), is("checksum"));
+ assertThat(b.build().pubKeyCS(), equalTo("checksum"));
}
@Test
void testSetEncodingIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setEncodingIfNotNull(b, "utf8"), is(true));
- assertThat(b.build().encoding(), is("utf8"));
+ assertThat(b.build().encoding(), equalTo("utf8"));
}
@Test
void testSetEncodingIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().encoding("utf8");
assertThat(Metadata.setEncodingIfNotNull(b, null), is(false));
- assertThat(b.build().encoding(), is("utf8"));
+ assertThat(b.build().encoding(), equalTo("utf8"));
}
@Test
void testSetIvNonceIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
Metadata.MetadataBuilder b = Metadata.builder();
assertThat(Metadata.setIvNonceIfNotNull(b, "iv99"), is(true));
- assertThat(b.build().ivNonce(), is("iv99"));
+ assertThat(b.build().ivNonce(), equalTo("iv99"));
}
@Test
void testSetIvNonceIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
Metadata.MetadataBuilder b = Metadata.builder().ivNonce("iv99");
assertThat(Metadata.setIvNonceIfNotNull(b, null), is(false));
- assertThat(b.build().ivNonce(), is("iv99"));
+ assertThat(b.build().ivNonce(), equalTo("iv99"));
+ }
+
+ @Test
+ void testSetPubKeyHashIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
+ Metadata.MetadataBuilder b = Metadata.builder();
+ Metadata.PublicKeyHash hash = Metadata.PublicKeyHash.builder().hash("HASH").hashingAlgo("algo").build();
+ assertThat(Metadata.setPubKeyHashIfNotNull(b, hash), is(true));
+ assertThat(b.build().pubKeyHash(), equalTo(hash));
+ }
+
+ @Test
+ void testSetPubKeyHashIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
+ Metadata.PublicKeyHash hash = Metadata.PublicKeyHash.builder().hash("HASH").hashingAlgo("algo").build();
+ Metadata.MetadataBuilder b = Metadata.builder().pubKeyHash(hash);
+ assertThat(Metadata.setPubKeyHashIfNotNull(b, null), is(false));
+ assertThat(b.build().pubKeyHash(), equalTo(hash));
+ }
+
+ @Test
+ void testSetEncKeyNameIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
+ Metadata.MetadataBuilder b = Metadata.builder();
+ assertThat(Metadata.setEncKeyNameIfNotNull(b, "encKeyName"), is(true));
+ assertThat(b.build().encKeyName(), equalTo("encKeyName"));
+ }
+
+ @Test
+ void testSetEncKeyNameIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
+ Metadata.MetadataBuilder b = Metadata.builder().encKeyName("encKeyName");
+ assertThat(Metadata.setEncKeyNameIfNotNull(b, null), is(false));
+ assertThat(b.build().encKeyName(), equalTo("encKeyName"));
+ }
+
+ @Test
+ void testSetEncAlgoIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
+ Metadata.MetadataBuilder b = Metadata.builder();
+ assertThat(Metadata.setEncAlgoIfNotNull(b, "encAlgo"), is(true));
+ assertThat(b.build().encAlgo(), equalTo("encAlgo"));
+ }
+
+ @Test
+ void testSetEncAlgoIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
+ Metadata.MetadataBuilder b = Metadata.builder().encAlgo("encAlgo");
+ assertThat(Metadata.setEncAlgoIfNotNull(b, null), is(false));
+ assertThat(b.build().encAlgo(), equalTo("encAlgo"));
+ }
+
+ @Test
+ void testSetSkeEncKeyNameIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
+ Metadata.MetadataBuilder b = Metadata.builder();
+ assertThat(Metadata.setSkeEncKeyNameIfNotNull(b, "skeEncAlgo"), is(true));
+ assertThat(b.build().skeEncKeyName(), equalTo("skeEncAlgo"));
+ }
+
+ @Test
+ void testSetSkeEncKeyNameIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
+ Metadata.MetadataBuilder b = Metadata.builder().skeEncKeyName("skeEncAlgo");
+ assertThat(Metadata.setSkeEncKeyNameIfNotNull(b, null), is(false));
+ assertThat(b.build().skeEncKeyName(), equalTo("skeEncAlgo"));
+ }
+
+ @Test
+ void testSetSkeEncAlgoIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
+ Metadata.MetadataBuilder b = Metadata.builder();
+ assertThat(Metadata.setSkeEncAlgoIfNotNull(b, "skeEncAlgo"), is(true));
+ assertThat(b.build().skeEncAlgo(), equalTo("skeEncAlgo"));
+ }
+
+ @Test
+ void testSetSkeEncAlgoIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
+ Metadata.MetadataBuilder b = Metadata.builder().skeEncAlgo("skeEncAlgo");
+ assertThat(Metadata.setSkeEncAlgoIfNotNull(b, null), is(false));
+ assertThat(b.build().skeEncAlgo(), equalTo("skeEncAlgo"));
+ }
+
+
+ @Test
+ void testSetImmutableIfNotNullReturnsTrueAndSetsValueWhenNotNull() {
+ Metadata.MetadataBuilder b = Metadata.builder();
+ assertThat(Metadata.setImmutableIfNotNull(b, false), is(true));
+ assertThat(b.build().immutable(), is(false));
}
+
+ @Test
+ void testSetImmutableIfNotNullReturnsFalseAndDoesNotOverwriteExistingValueWhenNull() {
+ Metadata.MetadataBuilder b = Metadata.builder().immutable(true);
+ assertThat(Metadata.setImmutableIfNotNull(b, null), is(false));
+ assertThat(b.build().immutable(), is(true));
+ }
+
}
diff --git a/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java b/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java
index 8512fdc6..fbeb590a 100644
--- a/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java
+++ b/at_client/src/test/java/org/atsign/client/impl/AtClientImplTest.java
@@ -420,14 +420,16 @@ void testGetSharedKey() throws Exception {
String iv = generateRandomIvBase64(16);
String encrypted1 = aesEncryptToBase64("hello from me", encryptKey, iv);
String encrypted2 = aesEncryptToBase64("greetings from another world", encryptKey, iv);
+ String sharedKeyEnc = rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey());
AtCommandExecutor executor = TestExecutorBuilder.builder()
- .stub("llookup:shared_key.another@test", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey()))
- .stubLookupResponse("llookup:all:@another:key1@test", "key1@test", encrypted1, "ivNonce", iv)
+ .stub("llookup:shared_key.another@test", "data:" + sharedKeyEnc)
+ .stubLookupResponse("llookup:all:@another:key1@test", "key1@test", encrypted1,
+ "ivNonce", iv, "sharedKeyEnc", sharedKeyEnc)
.stub("llookup:all:public:key2@test", "error:AT0001:deliberate")
.stubExecutionException("llookup:all:public:key3@test")
- .stub("lookup:shared_key@another", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey()))
- .stubLookupResponse("lookup:all:key4@another", "key4@test", encrypted2, "ivNonce", iv)
+ .stubLookupResponse("lookup:all:key4@another", "key4@test", encrypted2,
+ "ivNonce", iv, "sharedKeyEnc", sharedKeyEnc)
.build();
AtClientImpl client = AtClientImpl.builder().atSign(atSign).keys(keys).executor(executor).eventBus(bus).build();
@@ -458,13 +460,15 @@ void testGetSharedKeyBinary() throws Exception {
String encrypted1 = aesEncryptToBase64(Base2e15Utils.encode(bytes1), encryptKey, iv);
String encrypted2 = aesEncryptToBase64(Base2e15Utils.encode(bytes2), encryptKey, iv);
+ String sharedKeyEnc = rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey());
AtCommandExecutor executor = TestExecutorBuilder.builder()
- .stub("llookup:shared_key.another@test", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey()))
- .stub("lookup:shared_key@another", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey()))
- .stubLookupResponse("llookup:all:@another:key1@test", "key1@test", encrypted1, "ivNonce", iv, "isBinary", true)
+ .stub("llookup:shared_key.another@test", "data:" + sharedKeyEnc)
+ .stubLookupResponse("llookup:all:@another:key1@test", "key1@test", encrypted1,
+ "ivNonce", iv, "isBinary", true, "sharedKeyEnc", sharedKeyEnc)
.stub("llookup:all:public:key2@test", "error:AT0001:deliberate")
.stubExecutionException("llookup:all:public:key3@test")
- .stubLookupResponse("lookup:all:key4@another", "key4@test", encrypted2, "ivNonce", iv, "isBinary", true)
+ .stubLookupResponse("lookup:all:key4@another", "key4@test", encrypted2,
+ "ivNonce", iv, "isBinary", true, "sharedKeyEnc", sharedKeyEnc)
.stubLookupResponse("llookup:all:@another:key5@test", "key5@test", encrypted1, "ivNonce", iv)
.build();
diff --git a/at_client/src/test/java/org/atsign/client/impl/AtClientsIT.java b/at_client/src/test/java/org/atsign/client/impl/AtClientsIT.java
new file mode 100644
index 00000000..eededce2
--- /dev/null
+++ b/at_client/src/test/java/org/atsign/client/impl/AtClientsIT.java
@@ -0,0 +1,95 @@
+package org.atsign.client.impl;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+
+import org.atsign.client.api.AtClient;
+import org.atsign.client.api.AtKeys;
+import org.atsign.client.api.AtSign;
+import org.atsign.client.impl.common.ReconnectStrategy;
+import org.atsign.client.impl.util.KeysUtils;
+import org.atsign.cucumber.helpers.Helpers;
+import org.atsign.virtualenv.VirtualEnv;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+class AtClientsIT {
+
+ private static AtSign atSign;
+ private static AtKeys keys;
+ private static File keysFile;
+ public static String AT_SIGN_KEYS_DIR;
+ public static String ATSIGN_KEYS_SUFFIX;
+
+ @BeforeAll
+ public static void classSetup() throws Exception {
+ if (!Helpers.isHostPortReachable("vip.ve.atsign.zone:64", SECONDS.toMillis(2))) {
+ VirtualEnv.setUp();
+ }
+ atSign = AtSign.createAtSign("colin");
+ keysFile = new File("target/at_demo_data/lib/assets/atkeys/@colin.atKeys");
+ keys = KeysUtils.loadKeys(keysFile);
+ AT_SIGN_KEYS_DIR = KeysUtils.expectedKeysFilesLocation;
+ ATSIGN_KEYS_SUFFIX = KeysUtils.keysFileSuffix;
+ KeysUtils.expectedKeysFilesLocation = "target/at_demo_data/lib/assets/atkeys";
+ KeysUtils.keysFileSuffix = ".atKeys";
+ }
+
+ @AfterAll
+ public static void classTeardown() {
+ KeysUtils.expectedKeysFilesLocation = AT_SIGN_KEYS_DIR;
+ KeysUtils.keysFileSuffix = ATSIGN_KEYS_SUFFIX;
+ }
+
+ @Test
+ void testBuildWithoutKeys() throws Exception {
+ AtClients.AtClientBuilder builder = AtClients.builder()
+ .url("vip.ve.atsign.zone")
+ .atSign(atSign)
+ .reconnect(ReconnectStrategy.NONE);
+ try (AtClient client = builder.build()) {
+ assertThat(client.getAtKeys(".*", false).get(), is(not(empty())));
+ }
+ }
+
+ @Test
+ void testBuildWithKeys() throws Exception {
+ AtClients.AtClientBuilder builder = AtClients.builder()
+ .url("vip.ve.atsign.zone")
+ .atSign(atSign)
+ .keys(keys)
+ .reconnect(ReconnectStrategy.NONE);
+ try (AtClient client = builder.build()) {
+ assertThat(client.getAtKeys(".*", false).get(), is(not(empty())));
+ }
+ }
+
+ @Test
+ void testBuildWithKeysPathSetToDirectory() throws Exception {
+ AtClients.AtClientBuilder builder = AtClients.builder()
+ .url("vip.ve.atsign.zone")
+ .atSign(atSign)
+ .keysPath(keysFile.getParentFile().getAbsolutePath())
+ .reconnect(ReconnectStrategy.NONE);
+ try (AtClient client = builder.build()) {
+ assertThat(client.getAtKeys(".*", false).get(), is(not(empty())));
+ }
+ }
+
+ @Test
+ void testBuildWithKeysPathSetToFile() throws Exception {
+ AtClients.AtClientBuilder builder = AtClients.builder()
+ .url("vip.ve.atsign.zone")
+ .atSign(atSign)
+ .keysPath(keysFile.getAbsolutePath())
+ .reconnect(ReconnectStrategy.NONE);
+ try (AtClient client = builder.build()) {
+ assertThat(client.getAtKeys(".*", false).get(), is(not(empty())));
+ }
+ }
+
+}
diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java
index 9b7519e1..c52d60dd 100644
--- a/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java
+++ b/at_client/src/test/java/org/atsign/client/impl/commands/AuthenticationCommandsTest.java
@@ -13,6 +13,9 @@
import org.atsign.client.impl.exceptions.AtUnauthenticatedException;
import org.junit.jupiter.api.Test;
+import java.util.Collections;
+import java.util.Map;
+
public class AuthenticationCommandsTest {
@Test
@@ -60,6 +63,18 @@ public void testAuthenticateWithApkamWithEnrollmentId() throws Exception {
AuthenticationCommands.authenticateWithPkam(executor, createAtSign("@alice"), keys);
}
+ @Test
+ public void testAuthenticateWithApkamWithConfig() throws Exception {
+ AtKeys keys = AtKeys.builder().apkamKeyPair(generateRSAKeyPair()).enrollmentId(createEnrollmentId("12345")).build();
+ AtCommandExecutor executor = TestExecutorBuilder.builder()
+ .stub("from:@alice:clientConfig:.+", "data:challenge")
+ .stub("pkam:signingAlgo:rsa2048:hashingAlgo:sha256:enrollmentId:12345:.+", "data:success")
+ .build();
+
+ Map config = Collections.singletonMap("clientVersion", "1.2.3");
+ AuthenticationCommands.authenticateWithPkam(executor, createAtSign("@alice"), keys, config);
+ }
+
@Test
public void testAuthenticateWithApkamFailThrowsExpectedException() throws Exception {
AtKeys keys = AtKeys.builder().apkamKeyPair(generateRSAKeyPair()).enrollmentId(createEnrollmentId("12345")).build();
@@ -83,15 +98,9 @@ public void testPkamAuthenticatorThrowsOnReadyException() throws Exception {
.build();
Exception ex = assertThrows(Exception.class,
- () -> AuthenticationCommands.pkamAuthenticator(createAtSign("@alice"), keys)
+ () -> AuthenticationCommands.pkamAuthenticator(createAtSign("@alice"), keys, null)
.accept(executor));
assertThat(ex, instanceOf(AtOnReadyException.class));
assertThat(ex.getMessage(), containsString("deliberate"));
}
-
- @Test
- public void testBytesToHex() throws Exception {
- byte[] bytes = new byte[] {(byte) 0x00, (byte) 0x0f, (byte) 0xff};
- assertThat(AuthenticationCommands.bytesToHex(bytes), is("000fff"));
- }
}
diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/CommandBuildersTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/CommandBuildersTest.java
index 9136c86b..641197fe 100644
--- a/at_client/src/test/java/org/atsign/client/impl/commands/CommandBuildersTest.java
+++ b/at_client/src/test/java/org/atsign/client/impl/commands/CommandBuildersTest.java
@@ -1,13 +1,14 @@
package org.atsign.client.impl.commands;
import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.atsign.client.impl.common.EnrollmentId.createEnrollmentId;
import static org.atsign.client.api.AtSign.createAtSign;
+import static org.atsign.client.impl.common.EnrollmentId.createEnrollmentId;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -36,7 +37,35 @@ public void testFromBuilderGeneratesExpectedOutput() {
String command = CommandBuilders.fromCommandBuilder()
.atSign(createAtSign("@bob"))
.build();
- assertEquals("from:@bob", command);
+ assertThat(command, equalTo("from:@bob"));
+ }
+
+ @Test
+ public void testFromBuilderWithEmptyConfigGeneratesExpectedOutput() {
+
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> CommandBuilders.fromCommandBuilder().build());
+ assertThat(ex.getMessage(), containsString("atSign not set"));
+
+ String command = CommandBuilders.fromCommandBuilder()
+ .atSign(createAtSign("@bob"))
+ .config(new HashMap<>())
+ .build();
+ assertThat(command, equalTo("from:@bob"));
+ }
+
+ @Test
+ public void testFromBuilderWithConfigGeneratesExpectedOutput() {
+
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+ () -> CommandBuilders.fromCommandBuilder().build());
+ assertThat(ex.getMessage(), containsString("atSign not set"));
+
+ String command = CommandBuilders.fromCommandBuilder()
+ .atSign(createAtSign("@bob"))
+ .config(Collections.singletonMap("clientVersion", "1.2.3"))
+ .build();
+ assertThat(command, equalTo("from:@bob:clientConfig:{\"clientVersion\":\"1.2.3\"}"));
}
@Test
@@ -49,13 +78,13 @@ public void testCramBuilderGeneratesExpectedOutput() {
String command = CommandBuilders.cramCommandBuilder()
.digest("digest")
.build();
- assertEquals("cram:digest", command);
+ assertThat(command, equalTo("cram:digest"));
}
@Test
public void testPolBuilderGeneratesExpectedOutput() {
String command = CommandBuilders.polCommandBuilder().build();
- assertEquals("pol", command);
+ assertThat(command, equalTo("pol"));
}
@Test
@@ -68,7 +97,7 @@ public void testPkamBuilderGeneratesExpectedOutput() {
String command = CommandBuilders.pkamCommandBuilder()
.digest("digest")
.build();
- assertEquals("pkam:digest", command);
+ assertThat(command, equalTo("pkam:digest"));
}
@Test
@@ -95,7 +124,7 @@ public void testPkamBuilderGeneratesExpectedOutputWhenEnrollmentIdIsSet() {
.signingAlgo("RSA")
.hashingAlgo("SHA")
.build();
- assertEquals("pkam:signingAlgo:RSA:hashingAlgo:SHA:enrollmentId:12345-6789:digest", command);
+ assertThat(command, equalTo("pkam:signingAlgo:RSA:hashingAlgo:SHA:enrollmentId:12345-6789:digest"));
}
@Test
@@ -253,7 +282,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.sharedBy(createAtSign("@bob"))
.value("my Value 123")
.build();
- assertEquals("update:test@bob my Value 123", command);
+ assertThat(command, equalTo("update:test@bob my Value 123"));
// self key but shared with self
command = CommandBuilders.updateCommandBuilder()
@@ -262,7 +291,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.sharedWith(createAtSign("bob"))
.value("My value 123")
.build();
- assertEquals("update:@bob:test@bob My value 123", command);
+ assertThat(command, equalTo("update:@bob:test@bob My value 123"));
// public key
command = CommandBuilders.updateCommandBuilder()
@@ -271,7 +300,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.isPublic(true)
.value("my Value 123")
.build();
- assertEquals("update:public:publickey@bob my Value 123", command);
+ assertThat(command, equalTo("update:public:publickey@bob my Value 123"));
// cached public key
command = CommandBuilders.updateCommandBuilder()
@@ -281,7 +310,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.isCached(true)
.value("my Value 123")
.build();
- assertEquals("update:cached:public:publickey@alice my Value 123", command);
+ assertThat(command, equalTo("update:cached:public:publickey@alice my Value 123"));
// shared key
command = CommandBuilders.updateCommandBuilder()
@@ -290,7 +319,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.sharedWith(createAtSign("@alice"))
.value("my Value 123")
.build();
- assertEquals("update:@alice:sharedkey@bob my Value 123", command);
+ assertThat(command, equalTo("update:@alice:sharedkey@bob my Value 123"));
// with shared key
SharedKey sk1 = Keys.sharedKeyBuilder()
@@ -304,7 +333,8 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.key(sk1)
.value("myBinaryValue123456")
.build();
- assertEquals("update:ttl:600000:isBinary:true:isEncrypted:true:@alice:test@bob myBinaryValue123456", command);
+ assertThat(command,
+ equalTo("update:ttl:600000:isBinary:true:isEncrypted:true:@alice:test@bob myBinaryValue123456"));
// with public key
PublicKey pk1 = Keys.publicKeyBuilder()
@@ -316,7 +346,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.key(pk1)
.value("myValue123")
.build();
- assertEquals("update:isEncrypted:false:cached:public:test@bob myValue123", command);
+ assertThat(command, equalTo("update:isEncrypted:false:cached:public:test@bob myValue123"));
// with self key
SelfKey sk2 = Keys.selfKeyBuilder()
@@ -328,7 +358,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.key(sk2)
.value("myValue123")
.build();
- assertEquals("update:ttl:600000:isEncrypted:true:test@bob myValue123", command);
+ assertThat(command, equalTo("update:ttl:600000:isEncrypted:true:test@bob myValue123"));
// with self key (shared with self)
AtSign bob = createAtSign("@bob");
@@ -342,7 +372,7 @@ public void testUpdateBuilderGeneratesExpectedOutput() {
.key(sk3)
.value("myValue123")
.build();
- assertEquals("update:ttl:600000:isEncrypted:true:@bob:test@bob myValue123", command);
+ assertThat(command, equalTo("update:ttl:600000:isEncrypted:true:@bob:test@bob myValue123"));
// private hidden key
// TODO with private hidden key when implemented
@@ -438,7 +468,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.keyName("test")
.sharedBy(createAtSign("@alice"))
.build();
- assertEquals("llookup:test@alice", command);
+ assertThat(command, equalTo("llookup:test@alice"));
// Type.METADATA self key
command = CommandBuilders.llookupCommandBuilder()
@@ -446,7 +476,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.sharedBy(createAtSign("@alice"))
.operation(LookupOperation.meta)
.build();
- assertEquals("llookup:meta:test@alice", command);
+ assertThat(command, equalTo("llookup:meta:test@alice"));
// hidden self key, meta
command = CommandBuilders.llookupCommandBuilder()
@@ -455,7 +485,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.operation(LookupOperation.meta)
.isHidden(true)
.build();
- assertEquals("llookup:meta:_test@alice", command);
+ assertThat(command, equalTo("llookup:meta:_test@alice"));
// Type.ALL public cached key
command = CommandBuilders.llookupCommandBuilder()
@@ -465,7 +495,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.isPublic(true)
.operation(LookupOperation.all)
.build();
- assertEquals("llookup:all:cached:public:publickey@alice", command);
+ assertThat(command, equalTo("llookup:all:cached:public:publickey@alice"));
// no key name
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
@@ -489,12 +519,12 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.key(pk)
.operation(LookupOperation.meta)
.build();
- assertEquals("llookup:meta:public:publickey@bob", command);
+ assertThat(command, equalTo("llookup:meta:public:publickey@bob"));
command = CommandBuilders.llookupCommandBuilder()
.rawKey("public:publickey@bob")
.operation(LookupOperation.meta)
.build();
- assertEquals("llookup:meta:public:publickey@bob", command);
+ assertThat(command, equalTo("llookup:meta:public:publickey@bob"));
// with shared key
SharedKey sk = Keys.sharedKeyBuilder()
@@ -506,7 +536,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.key(sk)
.operation(LookupOperation.none)
.build();
- assertEquals("llookup:@alice:sharedkey@bob", command);
+ assertThat(command, equalTo("llookup:@alice:sharedkey@bob"));
// with self key
SelfKey selfKey1 = Keys.selfKeyBuilder().sharedBy(createAtSign("@bob")).name("test").build();
@@ -514,7 +544,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.key(selfKey1)
.operation(LookupOperation.all)
.build(); // "llookup:all:test@bob"
- assertEquals("llookup:all:test@bob", command);
+ assertThat(command, equalTo("llookup:all:test@bob"));
// with self key (shared with self)
AtSign as = createAtSign("@bob");
@@ -523,7 +553,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.key(selfKey2)
.operation(LookupOperation.all)
.build();
- assertEquals("llookup:all:@bob:test@bob", command);
+ assertThat(command, equalTo("llookup:all:@bob:test@bob"));
// with cached public key
PublicKey pk2 = Keys.publicKeyBuilder()
@@ -535,7 +565,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.key(pk2)
.operation(LookupOperation.all)
.build();
- assertEquals("llookup:all:cached:public:publickey@bob", command);
+ assertThat(command, equalTo("llookup:all:cached:public:publickey@bob"));
// with cached shared key
SharedKey sk2 = Keys.sharedKeyBuilder()
@@ -548,7 +578,7 @@ public void testLlookupBuilderGeneratesExpectedOutput() {
.key(sk2)
.operation(LookupOperation.none)
.build();
- assertEquals("llookup:cached:@alice:sharedkey@bob", command);
+ assertThat(command, equalTo("llookup:cached:@alice:sharedkey@bob"));
// with private hidden key
// TODO: not implemented yet
@@ -605,11 +635,11 @@ public void lookupVerbBuilderTest() {
.keyName("test")
.sharedBy(createAtSign("@alice"))
.build();
- assertEquals("lookup:test@alice", command);
+ assertThat(command, equalTo("lookup:test@alice"));
command = CommandBuilders.lookupCommandBuilder()
.rawKey("test@alice")
.build();
- assertEquals("lookup:test@alice", command);
+ assertThat(command, equalTo("lookup:test@alice"));
// Type.METADATA
command = CommandBuilders.lookupCommandBuilder()
@@ -617,7 +647,7 @@ public void lookupVerbBuilderTest() {
.sharedBy(createAtSign("@alice"))
.operation(LookupOperation.meta)
.build();
- assertEquals("lookup:meta:test@alice", command);
+ assertThat(command, equalTo("lookup:meta:test@alice"));
// Type.ALL
command = CommandBuilders.lookupCommandBuilder()
@@ -625,7 +655,7 @@ public void lookupVerbBuilderTest() {
.sharedBy(createAtSign("@alice"))
.operation(LookupOperation.all)
.build(); // "lookup:test@alice"
- assertEquals("lookup:all:test@alice", command);
+ assertThat(command, equalTo("lookup:all:test@alice"));
// no key name
IllegalArgumentException ex =
@@ -647,7 +677,7 @@ public void lookupVerbBuilderTest() {
.key(sk)
.operation(LookupOperation.meta)
.build();
- assertEquals("lookup:meta:test@sharedby", command);
+ assertThat(command, equalTo("lookup:meta:test@sharedby"));
}
@Test
@@ -673,11 +703,11 @@ public void plookupVerbBuilderTest() {
.keyName("publickey")
.sharedBy(createAtSign("@alice"))
.build(); // "plookup:publickey@alice"
- assertEquals("plookup:publickey@alice", command);
+ assertThat(command, equalTo("plookup:publickey@alice"));
command = CommandBuilders.plookupCommandBuilder()
.rawKey("publickey@alice")
.build(); // "plookup:publickey@alice"
- assertEquals("plookup:publickey@alice", command);
+ assertThat(command, equalTo("plookup:publickey@alice"));
// Type.METADATA
command = CommandBuilders.plookupCommandBuilder()
@@ -685,7 +715,7 @@ public void plookupVerbBuilderTest() {
.sharedBy(createAtSign("@alice"))
.operation(LookupOperation.meta)
.build();
- assertEquals("plookup:meta:publickey@alice", command);
+ assertThat(command, equalTo("plookup:meta:publickey@alice"));
// Type.ALL
command = CommandBuilders.plookupCommandBuilder()
@@ -693,7 +723,7 @@ public void plookupVerbBuilderTest() {
.sharedBy(createAtSign("@alice"))
.operation(LookupOperation.all)
.build();
- assertEquals("plookup:all:publickey@alice", command);
+ assertThat(command, equalTo("plookup:all:publickey@alice"));
// no key
IllegalArgumentException ex =
@@ -714,7 +744,7 @@ public void plookupVerbBuilderTest() {
.key(pk)
.operation(LookupOperation.all)
.build();
- assertEquals("plookup:all:publickey@bob", command);
+ assertThat(command, equalTo("plookup:all:publickey@bob"));
// bypasscache true
command = CommandBuilders.plookupCommandBuilder()
@@ -723,7 +753,7 @@ public void plookupVerbBuilderTest() {
.bypassCache(true)
.operation(LookupOperation.all)
.build();
- assertEquals("plookup:bypassCache:true:all:publickey@alice", command);
+ assertThat(command, equalTo("plookup:bypassCache:true:all:publickey@alice"));
}
@Test
@@ -751,11 +781,11 @@ public void deleteVerbBuilderTest() {
.keyName("publickey")
.sharedBy(createAtSign("@alice"))
.build();
- assertEquals("delete:public:publickey@alice", command);
+ assertThat(command, equalTo("delete:public:publickey@alice"));
command = CommandBuilders.deleteCommandBuilder()
.rawKey("public:publickey@alice")
.build();
- assertEquals("delete:public:publickey@alice", command);
+ assertThat(command, equalTo("delete:public:publickey@alice"));
// delete a cached public key
command = CommandBuilders.deleteCommandBuilder()
@@ -764,14 +794,14 @@ public void deleteVerbBuilderTest() {
.keyName("publickey")
.sharedBy(createAtSign("@bob"))
.build();
- assertEquals("delete:cached:public:publickey@bob", command);
+ assertThat(command, equalTo("delete:cached:public:publickey@bob"));
// delete a self key
command = CommandBuilders.deleteCommandBuilder()
.keyName("test")
.sharedBy(createAtSign("@alice"))
.build();
- assertEquals("delete:test@alice", command);
+ assertThat(command, equalTo("delete:test@alice"));
// delete a hidden self key
command = CommandBuilders.deleteCommandBuilder()
@@ -779,7 +809,7 @@ public void deleteVerbBuilderTest() {
.keyName("test")
.sharedBy(createAtSign("@alice"))
.build();
- assertEquals("delete:_test@alice", command);
+ assertThat(command, equalTo("delete:_test@alice"));
// delete a shared key
command = CommandBuilders.deleteCommandBuilder()
@@ -787,7 +817,7 @@ public void deleteVerbBuilderTest() {
.sharedBy(createAtSign("@alice"))
.sharedWith(createAtSign("@bob"))
.build();
- assertEquals("delete:@bob:test@alice", command);
+ assertThat(command, equalTo("delete:@bob:test@alice"));
// delete a cached shared key
command = CommandBuilders.deleteCommandBuilder()
@@ -796,7 +826,7 @@ public void deleteVerbBuilderTest() {
.sharedBy(createAtSign("@alice"))
.sharedWith(createAtSign("@bob"))
.build();
- assertEquals("delete:cached:@bob:test@alice", command);
+ assertThat(command, equalTo("delete:cached:@bob:test@alice"));
// missing key name
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
@@ -816,7 +846,7 @@ public void deleteVerbBuilderTest() {
command = CommandBuilders.deleteCommandBuilder()
.key(selfKey)
.build();
- assertEquals("delete:test@alice", command);
+ assertThat(command, equalTo("delete:test@alice"));
// with public key
PublicKey pk = Keys.publicKeyBuilder().sharedBy(createAtSign("@bob")).name("publickey").build();
@@ -833,7 +863,7 @@ public void deleteVerbBuilderTest() {
command = CommandBuilders.deleteCommandBuilder()
.key(sk)
.build();
- assertEquals("delete:@bob:test@alice", command);
+ assertThat(command, equalTo("delete:@bob:test@alice"));
}
@Test
@@ -880,45 +910,45 @@ public void scanVerbBuilderTest() {
// Test not setting any parameters
String command = CommandBuilders.scanCommandBuilder().build();
- assertEquals("scan", command);
+ assertThat(command, equalTo("scan"));
// Test setting just regex
command = CommandBuilders.scanCommandBuilder().regex("*.public")
.build();
- assertEquals("scan *.public", command);
+ assertThat(command, equalTo("scan *.public"));
// Test setting just fromAtSign
command = CommandBuilders.scanCommandBuilder()
.fromAtSign(createAtSign("@other"))
.build();
- assertEquals("scan:@other", command);
+ assertThat(command, equalTo("scan:@other"));
// Test seting just showHidden
command = CommandBuilders.scanCommandBuilder()
.showHidden(true)
.build();
- assertEquals("scan:showHidden:true", command);
+ assertThat(command, equalTo("scan:showHidden:true"));
// Test setting regex & fromAtSign
command = CommandBuilders.scanCommandBuilder()
.regex("*.public")
.fromAtSign(createAtSign("@other"))
.build();
- assertEquals("scan:@other *.public", command);
+ assertThat(command, equalTo("scan:@other *.public"));
// Test setting regex & showHidden
command = CommandBuilders.scanCommandBuilder()
.regex("*.public")
.showHidden(true)
.build();
- assertEquals("scan:showHidden:true *.public", command);
+ assertThat(command, equalTo("scan:showHidden:true *.public"));
// Test setting fromAtSign & showHidden
command = CommandBuilders.scanCommandBuilder()
.fromAtSign(createAtSign("@other"))
.showHidden(true)
.build();
- assertEquals("scan:showHidden:true:@other", command);
+ assertThat(command, equalTo("scan:showHidden:true:@other"));
// Test setting regex & fromAtSign & showHidden
command = CommandBuilders.scanCommandBuilder()
@@ -926,7 +956,7 @@ public void scanVerbBuilderTest() {
.fromAtSign(createAtSign("@other"))
.showHidden(true)
.build();
- assertEquals("scan:showHidden:true:@other *.public", command);
+ assertThat(command, equalTo("scan:showHidden:true:@other *.public"));
}
@Test
@@ -945,7 +975,7 @@ public void testNotifyTextBuilderGeneratesTheExpectedOutput() {
.recipient(createAtSign("@test"))
.text("Hi")
.build();
- assertEquals("notify:messageType:text:@test:Hi", command);
+ assertThat(command, equalTo("notify:messageType:text:@test:Hi"));
}
@Test
@@ -990,14 +1020,14 @@ public void notifyKeyChangeBuilderTest() {
.recipient(createAtSign("recipient"))
.key("phone")
.build();
- assertEquals("notify:update:messageType:key:@recipient:phone@sender", command);
+ assertThat(command, equalTo("notify:update:messageType:key:@recipient:phone@sender"));
// test command with a fully formed key
command = CommandBuilders.notifyKeyChangeCommandBuilder()
.operation(NotifyOperation.update)
.key("@recipient:phone@sender")
.build();
- assertEquals("notify:update:messageType:key:@recipient:phone@sender", command);
+ assertThat(command, equalTo("notify:update:messageType:key:@recipient:phone@sender"));
// test command when ttr and value are present
command = CommandBuilders.notifyKeyChangeCommandBuilder()
@@ -1006,7 +1036,7 @@ public void notifyKeyChangeBuilderTest() {
.ttr(1000L)
.value("cache_me")
.build();
- assertEquals("notify:update:messageType:key:ttr:1000:@recipient:phone@sender:cache_me", command);
+ assertThat(command, equalTo("notify:update:messageType:key:ttr:1000:@recipient:phone@sender:cache_me"));
}
@Test
@@ -1019,7 +1049,7 @@ public void notificationStatusVerbBuilderTest() {
String command = CommandBuilders.notifyStatusCommandBuilder()
.notificationId("n1234").build();
- assertEquals("notify:status:n1234", command);
+ assertThat(command, equalTo("notify:status:n1234"));
}
@Test
@@ -1326,7 +1356,7 @@ void testSubsequentEnrollRequestReturnsExpectedCommand() {
@Test
public void testOtpBuilderGeneratesExpectedOutput() {
String command = CommandBuilders.otpCommandBuilder().build();
- assertEquals("otp:get", command);
+ assertThat(command, equalTo("otp:get"));
}
@Test
diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/NotificationsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/NotificationsTest.java
index 93610411..d4a6ce67 100644
--- a/at_client/src/test/java/org/atsign/client/impl/commands/NotificationsTest.java
+++ b/at_client/src/test/java/org/atsign/client/impl/commands/NotificationsTest.java
@@ -1,7 +1,7 @@
package org.atsign.client.impl.commands;
-import static org.atsign.client.impl.util.EncryptionUtils.generateRSAKeyPair;
import static org.atsign.client.api.AtSign.createAtSign;
+import static org.atsign.client.impl.util.EncryptionUtils.generateRSAKeyPair;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -12,9 +12,9 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
+import org.atsign.client.api.AtCommandExecutor;
import org.atsign.client.api.AtEvents;
import org.atsign.client.api.AtKeys;
-import org.atsign.client.api.AtCommandExecutor;
import org.atsign.client.api.AtSign;
import org.atsign.client.impl.exceptions.AtOnReadyException;
import org.atsign.client.impl.exceptions.AtTimeoutException;
@@ -49,7 +49,8 @@ void testMonitorWrapsConsumer() throws Exception {
throw new AtTimeoutException("deliberate");
}).when(executor).sendSync(eq("monitor"), Mockito.any(Consumer.class));
- Exception ex = assertThrows(Exception.class, () -> Notifications.monitor(atSign, keys, consumer).accept(executor));
+ Exception ex =
+ assertThrows(Exception.class, () -> Notifications.monitor(atSign, keys, null, consumer).accept(executor));
assertThat(ex, instanceOf(AtOnReadyException.class));
}
diff --git a/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java b/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java
index c694bfd2..0c85db67 100644
--- a/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java
+++ b/at_client/src/test/java/org/atsign/client/impl/commands/SharedKeyCommandsTest.java
@@ -1,7 +1,7 @@
package org.atsign.client.impl.commands;
-import static org.atsign.client.impl.util.EncryptionUtils.*;
import static org.atsign.client.api.AtSign.createAtSign;
+import static org.atsign.client.impl.util.EncryptionUtils.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
@@ -9,10 +9,13 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
-import org.atsign.client.api.AtKeys;
import org.atsign.client.api.AtCommandExecutor;
+import org.atsign.client.api.AtKeys;
import org.atsign.client.api.Keys;
+import org.atsign.client.api.Metadata;
+import org.atsign.client.impl.exceptions.AtPublicKeyChangeException;
import org.atsign.client.impl.exceptions.AtServerRuntimeException;
+import org.atsign.client.impl.util.EncryptionUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -103,9 +106,30 @@ void getGetSharedByOther() throws Exception {
String encryptKey = generateAESKeyBase64();
String iv = generateRandomIvBase64(16);
String encrypted = aesEncryptToBase64("hello colin", encryptKey, iv);
+ String sharedKeyEnc = rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey());
+ Metadata.PublicKeyHash hash = Metadata.PublicKeyHash.builder()
+ .hash(EncryptionUtils.digest(keys.getEncryptPublicKey(), HASHING_ALGO_SHA512))
+ .hashingAlgo(HASHING_ALGO_SHA512)
+ .build();
+ AtCommandExecutor executor = TestExecutorBuilder.builder()
+ .stubLookupResponse("lookup:all:test@gary", "@colin:test@gary", encrypted,
+ "ivNonce", iv, "sharedKeyEnc", sharedKeyEnc, "pubKeyHash", hash)
+ .build();
+
+ String actual = SharedKeyCommands.get(executor, createAtSign("colin"), keys, key);
+
+ assertThat(actual, equalTo("hello colin"));
+ }
+
+ @Test
+ void getGetSharedByOtherBackwardCompatibilityCase() throws Exception {
+ String encryptKey = generateAESKeyBase64();
+ String iv = generateRandomIvBase64(16);
+ String encrypted = aesEncryptToBase64("hello colin", encryptKey, iv);
+ String sharedKeyEnc = rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey());
AtCommandExecutor executor = TestExecutorBuilder.builder()
.stubLookupResponse("lookup:all:test@gary", "@colin:test@gary", encrypted, "ivNonce", iv)
- .stub("lookup:shared_key@gary", "data:" + rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey()))
+ .stub("lookup:shared_key@gary", "data:" + sharedKeyEnc)
.build();
String actual = SharedKeyCommands.get(executor, createAtSign("colin"), keys, key);
@@ -113,6 +137,25 @@ void getGetSharedByOther() throws Exception {
assertThat(actual, equalTo("hello colin"));
}
+ @Test
+ void getGetSharedByOtherThrowsExceptionForPubKeyHashMismatch() throws Exception {
+ String encryptKey = generateAESKeyBase64();
+ String iv = generateRandomIvBase64(16);
+ String encrypted = aesEncryptToBase64("hello colin", encryptKey, iv);
+ String sharedKeyEnc = rsaEncryptToBase64(encryptKey, keys.getEncryptPublicKey());
+ Metadata.PublicKeyHash hash = Metadata.PublicKeyHash.builder()
+ .hash("XXX")
+ .hashingAlgo(HASHING_ALGO_SHA512)
+ .build();
+ AtCommandExecutor executor = TestExecutorBuilder.builder()
+ .stubLookupResponse("lookup:all:test@gary", "@colin:test@gary", encrypted,
+ "ivNonce", iv, "sharedKeyEnc", sharedKeyEnc, "pubKeyHash", hash)
+ .build();
+
+ assertThrows(AtPublicKeyChangeException.class,
+ () -> SharedKeyCommands.get(executor, createAtSign("colin"), keys, key));
+ }
+
@Test
void testPutWhenSharedKeyAlreadyExists() throws Exception {
String encryptKey = generateAESKeyBase64();
@@ -126,15 +169,18 @@ void testPutWhenSharedKeyAlreadyExists() throws Exception {
}
@Test
- void testPutWhenSharedKeDoesNotAlreadyExists() throws Exception {
+ void testPutWhenSharedKeyDoesNotAlreadyExists() throws Exception {
AtCommandExecutor executor = TestExecutorBuilder.builder()
.stub("llookup:shared_key.colin@gary", "error:AT0015:deliberate")
.stub("plookup:publickey@colin", "data:" + keys.getEncryptPublicKey())
.stub("update:shared_key.colin@gary .+", "data:1")
.stub("update:ttr:86400000:@colin:shared_key@gary .+", "data:2")
- .stub("update:isEncrypted:true:ivNonce:.+:@colin:test@gary .+", "data:3")
+ .stub("update:isEncrypted:true:sharedKeyEnc:.+:ivNonce:.+:@colin:test@gary .+", "data:3")
.build();
SharedKeyCommands.put(executor, createAtSign("gary"), keys, key, "hello colin");
+
+ verify(executor).sendSync(argThat(s -> s.contains("update:") && s.contains(":pubKeyHash:")));
+ verify(executor).sendSync(argThat(s -> s.contains("update:") && s.contains(":pubKeyCS:")));
}
}
diff --git a/at_client/src/test/java/org/atsign/client/impl/util/EncryptionUtilsTest.java b/at_client/src/test/java/org/atsign/client/impl/util/EncryptionUtilsTest.java
index 5d69eafc..ce4ae458 100644
--- a/at_client/src/test/java/org/atsign/client/impl/util/EncryptionUtilsTest.java
+++ b/at_client/src/test/java/org/atsign/client/impl/util/EncryptionUtilsTest.java
@@ -1,9 +1,16 @@
package org.atsign.client.impl.util;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.security.KeyPair;
+import java.security.Signature;
+import java.util.Base64;
+
+import org.atsign.client.impl.exceptions.AtDecryptionException;
+import org.atsign.client.impl.exceptions.AtEncryptionException;
import org.junit.jupiter.api.Test;
class EncryptionUtilsTest {
@@ -21,4 +28,148 @@ void testAesEncryptionWithRandomInitialisationVector() throws Exception {
assertThat(decrypted, equalTo(text));
}
+ @Test
+ void testAesEncryptToBase64ThrowsExpectedExceptions() throws Exception {
+ String key = EncryptionUtils.generateAESKeyBase64();
+ String text = "mary had a little lamb";
+ String iv = EncryptionUtils.generateRandomIvBase64(16);
+
+ AtEncryptionException ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.aesEncryptToBase64(null, key, iv));
+ assertThat(ex.getMessage(), containsString("AES encryption failed : input is blank"));
+
+ ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.aesEncryptToBase64(text, null, iv));
+ assertThat(ex.getMessage(), containsString("AES encryption failed : key is blank"));
+
+ ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.aesEncryptToBase64(text, key, null));
+ assertThat(ex.getMessage(), containsString("AES encryption failed : iv is blank"));
+ }
+
+ @Test
+ void testAesDecryptFromBase64ThrowsExpectedExceptions() throws Exception {
+ String key = EncryptionUtils.generateAESKeyBase64();
+ String text = "mary had a little lamb";
+ String iv = EncryptionUtils.generateRandomIvBase64(16);
+ String encrypted = EncryptionUtils.aesEncryptToBase64(text, key, iv);
+
+ AtDecryptionException ex = assertThrows(AtDecryptionException.class,
+ () -> EncryptionUtils.aesDecryptFromBase64(null, key, iv));
+ assertThat(ex.getMessage(), containsString("AES decryption failed : input is blank"));
+
+ ex = assertThrows(AtDecryptionException.class,
+ () -> EncryptionUtils.aesDecryptFromBase64(encrypted, null, iv));
+ assertThat(ex.getMessage(), containsString("AES decryption failed : key is blank"));
+
+ ex = assertThrows(AtDecryptionException.class,
+ () -> EncryptionUtils.aesDecryptFromBase64(encrypted, key, null));
+ assertThat(ex.getMessage(), containsString("AES decryption failed : iv is blank"));
+ }
+
+ @Test
+ void testRsaEncryptToBase64AndRsaDecryptFromBase64() throws Exception {
+ KeyPair keyPair = EncryptionUtils.generateRSAKeyPair();
+ String publicKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPublic());
+ String privateKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPrivate());
+ String text = "mary had a little lamb";
+
+ String encrypted = EncryptionUtils.rsaEncryptToBase64(text, publicKeyBase64);
+ assertThat(encrypted, not(equalTo(text)));
+
+ String decrypted = EncryptionUtils.rsaDecryptFromBase64(encrypted, privateKeyBase64);
+ assertThat(decrypted, equalTo(text));
+ }
+
+ @Test
+ void testRsaEncryptToBase64ThrowExpectedExceptions() throws Exception {
+ KeyPair keyPair = EncryptionUtils.generateRSAKeyPair();
+ String publicKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPublic());
+ String text = "mary had a little lamb";
+
+ AtEncryptionException ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.rsaEncryptToBase64(null, publicKeyBase64));
+ assertThat(ex.getMessage(), containsString("RSA encryption failed : input is blank"));
+
+ ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.rsaEncryptToBase64(text, null));
+ assertThat(ex.getMessage(), containsString("RSA encryption failed : key is blank"));
+ }
+
+ @Test
+ void testRsaDecryptFromBase64ThrowExpectedExceptions() throws Exception {
+ KeyPair keyPair = EncryptionUtils.generateRSAKeyPair();
+ String publicKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPublic());
+ String privateKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPrivate());
+ String text = "mary had a little lamb";
+ String encrypted = EncryptionUtils.rsaEncryptToBase64(text, publicKeyBase64);
+
+ AtDecryptionException ex = assertThrows(AtDecryptionException.class,
+ () -> EncryptionUtils.rsaDecryptFromBase64(null, privateKeyBase64));
+ assertThat(ex.getMessage(), containsString("RSA decryption failed : input is blank"));
+
+ ex = assertThrows(AtDecryptionException.class,
+ () -> EncryptionUtils.rsaDecryptFromBase64(encrypted, null));
+ assertThat(ex.getMessage(), containsString("RSA decryption failed : key is blank"));
+ }
+
+ @Test
+ void testSignSHA256RSA() throws Exception {
+ KeyPair keyPair = EncryptionUtils.generateRSAKeyPair();
+ String privateKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPrivate());
+ String text = "mary had a little lamb";
+
+ String signature = EncryptionUtils.signSHA256RSA(text, privateKeyBase64);
+ assertThat(signature, not(equalTo(text)));
+
+ Signature verifier = Signature.getInstance("SHA256withRSA");
+ verifier.initVerify(keyPair.getPublic());
+ verifier.update(text.getBytes(UTF_8));
+
+ assertThat(verifier.verify(Base64.getDecoder().decode(signature)), is(true));
+ }
+
+ @Test
+ void testSignSHA256RSAThrowExpectedExceptions() throws Exception {
+ KeyPair keyPair = EncryptionUtils.generateRSAKeyPair();
+ String privateKeyBase64 = EncryptionUtils.toStringBase64(keyPair.getPrivate());
+ String text = "mary had a little lamb";
+
+ AtEncryptionException ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.signSHA256RSA(null, privateKeyBase64));
+ assertThat(ex.getMessage(), containsString("SHA256 sign failed : input is blank"));
+
+ ex = assertThrows(AtEncryptionException.class,
+ () -> EncryptionUtils.signSHA256RSA(text, null));
+ assertThat(ex.getMessage(), containsString("SHA256 sign failed : key is blank"));
+ }
+
+ @Test
+ void testDigest() throws Exception {
+ String text = "mary had a little lamb";
+
+ String digest = EncryptionUtils.digest(text, "MD5");
+ assertThat(digest, not(equalTo(text)));
+ }
+
+ @Test
+ void testDigestThrowsExpectedExceptions() throws Exception {
+ String text = "mary had a little lamb";
+
+ AtEncryptionException ex = assertThrows(AtEncryptionException.class, () -> EncryptionUtils.digest(text, "XXX"));
+ assertThat(ex.getMessage(), containsString("failed to hash : XXX MessageDigest not available"));
+
+ ex = assertThrows(AtEncryptionException.class, () -> EncryptionUtils.digest(null, "MD5"));
+ assertThat(ex.getMessage(), containsString("failed to hash : input blank"));
+
+ ex = assertThrows(AtEncryptionException.class, () -> EncryptionUtils.digest("text", null));
+ assertThat(ex.getMessage(), containsString("failed to hash : algo blank"));
+ }
+
+ @Test
+ public void testBytesToHex() {
+ byte[] bytes = new byte[] {(byte) 0x00, (byte) 0x0f, (byte) 0xff};
+ assertThat(EncryptionUtils.bytesToHex(bytes), is("000fff"));
+ }
+
}
diff --git a/at_client/src/test/resources/features/Monitor.feature b/at_client/src/test/resources/features/Monitor.feature
index b3c5cb0b..4d803fd2 100644
--- a/at_client/src/test/resources/features/Monitor.feature
+++ b/at_client/src/test/resources/features/Monitor.feature
@@ -28,7 +28,6 @@ Feature: AtClient API Monitor tests
And @colin AtClient.delete for SharedKey test shared with @gary
Then @gary AtClient monitor receives the following
| Event Type | messageType | from | to | operation | key | decryptedValue |
- | sharedKeyNotification | MessageType.key | @colin | @gary | update | @gary:shared_key@colin | |
| updateNotification | MessageType.key | @colin | @gary | update | @gary:test@colin | |
| decryptedUpdateNotification | MessageType.key | @colin | @gary | update | @gary:test@colin | hello world |
| deleteNotification | MessageType.key | @colin | @gary | delete | @gary:test@colin | |
diff --git a/pom.xml b/pom.xml
index b816cfbe..1135db42 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,9 +61,11 @@
5.5.0
2.2
7.14.0
- 1.2.0
2.0.3
4.2.0
+
+
+ 1.2.0