Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions at_client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@
<config.checkstyle>../config/checkstyle.xml</config.checkstyle>

<!-- lower for now because cli and register code has no tests -->
<coverage.line>0.77</coverage.line>
<coverage.branch>0.7</coverage.branch>
<coverage.line>0.80</coverage.line>
<coverage.branch>0.80</coverage.branch>

</properties>

<build>

<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>

<plugins>

<plugin>
Expand Down
16 changes: 6 additions & 10 deletions at_client/src/main/java/org/atsign/client/api/AtKeys.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -154,18 +154,14 @@ public Map<String, String> 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());
}

}
104 changes: 101 additions & 3 deletions at_client/src/main/java/org/atsign/client/api/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
*/
Expand All @@ -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();
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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;
}
}
24 changes: 18 additions & 6 deletions at_client/src/main/java/org/atsign/client/impl/AtClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class AtClientImpl implements AtClient {

private final AtSign atSign;
private final AtKeys keys;
private final Map<String, Object> config;
private final AtCommandExecutor executor;
private final AtEventBus eventBus;
private final AtomicBoolean isMonitoring = new AtomicBoolean();
Expand All @@ -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<String, Object> 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));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -268,12 +274,18 @@ private void onSharedKeyNotification(Map<String, Object> eventData) throws AtDec
private void onUpdateNotification(Map<String, Object> 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<String, Object> metadata = (Map<String, Object>) 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<String, Object> newEventData = new HashMap<>(eventData);
newEventData.put("decryptedValue", decryptedValue);
Expand Down
35 changes: 30 additions & 5 deletions at_client/src/main/java/org/atsign/client/impl/AtClients.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,6 +35,8 @@ public class AtClients {
public static AtClient createAtClient(String url,
AtSign atSign,
AtKeys keys,
String keysPath,
Map<String, Object> config,
Long timeoutMillis,
Long awaitReadyMillis,
ReconnectStrategy reconnect,
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -89,6 +110,9 @@ public static AtClient createAtClient(String url,
* If <b>keys</b> 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 <b>keysPath</b> 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 <b>timeoutMillis</b> is not set then builder will default to
* {@link AtCommandExecutors#DEFAULT_TIMEOUT_MILLIS}.
* If <b>awaitReadyMillis</b> is not set then the builder will default to
Expand All @@ -100,4 +124,5 @@ public static AtClient createAtClient(String url,
public static class AtClientBuilder {
// required for javadoc
}

}
Loading
Loading