Skip to content

Commit 2efdc8e

Browse files
authored
Merge pull request #64 from /issues/63
Review device and account key setup
2 parents 97579ad + 2c2e45d commit 2efdc8e

31 files changed

+609
-447
lines changed

hub/src/main/java/ch/iterate/hub/core/FirstLoginDeviceSetupCallback.java renamed to hub/src/main/java/ch/iterate/hub/core/DeviceSetupCallback.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,30 @@
66

77
import ch.cyberduck.core.Host;
88
import ch.cyberduck.core.UUIDRandomStringService;
9-
import ch.cyberduck.core.exception.ConnectionCanceledException;
10-
import ch.cyberduck.core.exception.LoginCanceledException;
119

10+
import ch.iterate.hub.crypto.DeviceKeys;
11+
import ch.iterate.hub.crypto.UserKeys;
1212
import ch.iterate.hub.model.AccountKeyAndDeviceName;
13+
import ch.iterate.hub.workflows.exceptions.AccessException;
1314

14-
public interface FirstLoginDeviceSetupCallback {
15+
public interface DeviceSetupCallback {
1516

1617
/**
1718
* Prompt user for device name
1819
*
1920
* @return Device name
20-
* @throws ConnectionCanceledException Canceled prompt by user
21+
* @throws AccessException Canceled prompt by user
2122
*/
22-
String displayAccountKeyAndAskDeviceName(Host bookmark, AccountKeyAndDeviceName accountKeyAndDeviceName) throws ConnectionCanceledException;
23+
String displayAccountKeyAndAskDeviceName(Host bookmark, AccountKeyAndDeviceName accountKeyAndDeviceName) throws AccessException;
2324

2425
/**
2526
* Prompt user for existing account key
2627
*
2728
* @param initialDeviceName Default device name
2829
* @return Account key and device name
29-
* @throws ConnectionCanceledException Canceled prompt by user
30+
* @throws AccessException Canceled prompt by user
3031
*/
31-
AccountKeyAndDeviceName askForAccountKeyAndDeviceName(Host bookmark, String initialDeviceName) throws ConnectionCanceledException;
32+
AccountKeyAndDeviceName askForAccountKeyAndDeviceName(Host bookmark, String initialDeviceName) throws AccessException;
3233

3334
/**
3435
* Generate initial account key
@@ -39,15 +40,23 @@ default String generateAccountKey() {
3940
return new UUIDRandomStringService().random();
4041
}
4142

42-
FirstLoginDeviceSetupCallback disabled = new FirstLoginDeviceSetupCallback() {
43+
default DeviceKeys generateDeviceKey() {
44+
return DeviceKeys.create();
45+
}
46+
47+
default UserKeys generateUserKeys() {
48+
return UserKeys.create();
49+
}
50+
51+
DeviceSetupCallback disabled = new DeviceSetupCallback() {
4352
@Override
44-
public String displayAccountKeyAndAskDeviceName(final Host bookmark, final AccountKeyAndDeviceName accountKeyAndDeviceName) throws ConnectionCanceledException {
45-
throw new LoginCanceledException();
53+
public String displayAccountKeyAndAskDeviceName(final Host bookmark, final AccountKeyAndDeviceName accountKeyAndDeviceName) throws AccessException {
54+
throw new AccessException("Disabled");
4655
}
4756

4857
@Override
49-
public AccountKeyAndDeviceName askForAccountKeyAndDeviceName(final Host bookmark, final String initialDeviceName) throws ConnectionCanceledException {
50-
throw new LoginCanceledException();
58+
public AccountKeyAndDeviceName askForAccountKeyAndDeviceName(final Host bookmark, final String initialDeviceName) throws AccessException {
59+
throw new AccessException("Disabled");
5160
}
5261
};
5362
}
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313
import java.lang.reflect.Constructor;
1414
import java.lang.reflect.InvocationTargetException;
1515

16-
public class FirstLoginDeviceSetupCallbackFactory extends Factory<FirstLoginDeviceSetupCallback> {
17-
private static final Logger log = LogManager.getLogger(FirstLoginDeviceSetupCallbackFactory.class);
16+
public final class DeviceSetupCallbackFactory extends Factory<DeviceSetupCallback> {
17+
private static final Logger log = LogManager.getLogger(DeviceSetupCallbackFactory.class);
1818

19-
private FirstLoginDeviceSetupCallbackFactory() {
20-
super("factory.firstlogindevicesetupcallback.class");
19+
private DeviceSetupCallbackFactory() {
20+
super("factory.devicesetupcallback.class");
2121
}
2222

23-
public FirstLoginDeviceSetupCallback create() {
23+
public DeviceSetupCallback create() {
2424
try {
25-
final Constructor<? extends FirstLoginDeviceSetupCallback> constructor
25+
final Constructor<? extends DeviceSetupCallback> constructor
2626
= ConstructorUtils.getMatchingAccessibleConstructor(clazz);
2727
if(null == constructor) {
2828
log.warn("No default controller in {}", constructor.getClass());
@@ -33,18 +33,18 @@ public FirstLoginDeviceSetupCallback create() {
3333
}
3434
catch(InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
3535
log.error("Failure loading callback class {}. {}", clazz, e.getMessage());
36-
return FirstLoginDeviceSetupCallback.disabled;
36+
return DeviceSetupCallback.disabled;
3737
}
3838
}
3939

40-
private static FirstLoginDeviceSetupCallbackFactory singleton;
40+
private static DeviceSetupCallbackFactory singleton;
4141

4242
/**
4343
* @return Firs tLogin Device Setup Callback instance for the current platform.
4444
*/
45-
public static synchronized FirstLoginDeviceSetupCallback get() {
45+
public static synchronized DeviceSetupCallback get() {
4646
if(null == singleton) {
47-
singleton = new FirstLoginDeviceSetupCallbackFactory();
47+
singleton = new DeviceSetupCallbackFactory();
4848
}
4949
return singleton.create();
5050
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2025 shift7 GmbH. All rights reserved.
3+
*/
4+
5+
package ch.iterate.hub.crypto;
6+
7+
import org.cryptomator.cryptolib.common.ECKeyPair;
8+
import org.cryptomator.cryptolib.common.P384KeyPair;
9+
10+
import javax.security.auth.Destroyable;
11+
import java.util.Objects;
12+
13+
public class DeviceKeys implements Destroyable {
14+
15+
private final ECKeyPair ecKeyPair;
16+
17+
public DeviceKeys(final ECKeyPair ecKeyPair) {
18+
this.ecKeyPair = ecKeyPair;
19+
}
20+
21+
public ECKeyPair getEcKeyPair() {
22+
return ecKeyPair;
23+
}
24+
25+
@Override
26+
public void destroy() {
27+
ecKeyPair.destroy();
28+
}
29+
30+
@Override
31+
public boolean isDestroyed() {
32+
return ecKeyPair.isDestroyed();
33+
}
34+
35+
@Override
36+
public final boolean equals(final Object o) {
37+
if (!(o instanceof DeviceKeys)) return false;
38+
39+
DeviceKeys that = (DeviceKeys) o;
40+
return Objects.equals(ecKeyPair, that.ecKeyPair);
41+
}
42+
43+
@Override
44+
public int hashCode() {
45+
return Objects.hashCode(ecKeyPair);
46+
}
47+
48+
@Override
49+
public String toString() {
50+
final StringBuilder sb = new StringBuilder("DeviceKeys{");
51+
sb.append("ecKeyPair=").append(ecKeyPair);
52+
sb.append('}');
53+
return sb.toString();
54+
}
55+
56+
public static DeviceKeys create() {
57+
return new DeviceKeys(P384KeyPair.generate());
58+
}
59+
60+
public static boolean validate(final DeviceKeys deviceKeys) {
61+
return deviceKeys.getEcKeyPair() != null;
62+
}
63+
64+
public static final DeviceKeys notfound = new DeviceKeys(null);
65+
}

hub/src/main/java/ch/iterate/hub/crypto/KeyHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import ch.iterate.hub.crypto.exceptions.NotECKeyException;
2626
import com.google.common.io.BaseEncoding;
2727

28-
public class KeyHelper {
28+
public final class KeyHelper {
2929

3030
// adapted from org.cryptomator.ui.keyloading.hub.HubKeyLoadingModule
3131
public static String getDeviceIdFromDeviceKeyPair(final ECKeyPair deviceKeyPair) {

hub/src/main/java/ch/iterate/hub/crypto/UserKeys.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,26 @@
77
import org.cryptomator.cryptolib.common.ECKeyPair;
88
import org.cryptomator.cryptolib.common.P384KeyPair;
99

10+
import javax.security.auth.Destroyable;
1011
import java.security.interfaces.ECPrivateKey;
1112
import java.security.interfaces.ECPublicKey;
1213
import java.security.spec.InvalidKeySpecException;
1314
import java.text.ParseException;
1415
import java.util.Base64;
16+
import java.util.Objects;
17+
18+
import static ch.iterate.hub.crypto.KeyHelper.decodeKeyPair;
19+
import static ch.iterate.hub.crypto.UserKeyPayload.createFromPayload;
1520

1621
import ch.iterate.hub.crypto.uvf.UvfAccessTokenPayload;
1722
import com.fasterxml.jackson.core.JsonProcessingException;
1823
import com.nimbusds.jose.JOSEException;
1924

20-
import static ch.iterate.hub.crypto.KeyHelper.decodeKeyPair;
21-
import static ch.iterate.hub.crypto.UserKeyPayload.createFromPayload;
22-
2325
/**
2426
* Represents Cryptomator Hub <a href="https://docs.cryptomator.org/en/latest/security/hub/#user-key-pair>User Keys</a>.
2527
* Counterpart of <a href="https://github.com/cryptomator/hub/blob/develop/frontend/src/common/crypto.ts"><code>UserKeys</code></a>.
2628
*/
27-
public class UserKeys {
29+
public class UserKeys implements Destroyable {
2830

2931
private final ECKeyPair ecdhKeyPair;
3032
private final ECKeyPair ecdsaKeyPair;
@@ -42,6 +44,34 @@ public ECKeyPair ecdsaKeyPair() {
4244
return ecdsaKeyPair;
4345
}
4446

47+
@Override
48+
public void destroy() {
49+
ecdhKeyPair.destroy();
50+
ecdsaKeyPair.destroy();
51+
}
52+
53+
@Override
54+
public boolean isDestroyed() {
55+
return ecdhKeyPair.isDestroyed() || ecdsaKeyPair.isDestroyed();
56+
}
57+
58+
@Override
59+
public final boolean equals(final Object o) {
60+
if(!(o instanceof UserKeys)) {
61+
return false;
62+
}
63+
64+
UserKeys userKeys = (UserKeys) o;
65+
return Objects.equals(ecdhKeyPair, userKeys.ecdhKeyPair) && Objects.equals(ecdsaKeyPair, userKeys.ecdsaKeyPair);
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
int result = Objects.hashCode(ecdhKeyPair);
71+
result = 31 * result + Objects.hashCode(ecdsaKeyPair);
72+
return result;
73+
}
74+
4575
@Override
4676
public String toString() {
4777
final StringBuilder sb = new StringBuilder("UserKeys{");

hub/src/main/java/ch/iterate/hub/model/AccountKeyAndDeviceName.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@
66

77
public class AccountKeyAndDeviceName {
88
private String accountKey;
9-
109
private String deviceName;
1110

12-
public AccountKeyAndDeviceName() {
13-
}
14-
1511
public String accountKey() {
1612
return accountKey;
1713
}

hub/src/main/java/ch/iterate/hub/protocols/hub/HubGrantAccessSchedulerService.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,55 @@
55
package ch.iterate.hub.protocols.hub;
66

77
import ch.cyberduck.core.Host;
8+
import ch.cyberduck.core.HostPasswordStore;
89
import ch.cyberduck.core.PasswordCallback;
910
import ch.cyberduck.core.exception.BackgroundException;
1011
import ch.cyberduck.core.shared.OneTimeSchedulerFeature;
1112

1213
import org.apache.logging.log4j.LogManager;
1314
import org.apache.logging.log4j.Logger;
1415

16+
import java.util.List;
17+
1518
import ch.iterate.hub.client.ApiException;
16-
import ch.iterate.hub.client.api.DeviceResourceApi;
17-
import ch.iterate.hub.client.api.UsersResourceApi;
1819
import ch.iterate.hub.client.api.VaultResourceApi;
19-
import ch.iterate.hub.core.FirstLoginDeviceSetupCallbackFactory;
20+
import ch.iterate.hub.client.model.Role;
21+
import ch.iterate.hub.client.model.VaultDto;
22+
import ch.iterate.hub.crypto.UserKeys;
2023
import ch.iterate.hub.protocols.hub.exceptions.HubExceptionMappingService;
21-
import ch.iterate.hub.workflows.CachingUserKeysService;
22-
import ch.iterate.hub.workflows.CachingWoTService;
24+
import ch.iterate.hub.workflows.DeviceKeysServiceImpl;
2325
import ch.iterate.hub.workflows.GrantAccessServiceImpl;
2426
import ch.iterate.hub.workflows.UserKeysServiceImpl;
25-
import ch.iterate.hub.workflows.VaultServiceImpl;
26-
import ch.iterate.hub.workflows.WoTServiceImpl;
2727
import ch.iterate.hub.workflows.exceptions.AccessException;
2828
import ch.iterate.hub.workflows.exceptions.SecurityFailure;
2929

3030
public class HubGrantAccessSchedulerService extends OneTimeSchedulerFeature<Host> {
3131
private static final Logger log = LogManager.getLogger(HubGrantAccessSchedulerService.class);
3232

3333
private final HubSession session;
34+
private final HostPasswordStore keychain;
3435

35-
public HubGrantAccessSchedulerService(final HubSession session) {
36+
public HubGrantAccessSchedulerService(final HubSession session, final HostPasswordStore keychain) {
3637
this.session = session;
38+
this.keychain = keychain;
3739
}
3840

3941
@Override
4042
public Host operate(final PasswordCallback callback) throws BackgroundException {
4143
log.info("Scheduler for {}", session.getHost());
4244
try {
43-
final GrantAccessServiceImpl service = new GrantAccessServiceImpl(new VaultResourceApi(session.getClient()), new UsersResourceApi(session.getClient()),
44-
new CachingUserKeysService(new UserKeysServiceImpl(new UsersResourceApi(session.getClient()), new DeviceResourceApi(session.getClient()))),
45-
new VaultServiceImpl(new VaultResourceApi(session.getClient())),
46-
new CachingWoTService(new WoTServiceImpl(new UsersResourceApi(session.getClient()))));
47-
service.grantAccessToUsersRequiringAccessGrant(session.getHost(), FirstLoginDeviceSetupCallbackFactory.get());
45+
final UserKeys userKeys = new UserKeysServiceImpl(session).getUserKeys(session.getHost(), session.getMe(),
46+
new DeviceKeysServiceImpl(keychain).getDeviceKeys(session.getHost()));
47+
final List<VaultDto> accessibleVaults = new VaultResourceApi(session.getClient()).apiVaultsAccessibleGet(Role.OWNER);
48+
final GrantAccessServiceImpl service = new GrantAccessServiceImpl(session);
49+
for(final VaultDto accessibleVault : accessibleVaults) {
50+
if(Boolean.TRUE.equals(accessibleVault.getArchived())) {
51+
log.debug("Skip archived vault {}", accessibleVault);
52+
continue;
53+
}
54+
service.grantAccessToUsersRequiringAccessGrant(accessibleVault.getId(), userKeys);
55+
}
56+
userKeys.destroy();
4857
}
4958
catch(ApiException e) {
5059
log.error("Scheduler for {}: Automatic Access Grant failed.", session.getHost(), e);

0 commit comments

Comments
 (0)