Skip to content

Commit

Permalink
Import Minio signing classes
Browse files Browse the repository at this point in the history
- Copied signing classes from Minio: https://github.com/minio/minio-java
- Added Notice file for Minio
- Replaced OkHttp, etc. classes with emulated equivalents
- Added an initial test for a simple `aws s3 ls` based on this doc: https://min.io/docs/minio/linux/integrations/aws-cli-with-minio.html

Closes #5
  • Loading branch information
Randgalt committed May 16, 2024
1 parent 68187a0 commit 023778f
Show file tree
Hide file tree
Showing 18 changed files with 1,227 additions and 9 deletions.
1 change: 1 addition & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This product includes software developed by Minio. (https://github.com/minio/minio-java)
38 changes: 37 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@

<air.java.version>22.0.0</air.java.version>
<air.main.basedir>${project.basedir}</air.main.basedir>
<air.modernizer.java-version>8</air.modernizer.java-version>
<air.check.skip-spotbugs>true</air.check.skip-spotbugs>
<air.check.skip-pmd>true</air.check.skip-pmd>
<air.modernizer.java-version>8</air.modernizer.java-version>
<air.check.skip-modernizer>true</air.check.skip-modernizer>

<dep.airlift.version>245</dep.airlift.version>
<dep.aws-sdk.version>2.25.32</dep.aws-sdk.version>
</properties>

<dependencyManagement>
Expand All @@ -56,6 +58,40 @@
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${dep.aws-sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<configuration>
<licenseSets>
<licenseSet>
<excludes>
<exclude>**/io/trino/s3/proxy/server/signing/minio/*</exclude>
</excludes>
</licenseSet>
</licenseSets>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<excludes>**/io/trino/s3/proxy/server/signing/minio/*</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
6 changes: 6 additions & 0 deletions trino-s3-proxy/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.s3.proxy.server;
package io.trino.s3.proxy.server.signing;

import org.junit.jupiter.api.Test;
import java.util.Optional;

public class DummyTest
public interface CredentialsController
{
@Test
public void testDummy()
{
// stub test for now
}
Optional<String> secretKeyForAccessKey(String accessKey);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.s3.proxy.server.signing;

import com.google.inject.Inject;
import io.trino.s3.proxy.server.signing.minio.Signer;
import io.trino.s3.proxy.server.signing.minio.emulation.MinioRequest;
import io.trino.s3.proxy.server.signing.minio.emulation.MinioUrl;
import jakarta.ws.rs.core.MultivaluedMap;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

import static java.util.Objects.requireNonNull;

public class SigningController
{
private final CredentialsController credentialsController;

@Inject
public SigningController(CredentialsController credentialsController)
{
this.credentialsController = requireNonNull(credentialsController, "credentialsController is null");
}

public Map<String, String> signedRequestHeaders(String method, MultivaluedMap<String, String> requestHeaders, String encodedPath, String encodedQuery, String region, String accessKey)
{
// TODO
String secretKey = credentialsController.secretKeyForAccessKey(accessKey).orElseThrow();

MinioUrl minioUrl = MinioUrl.build(encodedPath, encodedQuery);
MinioRequest minioRequest = MinioRequest.build(requestHeaders, method, minioUrl);

// TODO
String sha256 = minioRequest.headerValue("x-amz-content-sha256").orElseThrow();

try {
return Signer.signV4S3(minioRequest, region, accessKey, secretKey, sha256).headers();
}
catch (NoSuchAlgorithmException | InvalidKeyException e) {
// TODO
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.trino.s3.proxy.server.signing.minio;

import com.google.common.io.BaseEncoding;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Locale;

/** Various global static functions used. */
public class Digest {
// MD5 hash of zero length byte array.
public static final String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg==";
// SHA-256 hash of zero length byte array.
public static final String ZERO_SHA256_HASH =
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";

/** Private constructor. */
private Digest() {}

/** Returns MD5 hash of byte array. */
public static String md5Hash(byte[] data, int length) throws NoSuchAlgorithmException {
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
md5Digest.update(data, 0, length);
return Base64.getEncoder().encodeToString(md5Digest.digest());
}

/** Returns SHA-256 hash of byte array. */
public static String sha256Hash(byte[] data, int length) throws NoSuchAlgorithmException {
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
sha256Digest.update((byte[]) data, 0, length);
return BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US);
}

/** Returns SHA-256 hash of given string. */
public static String sha256Hash(String string) throws NoSuchAlgorithmException {
byte[] data = string.getBytes(StandardCharsets.UTF_8);
return sha256Hash(data, data.length);
}

/**
* Returns SHA-256 and MD5 hashes of given data and it's length.
*
* @param data must be {@link RandomAccessFile}, {@link BufferedInputStream} or byte array.
* @param len length of data to be read for hash calculation.
* @deprecated This method is no longer supported.
*/
@Deprecated
public static String[] sha256Md5Hashes(Object data, int len)
throws NoSuchAlgorithmException, IOException, InsufficientDataException, InternalException {
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
MessageDigest md5Digest = MessageDigest.getInstance("MD5");

if (data instanceof BufferedInputStream || data instanceof RandomAccessFile) {
updateDigests(data, len, sha256Digest, md5Digest);
} else if (data instanceof byte[]) {
sha256Digest.update((byte[]) data, 0, len);
md5Digest.update((byte[]) data, 0, len);
} else {
throw new InternalException(
"Unknown data source to calculate SHA-256 hash. This should not happen, "
+ "please report this issue at https://github.com/minio/minio-java/issues",
null);
}

return new String[] {
BaseEncoding.base16().encode(sha256Digest.digest()).toLowerCase(Locale.US),
BaseEncoding.base64().encode(md5Digest.digest())
};
}

/** Updated MessageDigest with bytes read from file and stream. */
private static int updateDigests(
Object inputStream, int len, MessageDigest sha256Digest, MessageDigest md5Digest)
throws IOException, InsufficientDataException {
RandomAccessFile file = null;
BufferedInputStream stream = null;
if (inputStream instanceof RandomAccessFile) {
file = (RandomAccessFile) inputStream;
} else if (inputStream instanceof BufferedInputStream) {
stream = (BufferedInputStream) inputStream;
}

// hold current position of file/stream to reset back to this position.
long pos = 0;
if (file != null) {
pos = file.getFilePointer();
} else {
stream.mark(len);
}

// 16KiB buffer for optimization
byte[] buf = new byte[16384];
int bytesToRead = buf.length;
int bytesRead = 0;
int totalBytesRead = 0;
while (totalBytesRead < len) {
if ((len - totalBytesRead) < bytesToRead) {
bytesToRead = len - totalBytesRead;
}

if (file != null) {
bytesRead = file.read(buf, 0, bytesToRead);
} else {
bytesRead = stream.read(buf, 0, bytesToRead);
}

if (bytesRead < 0) {
// reached EOF
throw new InsufficientDataException(
"Insufficient data. bytes read " + totalBytesRead + " expected " + len);
}

if (bytesRead > 0) {
if (sha256Digest != null) {
sha256Digest.update(buf, 0, bytesRead);
}

if (md5Digest != null) {
md5Digest.update(buf, 0, bytesRead);
}

totalBytesRead += bytesRead;
}
}

// reset back to saved position.
if (file != null) {
file.seek(pos);
} else {
stream.reset();
}

return totalBytesRead;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.trino.s3.proxy.server.signing.minio;

/**
* Thrown to indicate that reading given InputStream gets EOFException before reading given length.
*/
public class InsufficientDataException extends MinioException {
private static final long serialVersionUID = -1619719290805056566L;

/** Constructs a new InsufficientDataException with given error message. */
public InsufficientDataException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.trino.s3.proxy.server.signing.minio;

/**
* Thrown to indicate that unexpected internal library error occured while processing given request.
*/
public class InternalException extends MinioException {
private static final long serialVersionUID = 138336287983212416L;

/** Constructs a new InternalException with given error message. */
public InternalException(String message, String httpTrace) {
super(message, httpTrace);
}
}
Loading

0 comments on commit 023778f

Please sign in to comment.