From 58f4260b6758a7fe76dd3f57b44d6d57c58dffe4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 24 Apr 2023 17:03:24 +0200 Subject: [PATCH 01/77] prepare table for new intermediary "user keys" --- .../org/cryptomator/hub/flyway/V8__User_Keys.sql | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql new file mode 100644 index 00000000..be200aea --- /dev/null +++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql @@ -0,0 +1,14 @@ +-- users will generate a new key pair during first login in the browser: +ALTER TABLE "user_details" ADD "publickey" VARCHAR(255); -- pem-encoded SPKI field (RFC 5280, 4.1.2.7) +ALTER TABLE "user_details" ADD "privatekey" VARCHAR(500); -- pem-encoded pkcs8 (RFC 5208), protected by kek generated via PBKDF2 +ALTER TABLE "user_details" ADD "salt" VARCHAR(255); +ALTER TABLE "user_details" ADD "iterations" INTEGER; + +-- when granting access, the vault key ("masterkey") is encrypted for this user using an ECIES (requires the user to have a key pair) +ALTER TABLE "vault_access" ADD "vault_key_jwe" VARCHAR(2000) UNIQUE; + +-- when the user adds a device, the user's private key is encrypted for this device +ALTER TABLE "device" ADD "user_key_jwe" VARCHAR(2000) UNIQUE; + +-- do be dropped in a later version: +COMMENT ON TABLE "access_token" IS 'DEPRECATED: This table is kept for compatibility with Cryptomator 1.7.x. No new tokens are issued.'; \ No newline at end of file From ec32f4b023b44dce3dcf114486b5a5e87cffbad3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 28 Apr 2023 12:51:41 +0200 Subject: [PATCH 02/77] generate user key + device key on first login --- .../hub/api/AuthorityResource.java | 6 +- .../cryptomator/hub/api/DeviceResource.java | 11 +- .../java/org/cryptomator/hub/api/UserDto.java | 23 +- .../cryptomator/hub/api/UsersResource.java | 38 ++- .../cryptomator/hub/api/VaultResource.java | 50 ++-- .../cryptomator/hub/entities/AccessToken.java | 41 ++- .../org/cryptomator/hub/entities/Device.java | 28 +- .../hub/entities/EffectiveVaultAccess.java | 4 +- .../org/cryptomator/hub/entities/User.java | 43 ++- .../src/main/resources/application.properties | 1 + .../org/cryptomator/hub/flyway/ERM.png | Bin 159896 -> 228266 bytes .../cryptomator/hub/flyway/V8__User_Keys.sql | 40 ++- .../hub/api/DeviceResourceTest.java | 20 +- .../hub/api/UsersResourceTest.java | 4 +- .../hub/api/VaultResourceTest.java | 192 ++++++------- .../hub/entities/EntityIntegrationTest.java | 37 +-- .../VaultAdminOnlyFilterProviderTestIT.java | 2 +- .../hub/flyway/V9999__Test_Data.sql | 18 +- frontend/src/common/backend.ts | 32 ++- frontend/src/common/crypto.ts | 262 +++++++++++++++--- frontend/src/common/jwe.ts | 27 +- frontend/src/components/AuthenticatedMain.vue | 2 +- frontend/src/components/DeviceList.vue | 2 +- .../src/components/GrantPermissionDialog.vue | 18 +- frontend/src/components/UnlockSuccess.vue | 8 +- frontend/src/components/UserSettings.vue | 51 +++- frontend/src/components/VaultDetails.vue | 18 +- frontend/src/router/index.ts | 13 +- frontend/test/common/crypto.spec.ts | 6 +- frontend/test/common/jwe.spec.ts | 7 +- 30 files changed, 644 insertions(+), 360 deletions(-) diff --git a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java index 5a4e9338..fda2d078 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/AuthorityResource.java @@ -28,9 +28,9 @@ public class AuthorityResource { public List search(@QueryParam("query") @NotBlank String query) { return Authority.byName(query).map(authority -> { if (authority instanceof User user) { - return new UserDto(authority.id, authority.name, user.pictureUrl, user.email, null); - } else if (authority instanceof Group) { - return new GroupDto(authority.id, authority.name); + return UserDto.justPublicInfo(user); + } else if (authority instanceof Group group) { + return GroupDto.fromEntity(group); } else { throw new IllegalStateException("authority is not of type user or group"); } diff --git a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java index 4c1613eb..54a50fed 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.api; import com.fasterxml.jackson.annotation.JsonProperty; - import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.persistence.PersistenceException; @@ -21,8 +20,10 @@ import org.cryptomator.hub.entities.Device; import org.cryptomator.hub.entities.User; import org.cryptomator.hub.validation.NoHtmlOrScriptChars; +import org.cryptomator.hub.validation.OnlyBase64Chars; import org.cryptomator.hub.validation.OnlyBase64UrlChars; import org.cryptomator.hub.validation.ValidId; +import org.cryptomator.hub.validation.ValidJWE; import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; @@ -31,7 +32,6 @@ import java.net.URI; import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.Set; @Path("/devices") public class DeviceResource { @@ -91,9 +91,9 @@ public Response remove(@PathParam("deviceId") @ValidId String deviceId) { public record DeviceDto(@JsonProperty("id") @ValidId String id, @JsonProperty("name") @NoHtmlOrScriptChars @NotBlank String name, - @JsonProperty("publicKey") @OnlyBase64UrlChars String publicKey, + @JsonProperty("publicKey") @OnlyBase64Chars String publicKey, + @JsonProperty("userKeyJwe") @ValidJWE String userKeyJwe, @JsonProperty("owner") @ValidId String ownerId, - @JsonProperty("accessTo") @Valid Set accessTo, @JsonProperty("creationTime") Instant creationTime) { public Device toDevice(User user, String id, Instant creationTime) { @@ -102,12 +102,13 @@ public Device toDevice(User user, String id, Instant creationTime) { device.owner = user; device.name = name; device.publickey = publicKey; + device.userKeyJwe = userKeyJwe; device.creationTime = creationTime; return device; } public static DeviceDto fromEntity(Device entity) { - return new DeviceDto(entity.id, entity.name, entity.publickey, entity.owner.id, Set.of(), entity.creationTime.truncatedTo(ChronoUnit.MILLIS)); + return new DeviceDto(entity.id, entity.name, entity.publickey, entity.userKeyJwe, entity.owner.id, entity.creationTime.truncatedTo(ChronoUnit.MILLIS)); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/UserDto.java b/backend/src/main/java/org/cryptomator/hub/api/UserDto.java index 70c2c9bc..56ecd3a2 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UserDto.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UserDto.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.cryptomator.hub.entities.User; +import org.cryptomator.hub.validation.OnlyBase64Chars; import java.util.Set; @@ -11,14 +12,30 @@ public final class UserDto extends AuthorityDto { public final String email; @JsonProperty("devices") public final Set devices; + @JsonProperty("accessibleVaults") + public final Set accessibleVaults; + @JsonProperty("publicKey") + public final String publicKey; + @JsonProperty("privateKey") + public final String privateKey; + @JsonProperty("salt") + public final String salt; + @JsonProperty("iterations") + public final int iterations; - UserDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("email") String email, @JsonProperty("devices") Set devices) { + UserDto(@JsonProperty("id") String id, @JsonProperty("name") String name, @JsonProperty("pictureUrl") String pictureUrl, @JsonProperty("email") String email, @JsonProperty("devices") Set devices, @JsonProperty("accessibleVaults") Set accessibleVaults, + @JsonProperty("publicKey") @OnlyBase64Chars String publicKey, @JsonProperty("privateKey") @OnlyBase64Chars String privateKey, @JsonProperty("salt") @OnlyBase64Chars String salt, @JsonProperty("iterations") int iterations) { super(id, Type.USER, name, pictureUrl); this.email = email; this.devices = devices; + this.accessibleVaults = accessibleVaults; + this.publicKey = publicKey; + this.privateKey = privateKey; + this.salt = salt; + this.iterations = iterations; } - public static UserDto fromEntity(User user) { - return new UserDto(user.id, user.name, user.pictureUrl, user.email, Set.of()); + public static UserDto justPublicInfo(User user) { + return new UserDto(user.id, user.name, user.pictureUrl, user.email, Set.of(), Set.of(), user.publicKey, null, null, 0); } } diff --git a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java index 04bf5957..d4913883 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/UsersResource.java @@ -1,9 +1,13 @@ package org.cryptomator.hub.api; +import jakarta.annotation.Nullable; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; @@ -21,6 +25,7 @@ import java.net.URI; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -52,6 +57,24 @@ public Response syncMe() { return Response.created(URI.create(".")).build(); } + @PUT + @Path("/me/key-pair") + @RolesAllowed("user") + @Consumes(MediaType.APPLICATION_JSON) + @Transactional + @Operation(summary = "update the logged-in user, storing provided key pair and key derivation parameters") + @APIResponse(responseCode = "201", description = "user updated") + public Response syncMe(@Valid UserDto dto) { + var userId = jwt.getSubject(); + var user = User.findByIdOptional(userId).orElseThrow(NotFoundException::new); + user.publicKey = dto.publicKey; + user.privateKey = dto.privateKey; + user.salt = dto.salt; + user.iterations = dto.iterations; + user.persist(); + return Response.created(URI.create(".")).build(); + } + @GET @Path("/me") @RolesAllowed("user") @@ -61,14 +84,11 @@ public Response syncMe() { @Operation(summary = "get the logged-in user") public UserDto getMe(@QueryParam("withDevices") boolean withDevices, @QueryParam("withAccessibleVaults") boolean withAccessibleVaults) { User user = User.findById(jwt.getSubject()); - Function mapAccessibleVaults = - a -> new VaultResource.VaultDto(a.vault.id, a.vault.name, a.vault.description, a.vault.creationTime.truncatedTo(ChronoUnit.MILLIS), null, 0, null, null, null); - Function mapDevices = withAccessibleVaults // - ? d -> new DeviceResource.DeviceDto(d.id, d.name, d.publickey, d.owner.id, d.accessTokens.stream().map(mapAccessibleVaults).collect(Collectors.toSet()), d.creationTime.truncatedTo(ChronoUnit.MILLIS)) // - : d -> new DeviceResource.DeviceDto(d.id, d.name, d.publickey, d.owner.id, Set.of(), d.creationTime.truncatedTo(ChronoUnit.MILLIS)); - return withDevices // - ? new UserDto(user.id, user.name, user.pictureUrl, user.email, user.devices.stream().map(mapDevices).collect(Collectors.toSet())) - : new UserDto(user.id, user.name, user.pictureUrl, user.email, Set.of()); + Function mapAccessibleVaults = a -> new VaultResource.VaultDto(a.vault.id, a.vault.name, a.vault.description, a.vault.creationTime.truncatedTo(ChronoUnit.MILLIS), null, 0, null, null, null); + Function mapDevices = d -> new DeviceResource.DeviceDto(d.id, d.name, d.publickey, d.userKeyJwe, d.owner.id, d.creationTime.truncatedTo(ChronoUnit.MILLIS)); + var devices = withDevices ? user.devices.stream().map(mapDevices).collect(Collectors.toSet()) : Set.of(); + var vaults = withAccessibleVaults ? user.accessTokens.stream().map(mapAccessibleVaults).collect(Collectors.toSet()) : Set.of(); + return new UserDto(user.id, user.name, user.pictureUrl, user.email, devices, vaults, user.publicKey, user.privateKey, user.salt, user.iterations); } @GET @@ -77,7 +97,7 @@ public UserDto getMe(@QueryParam("withDevices") boolean withDevices, @QueryParam @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "list all users") public List getAll() { - return User.findAll().stream().map(UserDto::fromEntity).toList(); + return User.findAll().stream().map(UserDto::justPublicInfo).toList(); } } \ No newline at end of file diff --git a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java index f9c6cb00..e68f73da 100644 --- a/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java +++ b/backend/src/main/java/org/cryptomator/hub/api/VaultResource.java @@ -97,7 +97,7 @@ public List getMembers(@PathParam("vaultId") UUID vaultId) { return vault.directMembers.stream().map(authority -> { if (authority instanceof User u) { - return UserDto.fromEntity(u); + return UserDto.justPublicInfo(u); } else if (authority instanceof Group g) { return GroupDto.fromEntity(g); } else { @@ -207,7 +207,7 @@ private Response removeAutority(UUID vaultId, String authorityId) { } @GET - @Path("/{vaultId}/devices-requiring-access-grant") + @Path("/{vaultId}/users-requiring-access-grant") @RolesAllowed("user") @VaultAdminOnlyFilter @Transactional @@ -216,67 +216,65 @@ private Response removeAutority(UUID vaultId, String authorityId) { @APIResponse(responseCode = "401", description = "VaultAdminAuthorizationJWT not provided") @APIResponse(responseCode = "403", description = "VaultAdminAuthorizationJWT expired or not yet valid") @APIResponse(responseCode = "404", description = "vault not found") - public List getDevicesRequiringAccessGrant(@PathParam("vaultId") UUID vaultId) { - return Device.findRequiringAccessGrant(vaultId).map(DeviceResource.DeviceDto::fromEntity).toList(); + public List getUsersRequiringAccessGrant(@PathParam("vaultId") UUID vaultId) { + return User.findRequiringAccessGrant(vaultId).map(UserDto::justPublicInfo).toList(); } @GET - @Path("/{vaultId}/keys/{deviceId}") + @Path("/{vaultId}/access-tokens/logged-in-user") // TODO: other path? @RolesAllowed("user") @Transactional @Produces(MediaType.TEXT_PLAIN) - @Operation(summary = "get the device-specific masterkey") + @Operation(summary = "get the user-specific vault key") @APIResponse(responseCode = "200") @APIResponse(responseCode = "402", description = "number of effective vault users exceeds available license seats") - @APIResponse(responseCode = "403", description = "device not authorized to access this vault") - @APIResponse(responseCode = "404", description = "unknown device") + @APIResponse(responseCode = "403", description = "user not authorized to access this vault") + @APIResponse(responseCode = "404", description = "unknown vault") @ActiveLicense - public String unlock(@PathParam("vaultId") UUID vaultId, @PathParam("deviceId") @ValidId String deviceId) { + public String unlock(@PathParam("vaultId") UUID vaultId) { var usedSeats = EffectiveVaultAccess.countEffectiveVaultUsers(); if (usedSeats > license.getAvailableSeats()) { throw new PaymentRequiredException("Number of effective vault users exceeds available license seats"); } - var access = AccessToken.unlock(vaultId, deviceId, jwt.getSubject()); + var access = AccessToken.unlock(vaultId, jwt.getSubject()); if (access != null) { return access.jwe; - } else if (Device.findById(deviceId) == null) { - throw new NotFoundException("No such device."); + } else if (Vault.findById(vaultId) == null) { + throw new NotFoundException("No such vault."); } else { - throw new ForbiddenException("Access to this device not granted."); + throw new ForbiddenException("Access to this vault not granted."); } } @PUT - @Path("/{vaultId}/keys/{deviceId}") + @Path("/{vaultId}/access-tokens/{userId}") @RolesAllowed("user") @VaultAdminOnlyFilter @Transactional @Consumes(MediaType.TEXT_PLAIN) - @Operation(summary = "adds a device-specific masterkey") - @APIResponse(responseCode = "201", description = "device-specific key stored") + @Operation(summary = "adds a user-specific vault key") + @APIResponse(responseCode = "201", description = "user-specific key stored") @APIResponse(responseCode = "401", description = "VaultAdminAuthorizationJWT not provided") @APIResponse(responseCode = "403", description = "VaultAdminAuthorizationJWT expired or not yet valid") - @APIResponse(responseCode = "404", description = "vault or device not found") + @APIResponse(responseCode = "404", description = "vault or userId not found") @APIResponse(responseCode = "409", description = "Access to vault for device already granted") - public Response grantAccess(@PathParam("vaultId") UUID vaultId, @PathParam("deviceId") @ValidId String deviceId, @ValidJWE String jwe) { - var vault = Vault.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); - var device = Device.findByIdOptional(deviceId).orElseThrow(NotFoundException::new); + public Response grantAccess(@PathParam("vaultId") UUID vaultId, @PathParam("userId") @ValidId String userId, @ValidJWE String jwe) { + var vault = Vault.findByIdOptional(vaultId).orElseThrow(NotFoundException::new); // should always be found, since @VaultAdminOnlyFilter already checked the jwt matching this vault + var user = User.findByIdOptional(userId).orElseThrow(NotFoundException::new); var access = new AccessToken(); access.vault = vault; - access.device = device; + access.user = user; access.jwe = jwe; try { access.persistAndFlush(); return Response.created(URI.create(".")).build(); + } catch (ConstraintViolationException e) { + throw new ClientErrorException(Response.Status.CONFLICT, e); } catch (PersistenceException e) { - if (e instanceof ConstraintViolationException) { - throw new ClientErrorException(Response.Status.CONFLICT, e); - } else { - throw new InternalServerErrorException(e); - } + throw new InternalServerErrorException(e); } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java index 09857281..851800a4 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/AccessToken.java @@ -22,13 +22,10 @@ @Table(name = "access_token") @NamedQuery(name = "AccessToken.get", query = """ SELECT a - FROM Vault v - INNER JOIN v.effectiveMembers u - INNER JOIN u.devices d - INNER JOIN d.accessTokens a ON a.id.deviceId = d.id AND a.id.vaultId = v.id - WHERE v.id = :vaultId - AND u.id = :userId - AND d.id = :deviceId + FROM User u + INNER JOIN EffectiveVaultAccess perm ON u.id = perm.id.authorityId + INNER JOIN u.accessTokens a ON a.id.vaultId = :vaultId AND a.id.userId = u.id + WHERE perm.id.vaultId = :vaultId AND u.id = :userId """) public class AccessToken extends PanacheEntityBase { @@ -36,21 +33,21 @@ public class AccessToken extends PanacheEntityBase { public AccessId id = new AccessId(); @ManyToOne(optional = false, cascade = {CascadeType.REMOVE}) - @MapsId("deviceId") - @JoinColumn(name = "device_id") - public Device device; + @MapsId("userId") + @JoinColumn(name = "user_id") + public User user; @ManyToOne(optional = false, cascade = {CascadeType.REMOVE}) @MapsId("vaultId") @JoinColumn(name = "vault_id") public Vault vault; - @Column(name = "jwe", nullable = false) + @Column(name = "vault_key_jwe", nullable = false) public String jwe; - public static AccessToken unlock(UUID vaultId, String deviceId, String userId) { + public static AccessToken unlock(UUID vaultId, String userId) { try { - return find("#AccessToken.get", Parameters.with("deviceId", deviceId).and("vaultId", vaultId).and("userId", userId)).firstResult(); + return find("#AccessToken.get", Parameters.with("vaultId", vaultId).and("userId", userId)).firstResult(); } catch (NoResultException e) { return null; } @@ -62,21 +59,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; AccessToken other = (AccessToken) o; return Objects.equals(id, other.id) - && Objects.equals(device, other.device) + && Objects.equals(user, other.user) && Objects.equals(vault, other.vault) && Objects.equals(jwe, other.jwe); } @Override public int hashCode() { - return Objects.hash(id, device, vault, jwe); + return Objects.hash(id, user, vault, jwe); } @Override public String toString() { return "Access{" + "id=" + id + - ", device=" + device.id + + ", user=" + user.id + ", vault=" + vault.id + ", jwe='" + jwe + '\'' + '}'; @@ -85,11 +82,11 @@ public String toString() { @Embeddable public static class AccessId implements Serializable { - public String deviceId; + public String userId; public UUID vaultId; - public AccessId(String deviceId, UUID vaultId) { - this.deviceId = deviceId; + public AccessId(String userId, UUID vaultId) { + this.userId = userId; this.vaultId = vaultId; } @@ -101,19 +98,19 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AccessId other = (AccessId) o; - return Objects.equals(deviceId, other.deviceId) // + return Objects.equals(userId, other.userId) // && Objects.equals(vaultId, other.vaultId); } @Override public int hashCode() { - return Objects.hash(deviceId, vaultId); + return Objects.hash(userId, vaultId); } @Override public String toString() { return "AccessId{" + - "deviceId='" + deviceId + '\'' + + "userId='" + userId + '\'' + ", vaultId='" + vaultId + '\'' + '}'; } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/Device.java b/backend/src/main/java/org/cryptomator/hub/entities/Device.java index 309b2851..300a2b3d 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/Device.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/Device.java @@ -21,16 +21,6 @@ @Entity @Table(name = "device") -@NamedQuery(name = "Device.requiringAccessGrant", - query = """ - SELECT d - FROM Vault v - INNER JOIN v.effectiveMembers m - INNER JOIN m.devices d - LEFT JOIN d.accessTokens a ON a.id.vaultId = :vaultId AND a.id.deviceId = d.id - WHERE v.id = :vaultId AND a.vault IS NULL - """ -) public class Device extends PanacheEntityBase { @Id @@ -41,15 +31,15 @@ public class Device extends PanacheEntityBase { @JoinColumn(name = "owner_id", updatable = false, nullable = false) public Authority owner; - @OneToMany(mappedBy = "device", fetch = FetchType.LAZY) - public Set accessTokens = new HashSet<>(); - @Column(name = "name", nullable = false) public String name; @Column(name = "publickey", nullable = false) public String publickey; + @Column(name = "user_key_jwe", nullable = false) + public String userKeyJwe; + @Column(name = "creation_time", nullable = false) public Instant creationTime; @@ -60,6 +50,8 @@ public String toString() { ", owner=" + owner.id + ", name='" + name + '\'' + ", publickey='" + publickey + '\'' + + ", userKeyJwe='" + userKeyJwe + '\'' + + ", creationTime='" + creationTime + '\'' + '}'; } @@ -71,16 +63,14 @@ public boolean equals(Object o) { return Objects.equals(this.id, other.id) && Objects.equals(this.owner, other.owner) && Objects.equals(this.name, other.name) - && Objects.equals(this.publickey, other.publickey); + && Objects.equals(this.publickey, other.publickey) + && Objects.equals(this.userKeyJwe, other.userKeyJwe) + && Objects.equals(this.creationTime, other.creationTime); } @Override public int hashCode() { - return Objects.hash(id, owner, name, publickey); - } - - public static Stream findRequiringAccessGrant(UUID vaultId) { - return find("#Device.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); + return Objects.hash(id, owner, name, publickey, userKeyJwe, creationTime); } } diff --git a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java index e2f12f29..b7deeab8 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/EffectiveVaultAccess.java @@ -23,12 +23,12 @@ SELECT count(eva) WHERE eva.id.authorityId = :userId """) @NamedQuery(name = "EffectiveVaultAccess.countEVUs", query = """ - SELECT count( DISTINCT u) + SELECT count(DISTINCT u) FROM User u INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId """) @NamedQuery(name = "EffectiveVaultAccess.countEVUsInGroup", query = """ - SELECT count( DISTINCT u) + SELECT count(DISTINCT u) FROM User u INNER JOIN EffectiveVaultAccess eva ON u.id = eva.id.authorityId INNER JOIN EffectiveGroupMembership egm ON u.id = egm.id.memberId diff --git a/backend/src/main/java/org/cryptomator/hub/entities/User.java b/backend/src/main/java/org/cryptomator/hub/entities/User.java index 73515dc8..31db271f 100644 --- a/backend/src/main/java/org/cryptomator/hub/entities/User.java +++ b/backend/src/main/java/org/cryptomator/hub/entities/User.java @@ -1,15 +1,32 @@ package org.cryptomator.hub.entities; +import io.quarkus.panache.common.Parameters; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.NamedQuery; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; @Entity @Table(name = "user_details") @DiscriminatorValue("USER") +@NamedQuery(name = "User.requiringAccessGrant", + query = """ + SELECT u + FROM User u + INNER JOIN EffectiveVaultAccess perm ON u.id = perm.id.authorityId + LEFT JOIN u.accessTokens token ON token.id.vaultId = :vaultId AND token.id.userId = u.id + WHERE perm.id.vaultId = :vaultId AND token.vault IS NULL + """ +) public class User extends Authority { @Column(name = "picture_url") @@ -18,6 +35,21 @@ public class User extends Authority { @Column(name = "email") public String email; + @Column(name = "publickey") + public String publicKey; + + @Column(name = "privatekey") + public String privateKey; // IV + GCM-encrypted PKCS#8 + + @Column(name = "salt") + public String salt; + + @Column(name = "iterations") + public int iterations; + + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + public Set accessTokens = new HashSet<>(); + @Override public boolean equals(Object o) { if (this == o) return true; @@ -25,12 +57,19 @@ public boolean equals(Object o) { User that = (User) o; return super.equals(that) // && Objects.equals(pictureUrl, that.pictureUrl) // - && Objects.equals(email, that.email); + && Objects.equals(email, that.email) // + && Objects.equals(publicKey, that.publicKey) // + && Objects.equals(privateKey, that.privateKey) // + && Objects.equals(salt, that.salt); } @Override public int hashCode() { - return Objects.hash(id, pictureUrl, email); + return Objects.hash(id, pictureUrl, email, publicKey, privateKey, salt, iterations); + } + + public static Stream findRequiringAccessGrant(UUID vaultId) { + return find("#User.requiringAccessGrant", Parameters.with("vaultId", vaultId)).stream(); } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 89eb6425..1babc23b 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -60,6 +60,7 @@ quarkus.datasource.jdbc.max-size=16 quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL10Dialect quarkus.hibernate-orm.database.globally-quoted-identifiers=true quarkus.flyway.migrate-at-start=true +quarkus.flyway.ignore-missing-migrations=true quarkus.flyway.locations=classpath:org/cryptomator/hub/flyway # log Hibernate SQL statements including values, for dev-purpose only diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/ERM.png b/backend/src/main/resources/org/cryptomator/hub/flyway/ERM.png index 1ef70a42bed871c2c51cb80e0383cbab3b1accae..7937a7fd054a78f84812c309c65e847a46181d2d 100644 GIT binary patch literal 228266 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 ~7dss{(C}H8sypk6n zZaQu{o2l(mOdReh>!E<8kgNtB7LS?$5#IPILgKkB8*BDnUG_@oUHl}~PY;)3YMw?1 z&k45Q9<`wru$v{~aYqA7W#1yXtC!StfM|44!6-|kyB6(W-iynEKUypu-MBS(-i6~L zyiWVQ+*GPVSZB7}$J<+Nl`_71ay8m>6SIc;<~x%WuQf=B0QtTocI&IpG~pG3?BkhY z&onw3?vwIa?q`ZM&)#5iK|dm)+}I)MS-uEQjA+HgB6Blq1Mij!nRHf;oyH5Jwl?W- zg+{kE#S-pX2&kQXU9XILb1$KpAQtIP9$5v{WD5K zKhde}RK%rr^SDUq2z%mp+Z+*z2cehEwX= z$Dhp&1;dIlQo)+B;mHLSK5w>4VUm&?EY@hvt&9s0InsO~`2%P8#FfQw?jNah3s=p) zw8*Al7QRT!EYhPY)U^d-!sU6*3Nj8muF~fYI2yRT+JkeQvFmb4$V_yX5 zi$7*TX(H+~Sd44-KD_&RI^q2))y`|;FP*KcBite*o{4-3KFFNz-e`fJ6SwBf9#i|v zNwI-%mTrHOtv!03X+g`^=G$iezNp|VujcAPg8gCZf^fS|pBTl(bJ;i^v{{MYF|K^A zeb%;MOvoY~V(^LNTrQsqj-X_T*(=`)GljA+i;UYjUy+NgQ{@G-T-@n0?~ z`?AVJ1E(_Y+Jrozib;*WrIXg_W*O8JmhTQ3rDJRpzfr&{UeLltYnny6u+#NpvS}14iiFlkJcdaSdto*~HGkYW4wC+_@jrUXkw)y~ejEzq^ic3AOjp$T) z*JO5DfU?w@K#uJlQEms%&+?TFww}X35xLS)+?$V+$BfAUIaw6TyhQR*GOL%<5@aSb zUgvIRp6IAL!A2}GI(kmrQ?vEHtE;gJypAc>yL@hX@Em%;#HF4uI%@0vBcn`Nu(5f` zCYEb1%w{na{}3*vZJ2mS%_A)!JLRcP#r3CIMv*L24}yeBqw#z(j(eK?b7j~KQ$pR} zwLTI(>BL?4E$@8GDP97LuCKMngx4vhnAFhSNAL-L+8yrqY*Gl>!_f9XZj764OlI$TWyI~nO9`a->;SA-SrJs#D#1Vu8^rXnk6{N>D`el zb5KF!z42WV!N_uTUCyHu0>m(Yl}x>2ViTWxcl1_9n1}Xuvwx?7Le;_qti4`UISh|AX-8mzquC5df9gI%%js- zE)i`%?b{Lx9P^m1_qD3i9|n|KoDyDvs88^__SAPKf@xaF3+E7m}*{)yCU^`{m#j^`Q^LLtqj?4a=}gw zfu`3XCu}PL*L-tQ)KfRdPW|*FU#{HzhBH(4WzoJhK2#X9>+F>Tt&`Fqr?vzk9CP=ot+wF5h?8fj z&{M0NU*5U$gD|G@WzH6)5_n5@RK@+a6g*qILNG{Qtzw;}2J?z#WWbygiaETK9{`5= z^W)%oPNo~m=Sv8KZ|rJdiAxjARl5CTs~mPy%ye06<6CkCjtf98@ABOpEXon9RA*l9-J(7l{Lr=guF)#u<*i38TX({> z03exnBT%utm}i2S*k|xOVUBM%R^OSToL|5A8*ov~K^6tg@w&lSrSsDhzE(ZF$kT8o zd_HG8aGFYvnfg`e91%@9qQB^V1n_@cyX4+Fo|~$%(1;k9@wKMvbbiEh;<^0JCuV^n zgg1-FxiM`#l&q&F7UO?PV0Gv;_fL;9;X`$lFt%-G4y_~$D6rD$NRVaTMaP{=wwY+N@%yq9fG{Q;$_Y3&{zUmD}=o}9Oi*5f9W k7<;DMX78R(sGMhTKDp*uOo)Fe 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%0A0~4t&2@{~2l!zCBw@?|XW7UVK}sAG$g? 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

s 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 Jm+hro{Re8AkTL)O diff --git a/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql b/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql index be200aea..634db4d2 100644 --- a/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql +++ b/backend/src/main/resources/org/cryptomator/hub/flyway/V8__User_Keys.sql @@ -2,13 +2,39 @@ ALTER TABLE "user_details" ADD "publickey" VARCHAR(255); -- pem-encoded SPKI field (RFC 5280, 4.1.2.7) ALTER TABLE "user_details" ADD "privatekey" VARCHAR(500); -- pem-encoded pkcs8 (RFC 5208), protected by kek generated via PBKDF2 ALTER TABLE "user_details" ADD "salt" VARCHAR(255); -ALTER TABLE "user_details" ADD "iterations" INTEGER; +ALTER TABLE "user_details" ADD "iterations" INTEGER DEFAULT 0; --- when granting access, the vault key ("masterkey") is encrypted for this user using an ECIES (requires the user to have a key pair) -ALTER TABLE "vault_access" ADD "vault_key_jwe" VARCHAR(2000) UNIQUE; +-- keep existing device-based access tokens for continuous unlock from old clients. +ALTER TABLE "device" RENAME TO "device_legacy"; +ALTER TABLE "device_legacy" RENAME CONSTRAINT "DEVICE_PK" TO "DEVICE_LEGACY_PK"; +ALTER TABLE "device_legacy" RENAME CONSTRAINT "DEVICE_FK_USER" TO "DEVICE_LEGACY_FK_USER"; +ALTER TABLE "device_legacy" RENAME CONSTRAINT "DEVICE_UNIQUE_NAME_PER_OWNER" TO "DEVICE_LEGACY_UNIQUE_NAME_PER_OWNER"; +ALTER TABLE "access_token" RENAME TO "access_token_legacy"; +ALTER TABLE "access_token_legacy" RENAME CONSTRAINT "ACCESS_PK" TO "ACCESS_LEGACY_PK"; +ALTER TABLE "access_token_legacy" RENAME CONSTRAINT "ACCESS_FK_DEVICE" TO "ACCESS_LEGACY_FK_DEVICE"; +ALTER TABLE "access_token_legacy" RENAME CONSTRAINT "ACCESS_FK_VAULT" TO "ACCESS_LEGACY_FK_VAULT"; --- when the user adds a device, the user's private key is encrypted for this device -ALTER TABLE "device" ADD "user_key_jwe" VARCHAR(2000) UNIQUE; +-- start from scratch with new device table (strictly require a user_key to be encrypted for this device): +CREATE TABLE "device" +( + "id" VARCHAR(64) COLLATE "C" NOT NULL, + "owner_id" VARCHAR(255) COLLATE "C" NOT NULL, + "name" VARCHAR(255) NOT NULL, + "publickey" VARCHAR(255) NOT NULL UNIQUE, + "user_key_jwe" VARCHAR(2000) NOT NULL UNIQUE, + "creation_time" TIMESTAMP NOT NULL, + CONSTRAINT "DEVICE_PK" PRIMARY KEY ("id"), + CONSTRAINT "DEVICE_FK_USER" FOREIGN KEY ("owner_id") REFERENCES "user_details" ("id") ON DELETE CASCADE, + CONSTRAINT "DEVICE_UNIQUE_NAME_PER_OWNER" UNIQUE ("owner_id", "name") +); --- do be dropped in a later version: -COMMENT ON TABLE "access_token" IS 'DEPRECATED: This table is kept for compatibility with Cryptomator 1.7.x. No new tokens are issued.'; \ No newline at end of file +-- new access tokens will be issued for users (not devices): +CREATE TABLE "access_token" +( + "user_id" VARCHAR(255) COLLATE "C" NOT NULL, + "vault_id" UUID NOT NULL, + "vault_key_jwe" VARCHAR(2000) NOT NULL UNIQUE, + CONSTRAINT "ACCESS_PK" PRIMARY KEY ("user_id", "vault_id"), + CONSTRAINT "ACCESS_FK_USER" FOREIGN KEY ("user_id") REFERENCES "user_details" ("id") ON DELETE CASCADE, + CONSTRAINT "ACCESS_FK_VAULT" FOREIGN KEY ("vault_id") REFERENCES "vault" ("id") ON DELETE CASCADE +); \ No newline at end of file diff --git a/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java index a8df1678..04a69110 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/DeviceResourceTest.java @@ -1,7 +1,6 @@ package org.cryptomator.hub.api; import io.agroal.api.AgroalDataSource; -import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import io.quarkus.test.security.oidc.Claim; @@ -16,7 +15,6 @@ import java.sql.SQLException; import java.time.Instant; -import java.util.Set; import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.when; @@ -52,16 +50,16 @@ public void testCreateNoDeviceDto() { @Test @DisplayName("PUT /devices/ with DTO returns 400") public void testCreateNoDeviceId() { - var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", "publickey1", "", Set.of(), Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", "publickey1", "jwe.jwe.jwe.user1.device1","user1", Instant.parse("2020-02-20T20:20:20Z")); given().contentType(ContentType.JSON).body(deviceDto) - .when().put("/devices/{deviceId}", "\u0020") //a whitespace + .when().put("/devices/{deviceId}", " ") //a whitespace .then().statusCode(400); } @Test @DisplayName("PUT /devices/device1 returns 409") public void testCreate1() { - var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", "publickey1", "owner1", Set.of(), Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", "publickey1", "jwe.jwe.jwe.user1.device1", "user1", Instant.parse("2020-02-20T20:20:20Z")); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "device1") @@ -71,7 +69,7 @@ public void testCreate1() { @Test @DisplayName("PUT /devices/deviceX returns 409 due to non-unique name") public void testCreateX() { - var deviceDto = new DeviceResource.DeviceDto("deviceX", "Computer 1", "publickey1", "owner1", Set.of(), Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("deviceX", "Computer 1", "publickey1", "jwe.jwe.jwe.user1.deviceX","user1", Instant.parse("2020-02-20T20:20:20Z")); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "deviceX") @@ -81,7 +79,7 @@ public void testCreateX() { @Test @DisplayName("PUT /devices/device999 returns 201") public void testCreate2() throws SQLException { - var deviceDto = new DeviceResource.DeviceDto("device999", "Computer 999", "publickey999", "owner1", Set.of(), Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device999", "Computer 999", "publickey999", "jwe.jwe.jwe.user1.device999", "user1", Instant.parse("2020-02-20T20:20:20Z")); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "device999") @@ -97,7 +95,7 @@ public void testCreate2() throws SQLException { @Test @DisplayName("DELETE /devices/ returns 400") public void testDeleteNoDeviceId() { - when().delete("/devices/{deviceId}", "\u0020") //a whitespace + when().delete("/devices/{deviceId}", " ") //a whitespace .then().statusCode(400); } @@ -120,8 +118,8 @@ public void testDeleteNotOwner() { public void testDeleteValid() throws SQLException { try (var s = dataSource.getConnection().createStatement()) { s.execute(""" - INSERT INTO "device" ("id", "owner_id", "name", "publickey", "creation_time") - VALUES ('device999', 'user1', 'To Be Deleted', 'publickey1', '2020-02-20 20:20:20'); + INSERT INTO "device" ("id", "owner_id", "name", "publickey", "user_key_jwe", "creation_time") + VALUES ('device999', 'user1', 'To Be Deleted', 'publickey999', 'jwe.jwe.jwe.user1.device999', '2020-02-20 20:20:20'); """); } @@ -139,7 +137,7 @@ public class AsAnonymous { @Test @DisplayName("PUT /devices/device1 returns 401") public void testCreate1() { - var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", "publickey1", "user1", Set.of(), Instant.parse("2020-02-20T20:20:20Z")); + var deviceDto = new DeviceResource.DeviceDto("device1", "Computer 1", "publickey1", "jwe.jwe.jwe.user1.device1", "user1", Instant.parse("2020-02-20T20:20:20Z")); given().contentType(ContentType.JSON).body(deviceDto) .when().put("/devices/{deviceId}", "device1") diff --git a/backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java index 66128368..e4cbd345 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/UsersResourceTest.java @@ -60,7 +60,7 @@ public void testGetMe2() { .then().statusCode(200) .body("id", is("user1")) .body("devices.id", hasItems("device1")) - .body("devices.accessTo.flatten()", empty()); + .body("accessibleVaults.flatten()", empty()); } @Test @@ -70,7 +70,7 @@ public void testGetMe3() { .then().statusCode(200) .body("id", is("user1")) .body("devices.id", hasItems("device1")) - .body("devices.accessTo.id.flatten()", hasItems(equalToIgnoringCase("7E57C0DE-0000-4000-8000-000100001111"))); + .body("accessibleVaults.id.flatten()", hasItems(equalToIgnoringCase("7E57C0DE-0000-4000-8000-000100001111"))); } @Test diff --git a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java index 2b985753..5be1383a 100644 --- a/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java +++ b/backend/src/test/java/org/cryptomator/hub/api/VaultResourceTest.java @@ -3,6 +3,7 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import io.agroal.api.AgroalDataSource; +import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import io.quarkus.test.security.oidc.Claim; @@ -135,33 +136,19 @@ public void testGetAccess() { } @Test - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/device1 returns 200 using user access") + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens/logged-in-user returns 200 using user access") public void testUnlock1() { - when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "device1") + when().get("/vaults/{vaultId}/access-tokens/logged-in-user", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200) - .body(is("jwe1")); + .body(is("jwe.jwe.jwe.vault1.user1")); } @Test - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/keys/device3 returns 200 using group access") + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/access-tokens/logged-in-user returns 200 using group access") public void testUnlock2() { - when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100002222", "device3") + when().get("/vaults/{vaultId}/access-tokens/logged-in-user", "7E57C0DE-0000-4000-8000-000100002222") .then().statusCode(200) - .body(is("jwe3")); - } - - @Test - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/noSuchDevice returns 404") - public void testUnlock3() { - when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "noSuchDevice") - .then().statusCode(404); - } - - @Test - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/device2 returns 403") - public void testUnlock4() { - when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "device2") - .then().statusCode(403); + .body(is("jwe.jwe.jwe.vault2.user1")); } } @@ -204,17 +191,6 @@ public void testCreateVault1() { .then().statusCode(409); } - @Test - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100002222 returns 409") - public void testCreateVault2() { - var uuid = UUID.fromString("7E57C0DE-0000-4000-8000-000100002222"); - var vaultDto = new VaultResource.VaultDto(uuid, "Vault 1", "This is a testvault.", Instant.parse("2020-02-20T20:20:20Z"), "masterkey1", 42, "salt1", "authPubKey1", "authPrvKey1"); - - given().contentType(ContentType.JSON).body(vaultDto) - .when().put("/vaults/{vaultId}", uuid.toString()) - .then().statusCode(409); - } - @Test @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100003333 returns 201") public void testCreateVault3() { @@ -227,51 +203,62 @@ public void testCreateVault3() { } @Test - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-BADBADBADBAD returns 400") + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-BADBADBADBAD returns 400 due to malformed request body") public void testCreateVault4() { given().contentType(ContentType.JSON) - .when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-BADBADBADBAD") + .when().put("/vaults/{vaultId}", "7E57C0DE-0000-4000-8000-BADBADBADBAD") // invalid body (expected json) .then().statusCode(400); } @Test - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/device3 returns 201") - public void testGrantAccess1() { + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens/user999 returns 201") + public void testGrantAccess1() throws SQLException { + try (var s = dataSource.getConnection().createStatement()) { + s.execute(""" + INSERT INTO "authority" ("id", "type", "name") VALUES ('user999', 'USER', 'User 999'); + INSERT INTO "user_details" ("id") VALUES ('user999'); + INSERT INTO "group_membership" ("group_id", "member_id") VALUES ('group2', 'user999') + """); + } + given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) - .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault1.device3") - .when().put("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "device3") + .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault1.user999") + .when().put("/vaults/{vaultId}/access-tokens/{userId}", "7E57C0DE-0000-4000-8000-000100001111", "user999") .then().statusCode(201); + + try (var s = dataSource.getConnection().createStatement()) { + s.execute(""" + DELETE FROM "authority" WHERE "id" = 'user999'; + """); + } } @Test - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/device1 returns 409 due to user access already granted") + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens/user1 returns 409 due to user access already granted") public void testGrantAccess2() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) - .contentType(ContentType.TEXT).body("jwe1.jwe1.jwe1.jwe1.jwe1") - .when().put("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "device1") + .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault1.user1") + .when().put("/vaults/{vaultId}/access-tokens/{userId}", "7E57C0DE-0000-4000-8000-000100001111", "user1") .then().statusCode(409); } @Test - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100002222/keys/device3 returns 409 due to group access already granted") - @TestSecurity(user = "User Name 2", roles = {"user"}) //we switch here for easy usage - @OidcSecurity(claims = { - @Claim(key = "sub", value = "user2") - }) + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-BADBADBADBAD/access-tokens/user1 returns 400 (vault admin jwt can not be checked for nonexisting vault)") public void testGrantAccess3() { - given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) - .contentType(ContentType.TEXT).body("jwe3.jwe3.jwe3.jwe3.jwe3") - .when().put("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100002222", "device3") - .then().statusCode(409); + given() + .header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) + .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault666.user1") + .when().put("/vaults/{vaultId}/access-tokens/{userId}", "7E57C0DE-0000-4000-8000-BADBADBADBAD", "user1") + .then().statusCode(400); } @Test - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/nonExistingDevice returns 404") + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens/nonExistingUser returns 404 (no such user)") public void testGrantAccess4() { given() .header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) - .contentType(ContentType.TEXT).body("jwe3.jwe3.jwe3.jwe3.jwe3") - .when().put("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "nonExistingDevice") + .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault2.user666") + .when().put("/vaults/{vaultId}/access-tokens/{userId}", "7E57C0DE-0000-4000-8000-000100001111", "nonExistingUser") .then().statusCode(404); } @@ -327,10 +314,10 @@ public void getAccess1() { @Test @Order(5) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/devices-requiring-access-grant does not contains device2") - public void testGetDevicesRequiringAccess1() { + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/users-requiring-access-grant does not contains device2") + public void testGetUsersRequiringAccess1() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) - .when().get("/vaults/{vaultId}/devices-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") + .when().get("/vaults/{vaultId}/users-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") .then().statusCode(200) .body("id", not(hasItems("device2"))); } @@ -374,58 +361,36 @@ public void testAddingMemberAgainFails() { @Test @Order(10) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/devices-requiring-access-grant contains device2") - public void testGetDevicesRequiringAccess2() { + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/users-requiring-access-grant contains device2") + public void testGetUsersRequiringAccess2() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) - .when().get("/vaults/{vaultId}/devices-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") + .when().get("/vaults/{vaultId}/users-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") .then().statusCode(200) - .body("id", hasItems("device2")); + .body("id", hasItems("user2")); } @Test @Order(11) - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100002222/keys/device2 returns 201") + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100002222/access-tokens/user2 returns 201") public void testGrantAccess1() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) .given().contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault2.device2") - .when().put("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100002222", "device2") + .when().put("/vaults/{vaultId}/access-tokens/{userId}", "7E57C0DE-0000-4000-8000-000100002222", "user2") .then().statusCode(201); } @Test @Order(12) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/devices-requiring-access-grant contains not device2") - public void testGetDevicesRequiringAccess3() { + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/users-requiring-access-grant contains not user2") + public void testGetUsersRequiringAccess3() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) - .when().get("/vaults/{vaultId}/devices-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") + .when().get("/vaults/{vaultId}/users-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") .then().statusCode(200) - .body("id", not(hasItems("device2"))); + .body("id", not(hasItems("user2"))); } @Test @Order(13) - @DisplayName("PUT /devices/device9999 returns 201") - public void testCreateDevice2() { - var deviceDto = new DeviceResource.DeviceDto("device9999", "Computer 9999", "publickey9999", "user2", Set.of(), Instant.parse("2020-02-20T20:20:20Z")); - - given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) - .given().contentType(ContentType.JSON).body(deviceDto) - .when().put("/devices/{deviceId}", "device9999") - .then().statusCode(201); - } - - @Test - @Order(14) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/devices-requiring-access-grant contains not device9999") - public void testGetDevicesRequiringAccess4() { - given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) - .when().get("/vaults/{vaultId}/devices-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100002222") - .then().statusCode(200) - .body("id", hasItems("device9999")); - } - - @Test - @Order(15) @DisplayName("DELETE /vaults/7E57C0DE-0000-4000-8000-000100002222/members/user2 returns 204") public void testRevokeAccess() { // previously added in testGrantAccess() given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) @@ -434,7 +399,7 @@ public void testRevokeAccess() { // previously added in testGrantAccess() } @Test - @Order(16) + @Order(14) @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100002222/access does not contain user2") public void getMembers3() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault2AdminJWT) @@ -454,6 +419,18 @@ public void getMembers3() { @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ManageAccessAsUser1 { + @BeforeAll + public void setup() throws SQLException { + try (var s = dataSource.getConnection().createStatement()) { + // user999 will be deleted in #cleanup() + s.execute(""" + INSERT INTO "authority" ("id", "type", "name") VALUES ('user999', 'USER', 'User 999'); + INSERT INTO "user_details" ("id") VALUES ('user999'); + INSERT INTO "group_membership" ("group_id", "member_id") VALUES ('group2', 'user999') + """); + } + } + @Test @Order(1) @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/groups/group3000 returns 404") @@ -484,41 +461,32 @@ public void getMembers1() { @Test @Order(4) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/devices-requiring-access-grant contains device999") - public void testGetDevicesRequiringAccess3() throws SQLException { - try (var s = dataSource.getConnection().createStatement()) { - // device999 will be deleted in #cleanup() - s.execute(""" - INSERT INTO "device" ("id", "owner_id", "name", "publickey", "creation_time") - VALUES - ('device999', 'user2', 'Computer 999', 'publickey90', '2020-02-20 20:20:20'); - """); - } - + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/users-requiring-access-grant contains user999") + public void testGetUsersRequiringAccess3() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) - .when().get("/vaults/{vaultId}/devices-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100001111") + .when().get("/vaults/{vaultId}/users-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200) - .body("id", hasItems("device999")); + .body("id", hasItems("user999")); } @Test @Order(5) - @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/device999 returns 201") + @DisplayName("PUT /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens/user999 returns 201") public void testGrantAccess2() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) - .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault2.device93") - .when().put("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "device999") + .contentType(ContentType.TEXT).body("jwe.jwe.jwe.vault2.user999") + .when().put("/vaults/{vaultId}/access-tokens/{userId}", "7E57C0DE-0000-4000-8000-000100001111", "user999") .then().statusCode(201); } @Test @Order(6) - @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/devices-requiring-access-grant contains not device999") - public void testGetDevicesRequiringAccess4() { + @DisplayName("GET /vaults/7E57C0DE-0000-4000-8000-000100001111/users-requiring-access-grant does no longer contain user999") + public void testGetUsersRequiringAccess4() { given().header(VaultAdminOnlyFilterProvider.VAULT_ADMIN_AUTHORIZATION, vault1AdminJWT) - .when().get("/vaults/{vaultId}/devices-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100001111") + .when().get("/vaults/{vaultId}/users-requiring-access-grant", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(200) - .body("id", not(hasItems("device999"))); + .body("id", not(hasItems("user999"))); } @Test @@ -544,7 +512,7 @@ public void getMembers2() { public void cleanup() throws SQLException { try (var s = dataSource.getConnection().createStatement()) { s.execute(""" - DELETE FROM "device" WHERE ID = 'device999'; + DELETE FROM "authority" WHERE ID = 'user999'; """); } } @@ -643,7 +611,7 @@ public void testUnlockBlockedIfLicenseExceeded() throws SQLException { } //Assumptions.assumeTrue(EffectiveVaultAccess.countEffectiveVaultUsers() > 5); - when().get("/vaults/{vaultId}/keys/{deviceId}", "7E57C0DE-0000-4000-8000-000100001111", "device1") + when().get("/vaults/{vaultId}/access-tokens/logged-in-user", "7E57C0DE-0000-4000-8000-000100001111") .then().statusCode(402); } @@ -671,8 +639,8 @@ public class AsAnonymous { "GET, /vaults/7E57C0DE-0000-4000-8000-000100001111/members", "PUT, /vaults/7E57C0DE-0000-4000-8000-000100001111/users/user1", "DELETE, /vaults/7E57C0DE-0000-4000-8000-000100001111/users/user1", - "GET, /vaults/7E57C0DE-0000-4000-8000-000100001111/devices-requiring-access-grant", - "GET, /vaults/7E57C0DE-0000-4000-8000-000100001111/keys/device1" + "GET, /vaults/7E57C0DE-0000-4000-8000-000100001111/users-requiring-access-grant", + "GET, /vaults/7E57C0DE-0000-4000-8000-000100001111/access-tokens/logged-in-user" }) public void testGet(String method, String path) { when().request(method, path) diff --git a/backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java b/backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java index 6c3d224f..ab8c5a87 100644 --- a/backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java +++ b/backend/src/test/java/org/cryptomator/hub/entities/EntityIntegrationTest.java @@ -29,19 +29,19 @@ public class EntityIntegrationTest { @Test @TestTransaction - @DisplayName("Removing a Device cascades to Access") - public void removingDeviceCascadesToAccess() throws SQLException { + @DisplayName("Removing a User cascades to Access") + public void removingUserCascadesToAccess() throws SQLException { try (var s = dataSource.getConnection().createStatement()) { // test data will be removed via @TestTransaction s.execute(""" - INSERT INTO "device" ("id", "owner_id", "name", "publickey", "creation_time") - VALUES ('device999', 'user1', 'Computer 999', 'publickey999', '2020-02-20 20:20:20'); - INSERT INTO "access_token" ("device_id", "vault_id", "jwe") VALUES ('device999', '7E57C0DE-0000-4000-8000-000100001111', 'jwe4'); + INSERT INTO "authority" ("id", "type", "name") VALUES ('user999', 'USER', 'User 999'); + INSERT INTO "user_details" ("id") VALUES ('user999'); + INSERT INTO "access_token" ("user_id", "vault_id", "vault_key_jwe") VALUES ('user999', '7E57C0DE-0000-4000-8000-000100001111', 'jwe4'); """); } - var deleted = Device.deleteById("device999"); - var matchAfter = AccessToken.findAll().stream().anyMatch(a -> "device999".equals(a.device.id)); + var deleted = User.deleteById("user999"); + var matchAfter = AccessToken.findAll().stream().anyMatch(a -> "user999".equals(a.user.id)); Assertions.assertTrue(deleted); Assertions.assertFalse(matchAfter); } @@ -64,29 +64,18 @@ public void testAddNonUniqueDeviceName() { @Test @TestTransaction - @DisplayName("Retrieve the correct token when a device has access to multiple vaults") - public void testGetCorrectTokenForDeviceWithAcessToMultipleVaults() throws SQLException { - try (var s = dataSource.getConnection().createStatement()) { - // test data will be removed via @TestTransaction - s.execute(""" - INSERT INTO "device" ("id", "owner_id", "name", "publickey", "creation_time") - VALUES ('device999', 'user1', 'Computer 999', 'publickey999', '2020-02-20 20:20:20'); - INSERT INTO "access_token" ("device_id", "vault_id", "jwe") VALUES ('device999', '7E57C0DE-0000-4000-8000-000100001111', 'jwe4'); - INSERT INTO "access_token" ("device_id", "vault_id", "jwe") VALUES ('device999', '7E57C0DE-0000-4000-8000-000100002222', 'jwe5'); - """); - } - + @DisplayName("Retrieve the correct token when a user has access to multiple vaults") + public void testGetCorrectTokenForDeviceWithAcessToMultipleVaults() { List tokens = AccessToken - .find("#AccessToken.get", Parameters.with("deviceId", "device999") - .and("vaultId", UUID.fromString("7E57C0DE-0000-4000-8000-000100001111")) - .and("userId", "user1")) + .find("#AccessToken.get", Parameters.with("userId", "user1") + .and("vaultId", UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"))) .stream().toList(); var token = tokens.get(0); Assertions.assertEquals(1, tokens.size()); Assertions.assertEquals(UUID.fromString("7E57C0DE-0000-4000-8000-000100001111"), token.vault.id); - Assertions.assertEquals("device999", token.device.id); - Assertions.assertEquals("jwe4", token.jwe); + Assertions.assertEquals("user1", token.user.id); + Assertions.assertEquals("jwe.jwe.jwe.vault1.user1", token.jwe); } } \ No newline at end of file diff --git a/backend/src/test/java/org/cryptomator/hub/filters/VaultAdminOnlyFilterProviderTestIT.java b/backend/src/test/java/org/cryptomator/hub/filters/VaultAdminOnlyFilterProviderTestIT.java index f6820abe..00140a9a 100644 --- a/backend/src/test/java/org/cryptomator/hub/filters/VaultAdminOnlyFilterProviderTestIT.java +++ b/backend/src/test/java/org/cryptomator/hub/filters/VaultAdminOnlyFilterProviderTestIT.java @@ -119,7 +119,7 @@ public void testMalformedKeyInDatabase() throws SQLException { try (var s = dataSource.getConnection().createStatement()) { s.execute(""" INSERT INTO "vault" ("id", "name", "description", "creation_time", "salt", "iterations", "masterkey", "auth_pubkey", "auth_prvkey") - VALUES ('7E57C0DE-0000-4000-8000-000100003000', 'Vault 1000', 'This is a testvault.', '2020-02-20 20:20:20', 'salt3000', 'iterations3000', 'masterkey3000', 'pubkey', 'prvkey') + VALUES ('7E57C0DE-0000-4000-8000-000100003000', 'Vault 1000', 'This is a testvault.', '2020-02-20 20:20:20', 'salt3000', 3000, 'masterkey3000', 'pubkey', 'prvkey') """); Assertions.assertThrows(VaultAdminValidationFailedException.class, () -> vaultAdminOnlyFilterProvider.filter(context)); diff --git a/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql b/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql index 8bd585bb..be3ea0ef 100644 --- a/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql +++ b/backend/src/test/resources/org/cryptomator/hub/flyway/V9999__Test_Data.sql @@ -42,16 +42,16 @@ INSERT INTO "vault_access" ("vault_id", "authority_id") VALUES ('7E57C0DE-0000-4000-8000-000100001111', 'user1'), ('7E57C0DE-0000-4000-8000-000100001111', 'user2'), - ('7E57C0DE-0000-4000-8000-000100002222', 'group1'); /* user1, part of group1, has access to vault2 */ + ('7E57C0DE-0000-4000-8000-000100002222', 'group1'); -INSERT INTO "device" ("id", "owner_id", "name", "publickey", "creation_time") +INSERT INTO "device" ("id", "owner_id", "name", "publickey", "creation_time", "user_key_jwe") VALUES - ('device1', 'user1', 'Computer 1', 'publickey1', '2020-02-20 20:20:20'), - ('device2', 'user2', 'Computer 2', 'publickey2', '2020-02-20 20:20:20'), - ('device3', 'user1', 'Computer 3', 'publickey3', '2020-02-20 20:20:20'); /* user1 is part of group1 */ + ('device1', 'user1', 'Computer 1', 'publickey1', '2020-02-20 20:20:20', 'jwe.jwe.jwe.user1.device1'), + ('device2', 'user2', 'Computer 2', 'publickey2', '2020-02-20 20:20:20', 'jwe.jwe.jwe.user2.device2'), + ('device3', 'user1', 'Computer 3', 'publickey3', '2020-02-20 20:20:20', 'jwe.jwe.jwe.user1.device3'); -INSERT INTO "access_token" ("device_id", "vault_id", "jwe") +INSERT INTO "access_token" ("user_id", "vault_id", "vault_key_jwe") VALUES - ('device1', '7E57C0DE-0000-4000-8000-000100001111', 'jwe1'), - ('device2', '7E57C0DE-0000-4000-8000-000100001111', 'jwe2'), - ('device3', '7E57C0DE-0000-4000-8000-000100002222', 'jwe3'); -- device3 of user1, part of group1, has access to vault2 + ('user1', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user1'), -- direct access + ('user2', '7E57C0DE-0000-4000-8000-000100001111', 'jwe.jwe.jwe.vault1.user2'), -- direct access + ('user1', '7E57C0DE-0000-4000-8000-000100002222', 'jwe.jwe.jwe.vault2.user1'); -- access via group1 diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index e9d694b4..8179a70b 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -1,4 +1,4 @@ -import AxiosStatic, { AxiosRequestConfig, AxiosResponse } from 'axios'; +import AxiosStatic, { AxiosHeaders, AxiosRequestConfig, AxiosResponse } from 'axios'; import { JdenticonConfig, toSvg } from 'jdenticon'; import { base64 } from 'rfc4648'; import authPromise from './auth'; @@ -23,9 +23,9 @@ axiosAuth.interceptors.request.use(async request => { try { const token = await authPromise.then(auth => auth.bearerToken()); if (request.headers) { - request.headers['Authorization'] = `Bearer ${token}`; + request.headers.setAuthorization(`Bearer ${token}`); } else { - request.headers = { 'Authorization': `Bearer ${token}` }; + request.headers = new AxiosHeaders({ 'Authorization': `Bearer ${token}` }); } return request; } catch (err: unknown) { @@ -52,7 +52,7 @@ export type DeviceDto = { id: string; name: string; publicKey: string; - accessTo: VaultDto[]; + userKeyJwe: string; creationTime: Date; }; @@ -82,7 +82,8 @@ export abstract class AuthorityDto { } export class UserDto extends AuthorityDto { - constructor(public id: string, public name: string, public type: AuthorityType, public email: string, public devices: DeviceDto[], pictureUrl?: string) { + constructor(public id: string, public name: string, public type: AuthorityType, public email: string, public devices: DeviceDto[], public accessibleVaults: VaultDto[], pictureUrl?: string, + public publicKey?: string, public privateKey?: string, public salt?: string, public iterations?: number) { super(id, name, type, pictureUrl); } @@ -110,7 +111,7 @@ export class UserDto extends AuthorityDto { } static copy(obj: UserDto): UserDto { - return new UserDto(obj.id, obj.name, obj.type, obj.email, obj.devices, obj.pictureUrl); + return new UserDto(obj.id, obj.name, obj.type, obj.email, obj.devices, obj.accessibleVaults, obj.pictureUrl, obj.publicKey, obj.privateKey, obj.salt, obj.iterations); } } @@ -203,6 +204,7 @@ class VaultService { }).catch(err => rethrowAndConvertIfExpected(err, 403)); } + // FIXME: dedup with grantAccess() public async addUser(vaultId: string, userId: string, vaultKeys: VaultKeys): Promise> { let vaultAdminAuthorizationJWT = await this.buildVaultAdminAuthorizationJWT(vaultId, vaultKeys); return axiosAuth.put(`/vaults/${vaultId}/users/${userId}`, null, { headers: { 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) @@ -215,9 +217,9 @@ class VaultService { .catch((error) => rethrowAndConvertIfExpected(error, 404, 409)); } - public async getDevicesRequiringAccessGrant(vaultId: string, vaultKeys: VaultKeys): Promise { + public async getUsersRequiringAccessGrant(vaultId: string, vaultKeys: VaultKeys): Promise { let vaultAdminAuthorizationJWT = await this.buildVaultAdminAuthorizationJWT(vaultId, vaultKeys); - return axiosAuth.get(`/vaults/${vaultId}/devices-requiring-access-grant`, { headers: { 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) + return axiosAuth.get(`/vaults/${vaultId}/users-requiring-access-grant`, { headers: { 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) .then(response => response.data).catch(err => rethrowAndConvertIfExpected(err, 403)); } @@ -227,9 +229,10 @@ class VaultService { .catch((error) => rethrowAndConvertIfExpected(error, 404, 409)); } - public async grantAccess(vaultId: string, deviceId: string, jwe: string, vaultKeys: VaultKeys) { + // FIXME: dedup with addUser() + public async grantAccess(vaultId: string, userId: string, jwe: string, vaultKeys: VaultKeys) { let vaultAdminAuthorizationJWT = await this.buildVaultAdminAuthorizationJWT(vaultId, vaultKeys); - await axiosAuth.put(`/vaults/${vaultId}/keys/${deviceId}`, jwe, { headers: { 'Content-Type': 'text/plain', 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) + await axiosAuth.put(`/vaults/${vaultId}/users/${userId}`, jwe, { headers: { 'Content-Type': 'text/plain', 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) .catch((error) => rethrowAndConvertIfExpected(error, 404, 409)); } @@ -254,6 +257,11 @@ class DeviceService { return axiosAuth.delete(`/devices/${deviceId}`) .catch((error) => rethrowAndConvertIfExpected(error, 404)); } + + public async addDevice(device: DeviceDto): Promise> { + // TODO + return axiosAuth.put(`/devices/${device.id}`, device); + } } class UserService { @@ -261,6 +269,10 @@ class UserService { return axiosAuth.put('/users/me'); } + public async putMyKeyPair(dto: UserDto): Promise { + return axiosAuth.put('/users/me/key-pair', dto); + } + public async me(withDevices: boolean = false, withAccessibleVaults: boolean = false): Promise { return axiosAuth.get(`/users/me?withDevices=${withDevices}&withAccessibleVaults=${withAccessibleVaults}`).then(response => UserDto.copy(response.data)); } diff --git a/frontend/src/common/crypto.ts b/frontend/src/common/crypto.ts index 482e8cde..39d7005e 100644 --- a/frontend/src/common/crypto.ts +++ b/frontend/src/common/crypto.ts @@ -37,6 +37,32 @@ interface JWEPayload { key: string } +const GCM_NONCE_LEN = 12; +const PBKDF2_ITERATION_COUNT = 1000000; + +async function pbkdf2(password: string, salt: Uint8Array, iterations: number, keyParams: AesDerivedKeyParams): Promise { + const encodedPw = new TextEncoder().encode(password); + const pwKey = await crypto.subtle.importKey( + 'raw', + encodedPw, + 'PBKDF2', + false, + ['deriveKey'] + ); + return await crypto.subtle.deriveKey( + { + name: 'PBKDF2', + hash: 'SHA-256', + salt: salt, + iterations: iterations + }, + pwKey, + keyParams, + false, + ['wrapKey', 'unwrapKey'] + ); +} + export class VaultKeys { private static readonly SIGNATURE_KEY_DESIGNATION: EcKeyImportParams | EcKeyGenParams = { name: 'ECDSA', @@ -58,8 +84,6 @@ export class VaultKeys { length: 256 }; - private static readonly GCM_NONCE_LEN = 12; - private static readonly PBKDF2_ITERATION_COUNT = 1000000; readonly masterKey: CryptoKey; readonly signatureKeyPair: CryptoKeyPair; @@ -86,29 +110,6 @@ export class VaultKeys { return new VaultKeys(await key, await keyPair); } - private static async pbkdf2(password: string, salt: Uint8Array, iterations: number): Promise { - const encodedPw = new TextEncoder().encode(password); - const pwKey = await crypto.subtle.importKey( - 'raw', - encodedPw, - 'PBKDF2', - false, - ['deriveKey'] - ); - return await crypto.subtle.deriveKey( - { - name: 'PBKDF2', - hash: 'SHA-256', - salt: salt, - iterations: iterations - }, - pwKey, - VaultKeys.KEK_KEY_DESIGNATION, - false, - ['wrapKey', 'unwrapKey'] - ); - } - /** * Protects the key material. Must only be called for a newly created masterkey, otherwise it will fail. * @param password Password used for wrapping @@ -116,13 +117,12 @@ export class VaultKeys { */ public async wrap(password: string): Promise { // salt: - const salt = new Uint8Array(16); - crypto.getRandomValues(salt); + const salt = crypto.getRandomValues(new Uint8Array(16)); const encodedSalt = base64.stringify(salt); // kek: - const kek = VaultKeys.pbkdf2(password, salt, VaultKeys.PBKDF2_ITERATION_COUNT); + const kek = pbkdf2(password, salt, PBKDF2_ITERATION_COUNT, VaultKeys.KEK_KEY_DESIGNATION); // masterkey: - const masterKeyIv = crypto.getRandomValues(new Uint8Array(VaultKeys.GCM_NONCE_LEN)); + const masterKeyIv = crypto.getRandomValues(new Uint8Array(GCM_NONCE_LEN)); const wrappedMasterKey = new Uint8Array(await crypto.subtle.wrapKey( 'raw', this.masterKey, @@ -131,7 +131,7 @@ export class VaultKeys { )); const encodedMasterKey = base64.stringify(new Uint8Array([...masterKeyIv, ...wrappedMasterKey])); // secretkey: - const secretKeyIv = crypto.getRandomValues(new Uint8Array(VaultKeys.GCM_NONCE_LEN)); + const secretKeyIv = crypto.getRandomValues(new Uint8Array(GCM_NONCE_LEN)); const wrappedSecretKey = new Uint8Array(await crypto.subtle.wrapKey( 'pkcs8', this.signatureKeyPair.privateKey, @@ -143,7 +143,7 @@ export class VaultKeys { const publicKey = new Uint8Array(await crypto.subtle.exportKey('spki', this.signatureKeyPair.publicKey)); const encodedPublicKey = base64.stringify(publicKey); // result: - return new WrappedVaultKeys(encodedMasterKey, encodedSecretKey, encodedPublicKey, encodedSalt, VaultKeys.PBKDF2_ITERATION_COUNT); + return new WrappedVaultKeys(encodedMasterKey, encodedSecretKey, encodedPublicKey, encodedSalt, PBKDF2_ITERATION_COUNT); } /** @@ -154,25 +154,25 @@ export class VaultKeys { * @throws WrongPasswordError, if the wrong password is used */ public static async unwrap(password: string, wrapped: WrappedVaultKeys): Promise { - const kek = VaultKeys.pbkdf2(password, base64.parse(wrapped.salt, { loose: true }), wrapped.iterations); + const kek = pbkdf2(password, base64.parse(wrapped.salt, { loose: true }), wrapped.iterations, VaultKeys.KEK_KEY_DESIGNATION); const decodedMasterKey = base64.parse(wrapped.masterkey, { loose: true }); const decodedPrivateKey = base64.parse(wrapped.signaturePrivateKey, { loose: true }); const decodedPublicKey = base64.parse(wrapped.signaturePublicKey, { loose: true }); try { const masterkey = crypto.subtle.unwrapKey( 'raw', - decodedMasterKey.slice(VaultKeys.GCM_NONCE_LEN), + decodedMasterKey.slice(GCM_NONCE_LEN), await kek, - { name: 'AES-GCM', iv: decodedMasterKey.slice(0, VaultKeys.GCM_NONCE_LEN) }, + { name: 'AES-GCM', iv: decodedMasterKey.slice(0, GCM_NONCE_LEN) }, VaultKeys.MASTERKEY_KEY_DESIGNATION, true, ['sign'] ); const signPrivKey = crypto.subtle.unwrapKey( 'pkcs8', - decodedPrivateKey.slice(VaultKeys.GCM_NONCE_LEN), + decodedPrivateKey.slice(GCM_NONCE_LEN), await kek, - { name: 'AES-GCM', iv: decodedPrivateKey.slice(0, VaultKeys.GCM_NONCE_LEN) }, + { name: 'AES-GCM', iv: decodedPrivateKey.slice(0, GCM_NONCE_LEN) }, VaultKeys.SIGNATURE_KEY_DESIGNATION, false, ['sign'] @@ -264,13 +264,13 @@ export class VaultKeys { /** * Encrypts this masterkey using the given public key - * @param devicePublicKey The recipient's public key (DER-encoded) + * @param userPublicKey The recipient's public key (DER-encoded) * @returns a JWE containing this Masterkey */ - public async encryptForDevice(devicePublicKey: Uint8Array): Promise { + public async encryptForUser(userPublicKey: Uint8Array): Promise { const publicKey = await crypto.subtle.importKey( 'spki', - devicePublicKey, + userPublicKey, { name: 'ECDH', namedCurve: 'P-384' @@ -313,3 +313,187 @@ export class VaultKeys { return wordEncoder.encodePadded(combined); } } + +interface UserKeyArchive { + publicKey: string, + encryptedPrivateKey: string, + salt: string, + iterations: number +} + +export class UserKeys { + private static readonly KEY_DESIGNATION: EcKeyImportParams | EcKeyGenParams = { + name: 'ECDH', + namedCurve: 'P-384' + }; + + private static readonly KEK_DESIGNATION: AesDerivedKeyParams = { + name: 'AES-GCM', + length: 256 + }; + + readonly keyPair: CryptoKeyPair; + + protected constructor(keyPair: CryptoKeyPair) { + this.keyPair = keyPair; + } + + /** + * Creates a new user key pair + * @returns A new user key pair + */ + public static async create(): Promise { + const keyPair = crypto.subtle.generateKey(UserKeys.KEY_DESIGNATION, true, ['deriveKey']); + return new UserKeys(await keyPair); + } + + /** + * Recovers the user key pair using a recovery code. All other information can be retrieved from the backend. + * @param encodedPublicKey The public key + * @param encryptedPrivateKey The encrypted private key + * @param recoveryCode The password used to protect the private key + * @param salt The salt used during PBKDF2 + * @param iterations The number of iterations used by PBKDF2 + * @returns + */ + public static async recover(encodedPublicKey: string, encryptedPrivateKey: string, recoveryCode: string, salt: string, iterations: number) { + const kek = pbkdf2(recoveryCode, base64.parse(salt, { loose: true }), iterations, UserKeys.KEK_DESIGNATION); + const decodedPublicKey = base64.parse(encodedPublicKey, { loose: true }); + const decodedPrivateKey = base64.parse(encryptedPrivateKey, { loose: true }); + const privateKey = crypto.subtle.unwrapKey( + 'pkcs8', + decodedPrivateKey.slice(GCM_NONCE_LEN), + await kek, + { name: 'AES-GCM', iv: decodedPrivateKey.slice(0, GCM_NONCE_LEN) }, + UserKeys.KEK_DESIGNATION, + false, + ['sign'] + ); + const publicKey = crypto.subtle.importKey( + 'spki', + decodedPublicKey, + UserKeys.KEK_DESIGNATION, + true, + ['verify'] + ); + return new UserKeys({ privateKey: await privateKey, publicKey: await publicKey }); + } + + public async export(recoveryCode: string): Promise { + const salt = crypto.getRandomValues(new Uint8Array(12)); + const kek = pbkdf2(recoveryCode, salt, PBKDF2_ITERATION_COUNT, UserKeys.KEK_DESIGNATION); + const iv = crypto.getRandomValues(new Uint8Array(GCM_NONCE_LEN)); + const publicKey = new Uint8Array(await crypto.subtle.exportKey('spki', this.keyPair.publicKey)); + const wrappedPrivateKey = new Uint8Array(await crypto.subtle.wrapKey( + 'pkcs8', + this.keyPair.privateKey, + await kek, + { name: 'AES-GCM', iv: iv } + )); + return { + publicKey: base64.stringify(publicKey), + encryptedPrivateKey: base64.stringify(new Uint8Array([...iv, ...wrappedPrivateKey])), + salt: base64.stringify(salt), + iterations: PBKDF2_ITERATION_COUNT + }; + } + + /** + * Encrypts the user's private key using the given public key + * @param userPublicKey The device's public key (DER-encoded) + * @returns a JWE containing the PKCS#8-encoded private key + */ + public async encryptForDevice(devicePublicKey: CryptoKey | Uint8Array): Promise { + let publicKey: CryptoKey; + if (devicePublicKey instanceof Uint8Array) { + const importParams: EcKeyImportParams = { name: 'ECDH', namedCurve: 'P-384' }; + publicKey = await crypto.subtle.importKey('spki', devicePublicKey, importParams, false, []); + } else { + publicKey = devicePublicKey; + } + + const rawkey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', this.keyPair.privateKey)); + try { + const payload: JWEPayload = { + key: base64.stringify(rawkey) + }; + const payloadJson = new TextEncoder().encode(JSON.stringify(payload)); + return JWE.build(payloadJson, publicKey); + } finally { + rawkey.fill(0x00); + } + } +} + +export class BrowserKeys { + private static readonly KEY_DESIGNATION: EcKeyImportParams | EcKeyGenParams = { + name: 'ECDH', + namedCurve: 'P-384' + }; + + readonly keyPair: CryptoKeyPair; + + protected constructor(keyPair: CryptoKeyPair) { + this.keyPair = keyPair; + } + + /** + * Creates a new device key pair for this browser + * @returns A new device key pair + */ + public static async create(): Promise { + const keyPair = crypto.subtle.generateKey(BrowserKeys.KEY_DESIGNATION, false, ['deriveKey']); + return new BrowserKeys(await keyPair); + } + + /** + * Attempts to load previously stored key pair from the browser's IndexedDB. + * @returns a promise resolving to the loaded browser key pair + */ + public static async load(): Promise { + const db = await new Promise((resolve, reject) => { + const req = indexedDB.open('hub'); + req.onsuccess = evt => { resolve(req.result); }; + req.onerror = evt => { reject(req.error); }; + req.onupgradeneeded = evt => { req.result.createObjectStore('keys'); }; + }); + return new Promise((resolve, reject) => { + const transaction = db.transaction('keys', 'readonly'); + const keyStore = transaction.objectStore('keys'); + const query = keyStore.get('browserKeyPair'); + query.onsuccess = evt => { resolve(query.result); }; + query.onerror = evt => { reject(query.error); }; + }).then((keyPair) => { + return new BrowserKeys(keyPair); + }).finally(() => { + db.close(); + }); + } + + /** + * Stores the key pair in the browser's IndexedDB. See https://www.w3.org/TR/WebCryptoAPI/#concepts-key-storage + * @returns a promise that will resolve if the key pair has been saved + */ + public async store(): Promise { + const db = await new Promise((resolve, reject) => { + const req = indexedDB.open('hub'); + req.onsuccess = evt => { resolve(req.result); }; + req.onerror = evt => { reject(req.error); }; + req.onupgradeneeded = evt => { req.result.createObjectStore('keys'); }; + }); + return new Promise((resolve, reject) => { + const transaction = db.transaction('keys', 'readwrite'); + const keyStore = transaction.objectStore('keys'); + const query = keyStore.put(this.keyPair, 'browserKeyPair'); + query.onsuccess = evt => { transaction.commit(); resolve(); }; + query.onerror = evt => { reject(query.error); }; + }).finally(() => { + db.close(); + }); + } + + public async encodedPublicKey() { + const publicKey = new Uint8Array(await crypto.subtle.exportKey('spki', this.keyPair.publicKey)); + return base64.stringify(publicKey); + } +} diff --git a/frontend/src/common/jwe.ts b/frontend/src/common/jwe.ts index ae5674f4..54d153a2 100644 --- a/frontend/src/common/jwe.ts +++ b/frontend/src/common/jwe.ts @@ -6,20 +6,10 @@ export class ConcatKDF { * * @param z A shared secret * @param keyDataLen Desired key length (in bytes) - * @param algorithmId Purpose of the derived key material - * @param partyUInfo Public information about party U - * @param partyVInfo Public information about party V - * @param suppPubInfo Mutually known public information (optional) - * @param suppPrivInfo Mutually known private information (optional) + * @param otherInfo Optional context info binding the derived key to a key agreement (see e.g. RFC 7518, Section 4.6.2) * @returns key data */ - public static async kdf(z: Uint8Array, keyDataLen: number, algorithmId: Uint8Array, partyUInfo: Uint8Array, partyVInfo: Uint8Array, suppPubInfo: Uint8Array = new Uint8Array(), suppPrivInfo: Uint8Array = new Uint8Array()): Promise { - // AlgorithmID || PartyUInfo || PartyVInfo {|| SuppPubInfo }{|| SuppPrivInfo } - const otherInfo = new Uint8Array([...algorithmId, ...partyUInfo, ...partyVInfo, ...suppPubInfo, ...suppPrivInfo]); - return this.kdfInternal(z, keyDataLen, new Uint8Array(otherInfo)); - } - - private static async kdfInternal(z: Uint8Array, keyDataLen: number, otherInfo: Uint8Array): Promise { + public static async kdf(z: Uint8Array, keyDataLen: number, otherInfo: Uint8Array): Promise { const hashLen = 32; // output length of SHA-256 const reps = Math.ceil(keyDataLen / hashLen); if (reps >= 0xFFFFFFFF) { @@ -94,7 +84,7 @@ export class JWE { const tag = m.slice(m.byteLength - 16); const encodedCiphertext = base64url.stringify(ciphertext, { pad: false }); const encodedTag = base64url.stringify(tag, { pad: false }); - return encodedHeader + '.' + encodedEncryptedKey + '.' + encodedIv + '.' + encodedCiphertext + '.' + encodedTag; + return `${encodedHeader}.${encodedEncryptedKey}.${encodedIv}.${encodedCiphertext}.${encodedTag}`; } // visible for testing @@ -115,7 +105,8 @@ export class JWE { ephemeralSecretKey, ecdhKeyBits )); - derivedKey = await ConcatKDF.kdf(new Uint8Array(agreedKey), desiredKeyBytes, algorithmId, partyUInfo, partyVInfo, new Uint8Array(suppPubInfo)); + const otherInfo = new Uint8Array([...algorithmId, ...partyUInfo, ...partyVInfo, ...new Uint8Array(suppPubInfo)]); + derivedKey = await ConcatKDF.kdf(new Uint8Array(agreedKey), desiredKeyBytes, otherInfo); return crypto.subtle.importKey('raw', derivedKey, { name: 'AES-GCM', length: desiredKeyBytes * 8 }, exportable, ['encrypt']); } finally { derivedKey.fill(0x00); @@ -124,9 +115,9 @@ export class JWE { } private static lengthPrefixed(data: Uint8Array): Uint8Array { - const result = new ArrayBuffer(4 + data.byteLength); - new DataView(result, 0, 4).setUint32(0, data.byteLength, false); - new Uint8Array(result).set(data, 4); - return new Uint8Array(result); + const result = new Uint8Array(4 + data.byteLength); + new DataView(result.buffer, 0, 4).setUint32(0, data.byteLength, false); + result.set(data, 4); + return result; } } diff --git a/frontend/src/components/AuthenticatedMain.vue b/frontend/src/components/AuthenticatedMain.vue index 18d0063f..bb9e6610 100644 --- a/frontend/src/components/AuthenticatedMain.vue +++ b/frontend/src/components/AuthenticatedMain.vue @@ -35,7 +35,7 @@ onMounted(fetchData); async function fetchData() { onFetchError.value = null; try { - me.value = await backend.users.me(true, true); + me.value = await backend.users.me(); } catch (error) { console.error('Retrieving logged in user failed.', error); onFetchError.value = error instanceof Error ? error : new Error('Unknown Error'); diff --git a/frontend/src/components/DeviceList.vue b/frontend/src/components/DeviceList.vue index 94cee72a..92c710c0 100644 --- a/frontend/src/components/DeviceList.vue +++ b/frontend/src/components/DeviceList.vue @@ -98,7 +98,7 @@ onMounted(fetchData); async function fetchData() { onFetchError.value = null; try { - me.value = await backend.users.me(true, true); + me.value = await backend.users.me(true); } catch (error) { console.error('Retrieving device list failed.', error); onFetchError.value = error instanceof Error ? error : new Error('Unknown Error'); diff --git a/frontend/src/components/GrantPermissionDialog.vue b/frontend/src/components/GrantPermissionDialog.vue index e7fbdc97..b0ee0e38 100644 --- a/frontend/src/components/GrantPermissionDialog.vue +++ b/frontend/src/components/GrantPermissionDialog.vue @@ -29,7 +29,7 @@

+ + +
+
+
+

User Key

+

This is your key...

+
+
+
+ no key yet... + +
+
+ you have a key: {{ user.publicKey }} +
+
+
+
@@ -83,13 +102,15 @@ import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } f import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/24/solid'; import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import backend, { VersionDto } from '../common/backend'; +import backend, { UserDto, VersionDto } from '../common/backend'; +import { BrowserKeys, UserKeys } from '../common/crypto'; import { Locale } from '../i18n'; import FetchError from './FetchError.vue'; const { t } = useI18n({ useScope: 'global' }); const version = ref(); +const user = ref(); const onFetchError = ref(); onMounted(fetchData); @@ -99,9 +120,37 @@ async function fetchData() { try { let versionInstalled = backend.version.get(); version.value = await versionInstalled; + user.value = await backend.users.me(); } catch (error) { console.error('Retrieving version information failed.', error); onFetchError.value = error instanceof Error ? error : new Error('Unknown Error'); } } + +async function createUserKey() { + if (!user.value) { + return; + } + const userKeys = await UserKeys.create(); + const recoveryCode = crypto.randomUUID(); // TODO something else? + const archive = await userKeys.export(recoveryCode); + const me = user.value; + me.publicKey = archive.publicKey; + me.privateKey = archive.encryptedPrivateKey; + me.salt = archive.salt; + me.iterations = archive.iterations; + + const browserKeys = await BrowserKeys.create(); // or .load() + await browserKeys.store(); + + const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); + backend.devices.addDevice({ + id: crypto.randomUUID(), + name: navigator.userAgent, // TODO something + publicKey: await browserKeys.encodedPublicKey(), + userKeyJwe: jwe, + creationTime: new Date() + }); + backend.users.putMyKeyPair(me); +} diff --git a/frontend/src/components/VaultDetails.vue b/frontend/src/components/VaultDetails.vue index 0953deff..2fd8a86a 100644 --- a/frontend/src/components/VaultDetails.vue +++ b/frontend/src/components/VaultDetails.vue @@ -72,7 +72,7 @@

{{ t('vaultDetails.actions.title') }}

-
- + @@ -108,7 +108,7 @@ import { ArrowPathIcon } from '@heroicons/vue/20/solid'; import { PlusSmallIcon } from '@heroicons/vue/24/solid'; import { computed, nextTick, onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import backend, { AuthorityDto, ConflictError, DeviceDto, NotFoundError, UserDto, VaultDto } from '../common/backend'; +import backend, { AuthorityDto, ConflictError, NotFoundError, UserDto, VaultDto } from '../common/backend'; import { VaultKeys } from '../common/crypto'; import AuthenticateVaultAdminDialog from './AuthenticateVaultAdminDialog.vue'; import DownloadVaultTemplateDialog from './DownloadVaultTemplateDialog.vue'; @@ -140,7 +140,7 @@ const showingRecoveryKey = ref(false); const showRecoveryKeyDialog = ref(); const vault = ref(); const members = ref>(new Map()); -const devicesRequiringAccessGrant = ref([]); +const usersRequiringAccessGrant = ref([]); const authenticateVaultAdminDialog = ref(); const authenticatingVaultAdmin = ref(false); const vaultKeys = ref(); @@ -166,7 +166,7 @@ async function fetchData() { async function vaultAdminAuthenticated(keys: VaultKeys) { try { (await backend.vaults.getMembers(props.vaultId, keys)).forEach(member => members.value.set(member.id, member)); - devicesRequiringAccessGrant.value = await backend.vaults.getDevicesRequiringAccessGrant(props.vaultId, keys); + usersRequiringAccessGrant.value = await backend.vaults.getUsersRequiringAccessGrant(props.vaultId, keys); isVaultAdmin.value = true; //only set if we can retrieve all necessary information vaultKeys.value = keys; } catch (error) { @@ -178,7 +178,7 @@ async function vaultAdminAuthenticated(keys: VaultKeys) { async function reloadDevicesRequiringAccessGrant() { try { if (vaultKeys.value) { - devicesRequiringAccessGrant.value = await backend.vaults.getDevicesRequiringAccessGrant(props.vaultId, vaultKeys.value); + usersRequiringAccessGrant.value = await backend.vaults.getUsersRequiringAccessGrant(props.vaultId, vaultKeys.value); } } catch (error) { console.error('Getting devices requiring access grant failed.', error); @@ -200,7 +200,7 @@ async function addAuthority(authority: unknown) { if (isVaultAdmin.value && vaultKeys.value) { await addAuthorityBackend(authority); members.value.set(authority.id, authority); - devicesRequiringAccessGrant.value = await backend.vaults.getDevicesRequiringAccessGrant(props.vaultId, vaultKeys.value); + usersRequiringAccessGrant.value = await backend.vaults.getUsersRequiringAccessGrant(props.vaultId, vaultKeys.value); } } catch (error) { //even if error instanceof NotFoundError, it is not expected from user perspective @@ -251,7 +251,7 @@ function showRecoveryKey() { } function permissionGranted() { - devicesRequiringAccessGrant.value = []; + usersRequiringAccessGrant.value = []; } async function searchAuthority(query: string): Promise { @@ -266,7 +266,7 @@ async function revokeUserAccess(userId: string) { if (isVaultAdmin.value && vaultKeys.value) { await backend.vaults.revokeUserAccess(props.vaultId, userId, vaultKeys.value); members.value.delete(userId); - devicesRequiringAccessGrant.value = await backend.vaults.getDevicesRequiringAccessGrant(props.vaultId, vaultKeys.value); + usersRequiringAccessGrant.value = await backend.vaults.getUsersRequiringAccessGrant(props.vaultId, vaultKeys.value); } } catch (error) { console.error('Revoking user access failed.', error); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 133aea94..0bcb8296 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -127,9 +127,16 @@ router.beforeEach((to, from, next) => { if (auth.isAuthenticated()) { await backend.users.syncMe(); } - }).finally(() => { - delete to.query.sync_me; // remove sync_me query parameter to avoid endless recursion - next({ path: to.path, query: to.query, params: to.params, replace: true }); + }).finally(async () => { + let me = await backend.users.me(); + if (!me.publicKey) { + // redirect to user setup + next({ path: '/app/settings' }); + } else { + // remove sync_me query parameter to avoid endless recursion + delete to.query.sync_me; + next({ path: to.path, query: to.query, params: to.params, replace: true }); + } }); } else { next(); diff --git a/frontend/test/common/crypto.spec.ts b/frontend/test/common/crypto.spec.ts index 2212966f..5d41a331 100644 --- a/frontend/test/common/crypto.spec.ts +++ b/frontend/test/common/crypto.spec.ts @@ -102,10 +102,10 @@ describe('crypto', () => { expect(wrapped.iterations).to.eq(1000000); }); - it('encryptForDevice()', async () => { - const deviceKey = base64url.parse('MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR-NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn-mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu'); + it('encryptForUser()', async () => { + const userKey = base64url.parse('MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR-NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn-mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu'); - const encrypted = await vaultKeys.encryptForDevice(deviceKey); + const encrypted = await vaultKeys.encryptForUser(userKey); expect(encrypted).to.be.not.null; }); diff --git a/frontend/test/common/jwe.spec.ts b/frontend/test/common/jwe.spec.ts index 97f87e2f..9e5e4522 100644 --- a/frontend/test/common/jwe.spec.ts +++ b/frontend/test/common/jwe.spec.ts @@ -4,7 +4,6 @@ import { base64url } from 'rfc4648'; import { ConcatKDF, JWE, JWEHeader } from '../../src/common/jwe'; describe('JWE', () => { - before(done => { // since this test runs on Node, we need to replace window.crypto: Object.defineProperty(global, 'crypto', { value: require('node:crypto').webcrypto }); @@ -14,7 +13,6 @@ describe('JWE', () => { }); describe('NIST SP 800-56A Rev. 2 Section 5.8.1', () => { - it('should fulfill test vectors', async () => { const z = new Uint8Array([158, 86, 217, 29, 129, 113, 53, 211, 114, 131, 66, 131, 191, 132, 38, 156, 251, 49, 110, 163, 218, 128, 106, 72, 246, 218, 167, 121, 140, 254, 144, 196]); const algorithmId = new Uint8Array([0, 0, 0, 7, 65, 49, 50, 56, 71, 67, 77]); @@ -22,14 +20,13 @@ describe('JWE', () => { const partyVInfo = new Uint8Array([0, 0, 0, 3, 66, 111, 98]); const suppPubInfo = new Uint8Array([0, 0, 0, 128]); - let derivedKey = await ConcatKDF.kdf(z, 16, algorithmId, partyUInfo, partyVInfo, suppPubInfo); + const otherInfo = new Uint8Array([...algorithmId, ...partyUInfo, ...partyVInfo, ...new Uint8Array(suppPubInfo)]); + let derivedKey = await ConcatKDF.kdf(z, 16, otherInfo); expect(new Uint8Array(derivedKey), 'derived key').to.eql(new Uint8Array([86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26])); }); - }); describe('RFC 7516 / RFC 7518', () => { - it('should build JWE for given public key', async () => { const recipientPublicKey = await crypto.subtle.importKey( 'jwk', From 1f5e7f3be21cea2050e1b70f179def5beaa4d046 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 28 Apr 2023 13:01:48 +0200 Subject: [PATCH 03/77] grant access by encrypting vault key for user --- frontend/src/common/backend.ts | 2 +- frontend/src/components/GrantPermissionDialog.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index 8179a70b..185c980a 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -232,7 +232,7 @@ class VaultService { // FIXME: dedup with addUser() public async grantAccess(vaultId: string, userId: string, jwe: string, vaultKeys: VaultKeys) { let vaultAdminAuthorizationJWT = await this.buildVaultAdminAuthorizationJWT(vaultId, vaultKeys); - await axiosAuth.put(`/vaults/${vaultId}/users/${userId}`, jwe, { headers: { 'Content-Type': 'text/plain', 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) + await axiosAuth.put(`/vaults/${vaultId}/access-tokens/${userId}`, jwe, { headers: { 'Content-Type': 'text/plain', 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }) .catch((error) => rethrowAndConvertIfExpected(error, 404, 409)); } diff --git a/frontend/src/components/GrantPermissionDialog.vue b/frontend/src/components/GrantPermissionDialog.vue index b0ee0e38..de6f6c8c 100644 --- a/frontend/src/components/GrantPermissionDialog.vue +++ b/frontend/src/components/GrantPermissionDialog.vue @@ -53,7 +53,7 @@ diff --git a/frontend/src/i18n/en-US.json b/frontend/src/i18n/en-US.json index 818d5a83..e314935d 100644 --- a/frontend/src/i18n/en-US.json +++ b/frontend/src/i18n/en-US.json @@ -107,6 +107,15 @@ "recoveryKeyDialog.description": "This is your recovery key for \"{0}\". Keep it safe, it is your only chance to regain access to a vault in case of system outage.", "recoveryKeyDialog.recoveryKey": "Recovery Key of Your Vault", + "setupUserKey.createUserKey.title": "Welcome to Cryptomator Hub", + "setupUserKey.createUserKey.description": "To get started, you are going to create a Personal Hub Secret. This secret is used to encrypt your personal access to vaults and is never sent to the server.", + "setupUserKey.createUserKey.submit": "Create Personal Hub Secret", + "setupUserKey.saveRecoveryCode.title": "Save your Personal Hub Secret", + "setupUserKey.saveRecoveryCode.description": "You'll need it to sign in on new devices.", + "setupUserKey.saveRecoveryCode.submit": "Finish Setup", + "setupUserKey.saveRecoveryCode.recoveryCode": "Personal Hub Secret", + "setupUserKey.saveRecoveryCode.recoveryCode.confirm": "I understand that I won't be able to sign it to my account on new devices if I don't have the Personal Hub Secret.", + "userSettings.title": "Settings", "userSettings.general.title": "General", "userSettings.general.language.title": "Language", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0bcb8296..7104c836 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -2,11 +2,13 @@ import { createRouter, createWebHistory, RouteLocationRaw, RouteRecordRaw } from import authPromise from '../common/auth'; import backend from '../common/backend'; import { baseURL } from '../common/config'; +import { BrowserKeys } from '../common/crypto'; import AdminSettings from '../components/AdminSettings.vue'; import AuthenticatedMain from '../components/AuthenticatedMain.vue'; import CreateVault from '../components/CreateVault.vue'; import DeviceList from '../components/DeviceList.vue'; import NotFound from '../components/NotFound.vue'; +import SetupUserKey from '../components/SetupUserKey.vue'; import UnlockError from '../components/UnlockError.vue'; import UnlockSuccess from '../components/UnlockSuccess.vue'; import UserSettings from '../components/UserSettings.vue'; @@ -81,6 +83,13 @@ const routes: RouteRecordRaw[] = [ }, ] }, + { + path: '/app/setup', + component: SetupUserKey, + beforeEnter: async () => { + return await requiresUserKeySetup(); //TODO: reroute to NotFound Screen/ AccessDeniedScreen? + } + }, { path: '/app/unlock-success', component: UnlockSuccess, @@ -127,20 +136,28 @@ router.beforeEach((to, from, next) => { if (auth.isAuthenticated()) { await backend.users.syncMe(); } - }).finally(async () => { - let me = await backend.users.me(); - if (!me.publicKey) { - // redirect to user setup - next({ path: '/app/settings' }); - } else { - // remove sync_me query parameter to avoid endless recursion - delete to.query.sync_me; - next({ path: to.path, query: to.query, params: to.params, replace: true }); - } + }).finally(() => { + delete to.query.sync_me; // remove sync_me query parameter to avoid endless recursion + next({ path: to.path, query: to.query, params: to.params, replace: true }); }); } else { next(); } }); +// THIRD check user/browser keys (requires auth) +router.beforeEach(async (to, from, next) => { + if (!to.meta.skipAuth && await requiresUserKeySetup() && to.path != '/app/setup') { + next({ path: '/app/setup' }); + } else { + next(); + } +}); + +async function requiresUserKeySetup() { + const me = await backend.users.me(); + const browserKeys = await BrowserKeys.load(me.id); + return !me.publicKey || !browserKeys.keyPair; +} + export default router; From 274ffa6f512c4256083bacf39af56405a592e98f Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 26 May 2023 15:27:38 +0200 Subject: [PATCH 17/77] added "enter recovery code" flow in SetupUserKey it doesn't work yet but it's probably close --- frontend/src/common/backend.ts | 2 +- frontend/src/components/CreateVault.vue | 6 +- frontend/src/components/SetupUserKey.vue | 81 +++++++++++++++++++++--- frontend/src/i18n/en-US.json | 4 ++ 4 files changed, 80 insertions(+), 13 deletions(-) diff --git a/frontend/src/common/backend.ts b/frontend/src/common/backend.ts index 3c309297..c3cebc9d 100644 --- a/frontend/src/common/backend.ts +++ b/frontend/src/common/backend.ts @@ -111,7 +111,7 @@ export class UserDto extends AuthorityDto { } static copy(obj: UserDto): UserDto { - return new UserDto(obj.id, obj.name, obj.type, obj.email, obj.devices, obj.accessibleVaults, obj.pictureUrl, obj.publicKey, obj.privateKey, obj.salt, obj.iterations); + return new UserDto(obj.id, obj.name, obj.type, obj.email, obj.devices, obj.accessibleVaults, obj.pictureUrl, obj.publicKey, obj.recoveryJwe, obj.recoveryPbkdf2, obj.recoverySalt, obj.recoveryIterations); } } diff --git a/frontend/src/components/CreateVault.vue b/frontend/src/components/CreateVault.vue index 2dcfe652..79657077 100644 --- a/frontend/src/components/CreateVault.vue +++ b/frontend/src/components/CreateVault.vue @@ -6,7 +6,7 @@
-
+
@@ -102,7 +102,7 @@
-
+
@@ -163,7 +163,7 @@
-
+
diff --git a/frontend/src/components/SetupUserKey.vue b/frontend/src/components/SetupUserKey.vue index 072a5897..e3b01569 100644 --- a/frontend/src/components/SetupUserKey.vue +++ b/frontend/src/components/SetupUserKey.vue @@ -12,7 +12,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -87,7 +87,7 @@
-
@@ -98,7 +98,36 @@
- TODO ENTER RECOVERY CODE + +
+
+
+
+
+

+ {{ t('setupUserKey.enterRecoveryCode.title') }} +

+
+

+ {{ t('setupUserKey.enterRecoveryCode.description') }} +

+
+
+ +
+
+ +
+

{{ t('common.unexpectedError', [onRecoverError.message]) }}

+
+
+
+
+
+
@@ -112,6 +141,7 @@ import backend, { UserDto } from '../common/backend'; import { BrowserKeys, UserKeys } from '../common/crypto'; import { JWE } from '../common/jwe'; import { debounce } from '../common/util'; +import router from '../router'; import FetchError from './FetchError.vue'; enum State { @@ -125,6 +155,7 @@ const { t } = useI18n({ useScope: 'global' }); const onFetchError = ref(null); const onCreateError = ref(null); +const onRecoverError = ref(null); const state = ref(State.Preparing); const processing = ref(false); @@ -158,7 +189,8 @@ async function fetchData() { async function createUserKey() { onCreateError.value = null; try { - if (!user.value) { + const me = user.value; + if (!me) { throw new Error('Invalid state'); } processing.value = true; @@ -166,8 +198,7 @@ async function createUserKey() { const userKeys = await UserKeys.create(); recoveryCode.value = crypto.randomUUID(); // TODO something else? const archive = await userKeys.export(recoveryCode.value); - - const me = user.value; + me.publicKey = archive.publicKey; me.recoveryJwe = await JWE.build({ recoveryCode: recoveryCode.value }, userKeys.keyPair.publicKey); me.recoveryPbkdf2 = archive.encryptedPrivateKey; @@ -177,14 +208,14 @@ async function createUserKey() { const browserKeys = await BrowserKeys.create(); await browserKeys.store(me.id); const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); - backend.devices.putDevice({ + await backend.devices.putDevice({ id: await browserKeys.id(), name: navigator.userAgent, // TODO something publicKey: await browserKeys.encodedPublicKey(), userKeyJwe: jwe, creationTime: new Date() }); - backend.users.putMyKeyPair(me); + await backend.users.putMyKeyPair(me); state.value = State.SaveRecoveryCode; } catch (error) { @@ -200,4 +231,36 @@ async function copyRecoveryCode() { copiedRecoveryCode.value = true; debouncedCopyFinish(); } + +async function recoverUserKey() { + onRecoverError.value = null; + try { + const me = user.value; + if (!me || !me.publicKey || !me.recoveryJwe || !me.recoveryPbkdf2 || !me.recoverySalt || !me.recoveryIterations) { + throw new Error('Invalid state'); + } + processing.value = true; + + const userKeys = await UserKeys.recover(me.publicKey, me.recoveryPbkdf2, recoveryCode.value, me.recoverySalt, me.recoveryIterations); + + const browserKeys = await BrowserKeys.create(); + await browserKeys.store(me.id); + const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); + await backend.devices.putDevice({ + id: await browserKeys.id(), + name: navigator.userAgent, // TODO something + publicKey: await browserKeys.encodedPublicKey(), + userKeyJwe: jwe, + creationTime: new Date() + }); + await backend.users.putMyKeyPair(me); + + router.push('/app/vaults'); + } catch (error) { + console.error('Recovering user key failed.', error); + onRecoverError.value = error instanceof Error ? error : new Error('Unknown reason'); + } finally { + processing.value = false; + } +} diff --git a/frontend/src/i18n/en-US.json b/frontend/src/i18n/en-US.json index e314935d..0ad8a06c 100644 --- a/frontend/src/i18n/en-US.json +++ b/frontend/src/i18n/en-US.json @@ -115,6 +115,10 @@ "setupUserKey.saveRecoveryCode.submit": "Finish Setup", "setupUserKey.saveRecoveryCode.recoveryCode": "Personal Hub Secret", "setupUserKey.saveRecoveryCode.recoveryCode.confirm": "I understand that I won't be able to sign it to my account on new devices if I don't have the Personal Hub Secret.", + "setupUserKey.enterRecoveryCode.title": "Enter your Personal Hub Secret", + "setupUserKey.enterRecoveryCode.description": "You need it to sign in on this new device.", + "setupUserKey.enterRecoveryCode.submit": "Finish Setup", + "setupUserKey.enterRecoveryCode.recoveryCode": "Personal Hub Secret", "userSettings.title": "Settings", "userSettings.general.title": "General", From ed57bde7d345df988d870a0aee1ecee804ac108f Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Fri, 26 May 2023 17:46:25 +0200 Subject: [PATCH 18/77] deduplicated code --- frontend/src/components/SetupUserKey.vue | 62 ++++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/SetupUserKey.vue b/frontend/src/components/SetupUserKey.vue index e3b01569..82d32b9b 100644 --- a/frontend/src/components/SetupUserKey.vue +++ b/frontend/src/components/SetupUserKey.vue @@ -198,24 +198,13 @@ async function createUserKey() { const userKeys = await UserKeys.create(); recoveryCode.value = crypto.randomUUID(); // TODO something else? const archive = await userKeys.export(recoveryCode.value); - me.publicKey = archive.publicKey; me.recoveryJwe = await JWE.build({ recoveryCode: recoveryCode.value }, userKeys.keyPair.publicKey); me.recoveryPbkdf2 = archive.encryptedPrivateKey; me.recoverySalt = archive.salt; me.recoveryIterations = archive.iterations; - - const browserKeys = await BrowserKeys.create(); - await browserKeys.store(me.id); - const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); - await backend.devices.putDevice({ - id: await browserKeys.id(), - name: navigator.userAgent, // TODO something - publicKey: await browserKeys.encodedPublicKey(), - userKeyJwe: jwe, - creationTime: new Date() - }); - await backend.users.putMyKeyPair(me); + const browserKeys = await loadOrCreateBrowserKeys(me.id); + await submitBrowserKeys(browserKeys, me, userKeys); state.value = State.SaveRecoveryCode; } catch (error) { @@ -226,12 +215,6 @@ async function createUserKey() { } } -async function copyRecoveryCode() { - await navigator.clipboard.writeText(recoveryCode.value); - copiedRecoveryCode.value = true; - debouncedCopyFinish(); -} - async function recoverUserKey() { onRecoverError.value = null; try { @@ -242,18 +225,8 @@ async function recoverUserKey() { processing.value = true; const userKeys = await UserKeys.recover(me.publicKey, me.recoveryPbkdf2, recoveryCode.value, me.recoverySalt, me.recoveryIterations); - - const browserKeys = await BrowserKeys.create(); - await browserKeys.store(me.id); - const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); - await backend.devices.putDevice({ - id: await browserKeys.id(), - name: navigator.userAgent, // TODO something - publicKey: await browserKeys.encodedPublicKey(), - userKeyJwe: jwe, - creationTime: new Date() - }); - await backend.users.putMyKeyPair(me); + const browserKeys = await loadOrCreateBrowserKeys(me.id); + await submitBrowserKeys(browserKeys, me, userKeys); router.push('/app/vaults'); } catch (error) { @@ -263,4 +236,31 @@ async function recoverUserKey() { processing.value = false; } } + +async function loadOrCreateBrowserKeys(userId: string): Promise { + let browserKeys = await BrowserKeys.load(userId); + if (!browserKeys.keyPair) { + browserKeys = await BrowserKeys.create(); + await browserKeys.store(userId); + } + return browserKeys; +} + +async function submitBrowserKeys(browserKeys: BrowserKeys, me: UserDto, userKeys: UserKeys) { + const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); + await backend.devices.putDevice({ + id: await browserKeys.id(), + name: navigator.userAgent, // TODO something + publicKey: await browserKeys.encodedPublicKey(), + userKeyJwe: jwe, + creationTime: new Date() + }); + await backend.users.putMyKeyPair(me); +} + +async function copyRecoveryCode() { + await navigator.clipboard.writeText(recoveryCode.value); + copiedRecoveryCode.value = true; + debouncedCopyFinish(); +} From 2f1afc656f6d8d695e0615f2e1af2510908e9007 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 29 May 2023 12:12:58 +0200 Subject: [PATCH 19/77] fix recovering user key from setup code --- frontend/src/common/crypto.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/common/crypto.ts b/frontend/src/common/crypto.ts index 277874e2..57dea61a 100644 --- a/frontend/src/common/crypto.ts +++ b/frontend/src/common/crypto.ts @@ -365,16 +365,16 @@ export class UserKeys { decodedPrivateKey.slice(GCM_NONCE_LEN), await kek, { name: 'AES-GCM', iv: decodedPrivateKey.slice(0, GCM_NONCE_LEN) }, - UserKeys.KEK_DESIGNATION, - false, - ['sign'] + UserKeys.KEY_DESIGNATION, + true, + UserKeys.KEY_USAGES ); const publicKey = crypto.subtle.importKey( 'spki', decodedPublicKey, - UserKeys.KEK_DESIGNATION, + UserKeys.KEY_DESIGNATION, true, - ['verify'] + [] ); return new UserKeys({ privateKey: await privateKey, publicKey: await publicKey }); } From efc4cdffc7647a82597d742d23c69e90b3d49aee Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 29 May 2023 12:14:04 +0200 Subject: [PATCH 20/77] always roll a new key pair when creating/recovering a user key --- frontend/src/components/SetupUserKey.vue | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/SetupUserKey.vue b/frontend/src/components/SetupUserKey.vue index 82d32b9b..68f2f93d 100644 --- a/frontend/src/components/SetupUserKey.vue +++ b/frontend/src/components/SetupUserKey.vue @@ -203,7 +203,7 @@ async function createUserKey() { me.recoveryPbkdf2 = archive.encryptedPrivateKey; me.recoverySalt = archive.salt; me.recoveryIterations = archive.iterations; - const browserKeys = await loadOrCreateBrowserKeys(me.id); + const browserKeys = await createBrowserKeys(me.id); await submitBrowserKeys(browserKeys, me, userKeys); state.value = State.SaveRecoveryCode; @@ -225,7 +225,7 @@ async function recoverUserKey() { processing.value = true; const userKeys = await UserKeys.recover(me.publicKey, me.recoveryPbkdf2, recoveryCode.value, me.recoverySalt, me.recoveryIterations); - const browserKeys = await loadOrCreateBrowserKeys(me.id); + const browserKeys = await createBrowserKeys(me.id); await submitBrowserKeys(browserKeys, me, userKeys); router.push('/app/vaults'); @@ -237,12 +237,9 @@ async function recoverUserKey() { } } -async function loadOrCreateBrowserKeys(userId: string): Promise { - let browserKeys = await BrowserKeys.load(userId); - if (!browserKeys.keyPair) { - browserKeys = await BrowserKeys.create(); - await browserKeys.store(userId); - } +async function createBrowserKeys(userId: string): Promise { + const browserKeys = await BrowserKeys.create(); + await browserKeys.store(userId); return browserKeys; } @@ -250,7 +247,7 @@ async function submitBrowserKeys(browserKeys: BrowserKeys, me: UserDto, userKeys const jwe = await userKeys.encryptForDevice(browserKeys.keyPair.publicKey); await backend.devices.putDevice({ id: await browserKeys.id(), - name: navigator.userAgent, // TODO something + name: navigator.userAgent, // TODO something, maybe let the user choose a name? publicKey: await browserKeys.encodedPublicKey(), userKeyJwe: jwe, creationTime: new Date() From 2ed662493c5f6ada7385cb35503ca641839a2ab4 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 30 May 2023 11:11:26 +0200 Subject: [PATCH 21/77] added current device badge, hidden remove button --- frontend/src/components/DeviceList.vue | 17 +++++++++++++++-- frontend/src/i18n/de-DE.json | 1 + frontend/src/i18n/en-US.json | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/DeviceList.vue b/frontend/src/components/DeviceList.vue index 6db8b0fc..8f066acb 100644 --- a/frontend/src/components/DeviceList.vue +++ b/frontend/src/components/DeviceList.vue @@ -61,7 +61,8 @@ {{ d(device.creationTime, 'short') }} - {{ t('common.remove') }} + {{ t('deviceList.thisDevice') }} + {{ t('common.remove') }}
TODO confirm @@ -93,10 +94,14 @@ import FetchError from './FetchError.vue'; const { t, d } = useI18n({ useScope: 'global' }); const me = ref(); +const myDeviceId = ref(); const onFetchError = ref(); const onRemoveDeviceError = ref< {[id: string]: Error} >({}); -onMounted(fetchData); +onMounted(async () => { + await fetchData(); + await determineMyDeviceId(); +}); async function fetchData() { onFetchError.value = null; @@ -108,6 +113,14 @@ async function fetchData() { } } +async function determineMyDeviceId() { + if (me.value == null) { + throw new Error('User not initialized.'); + } + const browserKeys = await BrowserKeys.load(me.value.id); + myDeviceId.value = await browserKeys.id(); +} + async function validateDevice(device: DeviceDto) { if (!me.value || !me.value.publicKey) { throw new Error('User keys not initialized.'); diff --git a/frontend/src/i18n/de-DE.json b/frontend/src/i18n/de-DE.json index bb4175b4..ea136904 100644 --- a/frontend/src/i18n/de-DE.json +++ b/frontend/src/i18n/de-DE.json @@ -86,6 +86,7 @@ "deviceList.deviceName": "Gerätename", "deviceList.type": "Typ", "deviceList.added": "Hinzugefügt", + "deviceList.thisDevice": "Dieses Gerät", "downloadVaultTemplateDialog.title": "Tresorvorlage herunterladen", "downloadVaultTemplateDialog.description": "Lade die gezippte Tresorvorlage herunter und entpacke ihn an einem beliebigen Ort, der für deine Teammitglieder freigegeben ist. Dies ist einmal für die Ersteinrichtung erforderlich.", diff --git a/frontend/src/i18n/en-US.json b/frontend/src/i18n/en-US.json index 0ad8a06c..61ea1cf9 100644 --- a/frontend/src/i18n/en-US.json +++ b/frontend/src/i18n/en-US.json @@ -87,6 +87,7 @@ "deviceList.deviceName": "Device Name", "deviceList.type": "Type", "deviceList.added": "Added", + "deviceList.thisDevice": "This Device", "downloadVaultTemplateDialog.title": "Download Vault Template", "downloadVaultTemplateDialog.description": "Download and unpack the zipped vault template to any location shared with your team members. This is required only once during the initial setup.", From 4c21c865082d129e72a4c2dc0e315d12473cddcc Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 30 May 2023 12:06:43 +0200 Subject: [PATCH 22/77] added device name input to SetupUserKey --- frontend/src/components/SetupUserKey.vue | 24 ++++++++++++++++++------ frontend/src/i18n/en-US.json | 10 ++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/SetupUserKey.vue b/frontend/src/components/SetupUserKey.vue index 68f2f93d..8c850346 100644 --- a/frontend/src/components/SetupUserKey.vue +++ b/frontend/src/components/SetupUserKey.vue @@ -25,6 +25,10 @@ {{ t('setupUserKey.createUserKey.description') }}

+
+ + +
- +