From 30318f805f726d497daaa235afe0379241ad0b13 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Mon, 8 Jul 2024 13:29:08 +0200 Subject: [PATCH 01/25] Adding bc-api dependency --- pom.xml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d40bb51..9c35d41 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 999999-SNAPSHOT - 2.387.3 + 2.426.3 jenkinsci/${project.artifactId}-plugin @@ -131,12 +131,22 @@ org.jenkins-ci.plugins credentials + + org.jenkins-ci.plugins + bouncycastle-api + + + + org.bouncycastle + bc-fips + 1.0.2.4 + test + io.jenkins.plugins.mina-sshd-api mina-sshd-api-core test - org.jenkins-ci.plugins cloudbees-folder From 878d63e33d5f6b6b8098868e0f2e5c76218dcb94 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Mon, 8 Jul 2024 13:39:08 +0200 Subject: [PATCH 02/25] Added FIPS compliance check --- .../impl/BasicSSHUserPrivateKey.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 88577c0..e64d3b3 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -36,6 +36,9 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -45,7 +48,9 @@ import java.util.logging.Logger; import java.util.stream.Collectors; +import jenkins.bouncycastle.api.PEMEncodable; import jenkins.model.Jenkins; +import jenkins.security.FIPS140; import net.jcip.annotations.GuardedBy; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; @@ -61,6 +66,8 @@ public class BasicSSHUserPrivateKey extends BaseSSHUser implements SSHUserPrivat */ private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(BasicSSHUserPrivateKey.class.getName()); + /** * The password. */ @@ -101,6 +108,7 @@ public BasicSSHUserPrivateKey(CredentialsScope scope, String id, String username super(scope, id, username, description); this.privateKeySource = privateKeySource == null ? new DirectEntryPrivateKeySource("") : privateKeySource; this.passphrase = fixEmpty(passphrase == null ? null : Secret.fromString(passphrase)); + checkKeyFipsCompliance(privateKeySource, this.passphrase); } private static Secret fixEmpty(Secret secret) { @@ -109,6 +117,7 @@ private static Secret fixEmpty(Secret secret) { @Override protected synchronized Object readResolve() { + checkKeyFipsCompliance(privateKeySource, passphrase); if (privateKeySource == null) { Secret passphrase = getPassphrase(); if (privateKeys != null) { @@ -171,6 +180,49 @@ public Secret getPassphrase() { return passphrase; } + /** + * Checks if provided key is compliant with FIPS 140-2. + * OpenSSH keys are not compliant (OpenSSH private key format ultimately contains a private key encrypted with a + * non-standard version of PBKDF2 that uses bcrypt as its core hash function, also the structure that contains the key is not ASN.1.) + * Only Ed25519 or RSA (with a minimum size of 1024, as it's used for identification, not signing) keys are accepted. + * Method will log and launch an {@link IllegalArgumentException} if key is not compliant. + * @param privateKeySource the keySource + * @param passphrase the secret used with the key (null if no secret provided) + */ + private static void checkKeyFipsCompliance(PrivateKeySource privateKeySource, Secret passphrase) { + if (!FIPS140.useCompliantAlgorithms()) { + return; // maintain existing behaviour if not in FIPS mode + } + if (privateKeySource == null || privateKeySource.getPrivateKeys().isEmpty()) { + return; + } + try { + char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); + PEMEncodable pem = PEMEncodable.decode(privateKeySource.getPrivateKeys().get(0), pass); + PrivateKey privateKey = pem.toPrivateKey(); + if (privateKey == null) { + LOGGER.log(Level.WARNING, "Private key can not be obtained from provided data."); + throw new IllegalArgumentException("Private key can not be obtained from provided data."); + } + if (privateKey instanceof RSAPrivateKey) { + if (((RSAPrivateKey) privateKey).getModulus().bitLength() < 1024) { + LOGGER.log(Level.WARNING, "Key size below 1024 for RSA keys is not accepted in FIPS mode."); + throw new IllegalArgumentException("Key size below 1024 for RSA keys is not accepted in FIPS mode."); + } + } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { + // Using algorithm name to check elliptic curve, as EdECPrivateKey is not available in jdk11 + LOGGER.log(Level.WARNING, String.format("Key algorithm %s is not accepted in FIPS mode.", privateKey.getAlgorithm())); + throw new IllegalArgumentException(String.format("Key algorithm %s is not accepted in FIPS mode.", privateKey.getAlgorithm())); + } + } catch (IOException ex) { // OpenSSH keys will raise this + LOGGER.log(Level.WARNING, "Provided private key is not FIPS compliant."); + throw new IllegalArgumentException("Provided private key is not FIPS compliant."); + } catch (UnrecoverableKeyException ex) { + LOGGER.log(Level.WARNING, "Key can not be recovered (possibly wrong passphrase?)."); + throw new IllegalArgumentException("Key can not be recovered (possibly wrong passphrase?)."); + } + } + /** * {@inheritDoc} */ From a79d5b71928efb5f1f3cab2595d1f2014206b349 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Mon, 8 Jul 2024 13:39:36 +0200 Subject: [PATCH 03/25] Testing --- .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 108 ++++++++++++++++++ .../credentials.xml | 34 ++++++ .../secrets/hudson.util.Secret | Bin 0 -> 272 bytes .../secrets/master.key | 1 + .../dsa2048 | 15 +++ .../ed25519 | 6 + .../openssh-rsa1024 | 17 +++ .../rsa1024 | 18 +++ .../rsa512 | 12 ++ .../unencrypted-rsa1024 | 16 +++ 10 files changed, 227 insertions(+) create mode 100644 src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/master.key create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/dsa2048 create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/ed25519 create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024 create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa1024 create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa512 create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/unencrypted-rsa1024 diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java new file mode 100644 index 0000000..b6d9962 --- /dev/null +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -0,0 +1,108 @@ +package com.cloudbees.jenkins.plugins.sshcredentials.impl; + +import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import com.cloudbees.plugins.credentials.CredentialsMatchers; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.CredentialsStore; +import com.cloudbees.plugins.credentials.domains.Domain; +import hudson.security.ACL; +import jenkins.security.FIPS140; +import org.apache.commons.io.FileUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.FlagRule; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.WithoutJenkins; +import org.jvnet.hudson.test.recipes.LocalData; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Paths; +import java.security.Security; +import java.util.Iterator; + +import static org.junit.Assert.*; + +public class BasicSSHUserPrivateKeyFIPSTest { + + @ClassRule + public static FlagRule fipsFlag = FlagRule.systemProperty(FIPS140.class.getName() + ".COMPLIANCE", "true"); + + @Rule public JenkinsRule r = new JenkinsRule(); + + @BeforeClass + public static void ensureBCIsAvailable() { + // Tests running without jenkins need the provider + if (Security.getProvider("BC") == null) { + Security.insertProviderAt(new BouncyCastleProvider(), 1); + } + } + + @Test + @Issue("JENKINS-73408") + @WithoutJenkins + public void nonCompliantKeysLaunchExceptionTest() throws IOException { + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", + getKey("rsa512"), "password", "Invalid size key")); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "openssh-rsa1024", "user", + getKey("openssh-rsa1024"), "password", "Invalid key format")); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "ed25519", "user", + getKey("ed25519"), "password", "Elliptic curve accepted key"); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", + getKey("rsa1024"), "password", "RSA 1024 accepted key"); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa1024", "user", + getKey("unencrypted-rsa1024"), null, "RSA 1024 with no encryption accepted key"); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", + getKey("rsa1024"), "NOT-password", "Wrong password avoids getting size or algorithm")); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "dsa2048", "user", + getKey("dsa2048"), null, "DSA is not accepted")); + } + + @Test + @Issue("JENKINS-73408") + public void invalidKeyIsNotSavedInFIPSModeTest() throws IOException { + BasicSSHUserPrivateKey entry = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "password", "RSA 1024 accepted key"); + Iterator stores = CredentialsProvider.lookupStores(r.jenkins).iterator(); + assertTrue(stores.hasNext()); + CredentialsStore store = stores.next(); + store.addCredentials(Domain.global(), entry); + store.save(); + // Valid key is saved + SSHUserPrivateKey cred = CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), + CredentialsMatchers.withId("rsa1024")); + assertNotNull(cred); + assertThrows(IllegalArgumentException.class, () -> store.addCredentials(Domain.global(), + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", getKey("rsa512"), "password", "Invalid size key"))); + store.save(); + // Invalid key threw an exception, so it wasn't saved + cred = CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), + CredentialsMatchers.withId("rsa512")); + assertNull(cred); + } + + @Test + @LocalData + @Issue("JENKINS-73408") + public void invalidKeysAreRemovedOnStartupTest() { + SSHUserPrivateKey cred = CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), + CredentialsMatchers.withId("valid-rsa-key")); + assertNotNull(cred); + cred = CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), + CredentialsMatchers.withId("invalid-rsa-key")); + assertNull(cred); + } + + private BasicSSHUserPrivateKey.DirectEntryPrivateKeySource getKey(String file) throws IOException { + String keyText = FileUtils.readFileToString(Paths.get("src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest").resolve(file).toFile(), Charset.defaultCharset()); + return new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(keyText); + } +} diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml new file mode 100644 index 0000000..344118c --- /dev/null +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml @@ -0,0 +1,34 @@ + + + + + + + + + + GLOBAL + valid-rsa-key + 1024 valid key + user + false + {AQAAABAAAAAQtMAQkpib9NQfKS7uUWgvN4nntFCdwA3cQYQVJ52bkEI=} + + {AQAAABAAAARAVVmNFM678Wo5RflGEU+aTod8ErVvRmIMuZHuoLRrRYoXAP5Oux3LDeCfoXg75FAuhBYsReq6j1IGKnSiagS4fExR4yjAkzjQiNPzIwW601UIZS+G2zJMijraczDecMik17wXtYqOz+mQZ7ayUbE7y2Yhtn+sPH2EeJUE39RcHbHf3w+L43w8Cbe/TwWuJFNrbPxhrtche8eIQfq75xS7JXTY1KwFrY4YKANCoBA6OYf//w40mJzNIp/LkRfykRJck4IWLkmc/UAN78teEKPkifT3d0w2uFU1lAmlF9LPZ4g6NBtp9cJgkpU81HMQqeuXKSgyfTPv47U9BvF2LWFgGxqUiSRm0Dw3yzvHdL8sf2otOdXOe+kp++jeEoscWu+CHXxE24qnGiQea0k60kigQIoGEoJgujqy35s/limZFrCWD1NJONBd3W238kjhDmIbVq/uiTeVwWsrbqAgiF0939ysvprpbs6TuwpP9TCgq+PjVYOVqF5TsIemBatYqSo5C+gvjf+qdYo78ga9WSSu0oJtU6MOIDyw1uIr/R6trsFWTx3uyQ42i4C/c2Vtptn5HBqvpAzGCwH4f48EYAapt92MTjp84vEy4ooiurCXqs1K0J9ml9jhr8YExZQwt8PPQW+hQnhzZU9jM2laJcSYS9lgX7ggCEBCPnUD8CWwDIzIk1P18aElFjNZuMhShIZtDZrIdxT+BX/BXATZ0Z5PBGRDUr3hSAtqUcqUZs6Hm7elOtSd2N8898XhUkj9UOMCNtXLqLixVwCdmb9u7hlg2DGeK+K9oOT1hVdBh40A0KwJgLmBDJgSfmXWoSGdwZONrFmLyPqrblwcye5GSgHjDYi9uQuyEdyCwS5HU/pYlSDu2/CjNXObRhoE5iStRg+TuOVIN/+iQ4/z7rPqLkB8ErbERxUrEPNzRn6XQr3O1M3p3Q8+9YVC3C2z6vElV6RmKOSmk2kxXnQ3U3JTUgae/s2hLJrOWCjZH65WPy8Ggz/Opa4EacRnzpia9CMMBD90XgzbaM//bmKT6TIgpbqf1TCXI46vTv3g/UMBgKgz302/+GtxkHzqIQiA4KvGNwu8VJuUG045yVNjrUNvpwir0cVNevJ+DSYldnSChOYU7TaDqLzoux9Hakw87SrBZvhWcrMDs908sQc/JnYTVdwu5+s3VZWUWbVxlCKQIpGGz/bNAeqOalQx5MFau6NOffhrph63rBC7n+1IOvZViCGuGrPz5nvQK1rvGj10EWZ9Gtjo0zd8oN8TZh7/NL03xWzUFqMejLh+M3Y+Y66VbfIDG+tbvIznWviniiQpt6XL/SkfzOhpRNIPwDoGe+A66cDHw1ZTw8RQhPcS2PBzRyuJt/FNzhlJjyUeD7MS3unfHUQyJ1mIR+QHPqZZkc2WgnfhCUfGqvnN6jdXfH5R8ju7J5TZIOahaE4eUcUeMYWTN1fd3zzGvMS6+6GP1yew8fJL} + + + + GLOBAL + invalid-rsa-key + 512 RSA key + user + false + {AQAAABAAAAAQnkf3F8jmG/sXDYHCGV/lXyKNXvYTBqWka/izbw3D+X8=} + + {AQAAABAAAALA9CE73scYOL1yqlN0fudGEhilHMbvsz56nSpIELSKfI+nz//9cO/N+u9W8y3qSq9FzaRkPjLbWqKuyYSsE1KyMOZ1qxzC4Rk/gM2pj17dce5GbnHRlIQFdSa1oXa/fUbCJFbLvOfLwk1TV1FFu0u4Va0itXSWg+2n5fKslzSEtze7wXrbNGiBiLG7iJBZtaoxyhDH2Vy6zdP01mBxLMvmQheijPsrZk5GBG3EPh2E8i1L69o0wQbnBbtqe2CYvhdxXjuLRgpExtFsKR2GNj0EC370RdEN7G/fX67IMktGRS880gdW/pUo1IItTeWzAjs7eYT6pDhsDaoQ+vs+EmTMXetzE9WTOhMn1MeazbP8kkV4xJyqwfN9LH4pOzSj5BuN32Gug44WNLd4VSV0n5Nihy5KT//AS7cIrUgLYoNE6JHni7l7qajADFGS8ZZx1H/fIPU09x7FUbUhlXLrD7tKYnC1AHUQXoll9e515zYO/wqdW6BVKbrLESU086f1Lmd/pwDm95q5rSihQkVjwxanDiN7cAeeohRnIuHESR2fOMloy5SUrRovL4l/EATfCsprKbe0qDaakC26Sa5lXAA7dFyI+XBexhz9MljIke9Tnk54gmEj5b78qB+msGRNWDta0CdBexM8zJgBsyVBInvcCTdIQw3hF5JiW+YX6AusblGZLbxu4kJI1yd+x/rpmsv7H3rTXDgRudO0trakXPq3mOua1fAeLr5GsCP1f7kBvUY9bRo+LwRBxL7g4rcti2CSlGJVSel33uEEUBhhJqIDimf85guFGjpukW8uFENmBGJK5WNL0VDgyn0jbhewGD0WbO3aLD/3lWFfrxOByWOBaX/kEud94CncXw0Vnw/GH8PTs4LBwMLC5hXOchg7P9NecW89ZC1jBgEoVOiyqVnZVSr3pg8ekQHAvNHzMY49G918DUnnDIKa9IOV/maN58eQ} + + + + + + \ No newline at end of file diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret new file mode 100644 index 0000000000000000000000000000000000000000..ebc2924b6c3ae78e5f4d2b524556ad50ff9f7acc GIT binary patch literal 272 zcmV+r0q_1!>OA}&Ajv%p`FxP%?vii8MLlk~EU?3Ye$OWpsTm63US_#r{s6eYPSiQU z3PIPr>GlG@9*oWZ4Al>6Ig42o)X3iUifurPq}8zgaQA~wI9TmcPYWcLY1oo!`Idt^W#JkI7`7P_lkJIWX0Do^yqK zI86HLDe Date: Tue, 9 Jul 2024 11:45:08 +0200 Subject: [PATCH 04/25] testing form validation --- .../impl/BasicSSHUserPrivateKey.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index e64d3b3..767fb6b 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -29,9 +29,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.DescriptorExtensionList; import hudson.Extension; +import hudson.RelativePath; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; +import hudson.model.Item; import hudson.model.Items; +import hudson.util.FormValidation; import hudson.util.Secret; import java.io.File; import java.io.IOException; @@ -54,7 +57,13 @@ import net.jcip.annotations.GuardedBy; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; +import org.bouncycastle.asn1.ASN1ParsingException; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.Stapler; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.interceptor.RequirePOST; /** * A simple username / password for use with SSH connections. @@ -108,7 +117,7 @@ public BasicSSHUserPrivateKey(CredentialsScope scope, String id, String username super(scope, id, username, description); this.privateKeySource = privateKeySource == null ? new DirectEntryPrivateKeySource("") : privateKeySource; this.passphrase = fixEmpty(passphrase == null ? null : Secret.fromString(passphrase)); - checkKeyFipsCompliance(privateKeySource, this.passphrase); + checkKeyFipsCompliance(this.privateKeySource.getPrivateKeys().get(0), this.passphrase); } private static Secret fixEmpty(Secret secret) { @@ -117,7 +126,6 @@ private static Secret fixEmpty(Secret secret) { @Override protected synchronized Object readResolve() { - checkKeyFipsCompliance(privateKeySource, passphrase); if (privateKeySource == null) { Secret passphrase = getPassphrase(); if (privateKeys != null) { @@ -139,6 +147,7 @@ protected synchronized Object readResolve() { getDescription() ); } + checkKeyFipsCompliance(privateKeySource.getPrivateKeys().get(0), passphrase); if (passphrase != null && fixEmpty(passphrase) == null) { return new BasicSSHUserPrivateKey( getScope(), @@ -189,16 +198,16 @@ public Secret getPassphrase() { * @param privateKeySource the keySource * @param passphrase the secret used with the key (null if no secret provided) */ - private static void checkKeyFipsCompliance(PrivateKeySource privateKeySource, Secret passphrase) { + private static void checkKeyFipsCompliance(String privateKeySource, Secret passphrase) { if (!FIPS140.useCompliantAlgorithms()) { return; // maintain existing behaviour if not in FIPS mode } - if (privateKeySource == null || privateKeySource.getPrivateKeys().isEmpty()) { + if (privateKeySource == null || StringUtils.isBlank(privateKeySource)) { return; } try { char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); - PEMEncodable pem = PEMEncodable.decode(privateKeySource.getPrivateKeys().get(0), pass); + PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); PrivateKey privateKey = pem.toPrivateKey(); if (privateKey == null) { LOGGER.log(Level.WARNING, "Private key can not be obtained from provided data."); @@ -219,7 +228,7 @@ private static void checkKeyFipsCompliance(PrivateKeySource privateKeySource, Se throw new IllegalArgumentException("Provided private key is not FIPS compliant."); } catch (UnrecoverableKeyException ex) { LOGGER.log(Level.WARNING, "Key can not be recovered (possibly wrong passphrase?)."); - throw new IllegalArgumentException("Key can not be recovered (possibly wrong passphrase?)."); + throw new ASN1ParsingException("Key can not be recovered (possibly wrong passphrase?)."); } } @@ -248,6 +257,14 @@ public DescriptorExtensionList> g public String getIconClassName() { return "symbol-fingerprint"; } + + @RequirePOST + public FormValidation doCheckPassphrase(@QueryParameter String passphrase, + @QueryParameter String privateKey, + @QueryParameter String privateKeySource) { + + return FormValidation.ok("ok"); + } } /** @@ -359,6 +376,22 @@ public static class DescriptorImpl extends PrivateKeySourceDescriptor { public String getDisplayName() { return Messages.BasicSSHUserPrivateKey_DirectEntryPrivateKeySourceDisplayName(); } + + @RequirePOST + public FormValidation doCheckPrivateKey(@QueryParameter String privateKey, + @RelativePath("..") @QueryParameter String passphrase) { + try { + checkKeyFipsCompliance(privateKey, Secret.fromString(passphrase)); + } catch (ASN1ParsingException ex) { + // At this point might not have the passphrase, so if key is cyphered we won't be able to read it. + // Then, if getting this exception it means key is cyphered, so we can not validate it yet + return FormValidation.ok(); + } catch (IllegalArgumentException ex) { + // Key could be read, this is a legit validation error + return FormValidation.error(ex.getMessage()); + } + return FormValidation.ok(); + } } } From b0082f06bd1e456ad87176edc92251965e866fb9 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Tue, 9 Jul 2024 12:24:32 +0200 Subject: [PATCH 05/25] using textarea --- .../DirectEntryPrivateKeySource/config.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index 548797e..39f26d2 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file From 9c7386ce829d212d63933d7d2fc689b425931bf7 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Tue, 9 Jul 2024 16:25:12 +0200 Subject: [PATCH 06/25] removing form validation --- .../impl/BasicSSHUserPrivateKey.java | 38 ++----------------- .../DirectEntryPrivateKeySource/config.jelly | 2 +- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 767fb6b..c8f97aa 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -29,12 +29,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.DescriptorExtensionList; import hudson.Extension; -import hudson.RelativePath; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; -import hudson.model.Item; import hudson.model.Items; -import hudson.util.FormValidation; import hudson.util.Secret; import java.io.File; import java.io.IOException; @@ -57,13 +54,7 @@ import net.jcip.annotations.GuardedBy; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; -import org.bouncycastle.asn1.ASN1ParsingException; -import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.Stapler; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.interceptor.RequirePOST; /** * A simple username / password for use with SSH connections. @@ -195,6 +186,7 @@ public Secret getPassphrase() { * non-standard version of PBKDF2 that uses bcrypt as its core hash function, also the structure that contains the key is not ASN.1.) * Only Ed25519 or RSA (with a minimum size of 1024, as it's used for identification, not signing) keys are accepted. * Method will log and launch an {@link IllegalArgumentException} if key is not compliant. + * This method could be invoked when doing form validation once https://issues.jenkins.io/browse/JENKINS-73404 is done * @param privateKeySource the keySource * @param passphrase the secret used with the key (null if no secret provided) */ @@ -209,7 +201,7 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); PrivateKey privateKey = pem.toPrivateKey(); - if (privateKey == null) { + if (privateKey == null) { //somehow malformed key or unknown algorithm LOGGER.log(Level.WARNING, "Private key can not be obtained from provided data."); throw new IllegalArgumentException("Private key can not be obtained from provided data."); } @@ -228,7 +220,7 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp throw new IllegalArgumentException("Provided private key is not FIPS compliant."); } catch (UnrecoverableKeyException ex) { LOGGER.log(Level.WARNING, "Key can not be recovered (possibly wrong passphrase?)."); - throw new ASN1ParsingException("Key can not be recovered (possibly wrong passphrase?)."); + throw new IllegalArgumentException("Key can not be recovered (possibly wrong passphrase?)."); } } @@ -257,14 +249,6 @@ public DescriptorExtensionList> g public String getIconClassName() { return "symbol-fingerprint"; } - - @RequirePOST - public FormValidation doCheckPassphrase(@QueryParameter String passphrase, - @QueryParameter String privateKey, - @QueryParameter String privateKeySource) { - - return FormValidation.ok("ok"); - } } /** @@ -376,22 +360,6 @@ public static class DescriptorImpl extends PrivateKeySourceDescriptor { public String getDisplayName() { return Messages.BasicSSHUserPrivateKey_DirectEntryPrivateKeySourceDisplayName(); } - - @RequirePOST - public FormValidation doCheckPrivateKey(@QueryParameter String privateKey, - @RelativePath("..") @QueryParameter String passphrase) { - try { - checkKeyFipsCompliance(privateKey, Secret.fromString(passphrase)); - } catch (ASN1ParsingException ex) { - // At this point might not have the passphrase, so if key is cyphered we won't be able to read it. - // Then, if getting this exception it means key is cyphered, so we can not validate it yet - return FormValidation.ok(); - } catch (IllegalArgumentException ex) { - // Key could be read, this is a legit validation error - return FormValidation.error(ex.getMessage()); - } - return FormValidation.ok(); - } } } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index 39f26d2..dffbad0 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file From fc6daf78977897b12daa110b05cf88620141dfd4 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 09:28:03 +0200 Subject: [PATCH 07/25] removing bc-fips test dependency --- pom.xml | 6 ------ .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 13 ------------- 2 files changed, 19 deletions(-) diff --git a/pom.xml b/pom.xml index 9c35d41..35c8bed 100644 --- a/pom.xml +++ b/pom.xml @@ -136,12 +136,6 @@ bouncycastle-api - - org.bouncycastle - bc-fips - 1.0.2.4 - test - io.jenkins.plugins.mina-sshd-api mina-sshd-api-core diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java index b6d9962..24dde7c 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -9,21 +9,17 @@ import hudson.security.ACL; import jenkins.security.FIPS140; import org.apache.commons.io.FileUtils; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.FlagRule; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.WithoutJenkins; import org.jvnet.hudson.test.recipes.LocalData; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Paths; -import java.security.Security; import java.util.Iterator; import static org.junit.Assert.*; @@ -35,17 +31,8 @@ public class BasicSSHUserPrivateKeyFIPSTest { @Rule public JenkinsRule r = new JenkinsRule(); - @BeforeClass - public static void ensureBCIsAvailable() { - // Tests running without jenkins need the provider - if (Security.getProvider("BC") == null) { - Security.insertProviderAt(new BouncyCastleProvider(), 1); - } - } - @Test @Issue("JENKINS-73408") - @WithoutJenkins public void nonCompliantKeysLaunchExceptionTest() throws IOException { assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", getKey("rsa512"), "password", "Invalid size key")); From eff0a488a128ae5c9fef4ad787c8a437ab679732 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 12:55:50 +0200 Subject: [PATCH 08/25] Form validation for secret key --- .../impl/BasicSSHUserPrivateKey.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index c8f97aa..7168c43 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -29,9 +29,11 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.DescriptorExtensionList; import hudson.Extension; +import hudson.RelativePath; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; import hudson.model.Items; +import hudson.util.FormValidation; import hudson.util.Secret; import java.io.File; import java.io.IOException; @@ -55,6 +57,8 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; /** * A simple username / password for use with SSH connections. @@ -360,6 +364,18 @@ public static class DescriptorImpl extends PrivateKeySourceDescriptor { public String getDisplayName() { return Messages.BasicSSHUserPrivateKey_DirectEntryPrivateKeySourceDisplayName(); } + + @RequirePOST + public FormValidation doCheckPrivateKey (@QueryParameter String privateKey, + @RelativePath("..") @QueryParameter String passphrase) { + try { + checkKeyFipsCompliance(privateKey, Secret.fromString(passphrase)); + return FormValidation.ok(); + } catch (IllegalArgumentException ex) { + return FormValidation.error(ex.getMessage()); + } + + } } } From c55e80335c136533e4b8c3fb8f591fe24abb4183 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 13:09:51 +0200 Subject: [PATCH 09/25] using resources messages instead of hardcoded ones --- .../impl/BasicSSHUserPrivateKey.java | 20 +++++++++---------- .../sshcredentials/impl/Messages.properties | 6 ++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 7168c43..14bc48a 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -206,25 +206,25 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); PrivateKey privateKey = pem.toPrivateKey(); if (privateKey == null) { //somehow malformed key or unknown algorithm - LOGGER.log(Level.WARNING, "Private key can not be obtained from provided data."); - throw new IllegalArgumentException("Private key can not be obtained from provided data."); + LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_UnknownAlgorithmFIPS()); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_UnknownAlgorithmFIPS()); } if (privateKey instanceof RSAPrivateKey) { if (((RSAPrivateKey) privateKey).getModulus().bitLength() < 1024) { - LOGGER.log(Level.WARNING, "Key size below 1024 for RSA keys is not accepted in FIPS mode."); - throw new IllegalArgumentException("Key size below 1024 for RSA keys is not accepted in FIPS mode."); + LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); } } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { // Using algorithm name to check elliptic curve, as EdECPrivateKey is not available in jdk11 - LOGGER.log(Level.WARNING, String.format("Key algorithm %s is not accepted in FIPS mode.", privateKey.getAlgorithm())); - throw new IllegalArgumentException(String.format("Key algorithm %s is not accepted in FIPS mode.", privateKey.getAlgorithm())); + LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); + throw new IllegalArgumentException( Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); } } catch (IOException ex) { // OpenSSH keys will raise this - LOGGER.log(Level.WARNING, "Provided private key is not FIPS compliant."); - throw new IllegalArgumentException("Provided private key is not FIPS compliant."); + LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS()); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS()); } catch (UnrecoverableKeyException ex) { - LOGGER.log(Level.WARNING, "Key can not be recovered (possibly wrong passphrase?)."); - throw new IllegalArgumentException("Key can not be recovered (possibly wrong passphrase?)."); + LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_WrongPassphraseFIPS()); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_WrongPassphraseFIPS()); } } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties index 25f0fec..b710fd5 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties @@ -23,3 +23,9 @@ # BasicSSHUserPrivateKey.DirectEntryPrivateKeySourceDisplayName=Enter directly BasicSSHUserPrivateKey.DisplayName=SSH Username with private key +BasicSSHUserPrivateKey.UnknownAlgorithmFIPS=Private key can not be obtained from provided data. +BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 1024 for RSA keys is not accepted in FIPS mode. +BasicSSHUserPrivateKey.InvalidAlgorithmFIPS=Key algorithm {0} is not accepted in FIPS mode. +BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided private key is not FIPS compliant. +BasicSSHUserPrivateKey.WrongPassphraseFIPS=Key can not be recovered (possibly wrong passphrase?). +BasicSSHUserPrivateKey.TooShortPassphraseFIPS=Password is too short (< 14 characters). From 2521e5b7e50e52f20eba9d8ceb807e05000b876c Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 13:45:12 +0200 Subject: [PATCH 10/25] Adding passphrase length validation --- .../impl/BasicSSHUserPrivateKey.java | 4 +++ .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 24 +++++++------- .../credentials.xml | 16 +++++----- .../ed25519 | 8 ++--- .../openssh-rsa1024 | 30 ++++++++--------- .../rsa1024 | 32 +++++++++---------- .../rsa512 | 20 ++++++------ 7 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 14bc48a..b6cbd75 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -203,6 +203,10 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp } try { char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); + if (pass == null || pass.length < 14) { + LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_TooShortPassphraseFIPS()); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_TooShortPassphraseFIPS()); + } PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); PrivateKey privateKey = pem.toPrivateKey(); if (privateKey == null) { //somehow malformed key or unknown algorithm diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java index 24dde7c..784f23d 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -35,25 +35,27 @@ public class BasicSSHUserPrivateKeyFIPSTest { @Issue("JENKINS-73408") public void nonCompliantKeysLaunchExceptionTest() throws IOException { assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", - getKey("rsa512"), "password", "Invalid size key")); + getKey("rsa512"), "fipsvalidpassword", "Invalid size key")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "openssh-rsa1024", "user", - getKey("openssh-rsa1024"), "password", "Invalid key format")); + getKey("openssh-rsa1024"), "fipsvalidpassword", "Invalid key format")); new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "ed25519", "user", - getKey("ed25519"), "password", "Elliptic curve accepted key"); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", - getKey("rsa1024"), "password", "RSA 1024 accepted key"); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa1024", "user", - getKey("unencrypted-rsa1024"), null, "RSA 1024 with no encryption accepted key"); + getKey("ed25519"), "fipsvalidpassword", "Elliptic curve accepted key"); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024-short-pass", "user", + getKey("rsa1024"), "fipsvalidpassword", "RSA 1024 accepted key"); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", - getKey("rsa1024"), "NOT-password", "Wrong password avoids getting size or algorithm")); + getKey("unencrypted-rsa1024"), "password", "password shorter than 14 chatacters is invalid")); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa1024", "user", + getKey("unencrypted-rsa1024"), null, "RSA 1024 with no encryption is invalid")); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", + getKey("rsa1024"), "NOT-fipsvalidpassword", "Wrong password avoids getting size or algorithm")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "dsa2048", "user", - getKey("dsa2048"), null, "DSA is not accepted")); + getKey("dsa2048"), "fipsvalidpassword", "DSA is not accepted")); } @Test @Issue("JENKINS-73408") public void invalidKeyIsNotSavedInFIPSModeTest() throws IOException { - BasicSSHUserPrivateKey entry = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "password", "RSA 1024 accepted key"); + BasicSSHUserPrivateKey entry = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "fipsvalidpassword", "RSA 1024 accepted key"); Iterator stores = CredentialsProvider.lookupStores(r.jenkins).iterator(); assertTrue(stores.hasNext()); CredentialsStore store = stores.next(); @@ -65,7 +67,7 @@ public void invalidKeyIsNotSavedInFIPSModeTest() throws IOException { CredentialsMatchers.withId("rsa1024")); assertNotNull(cred); assertThrows(IllegalArgumentException.class, () -> store.addCredentials(Domain.global(), - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", getKey("rsa512"), "password", "Invalid size key"))); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", getKey("rsa512"), "fipsvalidpassword", "Invalid size key"))); store.save(); // Invalid key threw an exception, so it wasn't saved cred = CredentialsMatchers.firstOrNull( diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml index 344118c..54278ee 100644 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml @@ -9,23 +9,23 @@ GLOBAL valid-rsa-key - 1024 valid key - user + + false - {AQAAABAAAAAQtMAQkpib9NQfKS7uUWgvN4nntFCdwA3cQYQVJ52bkEI=} + {AQAAABAAAAAgFCnsIsUd8jTrDtp7MrNhdZv2mRzkqx0SysgB62s9jXOhKcJH/zmqXlSJZ6eR+yPJ} - {AQAAABAAAARAVVmNFM678Wo5RflGEU+aTod8ErVvRmIMuZHuoLRrRYoXAP5Oux3LDeCfoXg75FAuhBYsReq6j1IGKnSiagS4fExR4yjAkzjQiNPzIwW601UIZS+G2zJMijraczDecMik17wXtYqOz+mQZ7ayUbE7y2Yhtn+sPH2EeJUE39RcHbHf3w+L43w8Cbe/TwWuJFNrbPxhrtche8eIQfq75xS7JXTY1KwFrY4YKANCoBA6OYf//w40mJzNIp/LkRfykRJck4IWLkmc/UAN78teEKPkifT3d0w2uFU1lAmlF9LPZ4g6NBtp9cJgkpU81HMQqeuXKSgyfTPv47U9BvF2LWFgGxqUiSRm0Dw3yzvHdL8sf2otOdXOe+kp++jeEoscWu+CHXxE24qnGiQea0k60kigQIoGEoJgujqy35s/limZFrCWD1NJONBd3W238kjhDmIbVq/uiTeVwWsrbqAgiF0939ysvprpbs6TuwpP9TCgq+PjVYOVqF5TsIemBatYqSo5C+gvjf+qdYo78ga9WSSu0oJtU6MOIDyw1uIr/R6trsFWTx3uyQ42i4C/c2Vtptn5HBqvpAzGCwH4f48EYAapt92MTjp84vEy4ooiurCXqs1K0J9ml9jhr8YExZQwt8PPQW+hQnhzZU9jM2laJcSYS9lgX7ggCEBCPnUD8CWwDIzIk1P18aElFjNZuMhShIZtDZrIdxT+BX/BXATZ0Z5PBGRDUr3hSAtqUcqUZs6Hm7elOtSd2N8898XhUkj9UOMCNtXLqLixVwCdmb9u7hlg2DGeK+K9oOT1hVdBh40A0KwJgLmBDJgSfmXWoSGdwZONrFmLyPqrblwcye5GSgHjDYi9uQuyEdyCwS5HU/pYlSDu2/CjNXObRhoE5iStRg+TuOVIN/+iQ4/z7rPqLkB8ErbERxUrEPNzRn6XQr3O1M3p3Q8+9YVC3C2z6vElV6RmKOSmk2kxXnQ3U3JTUgae/s2hLJrOWCjZH65WPy8Ggz/Opa4EacRnzpia9CMMBD90XgzbaM//bmKT6TIgpbqf1TCXI46vTv3g/UMBgKgz302/+GtxkHzqIQiA4KvGNwu8VJuUG045yVNjrUNvpwir0cVNevJ+DSYldnSChOYU7TaDqLzoux9Hakw87SrBZvhWcrMDs908sQc/JnYTVdwu5+s3VZWUWbVxlCKQIpGGz/bNAeqOalQx5MFau6NOffhrph63rBC7n+1IOvZViCGuGrPz5nvQK1rvGj10EWZ9Gtjo0zd8oN8TZh7/NL03xWzUFqMejLh+M3Y+Y66VbfIDG+tbvIznWviniiQpt6XL/SkfzOhpRNIPwDoGe+A66cDHw1ZTw8RQhPcS2PBzRyuJt/FNzhlJjyUeD7MS3unfHUQyJ1mIR+QHPqZZkc2WgnfhCUfGqvnN6jdXfH5R8ju7J5TZIOahaE4eUcUeMYWTN1fd3zzGvMS6+6GP1yew8fJL} + {AQAAABAAAARA5C9MpiKZTTSJGD/wMjCEEZjgyQy6hdegl5rjG2rSixkloEXjPkMhvyFZhY1dWnX/BRgyXQO+h88A+sz1PkIDBJiJQFm7+73wRHgC4cFginZ7XIKPRY88cGkoDoKesv7AvvPrxOtLAH/fVfoznnJ/G9RgUCkIuaVshbEXW4cT3o3WH+JW7iEuIGR288hvEjvRVjTbl9MwGP41rNqY1RrvPAdtykDPFsC0TmhsP4r8zBAAJGK3AtSmDS/w8AFIDMmmbJndprql87VB0vKttP0Dd3c5iwGjGSibsFO0CuD6Vmkhje2lnvHA18W2REhB7uvAwh6b0Lral+Syu76yTmu+XG0FXodR3pfCtURTchcUmqbc36oMzAi0aIQeo/pUdLIz3Q7MgMxOBEraPlBfJc29dY4kxDsfcQPR+y1WJGNZUs4aBj9UrboOZRkQ6P/arWqbGhBtV0wVn2pF1doupSnm0Au4tI5HAfnpytXZ38643PZtH51gXjnH9G4W1vuSYj7nszyvsPB5pZNS9nkUERp51czrgZeFtqUYjf/7Zxi53nGnVJvbxUsTkXIeezbYUGB/Su4oEldsDbC+IWuN6WTSv+gqxJ9/aAEKhtMbPHjtFnZZnV29JPSWD7VlP1JeuiWuDOKC7WkHOOaBIDtwwzLm2Qqby/t7B51ushb3T8w//lN/c+NCiVtmVUjOCguQR5YMqABYpZ+Jxn8zJ+36UoltGzLA7pYbzEU164rP1m3okjSNrx1mCPexeqBdnaWJSGyxEeyXd1822gmNyh0W5VGwtAnI/OcZ5ns+FCC9tj82dyS0GNas19/OvegqgD9ojc0YQEIFXS/8Eu609E//2xPNQdjy1iwoE4xvGRcHY0Zp5G7hqOtVWkWQxHuk54hQC93GIA1rIkF84txcIefzccRC+PaxENqlKepNLZIwV4LnKMLkeyyuJ/DMshN6N5Zp96vEakZtVioVDtQO5FL3QGKO2A89hbOBkZp9r+d9mSszD+JxyvRGUFFlHITw8Wteg/O9XHvmgcettITh1blnlKQHaimJqcRG8Uh5DZ/vfaMHDxUMcDi33SZKlu0YOsMbm1s08Gn1JrhGaVhIH5gXtjK1NOKdXtQ0P9SMzs223o+CKslqS9J8lLJ+NCxFlUTgWJaX7Pbb065emsTfgIRkDaVBTkFVGmI88OnAsIr9xCKd877O15VxnIRqleesofvWZQ5HR9OVygfmdcD5u23oyeB6amxyWtT4cfNlL/gsuzxR/6uNiydCDdb6mkX8PUIFkHcs1C0A00L8j+gxyylpHV03DltzQgnggXCNozdRbyNeFJfGK9zUZ97XsC9ddPw6oNgn0WYAQIPDAo0v5A6muXPKpaDJfPZYMGAa6TRzTF9zb0AgdQNsutztXRD6JlqyPxyXmqbzIddMHiBNyJZvndjlAx74rhXTJxfEJo231rA8Q72VAh2wyARUVtWN4Isl4QdL} GLOBAL invalid-rsa-key - 512 RSA key - user + + false - {AQAAABAAAAAQnkf3F8jmG/sXDYHCGV/lXyKNXvYTBqWka/izbw3D+X8=} + {AQAAABAAAAAg5EQgsoRg67mrkj7TgPi44jKR3NK8rqtobWjBWp0f7WP7DoinfhvXtHFiUdwdyyR4} - {AQAAABAAAALA9CE73scYOL1yqlN0fudGEhilHMbvsz56nSpIELSKfI+nz//9cO/N+u9W8y3qSq9FzaRkPjLbWqKuyYSsE1KyMOZ1qxzC4Rk/gM2pj17dce5GbnHRlIQFdSa1oXa/fUbCJFbLvOfLwk1TV1FFu0u4Va0itXSWg+2n5fKslzSEtze7wXrbNGiBiLG7iJBZtaoxyhDH2Vy6zdP01mBxLMvmQheijPsrZk5GBG3EPh2E8i1L69o0wQbnBbtqe2CYvhdxXjuLRgpExtFsKR2GNj0EC370RdEN7G/fX67IMktGRS880gdW/pUo1IItTeWzAjs7eYT6pDhsDaoQ+vs+EmTMXetzE9WTOhMn1MeazbP8kkV4xJyqwfN9LH4pOzSj5BuN32Gug44WNLd4VSV0n5Nihy5KT//AS7cIrUgLYoNE6JHni7l7qajADFGS8ZZx1H/fIPU09x7FUbUhlXLrD7tKYnC1AHUQXoll9e515zYO/wqdW6BVKbrLESU086f1Lmd/pwDm95q5rSihQkVjwxanDiN7cAeeohRnIuHESR2fOMloy5SUrRovL4l/EATfCsprKbe0qDaakC26Sa5lXAA7dFyI+XBexhz9MljIke9Tnk54gmEj5b78qB+msGRNWDta0CdBexM8zJgBsyVBInvcCTdIQw3hF5JiW+YX6AusblGZLbxu4kJI1yd+x/rpmsv7H3rTXDgRudO0trakXPq3mOua1fAeLr5GsCP1f7kBvUY9bRo+LwRBxL7g4rcti2CSlGJVSel33uEEUBhhJqIDimf85guFGjpukW8uFENmBGJK5WNL0VDgyn0jbhewGD0WbO3aLD/3lWFfrxOByWOBaX/kEud94CncXw0Vnw/GH8PTs4LBwMLC5hXOchg7P9NecW89ZC1jBgEoVOiyqVnZVSr3pg8ekQHAvNHzMY49G918DUnnDIKa9IOV/maN58eQ} + {AQAAABAAAALAZ0cgFjzyEYwnv/C0I6Bk0ic4NvAkWFb/fEl+UbriP8/n2SwcCFvjTKDqWmYClDopaMTv4p5DMFypbUFp5ppJh4WuYwv3iTsJ0vhwp901lqFKbfOV41TxyRD6pIRCTbwCzEyl9tfQjQT3y4rLOKVHRv5W/lA2N+ChOMaUw465tHYL11Ihz+HBvwwUCBAEldp11PpBw2g+0Ad7QIjNpF+Xb7VkJXrvulTf7OkABOHLDsQRFF+HosAVAXNK5/0FireJufwpMIAlS5VIOikXCpgsKQi0Bwv2Zp93JB6CLnNdXP/oDnHdesJm+itoFy+ZAAFMYJ/PNIXL3PU1gjcii2CruMYnvynmU0YH0oC+IY8q5WoCLOkyjf91IeInYzGu/bfA6/anu5QhUOB7klLnTBxuaxwbZP86FW8FYITqNDU7cdOQxKQKpP8LR23Ciucje/UttrxdmKTL7SOsJ7AgDS5FfX3ufqmM5Th7bhzERh44P8d3L81aFPgkXaakt5O14eZj22Gwxj0N2ySDwZarRZJh4018XPXOd5aCLXGGXUJQZ0D5RzIQWlr+w5ovbahmPT8Q1doQ+ou0W65HchyoQtpqGKad+rv1B9tHNaj3ZOCUDWl19ddCQSDeBILJYPWAeBGmmFokWQTn/aH7qZLKjg2hba5WeVC8esks9oqna8i7hnFvO7pKQrZROB1iTdL0tnU5cXPEIYnfy0JgiaTcJpeB5dmAR1KRKkzQxsYA4f2NnLK1UVYA3SZ5WdPGiqYmPnnB8sifiKiewvfiwZ2VmmwoJaf7tW6Zk73gugxRLT0AJTqIP9I0xC+qNLPXX9Y16Zmm3K1KECvktQg+3HsCFUbez55GHKAGYjAlIA8NONk9X05a4ZaIFlKOu7Pmkl4PC8ilfGWKDhN6B0ZssdsIuzgLHWw02OYmvXcvtWpCRcS2apTJn+/9SkioRweWIcljvOqC} diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/ed25519 b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/ed25519 index 644448d..4e4f1a0 100644 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/ed25519 +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/ed25519 @@ -1,6 +1,6 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBCN5H++KkG/1q+wdNZw -E5+2AgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQGVryACHbKShv1f6W -WzNq2gRAZTAe5KjAxLD7Mixu9HlyqADOU0t8osMMWOJa5D7NBym0IPoS2nmFROO3 -c31w57ayW7vyEGrZKwbmJR92K2lMRQ== +MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAbW5KJoN3rrAgoZUcE +AO/GAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQoyo3fFspm3I3J2ko +jmeMYARAMFdbiQX5Fd/IqrIHXlr6bMhc2On8aOsCU1pfFVJ9o2VK/PYQQQnEvkCZ +jnOIE4OSBOG2ABNRclnxAL8CHfNQQw== -----END ENCRYPTED PRIVATE KEY----- diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024 b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024 index 90a99e0..1d9e0c4 100644 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024 +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024 @@ -1,17 +1,17 @@ -----BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBziT8RHQ -PdHUoLdFD331UgAAAAGAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQCdJ5owPkUB -lntN8nJDvxvnm3YjLvDUJ0iy5u78th1vxRDNs3WKOYtWt4Yg8Y/J3gYYrhT6Psj8rQDNtE -nRwAqA6lt8w3LgD73XVGqdQC10sRo4aTDwxG7DvWKoppC1eSY6N6xeaz2COPYKeL5Ralyk -CIwuQYJ59H2ZSVV2SfTh2wAAAhBh8nW0Db3VzXIHrEWjnIgB/7MLBZqs43Utl9H0oHDhLA -nKZycXYeVnYkWGrOntM85RfxzPub1JTadHNSYGZgIvbIODEnTfN+L0xqjG7hbpSjHNWwJQ -uvmaEohPyssxk1W+gTfs0WHS81AYC4c9Dyqzz3ErKC7EH0C71sL9sGvTv8fQTtEbBPEPng -NoGlruUNOPpoxtBTLkW4cjpw3+xnFGvQAtqrxaE0XDoM41JdEp7RwZ0jQYulKvKgZs4Nw1 -bYzFSjwqdA4vglrIKCVwav8M7K+/0KIZgKAKeEOQPG40cLL651xK91RhwFH12Qjirgxc3d -FMexxv335bSzwV/zRbwdMvUF28bp/yWSvVdagH9ri8W+QC65e47LEwyuLLR4p2NYV3LQ4d -kaD2mjxaYQtZgzmU669oQ/4XX7HDWJpKIOY3cfEdFiZlR5ko8TEA1AKkTq1iX627y7q21a -U4ynx4feGDN0rx4lvJiTs5kNS/mor0DBaE+Lbv9W5qTPdeS8jpKaAMg5mYeD1+yzkvNULt -1tgsCwSi1OXxSR8tyEUT4HsK9+xh5YIVNyd5wTHzHMCHU0w3wzKjN+MlEy+/3sCCMxug/U -JpJe55fpireqNtaoOQ9uBVpeEOAPVjzH2p8VtjAjRxRqYHnQnvQg+jJQr7J7myssn+oTlY -NcM+kQluDFhMxIQYvKeRQx/VN4v/OTI= +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBFPydw9e +K2cDWnCXMFL9mVAAAAGAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQCo24dDC5o8 +IFfdrM6sRRQaKkyqzSWXpsMT5rSZ1lcZLGwYr4Qs5WoX6YYxfoWIvEwvd9LDJ0h2uAyHQk +yxoKrZgSSOYAGjZlBQLvDQbDUNjFdi3XYERBEQDxvhtiQvoo0F1/YgokljkZHGv9xSL7Br +ovNZZ5ITIP40EFYbQ5uF7QAAAiBEXY+V77fzRSmV5SfSikMB1sDBwyLUtD/HVJbiqYl/3E +OsJYvM1BwL0quFt7u5s6wefY7eT5p2N6y2+Z5Vk6ZGxoRJlPRqS0QHXslyz/8YaAynTV2E +QGFAiyWLfWhE2u51QUcWGDXaf10gW7YyOZwHFnA8iZi8+RikxUk2lRjtFwQ4pcZsz+eT6h +7TcBPmSe3tgNd/6fgajR4/9G4h9ZqNGOiBEtZvONKN8KXr61rebK+wuMrjUfg1u0BjyqTs +W13DnH4uLlC7r+2/zfGyMjwt/pw/dwhQwilZY7G3Ckp3O3jv3eAb1AmcRP5oOU+obWIcb2 +dee5jbRWgMrS6QEI5yGCDE8st6ezglmVECdRoXsSz+YYwAKPFg47Abh9mRSZEKuX0MyxI8 +n/U194AW2eO6G4u90TCFyiblJSjcoDRavbaunB3oYwhyBT6QKJh1nSd4f/TbyHWnTSFcOJ +ARn6SOZnS2S2ARk4t1pvNro94Ww6qCKloBGjlQevBfoEkftb9exSI6/0qTmsjFZiMjiADH +WEJQw+df6KZ9mIZ8xERYGXgBz43EvSjD3+D5DuZm+pFqstCtGxoznL8pGQaBWbuoVGHD+V +/Ncuu7SpolnLFQGjyIvDgSZUoQQKKj3MwN4BLhEiy/ok3TwSau4QzZ6OAiawhF4qIb4nIJ +OA/6HO+9ViuxIxrhKoUTJi88FIhGThXV9GLK4nQf7w6lyu3JD8qS -----END OPENSSH PRIVATE KEY----- diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa1024 b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa1024 index 613aa3f..2c3df89 100644 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa1024 +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa1024 @@ -1,18 +1,18 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIC5TBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ/tKpZEKemAWFpTJn -LD4qUwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEELR82lW54qCjeIN1 -KRXGK9AEggKAC5ZYzZBxNtCFrIHSglo302LWhMx7I6b/CaKQ40GgGh8+lRDN8NSC -VbDPbvtAQ1khDmtQF7elJNcB/49iruvRvndLA2jkVOV+4pCPqTBAU2vuPZJpxz5b -WTHIffiaJyDWQTZNN+DI5A8jNtzKEhlBUcgb+iE4e3v2RmHapo5TkArKpAI5oX4g -yRuUDJkomYJtAWlXnHQ7QbRFZ/A80O39kMcLtTJLTshjewVIB0jqv58mUmUF7Hdt -kkv283cLfLQeKux31KnZ3c9lx7c27FVOdBeAfWQm/ITBx5Y3K321Iapqml44ai9m -D2rnpB50hx3G5EwMapVzZEJrdb51sJ/HXkd83YKNup51Vkgl4L4k8qmCbgmFA7G2 -SVUEJaN5mqj3u3ijkETz3K1lkKYKurKDE4pS3d0eopkkV7CjBlXoEh0wWq/2ax42 -ubfRDEMMBfOhHu5XUn2uu7RxvJsLNleklCu/XrOoK39jWugB9gpx/OhpHXK+hgSz -RG1L1ATSflRfelLToqfLvMcyToCwyd+NZZIYmpQb4OPmEjvZBaeWrqsAADWYSnkj -DdN0OzQeUVqWeJvybNubDriez7YGDEOYMoZSbddmRfAx/1h6CemoP59Lrmrq9KBr -HI852A6qi+EYofQGn1Vqc+V4O9moOakTjUqTwVY4PDldrzaZl0uV3BabP1Wfa+m1 -1qz7L1dHAOwhkUsYYOQnlOR85kTmCxBhwFH8wOzKmu2MXDgirkX7toQfyJsBvHnw -U5sAQnOAoJulSJ7ZTMSKM6IQh7fDecaHnwK+dlBJnf0FgodcgVbObKVxp+L3VZ+t -X2Y+eh04PoU07e+wa43iKIGn2gsheJo6fw== +MIIC5TBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQmqBfFEhTkNYDOmt9 +C4uwjAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEJEGWoJ0cmwkrkxP +fAE3lZcEggKAnjVdB4IjbyIrMyeJEFZ6MYhmHKyKdX5RtbOUqGRoZiF9+ilJStGq +rwDo5ltASjRpVaNgrJZIY3445BXxRWuJXFIeyxhM9ULpuhDItdt+DzpNCZHIBOCR +dPm4m4K/Ot8v7HFBwMLc2+ttA9/Eif1eGAxC7+7qztH9fUo+ZIvlDxApOkz7MZ/u +PxFNun+uVrzzz3tDYepDvdmQLi1TTUxNgcAOb0b+E810VoBwN/83Pltion4jwhkf +eJHfu7B/M/vj7wGz+BFJZoGjHPOk8BF0T8lxm03DLkVgkXh3suDdEerRB11j10CW +9ETvDGZg19gAZNMFTzIeiIwpGB1ODnlB0iqgD4yy55fCS42vTROwaBhKKE3d8EIp +C6sy3sfxKn0vOe6bkP2ZgHos0XTriEL6l04UtUpJZHUqG8GMa3031EfD1m4NK190 +tS8alFHLD7icci2PZPl5A/Sw5+9DtHI9gM9gZxtIm+eJ0lXCoODmcG5Zuqfcjr1o +FT/Imcf9+lzQp0IX6nK4zd3MzAPxpwDyGCu9bIGiIERvWgtWixknfcv88t7F/05O +TU7uPYCqnUScT6VwmBQyDV5cgDboulBZWaFa2XCR8VbD3xuQwVT8R/dtvpcOHQ4j +B5ScU3aRCEn8eunR6ajs2j4avjVJmFTCdFUtme2z8m/imahK0dtcojaqz8G9jq7l +bkgrtinCKcQ8UdRDVwUwfd6ZFWEHjKhmc32PHJP095VtYQSgPpdwARTwgcd4W55w +TRuaqHif6bz/B2qYQuLCnkla9YJxfwZKwDSbCXaFb34j/1/c+Tu8zhrzv44s6c2Y +x3Sq3CRiOdekpCiD7aM0TSnuw+qb2JBYQw== -----END ENCRYPTED PRIVATE KEY----- diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa512 b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa512 index ca82811..0ef417e 100644 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa512 +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa512 @@ -1,12 +1,12 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIBxTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQvwYX71a35t9FChtb -2d5tCQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEKM2wellEW9OC7w/ -PoagB4cEggFgUJcl0EbuTlTDraUyMqk1Gj1s8Bk8SRtE+eHfFlOa/RB1UCvTnDKa -cMhCn2rp8jcBw7VabsoXZ62036v+AB/pA88mrJnXN2iSYFaaJuJYZVkALiS7cBy6 -9NmYcC3Ps1ra1ttaAk8KMZOGBozK1kid7RZSxpb6ooFsxZk0oJ3qoOh9ViY4U+s1 -D+pxp/LsErWquk7RDbC0On49iALkz3pnTQc/NY+msLJpoXmt+JI29HcU856+cRk0 -H8SoonrU4XvjxhK5scoOOcw9JS5XsIjgYyhGYazg26rGzC1+awN0KRzPqGTUzJQ+ -239/Bw4xNCe3TV5WjCiIc/WXbNWPROqJvsaKZ3S5zg8WUCabZkBpsF7aN9kxCBIb -VUn01WShDGI2J5UXHqTq0GahZJT7hVKee8VwcuUMgTbcyvaD6qKEkQPbRm8hYhiW -rYdwCpK4i48hPReXqTPD72DLqqGZTy+n1A== +MIIBxTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQmCWP564C9V1sfkw0 +Q7UozgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEAA716acDUEcwGCb +DIMSdRcEggFgCezLDgjmiGfZoBwlGDkl9pKrUoeOG3ckgq4MLABQLRdo9Qxa8PSU +5GLdOpQV4bdPUGLsJmQwE6iv0tF4SmVYHFYia+CCAh6DG88W4p4sR/vdDcvTLYr9 +h32PCS/1D1jBjR9XnCRJze1GxXsrt4RZcNnJkehuDc7oKFLcu61d5PgzrLlAqzsn +Fseudf1iee2V0JR9GoKym5An0N1++iy2MevEYpDXTSWceGmuk/jgdDSWVDP9FSmt +qbIfrqRDgLCvAxnH+EnKloduFqhzokUpAOpR29X3WDEIX4r2l17i/xr+cyUG+65/ +Rc6wOs9hDAiClvDBBtORQoFFWTuqJWBzBltBVjWb+58q9o3ZbPXonEAjogNbaP+v +73LSMQ4glUbddfiDtG9tSw40TAm3XO32oZaULJ4a/RngqVqLcA88zpagoXgU7Vmf ++jQMoBj79cUyd1ag9ZKUrasmAamOw+fbMA== -----END ENCRYPTED PRIVATE KEY----- From f3ef2e05c3b4c5ca55d40290c24459c4f34f4dd8 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 18:04:07 +0200 Subject: [PATCH 11/25] form validation working - to move back to secretTextArea --- .../DirectEntryPrivateKeySource/config.jelly | 2 +- .../impl/BasicSSHUserPrivateKey/credentials.jelly | 3 ++- .../impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index dffbad0..6c4ecd1 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly index c3b6e9c..e47d9ed 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly @@ -36,6 +36,7 @@ - + + \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js new file mode 100644 index 0000000..a02b7eb --- /dev/null +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js @@ -0,0 +1,7 @@ +var passphraseElement = document.getElementById('privateKeyPassphrase'); +var privateKeyElement = document.getElementById('privateKey'); + +passphraseElement.addEventListener("change", event => { + var newEvent = new Event("change") + privateKeyElement.dispatchEvent(newEvent) +}) \ No newline at end of file From dab1db9faca96b8d63ad3ba8e56feb69c9423497 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 18:09:46 +0200 Subject: [PATCH 12/25] using secretTextArea again --- .../DirectEntryPrivateKeySource/config.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index 6c4ecd1..003bf48 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file From aab327b98d30dfd1d1c327358de1ff9adc5a2fe3 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 10 Jul 2024 18:16:24 +0200 Subject: [PATCH 13/25] Added TODO related to JENKINS-65616 --- .../DirectEntryPrivateKeySource/config.jelly | 2 +- .../impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index 003bf48..144a499 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js index a02b7eb..cd81da7 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js @@ -1,3 +1,4 @@ +//TODO: this snippet, as well as ids in passphrase and private key fields can be removed once https://issues.jenkins.io/browse/JENKINS-65616 is completed var passphraseElement = document.getElementById('privateKeyPassphrase'); var privateKeyElement = document.getElementById('privateKey'); From ad8fbec2d497ede19e4bb48ad96b321c305349fa Mon Sep 17 00:00:00 2001 From: Pere Date: Thu, 11 Jul 2024 09:58:28 +0200 Subject: [PATCH 14/25] Update src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java Co-authored-by: Francisco Javier Fernandez <31063239+fcojfernandez@users.noreply.github.com> --- .../plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index b6cbd75..5cedadf 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -198,7 +198,7 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp if (!FIPS140.useCompliantAlgorithms()) { return; // maintain existing behaviour if not in FIPS mode } - if (privateKeySource == null || StringUtils.isBlank(privateKeySource)) { + if (StringUtils.isBlank(privateKeySource)) { return; } try { From ede9354afeaf4d579096b9b8476aed1de8297652 Mon Sep 17 00:00:00 2001 From: Pere Date: Mon, 15 Jul 2024 10:41:47 +0200 Subject: [PATCH 15/25] Update src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java Co-authored-by: James Nord --- .../plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 5cedadf..dbe5688 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -376,7 +376,7 @@ public FormValidation doCheckPrivateKey (@QueryParameter String privateKey, checkKeyFipsCompliance(privateKey, Secret.fromString(passphrase)); return FormValidation.ok(); } catch (IllegalArgumentException ex) { - return FormValidation.error(ex.getMessage()); + return FormValidation.error(ex, ex.getMessage()); } } From b6da84dbcd626c7e1c0a1b363a7f0a9eb41f8a4a Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Mon, 15 Jul 2024 10:42:34 +0200 Subject: [PATCH 16/25] removing extra logs --- .../sshcredentials/impl/BasicSSHUserPrivateKey.java | 12 ++---------- .../plugins/sshcredentials/impl/Messages.properties | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index b6cbd75..0d3c6fb 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -70,8 +70,6 @@ public class BasicSSHUserPrivateKey extends BaseSSHUser implements SSHUserPrivat */ private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(BasicSSHUserPrivateKey.class.getName()); - /** * The password. */ @@ -189,7 +187,7 @@ public Secret getPassphrase() { * OpenSSH keys are not compliant (OpenSSH private key format ultimately contains a private key encrypted with a * non-standard version of PBKDF2 that uses bcrypt as its core hash function, also the structure that contains the key is not ASN.1.) * Only Ed25519 or RSA (with a minimum size of 1024, as it's used for identification, not signing) keys are accepted. - * Method will log and launch an {@link IllegalArgumentException} if key is not compliant. + * Method will throw an {@link IllegalArgumentException} if key is not compliant. * This method could be invoked when doing form validation once https://issues.jenkins.io/browse/JENKINS-73404 is done * @param privateKeySource the keySource * @param passphrase the secret used with the key (null if no secret provided) @@ -204,31 +202,25 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp try { char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); if (pass == null || pass.length < 14) { - LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_TooShortPassphraseFIPS()); throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_TooShortPassphraseFIPS()); } PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); PrivateKey privateKey = pem.toPrivateKey(); if (privateKey == null) { //somehow malformed key or unknown algorithm - LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_UnknownAlgorithmFIPS()); throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_UnknownAlgorithmFIPS()); } if (privateKey instanceof RSAPrivateKey) { if (((RSAPrivateKey) privateKey).getModulus().bitLength() < 1024) { - LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); } } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { // Using algorithm name to check elliptic curve, as EdECPrivateKey is not available in jdk11 - LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); throw new IllegalArgumentException( Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); } } catch (IOException ex) { // OpenSSH keys will raise this - LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS()); throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS()); } catch (UnrecoverableKeyException ex) { - LOGGER.log(Level.WARNING, Messages.BasicSSHUserPrivateKey_WrongPassphraseFIPS()); - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_WrongPassphraseFIPS()); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIP(ex.getLocalizedMessage())); } } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties index b710fd5..d5d5ecf 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties @@ -27,5 +27,5 @@ BasicSSHUserPrivateKey.UnknownAlgorithmFIPS=Private key can not be obtained from BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 1024 for RSA keys is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidAlgorithmFIPS=Key algorithm {0} is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided private key is not FIPS compliant. -BasicSSHUserPrivateKey.WrongPassphraseFIPS=Key can not be recovered (possibly wrong passphrase?). +BasicSSHUserPrivateKey.KeyParseErrorFIP=Can not parse key {0} BasicSSHUserPrivateKey.TooShortPassphraseFIPS=Password is too short (< 14 characters). From 1c506bcd19ad2f7b4d376cbaebbad257dee68cf7 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Tue, 16 Jul 2024 11:35:34 +0200 Subject: [PATCH 17/25] support for unencrypted openssh keys --- .../impl/BasicSSHUserPrivateKey.java | 51 ++++++++++++++++--- .../sshcredentials/impl/Messages.properties | 4 +- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 144d3d5..81e8035 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -38,6 +38,7 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.io.StringReader; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.interfaces.RSAPrivateKey; @@ -56,6 +57,12 @@ import net.jcip.annotations.GuardedBy; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; +import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -110,7 +117,7 @@ public BasicSSHUserPrivateKey(CredentialsScope scope, String id, String username super(scope, id, username, description); this.privateKeySource = privateKeySource == null ? new DirectEntryPrivateKeySource("") : privateKeySource; this.passphrase = fixEmpty(passphrase == null ? null : Secret.fromString(passphrase)); - checkKeyFipsCompliance(this.privateKeySource.getPrivateKeys().get(0), this.passphrase); + checkKeyFipsCompliance(this.privateKeySource.getPrivateKeys().isEmpty() ? "" : this.privateKeySource.getPrivateKeys().get(0), this.passphrase); } private static Secret fixEmpty(Secret secret) { @@ -140,7 +147,7 @@ protected synchronized Object readResolve() { getDescription() ); } - checkKeyFipsCompliance(privateKeySource.getPrivateKeys().get(0), passphrase); + checkKeyFipsCompliance(this.privateKeySource.getPrivateKeys().isEmpty() ? "" : privateKeySource.getPrivateKeys().get(0), passphrase); if (passphrase != null && fixEmpty(passphrase) == null) { return new BasicSSHUserPrivateKey( getScope(), @@ -184,7 +191,7 @@ public Secret getPassphrase() { /** * Checks if provided key is compliant with FIPS 140-2. - * OpenSSH keys are not compliant (OpenSSH private key format ultimately contains a private key encrypted with a + * OpenSSH encrypted keys are not compliant (OpenSSH private key format ultimately contains a private key encrypted with a * non-standard version of PBKDF2 that uses bcrypt as its core hash function, also the structure that contains the key is not ASN.1.) * Only Ed25519 or RSA (with a minimum size of 1024, as it's used for identification, not signing) keys are accepted. * Method will throw an {@link IllegalArgumentException} if key is not compliant. @@ -201,7 +208,7 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp } try { char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); - if (pass == null || pass.length < 14) { + if (pass != null && pass.length < 14) { throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_TooShortPassphraseFIPS()); } PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); @@ -215,13 +222,41 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp } } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { // Using algorithm name to check elliptic curve, as EdECPrivateKey is not available in jdk11 - throw new IllegalArgumentException( Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); + } + } catch (IOException ex) { // OpenSSH keys will raise this, so we need to check if it's a valid openssh key + if (!checkValidOpenSshKey(privateKeySource)) { + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); } - } catch (IOException ex) { // OpenSSH keys will raise this - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS()); } catch (UnrecoverableKeyException ex) { - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIP(ex.getLocalizedMessage())); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIPS(ex.getLocalizedMessage()), ex); + } + } + + /** + * Unencrypted OpenSSH keys will be FIPS compliant with same criteria as open ssl ones: RSA > 1024 or ED25519. + * This method will check if the provided key is a FIPS approved key, and throw {@link IllegalArgumentException} if not. + * @param privateKey the key, no passphrase required as only unencrypted keys will be processed + * @return true if key is valid, false if not (provided key is not in PEM) or throwing an exception if key + */ + private static boolean checkValidOpenSshKey(@NonNull String privateKey) { + try { + PemObject pemObject = new PemReader(new StringReader(privateKey)).readPemObject(); + if (pemObject == null) { + return false; // Object can not be read, so continue with previous exception + } + AsymmetricKeyParameter params = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(pemObject.getContent()); + if (params instanceof RSAPrivateCrtKeyParameters) { + if (((RSAPrivateCrtKeyParameters) params).getModulus().bitLength() < 1024) { + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); + } + } else if (!(params instanceof Ed25519PrivateKeyParameters)) { + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(params.getClass().getSimpleName())); + } + }catch (IOException | IllegalStateException ex) { + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); } + return true; } /** diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties index d5d5ecf..01902ac 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties @@ -26,6 +26,6 @@ BasicSSHUserPrivateKey.DisplayName=SSH Username with private key BasicSSHUserPrivateKey.UnknownAlgorithmFIPS=Private key can not be obtained from provided data. BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 1024 for RSA keys is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidAlgorithmFIPS=Key algorithm {0} is not accepted in FIPS mode. -BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided private key is not FIPS compliant. -BasicSSHUserPrivateKey.KeyParseErrorFIP=Can not parse key {0} +BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided data can not be parsed: {0}. +BasicSSHUserPrivateKey.KeyParseErrorFIPS=Can not parse key {0} BasicSSHUserPrivateKey.TooShortPassphraseFIPS=Password is too short (< 14 characters). From 04f794796f04d988dd4871e64f0b6e5f30139405 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Tue, 16 Jul 2024 11:36:06 +0200 Subject: [PATCH 18/25] testing openssh keys --- .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 12 ++++++++++-- .../not-a-key | 1 + .../openssh-ed25519-nopass | 7 +++++++ .../openssh-rsa1024-nopass | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/not-a-key create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-ed25519-nopass create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024-nopass diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java index 784f23d..3d6013f 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -34,6 +34,8 @@ public class BasicSSHUserPrivateKeyFIPSTest { @Test @Issue("JENKINS-73408") public void nonCompliantKeysLaunchExceptionTest() throws IOException { + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "no-key", "user", + null, null, "no key provided doesn't throw exceptions"); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", getKey("rsa512"), "fipsvalidpassword", "Invalid size key")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "openssh-rsa1024", "user", @@ -44,12 +46,18 @@ public void nonCompliantKeysLaunchExceptionTest() throws IOException { getKey("rsa1024"), "fipsvalidpassword", "RSA 1024 accepted key"); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("unencrypted-rsa1024"), "password", "password shorter than 14 chatacters is invalid")); - assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa1024", "user", - getKey("unencrypted-rsa1024"), null, "RSA 1024 with no encryption is invalid")); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa1024", "user", + getKey("unencrypted-rsa1024"), null, "RSA 1024 with no encryption is valid"); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "NOT-fipsvalidpassword", "Wrong password avoids getting size or algorithm")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "dsa2048", "user", getKey("dsa2048"), "fipsvalidpassword", "DSA is not accepted")); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "not-a-key", "user", + getKey("not-a-key"), "fipsvalidpassword", "Provided data is not a key")); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-ed25519", "user", + getKey("openssh-ed25519-nopass"), null, "openssh ED25519 with no encryption is accepted"); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-rsa1024", "user", + getKey("openssh-rsa1024-nopass"), null, "openssh rsa >= 1024 with no encryption is accepted"); } @Test diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/not-a-key b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/not-a-key new file mode 100644 index 0000000..410cf67 --- /dev/null +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/not-a-key @@ -0,0 +1 @@ +I'm not a key \ No newline at end of file diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-ed25519-nopass b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-ed25519-nopass new file mode 100644 index 0000000..60f18d1 --- /dev/null +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-ed25519-nopass @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBijRTQ8qO1R5x2WdTkQxbSlUc0EIVEHGWkToICZf6Z1AAAAKA1h7sHNYe7 +BwAAAAtzc2gtZWQyNTUxOQAAACBijRTQ8qO1R5x2WdTkQxbSlUc0EIVEHGWkToICZf6Z1A +AAAEAuKgBGoss8OYtdfaAGbIHh6sULQ6VGPJw4vUvP1xUmJ2KNFNDyo7VHnHZZ1ORDFtKV +RzQQhUQcZaROggJl/pnUAAAAG3BlcmVATUFDLXBidWVub3llcmJlcy5sb2NhbAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024-nopass b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024-nopass new file mode 100644 index 0000000..9310b90 --- /dev/null +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024-nopass @@ -0,0 +1,16 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAIEA5kk7nIoIiMUxNZXShS1V7o5VEFfTPQgDifUqif1a5FeGhHuLR+ql +i4T5f5aHjskotzbxshytDW2cuFIz0KlO0Z5ePmMMgkEueckSTKFmss5B1Mro8n+PbxwywT +2XzTtXZt/DlhI/oGz1f3n0+Xeb/5V0DB+KyL76t000MaBL25MAAAIYL2BPqS9gT6kAAAAH +c3NoLXJzYQAAAIEA5kk7nIoIiMUxNZXShS1V7o5VEFfTPQgDifUqif1a5FeGhHuLR+qli4 +T5f5aHjskotzbxshytDW2cuFIz0KlO0Z5ePmMMgkEueckSTKFmss5B1Mro8n+PbxwywT2X +zTtXZt/DlhI/oGz1f3n0+Xeb/5V0DB+KyL76t000MaBL25MAAAADAQABAAAAgAoVruyeBt +2mdQ85rmxync7CoOsETZXgofcKq4A7fh19z8BBgMn3dqRzgFGcD/eqy4Oqfk8kfleWM0TH +JMnAgFkMZh5062VKm4xQ/1wWl7OvOGOadFc1gv+aY0D60H+Nk7gA9PvBEuIJ+K3SKnTjjB +WrnJeUDM27+wk4PNc83umhAAAAQQCuFD6EYyYZpnUMnb+V3y9I/HkcF9ZYU4uW9sLTtKrZ +xzu7qxRRcFE7kRn6vYeCfnzapWmElZshLgdiuQX652DKAAAAQQD5ysAWAGPo4zi3OP6mTc +pkmqFSEuxfGhrFOVW6BXWmUpte1YTi8T0yMsx0STu++O//LkSsH6meLgjSREGom8tZAAAA +QQDsAmFO2C+mLbx40BwIrJiWPvGRdbguLShUlCWTft0x5RPfZHWeCLakqhlYx3obvS06X+ +W4mjzZuo5u+rAghvzLAAAAG3BlcmVATUFDLXBidWVub3llcmJlcy5sb2NhbAECAwQFBgc= +-----END OPENSSH PRIVATE KEY----- From 07b5701e25a0f9e06e769c1d859439b405ec003e Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Tue, 16 Jul 2024 11:56:58 +0200 Subject: [PATCH 19/25] generating ids dinamically --- .../impl/BasicSSHUserPrivateKey.java | 3 ++- .../DirectEntryPrivateKeySource/config.jelly | 2 +- .../BasicSSHUserPrivateKey/credentials.jelly | 16 ++++++++++++++-- .../passphraseChangeEvent.js | 8 -------- .../sshcredentials/impl/Messages.properties | 2 +- 5 files changed, 18 insertions(+), 13 deletions(-) delete mode 100644 src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 81e8035..140e42e 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -229,7 +229,8 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); } } catch (UnrecoverableKeyException ex) { - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIPS(ex.getLocalizedMessage()), ex); + String errorMessage = ex.getLocalizedMessage() == null ? "wrong passphrase" : ex.getLocalizedMessage(); + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIPS(errorMessage), ex); } } diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index 144a499..3a95ece 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly index e47d9ed..d560c2f 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly @@ -26,6 +26,8 @@ + + @@ -36,7 +38,17 @@ - + - + \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js deleted file mode 100644 index cd81da7..0000000 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js +++ /dev/null @@ -1,8 +0,0 @@ -//TODO: this snippet, as well as ids in passphrase and private key fields can be removed once https://issues.jenkins.io/browse/JENKINS-65616 is completed -var passphraseElement = document.getElementById('privateKeyPassphrase'); -var privateKeyElement = document.getElementById('privateKey'); - -passphraseElement.addEventListener("change", event => { - var newEvent = new Event("change") - privateKeyElement.dispatchEvent(newEvent) -}) \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties index 01902ac..de1aaa2 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties @@ -27,5 +27,5 @@ BasicSSHUserPrivateKey.UnknownAlgorithmFIPS=Private key can not be obtained from BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 1024 for RSA keys is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidAlgorithmFIPS=Key algorithm {0} is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided data can not be parsed: {0}. -BasicSSHUserPrivateKey.KeyParseErrorFIPS=Can not parse key {0} +BasicSSHUserPrivateKey.KeyParseErrorFIPS=Can not parse key: {0} BasicSSHUserPrivateKey.TooShortPassphraseFIPS=Password is too short (< 14 characters). From c75d0af413bda3e3523ea6075a2f3ddc78565d85 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Tue, 16 Jul 2024 11:59:45 +0200 Subject: [PATCH 20/25] back to secretTextArea after checking validations --- .../DirectEntryPrivateKeySource/config.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly index 3a95ece..48e9784 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly @@ -26,6 +26,6 @@ - + \ No newline at end of file From bdce55b76a6e85c94be61e9e9e1cbe8bb7dd9f55 Mon Sep 17 00:00:00 2001 From: Pere Date: Tue, 16 Jul 2024 15:53:00 +0200 Subject: [PATCH 21/25] Update src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java Co-authored-by: James Nord --- .../plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 140e42e..7d61ab2 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -221,6 +221,7 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); } } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { + // TODO: update this to use the JDK17 support when available in the plugin baseline // Using algorithm name to check elliptic curve, as EdECPrivateKey is not available in jdk11 throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); } From 0cd9f12a64d0bf7803b77a43931aaef51b27cc08 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Wed, 17 Jul 2024 09:40:06 +0200 Subject: [PATCH 22/25] accepting rsa >= 2048 only --- .../impl/BasicSSHUserPrivateKey.java | 6 ++-- .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 30 +++++++++--------- .../credentials.xml | 18 +++++------ .../secrets/hudson.util.Secret | Bin 272 -> 272 bytes .../secrets/master.key | 2 +- .../openssh-rsa1024-nopass | 16 ---------- .../openssh-rsa2048-nopass | 27 ++++++++++++++++ .../rsa2048 | 30 ++++++++++++++++++ .../rsa512 | 12 ------- .../unencrypted-rsa2048 | 28 ++++++++++++++++ 10 files changed, 113 insertions(+), 56 deletions(-) delete mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024-nopass create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa2048-nopass create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa2048 delete mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa512 create mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/unencrypted-rsa2048 diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index 140e42e..0bd23f4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -193,7 +193,7 @@ public Secret getPassphrase() { * Checks if provided key is compliant with FIPS 140-2. * OpenSSH encrypted keys are not compliant (OpenSSH private key format ultimately contains a private key encrypted with a * non-standard version of PBKDF2 that uses bcrypt as its core hash function, also the structure that contains the key is not ASN.1.) - * Only Ed25519 or RSA (with a minimum size of 1024, as it's used for identification, not signing) keys are accepted. + * Only Ed25519 or RSA (with a minimum size of 2048) keys are accepted. * Method will throw an {@link IllegalArgumentException} if key is not compliant. * This method could be invoked when doing form validation once https://issues.jenkins.io/browse/JENKINS-73404 is done * @param privateKeySource the keySource @@ -217,7 +217,7 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_UnknownAlgorithmFIPS()); } if (privateKey instanceof RSAPrivateKey) { - if (((RSAPrivateKey) privateKey).getModulus().bitLength() < 1024) { + if (((RSAPrivateKey) privateKey).getModulus().bitLength() < 2048) { throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); } } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { @@ -248,7 +248,7 @@ private static boolean checkValidOpenSshKey(@NonNull String privateKey) { } AsymmetricKeyParameter params = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(pemObject.getContent()); if (params instanceof RSAPrivateCrtKeyParameters) { - if (((RSAPrivateCrtKeyParameters) params).getModulus().bitLength() < 1024) { + if (((RSAPrivateCrtKeyParameters) params).getModulus().bitLength() < 2048) { throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); } } else if (!(params instanceof Ed25519PrivateKeyParameters)) { diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java index 3d6013f..9864267 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -36,34 +36,34 @@ public class BasicSSHUserPrivateKeyFIPSTest { public void nonCompliantKeysLaunchExceptionTest() throws IOException { new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "no-key", "user", null, null, "no key provided doesn't throw exceptions"); - assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", - getKey("rsa512"), "fipsvalidpassword", "Invalid size key")); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", + getKey("rsa1024"), "fipsvalidpassword", "Invalid size key")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "openssh-rsa1024", "user", getKey("openssh-rsa1024"), "fipsvalidpassword", "Invalid key format")); new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "ed25519", "user", getKey("ed25519"), "fipsvalidpassword", "Elliptic curve accepted key"); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024-short-pass", "user", - getKey("rsa1024"), "fipsvalidpassword", "RSA 1024 accepted key"); - assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa2048", "user", + getKey("rsa2048"), "fipsvalidpassword", "RSA 2048 accepted key"); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024-short-pass", "user", getKey("unencrypted-rsa1024"), "password", "password shorter than 14 chatacters is invalid")); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa1024", "user", - getKey("unencrypted-rsa1024"), null, "RSA 1024 with no encryption is valid"); - assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", - getKey("rsa1024"), "NOT-fipsvalidpassword", "Wrong password avoids getting size or algorithm")); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa2048", "user", + getKey("unencrypted-rsa2048"), null, "RSA 2048 with no encryption is valid"); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa2048-wrong-pass", "user", + getKey("rsa2048"), "NOT-fipsvalidpassword", "Wrong password avoids getting size or algorithm")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "dsa2048", "user", getKey("dsa2048"), "fipsvalidpassword", "DSA is not accepted")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "not-a-key", "user", getKey("not-a-key"), "fipsvalidpassword", "Provided data is not a key")); new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-ed25519", "user", getKey("openssh-ed25519-nopass"), null, "openssh ED25519 with no encryption is accepted"); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-rsa1024", "user", - getKey("openssh-rsa1024-nopass"), null, "openssh rsa >= 1024 with no encryption is accepted"); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-rsa2048", "user", + getKey("openssh-rsa2048-nopass"), null, "openssh rsa >= 2048 with no encryption is accepted"); } @Test @Issue("JENKINS-73408") public void invalidKeyIsNotSavedInFIPSModeTest() throws IOException { - BasicSSHUserPrivateKey entry = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "fipsvalidpassword", "RSA 1024 accepted key"); + BasicSSHUserPrivateKey entry = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa2048", "user", getKey("rsa2048"), "fipsvalidpassword", "RSA 1024 accepted key"); Iterator stores = CredentialsProvider.lookupStores(r.jenkins).iterator(); assertTrue(stores.hasNext()); CredentialsStore store = stores.next(); @@ -72,15 +72,15 @@ public void invalidKeyIsNotSavedInFIPSModeTest() throws IOException { // Valid key is saved SSHUserPrivateKey cred = CredentialsMatchers.firstOrNull( CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), - CredentialsMatchers.withId("rsa1024")); + CredentialsMatchers.withId("rsa2048")); assertNotNull(cred); assertThrows(IllegalArgumentException.class, () -> store.addCredentials(Domain.global(), - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa512", "user", getKey("rsa512"), "fipsvalidpassword", "Invalid size key"))); + new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "fipsvalidpassword", "Invalid size key"))); store.save(); // Invalid key threw an exception, so it wasn't saved cred = CredentialsMatchers.firstOrNull( CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), - CredentialsMatchers.withId("rsa512")); + CredentialsMatchers.withId("rsa1024")); assertNull(cred); } diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml index 54278ee..da8a7f9 100644 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml +++ b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml @@ -1,31 +1,31 @@ - + - + GLOBAL valid-rsa-key - + valid RSA 2048 key false - {AQAAABAAAAAgFCnsIsUd8jTrDtp7MrNhdZv2mRzkqx0SysgB62s9jXOhKcJH/zmqXlSJZ6eR+yPJ} + {AQAAABAAAAAgQpXlmd7aog5KHqyd4CslpWwvXrItpGOGihMO+b0g0kTXV7McPM5feOqpUnZmsLPv} - {AQAAABAAAARA5C9MpiKZTTSJGD/wMjCEEZjgyQy6hdegl5rjG2rSixkloEXjPkMhvyFZhY1dWnX/BRgyXQO+h88A+sz1PkIDBJiJQFm7+73wRHgC4cFginZ7XIKPRY88cGkoDoKesv7AvvPrxOtLAH/fVfoznnJ/G9RgUCkIuaVshbEXW4cT3o3WH+JW7iEuIGR288hvEjvRVjTbl9MwGP41rNqY1RrvPAdtykDPFsC0TmhsP4r8zBAAJGK3AtSmDS/w8AFIDMmmbJndprql87VB0vKttP0Dd3c5iwGjGSibsFO0CuD6Vmkhje2lnvHA18W2REhB7uvAwh6b0Lral+Syu76yTmu+XG0FXodR3pfCtURTchcUmqbc36oMzAi0aIQeo/pUdLIz3Q7MgMxOBEraPlBfJc29dY4kxDsfcQPR+y1WJGNZUs4aBj9UrboOZRkQ6P/arWqbGhBtV0wVn2pF1doupSnm0Au4tI5HAfnpytXZ38643PZtH51gXjnH9G4W1vuSYj7nszyvsPB5pZNS9nkUERp51czrgZeFtqUYjf/7Zxi53nGnVJvbxUsTkXIeezbYUGB/Su4oEldsDbC+IWuN6WTSv+gqxJ9/aAEKhtMbPHjtFnZZnV29JPSWD7VlP1JeuiWuDOKC7WkHOOaBIDtwwzLm2Qqby/t7B51ushb3T8w//lN/c+NCiVtmVUjOCguQR5YMqABYpZ+Jxn8zJ+36UoltGzLA7pYbzEU164rP1m3okjSNrx1mCPexeqBdnaWJSGyxEeyXd1822gmNyh0W5VGwtAnI/OcZ5ns+FCC9tj82dyS0GNas19/OvegqgD9ojc0YQEIFXS/8Eu609E//2xPNQdjy1iwoE4xvGRcHY0Zp5G7hqOtVWkWQxHuk54hQC93GIA1rIkF84txcIefzccRC+PaxENqlKepNLZIwV4LnKMLkeyyuJ/DMshN6N5Zp96vEakZtVioVDtQO5FL3QGKO2A89hbOBkZp9r+d9mSszD+JxyvRGUFFlHITw8Wteg/O9XHvmgcettITh1blnlKQHaimJqcRG8Uh5DZ/vfaMHDxUMcDi33SZKlu0YOsMbm1s08Gn1JrhGaVhIH5gXtjK1NOKdXtQ0P9SMzs223o+CKslqS9J8lLJ+NCxFlUTgWJaX7Pbb065emsTfgIRkDaVBTkFVGmI88OnAsIr9xCKd877O15VxnIRqleesofvWZQ5HR9OVygfmdcD5u23oyeB6amxyWtT4cfNlL/gsuzxR/6uNiydCDdb6mkX8PUIFkHcs1C0A00L8j+gxyylpHV03DltzQgnggXCNozdRbyNeFJfGK9zUZ97XsC9ddPw6oNgn0WYAQIPDAo0v5A6muXPKpaDJfPZYMGAa6TRzTF9zb0AgdQNsutztXRD6JlqyPxyXmqbzIddMHiBNyJZvndjlAx74rhXTJxfEJo231rA8Q72VAh2wyARUVtWN4Isl4QdL} + {AQAAABAAAAdQHZ1QzGY21uxH3qs3Ukuy1Qju/IMka1ZPE6ls1IhMS97JVNc5BU0hU2vZ6t6RvGuLOfrplGHVD+A7oU9RTM3my8alGi1NX7hllw0tP+U72FmegoB59SYGpld8WvqwK/YFqvx2HnWNC1Cc+ZFkVDTm+0Uhl6JgfSzhXRgGgFrOfvIaXI+Bl1gUGRhmUjA/Oto8YSFk1VO6SnGBwhEcPyD7u9LAyklV84fLiOCxn3wWefWry1GEfOJk1l46UMHZSoqvqoc+avSHPIEzrhcPkRV5IJRoh9avB8bmzQu2W1J7upV2yzW7a50YU6FuVuhj8o7xc9aVFuOJiI2P1FC+iEu2TnhakKF28LlQbVN1A0eLdHgsBGz8pfudchmjR2FSe1/7G5MUd6ZbVoUDjrvDoM70oFqZz92nl0SXjrMw8eO4omOA/iLRyq2oHFmCzbjaGhp/SFsS39FYwlwhKwOK9vg8GDpDqZZGROpBbhWOJv47kFmzicLlYTMfeiRQXzeEXzJH5vR2a8MrgLp+bZGqIDZs7efV7IzBZb5QQ81MJ+fJ+STOOT/+RqMRcTN/0WpHxbtcloyBXa2sEP9v7fnaKCyH6mBLUdKLdlJGqdbadObUfCKPIk5ZHFeRng8/aZqZvFQ4GcgzCvHKn7cUcgLI12Cn/oL3o1BBhdBmXGI1NJ5QfyIr0DmOJ9oAdYyJFunn5SkHqjxn0MxAnmwlFeYVyBanBwn0d4Xq6pZ0a1/sKs74A0yzXn/Aaj/H/a1artHM0qA4P+yXy3magNAtzSVa4WwI74E4daYB881meSi9vNOijGqLvQLW87ZXE29jyXT26W1+bnEMMvw6OSkhsRbrBE04kFljBtwkPF7hsiueuVPZRgBQxKo2w9LU95ZCc0XJRc/x6gPJMH4c1QzTEvLmdvgr3tTYcKk781qHQAC+/z9ciP/RT0pA/m6ffs5CSZ5/XcuT+OkJn+A1uxQigTGfKkOH6KNDLkYh6aECl9O44UDS++6ceq8SBhwKmQoHNRC++FpouN5/vAoQCOCb4G1Fiy7iBpTHB4Fv71dHCRrffrz0utu9Iwy2USiCBGKmWfm4TzwWwyHJfOQDBjeJQT36tzjVG0yq9PjGkn8JAWGyxGvuu1R9kFH7pxGTJXFGAhdjJTlE8kb1v43GkST+qTgLz4D1dXkMcCZ1dXFObP12/X7iMJqvAR5q3QUGKcfX9DKJm0ZnBwmuhyMRpo3NrYnrpvENHE8VgSgZ8apfITtFT6eXjCOAG4aoIMB8tn+uRHmtxiW/1EZTwrDumSCttpqtgAmQPOtcYmSrtpuhjWooSVGEeodz7IHpi6rgw3okpskcA/mVcKwU/2QXFbIt6u6TR35NQSnoSoKf4XyZrpvZf5/gcaK7ALgjGR0AU9mjdMH39ldSuQuITHXQxa/xIPOMcFVEiUY9TPgVpx8Q3Z5KgBatb0KdotfB2Dcoxt1ey99unOPQF9gUUb2fS2L4kTjb9szY26GJAXztikooXDnl53PKNTlNNVNom5AezScu8FQSWbZuy8m9y/XQB72K91VBzPg+0uJsDCXjWK4seaJVjyQ+zFiWcxon3kC3ngft7vtZpwKTTTMAOtyXkOwvK8ri8sJHSCxrPHc8gz0Qx9vG71nU4jaUWLYOwgFis0Z1C2YhCGA+5JBuPd66mboHKqRE7W7pA443IkZqtJOpIcFXWPUGu/2NCr4hDCuNrudIvA9BU2ARUmYhdFySd4LGiRBVXtsQ+eBcBRmP4FT+2Evo3NhGtvvnG8eQzZcShOmm1utLxyjQcEaJ2bpoXe7CFQmas8sx+VZChRfEyPbRCnGmsXl9snl3xe9zu/otjIpZMjTRH/X7qQTqlk1PVtyY0HK8AIe774sIV/vV+BdTQPBYdSD6Nukb5q+Uw6EPdTowoZEgX+R/1wPYRaDVQNlfPexFWpRnGTyO4InCSW49ZLx3QE49HsRjRFEfIl3QlkAxro7nrkQnqiA3fxy0fiO2tRFuEvthmW60wLxbD+Io9pMTHFq4yfqHtLEpxumpWsDF2QK7+tqU1VoN2WAZapCkN6iwphswTnt2YYXr4XaPQb+eC/zUOz+nk5N7jiQ2h1x3wZidwUskznZRi3I4mMXy6fEH5ZcFyF67K/8Y5XY5yVEckskqtJtC+QSm0Mvi1VYDeOMq42q9OwmWYMqewJ5i4tv0cavgF8sDjlkjE4MnVOC2BJvVHs67isCcZrU/hsUrUikwmTcU8NSq7FbCjbEj1rL84bKrRUhOTcNeNGyygwSOOmC01OivmAyaY6Pi+08p0hrJ0R2q6fDX0t4hajo6dVPTtLb3eThplZf7qCjmuT6z1ASwYFenrrWMh8P/nuimmVCpx3ZP+TazNW3NzHktWxxnPZB6s9FH0spCWmaiGHXZpIzU5vj9dftJ2XWJ7LDsUqKyRfBWDH7X5AfWTh3FeCxBwvi9K4uu7wB2kx/ZQAHYiztGh4H5DJpTOSxCkZNrQfhUqtyIUZhspp3J6pXzP0eiu2H90w==} - + GLOBAL invalid-rsa-key - + invalid rsa 1024 key false - {AQAAABAAAAAg5EQgsoRg67mrkj7TgPi44jKR3NK8rqtobWjBWp0f7WP7DoinfhvXtHFiUdwdyyR4} + {AQAAABAAAAAgUqB2y9bDaBL0LNtjF8YMW3oi9Lu3x1hogk1uaAF1u4QKrJ983/4W5hXa6xPkkz+N} - {AQAAABAAAALAZ0cgFjzyEYwnv/C0I6Bk0ic4NvAkWFb/fEl+UbriP8/n2SwcCFvjTKDqWmYClDopaMTv4p5DMFypbUFp5ppJh4WuYwv3iTsJ0vhwp901lqFKbfOV41TxyRD6pIRCTbwCzEyl9tfQjQT3y4rLOKVHRv5W/lA2N+ChOMaUw465tHYL11Ihz+HBvwwUCBAEldp11PpBw2g+0Ad7QIjNpF+Xb7VkJXrvulTf7OkABOHLDsQRFF+HosAVAXNK5/0FireJufwpMIAlS5VIOikXCpgsKQi0Bwv2Zp93JB6CLnNdXP/oDnHdesJm+itoFy+ZAAFMYJ/PNIXL3PU1gjcii2CruMYnvynmU0YH0oC+IY8q5WoCLOkyjf91IeInYzGu/bfA6/anu5QhUOB7klLnTBxuaxwbZP86FW8FYITqNDU7cdOQxKQKpP8LR23Ciucje/UttrxdmKTL7SOsJ7AgDS5FfX3ufqmM5Th7bhzERh44P8d3L81aFPgkXaakt5O14eZj22Gwxj0N2ySDwZarRZJh4018XPXOd5aCLXGGXUJQZ0D5RzIQWlr+w5ovbahmPT8Q1doQ+ou0W65HchyoQtpqGKad+rv1B9tHNaj3ZOCUDWl19ddCQSDeBILJYPWAeBGmmFokWQTn/aH7qZLKjg2hba5WeVC8esks9oqna8i7hnFvO7pKQrZROB1iTdL0tnU5cXPEIYnfy0JgiaTcJpeB5dmAR1KRKkzQxsYA4f2NnLK1UVYA3SZ5WdPGiqYmPnnB8sifiKiewvfiwZ2VmmwoJaf7tW6Zk73gugxRLT0AJTqIP9I0xC+qNLPXX9Y16Zmm3K1KECvktQg+3HsCFUbez55GHKAGYjAlIA8NONk9X05a4ZaIFlKOu7Pmkl4PC8ilfGWKDhN6B0ZssdsIuzgLHWw02OYmvXcvtWpCRcS2apTJn+/9SkioRweWIcljvOqC} + {AQAAABAAAARAY16D8r9HDW7KhowwwRzIineCEp6z7vSv7A39XaacZgTIVPUReaAHpOTzBHbbgjwd7bbKDMqfIvT7Dm8/KbqHAgFFUf71fJW0W21g9rDJkZ2GdQh1FTeLEY0ZOQRsT/iTeSXHB6RQMk44xSsMqX93f1qfwh1kwBGTXyDaCLurinKFJZ60OxR4wrcSnwsBk8eJxOun8kLg7sTb/H30NIt4QsOAtKnsMbDISK495NRzaozIJw55BenmGmOEmeAPdOn6pFiBkXCf/oM62LztzdBJgx7x4r/S7J7q568F4tKfwaPjZqkBoiUAwNRw7zQV97YQeppcWobVZejtyYBjU0eb9lSsZBbVCAZRN13RIh6ACNXEr6oGt0nnQGMQtcTozkOCDe4TXQNlozx/hebolwJLtj8yk6PPp0PCqAh3AUXv/Q4JDiepfeKzUJZ//TgSyVYQc5sPxvLr5Ld3wN+g4FVjyyEAlrkuwrXGdvI1qdiIw0u6YBXAiyKqDEPRQ4cNdgwafPY43MU9Vbwhv2coS53rMAM8DCAkNsPM/wiYnMvZM18QwCSuDLgWblryAI1QWqIkcob3bpA/8DU05hpOK4SjePdi5UWKRl8uVNHtcQGdtGGgiD/+Q/Mmn6H6/OHVJIMy7ffR3x/bimfNkiEcan4W/OCeXvCm0A49hNcrBaCECjYhlQ8pZHnCMQPNXLfZAG1FPSJ5MYdqBf2p0Scknz24ChcxEtwTOe+iP7BtAk7xhbJBRdwc7N1k+Efi4CQPBetw8vgGH7jVk/nTmYmRhSWFgKLUZekgtanzQyNfTx1OXO66ZiV43+D1el6HSxgoigW690J2j6Rzkv+DFSnm93EVqwgJTlpz2qhRUwYTcpBJ82c+4DQwPhXzyXKGBFn6icO2YkXkFGl45NNpAK6qVz43AsbAbk7DtaYQBRyVbHv49W7/yJVNpOMeQmZpF+4QTI6YZSBPDoZK8FFL360uxqBgAPG4NFXPvHWaJNfL4OpSMxFW9kMyHI94OGrh1aT2jhM3K46Oak3sbfg7TRuj8HnEAfshNLvmbsxyoVpcvXWVCL3NOBM4AW+tsOJyg0tkb5wiv19rDw3IhELihx7wsM4du4qdfOJe9A4fL5wfkr8aP8SvOINxYfB5dHvK4M21Z1xWEmZnp6hWuC/1vdD+qDjzjXYTgclTGMR9yWHrt0jRHAV2OdrNzeVHhdJEd+0SRi9uXka1qshcbN4aBC0FMv0eiqSKQkZd/FybCe47/G11HO2HNtxwILeBTkVWI93DpFeX4km3Bmd6eIL260JngW48Qm42Dya2RPtpV2lljBp+fph1gIhpXn5PsDrGZ+racs75v/n7UBEcn6kRq9fIY/p+4dMXNbzfY7ekNLhchfPkgWO7ob2X1qFBGbHF7WF1q66FXwGvYZixgaXGabL/C60S2xqi+fyxsrBZx6Ag8ZjXvq9w9k9sb66aNY1VXqReMu1O} diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret index ebc2924b6c3ae78e5f4d2b524556ad50ff9f7acc..e7bbaa52856017540dbd377a5c0944b65dca3c48 100644 GIT binary patch literal 272 zcmV+r0q_2E1{YKDZZ=?P9w;X-oi=;ts846O)iMEFy^qS;;ty? zSFPebikWw#ByTBbjYw9B%-U*h_>fcjs>^00<)Z3o7e!SR6z^_)wD{#ZLjY2z4Ny8* zZi(f816fx%w30GwTiz&YY`J2H-p*}Sj}CbQDfBT%qiaY_NsP+~^vfx{ zWtgKK1H@JQKhCyc4>#|6t9KJhfh7j@teil7Vh`tm>vcth`8h<2=ZfhEk7@(tpZA_> W8dG91B;>*QY_zj1&DNNKo%{LigM}{u literal 272 zcmV+r0q_1!>OA}&Ajv%p`FxP%?vii8MLlk~EU?3Ye$OWpsTm63US_#r{s6eYPSiQU z3PIPr>GlG@9*oWZ4Al>6Ig42o)X3iUifurPq}8zgaQA~wI9TmcPYWcLY1oo!`Idt^W#JkI7`7P_lkJIWX0Do^yqK zI86HLDe Date: Wed, 17 Jul 2024 15:54:25 +0200 Subject: [PATCH 23/25] openssh keys simply propagate exception --- .../impl/BasicSSHUserPrivateKey.java | 41 +------------------ .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 6 +-- .../openssh-rsa2048-nopass | 27 ------------ 3 files changed, 4 insertions(+), 70 deletions(-) delete mode 100644 src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa2048-nopass diff --git a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java index b113b9b..99150b4 100644 --- a/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java +++ b/src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java @@ -38,7 +38,6 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; -import java.io.StringReader; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.interfaces.RSAPrivateKey; @@ -57,12 +56,6 @@ import net.jcip.annotations.GuardedBy; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; -import org.bouncycastle.crypto.params.AsymmetricKeyParameter; -import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; -import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; -import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemReader; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -191,11 +184,9 @@ public Secret getPassphrase() { /** * Checks if provided key is compliant with FIPS 140-2. - * OpenSSH encrypted keys are not compliant (OpenSSH private key format ultimately contains a private key encrypted with a - * non-standard version of PBKDF2 that uses bcrypt as its core hash function, also the structure that contains the key is not ASN.1.) + * OpenSSH keys are not compliant. (the structure that contains the key is not ASN.1.) * Only Ed25519 or RSA (with a minimum size of 2048) keys are accepted. * Method will throw an {@link IllegalArgumentException} if key is not compliant. - * This method could be invoked when doing form validation once https://issues.jenkins.io/browse/JENKINS-73404 is done * @param privateKeySource the keySource * @param passphrase the secret used with the key (null if no secret provided) */ @@ -226,41 +217,13 @@ private static void checkKeyFipsCompliance(String privateKeySource, Secret passp throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); } } catch (IOException ex) { // OpenSSH keys will raise this, so we need to check if it's a valid openssh key - if (!checkValidOpenSshKey(privateKeySource)) { - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); - } + throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); } catch (UnrecoverableKeyException ex) { String errorMessage = ex.getLocalizedMessage() == null ? "wrong passphrase" : ex.getLocalizedMessage(); throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIPS(errorMessage), ex); } } - /** - * Unencrypted OpenSSH keys will be FIPS compliant with same criteria as open ssl ones: RSA > 1024 or ED25519. - * This method will check if the provided key is a FIPS approved key, and throw {@link IllegalArgumentException} if not. - * @param privateKey the key, no passphrase required as only unencrypted keys will be processed - * @return true if key is valid, false if not (provided key is not in PEM) or throwing an exception if key - */ - private static boolean checkValidOpenSshKey(@NonNull String privateKey) { - try { - PemObject pemObject = new PemReader(new StringReader(privateKey)).readPemObject(); - if (pemObject == null) { - return false; // Object can not be read, so continue with previous exception - } - AsymmetricKeyParameter params = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(pemObject.getContent()); - if (params instanceof RSAPrivateCrtKeyParameters) { - if (((RSAPrivateCrtKeyParameters) params).getModulus().bitLength() < 2048) { - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); - } - } else if (!(params instanceof Ed25519PrivateKeyParameters)) { - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(params.getClass().getSimpleName())); - } - }catch (IOException | IllegalStateException ex) { - throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); - } - return true; - } - /** * {@inheritDoc} */ diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java index 9864267..80ce15b 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -36,6 +36,8 @@ public class BasicSSHUserPrivateKeyFIPSTest { public void nonCompliantKeysLaunchExceptionTest() throws IOException { new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "no-key", "user", null, null, "no key provided doesn't throw exceptions"); + assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-ed25519", "user", + getKey("openssh-ed25519-nopass"), null, "openssh ED25519 with no encryption is not compliant")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "fipsvalidpassword", "Invalid size key")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "openssh-rsa1024", "user", @@ -54,10 +56,6 @@ public void nonCompliantKeysLaunchExceptionTest() throws IOException { getKey("dsa2048"), "fipsvalidpassword", "DSA is not accepted")); assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "not-a-key", "user", getKey("not-a-key"), "fipsvalidpassword", "Provided data is not a key")); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-ed25519", "user", - getKey("openssh-ed25519-nopass"), null, "openssh ED25519 with no encryption is accepted"); - new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-rsa2048", "user", - getKey("openssh-rsa2048-nopass"), null, "openssh rsa >= 2048 with no encryption is accepted"); } @Test diff --git a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa2048-nopass b/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa2048-nopass deleted file mode 100644 index e68b21b..0000000 --- a/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa2048-nopass +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn -NhAAAAAwEAAQAAAQEArxJuRV/rvu9WWLqBdDNXNapeEd8V6o7S4FerAemaYYUkEFwr7w+c -few/GAr0at+VyZFv2DO8rZVrqPq8a9bVy32ZHFb9ESBAFG92cVsyu32XXOzW8yj6Uq6ipO -zJZmqiwvqeVnwdTFFV0Ej91mK2lXDK7Htfa1XmuVS7GCLXvomFs0vmVqEz8Nt2Cjwj4wb3 -siQBS1f0xrSdifqujMj6Y9+qjFni1BNBrbne/MaRJi3sbRDwuhnNSIlSKzAhZIQUUk3v40 -fiNfKKznkdoP1WNIuNbSsRvYJFNrsbL11j245w0xN5drnAdiT3GWnhRt+/AINW3jtlhxS1 -rOARFc54ewAAA9j4C8NO+AvDTgAAAAdzc2gtcnNhAAABAQCvEm5FX+u+71ZYuoF0M1c1ql -4R3xXqjtLgV6sB6ZphhSQQXCvvD5x97D8YCvRq35XJkW/YM7ytlWuo+rxr1tXLfZkcVv0R -IEAUb3ZxWzK7fZdc7NbzKPpSrqKk7MlmaqLC+p5WfB1MUVXQSP3WYraVcMrse19rVea5VL -sYIte+iYWzS+ZWoTPw23YKPCPjBveyJAFLV/TGtJ2J+q6MyPpj36qMWeLUE0Gtud78xpEm -LextEPC6Gc1IiVIrMCFkhBRSTe/jR+I18orOeR2g/VY0i41tKxG9gkU2uxsvXWPbjnDTE3 -l2ucB2JPcZaeFG378Ag1beO2WHFLWs4BEVznh7AAAAAwEAAQAAAQAiok3IUaqMkkKNuQ/F -EicNic+kT3LBfVHAafd9oF4XLsSsq99RItB9pM/yjLA/uE2km635onqQCIBM5JWD6/NNl2 -JD1f2odq3WnRpcYINPzg+0pXH8lr22v6+TWviY1z2tvvC2AUW2hFyqoNX+pHakZ7TtRZbh -4BON1HJS2wm9OpjVdl/oL9ITi005bB2CppJKmKiYVgxGins4eBz9jloZM1myACLLgTEydF -nc4655OmVW6UKdUMK5oRM1fFtLFFO68Q7GGRVk6IIpQs/HaxzZV6nSYNj3pz5ssdGRDnKn -ovkBOW7LUPulyP2UfT3vLiXrzsa7FaJ9hqJDctLrZg05AAAAgQCJBqZBVY+1/7qsi75Njx -o2hgDCsDc5tiJj4LPEfqgDXbi6SzBOhcQA2Lo/wYMztP9abZr2fCBSWSuxqj8RnZll0dLF -2aAiGrvABI859AK9hHz4EVHuZWl4DFpL9vDR9ha0Ifq2zVaB0E+yTu/ujN1lntCW6gbbOn -9RjLE3bC95+QAAAIEA9ZiIFamZ2q8pOVRcR++kCPee268oJ9yGXaDmguCTI4W84G3oS1PT -AiR2xl4ld89ngoioxG4FhE2mcEYokr69/oIOtJx7cD7IqorW+ZbPAeafuhEtFMhyNHDmJx -4heueCAhaFV1VFSbHskrnYwQoS2X+fxpVF9ErMcgDH2yylDXUAAACBALZ9Es85L/rJf4P/ -Pe2MbpVghXB3HJ2SwKWzB2LeKNqHiwwzOCLzWPm0FEX9b7BLe7SQQnfzUOIUJQhtPgoRX9 -PCcvhlRMi6K/e58uEUDkwAlnuCzGuPvp0qDhd0I4evets22ZminUxbmJ/RQ8tX6MZsy0Qc -Fv+OwbQAqaYAXQAvAAAAG3BlcmVATUFDLXBidWVub3llcmJlcy5sb2NhbAECAwQFBgc= ------END OPENSSH PRIVATE KEY----- From d4e934a2acd748313b26c918f992463deaaa94c5 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Thu, 18 Jul 2024 09:18:27 +0200 Subject: [PATCH 24/25] updated message literal --- .../jenkins/plugins/sshcredentials/impl/Messages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties index de1aaa2..a86ea43 100644 --- a/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties +++ b/src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties @@ -24,7 +24,7 @@ BasicSSHUserPrivateKey.DirectEntryPrivateKeySourceDisplayName=Enter directly BasicSSHUserPrivateKey.DisplayName=SSH Username with private key BasicSSHUserPrivateKey.UnknownAlgorithmFIPS=Private key can not be obtained from provided data. -BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 1024 for RSA keys is not accepted in FIPS mode. +BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 2048 for RSA keys is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidAlgorithmFIPS=Key algorithm {0} is not accepted in FIPS mode. BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided data can not be parsed: {0}. BasicSSHUserPrivateKey.KeyParseErrorFIPS=Can not parse key: {0} From 139f9e1e1b74fd9c3d3ffeba951a83c5fd9082f5 Mon Sep 17 00:00:00 2001 From: Pedro Bueno Date: Thu, 18 Jul 2024 16:50:18 +0200 Subject: [PATCH 25/25] Testing form validation too --- .../impl/BasicSSHUserPrivateKeyFIPSTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java index 80ce15b..2c5d52f 100644 --- a/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java +++ b/src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java @@ -6,9 +6,12 @@ import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.domains.Domain; +import hudson.ExtensionList; import hudson.security.ACL; +import hudson.util.FormValidation; import jenkins.security.FIPS140; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -96,6 +99,16 @@ public void invalidKeysAreRemovedOnStartupTest() { assertNull(cred); } + @Test + @Issue("JENKINS-73408") + public void formValidationTest() throws IOException { + BasicSSHUserPrivateKey.DirectEntryPrivateKeySource.DescriptorImpl descriptor = ExtensionList.lookupSingleton(BasicSSHUserPrivateKey.DirectEntryPrivateKeySource.DescriptorImpl.class); + FormValidation result = descriptor.doCheckPrivateKey(getKey("rsa2048").getPrivateKey().getPlainText(), "fipsvalidpassword"); + assertTrue(StringUtils.isBlank(result.getMessage())); + result = descriptor.doCheckPrivateKey(getKey("rsa1024").getPrivateKey().getPlainText(), "fipsvalidpassword"); + assertTrue(StringUtils.isNotBlank(result.getMessage())); + } + private BasicSSHUserPrivateKey.DirectEntryPrivateKeySource getKey(String file) throws IOException { String keyText = FileUtils.readFileToString(Paths.get("src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest").resolve(file).toFile(), Charset.defaultCharset()); return new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(keyText);