Skip to content

Commit 0f3cd0a

Browse files
authored
Merge pull request #13 from chenkins/feature/uvf-draft
Feature/uvf draft
2 parents 6bddcd2 + e6eaf1f commit 0f3cd0a

31 files changed

+666
-171
lines changed
Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
name: Publish to Maven Central
22
on:
3-
workflow_dispatch:
4-
inputs:
5-
tag:
6-
description: 'Tag'
7-
required: true
8-
default: '0.0.0'
3+
push:
4+
release:
5+
types: [published]
96
jobs:
107
publish:
8+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[release snapshot]')
119
runs-on: ubuntu-latest
1210
steps:
1311
- uses: actions/checkout@v4
14-
with:
15-
ref: "refs/tags/${{ github.event.inputs.tag }}"
1612
- uses: actions/setup-java@v4
1713
with:
1814
java-version: 23
1915
distribution: 'temurin'
2016
cache: 'maven'
21-
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
22-
server-username: MAVEN_USERNAME # env variable for username in deploy
23-
server-password: MAVEN_PASSWORD # env variable for token in deploy
24-
gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
25-
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
26-
- name: Enforce project version ${{ github.event.inputs.tag }}
27-
run: mvn versions:set -B -DnewVersion="$GIT_TAG"
28-
env:
29-
GIT_TAG: ${{ github.event.inputs.tag }}
17+
server-id: central
18+
server-username: MAVEN_CENTRAL_USERNAME
19+
server-password: MAVEN_CENTRAL_PASSWORD
20+
- name: Enforce project version ${{ github.event.release.tag_name }}
21+
if: github.event_name == 'release'
22+
run: mvn versions:set -B -DnewVersion=${{ github.event.release.tag_name }}
23+
- name: Verify this is a SNAPSHOT
24+
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[release snapshot]')
25+
run: |
26+
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
27+
if [[ "$VERSION" != *-SNAPSHOT ]]; then
28+
echo "::error file=pom.xml,title=Not a SNAPSHOT::Project version ($VERSION) does not end with -SNAPSHOT"
29+
exit 1
30+
fi
3031
- name: Deploy
3132
run: mvn deploy -B -DskipTests -Psign,deploy-central --no-transfer-progress
3233
env:
33-
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
34-
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
34+
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
35+
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
3536
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
37+
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
38+
MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }}
Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
11
name: Publish to GitHub Packages
22
on:
3+
push:
34
release:
45
types: [published]
56
jobs:
67
publish:
8+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[release snapshot]')
79
runs-on: ubuntu-latest
8-
if: startsWith(github.ref, 'refs/tags/') # only allow publishing tagged versions
910
steps:
1011
- uses: actions/checkout@v4
1112
- uses: actions/setup-java@v4
1213
with:
1314
java-version: 23
1415
distribution: 'temurin'
1516
cache: 'maven'
16-
gpg-private-key: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
17-
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
1817
- name: Enforce project version ${{ github.event.release.tag_name }}
18+
if: github.event_name == 'release'
1919
run: mvn versions:set -B -DnewVersion=${{ github.event.release.tag_name }}
20+
- name: Verify this is a SNAPSHOT
21+
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[release snapshot]')
22+
run: |
23+
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
24+
if [[ "$VERSION" != *-SNAPSHOT ]]; then
25+
echo "::error file=pom.xml,title=Not a SNAPSHOT::Project version ($VERSION) does not end with -SNAPSHOT"
26+
exit 1
27+
fi
2028
- name: Deploy
2129
run: mvn deploy -B -DskipTests -Psign,deploy-github --no-transfer-progress
2230
env:
2331
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2432
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
25-
- name: Slack Notification
26-
uses: rtCamp/action-slack-notify@v2
27-
env:
28-
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
29-
SLACK_USERNAME: 'Cryptobot'
30-
SLACK_ICON:
31-
SLACK_ICON_EMOJI: ':bot:'
32-
SLACK_CHANNEL: 'cryptomator-desktop'
33-
SLACK_TITLE: "Published ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}"
34-
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/publish-central.yml|deploy to Maven Central>."
35-
SLACK_FOOTER:
36-
MSG_MINIMAL: true
33+
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
34+
MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }}

pom.xml

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,22 @@
3232
<maven.compiler.target>8</maven.compiler.target>
3333

3434
<!-- dependencies -->
35-
<gson.version>2.11.0</gson.version>
36-
<guava.version>33.3.0-jre</guava.version>
37-
<siv-mode.version>1.6.0</siv-mode.version>
38-
<bouncycastle.version>1.78.1</bouncycastle.version>
39-
<slf4j.version>2.0.16</slf4j.version>
35+
<gson.version>2.12.1</gson.version>
36+
<guava.version>33.4.0-jre</guava.version>
37+
<siv-mode.version>1.6.1</siv-mode.version>
38+
<bouncycastle.version>1.80</bouncycastle.version>
39+
<slf4j.version>2.0.17</slf4j.version>
4040

4141
<!-- test dependencies -->
42-
<junit.jupiter.version>5.11.0</junit.jupiter.version>
42+
<junit.jupiter.version>5.12.0</junit.jupiter.version>
4343
<mockito.version>5.15.2</mockito.version>
4444
<hamcrest.version>3.0</hamcrest.version>
4545
<jmh.version>1.37</jmh.version>
4646

4747
<!-- build plugin dependencies -->
48-
<dependency-check.version>11.1.1</dependency-check.version>
49-
<jacoco.version>0.8.12</jacoco.version>
50-
<nexus-staging.version>1.7.0</nexus-staging.version>
48+
<dependency-check.version>12.1.0</dependency-check.version>
49+
<jacoco.version>0.8.13</jacoco.version>
50+
<central-publishing.version>0.7.0</central-publishing.version>
5151
</properties>
5252

5353
<licenses>
@@ -171,7 +171,7 @@
171171
<rules>
172172
<requireJavaVersion>
173173
<message>You need at least JDK 22 to build this project.</message>
174-
<version>[21,)</version>
174+
<version>[22,)</version>
175175
</requireJavaVersion>
176176
</rules>
177177
</configuration>
@@ -180,7 +180,7 @@
180180
</plugin>
181181
<plugin>
182182
<artifactId>maven-compiler-plugin</artifactId>
183-
<version>3.13.0</version>
183+
<version>3.14.0</version>
184184
<configuration>
185185
<source>${maven.compiler.source}</source>
186186
<target>${maven.compiler.target}</target>
@@ -284,6 +284,9 @@
284284
<groupId>org.apache.maven.plugins</groupId>
285285
<artifactId>maven-surefire-plugin</artifactId>
286286
<version>3.5.2</version>
287+
<configuration>
288+
<argLine>@{argLine} -Dnet.bytebuddy.experimental=true</argLine>
289+
</configuration>
287290
</plugin>
288291
<plugin>
289292
<groupId>org.apache.maven.plugins</groupId>
@@ -412,7 +415,7 @@
412415
</profile>
413416

414417
<profile>
415-
<id>release</id>
418+
<id>sign</id>
416419
<build>
417420
<plugins>
418421
<plugin>
@@ -426,10 +429,7 @@
426429
<goal>sign</goal>
427430
</goals>
428431
<configuration>
429-
<gpgArguments>
430-
<arg>--pinentry-mode</arg>
431-
<arg>loopback</arg>
432-
</gpgArguments>
432+
<signer>bc</signer>
433433
</configuration>
434434
</execution>
435435
</executions>
@@ -440,26 +440,17 @@
440440

441441
<profile>
442442
<id>deploy-central</id>
443-
<distributionManagement>
444-
<repository>
445-
<id>ossrh</id>
446-
<name>Maven Central</name>
447-
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
448-
</repository>
449-
</distributionManagement>
450443
<build>
451444
<plugins>
452445
<plugin>
453-
<artifactId>maven-javadoc-plugin</artifactId>
454-
<version>3.1.1</version>
455-
<executions>
456-
<execution>
457-
<id>attach-javadocs</id>
458-
<goals>
459-
<goal>jar</goal>
460-
</goals>
461-
</execution>
462-
</executions>
446+
<groupId>org.sonatype.central</groupId>
447+
<artifactId>central-publishing-maven-plugin</artifactId>
448+
<version>${central-publishing.version}</version>
449+
<extensions>true</extensions>
450+
<configuration>
451+
<publishingServerId>central</publishingServerId>
452+
<autoPublish>true</autoPublish>
453+
</configuration>
463454
</plugin>
464455
</plugins>
465456
</build>

src/main/java/org/cryptomator/cryptolib/api/Cryptor.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ public interface Cryptor extends Destroyable, AutoCloseable {
4343
* High-Level API for file name encryption and decryption
4444
* @return utility for encryption and decryption of file names in the context of a directory
4545
*/
46-
default DirectoryContentCryptor directoryContentCryptor() {
47-
throw new UnsupportedOperationException("not implemented");
48-
}
46+
DirectoryContentCryptor directoryContentCryptor();
4947

5048
@Override
5149
void destroy();

src/main/java/org/cryptomator/cryptolib/api/UVFMasterkey.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,18 @@ public static UVFMasterkey fromDecryptedPayload(String json) {
4242
Preconditions.checkArgument("HKDF-SHA512".equals(root.get("kdf").getAsString()));
4343
Preconditions.checkArgument(root.get("seeds").isJsonObject());
4444

45-
Base64.Decoder base64 = Base64.getDecoder();
46-
byte[] initialSeed = base64.decode(root.get("initialSeed").getAsString());
47-
byte[] latestSeed = base64.decode(root.get("latestSeed").getAsString());
48-
byte[] kdfSalt = base64.decode(root.get("kdfSalt").getAsString());
45+
Base64.Decoder base64Url = Base64.getUrlDecoder();
46+
byte[] initialSeed = base64Url.decode(root.get("initialSeed").getAsString());
47+
byte[] latestSeed = base64Url.decode(root.get("latestSeed").getAsString());
48+
byte[] kdfSalt = base64Url.decode(root.get("kdfSalt").getAsString());
4949

5050
Map<Integer, byte[]> seeds = new HashMap<>();
5151
ByteBuffer intBuf = ByteBuffer.allocate(Integer.BYTES);
5252
for (Map.Entry<String, JsonElement> entry : root.getAsJsonObject("seeds").asMap().entrySet()) {
5353
intBuf.clear();
54-
intBuf.put(base64.decode(entry.getKey()));
54+
intBuf.put(base64Url.decode(entry.getKey()));
5555
int seedNum = intBuf.getInt(0);
56-
byte[] seedVal = base64.decode(entry.getValue().getAsString());
56+
byte[] seedVal = base64Url.decode(entry.getValue().getAsString());
5757
seeds.put(seedNum, seedVal);
5858
}
5959
return new UVFMasterkey(seeds, kdfSalt, ByteBuffer.wrap(initialSeed).getInt(), ByteBuffer.wrap(latestSeed).getInt());

src/main/java/org/cryptomator/cryptolib/v1/Constants.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
/*******************************************************************************
2-
* Copyright (c) 2016 Sebastian Stenzel and others.
3-
* All rights reserved. This program and the accompanying materials
4-
* are made available under the terms of the accompanying LICENSE.txt.
5-
*
6-
* Contributors:
7-
* Sebastian Stenzel - initial API and implementation
8-
*******************************************************************************/
91
package org.cryptomator.cryptolib.v1;
102

113
final class Constants {
124

135
private Constants() {
146
}
157

8+
static final String C9R_FILE_EXT = ".c9r";
9+
1610
static final String CONTENT_ENC_ALG = "AES";
1711

1812
static final int NONCE_SIZE = 16;

src/main/java/org/cryptomator/cryptolib/v1/CryptorImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.cryptomator.cryptolib.v1;
22

33
import org.cryptomator.cryptolib.api.Cryptor;
4+
import org.cryptomator.cryptolib.api.DirectoryContentCryptor;
45
import org.cryptomator.cryptolib.api.FileHeaderCryptor;
56
import org.cryptomator.cryptolib.api.FileNameCryptor;
67
import org.cryptomator.cryptolib.api.Masterkey;
@@ -54,6 +55,11 @@ public FileNameCryptor fileNameCryptor(int revision) {
5455
throw new UnsupportedOperationException();
5556
}
5657

58+
@Override
59+
public DirectoryContentCryptor directoryContentCryptor() {
60+
return new DirectoryContentCryptorImpl(this);
61+
}
62+
5763
@Override
5864
public boolean isDestroyed() {
5965
return masterkey.isDestroyed();
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.cryptomator.cryptolib.v1;
2+
3+
import com.google.common.io.BaseEncoding;
4+
import org.cryptomator.cryptolib.api.DirectoryContentCryptor;
5+
import org.cryptomator.cryptolib.api.DirectoryMetadata;
6+
7+
import java.nio.charset.StandardCharsets;
8+
import java.util.UUID;
9+
10+
import static org.cryptomator.cryptolib.v1.Constants.C9R_FILE_EXT;
11+
12+
class DirectoryContentCryptorImpl implements DirectoryContentCryptor {
13+
14+
private final CryptorImpl cryptor;
15+
16+
public DirectoryContentCryptorImpl(CryptorImpl cryptor) {
17+
this.cryptor = cryptor;
18+
}
19+
20+
// DIRECTORY METADATA
21+
22+
@Override
23+
public DirectoryMetadataImpl rootDirectoryMetadata() {
24+
return new DirectoryMetadataImpl(new byte[0]);
25+
}
26+
27+
@Override
28+
public DirectoryMetadataImpl newDirectoryMetadata() {
29+
byte[] dirId = UUID.randomUUID().toString().getBytes(StandardCharsets.US_ASCII);
30+
return new DirectoryMetadataImpl(dirId);
31+
}
32+
33+
@Override
34+
public DirectoryMetadataImpl decryptDirectoryMetadata(byte[] ciphertext) {
35+
// dirId is stored in plaintext
36+
return new DirectoryMetadataImpl(ciphertext);
37+
}
38+
39+
@Override
40+
public byte[] encryptDirectoryMetadata(DirectoryMetadata directoryMetadata) {
41+
// dirId is stored in plaintext
42+
DirectoryMetadataImpl metadataImpl = DirectoryMetadataImpl.cast(directoryMetadata);
43+
return metadataImpl.dirId();
44+
}
45+
46+
// DIR PATH
47+
48+
@Override
49+
public String dirPath(DirectoryMetadata directoryMetadata) {
50+
DirectoryMetadataImpl metadataImpl = DirectoryMetadataImpl.cast(directoryMetadata);
51+
String dirIdStr = cryptor.fileNameCryptor().hashDirectoryId(metadataImpl.dirId());
52+
assert dirIdStr.length() == 32;
53+
return "d/" + dirIdStr.substring(0, 2) + "/" + dirIdStr.substring(2);
54+
}
55+
56+
// FILE NAMES
57+
58+
@Override
59+
public Decrypting fileNameDecryptor(DirectoryMetadata directoryMetadata) {
60+
DirectoryMetadataImpl metadataImpl = DirectoryMetadataImpl.cast(directoryMetadata);
61+
byte[] dirId = metadataImpl.dirId();
62+
FileNameCryptorImpl fileNameCryptor = cryptor.fileNameCryptor();
63+
return ciphertextAndExt -> {
64+
String ciphertext = removeExtension(ciphertextAndExt);
65+
return fileNameCryptor.decryptFilename(BaseEncoding.base64Url(), ciphertext, dirId);
66+
};
67+
}
68+
69+
@Override
70+
public Encrypting fileNameEncryptor(DirectoryMetadata directoryMetadata) {
71+
DirectoryMetadataImpl metadataImpl = DirectoryMetadataImpl.cast(directoryMetadata);
72+
byte[] dirId = metadataImpl.dirId();
73+
FileNameCryptorImpl fileNameCryptor = cryptor.fileNameCryptor();
74+
return plaintext -> {
75+
String ciphertext = fileNameCryptor.encryptFilename(BaseEncoding.base64Url(), plaintext, dirId);
76+
return ciphertext + C9R_FILE_EXT;
77+
};
78+
}
79+
80+
private static String removeExtension(String filename) {
81+
if (filename.endsWith(C9R_FILE_EXT)) {
82+
return filename.substring(0, filename.length() - C9R_FILE_EXT.length());
83+
} else {
84+
throw new IllegalArgumentException("Not a " + C9R_FILE_EXT + " file: " + filename);
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)