Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AmazonCloudFront-51e8b2d.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Amazon CloudFront",
"contributor": "",
"description": "Add support for ECDSA signed URLs."
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.net.URI;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.Immutable;
import software.amazon.awssdk.annotations.SdkPublicApi;
Expand Down Expand Up @@ -140,7 +141,7 @@ public SignedUrl getSignedUrlWithCannedPolicy(CannedSignerRequest request) {
try {
String resourceUrl = request.resourceUrl();
String cannedPolicy = SigningUtils.buildCannedPolicy(resourceUrl, request.expirationDate());
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(cannedPolicy.getBytes(UTF_8), request.privateKey());
byte[] signatureBytes = signPolicy(cannedPolicy.getBytes(UTF_8), request.privateKey());
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
URI uri = URI.create(resourceUrl);
String protocol = uri.getScheme();
Expand Down Expand Up @@ -266,7 +267,7 @@ public SignedUrl getSignedUrlWithCustomPolicy(CustomSignerRequest request) {
request.expirationDate(),
request.ipRange());

byte[] signatureBytes = SigningUtils.signWithSha1Rsa(policy.getBytes(UTF_8), request.privateKey());
byte[] signatureBytes = signPolicy(policy.getBytes(UTF_8), request.privateKey());
String urlSafePolicy = SigningUtils.makeStringUrlSafe(policy);
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
URI uri = URI.create(resourceUrl);
Expand Down Expand Up @@ -368,7 +369,7 @@ public CookiesForCannedPolicy getCookiesForCannedPolicy(Consumer<CannedSignerReq
public CookiesForCannedPolicy getCookiesForCannedPolicy(CannedSignerRequest request) {
try {
String cannedPolicy = SigningUtils.buildCannedPolicy(request.resourceUrl(), request.expirationDate());
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(cannedPolicy.getBytes(UTF_8), request.privateKey());
byte[] signatureBytes = signPolicy(cannedPolicy.getBytes(UTF_8), request.privateKey());
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
String expiry = String.valueOf(request.expirationDate().getEpochSecond());
return DefaultCookiesForCannedPolicy.builder()
Expand Down Expand Up @@ -469,7 +470,7 @@ public CookiesForCustomPolicy getCookiesForCustomPolicy(CustomSignerRequest requ
try {
String policy = SigningUtils.buildCustomPolicy(request.resourceUrl(), request.activeDate(), request.expirationDate(),
request.ipRange());
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(policy.getBytes(UTF_8), request.privateKey());
byte[] signatureBytes = signPolicy(policy.getBytes(UTF_8), request.privateKey());
String urlSafePolicy = SigningUtils.makeStringUrlSafe(policy);
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
return DefaultCookiesForCustomPolicy.builder()
Expand All @@ -482,4 +483,20 @@ public CookiesForCustomPolicy getCookiesForCustomPolicy(CustomSignerRequest requ
}
}

private static byte[] signPolicy(byte[] policyToSign, PrivateKey privateKey) throws InvalidKeyException {
// all CloudFront signed urls currently require the SHA1 and currently only support RSA and EC
switch (privateKey.getAlgorithm()) {
case "RSA":
return SigningUtils.signWithSha1Rsa(policyToSign, privateKey);
case "EC":
case "ECDSA":
return SigningUtils.signWithSha1ECDSA(policyToSign, privateKey);
default:
// do not attempt to use a generic Signer based on the privateKey algorithm:
// future supported key types likely require different hash algorithms (eg, SHA256 or higher instead of SHA1)
throw new IllegalArgumentException(
"Unsupported key algorithm for CloudFront signed URL: " + privateKey.getAlgorithm());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.List;
import java.util.regex.Pattern;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.services.cloudfront.internal.utils.SigningUtils;

@SdkInternalApi
public final class Pem {
Expand All @@ -51,16 +52,19 @@ public static PrivateKey readPrivateKey(InputStream is) throws InvalidKeySpecExc
for (PemObject object : objects) {
switch (object.getPemObjectType()) {
case PRIVATE_KEY_PKCS1:
// only supports RSA keys, so load it as RSA.
return Rsa.privateKeyFromPkcs1(object.getDerBytes());
case PRIVATE_KEY_PKCS8:
return Rsa.privateKeyFromPkcs8(object.getDerBytes());
return SigningUtils.privateKeyFromPkcs8(object.getDerBytes());
default:
break;
}
}
throw new IllegalArgumentException("Found no private key");
}



/**
* Returns the first public key that is found from the input stream of a PEM
* file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.cloudfront.internal.auth.Pem;
import software.amazon.awssdk.services.cloudfront.internal.auth.Rsa;
import software.amazon.awssdk.utils.IoUtils;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;
Expand Down Expand Up @@ -139,6 +142,22 @@ public static byte[] signWithSha1Rsa(byte[] dataToSign, PrivateKey privateKey) t
}
}

/**
* Signs the data given with the private key given, using the SHA1withECDSA
* algorithm provided by bouncy castle.
*/
public static byte[] signWithSha1ECDSA(byte[] dataToSign, PrivateKey privateKey) throws InvalidKeyException {
try {
Signature signature = Signature.getInstance("SHA1withECDSA");
SecureRandom random = new SecureRandom();
signature.initSign(privateKey, random);
signature.update(dataToSign);
return signature.sign();
} catch (NoSuchAlgorithmException | SignatureException e) {
throw new IllegalStateException(e);
}
}

/**
* Generate a policy document that describes custom access permissions to
* apply via a private distribution's signed URL.
Expand Down Expand Up @@ -198,10 +217,35 @@ public static PrivateKey loadPrivateKey(Path keyFile) throws Exception {
}
if (StringUtils.lowerCase(keyFile.toString()).endsWith(".der")) {
try (InputStream is = Files.newInputStream(keyFile)) {
return Rsa.privateKeyFromPkcs8(IoUtils.toByteArray(is));
return privateKeyFromPkcs8(IoUtils.toByteArray(is));
}
}
throw SdkClientException.create("Unsupported file type for private key");
}

/**
* Attempt to load a private key from PKCS8 DER
*/
public static PrivateKey privateKeyFromPkcs8(byte[] derBytes) {
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(derBytes);
try {
return tryKeyLoadFromSpec(privateKeySpec);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException("Invalid private key, unable to load as either RSA or ECDSA", e);
}
}

/**
* We don't have a way to determine which algorithm to use, so we try to load as RSA and EC
*/
private static PrivateKey tryKeyLoadFromSpec(EncodedKeySpec privateKeySpec)
throws NoSuchAlgorithmException, InvalidKeySpecException {
try {
return KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException rsaFail) {
return KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
}
}
}
Loading
Loading