From f22b49fb9a6ec3e4d0333ef22f23beab137be727 Mon Sep 17 00:00:00 2001 From: Emmanuel Bourg Date: Mon, 26 Feb 2024 11:44:24 +0100 Subject: [PATCH] WIP --- .../java/net/jsign/AbstractKeyStoreType.java | 85 +++ .../java/net/jsign/AmazonKeyStoreType.java | 66 ++ .../net/jsign/AzureKeyVaultKeyStoreType.java | 48 ++ .../AzureTrustedSigningKeyStoreType.java | 48 ++ .../net/jsign/DigiCertOneKeyStoreType.java | 46 ++ .../java/net/jsign/ESignerKeyStoreType.java | 53 ++ .../java/net/jsign/ETokenKeyStoreType.java | 35 ++ .../java/net/jsign/FileBasedKeyStoreType.java | 63 ++ .../java/net/jsign/GaraSignKeyStoreType.java | 62 ++ .../net/jsign/GoogleCloudKeyStoreType.java | 54 ++ .../net/jsign/HashiCorpVaultKeyStoreType.java | 51 ++ .../java/net/jsign/JCEKSKeyStoreType.java | 36 ++ .../main/java/net/jsign/JKSKeyStoreType.java | 36 ++ .../main/java/net/jsign/KeyStoreBuilder.java | 18 +- .../src/main/java/net/jsign/KeyStoreType.java | 562 ++---------------- .../java/net/jsign/NitrokeyKeyStoreType.java | 35 ++ .../main/java/net/jsign/NoneKeyStoreType.java | 81 +++ .../java/net/jsign/OpenPGPKeyStoreType.java | 50 ++ .../java/net/jsign/OpenSCKeyStoreType.java | 35 ++ .../net/jsign/OracleCloudKeyStoreType.java | 67 +++ .../main/java/net/jsign/PIVKeyStoreType.java | 50 ++ .../java/net/jsign/PKCS11KeyStoreType.java | 78 +++ .../java/net/jsign/PKCS12KeyStoreType.java | 36 ++ .../net/jsign/SignServerKeyStoreType.java | 63 ++ .../java/net/jsign/YubikeyKeyStoreType.java | 46 ++ .../java/net/jsign/KeyStoreBuilderTest.java | 41 ++ .../test/java/net/jsign/KeyStoreTypeTest.java | 53 +- 27 files changed, 1340 insertions(+), 558 deletions(-) create mode 100644 jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java create mode 100644 jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java diff --git a/jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java new file mode 100644 index 00000000..216816ab --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java @@ -0,0 +1,85 @@ +/* + * 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; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.Objects; +import java.util.function.Function; + +abstract class AbstractKeyStoreType implements KeyStoreType { + + public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { + KeyStore ks; + try { + if (provider != null) { + ks = KeyStore.getInstance(name(), provider); + } else { + ks = KeyStore.getInstance(name()); + } + } catch (KeyStoreException e) { + throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e); + } + + try { + boolean fileBased = this instanceof FileBasedKeyStoreType; + try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) { + ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null); + } + } catch (Exception e) { + throw new KeyStoreException("Unable to load the keystore " + params.keystore(), e); + } + + return ks; + } + + Function getCertificateStore(KeyStoreBuilder params) { + return alias -> { + if (alias == null || alias.isEmpty()) { + return null; + } + + try { + return CertificateUtils.loadCertificateChain(params.certfile()); + } catch (IOException | CertificateException e) { + throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e); + } + }; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof KeyStoreType)) { + return false; + } + KeyStoreType that = (KeyStoreType) o; + return Objects.equals(name(), that.name()); + } + + @Override + public int hashCode() { + return Objects.hashCode(name()); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java new file mode 100644 index 00000000..d1307e04 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java @@ -0,0 +1,66 @@ +/* + * 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; + +import java.io.IOException; +import java.net.UnknownServiceException; +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.AmazonCredentials; +import net.jsign.jca.AmazonSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class AmazonKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "AWS"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the AWS region"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + AmazonCredentials credentials; + if (params.storepass() != null) { + credentials = AmazonCredentials.parse(params.storepass()); + } else { + try { + credentials = AmazonCredentials.getDefault(); + } catch (UnknownServiceException e) { + throw new IllegalArgumentException("storepass " + params.parameterName() + + " must specify the AWS credentials: |[|]" + + ", when not running from an EC2 instance (" + e.getMessage() + ")", e); + } catch (IOException e) { + throw new RuntimeException("An error occurred while fetching temporary credentials from IMDSv2 service", e); + } + } + + return new SigningServiceJcaProvider(new AmazonSigningService(params.keystore(), credentials, getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java new file mode 100644 index 00000000..1eab8fb1 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java @@ -0,0 +1,48 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.AzureKeyVaultSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class AzureKeyVaultKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "AZUREKEYVAULT"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure vault name"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass())); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java new file mode 100644 index 00000000..ed882b56 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java @@ -0,0 +1,48 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.AzureTrustedSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class AzureTrustedSigningKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "TRUSTEDSIGNING"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (.codesigning.azure.net)"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass())); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java new file mode 100644 index 00000000..75dbc407 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java @@ -0,0 +1,46 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.DigiCertOneSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class DigiCertOneKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "DIGICERTONE"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.storepass() == null || params.storepass().split("\\|").length != 3) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the DigiCert ONE API key and the client certificate: ||"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String[] elements = params.storepass().split("\\|"); + return new SigningServiceJcaProvider(new DigiCertOneSigningService(params.keystore(), elements[0], params.createFile(elements[1]), elements[2])); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java new file mode 100644 index 00000000..46c8a7f5 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java @@ -0,0 +1,53 @@ +/* + * 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; + +import java.io.IOException; +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.ESignerSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class ESignerKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "ESIGNER"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.storepass() == null || !params.storepass().contains("|")) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SSL.com username and password: |"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String[] elements = params.storepass().split("\\|", 2); + String endpoint = params.keystore() != null ? params.keystore() : "https://cs.ssl.com"; + try { + return new SigningServiceJcaProvider(new ESignerSigningService(endpoint, elements[0], elements[1])); + } catch (IOException e) { + throw new IllegalStateException("Authentication failed with SSL.com", e); + } + } +} + diff --git a/jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java new file mode 100644 index 00000000..6e46e9a8 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java @@ -0,0 +1,35 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class ETokenKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "ETOKEN"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return SafeNetEToken.getProvider(); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java new file mode 100644 index 00000000..cecebacb --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java @@ -0,0 +1,63 @@ +/* + * 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; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +abstract class FileBasedKeyStoreType extends AbstractKeyStoreType { + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); + } + if (!params.createFile(params.keystore()).exists()) { + throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found"); + } + if (params.keypass() == null && params.storepass() != null) { + // reuse the storepass as the keypass + params.keypass(params.storepass()); + } + } + + boolean hasSignature(File file, long signature, long mask) { + if (file.exists()) { + try (FileInputStream in = new FileInputStream(file)) { + byte[] header = new byte[4]; + in.read(header); + ByteBuffer buffer = ByteBuffer.wrap(header); + if ((buffer.getInt(0) & mask) == signature) { + return true; + } + } catch (IOException e) { + throw new RuntimeException("Unable to load the keystore " + file, e); + } + } + + return false; + } + + /** + * Tells if the specified file is a keystore of this type. + * + * @param file the path to the keystore + */ + abstract boolean isSupported(File file); +} diff --git a/jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java new file mode 100644 index 00000000..a04a4591 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java @@ -0,0 +1,62 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.GaraSignCredentials; +import net.jsign.jca.GaraSignSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class GaraSignKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "GARASIGN"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.storepass() == null || params.storepass().split("\\|").length > 3) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the GaraSign username/password and/or the path to the keystore containing the TLS client certificate: |, , or ||"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String[] elements = params.storepass().split("\\|"); + String username = null; + String password = null; + String certificate = null; + if (elements.length == 1) { + certificate = elements[0]; + } else if (elements.length == 2) { + username = elements[0]; + password = elements[1]; + } else if (elements.length == 3) { + username = elements[0]; + password = elements[1]; + certificate = elements[2]; + } + + GaraSignCredentials credentials = new GaraSignCredentials(username, password, certificate, params.keypass()); + return new SigningServiceJcaProvider(new GaraSignSigningService(params.keystore(), credentials)); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java new file mode 100644 index 00000000..dfc8eb80 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java @@ -0,0 +1,54 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.GoogleCloudSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class GoogleCloudKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "GOOGLECLOUD"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Goole Cloud keyring"); + } + if (!params.keystore().matches("projects/[^/]+/locations/[^/]+/keyRings/[^/]+")) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the path of the keyring (projects/{projectName}/locations/{location}/keyRings/{keyringName})"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Goole Cloud API access token"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new GoogleCloudSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java new file mode 100644 index 00000000..560d1ac8 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java @@ -0,0 +1,51 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.HashiCorpVaultSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class HashiCorpVaultKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "HASHICORPVAULT"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the HashiCorp Vault secrets engine URL"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the HashiCorp Vault token"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java new file mode 100644 index 00000000..87e84ce9 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java @@ -0,0 +1,36 @@ +/* + * 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; + +import java.io.File; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class JCEKSKeyStoreType extends FileBasedKeyStoreType { + + @Override + public String name() { + return "JCEKS"; + } + + @Override + boolean isSupported(File file) { + String filename = file.getName().toLowerCase(); + return hasSignature(file, 0xCECECECEL, 0xFFFFFFFFL) || filename.endsWith(".jceks"); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java new file mode 100644 index 00000000..628c9d9c --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java @@ -0,0 +1,36 @@ +/* + * 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; + +import java.io.File; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class JKSKeyStoreType extends FileBasedKeyStoreType { + + @Override + public String name() { + return "JKS"; + } + + @Override + boolean isSupported(File file) { + String filename = file.getName().toLowerCase(); + return hasSignature(file, 0xFEEDFEEDL, 0xFFFFFFFFL) || filename.endsWith(".jks"); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java index 51d75f0b..57c00423 100644 --- a/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java +++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java @@ -24,6 +24,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Provider; +import java.util.ServiceLoader; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -140,7 +141,7 @@ KeyStoreType storetype() { if (!file.isFile()) { throw new IllegalArgumentException("Keystore file '" + keystore + "' not found"); } - storetype = KeyStoreType.of(file); + storetype = getType(file); if (storetype == null) { throw new IllegalArgumentException("Keystore type of '" + keystore + "' not recognized"); } @@ -149,6 +150,21 @@ KeyStoreType storetype() { return storetype; } + /** + * Guess the type of the keystore from the header or the extension of the file. + * + * @param file the path to the keystore + */ + static KeyStoreType getType(File file) { + for (KeyStoreType storetype : ServiceLoader.load(KeyStoreType.class)) { + if (storetype instanceof FileBasedKeyStoreType && ((FileBasedKeyStoreType) storetype).isSupported(file)) { + return storetype; + } + } + + return null; + } + /** * Sets the password to access the private key. The password can be loaded from a file by using the file: * prefix followed by the path of the file, or from an environment variable by using the env: prefix diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java index 3f92aa29..5ef62cc3 100644 --- a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java +++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java @@ -16,179 +16,40 @@ package net.jsign; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.UnknownServiceException; -import java.nio.ByteBuffer; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.PrivateKey; import java.security.Provider; -import java.security.Security; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.ServiceLoader; import java.util.Set; -import java.util.function.Function; -import javax.smartcardio.CardException; - -import net.jsign.jca.AmazonCredentials; -import net.jsign.jca.AmazonSigningService; -import net.jsign.jca.AzureKeyVaultSigningService; -import net.jsign.jca.AzureTrustedSigningService; -import net.jsign.jca.DigiCertOneSigningService; -import net.jsign.jca.ESignerSigningService; -import net.jsign.jca.GaraSignCredentials; -import net.jsign.jca.GaraSignSigningService; -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.SignServerCredentials; -import net.jsign.jca.SignServerSigningService; -import net.jsign.jca.SigningServiceJcaProvider; +import java.util.stream.StreamSupport; /** * Type of a keystore. * * @since 5.0 */ -public enum KeyStoreType { +public interface KeyStoreType { /** Not a keystore, a private key file and a certificate file are provided separately and assembled into an in-memory keystore */ - NONE(true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keyfile() == null) { - throw new IllegalArgumentException("keyfile " + params.parameterName() + " must be set"); - } - if (!params.keyfile().exists()) { - throw new IllegalArgumentException("The keyfile " + params.keyfile() + " couldn't be found"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - if (!params.certfile().exists()) { - throw new IllegalArgumentException("The certfile " + params.certfile() + " couldn't be found"); - } - } - - @Override - KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { - // load the certificate chain - Certificate[] chain; - try { - chain = CertificateUtils.loadCertificateChain(params.certfile()); - } catch (Exception e) { - throw new KeyStoreException("Failed to load the certificate from " + params.certfile(), e); - } - - // load the private key - PrivateKey privateKey; - try { - privateKey = PrivateKeyUtils.load(params.keyfile(), params.keypass() != null ? params.keypass() : params.storepass()); - } catch (Exception e) { - throw new KeyStoreException("Failed to load the private key from " + params.keyfile(), e); - } - - // build the in-memory keystore - KeyStore ks = KeyStore.getInstance("JKS"); - try { - ks.load(null, null); - String keypass = params.keypass(); - ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain); - } catch (Exception e) { - throw new KeyStoreException(e); - } - - return ks; - } - }, + KeyStoreType NONE = KeyStoreType.valueOf("NONE"); /** Java keystore */ - JKS(true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - if (!params.createFile(params.keystore()).exists()) { - throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found"); - } - if (params.keypass() == null && params.storepass() != null) { - // reuse the storepass as the keypass - params.keypass(params.storepass()); - } - } - }, + KeyStoreType JKS = KeyStoreType.valueOf("JKS"); /** JCE keystore */ - JCEKS(true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - if (!params.createFile(params.keystore()).exists()) { - throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found"); - } - if (params.keypass() == null && params.storepass() != null) { - // reuse the storepass as the keypass - params.keypass(params.storepass()); - } - } - }, + KeyStoreType JCEKS = KeyStoreType.valueOf("JCEKS"); /** PKCS#12 keystore */ - PKCS12(true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - if (!params.createFile(params.keystore()).exists()) { - throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found"); - } - if (params.keypass() == null && params.storepass() != null) { - // reuse the storepass as the keypass - params.keypass(params.storepass()); - } - } - }, + KeyStoreType PKCS12 = KeyStoreType.valueOf("PKCS12"); /** * PKCS#11 hardware token. The keystore parameter specifies either the name of the provider defined * in jre/lib/security/java.security or the path to the * SunPKCS11 configuration file. */ - PKCS11(false, true) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - // the keystore parameter is either the provider name or the SunPKCS11 configuration file - if (params.createFile(params.keystore()).exists()) { - return ProviderUtils.createSunPKCS11Provider(params.keystore()); - } else if (params.keystore().startsWith("SunPKCS11-")) { - Provider provider = Security.getProvider(params.keystore()); - if (provider == null) { - throw new IllegalArgumentException("Security provider " + params.keystore() + " not found"); - } - return provider; - } else { - throw new IllegalArgumentException("keystore " + params.parameterName() + " should either refer to the SunPKCS11 configuration file or to the name of the provider configured in jre/lib/security/java.security"); - } - } - }, + KeyStoreType PKCS11 = KeyStoreType.valueOf("PKCS11"); /** * OpenPGP card. OpenPGP cards contain up to 3 keys, one for signing, one for encryption, and one for authentication. @@ -198,35 +59,14 @@ Provider getProvider(KeyStoreBuilder params) { * the keystore parameter can be used to specify the name of the one to use. This keystore type doesn't require * any external library to be installed. */ - OPENPGP(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - try { - return new SigningServiceJcaProvider(new OpenPGPCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); - } catch (CardException e) { - throw new IllegalStateException("Failed to initialize the OpenPGP card", e); - } - } - }, + KeyStoreType OPENPGP = KeyStoreType.valueOf("OPENPGP"); /** * OpenSC supported smart card. * This keystore requires the installation of OpenSC. * If multiple devices are connected, the keystore parameter can be used to specify the name of the one to use. */ - OPENSC(false, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return OpenSC.getProvider(params.keystore()); - } - }, + KeyStoreType OPENSC = KeyStoreType.valueOf("OPENSC"); /** * PIV card. PIV cards contain up to 24 private keys and certificates. The alias to select the key is either, @@ -235,23 +75,7 @@ Provider getProvider(KeyStoreBuilder params) { * signature key). If multiple devices are connected, the keystore parameter can be used to specify the name * of the one to use. This keystore type doesn't require any external library to be installed. */ - PIV(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - try { - return new SigningServiceJcaProvider(new PIVCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); - } catch (CardException e) { - throw new IllegalStateException("Failed to initialize the PIV card", e); - } - } - }, + KeyStoreType PIV = KeyStoreType.valueOf("PIV"); /** * Nitrokey HSM. This keystore requires the installation of OpenSC. @@ -259,32 +83,14 @@ Provider getProvider(KeyStoreBuilder params) { * certificate must be imported into the Nitrokey (using the gnupg writecert command). Keys without certificates * are ignored. Otherwise the {@link #OPENPGP} type should be used. */ - NITROKEY(false, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return OpenSC.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey"); - } - }, + KeyStoreType NITROKEY = KeyStoreType.valueOf("NITROKEY"); /** * YubiKey PIV. This keystore requires the ykcs11 library from the Yubico PIV Tool * to be installed at the default location. On Windows, the path to the library must be specified in the * PATH environment variable. */ - YUBIKEY(false, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return YubiKey.getProvider(); - } - - @Override - Set getAliases(KeyStore keystore) throws KeyStoreException { - Set aliases = super.getAliases(keystore); - // the attestation certificate is never used for signing - aliases.remove("X.509 Certificate for PIV Attestation"); - return aliases; - } - }, + KeyStoreType YUBIKEY = KeyStoreType.valueOf("YUBIKEY"); /** * AWS Key Management Service (KMS). AWS KMS stores only the private key, the certificate must be provided @@ -298,131 +104,34 @@ Set getAliases(KeyStore keystore) throws KeyStoreException { *

In any case, the credentials must allow the following actions: kms:ListKeys, * kms:DescribeKey and kms:Sign.

* */ - AWS(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the AWS region"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - AmazonCredentials credentials; - if (params.storepass() != null) { - credentials = AmazonCredentials.parse(params.storepass()); - } else { - try { - credentials = AmazonCredentials.getDefault(); - } catch (UnknownServiceException e) { - throw new IllegalArgumentException("storepass " + params.parameterName() - + " must specify the AWS credentials: |[|]" - + ", when not running from an EC2 instance (" + e.getMessage() + ")", e); - } catch (IOException e) { - throw new RuntimeException("An error occurred while fetching temporary credentials from IMDSv2 service", e); - } - } - - return new SigningServiceJcaProvider(new AmazonSigningService(params.keystore(), credentials, getCertificateStore(params))); - } - }, + KeyStoreType AWS = KeyStoreType.valueOf("AWS"); /** * Azure Key Vault. The keystore parameter specifies the name of the key vault, either the short name * (e.g. myvault), or the full URL (e.g. https://myvault.vault.azure.net). * The Azure API access token is used as the keystore password. */ - AZUREKEYVAULT(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure vault name"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass())); - } - }, + KeyStoreType AZUREKEYVAULT = KeyStoreType.valueOf("AZUREKEYVAULT"); /** * DigiCert ONE. Certificates and keys stored in the DigiCert ONE Secure Software Manager can be used directly * without installing the DigiCert client tools. The API key, the PKCS#12 keystore holding the client certificate * and its password are combined to form the storepass parameter: <api-key>|<keystore>|<password>. */ - DIGICERTONE(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null || params.storepass().split("\\|").length != 3) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the DigiCert ONE API key and the client certificate: ||"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String[] elements = params.storepass().split("\\|"); - return new SigningServiceJcaProvider(new DigiCertOneSigningService(params.keystore(), elements[0], params.createFile(elements[1]), elements[2])); - } - }, + KeyStoreType DIGICERTONE = KeyStoreType.valueOf("DIGICERTONE"); /** * SSL.com eSigner. The SSL.com username and password are used as the keystore password (<username>|<password>), * and the base64 encoded TOTP secret is used as the key password. */ - ESIGNER(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null || !params.storepass().contains("|")) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SSL.com username and password: |"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String[] elements = params.storepass().split("\\|", 2); - String endpoint = params.keystore() != null ? params.keystore() : "https://cs.ssl.com"; - try { - return new SigningServiceJcaProvider(new ESignerSigningService(endpoint, elements[0], elements[1])); - } catch (IOException e) { - throw new IllegalStateException("Authentication failed with SSL.com", e); - } - } - }, + KeyStoreType ESIGNER = KeyStoreType.valueOf("ESIGNER"); /** * Google Cloud KMS. Google Cloud KMS stores only the private key, the certificate must be provided separately. * The keystore parameter references the path of the keyring. The alias can specify either the full path of the key, * or only the short name. If the version is omitted the most recent one will be picked automatically. */ - GOOGLECLOUD(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Goole Cloud keyring"); - } - if (!params.keystore().matches("projects/[^/]+/locations/[^/]+/keyRings/[^/]+")) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the path of the keyring (projects/{projectName}/locations/{location}/keyRings/{keyringName})"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Goole Cloud API access token"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new GoogleCloudSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); - } - }, + KeyStoreType GOOGLECLOUD = KeyStoreType.valueOf("GOOGLECLOUD"); /** * HashiCorp Vault secrets engine (Transit or GCPKMS). The certificate must be provided separately. The keystore @@ -430,36 +139,13 @@ Provider getProvider(KeyStoreBuilder params) { * The alias parameter specifies the name of the key in Vault. For the Google Cloud KMS secrets engine, the version * of the Google Cloud key is appended to the key name, separated by a colon character. (mykey:1). */ - HASHICORPVAULT(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the HashiCorp Vault secrets engine URL"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the HashiCorp Vault token"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); - } - }, + KeyStoreType HASHICORPVAULT = KeyStoreType.valueOf("HASHICORPVAULT"); /** * SafeNet eToken * This keystore requires the installation of the SafeNet Authentication Client. */ - ETOKEN(false, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return SafeNetEToken.getProvider(); - } - }, + KeyStoreType ETOKEN = KeyStoreType.valueOf("ETOKEN"); /** * Oracle Cloud Infrastructure Key Management Service. This keystore requires the configuration file @@ -473,38 +159,7 @@ Provider getProvider(KeyStoreBuilder params) { *

The certificate must be provided separately using the certfile parameter. The alias specifies the OCID * of the key.

*/ - ORACLECLOUD(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 { - File config = null; - String profile = null; - if (params.storepass() != null) { - String[] elements = params.storepass().split("\\|", 2); - config = new File(elements[0]); - if (elements.length > 1) { - profile = elements[1]; - } - } - credentials.load(config, profile); - credentials.loadFromEnvironment(); - if (params.keypass() != null) { - credentials.setPassphrase(params.keypass()); - } - } catch (IOException e) { - throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e); - } - return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params))); - } - }, + KeyStoreType ORACLECLOUD = KeyStoreType.valueOf("ORACLECLOUD"); /** * Azure Trusted Signing Service. The keystore parameter specifies the API endpoint (for example @@ -513,52 +168,9 @@ Provider getProvider(KeyStoreBuilder params) { * *
  az account get-access-token --resource https://codesigning.azure.net
*/ - TRUSTEDSIGNING(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (.codesigning.azure.net)"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass())); - } - }, + KeyStoreType TRUSTEDSIGNING = KeyStoreType.valueOf("TRUSTEDSIGNING"); - GARASIGN(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null || params.storepass().split("\\|").length > 3) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the GaraSign username/password and/or the path to the keystore containing the TLS client certificate: |, , or ||"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String[] elements = params.storepass().split("\\|"); - String username = null; - String password = null; - String certificate = null; - if (elements.length == 1) { - certificate = elements[0]; - } else if (elements.length == 2) { - username = elements[0]; - password = elements[1]; - } else if (elements.length == 3) { - username = elements[0]; - password = elements[1]; - certificate = elements[2]; - } - - GaraSignCredentials credentials = new GaraSignCredentials(username, password, certificate, params.keypass()); - return new SigningServiceJcaProvider(new GaraSignSigningService(params.keystore(), credentials)); - } - }, + KeyStoreType GARASIGN = KeyStoreType.valueOf("GARASIGN"); /** * Keyfactor SignServer. This keystore requires a Plain Signer worker configured to allow client-side hashing (with @@ -570,144 +182,56 @@ Provider getProvider(KeyStoreBuilder params) { * specified in the keypass parameter. The keystore parameter references the URL of the SignServer REST API. The * alias parameter specifies the id or the name of the worker.

*/ - SIGNSERVER(false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignServer API endpoint (e.g. https://example.com/signserver/)"); - } - if (params.storepass() != null && params.storepass().split("\\|").length > 2) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignServer username/password or the path to the keystore containing the TLS client certificate: | or "); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String username = null; - String password = null; - String certificate = null; - if (params.storepass() != null) { - String[] elements = params.storepass().split("\\|"); - if (elements.length == 1) { - certificate = elements[0]; - } else if (elements.length == 2) { - username = elements[0]; - password = elements[1]; - } - } - - SignServerCredentials credentials = new SignServerCredentials(username, password, certificate, params.keypass()); - return new SigningServiceJcaProvider(new SignServerSigningService(params.keystore(), credentials)); - } - }; - - - /** Tells if the keystore is contained in a local file */ - private final boolean fileBased; + KeyStoreType SIGNSERVER = KeyStoreType.valueOf("SIGNSERVER"); - /** Tells if the keystore is actually a PKCS#11 keystore */ - private final boolean pkcs11; - - KeyStoreType(boolean fileBased, boolean pkcs11) { - this.fileBased = fileBased; - this.pkcs11 = pkcs11; - } + /** + * Returns the name of the keystore type. + */ + String name(); /** * Validates the keystore parameters. */ - void validate(KeyStoreBuilder params) throws IllegalArgumentException { + default void validate(KeyStoreBuilder params) throws IllegalArgumentException { } /** * Returns the security provider to use the keystore. */ - Provider getProvider(KeyStoreBuilder params) { + default Provider getProvider(KeyStoreBuilder params) { return null; } /** * Build the keystore. */ - KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { - KeyStore ks; - try { - KeyStoreType storetype = pkcs11 ? PKCS11 : this; - if (provider != null) { - ks = KeyStore.getInstance(storetype.name(), provider); - } else { - ks = KeyStore.getInstance(storetype.name()); - } - } catch (KeyStoreException e) { - throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e); - } - - try { - try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) { - ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null); - } - } catch (Exception e) { - throw new KeyStoreException("Unable to load the " + name() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e); - } - - return ks; - } + KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException; /** * Returns the aliases of the keystore available for signing. */ - Set getAliases(KeyStore keystore) throws KeyStoreException { + default Set getAliases(KeyStore keystore) throws KeyStoreException { return new LinkedHashSet<>(Collections.list(keystore.aliases())); } /** - * Guess the type of the keystore from the header or the extension of the file. + * Returns the storetype with the specified name. * - * @param path the path to the keystore + * @param name the name of the storetype + * @return the storetype with the specified name + * @throws IllegalArgumentException if the storetype specified isn't supported */ - static KeyStoreType of(File path) { - // guess the type of the keystore from the header of the file - if (path.exists()) { - try (FileInputStream in = new FileInputStream(path)) { - byte[] header = new byte[4]; - in.read(header); - ByteBuffer buffer = ByteBuffer.wrap(header); - if (buffer.get(0) == 0x30) { - return PKCS12; - } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) { - return JCEKS; - } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) { - return JKS; - } - } catch (IOException e) { - throw new RuntimeException("Unable to load the keystore " + path, e); + static KeyStoreType valueOf(String name) { + for (KeyStoreType storetype : ServiceLoader.load(KeyStoreType.class)) { + if (name.equals(storetype.name())) { + return storetype; } } - // guess the type of the keystore from the extension of the file - String filename = path.getName().toLowerCase(); - if (filename.endsWith(".p12") || filename.endsWith(".pfx")) { - return PKCS12; - } else if (filename.endsWith(".jceks")) { - return JCEKS; - } else if (filename.endsWith(".jks")) { - return JKS; - } else { - return null; - } + throw new IllegalArgumentException("Unsupported KeyStore type: " + name); } - private static Function getCertificateStore(KeyStoreBuilder params) { - return alias -> { - if (alias == null || alias.isEmpty()) { - return null; - } - - try { - return CertificateUtils.loadCertificateChain(params.certfile()); - } catch (IOException | CertificateException e) { - throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e); - } - }; + static KeyStoreType[] values() { + return StreamSupport.stream(ServiceLoader.load(KeyStoreType.class).spliterator(), false).toArray(KeyStoreType[]::new); } } diff --git a/jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java new file mode 100644 index 00000000..8afb7b44 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java @@ -0,0 +1,35 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class NitrokeyKeyStoreType extends PKCS11KeyStoreType { + + @Override + public String name() { + return "NITROKEY"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return OpenSC.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey"); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java new file mode 100644 index 00000000..3f6b5f52 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java @@ -0,0 +1,81 @@ +/* + * 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; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.cert.Certificate; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class NoneKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "NONE"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keyfile() == null) { + throw new IllegalArgumentException("keyfile " + params.parameterName() + " must be set"); + } + if (!params.keyfile().exists()) { + throw new IllegalArgumentException("The keyfile " + params.keyfile() + " couldn't be found"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + if (!params.certfile().exists()) { + throw new IllegalArgumentException("The certfile " + params.certfile() + " couldn't be found"); + } + } + + @Override + public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { + // load the certificate chain + Certificate[] chain; + try { + chain = CertificateUtils.loadCertificateChain(params.certfile()); + } catch (Exception e) { + throw new KeyStoreException("Failed to load the certificate from " + params.certfile(), e); + } + + // load the private key + PrivateKey privateKey; + try { + privateKey = PrivateKeyUtils.load(params.keyfile(), params.keypass() != null ? params.keypass() : params.storepass()); + } catch (Exception e) { + throw new KeyStoreException("Failed to load the private key from " + params.keyfile(), e); + } + + // build the in-memory keystore + KeyStore ks = KeyStore.getInstance("JKS"); + try { + ks.load(null, null); + String keypass = params.keypass(); + ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain); + } catch (Exception e) { + throw new KeyStoreException(e); + } + + return ks; + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java new file mode 100644 index 00000000..24a81b76 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java @@ -0,0 +1,50 @@ +/* + * 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; + +import java.security.Provider; +import javax.smartcardio.CardException; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.OpenPGPCardSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class OpenPGPKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "OPENPGP"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + try { + return new SigningServiceJcaProvider(new OpenPGPCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); + } catch (CardException e) { + throw new IllegalStateException("Failed to initialize the OpenPGP card", e); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java new file mode 100644 index 00000000..41d47225 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java @@ -0,0 +1,35 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class OpenSCKeyStoreType extends PKCS11KeyStoreType { + + @Override + public String name() { + return "OPENSC"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return OpenSC.getProvider(params.keystore()); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java new file mode 100644 index 00000000..136e9d59 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java @@ -0,0 +1,67 @@ +/* + * 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; + +import java.io.File; +import java.io.IOException; +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.OracleCloudCredentials; +import net.jsign.jca.OracleCloudSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class OracleCloudKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "ORACLECLOUD"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + OracleCloudCredentials credentials = new OracleCloudCredentials(); + try { + File config = null; + String profile = null; + if (params.storepass() != null) { + String[] elements = params.storepass().split("\\|", 2); + config = new File(elements[0]); + if (elements.length > 1) { + profile = elements[1]; + } + } + credentials.load(config, profile); + credentials.loadFromEnvironment(); + if (params.keypass() != null) { + credentials.setPassphrase(params.keypass()); + } + } catch (IOException e) { + throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e); + } + return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java new file mode 100644 index 00000000..12613956 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java @@ -0,0 +1,50 @@ +/* + * 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; + +import java.security.Provider; +import javax.smartcardio.CardException; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.PIVCardSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class PIVKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "PIV"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + try { + return new SigningServiceJcaProvider(new PIVCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); + } catch (CardException e) { + throw new IllegalStateException("Failed to initialize the PIV card", e); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java new file mode 100644 index 00000000..19f43dce --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java @@ -0,0 +1,78 @@ +/* + * 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; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.Security; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class PKCS11KeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "PKCS11"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + // the keystore parameter is either the provider name or the SunPKCS11 configuration file + if (params.createFile(params.keystore()).exists()) { + return ProviderUtils.createSunPKCS11Provider(params.keystore()); + } else if (params.keystore().startsWith("SunPKCS11-")) { + Provider provider = Security.getProvider(params.keystore()); + if (provider == null) { + throw new IllegalArgumentException("Security provider " + params.keystore() + " not found"); + } + return provider; + } else { + throw new IllegalArgumentException("keystore " + params.parameterName() + " should either refer to the SunPKCS11 configuration file or to the name of the provider configured in jre/lib/security/java.security"); + } + } + + @Override + public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { + KeyStore ks; + try { + if (provider != null) { + ks = KeyStore.getInstance("PKCS11", provider); + } else { + ks = KeyStore.getInstance("PKCS11"); + } + } catch (KeyStoreException e) { + throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e); + } + + try { + ks.load(null, params.storepass() != null ? params.storepass().toCharArray() : null); + } catch (Exception e) { + throw new KeyStoreException("Unable to load the keystore " + params.keystore(), e); + } + + return ks; + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java new file mode 100644 index 00000000..2cca7d6a --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java @@ -0,0 +1,36 @@ +/* + * 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; + +import java.io.File; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class PKCS12KeyStoreType extends FileBasedKeyStoreType { + + @Override + public String name() { + return "PKCS12"; + } + + @Override + boolean isSupported(File file) { + String filename = file.getName().toLowerCase(); + return hasSignature(file, 0x30000000L, 0xFF000000L) || filename.endsWith(".p12") || filename.endsWith(".pfx"); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java new file mode 100644 index 00000000..178e7666 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java @@ -0,0 +1,63 @@ +/* + * 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; + +import java.security.Provider; + +import org.kohsuke.MetaInfServices; + +import net.jsign.jca.SignServerCredentials; +import net.jsign.jca.SignServerSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +@MetaInfServices(KeyStoreType.class) +public class SignServerKeyStoreType extends AbstractKeyStoreType { + + @Override + public String name() { + return "SIGNSERVER"; + } + + @Override + public void validate(KeyStoreBuilder params) { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignServer API endpoint (e.g. https://example.com/signserver/)"); + } + if (params.storepass() != null && params.storepass().split("\\|").length > 2) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignServer username/password or the path to the keystore containing the TLS client certificate: | or "); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String username = null; + String password = null; + String certificate = null; + if (params.storepass() != null) { + String[] elements = params.storepass().split("\\|"); + if (elements.length == 1) { + certificate = elements[0]; + } else if (elements.length == 2) { + username = elements[0]; + password = elements[1]; + } + } + + SignServerCredentials credentials = new SignServerCredentials(username, password, certificate, params.keypass()); + return new SigningServiceJcaProvider(new SignServerSigningService(params.keystore(), credentials)); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java new file mode 100644 index 00000000..cee1f911 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java @@ -0,0 +1,46 @@ +/* + * 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; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.util.Set; + +import org.kohsuke.MetaInfServices; + +@MetaInfServices(KeyStoreType.class) +public class YubikeyKeyStoreType extends PKCS11KeyStoreType { + + @Override + public String name() { + return "YUBIKEY"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return YubiKey.getProvider(); + } + + @Override + public Set getAliases(KeyStore keystore) throws KeyStoreException { + Set aliases = super.getAliases(keystore); + // the attestation certificate is never used for signing + aliases.remove("X.509 Certificate for PIV Attestation"); + return aliases; + } +} diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java index bcb029b2..2d3bae49 100644 --- a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java +++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java @@ -457,4 +457,45 @@ public void testLowerCaseStoreType() { KeyStoreBuilder builder = new KeyStoreBuilder().storetype("pkcs12"); assertEquals("storetype", PKCS12, builder.storetype()); } + + @Test + public void testGetType() { + assertEquals(PKCS12, KeyStoreBuilder.getType(new File("keystore.p12"))); + assertEquals(PKCS12, KeyStoreBuilder.getType(new File("keystore.pfx"))); + assertEquals(JCEKS, KeyStoreBuilder.getType(new File("keystore.jceks"))); + assertEquals(JKS, KeyStoreBuilder.getType(new File("keystore.jks"))); + assertNull(KeyStoreBuilder.getType(new File("keystore.unknown"))); + } + + @Test + public void testGetTypePKCS12FromHeader() throws Exception { + File source = new File("target/test-classes/keystores/keystore.p12"); + File target = new File("target/test-classes/keystores/keystore.p12.ext"); + FileUtils.copyFile(source, target); + + assertEquals(PKCS12, KeyStoreBuilder.getType(target)); + } + + @Test + public void testGetTypeJCEKSFromHeader() throws Exception { + File source = new File("target/test-classes/keystores/keystore.jceks"); + File target = new File("target/test-classes/keystores/keystore.jceks.ext"); + FileUtils.copyFile(source, target); + + assertEquals(JCEKS, KeyStoreBuilder.getType(target)); + } + + @Test + public void testGetTypeJKSFromHeader() throws Exception { + File source = new File("target/test-classes/keystores/keystore.jks"); + File target = new File("target/test-classes/keystores/keystore.jks.ext"); + FileUtils.copyFile(source, target); + + assertEquals(JKS, KeyStoreBuilder.getType(target)); + } + + @Test + public void testGetTypeUnknown() { + assertNull(KeyStoreBuilder.getType(new File("target/test-classes/keystores/jsign-root-ca.pem"))); + } } diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java index 73f027a5..26a5f5a7 100644 --- a/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java +++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java @@ -16,54 +16,31 @@ package net.jsign; -import java.io.File; +import java.util.Arrays; +import java.util.List; -import org.apache.commons.io.FileUtils; import org.junit.Test; -import static net.jsign.KeyStoreType.*; import static org.junit.Assert.*; public class KeyStoreTypeTest { @Test - public void testGetType() throws Exception { - assertEquals(PKCS12, KeyStoreType.of(new File("keystore.p12"))); - assertEquals(PKCS12, KeyStoreType.of(new File("keystore.pfx"))); - assertEquals(JCEKS, KeyStoreType.of(new File("keystore.jceks"))); - assertEquals(JKS, KeyStoreType.of(new File("keystore.jks"))); - assertNull(KeyStoreType.of(new File("keystore.unknown"))); + public void testValueOf() { + assertEquals(KeyStoreType.JCEKS, KeyStoreType.valueOf("JCEKS")); + assertEquals(KeyStoreType.OPENSC, KeyStoreType.valueOf("OPENSC")); + assertEquals(KeyStoreType.OPENPGP, KeyStoreType.valueOf("OPENPGP")); + assertEquals(KeyStoreType.DIGICERTONE, KeyStoreType.valueOf("DIGICERTONE")); } @Test - public void testGetTypePKCS12FromHeader() throws Exception { - File source = new File("target/test-classes/keystores/keystore.p12"); - File target = new File("target/test-classes/keystores/keystore.p12.ext"); - FileUtils.copyFile(source, target); - - assertEquals(PKCS12, KeyStoreType.of(target)); - } - - @Test - public void testGetTypeJCEKSFromHeader() throws Exception { - File source = new File("target/test-classes/keystores/keystore.jceks"); - File target = new File("target/test-classes/keystores/keystore.jceks.ext"); - FileUtils.copyFile(source, target); - - assertEquals(JCEKS, KeyStoreType.of(target)); - } - - @Test - public void testGetTypeJKSFromHeader() throws Exception { - File source = new File("target/test-classes/keystores/keystore.jks"); - File target = new File("target/test-classes/keystores/keystore.jks.ext"); - FileUtils.copyFile(source, target); - - assertEquals(JKS, KeyStoreType.of(target)); - } - - @Test - public void testGetTypeUnknown() throws Exception { - assertNull(KeyStoreType.of(new File("target/test-classes/keystores/jsign-root-ca.pem"))); + public void testValues() { + List values = Arrays.asList(KeyStoreType.values()); + assertTrue(values.contains(KeyStoreType.NONE)); + assertTrue(values.contains(KeyStoreType.PKCS12)); + assertTrue(values.contains(KeyStoreType.JKS)); + assertTrue(values.contains(KeyStoreType.PKCS11)); + assertTrue(values.contains(KeyStoreType.PIV)); + assertTrue(values.contains(KeyStoreType.AWS)); } }