Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2bdef86

Browse files
committedNov 15, 2023
Automatic PKCS#11 configuration for SafeNet eTokens
1 parent 42696ff commit 2bdef86

File tree

9 files changed

+206
-4
lines changed

9 files changed

+206
-4
lines changed
 

‎README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Jsign is free to use and licensed under the [Apache License version 2.0](https:/
2828
* Hashing algorithms: MD5, SHA-1, SHA-256, SHA-384 and SHA-512
2929
* Keystores supported:
3030
* PKCS#12, JKS and JCEKS files
31-
* PKCS#11 hardware tokens ([YubiKey](https://www.yubico.com), [Nitrokey](https://www.nitrokey.com), etc)
31+
* PKCS#11 hardware tokens ([YubiKey](https://www.yubico.com), [Nitrokey](https://www.nitrokey.com), [SafeNet eToken](https://cpl.thalesgroup.com/access-management/authenticators/pki-usb-authentication), etc)
3232
* Cloud key management systems:
3333
* [AWS KMS](https://aws.amazon.com/kms/)
3434
* [Azure Key Vault](https://azure.microsoft.com/services/key-vault/)
@@ -51,6 +51,7 @@ See https://ebourg.github.io/jsign for more information.
5151
#### Version 5.1 (in development)
5252
* Signing of APPX/MSIX packages has been implemented (thanks to Maciej Panek for the help)
5353
* Signing of Microsoft Dynamics 365 extension packages has been implemented
54+
* SafeNet eToken support has been improved with automatic PKCS#11 configuration using the new `ETOKEN` storetype
5455
* The certificate chain in the file specified by the `certfile` parameter can now be in any order
5556
* VBScript, JScript and PowerShell XML files without byte order marks are now parsed as Windows-1252 instead of ISO-8859-1
5657
* The format detection based on the file extension is now case insensitive (contributed by Mathieu Delrocq)

‎docs/index.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ <h3 id="features">Features</h3>
6565
<li>Keystores supported:
6666
<ul>
6767
<li>PKCS#12, JKS and JCEKS files</li>
68-
<li>PKCS#11 hardware tokens (<a href="https://www.yubico.com">YubiKey</a>, <a href="https://www.nitrokey.com">Nitrokey</a>, etc)</li>
68+
<li>PKCS#11 hardware tokens (<a href="https://www.yubico.com">YubiKey</a>, <a href="https://www.nitrokey.com">Nitrokey</a>, <a href="https://cpl.thalesgroup.com/access-management/authenticators/pki-usb-authentication">SafeNet eToken</a>, etc)</li>
6969
<li>Cloud key management systems:
7070
<ul>
7171
<li><a href="https://aws.amazon.com/kms/">AWS KMS</a></li>
@@ -183,6 +183,7 @@ <h4 id="attributes" class="mobile-only">Attributes</h4>
183183
<li><code>JCEKS</code>: SunJCE keystore (<code>.jceks</code> files)</li>
184184
<li><code>PKCS12</code>: Standard PKCS#12 keystore (<code>.p12</code> or <code>.pfx</code> files)</li>
185185
<li><code>PKCS11</code>: PKCS#11 hardware token</li>
186+
<li><code>ETOKEN</code>: SafeNet eToken</li>
186187
<li><code>NITROKEY</code>: Nitrokey HSM</li>
187188
<li><code>OPENPGP</code>: OpenPGP card</li>
188189
<li><code>OPENSC</code>: Smart card</li>
@@ -454,6 +455,7 @@ <h3 id="cli">Command Line Tool</h3>
454455
- JCEKS: SunJCE keystore (.jceks files)
455456
- PKCS12: Standard PKCS#12 keystore (.p12 or .pfx files)
456457
- PKCS11: PKCS#11 hardware token
458+
- ETOKEN: SafeNet eToken
457459
- NITROKEY: Nitrokey HSM
458460
- OPENPGP: OpenPGP card
459461
- OPENSC: Smart card
@@ -531,6 +533,15 @@ <h4 id="example-nitrokey">Signing with a Nitrokey HSM</h4>
531533
command). Keys without certificates are ignored. Alternatively, the OPENPGP storetype can also be used, it doesn't
532534
require OpenSC and any key can be used by providing an external certificate.</p>
533535

536+
<h4 id="example-etoken">Signing with a SafeNet eToken</h4>
537+
538+
<p>Signing with a SafeNet eToken requires the installation of the
539+
<a href="https://knowledge.digicert.com/general-information/how-to-download-safenet-authentication-client">SafeNet Authentication Client</a>.</p>
540+
541+
<pre>
542+
jsign --storetype ETOKEN --storepass &ltPIN&gt; --certfile full-chain.pem application.exe
543+
</pre>
544+
534545
<h4 id="example-smart-card">Signing with a smart card</h4>
535546

536547
<p>Signing with a smart card requires the installation of <a href="https://github.com/OpenSC/OpenSC">OpenSC</a>.</p>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static void main(String... args) {
6060
+ "- JCEKS: SunJCE keystore (.jceks files)\n"
6161
+ "- PKCS12: Standard PKCS#12 keystore (.p12 or .pfx files)\n"
6262
+ "- PKCS11: PKCS#11 hardware token\n"
63+
+ "- ETOKEN: SafeNet eToken\n"
6364
+ "- NITROKEY: Nitrokey HSM\n"
6465
+ "- OPENPGP: OpenPGP card\n"
6566
+ "- OPENSC: Smart card\n"

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,17 @@ void validate(KeyStoreBuilder params) {
398398
Provider getProvider(KeyStoreBuilder params) {
399399
return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params)));
400400
}
401+
},
402+
403+
/**
404+
* SafeNet eToken
405+
* This keystore requires the installation of the SafeNet Authentication Client.
406+
*/
407+
ETOKEN(false, true, true) {
408+
@Override
409+
Provider getProvider(KeyStoreBuilder params) {
410+
return SafeNetEToken.getProvider();
411+
}
401412
};
402413

403414

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Copyright 2023 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;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.security.Provider;
22+
import java.security.ProviderException;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
26+
import sun.security.pkcs11.wrapper.PKCS11;
27+
import sun.security.pkcs11.wrapper.PKCS11Exception;
28+
29+
/**
30+
* Helper class for working with SafeNet eTokens.
31+
*
32+
* @since 5.1
33+
*/
34+
class SafeNetEToken {
35+
36+
/**
37+
* Returns the security provider for the SafeNet eToken.
38+
*
39+
* @return the SafeNet eTokens security provider
40+
* @throws ProviderException thrown if the provider can't be initialized
41+
*/
42+
static Provider getProvider() {
43+
return ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration());
44+
}
45+
46+
/**
47+
* Returns the SunPKCS11 configuration of the SafeNet eToken.
48+
*
49+
* @throws ProviderException thrown if the PKCS11 modules cannot be found
50+
*/
51+
static String getSunPKCS11Configuration() {
52+
File library = getPKCS11Library();
53+
if (!library.exists()) {
54+
throw new ProviderException("SafeNet eToken PKCS11 module is not installed (" + library + " is missing)");
55+
}
56+
String configuration = "--name=SafeNet eToken\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n";
57+
try {
58+
long slot = getTokenSlot(library);
59+
if (slot >= 0) {
60+
configuration += "slot=" + slot;
61+
}
62+
} catch (Exception e) {
63+
throw new ProviderException(e);
64+
}
65+
return configuration;
66+
}
67+
68+
/**
69+
* Returns the slot index associated to the token.
70+
*/
71+
static long getTokenSlot(File libraryPath) throws PKCS11Exception, IOException {
72+
PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false);
73+
long[] slots = pkcs11.C_GetSlotList(true);
74+
return slots.length > 0 ? slots[0] : -1;
75+
}
76+
77+
/**
78+
* Attempts to locate the SafeNet eToken PKCS11 library on the system.
79+
*/
80+
static File getPKCS11Library() {
81+
String osname = System.getProperty("os.name");
82+
String arch = System.getProperty("sun.arch.data.model");
83+
84+
if (osname.contains("Windows")) {
85+
return new File(System.getenv("windir") + "/system32/eTPKCS11.dll");
86+
87+
} else if (osname.contains("Mac")) {
88+
return new File("/usr/local/lib/libeTPkcs11.dylib");
89+
90+
} else {
91+
// Linux
92+
List<String> paths = new ArrayList<>();
93+
if ("64".equals(arch)) {
94+
paths.add("/usr/lib64/pkcs11/libeTPkcs11.so");
95+
paths.add("/usr/lib64/libeTPkcs11.so");
96+
paths.add("/usr/lib64/libeToken.so");
97+
}
98+
paths.add("/usr/lib/pkcs11/libeTPkcs11.so");
99+
paths.add("/usr/lib/pkcs11/libeToken.so");
100+
paths.add("/usr/lib/libeTPkcs11.so");
101+
paths.add("/usr/lib/libeToken.so");
102+
103+
for (String path : paths) {
104+
File library = new File(path);
105+
if (library.exists()) {
106+
return library;
107+
}
108+
}
109+
110+
return new File("/usr/local/lib/libeToken.so");
111+
}
112+
}
113+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright 2023 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;
18+
19+
import java.io.File;
20+
import java.security.Provider;
21+
22+
import org.junit.Assume;
23+
import org.junit.Test;
24+
25+
import static org.junit.Assert.*;
26+
27+
public class SafeNetETokenTest {
28+
29+
private void assumeSafeNetEToken() {
30+
Assume.assumeTrue("SafeNet Authentication Client isn't installed",
31+
new File(System.getenv("ProgramFiles") + "/OpenSC Project/OpenSC/pkcs11/opensc-pkcs11.dll").exists()
32+
|| new File("/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so").exists());
33+
}
34+
35+
@Test
36+
public void testGetProvider() {
37+
assumeSafeNetEToken();
38+
try {
39+
Provider provider = SafeNetEToken.getProvider();
40+
assertNotNull(provider);
41+
} catch (RuntimeException e) {
42+
assertEquals("No PKCS11 token found", e.getCause().getMessage());
43+
}
44+
}
45+
46+
@Test
47+
public void testGetLibrary() {
48+
assumeSafeNetEToken();
49+
File library = SafeNetEToken.getPKCS11Library();
50+
assertNotNull(library);
51+
assertTrue(library.exists());
52+
}
53+
}

‎jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public class JsignMojo extends AbstractMojo {
7171
@Parameter( property = "jsign.storepass" )
7272
private String storepass;
7373

74-
/** The type of the keystore (JKS, PKCS12, PKCS11, YUBIKEY, AWS, AZUREKEYVAULT, DIGICERTONE, ESIGNER or GOOGLECLOUD). */
74+
/** The type of the keystore (JKS, PKCS12, PKCS11, ETOKEN, YUBIKEY, AWS, AZUREKEYVAULT, DIGICERTONE, ESIGNER or GOOGLECLOUD). */
7575
@Parameter( property = "jsign.storetype" )
7676
private String storetype;
7777

‎jsign/src/deb/data/usr/share/bash-completion/completions/jsign

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ _jsign()
2222
return 0
2323
;;
2424
--storetype)
25-
COMPREPLY=( $( compgen -W 'JKS JCEKS PKCS12 PKCS11 AWS AZUREKEYVAULT DIGICERTONE ESIGNER GOOGLECLOUD HASHICORPVAULT YUBIKEY NITROKEY OPENPGP OPENSC' -- "$cur" ) )
25+
COMPREPLY=( $( compgen -W 'JKS JCEKS PKCS12 PKCS11 AWS AZUREKEYVAULT DIGICERTONE ESIGNER ETOKEN GOOGLECLOUD HASHICORPVAULT YUBIKEY NITROKEY OPENPGP OPENSC' -- "$cur" ) )
2626
return 0
2727
;;
2828
--storepass|-a|--alias|--keypass|-t|--tsaurl|-r|--tsretries|-w|--tsretrywait|-n|--name|-u|--url|-e|--encoding)

‎jsign/src/deb/data/usr/share/man/man1/jsign.1

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ The type of the keystore:
4747
.br
4848
- PKCS11 : PKCS#11 hardware token
4949
.br
50+
- ETOKEN : SafeNet eToken
51+
.br
5052
- NITROKEY : Nitrokey HSM
5153
.br
5254
- OPENPGP : OpenPGP card
@@ -208,6 +210,16 @@ Keys without certificates are ignored. Alternatively, the OPENPGP storetype can
208210
it doesn't require OpenSC and any key can be used by providing an external certificate.
209211

210212

213+
.TP
214+
215+
Signing with a SafeNet eToken:
216+
217+
Signing with a SafeNet eToken requires the installation of the SafeNet Authentication Client
218+
<a href="https://knowledge.digicert.com/general-information/how-to-download-safenet-authentication-client"></a>.</p>
219+
220+
jsign --storetype ETOKEN --storepass <PIN> --certfile full-chain.pem application.exe
221+
222+
211223
.TP
212224

213225
Signing with a smart card:

0 commit comments

Comments
 (0)