Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ebourg/jsign
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 7.1
Choose a base ref
...
head repository: ebourg/jsign
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 3 commits
  • 16 files changed
  • 2 contributors

Commits on Feb 14, 2025

  1. Set the next version to 7.2

    ebourg committed Feb 14, 2025
    Copy the full SHA
    6db9112 View commit details

Commits on Mar 22, 2025

  1. Also query ECS container creds endpoint for KMS when no storepass is set

    Issue #147 and its linked PR made it much more ergonomic to use `jsign`
    within an AWS EC2 instance, but as highlighted in the latter comments on
    that issue, it's not helpful when running ´jsign´ in the context of AWS
    Elastic Container Service (ECS) based products, such as AWS Fargate,
    AWS Codebuild, and AWS Greengrass. The precise technical reason for this
    is that ECS environments don't have that EC2 IMDSv2 endpoint exposed to
    them, so they can't use it.
    
    [As the official AWS SDK documentation
    states](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html),
    the official SDK handles this situation by fetching credentials from a
    specific ECS container credentials endpoint, which doesn't share any host
    or path with IMDSv2, on the `ContainerCredentialsProvider` class.
    
    Roughly, the official container credentials provider works as follows:
    
    - If the `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment variable
      is set, it concatenates it with a well-known base URI,
      `http://169.254.170.2`, configurable through the
      [`AWS_CONTAINER_SERVICE_ENDPOINT`](https://github.com/aws/aws-sdk-java-v2/blob/186b433dd32366b095c8bc415342accb8b734cf7/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkSystemSetting.java#L112)
      parameter. (In production deployments, such URI can be considered a
      constant; changing it is mainly useful for testing purposes.) Then, it
      sends a HTTP GET request to the resulting endpoint, which is expected
      to return a JSON object in its body with the AWS credentials for the
      container. Optionally, an `Authorization` header in the request may be
      set to the value of either the `AWS_CONTAINER_AUTHORIZATION_TOKEN` or
      `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE` environment variables, if any
      of those are set.
    - Else, if the `AWS_CONTAINER_CREDENTIALS_FULL_URI` environment variable
      is set, the same request as above is sent to the endpoint determined
      by this variable, without any modification. The contents and handling
      of the response to this request are the same as above.
    
    Experimentally, I've found AWS Fargate to only set
    `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`, without any authorization
    token. But the AWS documentation suggests that AWS EKS may make use of
    the full URI and token environment variables, so it's fair to say
    that supporting them is also useful in practice.
    
    To improve on the situation, these changes implement a lightweight but
    feature-complete ECS container credentials client in ´jsign´. This
    credentials client attempts to fetch credentials before its counterpart
    for IMDSv2, which guarantees that both EC2 and ECS environments are
    handled appropriately without any manual configuration, following the
    same logic as the AWS SDK. If neither credentials provider worked, the
    user is instructed to manually pass AWS credentials as before, and the
    resulting exception stack trace contains details on why both providers
    failed.
    
    I've tested this custom client logic to work successfully on a real AWS
    Fargate deployment. The documentation was also updated to reflect this
    new provider, and unit tests for it added.
    AlexTMjugador committed Mar 22, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    AlexTMjugador Alejandro González
    Copy the full SHA
    c0cb9fa View commit details
  2. Changelog & credits update

    ebourg committed Mar 22, 2025
    Copy the full SHA
    535387c View commit details
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -54,6 +54,10 @@ See https://ebourg.github.io/jsign for more information.

## Changes

#### Version 7.2 (in development)

* ECS container credentials are now supported when signing with AWS KMS (contributed by Alejandro González)

#### Version 7.1 (2025-02-14)

* New signing service: SignPath
7 changes: 4 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -710,7 +710,8 @@ <h4 id="example-awskms">Signing with AWS Key Management Service</h4>

<p>The AWS access key, secret key, and optionally the session token, are concatenated
and used as the <code>storepass</code> parameter; if the latter is not provided, Jsign attempts to fetch the credentials
from the environment variables (<code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code> and <code>AWS_SESSION_TOKEN</code>)
from the environment variables (<code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code> and <code>AWS_SESSION_TOKEN</code>),
from the <a href="https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html">ECS container credentials endpoint</a>,
or from the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html">IMDSv2</a> service
when running on an AWS EC2 instance.</p>

@@ -1121,8 +1122,8 @@ <h3 id="credits">Credits</h3>
MSI signing was possible thanks to the work done by the <a href="https://github.com/mtrojnar/osslsigncode">osslsigncode</a> and <a href="https://poi.apache.org/">Apache POI</a> projects.</p>

<p>Jsign includes contributions from Emmanuel Bourg, Florent Daigniere, Michael Szediwy, Michael Peterson, Markus Kilås,
Erwin Tratar, Björn Kautler, Joseph Lee, Maria Merkel, Vincent Malmedy, Sebastian Müller, Sebastian Stamm
and Eatay Mizrachi.</p>
Erwin Tratar, Björn Kautler, Joseph Lee, Maria Merkel, Vincent Malmedy, Sebastian Müller, Sebastian Stamm,
Eatay Mizrachi and Alejandro González.</p>

<h3 id="contacts">Contact</h3>

4 changes: 2 additions & 2 deletions jsign-ant/pom.xml
Original file line number Diff line number Diff line change
@@ -6,11 +6,11 @@
<parent>
<groupId>net.jsign</groupId>
<artifactId>jsign-parent</artifactId>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Jsign - Authenticode signing in Java (Ant Task)</name>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
4 changes: 2 additions & 2 deletions jsign-cli/pom.xml
Original file line number Diff line number Diff line change
@@ -6,11 +6,11 @@
<parent>
<groupId>net.jsign</groupId>
<artifactId>jsign-parent</artifactId>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Jsign - Authenticode signing in Java (Command Line Tool)</name>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
4 changes: 2 additions & 2 deletions jsign-core/pom.xml
Original file line number Diff line number Diff line change
@@ -6,11 +6,11 @@
<parent>
<groupId>net.jsign</groupId>
<artifactId>jsign-parent</artifactId>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Jsign - Authenticode signing in Java (Core)</name>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
4 changes: 2 additions & 2 deletions jsign-crypto/pom.xml
Original file line number Diff line number Diff line change
@@ -6,11 +6,11 @@
<parent>
<groupId>net.jsign</groupId>
<artifactId>jsign-parent</artifactId>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Jsign - Authenticode signing in Java (Crypto)</name>
<version>7.1</version>
<version>7.2-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
8 changes: 5 additions & 3 deletions jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;

import javax.smartcardio.CardException;

import net.jsign.jca.AmazonCredentials;
@@ -294,7 +295,8 @@ Set<String> getAliases(KeyStore keystore) throws KeyStoreException {
* <p>The AWS access key, secret key, and optionally the session token, are concatenated and used as
* the storepass parameter; if the latter is not provided, Jsign attempts to fetch the credentials from
* the environment variables (<code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code> and
* <code>AWS_SESSION_TOKEN</code>) or from the IMDSv2 service when running on an AWS EC2 instance.</p>
* <code>AWS_SESSION_TOKEN</code>), from the ECS container credentials endpoint, or from the IMDSv2
* service when running on an AWS EC2 instance.</p>
*
* <p>In any case, the credentials must allow the following actions: <code>kms:ListKeys</code>,
* <code>kms:DescribeKey</code> and <code>kms:Sign</code>.</p>
@@ -321,9 +323,9 @@ Provider getProvider(KeyStoreBuilder params) {
} catch (UnknownServiceException e) {
throw new IllegalArgumentException("storepass " + params.parameterName()
+ " must specify the AWS credentials: <accessKey>|<secretKey>[|<sessionToken>]"
+ ", when not running from an EC2 instance (" + e.getMessage() + ")", e);
+ ", when not running from ECS or an EC2 instance", e);
} catch (IOException e) {
throw new RuntimeException("An error occurred while fetching temporary credentials from IMDSv2 service", e);
throw new RuntimeException("Failed fetching temporary credentials from ECS and IMDSv2 services", e);
}
}

21 changes: 15 additions & 6 deletions jsign-crypto/src/main/java/net/jsign/jca/AmazonCredentials.java
Original file line number Diff line number Diff line change
@@ -70,25 +70,34 @@ public static AmazonCredentials parse(String credentials) throws IllegalArgument
* <ul>
* <li>The environment variables <tt>AWS_ACCESS_KEY_ID</tt> (or <tt>AWS_ACCESS_KEY</tt>),
* <tt>AWS_SECRET_KEY</tt> (or <tt>AWS_SECRET_ACCESS_KEY</tt>) and <tt>AWS_SESSION_TOKEN</tt></li>
* <li>The ECS container credentials service (ECS, EKS, Greengrass, Fargate)</li>
* <li>The EC2 instance metadata service (IMDSv2)</li>
* </ul>
*/
public static AmazonCredentials getDefault() throws IOException {
if (getenv("AWS_ACCESS_KEY_ID") != null || getenv("AWS_ACCESS_KEY") != null) {
String accesKey = getenv("AWS_ACCESS_KEY_ID");
if (accesKey == null) {
accesKey = getenv("AWS_ACCESS_KEY");
String accessKey = getenv("AWS_ACCESS_KEY_ID");
if (accessKey == null) {
accessKey = getenv("AWS_ACCESS_KEY");
}
String secretKey = getenv("AWS_SECRET_KEY");
if (secretKey == null) {
secretKey = getenv("AWS_SECRET_ACCESS_KEY");
}
String sessionToken = getenv("AWS_SESSION_TOKEN");

return new AmazonCredentials(accesKey, secretKey, sessionToken);

return new AmazonCredentials(accessKey, secretKey, sessionToken);
} else {
return new AmazonIMDS2Client().getCredentials();
try {
return new AmazonECSCredentialsClient().getCredentials();
} catch (IOException ecsException) {
try {
return new AmazonIMDS2Client().getCredentials();
} catch (IOException imds2Exception) {
ecsException.addSuppressed(imds2Exception);
throw ecsException;
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2025 Alejandro González
*
* 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 net.jsign.jca;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownServiceException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import com.cedarsoftware.util.io.JsonIoException;
import com.cedarsoftware.util.io.JsonReader;

/**
* Client to query the Elastic Container Service (ECS) credential metadata
* endpoint for containers running in AWS.
*
* @since 7.2
* @see
* <a href="https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html">Using
* the Amazon ECS container credentials provider</a>
* @see
* <a href="https://github.com/aws/aws-sdk-java-v2/blob/master/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java">ContainerCredentialsProvider</a>
*/
class AmazonECSCredentialsClient {

private static final URL DEFAULT_AWS_CONTAINER_SERVICE_ENDPOINT;

private final URL endpoint;

static {
try {
DEFAULT_AWS_CONTAINER_SERVICE_ENDPOINT = new URL("http://169.254.170.2");
} catch (MalformedURLException e) {
throw new AssertionError("Invalid default URI for AWS container credential metadata endpoint", e);
}
}

/**
* Creates a new client to query the ECS credential metadata endpoint, using
* the endpoint URL provided by the environment variables
* {@code AWS_CONTAINER_CREDENTIALS_RELATIVE_URI} or
* {@code AWS_CONTAINER_CREDENTIALS_FULL_URI}.
*
* @throws UnknownServiceException If no valid ECS endpoint URL is
* available.
*/
AmazonECSCredentialsClient() throws UnknownServiceException {
this(defaultCredentialsUrl());
}

/**
* Creates a new client to query the ECS credential metadata endpoint, using
* the specified endpoint URL.
*
* @param endpoint The URL of the ECS credential metadata endpoint.
* @throws IllegalArgumentException If the endpoint URL is null or has an
* unexpected protocol.
*/
AmazonECSCredentialsClient(URL endpoint) {
if (endpoint == null || (!"http".equals(endpoint.getProtocol()) && !"https".equals(endpoint.getProtocol()))) {
throw new IllegalArgumentException(
"Null endpoint or unexpected protocol for AWS container credential metadata endpoint: " + endpoint
);
}

this.endpoint = endpoint;
}

/**
* Queries the ECS credential metadata endpoint to obtain the credentials
* for the container.
*/
public AmazonCredentials getCredentials() throws IOException {
HttpURLConnection connection = (HttpURLConnection) this.endpoint.openConnection();
connection.setConnectTimeout(3000);
connection.setReadTimeout(3000);

String authToken = System.getenv("AWS_CONTAINER_AUTHORIZATION_TOKEN");
if (authToken == null) {
String authTokenFile = System.getenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE");
if (authTokenFile != null) {
authToken = new String(Files.readAllBytes(Paths.get(authTokenFile)), StandardCharsets.UTF_8);
}
}
if (authToken != null) {
connection.addRequestProperty("Authorization", authToken);
}

int responseCode = connection.getResponseCode();
if (responseCode != 200) {
String responseMessage = connection.getResponseMessage();
throw new IOException(String.format(
"Unexpected HTTP response code fetching AWS container credentials: %d (%s)",
responseCode, responseMessage == null ? "No message" : responseMessage
));
}

try {
Map<String, String> json = JsonReader.jsonToMaps(connection.getInputStream(), new HashMap<>());
return new AmazonCredentials(json.get("AccessKeyId"), json.get("SecretAccessKey"), json.get("Token"));
} catch (JsonIoException e) {
throw new IOException("Error parsing JSON response from AWS container credentials endpoint", e);
}
}

/**
* Returns the URL of the ECS credential metadata endpoint, using the
* environment variables {@code AWS_CONTAINER_CREDENTIALS_RELATIVE_URI} or
* {@code AWS_CONTAINER_CREDENTIALS_FULL_URI}.
*
* @throws UnknownServiceException If no valid ECS endpoint URL is
* available.
*/
private static URL defaultCredentialsUrl() throws UnknownServiceException {
String relativeUri, fullUri;
URL endpoint;

if ((relativeUri = System.getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) != null) {
try {
endpoint = new URL(DEFAULT_AWS_CONTAINER_SERVICE_ENDPOINT, relativeUri);
} catch (MalformedURLException e) {
throw new UnknownServiceException("Invalid relative URI for AWS container credential metadata endpoint: " + relativeUri);
}
} else if ((fullUri = System.getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")) != null) {
try {
endpoint = new URL(fullUri);
} catch (MalformedURLException e) {
throw new UnknownServiceException("Invalid full URI for AWS container credential metadata endpoint: " + fullUri);
}
} else {
throw new UnknownServiceException("No AWS container credential metadata endpoint URIs available");
}

return endpoint;
}
}
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ public void testBuildAWS() throws Exception {

e = assertThrows(IllegalArgumentException.class, builder::build);
assertTrue("message", e.getMessage().matches(
"storepass parameter must specify the AWS credentials\\: \\<accessKey\\>\\|\\<secretKey\\>\\[\\|\\<sessionToken\\>\\], when not running from an EC2 instance \\(.*\\)"));
"storepass parameter must specify the AWS credentials\\: \\<accessKey\\>\\|\\<secretKey\\>\\[\\|\\<sessionToken\\>\\], when not running from ECS or an EC2 instance"));

builder.storepass("<accessKey>|<secretKey>|<sessionToken>");

Loading