Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,6 @@ protected boolean validateAWSDocument(final String provider, AWSAttestationData
StringBuilder privateIp, StringBuilder errMsg) {

final String document = info.getDocument();
if (!awsUtils.validateAWSSignature(document, info.getSignature(), errMsg)) {
return false;
}

// convert our document into a struct that we can extract data

Expand All @@ -198,8 +195,15 @@ protected boolean validateAWSDocument(final String provider, AWSAttestationData
LOGGER.error("Identity Document: {}", document);
return false;
}

if (!validateAWSProvider(provider, instanceDocument.getString(ATTR_REGION), errMsg)) {

// now validate our signature

final String region = instanceDocument.getString(ATTR_REGION);
if (!awsUtils.validateAWSSignature(document, info.getSignature(), region, errMsg)) {
return false;
}

if (!validateAWSProvider(provider, region, errMsg)) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,124 @@

import com.yahoo.athenz.auth.util.Crypto;
import com.yahoo.athenz.auth.util.CryptoException;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

public class InstanceAWSUtils {

private static final Logger LOGGER = LoggerFactory.getLogger(InstanceAWSUtils.class);

static final String AWS_PROP_PUBLIC_CERT = "athenz.zts.aws_public_cert";
static final String AWS_PROP_PUBLIC_CERT = "athenz.zts.aws_public_cert";
static final String AWS_PROP_PUBLIC_CERT_PER_REGION = "athenz.zts.aws_public_cert_per_region";

PublicKey awsPublicKey = null; // AWS public key for validating instance documents
// AWS public key for validating instance documents
PublicKey awsPublicKey = null;
// AWS public keys for validating instance documents per region
Map<String, PublicKey> awsPublicKeyRegionMap = new HashMap<>();

public InstanceAWSUtils() {

// the default AWS public cert file is specified for backward compatibility
// the preference would be to use the new per-region certs

final String awsCertFileName = System.getProperty(AWS_PROP_PUBLIC_CERT, "");
if (!awsCertFileName.isEmpty()) {
File awsCertFile = new File(awsCertFileName);
X509Certificate awsCert = Crypto.loadX509Certificate(awsCertFile);
awsPublicKey = awsCert.getPublicKey();
awsPublicKey = loadPublicKey(awsCertFileName);

// the format for the region cert files is <region>:<filename>[,<region>:<filename>...]
// e.g. us-east-1:/path/to/us-east-1.pem,us-west-2:/path/to/us-west-2.pem

final String awsPerRegionCertFiles = System.getProperty(AWS_PROP_PUBLIC_CERT_PER_REGION, "");
if (!awsPerRegionCertFiles.isEmpty()) {
String[] regionCertFiles = awsPerRegionCertFiles.split(",");
for (String regionCertFile : regionCertFiles) {
String[] parts = regionCertFile.split(":");
if (parts.length != 2) {
LOGGER.error("Invalid AWS public cert per region format: {}", regionCertFile);
continue;
}
PublicKey publicKey = loadPublicKey(parts[1].trim());
if (publicKey != null) {
awsPublicKeyRegionMap.put(parts[0].trim(), publicKey);
}
}
}

if (awsPublicKey == null) {
if (awsPublicKey == null && awsPublicKeyRegionMap.isEmpty()) {
LOGGER.error("AWS Public Key not specified - no instance requests will be authorized");
}
}

public boolean validateAWSSignature(final String document, final String signature, StringBuilder errMsg) {
/**
* Load the AWS public key from the specified certificate file.
*
* @param certFileName the name of the certificate file
* @return the PublicKey object, or null if unable to load
*/
PublicKey loadPublicKey(final String certFileName) {
if (certFileName.isEmpty()) {
return null;
}
try {
File certFile = new File(certFileName);
X509Certificate awsCert = Crypto.loadX509Certificate(certFile);
return awsCert.getPublicKey();
} catch (Exception ex) {
LOGGER.error("Unable to load AWS public cert from filename: {}, error: {}", certFileName, ex.getMessage());
}
return null;
}

/**
* Validate the AWS instance document signature.
*
* @param document the instance document
* @param signature the signature to validate
* @param region the AWS region of the instance
* @param errMsg StringBuilder to append error messages
* @return true if the signature is valid, false otherwise
*/
public boolean validateAWSSignature(final String document, final String signature, final String region, StringBuilder errMsg) {

if (signature == null || signature.isEmpty()) {
errMsg.append("AWS instance document signature is empty");
return false;
}

if (awsPublicKey == null) {
// first we need to check if we have a public key for
// the specified region, if not then we use the default key

PublicKey publicKey = getAwsPublicKey(region);
if (publicKey == null) {
errMsg.append("AWS Public key is not available");
return false;
}

boolean valid = false;
try {
valid = Crypto.validatePKCS7Signature(document, signature, awsPublicKey);
valid = Crypto.validatePKCS7Signature(document, signature, publicKey);
} catch (CryptoException ex) {
errMsg.append("verifyInstanceDocument: unable to verify AWS instance document: ");
errMsg.append(ex.getMessage());
}

return valid;
}

PublicKey getAwsPublicKey(final String region) {
PublicKey publicKey = null;
if (!StringUtil.isEmpty(region)) {
publicKey = awsPublicKeyRegionMap.get(region);
}
if (publicKey == null) {
publicKey = awsPublicKey;
}
return publicKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ public void testInitializeDefaults() {
public void testInitialize() {

System.setProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT, "src/test/resources/aws_public.cert");
System.setProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT_PER_REGION,
"us-east-1:src/test/resources/aws_public_us_east_1.cert,us-east-2:src/test/resources/aws_public_us_east_2_invalid.cert,us-central-1");
InstanceAWSUtils utils = new InstanceAWSUtils();
assertNotNull(utils.awsPublicKey);
assertNotNull(utils.awsPublicKeyRegionMap.get("us-east-1"));
assertNull(utils.awsPublicKeyRegionMap.get("us-east-2"));
assertNull(utils.awsPublicKeyRegionMap.get("us-central-1"));
System.clearProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT);
System.clearProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT_PER_REGION);
}

@Test
Expand All @@ -47,29 +53,32 @@ public void testValidateAWSSignatureFailure() {
StringBuilder errMsg = new StringBuilder(256);

InstanceAWSUtils utils = new InstanceAWSUtils();
assertFalse(utils.validateAWSSignature("document", null, errMsg));
assertFalse(utils.validateAWSSignature("document", "", errMsg));
assertFalse(utils.validateAWSSignature("document", null, "us-west-2", errMsg));
assertFalse(utils.validateAWSSignature("document", "", "us-west-2", errMsg));

// aws public key is null
assertFalse(utils.validateAWSSignature("document", "signature", errMsg));
assertFalse(utils.validateAWSSignature("document", "signature", "us-west-2", errMsg));

System.setProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT, "src/test/resources/aws_public.cert");
utils = new InstanceAWSUtils();

assertFalse(utils.validateAWSSignature("document", "invalid-signature", errMsg));
assertFalse(utils.validateAWSSignature("document", "invalid-signature", "us-west-2", errMsg));
System.clearProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT);
}

@Test
public void testValidateAWSSignatureValid() {
StringBuilder errMsg = new StringBuilder(256);
System.setProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT, "src/test/resources/aws_public.cert");
System.setProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT_PER_REGION, "us-east-1:src/test/resources/aws_public_us_east_1.cert");
InstanceAWSUtils utils = new InstanceAWSUtils();
try(MockedStatic<Crypto> crypto = mockStatic(Crypto.class)) {
crypto.when(() -> Crypto.validatePKCS7Signature(any(), any(), any()))
.thenReturn(true);
assertTrue(utils.validateAWSSignature("document", "aaa", errMsg));
assertTrue(utils.validateAWSSignature("document", "aaa", "us-west-2", errMsg));
assertTrue(utils.validateAWSSignature("document", "aaa", "us-east-1", errMsg));
}
System.clearProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT);
System.clearProperty(InstanceAWSUtils.AWS_PROP_PUBLIC_CERT_PER_REGION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ void setSignatureResult(boolean value) {
}

@Override
public boolean validateAWSSignature(final String document, final String signature, StringBuilder errMsg) {
public boolean validateAWSSignature(final String document, final String signature, final String region, StringBuilder errMsg) {
return signatureResult;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE-----
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3