Skip to content

Commit 9f03aa1

Browse files
committed
Oracle Cloud support (Fixes #213)
1 parent 12c386a commit 9f03aa1

File tree

14 files changed

+816
-4
lines changed

14 files changed

+816
-4
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Jsign is free to use and licensed under the [Apache License version 2.0](https:/
3535
* [DigiCert ONE](https://one.digicert.com)
3636
* [Google Cloud KMS](https://cloud.google.com/security-key-management)
3737
* [HashiCorp Vault](https://www.vaultproject.io/)
38+
* [Oracle Cloud KMS](https://www.oracle.com/security/cloud-security/key-management/)
3839
* [SSL.com eSigner](https://www.ssl.com/esigner/)
3940
* Private key formats: PVK and PEM (PKCS#1 and PKCS#8), encrypted or not
4041
* Certificates: PKCS#7 in PEM and DER format
@@ -50,6 +51,7 @@ See https://ebourg.github.io/jsign for more information.
5051

5152
#### Version 6.1 (in development)
5253

54+
* The Oracle Cloud signing service has been integrated
5355
* Signing of NuGet packages has been implemented (contributed by Sebastian Stamm)
5456
* Jsign now checks if the certificate subject matches the app manifest publisher before signing APPX/MSIX packages
5557
* The JCA provider now works with [apksigner](https://developer.android.com/tools/apksigner) for signing Android applications

docs/index.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ <h3 id="features">Features</h3>
7373
<li><a href="https://one.digicert.com">DigiCert ONE</a></li>
7474
<li><a href="https://cloud.google.com/security-key-management">Google Cloud KMS</a></li>
7575
<li><a href="https://www.vaultproject.io">HashiCorp Vault</a></li>
76+
<li><a href="https://www.oracle.com/security/cloud-security/key-management/">Oracle Cloud KMS</a></li>
7677
<li><a href="https://www.ssl.com/esigner/">SSL.com eSigner</a></li>
7778
</ul>
7879
</li>
@@ -196,6 +197,7 @@ <h4 id="attributes" class="mobile-only">Attributes</h4>
196197
<li><code>ESIGNER</code>: SSL.com eSigner</li>
197198
<li><code>GOOGLECLOUD</code>: Google Cloud KMS</li>
198199
<li><code>HASHICORPVAULT</code>: Google Cloud KMS via HashiCorp Vault</li>
200+
<li><code>ORACLECLOUD</code>: Oracle Cloud Key Management Service</li>
199201
</ul>
200202
</td>
201203
<td class="required">No, automatically detected for file based keystores.</td>
@@ -469,6 +471,7 @@ <h3 id="cli">Command Line Tool</h3>
469471
- ESIGNER: SSL.com eSigner
470472
- GOOGLECLOUD: Google Cloud KMS
471473
- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault
474+
- ORACLECLOUD: Oracle Cloud Key Management Service
472475
-a,--alias &lt;NAME> The alias of the certificate used for signing in the keystore.
473476
--keypass &lt;PASSWORD> The password of the private key. When using a keystore,
474477
this parameter can be omitted if the keystore shares the
@@ -709,6 +712,50 @@ <h4 id="example-hashicorpvault">Signing with Google Cloud KMS via HashiCorp Vaul
709712
--certfile full-chain.pem application.exe
710713
</pre>
711714

715+
<h4 id="example-oraclecloud">Signing with Oracle Cloud Key Management Service</h4>
716+
717+
<p>Signing with the Oracle Cloud Infrastructure Key Management Service requires the
718+
<a href="https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm">configuration file</a> or the
719+
<a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">environment variables</a>
720+
used by the OCI CLI. The OCI CLI isn't required for signing, but it may be used to initialize the configuration file
721+
with <code>oci setup bootstrap</code>.</p>
722+
723+
<p>The <code>keystore</code> parameter specifies the profile used in the configuration file
724+
(the default value is <code>DEFAULT</code>), and the <code>storepass</code> parameter specifies the path
725+
to the configuration file (<code>~/.oci/config</code> by default).</p>
726+
727+
<p>The certificate must be provided separately using the <code>certfile</code> parameter. The alias specifies the OCID
728+
of the key.</p>
729+
730+
<p>The general syntax looks like this:</p>
731+
732+
<pre>
733+
jsign --storetype ORACLECLOUD \
734+
--keystore &lt;profile&gt; \
735+
--storepass &lt;oci-config-file&gt; \
736+
--alias ocid1.key.oc1.eu-paris-1.abcdefghijklm.abrwiljrwkhgllb5zfqchmvdkmqnzutqeq5pz7 \
737+
--certfile full-chain.pem application.exe
738+
</pre>
739+
740+
<p>When using the default configuration file and profile, the command is simplified to:</p>
741+
742+
<pre>
743+
jsign --storetype ORACLECLOUD \
744+
--alias ocid1.key.oc1.eu-paris-1.abcdefghijklm.abrwiljrwkhgllb5zfqchmvdkmqnzutqeq5pz7 \
745+
--certfile full-chain.pem application.exe
746+
</pre>
747+
748+
<p>The configuration file can be replaced (or overridden) by environment variables. Here are the variables expected:</p>
749+
750+
<ul>
751+
<li><code>OCI_CLI_USER</code>: OCID of the user (e.g. <code>ocid1.user.oc1..&ltunique_ID&gt</code>)</li>
752+
<li><code>OCI_CLI_TENANCY</code>: The OCID of the tenancy (e.g. <code>ocid1.tenancy.oc1..&ltunique_ID&gt</code>)</li>
753+
<li><code>OCI_CLI_REGION</code>: The OCI region (e.g. <code>eu-paris-1</code>)</li>
754+
<li><code>OCI_CLI_KEY_FILE</code>: The path to the private key signing the API requests in PEM format</li>
755+
<li><code>OCI_CLI_PASS_PHRASE</code>: The pass phrase of the private key</li>
756+
<li><code>OCI_CLI_FINGERPRINT</code>: The fingerprint of the private key</li>
757+
</ul>
758+
712759

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

jsign-cli/src/main/java/net/jsign/JsignCLI.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public static void main(String... args) {
7171
+ "- DIGICERTONE: DigiCert ONE Secure Software Manager\n"
7272
+ "- ESIGNER: SSL.com eSigner\n"
7373
+ "- GOOGLECLOUD: Google Cloud KMS\n"
74-
+ "- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault\n").build());
74+
+ "- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault\n"
75+
+ "- ORACLECLOUD: Oracle Cloud Key Management Service\n").build());
7576
options.addOption(Option.builder("a").hasArg().longOpt(PARAM_ALIAS).argName("NAME").desc("The alias of the certificate used for signing in the keystore.").build());
7677
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());
7778
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());

jsign-core/src/main/java/net/jsign/KeyStoreType.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import net.jsign.jca.GoogleCloudSigningService;
4343
import net.jsign.jca.HashiCorpVaultSigningService;
4444
import net.jsign.jca.OpenPGPCardSigningService;
45+
import net.jsign.jca.OracleCloudCredentials;
46+
import net.jsign.jca.OracleCloudSigningService;
4547
import net.jsign.jca.PIVCardSigningService;
4648
import net.jsign.jca.SigningServiceJcaProvider;
4749

@@ -436,6 +438,37 @@ Provider getProvider(KeyStoreBuilder params) {
436438
Provider getProvider(KeyStoreBuilder params) {
437439
return SafeNetEToken.getProvider();
438440
}
441+
},
442+
443+
/**
444+
* 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>
445+
* or the <a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">environment
446+
* variables</a> used by the OCI CLI. The keystore parameter specifies the profile used in the configuration file
447+
* (the default value is <code>DEFAULT</code>), and the storepass parameter specifies the path to the configuration
448+
* file (<code>~/.oci/config</code> by default).
449+
*
450+
* <p>The certificate must be provided separately using the certfile parameter. The alias specifies the OCID
451+
* of the key.</p>
452+
*/
453+
ORACLECLOUD(false, false, false) {
454+
@Override
455+
void validate(KeyStoreBuilder params) {
456+
if (params.certfile() == null) {
457+
throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
458+
}
459+
}
460+
461+
@Override
462+
Provider getProvider(KeyStoreBuilder params) {
463+
OracleCloudCredentials credentials = new OracleCloudCredentials();
464+
try {
465+
credentials.load(params.storepass() != null ? new File(params.storepass()) : null, params.keystore());
466+
credentials.loadFromEnvironment();
467+
} catch (IOException e) {
468+
throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e);
469+
}
470+
return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params)));
471+
}
439472
};
440473

441474

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* Copyright 2024 Emmanuel Bourg
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.jsign.jca;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.security.KeyException;
23+
import java.security.PrivateKey;
24+
import java.util.Properties;
25+
26+
import net.jsign.PrivateKeyUtils;
27+
28+
/**
29+
* Oracle Cloud credentials loaded from the <code>.oci/config</code> file or from the environment variables.
30+
*
31+
* @since 6.1
32+
*/
33+
public class OracleCloudCredentials {
34+
35+
private String user;
36+
private String tenancy;
37+
private String region;
38+
private String keyfile;
39+
private String fingerprint;
40+
private String passphrase;
41+
private PrivateKey privateKey;
42+
43+
public String getUser() {
44+
return user;
45+
}
46+
47+
public String getTenancy() {
48+
return tenancy;
49+
}
50+
51+
public String getRegion() {
52+
return region;
53+
}
54+
55+
public String getKeyfile() {
56+
return keyfile;
57+
}
58+
59+
public String getFingerprint() {
60+
return fingerprint;
61+
}
62+
63+
public String getPassphrase() {
64+
return passphrase;
65+
}
66+
67+
public String getKeyId() {
68+
return getTenancy() + "/" + getUser() + "/" + getFingerprint();
69+
}
70+
71+
PrivateKey getPrivateKey() {
72+
if (privateKey == null) {
73+
try {
74+
privateKey = PrivateKeyUtils.load(new File(getKeyfile()), getPassphrase());
75+
} catch (KeyException e) {
76+
throw new RuntimeException("Unable to load the private key", e);
77+
}
78+
}
79+
return privateKey;
80+
}
81+
82+
/**
83+
* Loads the credentials from the specified file.
84+
*
85+
* @param file the configuration file (null for the default location)
86+
* @param profile the name of the profile (null for the default profile)
87+
*/
88+
public void load(File file, String profile) throws IOException {
89+
if (file == null) {
90+
file = getConfigFile();
91+
}
92+
if (profile == null) {
93+
profile = getDefaultProfile();
94+
}
95+
96+
Properties properties = new Properties();
97+
98+
// parse le lines of the file
99+
boolean profileFound = false;
100+
for (String line : Files.readAllLines(file.toPath())) {
101+
if (profileFound && line.startsWith("[")) {
102+
break; // end of the profile
103+
}
104+
105+
if (line.equals("[" + profile + "]")) {
106+
profileFound = true;
107+
continue;
108+
}
109+
110+
if (profileFound) {
111+
String[] elements = line.split("=", 2);
112+
if (elements.length == 2) {
113+
properties.setProperty(elements[0].trim(), elements[1].trim());
114+
}
115+
}
116+
}
117+
118+
if (!profileFound) {
119+
throw new IOException("Profile '" + profile + "' not found in " + file);
120+
}
121+
122+
user = properties.getProperty("user");
123+
tenancy = properties.getProperty("tenancy");
124+
region = properties.getProperty("region");
125+
keyfile = properties.getProperty("key_file");
126+
fingerprint = properties.getProperty("fingerprint");
127+
passphrase = properties.getProperty("pass_phrase");
128+
}
129+
130+
/**
131+
* Loads the credentials from the environment variables.
132+
*
133+
* @see <a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">CLI Environment Variables</a>
134+
*/
135+
public void loadFromEnvironment() {
136+
if (getenv("OCI_CLI_USER") != null) {
137+
user = getenv("OCI_CLI_USER");
138+
}
139+
if (getenv("OCI_CLI_TENANCY") != null) {
140+
tenancy = getenv("OCI_CLI_TENANCY");
141+
}
142+
if (getenv("OCI_CLI_REGION") != null) {
143+
region = getenv("OCI_CLI_REGION");
144+
}
145+
if (getenv("OCI_CLI_KEY_FILE") != null) {
146+
keyfile = getenv("OCI_CLI_KEY_FILE");
147+
}
148+
if (getenv("OCI_CLI_FINGERPRINT") != null) {
149+
fingerprint = getenv("OCI_CLI_FINGERPRINT");
150+
}
151+
if (getenv("OCI_CLI_PASS_PHRASE") != null) {
152+
passphrase = getenv("OCI_CLI_PASS_PHRASE");
153+
}
154+
}
155+
156+
/**
157+
* Returns the default Oracle Cloud configuration.
158+
*/
159+
public static OracleCloudCredentials getDefault() throws IOException {
160+
OracleCloudCredentials credentials = new OracleCloudCredentials();
161+
File config = getConfigFile();
162+
if (config.exists()) {
163+
credentials.load(config, getDefaultProfile());
164+
}
165+
credentials.loadFromEnvironment();
166+
return credentials;
167+
}
168+
169+
/**
170+
* Returns the name of the default profile, either the value of the OCI_CLI_PROFILE environment variable or "DEFAULT".
171+
*/
172+
public static String getDefaultProfile() {
173+
String profile = getenv("OCI_CLI_PROFILE");
174+
if (profile == null) {
175+
profile = "DEFAULT";
176+
}
177+
return profile;
178+
}
179+
180+
/**
181+
* Returns the location of the configuration file, either the value of the OCI_CLI_CONFIG_FILE environment variable
182+
* or <code>~/.oci/config</code>.
183+
*/
184+
public static File getConfigFile() {
185+
String config = getenv("OCI_CLI_CONFIG_FILE");
186+
if (config != null) {
187+
return new File(config);
188+
} else {
189+
return new File(System.getProperty("user.home"), ".oci/config");
190+
}
191+
}
192+
193+
static String getenv(String name) {
194+
return System.getenv(name);
195+
}
196+
}

0 commit comments

Comments
 (0)