Skip to content

add v3 impl compatible to UVF draft #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 35 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b07e43b
add v3 impl with UVF compatible file header and hardcoded key id
overheadhunter Feb 9, 2024
f35f9ac
Merge branch 'develop' into feature/uvf-draft
overheadhunter Nov 2, 2024
f47b27b
split Masterkey API into Perpetual + Revolving
overheadhunter Nov 29, 2024
f07ef0e
allow empty chunks, so UVF's EOF-chunks can be added
overheadhunter Nov 29, 2024
56dc34e
java 8 api sucks...
overheadhunter Nov 29, 2024
dd3ac84
fixed test after changing f07ef0e
overheadhunter Nov 29, 2024
485a7bb
fix javadoc
overheadhunter Dec 5, 2024
084e78a
added primitives for file name encryption
overheadhunter Dec 6, 2024
2445d1c
allow encrypting empty chunks
overheadhunter Jan 10, 2025
3924abc
Merge branch 'develop' into feature/uvf-draft
overheadhunter Jan 10, 2025
940857f
allow empty chunks (third attempt)
overheadhunter Jan 11, 2025
4db62e9
fix UVF file header
overheadhunter Jan 17, 2025
e8aeec4
use same test vectors as in typescript impl
overheadhunter Jan 17, 2025
47a26a2
fix build with Java 8
overheadhunter Jan 18, 2025
1170de4
Merge branch 'develop' into feature/uvf-draft
overheadhunter Jan 24, 2025
dcea94d
Introduce new `DirectoryContentCryptor` API
overheadhunter Jan 24, 2025
dcc1aa0
Merge branch 'develop' into feature/uvf-draft
overheadhunter Jan 24, 2025
361b3b0
typo
overheadhunter Mar 5, 2025
1e9bd32
UVF: use 64 bit keys for HMAC-SHA256
overheadhunter Mar 6, 2025
4fa5861
remove generic types
overheadhunter Mar 7, 2025
a431cf4
cleanup
overheadhunter Mar 7, 2025
688845d
API: allow file encryption w/ specific revision
overheadhunter Mar 7, 2025
8865144
API: add `Masterkey.rootDirId()`
overheadhunter Mar 7, 2025
d41b6e7
add convenience method `dirPath(dirUvfMetadata)`
overheadhunter Mar 7, 2025
d8c567b
add test to generate reference directory structure
overheadhunter Mar 7, 2025
f2745ea
fix missing `flush` before returning ciphertext
overheadhunter Mar 12, 2025
fd8ac29
Merge branch 'develop' into feature/uvf-draft
overheadhunter Mar 14, 2025
3c29fb6
implement `DirectoryContentCryptor` API for v1/v2
overheadhunter Mar 28, 2025
28dfcaa
Merge branch 'develop' into feature/uvf-draft
overheadhunter Mar 28, 2025
ad924b1
Merge branch 'develop' into feature/uvf-draft
overheadhunter Mar 28, 2025
030e3e4
use base64url in `vault.uvf` file
overheadhunter Apr 3, 2025
767b088
Merge branch 'develop' into feature/uvf-draft
overheadhunter Apr 3, 2025
577bf0e
deploy SNAPSHOTs when commit message contains
overheadhunter Jun 5, 2025
fdb58d0
fix workflow syntax
overheadhunter Jun 5, 2025
4b9ffa9
fix incorrectly merged fd8ac29
overheadhunter Jun 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Introduce new DirectoryContentCryptor API
for high level encryption/decryption of directory contents
  • Loading branch information
overheadhunter committed Jan 24, 2025
commit dcea94ddbf03cdb796645111707ba35359bb0952
8 changes: 8 additions & 0 deletions src/main/java/org/cryptomator/cryptolib/api/Cryptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ public interface Cryptor extends Destroyable, AutoCloseable {
*/
FileNameCryptor fileNameCryptor(int revision);

/**
* High-Level API for file name encryption and decryption
* @return utility for encryption and decryption of file names in the context of a directory
*/
default DirectoryContentCryptor<?> directoryContentCryptor() {
throw new UnsupportedOperationException("not implemented");
}

@Override
void destroy();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.cryptomator.cryptolib.api;

public interface DirectoryContentCryptor<T extends DirectoryMetadata> {

T rootDirectoryMetadata(); // TODO required?

T newDirectoryMetadata();

/**
* Decrypts the given directory metadata.
*
* @param ciphertext The encrypted directory metadata to decrypt.
* @return The decrypted directory metadata.
* @throws AuthenticationFailedException If the ciphertext is unauthentic.
*/
T decryptDirectoryMetadata(byte[] ciphertext) throws AuthenticationFailedException;

/**
* Encrypts the given directory metadata.
*
* @param directoryMetadata The directory metadata to encrypt.
* @return The encrypted directory metadata.
*/
byte[] encryptDirectoryMetadata(T directoryMetadata);

Decrypting fileNameDecryptor(T directoryMetadata);

Encrypting fileNameEncryptor(T directoryMetadata);

@FunctionalInterface
interface Decrypting {
/**
* Decrypts a single filename
*
* @param ciphertext the full filename to decrypt, including the file extension
* @return Plaintext
* @throws AuthenticationFailedException If the ciphertext is unauthentic.
* @throws IllegalArgumentException If the filename does not meet the expected format.
*/
String decrypt(String ciphertext) throws AuthenticationFailedException, IllegalArgumentException;
}

@FunctionalInterface
interface Encrypting {
/**
* Encrypts a single filename
*
* @param plaintext the full filename to encrypt, including the file extension
* @return Ciphertext
*/
String encrypt(String plaintext);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.cryptomator.cryptolib.api;

public interface DirectoryMetadata {
}
2 changes: 2 additions & 0 deletions src/main/java/org/cryptomator/cryptolib/v3/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

final class Constants {

public static final String UVF_FILE_EXT = ".uvf";

private Constants() {
}

Expand Down
16 changes: 8 additions & 8 deletions src/main/java/org/cryptomator/cryptolib/v3/CryptorImpl.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE.txt.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.cryptolib.v3;

import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.DirectoryContentCryptor;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.RevolvingMasterkey;
Expand All @@ -21,6 +14,7 @@ class CryptorImpl implements Cryptor {
private final RevolvingMasterkey masterkey;
private final FileContentCryptorImpl fileContentCryptor;
private final FileHeaderCryptorImpl fileHeaderCryptor;
private final SecureRandom random;

/**
* Package-private constructor.
Expand All @@ -30,6 +24,7 @@ class CryptorImpl implements Cryptor {
this.masterkey = masterkey;
this.fileHeaderCryptor = new FileHeaderCryptorImpl(masterkey, random);
this.fileContentCryptor = new FileContentCryptorImpl(random);
this.random = random;
}

@Override
Expand All @@ -55,6 +50,11 @@ public FileNameCryptor fileNameCryptor(int revision) {
return new FileNameCryptorImpl(masterkey, revision);
}

@Override
public DirectoryContentCryptorImpl directoryContentCryptor() {
return new DirectoryContentCryptorImpl(masterkey, random, this);
}

@Override
public boolean isDestroyed() {
return masterkey.isDestroyed();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE.txt.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.cryptolib.v3;

import org.cryptomator.cryptolib.api.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.cryptomator.cryptolib.v3;

import com.google.common.io.BaseEncoding;
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
import org.cryptomator.cryptolib.api.DirectoryContentCryptor;
import org.cryptomator.cryptolib.api.FileHeader;
import org.cryptomator.cryptolib.api.RevolvingMasterkey;

import java.nio.ByteBuffer;
import java.security.SecureRandom;

import static org.cryptomator.cryptolib.v3.Constants.UVF_FILE_EXT;

class DirectoryContentCryptorImpl implements DirectoryContentCryptor<DirectoryMetadataImpl> {

private final RevolvingMasterkey masterkey;
private final SecureRandom random;
private final CryptorImpl cryptor;

public DirectoryContentCryptorImpl(RevolvingMasterkey masterkey, SecureRandom random, CryptorImpl cryptor) {
this.masterkey = masterkey;
this.random = random;
this.cryptor = cryptor;
}

// DIRECTORY METADATA

@Override
public DirectoryMetadataImpl rootDirectoryMetadata() {
// TODO
return null;
}

@Override
public DirectoryMetadataImpl newDirectoryMetadata() {
byte[] dirId = new byte[32];
random.nextBytes(dirId);
return new DirectoryMetadataImpl(masterkey.currentRevision(), dirId);
}

@Override
public DirectoryMetadataImpl decryptDirectoryMetadata(byte[] ciphertext) throws AuthenticationFailedException {
if (ciphertext.length != 128) {
throw new IllegalArgumentException("Invalid dir.uvf length: " + ciphertext.length);
}
int headerSize = cryptor.fileHeaderCryptor().headerSize();
ByteBuffer buffer = ByteBuffer.wrap(ciphertext);
ByteBuffer headerBuf = buffer.duplicate().position(0).limit(headerSize);
ByteBuffer contentBuf = buffer.duplicate().position(headerSize);
FileHeaderImpl header = cryptor.fileHeaderCryptor().decryptHeader(headerBuf);
ByteBuffer plaintext = cryptor.fileContentCryptor().decryptChunk(contentBuf, 0, header, true);
assert plaintext.remaining() == 32;
byte[] dirId = new byte[32];
plaintext.get(dirId);
return new DirectoryMetadataImpl(header.getSeedId(), dirId);
}

@Override
public byte[] encryptDirectoryMetadata(DirectoryMetadataImpl directoryMetadata) {
ByteBuffer cleartextBuf = ByteBuffer.wrap(directoryMetadata.dirId());
FileHeader header = cryptor.fileHeaderCryptor().create();
ByteBuffer headerBuf = cryptor.fileHeaderCryptor().encryptHeader(header);
ByteBuffer contentBuf = cryptor.fileContentCryptor().encryptChunk(cleartextBuf, 0, header);
byte[] result = new byte[headerBuf.remaining() + contentBuf.remaining()];
headerBuf.get(result, 0, headerBuf.remaining());
contentBuf.get(result, headerBuf.limit(), contentBuf.remaining());
return result;
}

// FILE NAMES

@Override
public Decrypting fileNameDecryptor(DirectoryMetadataImpl directoryMetadata) {
byte[] dirId = directoryMetadata.dirId();
FileNameCryptorImpl fileNameCryptor = new FileNameCryptorImpl(masterkey, directoryMetadata.seedId());
return ciphertextAndExt -> {
String ciphertext = removeExtension(ciphertextAndExt);
return fileNameCryptor.decryptFilename(BaseEncoding.base64Url(), ciphertext, dirId);
};
}

@Override
public Encrypting fileNameEncryptor(DirectoryMetadataImpl directoryMetadata) {
byte[] dirId = directoryMetadata.dirId();
FileNameCryptorImpl fileNameCryptor = new FileNameCryptorImpl(masterkey, directoryMetadata.seedId());
return plaintext -> {
String ciphertext = fileNameCryptor.encryptFilename(BaseEncoding.base64Url(), plaintext, dirId);
return ciphertext + UVF_FILE_EXT;
};
}

private static String removeExtension(String filename) {
if (filename.endsWith(UVF_FILE_EXT)) {
return filename.substring(0, filename.length() - UVF_FILE_EXT.length());
} else {
throw new IllegalArgumentException("Not a " + UVF_FILE_EXT + " file: " + filename);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.cryptomator.cryptolib.v3;

import org.cryptomator.cryptolib.api.DirectoryMetadata;

class DirectoryMetadataImpl implements DirectoryMetadata {

private final int seedId;
private final byte[] dirId;

public DirectoryMetadataImpl(int seedId, byte[] dirId) {
this.seedId = seedId;
this.dirId = dirId;
}

public byte[] dirId() {
return dirId;
}

public int seedId() {
return seedId;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

import static org.cryptomator.cryptolib.v3.Constants.GCM_TAG_SIZE;

Expand All @@ -33,13 +32,13 @@ class FileHeaderCryptorImpl implements FileHeaderCryptor {
}

@Override
public FileHeader create() {
public FileHeaderImpl create() {
byte[] nonce = new byte[FileHeaderImpl.NONCE_LEN];
random.nextBytes(nonce);
byte[] contentKeyBytes = new byte[FileHeaderImpl.CONTENT_KEY_LEN];
random.nextBytes(contentKeyBytes);
DestroyableSecretKey contentKey = new DestroyableSecretKey(contentKeyBytes, Constants.CONTENT_ENC_ALG);
return new FileHeaderImpl(nonce, contentKey);
return new FileHeaderImpl(masterkey.currentRevision(), nonce, contentKey);
}

@Override
Expand All @@ -50,13 +49,12 @@ public int headerSize() {
@Override
public ByteBuffer encryptHeader(FileHeader header) {
FileHeaderImpl headerImpl = FileHeaderImpl.cast(header);
int seedId = masterkey.currentRevision();
try (DestroyableSecretKey headerKey = masterkey.subKey(seedId, 32, KDF_CONTEXT, "AES")) {
try (DestroyableSecretKey headerKey = masterkey.subKey(headerImpl.getSeedId(), 32, KDF_CONTEXT, "AES")) {
ByteBuffer result = ByteBuffer.allocate(FileHeaderImpl.SIZE);

// general header:
result.put(Constants.UVF_MAGIC_BYTES);
result.order(ByteOrder.BIG_ENDIAN).putInt(seedId);
result.order(ByteOrder.BIG_ENDIAN).putInt(headerImpl.getSeedId());
ByteBuffer generalHeaderBuf = result.duplicate();
generalHeaderBuf.position(0).limit(FileHeaderImpl.UVF_GENERAL_HEADERS_LEN);

Expand Down Expand Up @@ -115,7 +113,7 @@ public FileHeaderImpl decryptHeader(ByteBuffer ciphertextHeaderBuf) throws Authe
byte[] contentKeyBytes = new byte[FileHeaderImpl.CONTENT_KEY_LEN];
payloadCleartextBuf.get(contentKeyBytes);
DestroyableSecretKey contentKey = new DestroyableSecretKey(contentKeyBytes, Constants.CONTENT_ENC_ALG);
return new FileHeaderImpl(nonce, contentKey);
return new FileHeaderImpl(seedId, nonce, contentKey);
} catch (AEADBadTagException e) {
throw new AuthenticationFailedException("Header tag mismatch.", e);
} catch (ShortBufferException e) {
Expand Down
16 changes: 7 additions & 9 deletions src/main/java/org/cryptomator/cryptolib/v3/FileHeaderImpl.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE.txt.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.cryptolib.v3;

import org.cryptomator.cryptolib.api.FileHeader;
Expand All @@ -24,13 +16,15 @@ class FileHeaderImpl implements FileHeader, Destroyable {
static final int TAG_LEN = Constants.GCM_TAG_SIZE;
static final int SIZE = UVF_GENERAL_HEADERS_LEN + NONCE_LEN + CONTENT_KEY_LEN + TAG_LEN;

private final int seedId;
private final byte[] nonce;
private final DestroyableSecretKey contentKey;

FileHeaderImpl(byte[] nonce, DestroyableSecretKey contentKey) {
FileHeaderImpl(int seedId, byte[] nonce, DestroyableSecretKey contentKey) {
if (nonce.length != NONCE_LEN) {
throw new IllegalArgumentException("Invalid nonce length. (was: " + nonce.length + ", required: " + NONCE_LEN + ")");
}
this.seedId = seedId;
this.nonce = nonce;
this.contentKey = contentKey;
}
Expand All @@ -42,6 +36,10 @@ static FileHeaderImpl cast(FileHeader header) {
throw new IllegalArgumentException("Unsupported header type " + header.getClass());
}
}

public int getSeedId() {
return seedId;
}

public byte[] getNonce() {
return nonce;
Expand Down
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy