Skip to content

Commit bc2a5eb

Browse files
ylangiscdkocher
authored andcommitted
Inherit from UVFVault.
1 parent 51d2043 commit bc2a5eb

File tree

7 files changed

+123
-141
lines changed

7 files changed

+123
-141
lines changed

hub/src/main/java/ch/iterate/hub/crypto/uvf/UvfMetadataPayload.java

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
import ch.cyberduck.core.AlphanumericRandomStringService;
99
import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
1010

11-
import org.bouncycastle.crypto.Digest;
12-
import org.bouncycastle.crypto.digests.SHA256Digest;
13-
import org.bouncycastle.crypto.macs.HMac;
14-
import org.bouncycastle.crypto.params.KeyParameter;
15-
import org.bouncycastle.util.encoders.Base32;
11+
import org.cryptomator.cryptolib.api.Cryptor;
12+
import org.cryptomator.cryptolib.api.CryptorProvider;
13+
import org.cryptomator.cryptolib.api.UVFMasterkey;
1614
import org.cryptomator.cryptolib.common.P384KeyPair;
1715
import org.openapitools.jackson.nullable.JsonNullableModule;
1816

@@ -27,7 +25,6 @@
2725
import java.util.Objects;
2826
import java.util.UUID;
2927

30-
import at.favre.lib.hkdf.HKDF;
3128
import ch.iterate.hub.crypto.exceptions.NotECKeyException;
3229
import ch.iterate.hub.model.JWEPayload;
3330
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -93,8 +90,14 @@ public static UvfMetadataPayload fromJWE(final String jwe) throws JsonProcessing
9390
return mapper.readValue(jwe, UvfMetadataPayload.class);
9491
}
9592

93+
public String toJSON() throws JsonProcessingException {
94+
ObjectMapper mapper = new ObjectMapper();
95+
mapper.registerModule(new JsonNullableModule());
96+
return mapper.writeValueAsString(this);
97+
}
98+
9699
public static UvfMetadataPayload create() {
97-
final String kid = new AlphanumericRandomStringService(4).random();
100+
final String kid = Base64URL.encode(new AlphanumericRandomStringService(4).random()).toString();
98101
final byte[] rawSeed = new byte[32];
99102
FastSecureRandomProvider.get().provide().nextBytes(rawSeed);
100103
final byte[] kdfSalt = new byte[32];
@@ -103,28 +106,21 @@ public static UvfMetadataPayload create() {
103106
.withFileFormat("AES-256-GCM-32k")
104107
.withNameFormat("AES-SIV-512-B64URL")
105108
.withSeeds(new HashMap<String, String>() {{
106-
put(kid, Base64URL.encode(rawSeed).toString());
109+
put(kid, Base64.getEncoder().encodeToString(rawSeed));
107110
}})
108111
.withLatestSeed(kid)
109112
.withinitialSeed(kid)
110113
.withKdf("HKDF-SHA512")
111-
.withKdfSalt(Base64URL.encode(kdfSalt).toString());
112-
}
113-
114-
public byte[] computeRootDirId() {
115-
return HKDF.fromHmacSha512().extractAndExpand(Base64URL.from(kdfSalt()).decode(), Base64URL.from(seeds().get(initialSeed())).decode(), "rootDirId".getBytes(), 256 / 8);
114+
.withKdfSalt(Base64.getEncoder().encodeToString(kdfSalt));
116115
}
117116

118-
public String computeRootDirIdHash(final byte[] rootDirId) {
119-
final byte[] hmacKey = HKDF.fromHmacSha512()
120-
.extractAndExpand(Base64URL.from(kdfSalt()).decode(), Base64URL.from(seeds().get(initialSeed())).decode(), "hmac".getBytes(), 512 / 8);
121-
final Digest digest = new SHA256Digest();
122-
final HMac hMac = new HMac(digest);
123-
hMac.init(new KeyParameter(hmacKey));
124-
hMac.update(rootDirId, 0, rootDirId.length);
125-
final byte[] hmacOut = new byte[hMac.getMacSize()];
126-
hMac.doFinal(hmacOut, 0);
127-
return Base32.toBase32String(Arrays.copyOfRange(hmacOut, 0, 20));
117+
public String computeRootDirIdHash() throws JsonProcessingException {
118+
final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.toJSON());
119+
final CryptorProvider provider = CryptorProvider.forScheme(CryptorProvider.Scheme.UVF_DRAFT);
120+
final Cryptor cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide());
121+
final byte[] rootDirId = masterKey.rootDirId();
122+
final String hashedRootDirId = cryptor.fileNameCryptor(masterKey.firstRevision()).hashDirectoryId(rootDirId);
123+
return hashedRootDirId;
128124
}
129125

130126

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

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
import ch.cyberduck.core.AbstractPath;
99
import ch.cyberduck.core.DisabledListProgressListener;
1010
import ch.cyberduck.core.ListService;
11-
import ch.cyberduck.core.LoginOptions;
12-
import ch.cyberduck.core.PasswordCallback;
1311
import ch.cyberduck.core.Path;
1412
import ch.cyberduck.core.Session;
1513
import ch.cyberduck.core.cryptomator.ContentWriter;
16-
import ch.cyberduck.core.cryptomator.CryptoVault;
14+
import ch.cyberduck.core.cryptomator.UVFVault;
1715
import ch.cyberduck.core.exception.BackgroundException;
1816
import ch.cyberduck.core.features.Directory;
1917
import ch.cyberduck.core.preferences.PreferencesFactory;
@@ -22,47 +20,50 @@
2220

2321
import org.apache.logging.log4j.LogManager;
2422
import org.apache.logging.log4j.Logger;
25-
import org.cryptomator.cryptolib.api.CryptorProvider;
26-
import org.cryptomator.cryptolib.api.Masterkey;
23+
import org.cryptomator.cryptolib.api.UVFMasterkey;
2724

2825
import java.nio.charset.StandardCharsets;
29-
import java.util.Base64;
3026
import java.util.EnumSet;
3127

3228
/**
3329
* Cryptomator vault implementation for Cipherduck (without masterkey file).
3430
*/
35-
public class HubCryptoVault extends CryptoVault {
31+
public class HubCryptoVault extends UVFVault {
3632
private static final Logger log = LogManager.getLogger(HubCryptoVault.class);
33+
private final String decryptedPayload;
3734

38-
// See https://github.com/cryptomator/hub/blob/develop/frontend/src/common/vaultconfig.ts
39-
//const jwtPayload: VaultConfigPayload = {
40-
// jti: vaultId,
41-
// format: 8,
42-
// cipherCombo: 'SIV_GCM',
43-
// shorteningThreshold: 220
44-
//};
45-
//const header = JSON.stringify({
46-
// kid: kid,
47-
// typ: 'jwt',
48-
// alg: 'HS256',
49-
// hub: hubConfig
50-
//});
51-
private static final VaultConfig VAULT_CONFIG = new VaultConfig(8, 220, CryptorProvider.Scheme.SIV_GCM, "HS256", null);
5235

5336
public HubCryptoVault(final Path home) {
54-
super(home);
37+
this(home, null, null, null); // TODO cleanup
5538
}
5639

57-
public HubCryptoVault(final Path home, final String masterkey, final String config, final byte[] pepper) {
58-
super(home);
40+
public HubCryptoVault(final Path home, final String decryptedPayload, final String config, final byte[] pepper) {
41+
super(home, decryptedPayload, config, pepper);
42+
this.decryptedPayload = decryptedPayload;
5943
}
6044

45+
public Path encrypt(Session<?> session, Path file, byte[] directoryId, boolean metadata) throws BackgroundException {
46+
log.debug("HubCryptoVault.encrypt. Use directory ID '{}' for folder {}", directoryId, file);
47+
return super.encrypt(session, file, directoryId, metadata);
48+
}
49+
50+
51+
@Override
52+
public Path getHome() {
53+
final Path home = super.getHome();
54+
final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.decryptedPayload);
55+
byte[] directoryId = masterKey.rootDirId();
56+
assert directoryId != null;
57+
home.attributes().setDirectoryId(directoryId);
58+
return home;
59+
}
60+
61+
6162
/**
6263
* Upload vault template into existing bucket (permanent credentials)
6364
*/
6465
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 review @dko check method signature?
65-
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials, final int version, final String metadata, final String rootDirHash) throws BackgroundException {
66+
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials, final int version, final String metadata, final String hashedRootDirId) throws BackgroundException {
6667
final Path home = new Path(session.getHost().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory));
6768
log.debug("Uploading vault template {} in {} ", home, session.getHost());
6869

@@ -75,11 +76,11 @@ public synchronized Path create(final Session<?> session, final String region, f
7576
// zip.file('vault.cryptomator', this.vaultConfigToken);
7677
// zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2));
7778
(new ContentWriter(session)).write(new Path(home, PreferencesFactory.get().getProperty("cryptomator.vault.config.filename"), EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.vault)), metadata.getBytes(StandardCharsets.US_ASCII));
78-
Directory<?> directory = (Directory) session._getFeature(Directory.class);
79+
Directory<?> directory = (Directory<?>) session._getFeature(Directory.class);
7980

8081
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 implement CryptoDirectory for uvf
8182
// Path secondLevel = this.directoryProvider.toEncrypted(session, this.home.attributes().getDirectoryId(), this.home);
82-
final Path secondLevel = new Path(String.format("/%s/d/%s/%s/", session.getHost().getDefaultPath(), rootDirHash.substring(0, 2), rootDirHash.substring(2)), EnumSet.of(AbstractPath.Type.directory));
83+
final Path secondLevel = new Path(String.format("/%s/d/%s/%s/", session.getHost().getDefaultPath(), hashedRootDirId.substring(0, 2), hashedRootDirId.substring(2)), EnumSet.of(AbstractPath.Type.directory));
8384
final Path firstLevel = secondLevel.getParent();
8485
final Path dataDir = firstLevel.getParent();
8586
log.debug("Create vault root directory at {}", secondLevel);
@@ -91,19 +92,6 @@ public synchronized Path create(final Session<?> session, final String region, f
9192
return home;
9293
}
9394

94-
@Override
95-
public HubCryptoVault load(final Session<?> session, final PasswordCallback prompt) throws BackgroundException {
96-
// no-interactive prompt in Cipherduck
97-
final String masterkey = prompt.prompt(session.getHost(), "", "", new LoginOptions()).getPassword();
98-
try {
99-
this.open(VAULT_CONFIG, new Masterkey(Base64.getDecoder().decode(masterkey)));
100-
}
101-
catch(IllegalArgumentException e) {
102-
throw new BackgroundException(e);
103-
}
104-
return this;
105-
}
106-
10795
public Path getMasterkey() {
10896
// No master key in vault
10997
return null;

hub/src/main/java/ch/iterate/hub/protocols/s3/S3AutoLoadVaultSession.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@
4545
import ch.iterate.hub.workflows.VaultServiceImpl;
4646
import ch.iterate.hub.workflows.exceptions.AccessException;
4747
import ch.iterate.hub.workflows.exceptions.SecurityFailure;
48-
import com.google.common.primitives.Bytes;
49-
import com.nimbusds.jose.util.Base64URL;
48+
import com.fasterxml.jackson.core.JsonProcessingException;
5049

5150
public class S3AutoLoadVaultSession extends S3AssumeRoleSession {
5251
private static final Logger log = LogManager.getLogger(S3AutoLoadVaultSession.class);
@@ -84,25 +83,21 @@ public void login(final LoginCallback prompt, final CancelCallback cancel) throw
8483
super.login(prompt, cancel);
8584
final Path home = new DelegatingHomeFeature(new DefaultPathHomeFeature(host)).find();
8685
log.debug("Attempting to locate vault in {}", home);
87-
final Vault vault = VaultFactory.get(home);
88-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! MUST NEVER BE RELEASED LIKE THIS
89-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 use rawFileKey,rawNameKey as vault key for now (going into cryptolib's Masterkey)
9086
// Retrieve from keychain
9187
final DeviceKeys deviceKeys = new DeviceKeysServiceImpl(keychain).getDeviceKeys(backend.getHost());
9288
final UvfMetadataPayload vaultMetadata = new VaultServiceImpl(backend).getVaultMetadataJWE(
9389
UUID.fromString(host.getUuid()), new UserKeysServiceImpl(backend).getUserKeys(backend.getHost(), backend.getMe(), deviceKeys));
94-
final byte[] rawFileKey = Base64URL.from(vaultMetadata.seeds().get(vaultMetadata.latestSeed())).decode();
95-
final byte[] rawNameKey = Base64URL.from(vaultMetadata.seeds().get(vaultMetadata.latestSeed())).decode();
96-
final byte[] vaultKey = Bytes.concat(rawFileKey, rawNameKey);
90+
final String decryptedPayload = vaultMetadata.toJSON();
91+
final Vault vault = VaultFactory.get(home, decryptedPayload, "", new byte[0]);
9792
registry.add(vault.load(this, new DisabledPasswordCallback() {
9893
@Override
9994
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
100-
return new VaultCredentials(Base64.getEncoder().encodeToString(vaultKey));
95+
return new VaultCredentials(decryptedPayload);
10196
}
10297
}));
10398
backend.close();
10499
}
105-
catch(ApiException | SecurityFailure | AccessException e) {
100+
catch(ApiException | SecurityFailure | AccessException | JsonProcessingException e) {
106101
throw new LoginFailureException(LocaleFactory.localizedString("Login failed", "Credentials"), e);
107102
}
108103
}

hub/src/main/java/ch/iterate/hub/workflows/CreateVaultService.java

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,51 +69,51 @@ public CreateVaultService(final HubSession hubSession) {
6969
public void createVault(final UserKeys userKeys, final StorageProfileDtoWrapper storageProfileWrapper, final CreateVaultModel vaultModel) throws ApiException, AccessException, SecurityFailure, BackgroundException {
7070
try {
7171
final UvfMetadataPayload.UniversalVaultFormatJWKS jwks = UvfMetadataPayload.createKeys();
72-
final VaultMetadataJWEBackendDto backendDto = new VaultMetadataJWEBackendDto()
73-
.provider(storageProfileWrapper.getId().toString())
74-
.defaultPath(storageProfileWrapper.getStsEndpoint() != null ? storageProfileWrapper.getBucketPrefix() + vaultModel.vaultId() : vaultModel.bucketName())
75-
.nickname(vaultModel.vaultName())
76-
.username(vaultModel.accessKeyId())
77-
.password(vaultModel.secretKey());
78-
final VaultMetadataJWEAutomaticAccessGrantDto accessGrantDto = new VaultMetadataJWEAutomaticAccessGrantDto()
79-
.enabled(vaultModel.automaticAccessGrant())
80-
.maxWotDepth(vaultModel.maxWotLevel());
81-
final UvfMetadataPayload metadataJWE = UvfMetadataPayload.create()
82-
.withStorage(backendDto)
83-
.withAutomaticAccessGrant(accessGrantDto);
84-
log.debug("Created metadata JWE {}", metadataJWE);
85-
final String uvfMetadataFile = metadataJWE.encrypt(
72+
final UvfMetadataPayload metadataPayload = UvfMetadataPayload.create()
73+
.withStorage(new VaultMetadataJWEBackendDto()
74+
.provider(storageProfileWrapper.getId().toString())
75+
.defaultPath(storageProfileWrapper.getStsEndpoint() != null ? storageProfileWrapper.getBucketPrefix() + vaultModel.vaultId() : vaultModel.bucketName())
76+
.nickname(vaultModel.vaultName())
77+
.username(vaultModel.accessKeyId())
78+
.password(vaultModel.secretKey()))
79+
.withAutomaticAccessGrant(new VaultMetadataJWEAutomaticAccessGrantDto()
80+
.enabled(vaultModel.automaticAccessGrant())
81+
.maxWotDepth(vaultModel.maxWotLevel())
82+
);
83+
log.debug("Created metadata JWE {}", metadataPayload);
84+
final String uvfMetadataFile = metadataPayload.encrypt(
8685
String.format("%s/api", new HostUrlProvider(false, true).get(hubSession.getHost())),
8786
vaultModel.vaultId(),
8887
jwks.toJWKSet()
8988
);
9089
final VaultDto vaultDto = new VaultDto()
9190
.id(vaultModel.vaultId())
92-
.name(metadataJWE.storage().getNickname())
91+
.name(metadataPayload.storage().getNickname())
9392
.description(vaultModel.vaultDescription())
9493
.archived(false)
9594
.creationTime(DateTime.now())
9695
.uvfMetadataFile(uvfMetadataFile)
9796
.uvfKeySet(jwks.serializePublicRecoverykey());
97+
98+
final String hashedRootDirId = metadataPayload.computeRootDirIdHash();
9899
final CreateS3STSBucketDto storageDto = new CreateS3STSBucketDto()
99100
.vaultId(vaultModel.vaultId().toString())
100101
.storageConfigId(storageProfileWrapper.getId())
101-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 do we need to store here as well? Only in VaultDto?
102102
.vaultUvf(uvfMetadataFile)
103-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 do we need to store here?
104-
.rootDirHash(metadataJWE.computeRootDirIdHash(metadataJWE.computeRootDirId()))
105-
.region(metadataJWE.storage().getRegion());
103+
.rootDirHash(hashedRootDirId)
104+
.region(metadataPayload.storage().getRegion());
106105
log.debug("Created storage dto {}", storageDto);
107-
final Host bookmark = HubStorageVaultSyncSchedulerService.toBookmark(hubSession.getHost(), vaultDto.getId(), metadataJWE.storage());
106+
final Host bookmark = HubStorageVaultSyncSchedulerService.toBookmark(hubSession.getHost(), vaultDto.getId(), metadataPayload.storage());
108107
if(storageProfileWrapper.getStsEndpoint() == null) {
109-
// permanent: template upload into existing bucket
108+
// permanent: template upload into existing bucket from client (not backend)
110109
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 review @dko
111110
final S3Session session = new S3Session(bookmark);
112111
session.open(new DisabledProxyFinder(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback());
113112
session.login(new DisabledLoginCallback(), new DisabledCancelCallback());
113+
114114
// upload vault template
115-
new HubCryptoVault(new Path(metadataJWE.storage().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)))
116-
.create(session, metadataJWE.storage().getRegion(), null, 42, storageDto.getVaultUvf(), storageDto.getRootDirHash());
115+
new HubCryptoVault(new Path(metadataPayload.storage().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)))
116+
.create(session, metadataPayload.storage().getRegion(), null, 42, storageDto.getVaultUvf(), hashedRootDirId);
117117
session.close();
118118
}
119119
else {
@@ -149,6 +149,7 @@ public void createVault(final UserKeys userKeys, final StorageProfileDtoWrapper
149149
}
150150
}
151151

152+
152153
private static TemporaryAccessTokens getSTSTokensFromAccessTokenWithCreateBucketInlinePoliy(final String token, final String roleArn, final String roleSessionName, final String stsEndpoint, final String bucketName, final Boolean bucketAcceleration) throws IOException {
153154
log.debug("Get STS tokens from {} to pass to backend {} with role {} and session name {}", token, stsEndpoint, roleArn, roleSessionName);
154155

0 commit comments

Comments
 (0)