Skip to content
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

SNOW-1858529 Implement FLOE #2006

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft

SNOW-1858529 Implement FLOE #2006

wants to merge 2 commits into from

Conversation

sfc-gh-pfus
Copy link
Contributor

Overview

SNOW-XXXXX

Pre-review self checklist

  • PR branch is updated with all the changes from master branch
  • The code is correctly formatted (run mvn -P check-style validate)
  • New public API is not unnecessary exposed (run mvn verify and inspect target/japicmp/japicmp.html)
  • The pull request name is prefixed with SNOW-XXXX:
  • Code is in compliance with internal logging requirements

External contributors - please answer these questions before submitting a pull request. Thanks!

  1. What GitHub issue is this PR addressing? Make sure that there is an accompanying issue to your PR.

    Issue: #NNNN

  2. Fill out the following pre-review checklist:

    • I am adding a new automated test(s) to verify correctness of my new code
    • I am adding new logging messages
    • I am modifying authorization mechanisms
    • I am adding new credentials
    • I am modifying OCSP code
    • I am adding a new dependency or upgrading an existing one
    • I am adding new public/protected component not marked with @SnowflakeJdbcInternalApi (note that public/protected methods/fields in classes marked with this annotation are already internal)
  3. Please describe how your code solves the related issue.

    Please write a short description of how your code change solves the related issue.

@sfc-gh-pfus sfc-gh-pfus changed the title SNOW-1858529 Implement header handling in FLOE SNOW-1858529 Implement FLOE Dec 20, 2024
@sfc-gh-pfus sfc-gh-pfus force-pushed the SNOW-1858529-floe branch 2 times, most recently from dd4ff7d to 52d061f Compare December 20, 2024 13:06
Copy link

@sfc-gh-grubin sfc-gh-grubin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some quick early feedback so it's easy to incorporate if needed.

As a general note, I suspect this implementation will be less performant than the reference implementation due to extra byte copies and such. We'll want to measure and profile it to see how much we care.

@@ -41,8 +41,7 @@ public class EncryptionProvider {
private static final String FILE_CIPHER = "AES/CBC/PKCS5Padding";
private static final String KEY_CIPHER = "AES/ECB/PKCS5Padding";
private static final int BUFFER_SIZE = 2 * 1024 * 1024; // 2 MB
private static ThreadLocal<SecureRandom> secRnd =
new ThreadLocal<>().withInitial(SecureRandom::new);
private static ThreadLocal<SecureRandom> secRnd = ThreadLocal.withInitial(SecureRandom::new);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this final. No reason not to. Also, as it is now static final it should probably also BE_IN_CAPS.

import net.snowflake.client.jdbc.MatDesc;
import net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;

class GcmEncryptionProvider {
@SnowflakeJdbcInternalApi
public class GcmEncryptionProvider {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ever being shipped? I thought we designed FLOE because we determined GCM wouldn't work?

@@ -99,7 +99,7 @@ private static CipherInputStream encryptContent(
NoSuchAlgorithmException {
SecretKey fileKey = new SecretKeySpec(keyBytes, 0, keyBytes.length, AES);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NitPick

Suggested change
SecretKey fileKey = new SecretKeySpec(keyBytes, 0, keyBytes.length, AES);
SecretKey fileKey = new SecretKeySpec(keyBytes, AES);

Cipher fileCipher = Cipher.getInstance(FILE_CIPHER);
Cipher fileCipher = Cipher.getInstance(JCE_CIPHER_NAME);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this method use decryptContentFromStream()?

Also, decrypting the file in place seems risky. It would be far safer to decrypt to a temporary file and then rename the temporary file over the input file only upon completion/success.

private final byte[] aad;

FloeAad(byte[] aad) {
this.aad = Optional.ofNullable(aad).orElse(new byte[0]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NitPick: It's often better to have a hidden constant EMPTY_ARRAY = new byte[0] because otherwise creating and managing these empty arrays is unneeded GC pressure.

byte[] headerTag =
keyDerivator.hkdfExpand(
this.floeKey, floeIv, this.floeAad, HeaderTagFloePurpose.INSTANCE, headerTagLength);
if (!Arrays.equals(headerTag, headerTagFromHeader)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is insecure. You must use a constant-time method for this comparison. This is documented in the primary FLOE document and pseudo-code:

assert ExpectedHeaderTag == HeaderTag // Must be constant time

Please use MessageDigest.isEqual() instead.

verifySegmentSizeMarker(inputBuf);
AeadKey aeadKey = getKey(floeKey, floeIv, floeAad, segmentCounter);
AeadIv aeadIv = AeadIv.from(inputBuf, parameterSpec.getAead().getIvLength());
AeadAad aeadAad = AeadAad.nonTerminal(segmentCounter++);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NitPick: The psuedo-code only increments the counter after successful decryption while yours increments it before. This does mean that the reference implementation and the current JDBC implementation will act differently after a failed decryption if decryption attempts continue.

The reference implementation acts as if the failed decryption were never attempted (because state doesn't change). Yours increments the counter and thus cannot return decryption of the same segment.

I note this as a nitpick because it isn't actually a problem, but it is a deviation from the provided algorithm description.

AeadIv aeadIv =
AeadIv.generateRandom(
parameterSpec.getFloeRandom(), parameterSpec.getAead().getIvLength());
AeadAad aeadAad = AeadAad.nonTerminal(segmentCounter++);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nitpick as above related to where the counter is incremented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants