Skip to content

Commit

Permalink
Oracle Cloud support (Fixes #213)
Browse files Browse the repository at this point in the history
  • Loading branch information
ebourg committed Mar 27, 2024
1 parent 12c386a commit 9f03aa1
Show file tree
Hide file tree
Showing 14 changed files with 816 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Jsign is free to use and licensed under the [Apache License version 2.0](https:/
* [DigiCert ONE](https://one.digicert.com)
* [Google Cloud KMS](https://cloud.google.com/security-key-management)
* [HashiCorp Vault](https://www.vaultproject.io/)
* [Oracle Cloud KMS](https://www.oracle.com/security/cloud-security/key-management/)
* [SSL.com eSigner](https://www.ssl.com/esigner/)
* Private key formats: PVK and PEM (PKCS#1 and PKCS#8), encrypted or not
* Certificates: PKCS#7 in PEM and DER format
Expand All @@ -50,6 +51,7 @@ See https://ebourg.github.io/jsign for more information.

#### Version 6.1 (in development)

* The Oracle Cloud signing service has been integrated
* Signing of NuGet packages has been implemented (contributed by Sebastian Stamm)
* Jsign now checks if the certificate subject matches the app manifest publisher before signing APPX/MSIX packages
* The JCA provider now works with [apksigner](https://developer.android.com/tools/apksigner) for signing Android applications
Expand Down
47 changes: 47 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ <h3 id="features">Features</h3>
<li><a href="https://one.digicert.com">DigiCert ONE</a></li>
<li><a href="https://cloud.google.com/security-key-management">Google Cloud KMS</a></li>
<li><a href="https://www.vaultproject.io">HashiCorp Vault</a></li>
<li><a href="https://www.oracle.com/security/cloud-security/key-management/">Oracle Cloud KMS</a></li>
<li><a href="https://www.ssl.com/esigner/">SSL.com eSigner</a></li>
</ul>
</li>
Expand Down Expand Up @@ -196,6 +197,7 @@ <h4 id="attributes" class="mobile-only">Attributes</h4>
<li><code>ESIGNER</code>: SSL.com eSigner</li>
<li><code>GOOGLECLOUD</code>: Google Cloud KMS</li>
<li><code>HASHICORPVAULT</code>: Google Cloud KMS via HashiCorp Vault</li>
<li><code>ORACLECLOUD</code>: Oracle Cloud Key Management Service</li>
</ul>
</td>
<td class="required">No, automatically detected for file based keystores.</td>
Expand Down Expand Up @@ -469,6 +471,7 @@ <h3 id="cli">Command Line Tool</h3>
- ESIGNER: SSL.com eSigner
- GOOGLECLOUD: Google Cloud KMS
- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault
- ORACLECLOUD: Oracle Cloud Key Management Service
-a,--alias &lt;NAME> The alias of the certificate used for signing in the keystore.
--keypass &lt;PASSWORD> The password of the private key. When using a keystore,
this parameter can be omitted if the keystore shares the
Expand Down Expand Up @@ -709,6 +712,50 @@ <h4 id="example-hashicorpvault">Signing with Google Cloud KMS via HashiCorp Vaul
--certfile full-chain.pem application.exe
</pre>

<h4 id="example-oraclecloud">Signing with Oracle Cloud Key Management Service</h4>

<p>Signing with the Oracle Cloud Infrastructure Key Management Service requires the
<a href="https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm">configuration file</a> or the
<a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">environment variables</a>
used by the OCI CLI. The OCI CLI isn't required for signing, but it may be used to initialize the configuration file
with <code>oci setup bootstrap</code>.</p>

<p>The <code>keystore</code> parameter specifies the profile used in the configuration file
(the default value is <code>DEFAULT</code>), and the <code>storepass</code> parameter specifies the path
to the configuration file (<code>~/.oci/config</code> by default).</p>

<p>The certificate must be provided separately using the <code>certfile</code> parameter. The alias specifies the OCID
of the key.</p>

<p>The general syntax looks like this:</p>

<pre>
jsign --storetype ORACLECLOUD \
--keystore &lt;profile&gt; \
--storepass &lt;oci-config-file&gt; \
--alias ocid1.key.oc1.eu-paris-1.abcdefghijklm.abrwiljrwkhgllb5zfqchmvdkmqnzutqeq5pz7 \
--certfile full-chain.pem application.exe
</pre>

<p>When using the default configuration file and profile, the command is simplified to:</p>

<pre>
jsign --storetype ORACLECLOUD \
--alias ocid1.key.oc1.eu-paris-1.abcdefghijklm.abrwiljrwkhgllb5zfqchmvdkmqnzutqeq5pz7 \
--certfile full-chain.pem application.exe
</pre>

<p>The configuration file can be replaced (or overridden) by environment variables. Here are the variables expected:</p>

<ul>
<li><code>OCI_CLI_USER</code>: OCID of the user (e.g. <code>ocid1.user.oc1..&ltunique_ID&gt</code>)</li>
<li><code>OCI_CLI_TENANCY</code>: The OCID of the tenancy (e.g. <code>ocid1.tenancy.oc1..&ltunique_ID&gt</code>)</li>
<li><code>OCI_CLI_REGION</code>: The OCI region (e.g. <code>eu-paris-1</code>)</li>
<li><code>OCI_CLI_KEY_FILE</code>: The path to the private key signing the API requests in PEM format</li>
<li><code>OCI_CLI_PASS_PHRASE</code>: The pass phrase of the private key</li>
<li><code>OCI_CLI_FINGERPRINT</code>: The fingerprint of the private key</li>
</ul>


<h3 id="api">API</h3>

Expand Down
3 changes: 2 additions & 1 deletion jsign-cli/src/main/java/net/jsign/JsignCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public static void main(String... args) {
+ "- DIGICERTONE: DigiCert ONE Secure Software Manager\n"
+ "- ESIGNER: SSL.com eSigner\n"
+ "- GOOGLECLOUD: Google Cloud KMS\n"
+ "- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault\n").build());
+ "- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault\n"
+ "- ORACLECLOUD: Oracle Cloud Key Management Service\n").build());
options.addOption(Option.builder("a").hasArg().longOpt(PARAM_ALIAS).argName("NAME").desc("The alias of the certificate used for signing in the keystore.").build());
options.addOption(Option.builder().hasArg().longOpt(PARAM_KEYPASS).argName("PASSWORD").desc("The password of the private key. When using a keystore, this parameter can be omitted if the keystore shares the same password.").build());
options.addOption(Option.builder().hasArg().longOpt(PARAM_KEYFILE).argName("FILE").desc("The file containing the private key. PEM and PVK files are supported. ").type(File.class).build());
Expand Down
33 changes: 33 additions & 0 deletions jsign-core/src/main/java/net/jsign/KeyStoreType.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import net.jsign.jca.GoogleCloudSigningService;
import net.jsign.jca.HashiCorpVaultSigningService;
import net.jsign.jca.OpenPGPCardSigningService;
import net.jsign.jca.OracleCloudCredentials;
import net.jsign.jca.OracleCloudSigningService;
import net.jsign.jca.PIVCardSigningService;
import net.jsign.jca.SigningServiceJcaProvider;

Expand Down Expand Up @@ -436,6 +438,37 @@ Provider getProvider(KeyStoreBuilder params) {
Provider getProvider(KeyStoreBuilder params) {
return SafeNetEToken.getProvider();
}
},

/**
* Oracle Cloud Infrastructure Key Management Service. This keystore requires the <a href="https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm">configuration file</a>
* or the <a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">environment
* variables</a> used by the OCI CLI. The keystore parameter specifies the profile used in the configuration file
* (the default value is <code>DEFAULT</code>), and the storepass parameter specifies the path to the configuration
* file (<code>~/.oci/config</code> by default).
*
* <p>The certificate must be provided separately using the certfile parameter. The alias specifies the OCID
* of the key.</p>
*/
ORACLECLOUD(false, false, false) {
@Override
void validate(KeyStoreBuilder params) {
if (params.certfile() == null) {
throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
}
}

@Override
Provider getProvider(KeyStoreBuilder params) {
OracleCloudCredentials credentials = new OracleCloudCredentials();
try {
credentials.load(params.storepass() != null ? new File(params.storepass()) : null, params.keystore());
credentials.loadFromEnvironment();
} catch (IOException e) {
throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e);
}
return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params)));
}
};


Expand Down
196 changes: 196 additions & 0 deletions jsign-core/src/main/java/net/jsign/jca/OracleCloudCredentials.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* Copyright 2024 Emmanuel Bourg
*
* 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.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.KeyException;
import java.security.PrivateKey;
import java.util.Properties;

import net.jsign.PrivateKeyUtils;

/**
* Oracle Cloud credentials loaded from the <code>.oci/config</code> file or from the environment variables.
*
* @since 6.1
*/
public class OracleCloudCredentials {

private String user;
private String tenancy;
private String region;
private String keyfile;
private String fingerprint;
private String passphrase;
private PrivateKey privateKey;

public String getUser() {
return user;
}

public String getTenancy() {
return tenancy;
}

public String getRegion() {
return region;
}

public String getKeyfile() {
return keyfile;
}

public String getFingerprint() {
return fingerprint;
}

public String getPassphrase() {
return passphrase;
}

public String getKeyId() {
return getTenancy() + "/" + getUser() + "/" + getFingerprint();
}

PrivateKey getPrivateKey() {
if (privateKey == null) {
try {
privateKey = PrivateKeyUtils.load(new File(getKeyfile()), getPassphrase());
} catch (KeyException e) {
throw new RuntimeException("Unable to load the private key", e);
}
}
return privateKey;
}

/**
* Loads the credentials from the specified file.
*
* @param file the configuration file (null for the default location)
* @param profile the name of the profile (null for the default profile)
*/
public void load(File file, String profile) throws IOException {
if (file == null) {
file = getConfigFile();
}
if (profile == null) {
profile = getDefaultProfile();
}

Properties properties = new Properties();

// parse le lines of the file
boolean profileFound = false;
for (String line : Files.readAllLines(file.toPath())) {
if (profileFound && line.startsWith("[")) {
break; // end of the profile
}

if (line.equals("[" + profile + "]")) {
profileFound = true;
continue;
}

if (profileFound) {
String[] elements = line.split("=", 2);
if (elements.length == 2) {
properties.setProperty(elements[0].trim(), elements[1].trim());
}
}
}

if (!profileFound) {
throw new IOException("Profile '" + profile + "' not found in " + file);
}

user = properties.getProperty("user");
tenancy = properties.getProperty("tenancy");
region = properties.getProperty("region");
keyfile = properties.getProperty("key_file");
fingerprint = properties.getProperty("fingerprint");
passphrase = properties.getProperty("pass_phrase");
}

/**
* Loads the credentials from the environment variables.
*
* @see <a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">CLI Environment Variables</a>
*/
public void loadFromEnvironment() {
if (getenv("OCI_CLI_USER") != null) {
user = getenv("OCI_CLI_USER");
}
if (getenv("OCI_CLI_TENANCY") != null) {
tenancy = getenv("OCI_CLI_TENANCY");
}
if (getenv("OCI_CLI_REGION") != null) {
region = getenv("OCI_CLI_REGION");
}
if (getenv("OCI_CLI_KEY_FILE") != null) {
keyfile = getenv("OCI_CLI_KEY_FILE");
}
if (getenv("OCI_CLI_FINGERPRINT") != null) {
fingerprint = getenv("OCI_CLI_FINGERPRINT");
}
if (getenv("OCI_CLI_PASS_PHRASE") != null) {
passphrase = getenv("OCI_CLI_PASS_PHRASE");
}
}

/**
* Returns the default Oracle Cloud configuration.
*/
public static OracleCloudCredentials getDefault() throws IOException {
OracleCloudCredentials credentials = new OracleCloudCredentials();
File config = getConfigFile();
if (config.exists()) {
credentials.load(config, getDefaultProfile());
}
credentials.loadFromEnvironment();
return credentials;
}

/**
* Returns the name of the default profile, either the value of the OCI_CLI_PROFILE environment variable or "DEFAULT".
*/
public static String getDefaultProfile() {
String profile = getenv("OCI_CLI_PROFILE");
if (profile == null) {
profile = "DEFAULT";
}
return profile;
}

/**
* Returns the location of the configuration file, either the value of the OCI_CLI_CONFIG_FILE environment variable
* or <code>~/.oci/config</code>.
*/
public static File getConfigFile() {
String config = getenv("OCI_CLI_CONFIG_FILE");
if (config != null) {
return new File(config);
} else {
return new File(System.getProperty("user.home"), ".oci/config");
}
}

static String getenv(String name) {
return System.getenv(name);
}
}
Loading

0 comments on commit 9f03aa1

Please sign in to comment.