-
Notifications
You must be signed in to change notification settings - Fork 35
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
overheadhunter
wants to merge
35
commits into
develop
Choose a base branch
from
feature/uvf-draft
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
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 f35f9ac
Merge branch 'develop' into feature/uvf-draft
overheadhunter f47b27b
split Masterkey API into Perpetual + Revolving
overheadhunter f07ef0e
allow empty chunks, so UVF's EOF-chunks can be added
overheadhunter 56dc34e
java 8 api sucks...
overheadhunter dd3ac84
fixed test after changing f07ef0e
overheadhunter 485a7bb
fix javadoc
overheadhunter 084e78a
added primitives for file name encryption
overheadhunter 2445d1c
allow encrypting empty chunks
overheadhunter 3924abc
Merge branch 'develop' into feature/uvf-draft
overheadhunter 940857f
allow empty chunks (third attempt)
overheadhunter 4db62e9
fix UVF file header
overheadhunter e8aeec4
use same test vectors as in typescript impl
overheadhunter 47a26a2
fix build with Java 8
overheadhunter 1170de4
Merge branch 'develop' into feature/uvf-draft
overheadhunter dcea94d
Introduce new `DirectoryContentCryptor` API
overheadhunter dcc1aa0
Merge branch 'develop' into feature/uvf-draft
overheadhunter 361b3b0
typo
overheadhunter 1e9bd32
UVF: use 64 bit keys for HMAC-SHA256
overheadhunter 4fa5861
remove generic types
overheadhunter a431cf4
cleanup
overheadhunter 688845d
API: allow file encryption w/ specific revision
overheadhunter 8865144
API: add `Masterkey.rootDirId()`
overheadhunter d41b6e7
add convenience method `dirPath(dirUvfMetadata)`
overheadhunter d8c567b
add test to generate reference directory structure
overheadhunter f2745ea
fix missing `flush` before returning ciphertext
overheadhunter fd8ac29
Merge branch 'develop' into feature/uvf-draft
overheadhunter 3c29fb6
implement `DirectoryContentCryptor` API for v1/v2
overheadhunter 28dfcaa
Merge branch 'develop' into feature/uvf-draft
overheadhunter ad924b1
Merge branch 'develop' into feature/uvf-draft
overheadhunter 030e3e4
use base64url in `vault.uvf` file
overheadhunter 767b088
Merge branch 'develop' into feature/uvf-draft
overheadhunter 577bf0e
deploy SNAPSHOTs when commit message contains
overheadhunter fdb58d0
fix workflow syntax
overheadhunter 4b9ffa9
fix incorrectly merged fd8ac29
overheadhunter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/******************************************************************************* | ||
* 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 java.nio.charset.StandardCharsets; | ||
|
||
final class Constants { | ||
|
||
private Constants() { | ||
} | ||
|
||
static final String CONTENT_ENC_ALG = "AES"; | ||
|
||
static final byte[] UVF_MAGIC_BYTES = "UVF0".getBytes(StandardCharsets.US_ASCII); | ||
static final byte[] KEY_ID = "KEY0".getBytes(StandardCharsets.US_ASCII); | ||
|
||
static final int GCM_NONCE_SIZE = 12; // 96 bit IVs strongly recommended for GCM | ||
static final int PAYLOAD_SIZE = 32 * 1024; | ||
static final int GCM_TAG_SIZE = 16; | ||
static final int CHUNK_SIZE = GCM_NONCE_SIZE + PAYLOAD_SIZE + GCM_TAG_SIZE; | ||
|
||
} |
74 changes: 74 additions & 0 deletions
74
src/main/java/org/cryptomator/cryptolib/v3/CryptorImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/******************************************************************************* | ||
* 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.Masterkey; | ||
import org.cryptomator.cryptolib.v1.CryptorProviderImpl; | ||
|
||
import java.security.SecureRandom; | ||
|
||
class CryptorImpl implements Cryptor { | ||
|
||
private final Masterkey masterkey; | ||
private final FileContentCryptorImpl fileContentCryptor; | ||
private final FileHeaderCryptorImpl fileHeaderCryptor; | ||
private final FileNameCryptorImpl fileNameCryptor; | ||
|
||
/** | ||
* Package-private constructor. | ||
* Use {@link CryptorProviderImpl#provide(Masterkey, SecureRandom)} to obtain a Cryptor instance. | ||
*/ | ||
CryptorImpl(Masterkey masterkey, SecureRandom random) { | ||
this.masterkey = masterkey; | ||
this.fileHeaderCryptor = new FileHeaderCryptorImpl(masterkey, random); | ||
this.fileContentCryptor = new FileContentCryptorImpl(random); | ||
this.fileNameCryptor = new FileNameCryptorImpl(masterkey); | ||
} | ||
|
||
@Override | ||
public FileContentCryptorImpl fileContentCryptor() { | ||
assertNotDestroyed(); | ||
return fileContentCryptor; | ||
} | ||
|
||
@Override | ||
public FileHeaderCryptorImpl fileHeaderCryptor() { | ||
assertNotDestroyed(); | ||
return fileHeaderCryptor; | ||
} | ||
|
||
@Override | ||
public FileNameCryptorImpl fileNameCryptor() { | ||
assertNotDestroyed(); | ||
return fileNameCryptor; | ||
} | ||
|
||
@Override | ||
public boolean isDestroyed() { | ||
return masterkey.isDestroyed(); | ||
} | ||
|
||
@Override | ||
public void close() { | ||
destroy(); | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
masterkey.destroy(); | ||
} | ||
|
||
private void assertNotDestroyed() { | ||
if (isDestroyed()) { | ||
throw new IllegalStateException("Cryptor destroyed."); | ||
} | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
src/main/java/org/cryptomator/cryptolib/v3/CryptorProviderImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/******************************************************************************* | ||
* 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.CryptorProvider; | ||
import org.cryptomator.cryptolib.api.Masterkey; | ||
import org.cryptomator.cryptolib.common.ReseedingSecureRandom; | ||
|
||
import java.security.SecureRandom; | ||
|
||
public class CryptorProviderImpl implements CryptorProvider { | ||
|
||
@Override | ||
public Scheme scheme() { | ||
return Scheme.UVF_DRAFT; | ||
} | ||
|
||
@Override | ||
public CryptorImpl provide(Masterkey masterkey, SecureRandom random) { | ||
return new CryptorImpl(masterkey, ReseedingSecureRandom.create(random)); | ||
} | ||
|
||
} |
158 changes: 158 additions & 0 deletions
158
src/main/java/org/cryptomator/cryptolib/v3/FileContentCryptorImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/******************************************************************************* | ||
* 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.AuthenticationFailedException; | ||
import org.cryptomator.cryptolib.api.FileContentCryptor; | ||
import org.cryptomator.cryptolib.api.FileHeader; | ||
import org.cryptomator.cryptolib.common.CipherSupplier; | ||
import org.cryptomator.cryptolib.common.DestroyableSecretKey; | ||
import org.cryptomator.cryptolib.common.ObjectPool; | ||
|
||
import javax.crypto.AEADBadTagException; | ||
import javax.crypto.BadPaddingException; | ||
import javax.crypto.Cipher; | ||
import javax.crypto.IllegalBlockSizeException; | ||
import javax.crypto.ShortBufferException; | ||
import javax.crypto.spec.GCMParameterSpec; | ||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.security.SecureRandom; | ||
|
||
import static org.cryptomator.cryptolib.v3.Constants.CHUNK_SIZE; | ||
import static org.cryptomator.cryptolib.v3.Constants.GCM_NONCE_SIZE; | ||
import static org.cryptomator.cryptolib.v3.Constants.GCM_TAG_SIZE; | ||
import static org.cryptomator.cryptolib.v3.Constants.PAYLOAD_SIZE; | ||
|
||
class FileContentCryptorImpl implements FileContentCryptor { | ||
|
||
private final SecureRandom random; | ||
|
||
FileContentCryptorImpl(SecureRandom random) { | ||
this.random = random; | ||
} | ||
|
||
@Override | ||
public boolean canSkipAuthentication() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public int cleartextChunkSize() { | ||
return PAYLOAD_SIZE; | ||
} | ||
|
||
@Override | ||
public int ciphertextChunkSize() { | ||
return CHUNK_SIZE; | ||
} | ||
|
||
@Override | ||
public ByteBuffer encryptChunk(ByteBuffer cleartextChunk, long chunkNumber, FileHeader header) { | ||
ByteBuffer ciphertextChunk = ByteBuffer.allocate(CHUNK_SIZE); | ||
encryptChunk(cleartextChunk, ciphertextChunk, chunkNumber, header); | ||
ciphertextChunk.flip(); | ||
return ciphertextChunk; | ||
} | ||
|
||
@Override | ||
public void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long chunkNumber, FileHeader header) { | ||
if (cleartextChunk.remaining() <= 0 || cleartextChunk.remaining() > PAYLOAD_SIZE) { | ||
throw new IllegalArgumentException("Invalid cleartext chunk size: " + cleartextChunk.remaining() + ", expected range [1, " + PAYLOAD_SIZE + "]"); | ||
} | ||
if (ciphertextChunk.remaining() < CHUNK_SIZE) { | ||
throw new IllegalArgumentException("Invalid cipehrtext chunk size: " + ciphertextChunk.remaining() + ", must fit up to " + CHUNK_SIZE + " bytes."); | ||
overheadhunter marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
FileHeaderImpl headerImpl = FileHeaderImpl.cast(header); | ||
encryptChunk(cleartextChunk, ciphertextChunk, chunkNumber, headerImpl.getNonce(), headerImpl.getContentKey()); | ||
} | ||
|
||
@Override | ||
public ByteBuffer decryptChunk(ByteBuffer ciphertextChunk, long chunkNumber, FileHeader header, boolean authenticate) throws AuthenticationFailedException { | ||
// FileHeaderImpl.Payload.SIZE + GCM_TAG_SIZE is required to fix a bug in Android API level pre 29, see https://issuetracker.google.com/issues/197534888 and #35 | ||
ByteBuffer cleartextChunk = ByteBuffer.allocate(PAYLOAD_SIZE + GCM_TAG_SIZE); | ||
decryptChunk(ciphertextChunk, cleartextChunk, chunkNumber, header, authenticate); | ||
cleartextChunk.flip(); | ||
return cleartextChunk; | ||
} | ||
|
||
@Override | ||
public void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, long chunkNumber, FileHeader header, boolean authenticate) throws AuthenticationFailedException { | ||
if (ciphertextChunk.remaining() < GCM_NONCE_SIZE + GCM_TAG_SIZE || ciphertextChunk.remaining() > CHUNK_SIZE) { | ||
throw new IllegalArgumentException("Invalid ciphertext chunk size: " + ciphertextChunk.remaining() + ", expected range [" + (GCM_NONCE_SIZE + GCM_TAG_SIZE) + ", " + CHUNK_SIZE + "]"); | ||
} | ||
if (cleartextChunk.remaining() < PAYLOAD_SIZE) { | ||
throw new IllegalArgumentException("Invalid cleartext chunk size: " + cleartextChunk.remaining() + ", must fit up to " + PAYLOAD_SIZE + " bytes."); | ||
} | ||
if (!authenticate) { | ||
throw new UnsupportedOperationException("authenticate can not be false"); | ||
} | ||
FileHeaderImpl headerImpl = FileHeaderImpl.cast(header); | ||
decryptChunk(ciphertextChunk, cleartextChunk, chunkNumber, headerImpl.getNonce(), headerImpl.getContentKey()); | ||
} | ||
|
||
// visible for testing | ||
void encryptChunk(ByteBuffer cleartextChunk, ByteBuffer ciphertextChunk, long chunkNumber, byte[] headerNonce, DestroyableSecretKey fileKey) { | ||
try (DestroyableSecretKey fk = fileKey.copy()) { | ||
// nonce: | ||
byte[] nonce = new byte[GCM_NONCE_SIZE]; | ||
random.nextBytes(nonce); | ||
|
||
// payload: | ||
try (ObjectPool.Lease<Cipher> cipher = CipherSupplier.AES_GCM.encryptionCipher(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { | ||
final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); | ||
cipher.get().updateAAD(chunkNumberBigEndian); | ||
cipher.get().updateAAD(headerNonce); | ||
ciphertextChunk.put(nonce); | ||
assert ciphertextChunk.remaining() >= cipher.get().getOutputSize(cleartextChunk.remaining()); | ||
cipher.get().doFinal(cleartextChunk, ciphertextChunk); | ||
} | ||
} catch (ShortBufferException e) { | ||
throw new IllegalStateException("Buffer allocated for reported output size apparently not big enough.", e); | ||
} catch (IllegalBlockSizeException | BadPaddingException e) { | ||
throw new IllegalStateException("Unexpected exception during GCM encryption.", e); | ||
} | ||
} | ||
|
||
// visible for testing | ||
void decryptChunk(ByteBuffer ciphertextChunk, ByteBuffer cleartextChunk, long chunkNumber, byte[] headerNonce, DestroyableSecretKey fileKey) throws AuthenticationFailedException { | ||
assert ciphertextChunk.remaining() >= GCM_NONCE_SIZE + GCM_TAG_SIZE; | ||
|
||
try (DestroyableSecretKey fk = fileKey.copy()) { | ||
// nonce: | ||
final byte[] nonce = new byte[GCM_NONCE_SIZE]; | ||
ciphertextChunk.get(nonce, 0, GCM_NONCE_SIZE); | ||
|
||
// payload: | ||
final ByteBuffer payloadBuf = ciphertextChunk.duplicate(); | ||
payloadBuf.position(GCM_NONCE_SIZE); | ||
assert payloadBuf.remaining() >= GCM_TAG_SIZE; | ||
|
||
// payload: | ||
try (ObjectPool.Lease<Cipher> cipher = CipherSupplier.AES_GCM.decryptionCipher(fk, new GCMParameterSpec(GCM_TAG_SIZE * Byte.SIZE, nonce))) { | ||
final byte[] chunkNumberBigEndian = longToBigEndianByteArray(chunkNumber); | ||
cipher.get().updateAAD(chunkNumberBigEndian); | ||
cipher.get().updateAAD(headerNonce); | ||
assert cleartextChunk.remaining() >= cipher.get().getOutputSize(payloadBuf.remaining()); | ||
cipher.get().doFinal(payloadBuf, cleartextChunk); | ||
} | ||
} catch (AEADBadTagException e) { | ||
throw new AuthenticationFailedException("Content tag mismatch.", e); | ||
} catch (ShortBufferException e) { | ||
throw new IllegalStateException("Buffer allocated for reported output size apparently not big enough.", e); | ||
} catch (IllegalBlockSizeException | BadPaddingException e) { | ||
throw new IllegalStateException("Unexpected exception during GCM decryption.", e); | ||
} | ||
} | ||
|
||
private byte[] longToBigEndianByteArray(long n) { | ||
return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.BIG_ENDIAN).putLong(n).array(); | ||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.