├── .github ├── CODEOWNERS ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── CHANGELOG.md ├── Jenkinsfile ├── LICENSE.txt ├── README.md ├── doc └── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── cloudbees │ │ └── jenkins │ │ └── plugins │ │ └── sshcredentials │ │ ├── SSHAuthenticator.java │ │ ├── SSHAuthenticatorException.java │ │ ├── SSHAuthenticatorFactory.java │ │ ├── SSHUser.java │ │ ├── SSHUserListBoxModel.java │ │ ├── SSHUserPassword.java │ │ ├── SSHUserPrivateKey.java │ │ └── impl │ │ ├── BaseSSHUser.java │ │ ├── BasicSSHUserPassword.java │ │ ├── BasicSSHUserPrivateKey.java │ │ ├── SSHUserPrivateKeySnapshotTaker.java │ │ ├── TrileadSSHPasswordAuthenticator.java │ │ └── TrileadSSHPublicKeyAuthenticator.java └── resources │ ├── com │ └── cloudbees │ │ └── jenkins │ │ └── plugins │ │ └── sshcredentials │ │ ├── Messages.properties │ │ └── impl │ │ ├── BasicSSHUserPrivateKey │ │ ├── DirectEntryPrivateKeySource │ │ │ ├── config.jelly │ │ │ ├── config_de.properties │ │ │ └── config_ja.properties │ │ ├── credentials.jelly │ │ ├── credentials_de.properties │ │ ├── credentials_ja.properties │ │ ├── help-usernameSecret.html │ │ └── passphraseChangeEvent.js │ │ ├── Messages.properties │ │ ├── Messages_de.properties │ │ └── Messages_ja.properties │ └── index.jelly └── test ├── java └── com │ └── cloudbees │ └── jenkins │ └── plugins │ └── sshcredentials │ ├── impl │ ├── BasicSSHUserPrivateKeyFIPSTest.java │ ├── BasicSSHUserPrivateKeyTest.java │ ├── TrileadSSHPasswordAuthenticatorTest.java │ └── TrileadSSHPublicKeyAuthenticatorTest.java │ └── jcasc │ └── ExportImportRoundTripCasCSSHCredentialsTest.java └── resources └── com └── cloudbees └── jenkins └── plugins └── sshcredentials ├── impl ├── BasicSSHUserPrivateKeyFIPSTest │ ├── invalidKeysAreRemovedOnStartupTest │ │ ├── credentials.xml │ │ └── secrets │ │ │ ├── hudson.util.Secret │ │ │ └── master.key │ └── nonCompliantKeysLaunchExceptionTest │ │ ├── dsa2048 │ │ ├── ed25519 │ │ ├── not-a-key │ │ ├── openssh-ed25519-nopass │ │ ├── openssh-rsa1024 │ │ ├── rsa1024 │ │ ├── rsa2048 │ │ ├── unencrypted-rsa1024 │ │ └── unencrypted-rsa2048 └── BasicSSHUserPrivateKeyTest │ ├── readOldCredentials │ ├── config.xml │ └── credentials.xml │ └── userWithoutRunScripts_cannotMigrateDangerousPrivateKeySource │ └── update_folder.xml └── jcasc └── configuration-as-code.yaml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/ssh-credentials-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | work/ 3 | 4 | # IntelliJ project files 5 | *.iml 6 | *.ipr 7 | *.iws 8 | .idea/ 9 | 10 | # eclipse project file 11 | .settings 12 | .classpath 13 | .project 14 | build/ 15 | 16 | /nb-configuration.xml -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version History 2 | 3 | ## Version 1.18 (Oct 07, 2019) 4 | 5 | Update the minimum Jenkins core requirement to 2.190.1. 6 | 7 | ## Version 1.17.3 (Sep 24, 2019) 8 | [JENKINS-50181](https://issues.jenkins-ci.org/browse/JENKINS-50181): Ensure private key ends with a newline when returning it 9 | 10 | ## Version 1.17.2 (Sep 10, 2019) 11 | 12 | [PR-44](https://github.com/jenkinsci/ssh-credentials-plugin/pull/44): update JCasC to 1.30. 13 | 14 | ## Version 1.17.1 (Jul 10, 2019) 15 | 16 | [JENKINS-50181](https://issues.jenkins-ci.org/browse/JENKINS-50181): ssh-agent/ssh-credentials-plugin failing because ssh-add expects a newline in the keyfile 17 | 18 | ## Version 1.17 (Jun 10, 2019) 19 | 20 | Update the minimum Jenkins core requirement to 2.138.4 21 | 22 | Update plugin dependencies to the recent versions 23 | 24 | ## Version 1.16 (Apr 22, 2019) 25 | 26 | [PR-40](https://github.com/jenkinsci/ssh-credentials-plugin/pull/40): switch to use secret textarea form component for entering SSH private key in config form. 27 | 28 | ## Version 1.15 (Mar 08, 2019) 29 | 30 | The plugin now requires Jenkins 2.73.3 and SSH Module 2.x 31 | 32 | ## Version 1.14 (Jun 25, 2018) 33 | 34 | Fix security issue when Credentials Binding 1.13 or newer is installed 35 | 36 | ## Version 1.13 (Jan 31, 2017) 37 | 38 | [JENKINS-23511](https://issues.jenkins-ci.org/browse/JENKINS-23511) InvalidClassException for SSHAuthenticator$1 when doing a git clone on an AIX slave 39 | 40 | [JENKINS-35562](https://issues.jenkins-ci.org/browse/JENKINS-35562) Upgrade to Credentials 2.1.0+ API for populating credentials drop-down 41 | 42 | [JENKINS-24613](https://issues.jenkins-ci.org/browse/JENKINS-24613) SSH Credentials should document the file names considered for UsersPrivateKeySource 43 | 44 | [JENKINS-21283](https://issues.jenkins-ci.org/browse/JENKINS-21283) BasicSSHUserPrivateKey.getPassphrase breaks nullness contract of interface 45 | 46 | [JENKINS-40003](https://issues.jenkins-ci.org/browse/JENKINS-40003) Add description to POM 47 | 48 | [JENKINS-39836](https://issues.jenkins-ci.org/browse/JENKINS-39836) InvalidClassException for SSHAuthenticator$1 when doing a git clone on an Linux Z series, Linux P series, and Linux P LE series agents 49 | 50 | ## Version 1.12 (May 11, 2016) 51 | 52 | [JENKINS-26943](https://issues.jenkins-ci.org/browse/JENKINS-26943) BasicSSHUserPrivateKey.DirectEntryPrivateKeySource.privateKey stored in plaintext 53 | 54 | Fix NPE in BasicSSHUserPrivateKey when the user has not configured a private key source 55 | 56 | ## Version 1.11 (Mar 30, 2015) 57 | 58 | [JENKINS-26099](https://issues.jenkins-ci.org/browse/JENKINS-26099) Permit the ID of a newly configured private key credentials item to be defined. 59 | 60 | ## Version 1.10 (Oct 17, 2014) 61 | 62 | Code to let agents load private keys from files on the controller did not work as intended. 63 | 64 | Deprecating SSHUserListBoxModel. 65 | 66 | ## Version 1.9 (Aug 15, 2014) 67 | 68 | Add safety to the Trilead SSH Authentication provider so that unknown key types do not cause authentication to bail ([JENKINS-24273](https://issues.jenkins-ci.org/browse/JENKINS-24273)) 69 | 70 | ## Version 1.8 (Aug 11, 2014) 71 | 72 | Add (experimental) support for ECDSA keys 73 | 74 | ## Version 1.7.1 (Jun 16, 2014) 75 | 76 | Re-release of 1.7 (which failed to upload) 77 | 78 | ## Version 1.7 (Jun 16, 2014) 79 | 80 | Update credentials plugin dependency to 1.14 81 | 82 | Add support for snapshotting SSH credentials 83 | 84 | ## Version 1.6.1 (Feb 5, 2014) 85 | 86 | Update credentials plugin dependency to 1.9.4 87 | 88 | ## Version 1.6 (Nov 8, 2013) 89 | 90 | UI bugfix and update credentials plugin dependency to 1.9.2 91 | 92 | ## Version 1.5.1 (Oct 4, 2013) 93 | 94 | Fix some annoying UI glitches that fell through the cracks 95 | 96 | ## Version 1.5 (Oct 4, 2013) 97 | 98 | Add a readResolve to FileOnMasterPrivateKeySource that heals any borked upgrades where the key contents were set as the filename. 99 | 100 | ## Version 1.4 (Aug 30, 2013) 101 | 102 | Add alternative API to allow overriding the username from SSHAuthenticator.newInstance(connector, user, username) - needed to support e.g. git@github.com SSH connections via JGit 103 | 104 | ## Version 1.3 (Aug 8, 2013) 105 | 106 | Another binary incompatibility known to affect CloudBees DEV@cloud servers. 107 | 108 | ## Version 1.2 (Aug 8, 2013) 109 | 110 | Binary incompatibility affecting older versions of the SSH Slaves plugin. ([JENKINS-19104](https://issues.jenkins-ci.org/browse/JENKINS-19104)) 111 | 112 | ## Version 1.1 (Aug 7, 2013) 113 | 114 | PuTTY key format regression. ([JENKINS-19104](https://issues.jenkins-ci.org/browse/JENKINS-19104)) 115 | 116 | ## Version 1.0 (Aug 7, 2013) 117 | 118 | Upgrade to Credentials Plugin 1.0 and migrate to new data types. 119 | 120 | Any existing plugins that request credentials of type SSHUserPrivateKey explicitly will be unaffected. 121 | 122 | If an existing plugin requests credentials of type BasicSSHUserPassword the resolution mechanism will handle the mapping to a concrete StandardUsernamePasswordCredentials transparently 123 | 124 | If an existing plugin requests credentials of the base interface type SSHUser it will not be able to locate and StandardUsernamePasswordCredentials implementations and will need to be adapted to integrate correctly with the new class tree. 125 | 126 | SSHAuthenticator.matcher() and SSHAuthenticator.matcher(Class< Connection type>) can be used to retrieve a CredentialsMatcher to narrow the search for appropriate credentials. 127 | 128 | **NOTE: This version requires the SSH Slaves plugin be upgraded to at least version 1.0 or it will break the installed SSH Slaves plugin.** 129 | 130 | **NOTE: This version modifies the configuration data format from a format that can be read by version 0.4 to a format that can only be read by 1.0 or newer. It will not be possible to downgrade from 1.0 to a previous release without risking configuration data loss.** 131 | 132 | ## Version 0.4 (Jul 1, 2013) 133 | 134 | Made the authentication usable on slaves. 135 | 136 | ## Version 0.2 (Oct 25, 2012) 137 | 138 | Add support for the JSch client library 139 | 140 | ## Version 0.1 (Feb 28, 2012) 141 | 142 | Initial release 143 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores 7 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'linux', jdk: 21], 10 | [platform: 'windows', jdk: 17], 11 | ]) 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jenkins SSH Credentials Plugin 2 | ============================== 3 | 4 | Read more: [https://plugins.jenkins.io/ssh-credentials](https://plugins.jenkins.io/ssh-credentials) 5 | 6 | Development 7 | =========== 8 | 9 | Start the local Jenkins instance: 10 | 11 | mvn hpi:run 12 | 13 | 14 | How to install 15 | -------------- 16 | 17 | Run 18 | 19 | mvn clean package 20 | 21 | to create the plugin .hpi file. 22 | 23 | 24 | To install: 25 | 26 | 1. copy the resulting ./target/credentials.hpi file to the $JENKINS_HOME/plugins directory. Don't forget to restart Jenkins afterwards. 27 | 28 | 2. or use the plugin management console (http://example.com:8080/pluginManager/advanced) to upload the hpi file. You have to restart Jenkins in order to find the pluing in the installed plugins list. 29 | 30 | 31 | Plugin releases 32 | --------------- 33 | 34 | mvn release:prepare release:perform -B 35 | 36 | 37 | License 38 | ------- 39 | 40 | (The MIT License) 41 | 42 | Copyright © 2011-2012, CloudBees, Inc., Stephen Connolly. 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining a copy 45 | of this software and associated documentation files (the "Software"), to deal 46 | in the Software without restriction, including without limitation the rights 47 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 48 | copies of the Software, and to permit persons to whom the Software is 49 | furnished to do so, subject to the following conditions: 50 | 51 | The above copyright notice and this permission notice shall be included in 52 | all copies or substantial portions of the Software. 53 | 54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 60 | THE SOFTWARE. 61 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # SSH Credentials Plugin 2 | 3 | This plugin allows you to store SSH credentials in Jenkins. For more information on how to create and use credentials in general in Jenkins, please visit the [Credentials Plugin description page](https://plugins.jenkins.io/credentials). 4 | 5 | ## For Developers 6 | 7 | ### Using with Trilead SSH client library 8 | 9 | Get the authenticator after you have opened the connection and let it handle authentication for you: 10 | 11 | ``` 12 | StandardUsernameCredentials user = ... 13 | Connection connection = ... 14 | 15 | SSHAuthenticator authenticator = SSHAuthenticator.newInstance(connection, user); 16 | if (!authenticator.authenticate()) throw new RuntimeException("Couldn't authenticate"); 17 | ``` 18 | 19 | ### Using with JSch SSH client library 20 | 21 | Get the authenticator before you have opened the connection (using the `JSchConnector`, 22 | needed because of the strange dichotomy with JSch between public key authentication 23 | and user/password authentication) and let feed in authentication for you: 24 | 25 | ``` 26 | StandardUsernameCredentials user = ... 27 | JSchConnector connector = new JSchConnector(user.getUsername(), hostName, port); 28 | 29 | SSHAuthenticator authenticator = SSHAuthenticator.newInstance(connector, user); 30 | authenticator.authenticate(); 31 | Session session = connector.getSession(); 32 | session.setConfig(...); 33 | session.connect(timeout); 34 | ``` 35 | 36 | ## Version history 37 | 38 | Please refer to the [changelog](/CHANGELOG.md) 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 4.0.0 28 | 29 | 30 | org.jenkins-ci.plugins 31 | plugin 32 | 5.9 33 | 34 | 35 | 36 | ssh-credentials 37 | ${changelist} 38 | hpi 39 | 40 | SSH Credentials Plugin 41 | Allows storage of SSH credentials in Jenkins 42 | https://github.com/jenkinsci/${project.artifactId}-plugin 43 | 44 | 45 | MIT License 46 | https://opensource.org/licenses/MIT 47 | 48 | 49 | 50 | 51 | 52 | stephenconnolly 53 | Stephen Connolly 54 | 55 | 56 | oleg_nenashev 57 | Oleg Nenashev 58 | 59 | 60 | jvz 61 | Matt Sicker 62 | 63 | 64 | 65 | 66 | scm:git:https://github.com/${gitHubRepo}.git 67 | scm:git:git@github.com:${gitHubRepo}.git 68 | https://github.com/${gitHubRepo} 69 | ${scmTag} 70 | 71 | 72 | 73 | 999999-SNAPSHOT 74 | 75 | 2.479 76 | ${jenkins.baseline}.1 77 | jenkinsci/${project.artifactId}-plugin 78 | 79 | 80 | 81 | 82 | repo.jenkins-ci.org 83 | https://repo.jenkins-ci.org/public/ 84 | 85 | 86 | 87 | 88 | repo.jenkins-ci.org 89 | https://repo.jenkins-ci.org/public/ 90 | 91 | 92 | 93 | 94 | 95 | 96 | io.jenkins.tools.bom 97 | bom-${jenkins.baseline}.x 98 | 3893.v213a_42768d35 99 | import 100 | pom 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.jenkins-ci.tools 110 | maven-hpi-plugin 111 | 112 | 114 | 334.v7732563deee1 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.jenkins-ci.plugins 125 | trilead-api 126 | true 127 | 128 | 129 | org.jenkins-ci.plugins 130 | variant 131 | 132 | 133 | org.jenkins-ci.plugins 134 | credentials 135 | 136 | 137 | org.jenkins-ci.plugins 138 | bouncycastle-api 139 | 140 | 141 | 142 | io.jenkins.plugins.mina-sshd-api 143 | mina-sshd-api-core 144 | test 145 | 146 | 147 | org.jenkins-ci.plugins 148 | cloudbees-folder 149 | test 150 | 151 | 152 | io.jenkins 153 | configuration-as-code 154 | test 155 | 156 | 157 | io.jenkins.configuration-as-code 158 | test-harness 159 | test 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHAuthenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials; 25 | 26 | import com.cloudbees.plugins.credentials.Credentials; 27 | import com.cloudbees.plugins.credentials.CredentialsMatcher; 28 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 29 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 30 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 31 | import edu.umd.cs.findbugs.annotations.CheckForNull; 32 | import edu.umd.cs.findbugs.annotations.NonNull; 33 | import hudson.ExtensionList; 34 | import hudson.Functions; 35 | import hudson.model.BuildListener; 36 | import hudson.model.TaskListener; 37 | import hudson.remoting.Channel; 38 | import hudson.util.StreamTaskListener; 39 | import java.io.IOException; 40 | import java.util.ArrayList; 41 | import java.util.Collection; 42 | import java.util.Collections; 43 | import java.util.List; 44 | import java.util.Objects; 45 | import java.util.stream.Collectors; 46 | import java.util.stream.StreamSupport; 47 | 48 | import jenkins.model.Jenkins; 49 | import jenkins.security.SlaveToMasterCallable; 50 | import net.jcip.annotations.GuardedBy; 51 | 52 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.anyOf; 53 | import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf; 54 | import jenkins.util.JenkinsJVM; 55 | 56 | /** 57 | * Abstraction for something that can authenticate an SSH connection. 58 | * 59 | * @param the type of connection. 60 | * @param the user to authenticate. 61 | */ 62 | public abstract class SSHAuthenticator { 63 | /** 64 | * Our connection. 65 | */ 66 | @NonNull 67 | private final C connection; 68 | 69 | /** 70 | * Our user details. 71 | */ 72 | @NonNull 73 | private final U user; 74 | 75 | private final String username; 76 | 77 | /** 78 | * Lock to prevent threading issues. 79 | */ 80 | @NonNull 81 | private final Object lock = new Object(); 82 | 83 | /** 84 | * Authentication state. 85 | */ 86 | @CheckForNull 87 | @GuardedBy("lock") 88 | private Boolean authenticated = null; 89 | 90 | /** 91 | * Subtypes are expected to report authentication failures to this listener. 92 | *

93 | * For backward compatibility with clients that do not supply a valid listener, use one that's connected 94 | * to server's stderr. This way, at least we know the error will be reported somewhere. 95 | *

96 | */ 97 | @NonNull 98 | private volatile TaskListener listener = StreamTaskListener.fromStderr(); 99 | 100 | /** 101 | * Constructor. 102 | * 103 | * @param connection the connection we will be authenticating. 104 | * @param user the user we will be authenticating as. 105 | * @deprecated use 106 | * {@link #SSHAuthenticator(Object, com.cloudbees.plugins.credentials.common.StandardUsernameCredentials, String)} 107 | */ 108 | @Deprecated 109 | protected SSHAuthenticator(@NonNull C connection, @NonNull U user) { 110 | this(connection, user, null); 111 | } 112 | 113 | /** 114 | * Constructor. 115 | * 116 | * @param connection the connection we will be authenticating. 117 | * @param user the user we will be authenticating as. 118 | * @param username the username we will be authenticating as or {@code null} to use the users username. 119 | * @since 1.4 120 | */ 121 | protected SSHAuthenticator(@NonNull C connection, @NonNull U user, @CheckForNull String username) { 122 | this.connection = Objects.requireNonNull(connection); 123 | this.user = Objects.requireNonNull(user); 124 | this.username = username; 125 | } 126 | 127 | /** 128 | * Returns the username to authenticate as. 129 | * 130 | * @return the username to authenticate as. 131 | * @since 1.4 132 | */ 133 | public String getUsername() { 134 | return username == null ? getUser().getUsername() : username; 135 | } 136 | 137 | @NonNull 138 | public TaskListener getListener() { 139 | return listener; 140 | } 141 | 142 | /** 143 | * Sets the {@link TaskListener} that receives errors that happen during the authentication. 144 | *

145 | * If you are doing this as a part of a build, pass in your {@link BuildListener}. 146 | * Pass in null to suppress the error reporting. Doing so is useful if the caller intends 147 | * to try another {@link SSHAuthenticator} when this one fails. 148 | *

149 | * For assisting troubleshooting with callers that do not provide a valid listener, 150 | * by default the errors will be sent to stderr of the server. 151 | *

152 | */ 153 | public void setListener(TaskListener listener) { 154 | if (listener == null) { 155 | listener = TaskListener.NULL; 156 | } 157 | this.listener = listener; 158 | } 159 | 160 | /** 161 | * Creates an authenticator that may be able to authenticate the supplied connection with the supplied user. 162 | * 163 | * @param connection the connection to authenticate on. 164 | * @param user the user to authenticate with. 165 | * @param the type of connection. 166 | * @param the type of user. 167 | * @return a {@link SSHAuthenticator} that may or may not be able to successfully authenticate. 168 | */ 169 | @NonNull 170 | public static SSHAuthenticator newInstance(@NonNull C connection, 171 | @NonNull U user) 172 | throws InterruptedException, IOException { 173 | return newInstance(connection, user, null); 174 | } 175 | 176 | /** 177 | * Creates an authenticator that may be able to authenticate the supplied connection with the supplied user. 178 | * 179 | * @param connection the connection to authenticate on. 180 | * @param user the user to authenticate with. 181 | * @param username the username or {@code null} to fall back to the username in the user parameter. 182 | * @param the type of connection. 183 | * @param the type of user. 184 | * @return a {@link SSHAuthenticator} that may or may not be able to successfully authenticate. 185 | * @since 1.4 186 | */ 187 | @NonNull 188 | public static SSHAuthenticator newInstance(@NonNull C connection, 189 | @NonNull U user, 190 | @CheckForNull String 191 | username) 192 | throws InterruptedException, IOException { 193 | Objects.requireNonNull(connection); 194 | Objects.requireNonNull(user); 195 | Collection factories; 196 | try { 197 | factories = lookupFactories(); 198 | } catch (LinkageError e) { 199 | // we are probably running on a remote agent and controller only classes are banned from remote class loading 200 | factories = null; 201 | } catch (IllegalStateException e) { 202 | // Jenkins.getInstance() is throwing an IllegalStateException when invoked on a remote agent 203 | factories = null; 204 | } 205 | if (factories == null) { 206 | // we are probably running on a remote agent 207 | Channel channel = Channel.current(); 208 | if (channel == null) { 209 | // ok we are not running on a remote agent, we have no ability to authenticate 210 | factories = Collections.emptySet(); 211 | } else { 212 | // call back to the controller and get an instance 213 | factories = channel.call(new NewInstance()); 214 | } 215 | 216 | } 217 | 218 | return factories.stream() 219 | .map(factory -> factory.newInstance(connection, user, username)) 220 | .filter(Objects::nonNull) 221 | .filter(SSHAuthenticator::canAuthenticate) 222 | .findFirst() 223 | .orElseGet(() -> new SSHNonauthenticator<>(connection, user, username)); 224 | } 225 | 226 | /** 227 | * This method allows a build agent to invoke {@link #newInstance(Object, StandardUsernameCredentials, String)} 228 | * after we start blocking build agents from loading master-only classes such as {@link Jenkins} and 229 | * {@link ExtensionList} as the JVM will not attempt to load these classes until this method gets invoked. 230 | * 231 | * @return the {@link SSHAuthenticatorFactory} instances (or {@code null} if you invoke from a build agent) 232 | * @throws LinkageError if you invoke from a build agent 233 | * @throws IllegalStateException if you invoke from a build agent 234 | */ 235 | private static List lookupFactories() { 236 | return JenkinsJVM.isJenkinsJVM() ? ExtensionList.lookup(SSHAuthenticatorFactory.class) : null; 237 | } 238 | 239 | /** 240 | * @deprecated Use {@link #newInstance(Object, StandardUsernameCredentials)} instead. 241 | */ 242 | @Deprecated 243 | public static SSHAuthenticator newInstance(Object connection, SSHUser user) 244 | throws InterruptedException, IOException { 245 | return newInstance(connection, user, null); 246 | } 247 | 248 | /** 249 | * Returns {@code true} if and only if the supplied connection class and user class are supported by at least one 250 | * factory. 251 | * 252 | * @param connectionClass the connection class. 253 | * @param userClass the user class. 254 | * @param the type of connection. 255 | * @param the type of user. 256 | * @return {@code true} if and only if the supplied connection class and user class are supported by at least one 257 | * factory. 258 | */ 259 | public static boolean isSupported(@NonNull Class connectionClass, 260 | @NonNull Class userClass) { 261 | Objects.requireNonNull(connectionClass); 262 | Objects.requireNonNull(userClass); 263 | return ExtensionList.lookup(SSHAuthenticatorFactory.class).stream() 264 | .anyMatch(factory -> factory.supports(connectionClass, userClass)); 265 | } 266 | 267 | /** 268 | * Filters {@link Credentials} returning only those which are supported with the specified type of connection. 269 | * 270 | * @param credentials the credentials to filter. 271 | * @param connectionClass the type of connection to filter for. 272 | * @return a list of {@link SSHUser} credentials appropriate for use with the supplied type of connection. 273 | * @deprecated Use 274 | * {@link CredentialsMatchers#filter(List, CredentialsMatcher)} 275 | * and {@link #matcher(Class)} 276 | */ 277 | public static List filter(Iterable credentials, 278 | Class connectionClass) { 279 | CredentialsMatcher matcher = matcher(connectionClass); 280 | return StreamSupport.stream(credentials.spliterator(), false) 281 | .filter(StandardUsernameCredentials.class::isInstance) 282 | .filter(matcher::matches) 283 | .map(StandardUsernameCredentials.class::cast) 284 | .collect(Collectors.toList()); 285 | } 286 | 287 | 288 | /** 289 | * Returns a {@link CredentialsMatcher} that matches the generic types of credential that are valid for use over 290 | * SSH. 291 | * When you know the connection type you will be using, it is better to use {@link #matcher(Class)}. 292 | * 293 | * @return a {@link CredentialsMatcher} that matches the generic types of credential that are valid for use over 294 | * SSH. 295 | */ 296 | public static CredentialsMatcher matcher() { 297 | return anyOf( 298 | instanceOf(StandardUsernamePasswordCredentials.class), 299 | instanceOf(SSHUserPrivateKey.class) 300 | ); 301 | } 302 | 303 | /** 304 | * Creates a {@link CredentialsMatcher} for the specific type of connection. 305 | * 306 | * @param connectionClass the type of connection. 307 | * @return a {@link CredentialsMatcher} 308 | * @since 0.5 309 | */ 310 | public static CredentialsMatcher matcher(Class connectionClass) { 311 | return new Matcher(connectionClass); 312 | } 313 | 314 | /** 315 | * {@link CredentialsMatcher} that matches credentials supported by a specific connection class. 316 | * 317 | * @since 0.5 318 | */ 319 | private static class Matcher implements CredentialsMatcher { 320 | /** 321 | * Standardize serialization across different JVMs. 322 | * 323 | * @since 1.13 324 | */ 325 | // historical value generated from 1.12 code with Java 8 326 | private static final Long serialVersionUID = -5078593817273453864L; 327 | 328 | /** 329 | * The connection class. 330 | */ 331 | private final Class connectionClass; 332 | 333 | /** 334 | * Constructor. 335 | * 336 | * @param connectionClass the connection class. 337 | */ 338 | public Matcher(Class connectionClass) { 339 | this.connectionClass = connectionClass; 340 | } 341 | 342 | /** 343 | * {@inheritDoc} 344 | */ 345 | public boolean matches(@NonNull Credentials item) { 346 | return item instanceof StandardUsernameCredentials && isSupported(connectionClass, 347 | ((StandardUsernameCredentials) item).getClass()); 348 | } 349 | } 350 | 351 | /** 352 | * Returns {@code true} if the bound connection is in a state where authentication can be tried using the 353 | * supplied credentials. 354 | *

355 | * Subclasses can override this if they can tell whether it is possible to make an authentication attempt, default 356 | * implementation is one-shot always. 357 | *

358 | * 359 | * @return {@code true} if the bound connection is in a state where authentication can be tried using the 360 | * supplied credentials. 361 | */ 362 | public boolean canAuthenticate() { 363 | synchronized (lock) { 364 | return authenticated == null; 365 | } 366 | } 367 | 368 | /** 369 | * Returns {@code true} if the bound connection has been authenticated. 370 | * 371 | * @return {@code true} if the bound connection has been authenticated. 372 | */ 373 | public final boolean isAuthenticated() { 374 | synchronized (lock) { 375 | return authenticated != null && authenticated; 376 | } 377 | } 378 | 379 | /** 380 | * Gets the bound connection. 381 | * 382 | * @return the bound connection. 383 | */ 384 | @NonNull 385 | protected final C getConnection() { 386 | return connection; 387 | } 388 | 389 | /** 390 | * Gets the supplied credentials. 391 | * 392 | * @return the supplied credentials. 393 | */ 394 | @NonNull 395 | public U getUser() { 396 | return user; 397 | } 398 | 399 | /** 400 | * SPI for authenticating the bound connection using the supplied credentials. 401 | *

402 | * As a guideline, authentication errors should be reported to {@link #getListener()} 403 | * before this method returns with {@code false}. This helps an user better understand 404 | * what is tried and failing. Logging can be used in addition to this to capture further details. 405 | * (in contrast, please avoid reporting a success to the listener to improve S/N ratio) 406 | *

407 | * 408 | * @return {@code true} if and only if authentication was successful. 409 | */ 410 | protected abstract boolean doAuthenticate(); 411 | 412 | /** 413 | * Returns the mode of authentication that this {@link SSHAuthenticator} supports. 414 | * 415 | * @return the mode of authentication that this {@link SSHAuthenticator} supports. 416 | * @since 0.2 417 | */ 418 | @NonNull 419 | public Mode getAuthenticationMode() { 420 | return Mode.AFTER_CONNECT; 421 | } 422 | 423 | /** 424 | * @deprecated as of 0.3 425 | * Use {@link #authenticate(TaskListener)} and provide a listener to receive errors. 426 | */ 427 | public final boolean authenticate() { 428 | synchronized (lock) { 429 | if (canAuthenticate()) { 430 | try { 431 | authenticated = doAuthenticate(); 432 | } catch (Throwable t) { 433 | Functions.printStackTrace(t, listener.error("SSH authentication failed")); 434 | authenticated = false; 435 | } 436 | } 437 | return isAuthenticated() || Mode.BEFORE_CONNECT.equals(getAuthenticationMode()); 438 | } 439 | } 440 | 441 | /** 442 | * @since TODO 443 | * @throws IOException Failure reason 444 | */ 445 | public final void authenticateOrFail() throws IOException { 446 | synchronized (lock) { 447 | if (!canAuthenticate()) { 448 | throw new IOException("Cannot authenticate"); 449 | } 450 | 451 | try { 452 | authenticated = doAuthenticate(); 453 | } catch (Throwable t) { 454 | throw new IOException("SSH authentication failed", t); 455 | } 456 | } 457 | } 458 | 459 | /** 460 | * Authenticate the bound connection using the supplied credentials. 461 | * 462 | * @return For an {@link #getAuthenticationMode()} of {@link Mode#BEFORE_CONNECT} the return value is 463 | * always {@code true} otherwise the return value is {@code true} if and only if authentication was 464 | * successful. 465 | */ 466 | public final boolean authenticate(TaskListener listener) { 467 | setListener(listener); 468 | return authenticate(); 469 | } 470 | 471 | /** 472 | * Same as {@link SSHUserPrivateKey#getPrivateKeys} but backward compatible for old implementations. 473 | * 474 | * @since 1.3 475 | */ 476 | @SuppressWarnings("deprecation") 477 | public static List getPrivateKeys(SSHUserPrivateKey user) { 478 | try { 479 | return user.getPrivateKeys(); 480 | } catch (AbstractMethodError x) { 481 | return Collections.singletonList(user.getPrivateKey()); 482 | } 483 | } 484 | 485 | /** 486 | * Reflects the different styles of applying authentication. 487 | * 488 | * @since 0.2 489 | */ 490 | public enum Mode { 491 | /** 492 | * This {@link SSHAuthenticator} performs authentication before establishing the connection. 493 | */ 494 | BEFORE_CONNECT, 495 | /** 496 | * This {@link SSHAuthenticator} performs authentication after establishing the connection. 497 | */ 498 | AFTER_CONNECT 499 | } 500 | 501 | /** 502 | * A callable invoked from the remote agents to retrieve the {@link SSHAuthenticatorFactory} instances. 503 | */ 504 | private static class NewInstance extends SlaveToMasterCallable, IOException> { 505 | /** 506 | * Standardize serialization. 507 | */ 508 | private static final long serialVersionUID = 1L; 509 | 510 | /** 511 | * {@inheritDoc} 512 | */ 513 | public Collection call() { 514 | return new ArrayList<>(ExtensionList.lookup(SSHAuthenticatorFactory.class)); 515 | } 516 | } 517 | 518 | /** 519 | * A dummy {@link SSHAuthenticator} that will never authenticate. 520 | * 521 | * @param the connection type. 522 | * @param the credential type. 523 | */ 524 | private static class SSHNonauthenticator extends SSHAuthenticator { 525 | /** 526 | * {@inheritDoc} 527 | */ 528 | public SSHNonauthenticator(C connection, U user, String username) { 529 | super(connection, user, username); 530 | } 531 | 532 | /** 533 | * {@inheritDoc} 534 | */ 535 | @Override 536 | protected boolean doAuthenticate() { 537 | return false; 538 | } 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHAuthenticatorException.java: -------------------------------------------------------------------------------- 1 | package com.cloudbees.jenkins.plugins.sshcredentials; 2 | 3 | /** 4 | * @author stephenc 5 | * @since 25/10/2012 15:20 6 | */ 7 | public class SSHAuthenticatorException extends RuntimeException { 8 | public SSHAuthenticatorException() { 9 | super(); 10 | } 11 | 12 | public SSHAuthenticatorException(Throwable cause) { 13 | super(cause); 14 | } 15 | 16 | public SSHAuthenticatorException(String message) { 17 | super(message); 18 | } 19 | 20 | public SSHAuthenticatorException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHAuthenticatorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials; 25 | 26 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 27 | import edu.umd.cs.findbugs.annotations.CheckForNull; 28 | import edu.umd.cs.findbugs.annotations.NonNull; 29 | import edu.umd.cs.findbugs.annotations.Nullable; 30 | import hudson.ExtensionPoint; 31 | 32 | import java.io.Serializable; 33 | 34 | /** 35 | * Extension point to allow plugging in {@link SSHAuthenticator} implementations for the many SSH client libraries 36 | * available. 37 | *

38 | * This object can be shipped to remote to create an {@link SSHAuthenticator} on a remote node. 39 | *

40 | * 41 | * @see SSHAuthenticator#newInstance(Object, SSHUser) 42 | */ 43 | public abstract class SSHAuthenticatorFactory implements ExtensionPoint, Serializable { 44 | 45 | /** 46 | * Returns an instance of {@link SSHAuthenticator} for the supplied connection and user, or {@code null} if 47 | * the factory does not support the connection and user combination. 48 | * 49 | * @param connection the connection. 50 | * @param user the user. 51 | * @param the type of connection. 52 | * @param the type of user. 53 | * @return {@code null} if the connection or user is not supported by this factory, or a {@link SSHAuthenticator} 54 | * instance bound to the supplied connection and user. 55 | */ 56 | @Nullable 57 | protected abstract SSHAuthenticator newInstance( 58 | @NonNull C connection, 59 | @NonNull U user); 60 | 61 | /** 62 | * Returns an instance of {@link SSHAuthenticator} for the supplied connection and user, or {@code null} if 63 | * the factory does not support the connection and user combination. 64 | * 65 | * @param connection the connection. 66 | * @param user the user. 67 | * @param username the username or {@code null} to fall back to the username in the user parameter. 68 | * @param the type of connection. 69 | * @param the type of user. 70 | * @return {@code null} if the connection or user is not supported by this factory, or a {@link SSHAuthenticator} 71 | * instance bound to the supplied connection and user. 72 | * @since 1.4 73 | */ 74 | @Nullable 75 | protected SSHAuthenticator newInstance( 76 | @NonNull C connection, 77 | @NonNull U user, 78 | @CheckForNull String username) { 79 | return newInstance(connection, user); 80 | } 81 | 82 | /** 83 | * Returns {@code true} if and only if the supplied connection class and user class are supported by this factory. 84 | * 85 | * @param connectionClass the connection class. 86 | * @param userClass the user class. 87 | * @param the type of connection. 88 | * @param the type of user. 89 | * @return {@code true} if and only if the supplied connection class and user class are supported by this factory. 90 | */ 91 | protected abstract boolean supports(@NonNull Class connectionClass, 92 | @NonNull Class userClass); 93 | 94 | private static final long serialVersionUID = 1L; 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials; 25 | 26 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 27 | 28 | /** 29 | * Represents a user name and a secret (password/privateKey/etc) needed to perform an authentication in an SSH 30 | * connection. 31 | *

32 | * This interface is a base interface that defines commonality across all the modes of authentications, 33 | * then the subtype defines a specific type of secret. 34 | *

35 | * 36 | * @see SSHUserPassword 37 | * @see SSHUserPrivateKey 38 | * @see SSHUserListBoxModel 39 | * @deprecated Use {@link StandardUsernameCredentials} as the common class. 40 | */ 41 | @Deprecated 42 | public interface SSHUser extends StandardUsernameCredentials { 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHUserListBoxModel.java: -------------------------------------------------------------------------------- 1 | package com.cloudbees.jenkins.plugins.sshcredentials; 2 | 3 | import com.cloudbees.plugins.credentials.CredentialsMatcher; 4 | import com.cloudbees.plugins.credentials.common.AbstractIdCredentialsListBoxModel; 5 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 6 | import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel; 7 | import com.cloudbees.plugins.credentials.domains.DomainRequirement; 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | import hudson.Util; 10 | import hudson.security.ACL; 11 | import jenkins.model.Jenkins; 12 | 13 | import java.util.Arrays; 14 | import java.util.Collection; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | /** @deprecated Use {@link StandardUsernameListBoxModel} with {@link SSHAuthenticator} instead. */ 19 | public class SSHUserListBoxModel extends AbstractIdCredentialsListBoxModel { 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @NonNull 24 | protected String describe(@NonNull StandardUsernameCredentials c) { 25 | String description = Util.fixEmptyAndTrim(c.getDescription()); 26 | return c.getUsername() + (description != null ? " (" + description + ")" : ""); 27 | } 28 | 29 | /** 30 | * @deprecated use {@link #with(com.cloudbees.plugins.credentials.common.IdCredentials)} 31 | */ 32 | @Deprecated 33 | public SSHUserListBoxModel add(StandardUsernameCredentials u) { 34 | with(u); 35 | return this; 36 | } 37 | 38 | /** 39 | * Adds a collection of credentials (they will be filtered with {@link SSHAuthenticator#matcher()} implicitly). 40 | * 41 | * @param col the collection of credentials. 42 | * @return {@code this} for method chaining. 43 | * @deprecated use {@link #withMatching(CredentialsMatcher, Iterable)} or {@link #withAll(Iterable)} 44 | */ 45 | @Deprecated 46 | public SSHUserListBoxModel addCollection(Collection col) { 47 | withMatching(SSHAuthenticator.matcher(), col); 48 | return this; 49 | } 50 | 51 | /** 52 | * Adds all the system-scoped credentials (they will be filtered with {@link SSHAuthenticator#matcher()} 53 | * implicitly). 54 | *

55 | * These credentials are meant to be used for system configuration and other things scoped to the {@link Jenkins} 56 | * object, 57 | * such as slaves. 58 | *

59 | * 60 | * @return {@code this} for method chaining. 61 | * @deprecated use {@link #withSystemScopeCredentials()} 62 | */ 63 | @Deprecated 64 | public SSHUserListBoxModel addSystemScopeCredentials() { 65 | return withSystemScopeCredentials(); 66 | } 67 | 68 | /** 69 | * Adds all the system-scoped credentials (they will be filtered with {@link SSHAuthenticator#matcher()} 70 | * implicitly). 71 | *

72 | * These credentials are meant to be used for system configuration and other things scoped to the {@link Jenkins} 73 | * object, 74 | * such as slaves. 75 | *

76 | * 77 | * @return {@code this} for method chaining. 78 | */ 79 | public SSHUserListBoxModel withSystemScopeCredentials() { 80 | return withSystemScopeCredentials(Collections.emptyList()); 81 | } 82 | 83 | /** 84 | * Adds all the system-scoped credentials (they will be filtered with {@link SSHAuthenticator#matcher()} 85 | * implicitly). 86 | *

87 | * These credentials are meant to be used for system configuration and other things scoped to the {@link Jenkins} 88 | * object, 89 | * such as slaves. 90 | *

91 | * 92 | * @param domainRequirements the domain requirements 93 | * @return {@code this} for method chaining. 94 | */ 95 | public SSHUserListBoxModel withSystemScopeCredentials(DomainRequirement... domainRequirements) { 96 | return withSystemScopeCredentials(SSHAuthenticator.matcher(), domainRequirements); 97 | } 98 | 99 | /** 100 | * Adds all the system-scoped credentials. 101 | *

102 | * These credentials are meant to be used for system configuration and other things scoped to the {@link Jenkins} 103 | * object, 104 | * such as slaves. 105 | *

106 | * 107 | * @param matcher a matcher to filter the credentials 108 | * @param domainRequirements the domain requirements 109 | * @return {@code this} for method chaining. 110 | */ 111 | public SSHUserListBoxModel withSystemScopeCredentials(CredentialsMatcher matcher, 112 | DomainRequirement... domainRequirements) { 113 | return withSystemScopeCredentials(matcher, Arrays.asList(domainRequirements)); 114 | } 115 | 116 | /** 117 | * Adds all the system-scoped credentials (they will be filtered with {@link SSHAuthenticator#matcher()} 118 | * implicitly). 119 | *

120 | * These credentials are meant to be used for system configuration and other things scoped to the {@link Jenkins} 121 | * object, 122 | * such as slaves. 123 | *

124 | * 125 | * @param domainRequirements the domain requirements 126 | * @return {@code this} for method chaining. 127 | */ 128 | public SSHUserListBoxModel withSystemScopeCredentials(List domainRequirements) { 129 | return withSystemScopeCredentials(SSHAuthenticator.matcher(), domainRequirements); 130 | } 131 | 132 | /** 133 | * Adds all the system-scoped credentials. 134 | *

135 | * These credentials are meant to be used for system configuration and other things scoped to the {@link Jenkins} 136 | * object, 137 | * such as slaves. 138 | *

139 | * 140 | * @param matcher a matcher to filter the credentials 141 | * @param domainRequirements the domain requirements 142 | * @return {@code this} for method chaining. 143 | */ 144 | public SSHUserListBoxModel withSystemScopeCredentials(CredentialsMatcher matcher, 145 | List domainRequirements) { 146 | includeMatchingAs(ACL.SYSTEM, Jenkins.getActiveInstance(), StandardUsernameCredentials.class, 147 | domainRequirements, matcher); 148 | return this; 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHUserPassword.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials; 25 | 26 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 27 | 28 | /** 29 | * Details of a SSH user with password. 30 | * @deprecated use {@link StandardUsernamePasswordCredentials} 31 | */ 32 | @Deprecated 33 | public interface SSHUserPassword extends SSHUser, StandardUsernamePasswordCredentials { 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/SSHUserPrivateKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials; 25 | 26 | import edu.umd.cs.findbugs.annotations.CheckForNull; 27 | import edu.umd.cs.findbugs.annotations.NonNull; 28 | import hudson.util.Secret; 29 | import java.util.List; 30 | 31 | /** 32 | * Details of a SSH user with a private key. 33 | */ 34 | public interface SSHUserPrivateKey extends SSHUser { 35 | /** 36 | * Returns the first private key. This should be in OpenSSH format. 37 | * 38 | * @return This is the actual content of the first private key and not the path to the private key. 39 | * @deprecated use {@link #getPrivateKeys()} 40 | */ 41 | @Deprecated 42 | @NonNull 43 | default String getPrivateKey() { 44 | List keys = getPrivateKeys(); 45 | return keys.isEmpty() ? "" : keys.get(0); 46 | } 47 | 48 | /** 49 | * Gets the passphrase for the private keys or {@code null} if the private keys are not protected by a 50 | * passphase. 51 | * 52 | * @return the passphrase for the private keys or {@code null} if the private key are not protected by 53 | * a passphase. 54 | */ 55 | @CheckForNull 56 | Secret getPassphrase(); 57 | 58 | /** 59 | * Returns a collection of keys to try in order for authentication. 60 | * 61 | * @return a collection of keys to try in order for authentication. 62 | * @since 0.5 63 | * @see SSHAuthenticator#getPrivateKeys 64 | */ 65 | @NonNull 66 | List getPrivateKeys(); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BaseSSHUser.java: -------------------------------------------------------------------------------- 1 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 2 | 3 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUser; 4 | import com.cloudbees.plugins.credentials.CredentialsScope; 5 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 6 | import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; 7 | import edu.umd.cs.findbugs.annotations.NonNull; 8 | import edu.umd.cs.findbugs.annotations.Nullable; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.kohsuke.stapler.DataBoundSetter; 11 | 12 | /** 13 | * @author stephenc 14 | * @since 28/02/2012 13:44 15 | */ 16 | public class BaseSSHUser extends BaseStandardCredentials implements SSHUser, StandardUsernameCredentials { 17 | 18 | /** 19 | * Ensure consistent serialization. 20 | */ 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * The username. 25 | */ 26 | protected final String username; 27 | 28 | @Nullable 29 | private Boolean usernameSecret = false; 30 | 31 | public BaseSSHUser(CredentialsScope scope, String id, String username, String description) { 32 | super(scope, id, description); 33 | this.username = username; 34 | } 35 | 36 | protected Object readResolve() { 37 | if (usernameSecret == null) { 38 | usernameSecret = true; 39 | } 40 | return this; 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | @NonNull 47 | public String getUsername() { 48 | return StringUtils.isEmpty(username) ? System.getProperty("user.name") : username; 49 | } 50 | 51 | @Override 52 | public boolean isUsernameSecret() { 53 | return usernameSecret; 54 | } 55 | 56 | @DataBoundSetter 57 | public void setUsernameSecret(boolean usernameSecret) { 58 | this.usernameSecret = usernameSecret; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPassword.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 25 | 26 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPassword; 27 | import com.cloudbees.plugins.credentials.CredentialsResolver; 28 | import com.cloudbees.plugins.credentials.CredentialsScope; 29 | import com.cloudbees.plugins.credentials.ResolveWith; 30 | import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; 31 | import edu.umd.cs.findbugs.annotations.NonNull; 32 | import hudson.model.Descriptor; 33 | import hudson.util.Secret; 34 | 35 | /** 36 | * A simple username / password for use with SSH connections. 37 | * 38 | * @deprecated use {@link UsernamePasswordCredentialsImpl} 39 | */ 40 | @ResolveWith(BasicSSHUserPassword.ResolverImpl.class) 41 | @Deprecated 42 | public class BasicSSHUserPassword extends BaseSSHUser implements SSHUserPassword { 43 | 44 | /** 45 | * Ensure consistent serialization. 46 | */ 47 | private static final long serialVersionUID = 1L; 48 | 49 | /** 50 | * The password. 51 | */ 52 | private final Secret password; 53 | 54 | /** 55 | * Constructor for stapler. 56 | * 57 | * @param scope the credentials scope 58 | * @param id 59 | * @param username the username. 60 | * @param password the password. 61 | * @param description the description. 62 | */ 63 | public BasicSSHUserPassword(CredentialsScope scope, String id, String username, String password, 64 | String description) { 65 | super(scope, id, username, description); 66 | this.password = Secret.fromString(password); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | @NonNull 73 | public Secret getPassword() { 74 | return password; 75 | } 76 | 77 | @Override 78 | protected Object readResolve() { 79 | UsernamePasswordCredentialsImpl resolved; 80 | try { 81 | resolved = new UsernamePasswordCredentialsImpl(getScope(), getId(), getDescription(), getUsername(), getPassword().getEncryptedValue()); 82 | } catch (Descriptor.FormException e) { 83 | throw new RuntimeException(e); 84 | } 85 | resolved.setUsernameSecret(true); 86 | return resolved; 87 | } 88 | 89 | /** 90 | * Resolve credentials for legacy code. 91 | * 92 | * @since 0.5 93 | */ 94 | public static class ResolverImpl 95 | extends CredentialsResolver { 96 | 97 | /** 98 | * Default constructor. 99 | */ 100 | public ResolverImpl() { 101 | super(UsernamePasswordCredentialsImpl.class); 102 | } 103 | 104 | @NonNull 105 | @Override 106 | protected BasicSSHUserPassword doResolve(@NonNull UsernamePasswordCredentialsImpl original) { 107 | return new BasicSSHUserPassword(original.getScope(), original.getId(), original.getUsername(), 108 | original.getPassword().getEncryptedValue(), original.getDescription()); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 25 | 26 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 27 | import com.cloudbees.plugins.credentials.CredentialsScope; 28 | import edu.umd.cs.findbugs.annotations.CheckForNull; 29 | import edu.umd.cs.findbugs.annotations.NonNull; 30 | import hudson.DescriptorExtensionList; 31 | import hudson.Extension; 32 | import hudson.RelativePath; 33 | import hudson.model.AbstractDescribableImpl; 34 | import hudson.model.Descriptor; 35 | import hudson.model.Items; 36 | import hudson.util.FormValidation; 37 | import hudson.util.Secret; 38 | import java.io.File; 39 | import java.io.IOException; 40 | import java.io.Serializable; 41 | import java.security.PrivateKey; 42 | import java.security.UnrecoverableKeyException; 43 | import java.security.interfaces.RSAPrivateKey; 44 | import java.util.ArrayList; 45 | import java.util.Arrays; 46 | import java.util.Collections; 47 | import java.util.List; 48 | import java.util.concurrent.TimeUnit; 49 | import java.util.logging.Level; 50 | import java.util.logging.Logger; 51 | import java.util.stream.Collectors; 52 | 53 | import jenkins.bouncycastle.api.PEMEncodable; 54 | import jenkins.model.Jenkins; 55 | import jenkins.security.FIPS140; 56 | import net.jcip.annotations.GuardedBy; 57 | import org.apache.commons.io.FileUtils; 58 | import org.apache.commons.lang.StringUtils; 59 | import org.kohsuke.stapler.DataBoundConstructor; 60 | import org.kohsuke.stapler.QueryParameter; 61 | import org.kohsuke.stapler.interceptor.RequirePOST; 62 | 63 | /** 64 | * A simple username / password for use with SSH connections. 65 | */ 66 | public class BasicSSHUserPrivateKey extends BaseSSHUser implements SSHUserPrivateKey { 67 | 68 | /** 69 | * Ensure consistent serialization. 70 | */ 71 | private static final long serialVersionUID = 1L; 72 | 73 | /** 74 | * The password. 75 | */ 76 | private final Secret passphrase; 77 | 78 | /** 79 | * The private key. If you care about securing this, use a passphrase. 80 | */ 81 | private final PrivateKeySource privateKeySource; 82 | 83 | /** 84 | * The private key. 85 | */ 86 | @GuardedBy("this") 87 | private transient List privateKeys; 88 | 89 | /** 90 | * The maximum amount of time to cache the private keys before refreshing. 91 | * 92 | * @since 1.1 93 | */ 94 | @GuardedBy("this") 95 | private transient long privateKeysLastModified; 96 | 97 | /** 98 | * Constructor for stapler. 99 | * 100 | * @param scope the credentials scope 101 | * @param username the username. 102 | * @param privateKeySource the private key. 103 | * @param passphrase the password. 104 | * @param description the description. 105 | */ 106 | @DataBoundConstructor 107 | public BasicSSHUserPrivateKey(CredentialsScope scope, String id, String username, PrivateKeySource privateKeySource, 108 | String passphrase, 109 | String description) { 110 | super(scope, id, username, description); 111 | this.privateKeySource = privateKeySource == null ? new DirectEntryPrivateKeySource("") : privateKeySource; 112 | this.passphrase = fixEmpty(passphrase == null ? null : Secret.fromString(passphrase)); 113 | checkKeyFipsCompliance(this.privateKeySource.getPrivateKeys().isEmpty() ? "" : this.privateKeySource.getPrivateKeys().get(0), this.passphrase); 114 | } 115 | 116 | private static Secret fixEmpty(Secret secret) { 117 | return secret == null ? null : secret.getPlainText().isEmpty() ? null : secret; 118 | } 119 | 120 | @Override 121 | protected synchronized Object readResolve() { 122 | if (privateKeySource == null) { 123 | Secret passphrase = getPassphrase(); 124 | if (privateKeys != null) { 125 | return new BasicSSHUserPrivateKey( 126 | getScope(), 127 | getId(), 128 | getUsername(), 129 | new DirectEntryPrivateKeySource(privateKeys), 130 | passphrase == null ? null : passphrase.getEncryptedValue(), 131 | getDescription() 132 | ); 133 | } 134 | return new BasicSSHUserPrivateKey( 135 | getScope(), 136 | getId(), 137 | getUsername(), 138 | new DirectEntryPrivateKeySource(""), 139 | passphrase == null ? null : passphrase.getEncryptedValue(), 140 | getDescription() 141 | ); 142 | } 143 | checkKeyFipsCompliance(this.privateKeySource.getPrivateKeys().isEmpty() ? "" : privateKeySource.getPrivateKeys().get(0), passphrase); 144 | if (passphrase != null && fixEmpty(passphrase) == null) { 145 | return new BasicSSHUserPrivateKey( 146 | getScope(), 147 | getId(), 148 | getUsername(), 149 | privateKeySource, 150 | null, 151 | getDescription() 152 | ); 153 | } 154 | return super.readResolve(); 155 | } 156 | 157 | @NonNull 158 | public synchronized List getPrivateKeys() { 159 | if (privateKeySource == null) { 160 | return Collections.emptyList(); 161 | } 162 | long lastModified = privateKeySource.getPrivateKeysLastModified(); 163 | if (privateKeys == null || privateKeys.isEmpty() || lastModified > privateKeysLastModified) { 164 | this.privateKeys = privateKeySource.getPrivateKeys().stream() 165 | .map(privateKey -> privateKey.endsWith("\n") ? privateKey : privateKey + "\n") 166 | .collect(Collectors.toList()); 167 | this.privateKeysLastModified = lastModified; 168 | } 169 | return privateKeys; 170 | } 171 | 172 | @NonNull 173 | public PrivateKeySource getPrivateKeySource() { 174 | return privateKeySource == null ? new DirectEntryPrivateKeySource("") : privateKeySource; 175 | } 176 | 177 | /** 178 | * {@inheritDoc} 179 | */ 180 | @CheckForNull 181 | public Secret getPassphrase() { 182 | return passphrase; 183 | } 184 | 185 | /** 186 | * Checks if provided key is compliant with FIPS 140-2. 187 | * OpenSSH keys are not compliant. (the structure that contains the key is not ASN.1.) 188 | * Only Ed25519 or RSA (with a minimum size of 2048) keys are accepted. 189 | * Method will throw an {@link IllegalArgumentException} if key is not compliant. 190 | * @param privateKeySource the keySource 191 | * @param passphrase the secret used with the key (null if no secret provided) 192 | */ 193 | private static void checkKeyFipsCompliance(String privateKeySource, Secret passphrase) { 194 | if (!FIPS140.useCompliantAlgorithms()) { 195 | return; // maintain existing behaviour if not in FIPS mode 196 | } 197 | if (StringUtils.isBlank(privateKeySource)) { 198 | return; 199 | } 200 | try { 201 | char[] pass = passphrase == null ? null : passphrase.getPlainText().toCharArray(); 202 | if (pass != null && pass.length < 14) { 203 | throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_TooShortPassphraseFIPS()); 204 | } 205 | PEMEncodable pem = PEMEncodable.decode(privateKeySource, pass); 206 | PrivateKey privateKey = pem.toPrivateKey(); 207 | if (privateKey == null) { //somehow malformed key or unknown algorithm 208 | throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_UnknownAlgorithmFIPS()); 209 | } 210 | if (privateKey instanceof RSAPrivateKey) { 211 | if (((RSAPrivateKey) privateKey).getModulus().bitLength() < 2048) { 212 | throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeySizeFIPS()); 213 | } 214 | } else if (!"Ed25519".equals(privateKey.getAlgorithm())) { 215 | // TODO: update this to use the JDK17 support when available in the plugin baseline 216 | // Using algorithm name to check elliptic curve, as EdECPrivateKey is not available in jdk11 217 | throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidAlgorithmFIPS(privateKey.getAlgorithm())); 218 | } 219 | } catch (IOException ex) { // OpenSSH keys will raise this, so we need to check if it's a valid openssh key 220 | throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_InvalidKeyFormatFIPS(ex.getLocalizedMessage()), ex); 221 | } catch (UnrecoverableKeyException ex) { 222 | String errorMessage = ex.getLocalizedMessage() == null ? "wrong passphrase" : ex.getLocalizedMessage(); 223 | throw new IllegalArgumentException(Messages.BasicSSHUserPrivateKey_KeyParseErrorFIPS(errorMessage), ex); 224 | } 225 | } 226 | 227 | /** 228 | * {@inheritDoc} 229 | */ 230 | @Extension 231 | public static class DescriptorImpl extends BaseStandardCredentialsDescriptor { 232 | 233 | /** 234 | * {@inheritDoc} 235 | */ 236 | @NonNull 237 | @Override 238 | public String getDisplayName() { 239 | return Messages.BasicSSHUserPrivateKey_DisplayName(); 240 | } 241 | 242 | public DescriptorExtensionList> getPrivateKeySources() { 243 | return Jenkins.get().getDescriptorList(PrivateKeySource.class); 244 | } 245 | 246 | /** 247 | * {@inheritDoc} 248 | */ 249 | public String getIconClassName() { 250 | return "symbol-fingerprint"; 251 | } 252 | } 253 | 254 | /** 255 | * A source of private keys 256 | */ 257 | public static abstract class PrivateKeySource extends AbstractDescribableImpl { 258 | /** 259 | * Gets the private key from the source 260 | */ 261 | @NonNull 262 | public abstract List getPrivateKeys(); 263 | 264 | /** 265 | * Returns a revision count or a timestamp (in either case strictly increasing after changes to the private 266 | * keys) 267 | * 268 | * @return a revision count or a timestamp. 269 | * @since 1.4 270 | */ 271 | public long getPrivateKeysLastModified() { 272 | return 1; // pick a default that is greater than the field initializer for constant sources. 273 | } 274 | 275 | /** 276 | * Returns {@code true} if and only if the source is self contained. 277 | * 278 | * @return {@code true} if and only if the source is self contained. 279 | * @since 1.7 280 | * @deprecated no more used since FileOnMaster- and Users- PrivateKeySource are deprecated too 281 | */ 282 | @Deprecated 283 | public boolean isSnapshotSource() { 284 | return false; 285 | } 286 | 287 | } 288 | 289 | /** 290 | * Descriptor for a {@link PrivateKeySource} 291 | */ 292 | public static abstract class PrivateKeySourceDescriptor extends Descriptor { 293 | } 294 | 295 | /** 296 | * Let the user enter the key directly via copy & paste 297 | */ 298 | public static class DirectEntryPrivateKeySource extends PrivateKeySource implements Serializable { 299 | /** 300 | * Ensure consistent serialization. 301 | */ 302 | private static final long serialVersionUID = 1L; 303 | 304 | private final Secret privateKey; 305 | 306 | public DirectEntryPrivateKeySource(String privateKey) { 307 | this(Secret.fromString(privateKey.endsWith("\n") ? privateKey : privateKey + "\n")); 308 | } 309 | 310 | @DataBoundConstructor 311 | public DirectEntryPrivateKeySource(Secret privateKey) { 312 | this.privateKey = privateKey; 313 | } 314 | 315 | public DirectEntryPrivateKeySource(List privateKeys) { 316 | this(StringUtils.join(privateKeys, "\f")); 317 | } 318 | 319 | /** 320 | * {@inheritDoc} 321 | */ 322 | @NonNull 323 | @Override 324 | public List getPrivateKeys() { 325 | String privateKeys = Secret.toString(privateKey); 326 | return StringUtils.isBlank(privateKeys) 327 | ? Collections.emptyList() 328 | : Arrays.asList(StringUtils.split(privateKeys, "\f")); 329 | } 330 | 331 | /** 332 | * Returns the private key. 333 | * 334 | * @return the private key. 335 | */ 336 | @SuppressWarnings("unused") // used by Jelly EL 337 | public Secret getPrivateKey() { 338 | return privateKey; 339 | } 340 | 341 | /** 342 | * {@inheritDoc} 343 | */ 344 | @Override 345 | public boolean isSnapshotSource() { 346 | return true; 347 | } 348 | 349 | /** 350 | * {@inheritDoc} 351 | */ 352 | @Extension 353 | public static class DescriptorImpl extends PrivateKeySourceDescriptor { 354 | 355 | /** 356 | * {@inheritDoc} 357 | */ 358 | @NonNull 359 | @Override 360 | public String getDisplayName() { 361 | return Messages.BasicSSHUserPrivateKey_DirectEntryPrivateKeySourceDisplayName(); 362 | } 363 | 364 | @RequirePOST 365 | public FormValidation doCheckPrivateKey (@QueryParameter String privateKey, 366 | @RelativePath("..") @QueryParameter String passphrase) { 367 | try { 368 | checkKeyFipsCompliance(privateKey, Secret.fromString(passphrase)); 369 | return FormValidation.ok(); 370 | } catch (IllegalArgumentException ex) { 371 | return FormValidation.error(ex, ex.getMessage()); 372 | } 373 | 374 | } 375 | } 376 | } 377 | 378 | /** 379 | * Let the user reference a file on the disk. 380 | * @deprecated This approach has security vulnerability and should be migrated to {@link DirectEntryPrivateKeySource} 381 | */ 382 | @Deprecated 383 | public static class FileOnMasterPrivateKeySource extends PrivateKeySource { 384 | 385 | /** 386 | * Our logger 387 | */ 388 | private static final Logger LOGGER = Logger.getLogger(FileOnMasterPrivateKeySource.class.getName()); 389 | 390 | /** 391 | * The path to the private key. 392 | */ 393 | private final String privateKeyFile; 394 | 395 | /** 396 | * When any of the key files was last modified. 397 | */ 398 | private transient volatile long lastModified; 399 | 400 | /** 401 | * When we will next try a refresh of the status. 402 | */ 403 | private transient volatile long nextCheckLastModified; 404 | 405 | public FileOnMasterPrivateKeySource(String privateKeyFile) { 406 | this.privateKeyFile = privateKeyFile; 407 | } 408 | 409 | /** 410 | * {@inheritDoc} 411 | */ 412 | @NonNull 413 | @Override 414 | public List getPrivateKeys() { 415 | if (privateKeyFile != null) { 416 | File key = new File(privateKeyFile); 417 | if (key.isFile()) { 418 | try { 419 | return Collections.singletonList(FileUtils.readFileToString(key)); 420 | } catch (IOException e) { 421 | LOGGER.log(Level.WARNING, "Could not read private key file " + privateKeyFile, e); 422 | } 423 | } 424 | } 425 | return Collections.emptyList(); 426 | } 427 | 428 | /** 429 | * Returns the private key file name. 430 | * 431 | * @return the private key file name. 432 | */ 433 | public String getPrivateKeyFile() { 434 | return privateKeyFile; 435 | } 436 | 437 | private Object readResolve() { 438 | if (privateKeyFile != null 439 | && privateKeyFile.startsWith("---") 440 | && privateKeyFile.contains("---BEGIN") 441 | && privateKeyFile.contains("---END")) { 442 | // this is a borked upgrade, not actually the file name but is actually the key contents 443 | return new DirectEntryPrivateKeySource(privateKeyFile); 444 | } 445 | 446 | Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); 447 | 448 | LOGGER.log(Level.INFO, "SECURITY-440: Migrating FileOnMasterPrivateKeySource to DirectEntryPrivateKeySource"); 449 | // read the content of the file and then migrate to Direct 450 | return new DirectEntryPrivateKeySource(getPrivateKeys()); 451 | } 452 | 453 | @Override 454 | public long getPrivateKeysLastModified() { 455 | if (nextCheckLastModified > System.currentTimeMillis() || lastModified < 0) { 456 | lastModified = Long.MIN_VALUE; 457 | if (privateKeyFile != null) { 458 | File file = new File(privateKeyFile); 459 | if (file.exists()) { 460 | lastModified = file.lastModified(); 461 | } 462 | } 463 | nextCheckLastModified = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); 464 | } 465 | return lastModified; 466 | } 467 | } 468 | 469 | /** 470 | * Let the user 471 | * @deprecated This approach has security vulnerability and should be migrated to {@link DirectEntryPrivateKeySource} 472 | */ 473 | @Deprecated 474 | public static class UsersPrivateKeySource extends PrivateKeySource { 475 | 476 | /** 477 | * Our logger 478 | */ 479 | private static final Logger LOGGER = Logger.getLogger(UsersPrivateKeySource.class.getName()); 480 | 481 | /** 482 | * When any of the key files was last modified. 483 | */ 484 | private transient volatile long lastModified; 485 | 486 | /** 487 | * When we will next try a refresh of the status. 488 | */ 489 | private transient volatile long nextCheckLastModified; 490 | 491 | private List files() { 492 | List files = new ArrayList<>(); 493 | File sshHome = new File(new File(System.getProperty("user.home")), ".ssh"); 494 | for (String keyName : Arrays.asList("id_ecdsa", "id_ed25519", "id_rsa", "id_dsa", "identity")) { 495 | File key = new File(sshHome, keyName); 496 | if (key.isFile()) { 497 | files.add(key); 498 | } 499 | } 500 | return files; 501 | } 502 | 503 | /** 504 | * {@inheritDoc} 505 | */ 506 | @NonNull 507 | @Override 508 | public List getPrivateKeys() { 509 | List keys = new ArrayList<>(); 510 | for (File file : files()) { 511 | try { 512 | keys.add(FileUtils.readFileToString(file)); 513 | } catch (IOException e) { 514 | LOGGER.log(Level.WARNING, "Could not read private key", e); 515 | } 516 | } 517 | return keys; 518 | } 519 | 520 | @Override 521 | public long getPrivateKeysLastModified() { 522 | if (nextCheckLastModified > System.currentTimeMillis() || lastModified < 0) { 523 | lastModified = Long.MIN_VALUE; 524 | for (File file : files()) { 525 | lastModified = Math.max(lastModified, file.lastModified()); 526 | } 527 | nextCheckLastModified = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); 528 | } 529 | return lastModified; 530 | } 531 | 532 | private Object readResolve() { 533 | Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); 534 | 535 | LOGGER.log(Level.INFO, "SECURITY-440: Migrating UsersPrivateKeySource to DirectEntryPrivateKeySource"); 536 | // read the content of the file and then migrate to Direct 537 | return new DirectEntryPrivateKeySource(getPrivateKeys()); 538 | } 539 | } 540 | 541 | static { 542 | // the critical field allow the permission check to make the XML read to fail completely in case of violation 543 | Items.XSTREAM2.addCriticalField(BasicSSHUserPrivateKey.class, "privateKeySource"); 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/SSHUserPrivateKeySnapshotTaker.java: -------------------------------------------------------------------------------- 1 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 2 | 3 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 4 | import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.DirectEntryPrivateKeySource; 5 | import com.cloudbees.plugins.credentials.CredentialsSnapshotTaker; 6 | 7 | import hudson.Extension; 8 | import hudson.util.Secret; 9 | 10 | @Extension 11 | public class SSHUserPrivateKeySnapshotTaker extends CredentialsSnapshotTaker { 12 | /** 13 | * {@inheritDoc} 14 | */ 15 | @Override 16 | public Class type() { 17 | return SSHUserPrivateKey.class; 18 | } 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | public SSHUserPrivateKey snapshot(SSHUserPrivateKey credentials) { 25 | if (credentials instanceof BasicSSHUserPrivateKey && ((BasicSSHUserPrivateKey) credentials).getPrivateKeySource() instanceof DirectEntryPrivateKeySource) { 26 | return credentials; 27 | } 28 | final Secret passphrase = credentials.getPassphrase(); 29 | return new BasicSSHUserPrivateKey(credentials.getScope(), credentials.getId(), credentials.getUsername(), 30 | new DirectEntryPrivateKeySource(credentials.getPrivateKeys()), 31 | passphrase == null ? null : passphrase.getEncryptedValue(), credentials.getDescription()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/TrileadSSHPasswordAuthenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 25 | 26 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticator; 27 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticatorFactory; 28 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 29 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 30 | import com.trilead.ssh2.Connection; 31 | import edu.umd.cs.findbugs.annotations.CheckForNull; 32 | import edu.umd.cs.findbugs.annotations.NonNull; 33 | import edu.umd.cs.findbugs.annotations.Nullable; 34 | import org.jenkinsci.plugins.variant.OptionalExtension; 35 | 36 | import java.io.IOException; 37 | import java.util.Arrays; 38 | import java.util.List; 39 | import java.util.Locale; 40 | import java.util.logging.Logger; 41 | 42 | /** 43 | * Does password auth with a {@link Connection}. 44 | */ 45 | public class TrileadSSHPasswordAuthenticator extends SSHAuthenticator { 46 | 47 | /** 48 | * Our logger 49 | */ 50 | private static final Logger LOGGER = Logger.getLogger(TrileadSSHPasswordAuthenticator.class.getName()); 51 | private static final String PASSWORD = "password"; 52 | private static final String KEYBOARD_INTERACTIVE = "keyboard-interactive"; 53 | 54 | /** 55 | * Constructor. 56 | * 57 | * @param connection the connection we will be authenticating. 58 | * @deprecated 59 | */ 60 | @Deprecated 61 | public TrileadSSHPasswordAuthenticator(Connection connection, StandardUsernamePasswordCredentials user) { 62 | this(connection, user, null); 63 | } 64 | 65 | /** 66 | * Constructor. 67 | * 68 | * @param connection the connection we will be authenticating. 69 | * @since 1.4 70 | */ 71 | public TrileadSSHPasswordAuthenticator(@NonNull Connection connection, 72 | @NonNull StandardUsernamePasswordCredentials user, 73 | @CheckForNull String username) { 74 | super(connection, user, username); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | @Override 81 | public boolean canAuthenticate() { 82 | try { 83 | for (String authMethod : getConnection().getRemainingAuthMethods(getUsername())) { 84 | if (PASSWORD.equals(authMethod)) { 85 | // prefer password 86 | return true; 87 | } 88 | if (KEYBOARD_INTERACTIVE.equals(authMethod)) { 89 | return true; 90 | } 91 | } 92 | } catch (IOException e) { 93 | e.printStackTrace(getListener().error("Failed to authenticate")); 94 | } 95 | return false; 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | */ 101 | @Override 102 | protected boolean doAuthenticate() { 103 | final StandardUsernamePasswordCredentials user = getUser(); 104 | final String username = getUsername(); 105 | 106 | try { 107 | final Connection connection = getConnection(); 108 | final String password = user.getPassword().getPlainText(); 109 | boolean tried = false; 110 | 111 | List availableMethods = Arrays.asList(connection.getRemainingAuthMethods(username)); 112 | if (availableMethods.contains(PASSWORD)) { 113 | // prefer password 114 | if (connection.authenticateWithPassword(username, password)) { 115 | LOGGER.fine("Authentication with 'password' succeeded."); 116 | return true; 117 | } 118 | getListener().error("Failed to authenticate as %s. Wrong password. (credentialId:%s/method:password)", 119 | username, user.getId()); 120 | tried = true; 121 | } 122 | if (availableMethods.contains(KEYBOARD_INTERACTIVE)) { 123 | if (connection.authenticateWithKeyboardInteractive(username, (name, instruction, numPrompts, prompt, echo) -> { 124 | // most SSH servers just use keyboard interactive to prompt for the password 125 | // match "assword" is safer than "password"... you don't *want* to know why! 126 | return prompt != null && prompt.length > 0 && prompt[0].toLowerCase(Locale.ENGLISH) 127 | .contains("assword") 128 | ? new String[]{password} 129 | : new String[0]; 130 | })) { 131 | LOGGER.fine("Authentication with 'keyboard-interactive' succeeded."); 132 | return true; 133 | } 134 | getListener() 135 | .error("Failed to authenticate as %s. Wrong password. " 136 | + "(credentialId:%s/method:keyboard-interactive)", 137 | username, user.getId()); 138 | tried = true; 139 | } 140 | 141 | if (!tried) { 142 | getListener().error("The server does not allow password authentication. Available options are %s", 143 | availableMethods); 144 | } 145 | } catch (IOException e) { 146 | e.printStackTrace(getListener() 147 | .error("Unexpected error while trying to authenticate as %s with credential=%s", username, 148 | user.getId())); 149 | } 150 | return false; 151 | } 152 | 153 | /** 154 | * {@inheritDoc} 155 | */ 156 | @OptionalExtension(requirePlugins = {"trilead-api"}) 157 | public static class Factory extends SSHAuthenticatorFactory { 158 | 159 | /** 160 | * {@inheritDoc} 161 | */ 162 | @Override 163 | protected SSHAuthenticator newInstance(@NonNull C connection, 164 | @NonNull U user) { 165 | return newInstance(connection, user, null); 166 | } 167 | 168 | /** 169 | * {@inheritDoc} 170 | */ 171 | @Nullable 172 | @Override 173 | @SuppressWarnings("unchecked") 174 | protected SSHAuthenticator newInstance(@NonNull C connection, 175 | @NonNull U user, 176 | @CheckForNull String 177 | username) { 178 | if (supports(connection.getClass(), user.getClass())) { 179 | return (SSHAuthenticator) new TrileadSSHPasswordAuthenticator((Connection) connection, 180 | (StandardUsernamePasswordCredentials) user, username); 181 | } 182 | return null; 183 | } 184 | 185 | /** 186 | * {@inheritDoc} 187 | */ 188 | @Override 189 | protected boolean supports(@NonNull Class connectionClass, 190 | @NonNull Class userClass) { 191 | return Connection.class.isAssignableFrom(connectionClass) 192 | && StandardUsernamePasswordCredentials.class.isAssignableFrom(userClass); 193 | } 194 | 195 | private static final long serialVersionUID = 1L; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/TrileadSSHPublicKeyAuthenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 25 | 26 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticator; 27 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticatorFactory; 28 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 29 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 30 | import com.trilead.ssh2.Connection; 31 | import edu.umd.cs.findbugs.annotations.CheckForNull; 32 | import edu.umd.cs.findbugs.annotations.NonNull; 33 | import edu.umd.cs.findbugs.annotations.Nullable; 34 | import hudson.util.Secret; 35 | import org.jenkinsci.plugins.variant.OptionalExtension; 36 | 37 | import java.io.IOException; 38 | import java.util.ArrayList; 39 | import java.util.Arrays; 40 | import java.util.Collection; 41 | import java.util.List; 42 | import java.util.logging.Logger; 43 | 44 | /** 45 | * Does public key auth with a {@link Connection}. 46 | */ 47 | public class TrileadSSHPublicKeyAuthenticator extends SSHAuthenticator { 48 | 49 | /** 50 | * Our logger. 51 | */ 52 | private static final Logger LOGGER = Logger.getLogger(TrileadSSHPublicKeyAuthenticator.class.getName()); 53 | private static final String PUBLICKEY = "publickey"; 54 | 55 | /** 56 | * Constructor. 57 | * 58 | * @param connection the connection we will be authenticating. 59 | */ 60 | public TrileadSSHPublicKeyAuthenticator(Connection connection, SSHUserPrivateKey user) { 61 | this(connection, user, null); 62 | } 63 | 64 | /** 65 | * Constructor. 66 | * 67 | * @param connection the connection we will be authenticating. 68 | */ 69 | public TrileadSSHPublicKeyAuthenticator(@NonNull Connection connection, 70 | @NonNull SSHUserPrivateKey user, 71 | @CheckForNull String username) { 72 | super(connection, user, username); 73 | } 74 | 75 | /** 76 | * {@inheritDoc} 77 | */ 78 | @Override 79 | public boolean canAuthenticate() { 80 | try { 81 | return getRemainingAuthMethods().contains(PUBLICKEY); 82 | } catch (IOException e) { 83 | e.printStackTrace(getListener().error("Failed to authenticate")); 84 | return false; 85 | } 86 | } 87 | 88 | private List getRemainingAuthMethods() throws IOException { 89 | return Arrays.asList(getConnection().getRemainingAuthMethods(getUsername())); 90 | } 91 | 92 | /** 93 | * {@inheritDoc} 94 | */ 95 | @Override 96 | protected boolean doAuthenticate() { 97 | final SSHUserPrivateKey user = getUser(); 98 | final String username = getUsername(); 99 | try { 100 | final Connection connection = getConnection(); 101 | final Secret userPassphrase = user.getPassphrase(); 102 | final String passphrase = userPassphrase == null ? null : userPassphrase.getPlainText(); 103 | 104 | Collection availableMethods = getRemainingAuthMethods(); 105 | if (availableMethods.contains(PUBLICKEY)) { 106 | int count = 0; 107 | List ioe = new ArrayList<>(); 108 | for (String privateKey : getPrivateKeys(user)) { 109 | try { 110 | if (connection.authenticateWithPublicKey(username, privateKey.toCharArray(), passphrase)) { 111 | LOGGER.fine("Authentication with 'publickey' succeeded."); 112 | return true; 113 | } 114 | } catch (IOException e) { 115 | ioe.add(e); 116 | } 117 | count++; 118 | getListener() 119 | .error("Server rejected the %d private key(s) for %s (credentialId:%s/method:publickey)", 120 | count, username, user.getId()); 121 | } 122 | for (IOException e : ioe) { 123 | e.printStackTrace(getListener() 124 | .error("Failed to authenticate as %s with credential=%s", username, getUser().getId())); 125 | } 126 | return false; 127 | } else { 128 | getListener().error("The server does not allow public key authentication. Available options are %s", 129 | availableMethods); 130 | return false; 131 | } 132 | } catch (IOException e) { 133 | e.printStackTrace(getListener() 134 | .error("Failed to authenticate as %s with credential=%s", username, getUser().getId())); 135 | return false; 136 | } 137 | } 138 | 139 | /** 140 | * {@inheritDoc} 141 | */ 142 | @OptionalExtension(requirePlugins = {"trilead-api"}) 143 | public static class Factory extends SSHAuthenticatorFactory { 144 | 145 | /** 146 | * {@inheritDoc} 147 | */ 148 | @Override 149 | @SuppressWarnings("unchecked") 150 | protected SSHAuthenticator newInstance(@NonNull C connection, 151 | @NonNull U user) { 152 | return newInstance(connection, user, null); 153 | } 154 | 155 | /** 156 | * {@inheritDoc} 157 | */ 158 | @Nullable 159 | @Override 160 | @SuppressWarnings("unchecked") 161 | protected SSHAuthenticator newInstance(@NonNull C connection, 162 | @NonNull U user, 163 | @CheckForNull String 164 | username) { 165 | if (supports(connection.getClass(), user.getClass())) { 166 | return (SSHAuthenticator) new TrileadSSHPublicKeyAuthenticator((Connection) connection, 167 | (SSHUserPrivateKey) user, username); 168 | } 169 | return null; 170 | } 171 | 172 | /** 173 | * {@inheritDoc} 174 | */ 175 | @Override 176 | protected boolean supports(@NonNull Class connectionClass, 177 | @NonNull Class userClass) { 178 | return Connection.class.isAssignableFrom(connectionClass) 179 | && SSHUserPrivateKey.class.isAssignableFrom(userClass); 180 | } 181 | 182 | private static final long serialVersionUID = 1L; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/Messages.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2011-2013, CloudBees, Inc., Stephen Connolly. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | SSHUserListBoxModel.EmptySelection=- none - -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config_de.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2013 Harald Albers 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | 24 | Key=Schl\u00FCssel -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/DirectEntryPrivateKeySource/config_ja.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2013 Seiji Sogabe. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | Key=\u9375 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials.jelly: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials_de.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2013 Harald Albers 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | 24 | Username=Benutzername 25 | Private\ Key=Privater Schl\u00fcssel 26 | Passphrase=Passwort/Passphrase 27 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/credentials_ja.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright (c) 2013 Seiji Sogabe. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | Username=\u30e6\u30fc\u30b6\u30fc\u540d 24 | Private\ Key=\u79d8\u5bc6\u9375 25 | Passphrase=\u30d1\u30b9\u30d5\u30ec\u30fc\u30ba 26 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/help-usernameSecret.html: -------------------------------------------------------------------------------- 1 |
2 | By default, usernames are not masked in the build log. 3 | You may choose to mask a credential's username if you consider it to be sensitive information. 4 | However, this can interfere with diagnostics. 5 | For example, if the username is a common word it will cause unrelated occurrences of that word to also be masked. 6 |
7 | Regardless of this setting, the username will be displayed in the selection dropdown to anyone permitted to reconfigure the credentials. 8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey/passphraseChangeEvent.js: -------------------------------------------------------------------------------- 1 | //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 2 | const passphraseElements = document.getElementsByClassName('sshCredentials_passphrase'); 3 | 4 | if (passphraseElements.length > 0) { 5 | // Failsafe in case there's more than 1 element we'll only use the first one. Should not happen. 6 | passphraseElements[0].addEventListener("change", event => { 7 | var newEvent = new Event("change", {"bubbles": true}) 8 | const privateKeyElements = document.getElementsByName('_.privateKey'); 9 | if (privateKeyElements.length > 0) { 10 | privateKeyElements[0].dispatchEvent(newEvent) 11 | } 12 | }) 13 | } -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | BasicSSHUserPrivateKey.DirectEntryPrivateKeySourceDisplayName=Enter directly 25 | BasicSSHUserPrivateKey.DisplayName=SSH Username with private key 26 | BasicSSHUserPrivateKey.UnknownAlgorithmFIPS=Private key can not be obtained from provided data. 27 | BasicSSHUserPrivateKey.InvalidKeySizeFIPS=Key size below 2048 for RSA keys is not accepted in FIPS mode. 28 | BasicSSHUserPrivateKey.InvalidAlgorithmFIPS=Key algorithm {0} is not accepted in FIPS mode. 29 | BasicSSHUserPrivateKey.InvalidKeyFormatFIPS=Provided data can not be parsed: {0}. 30 | BasicSSHUserPrivateKey.KeyParseErrorFIPS=Can not parse key: {0} 31 | BasicSSHUserPrivateKey.TooShortPassphraseFIPS=Password is too short (< 14 characters). 32 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages_de.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2013 Harald Albers 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | BasicSSHUserPrivateKey.DirectEntryPrivateKeySourceDisplayName=Direkt eingeben 25 | BasicSSHUserPrivateKey.DisplayName=SSH Benutzername und privater Schl\u00fcssel 26 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/Messages_ja.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2011-2013, CloudBees, Inc., Stephen Connolly, Seiji Sogabe 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # 24 | BasicSSHUserPrivateKey.DirectEntryPrivateKeySourceDisplayName=\u76f4\u63a5\u5165\u529b 25 | BasicSSHUserPrivateKey.DisplayName=SSH \u30e6\u30fc\u30b6\u30fc\u540d\u3068\u79d8\u5bc6\u9375 26 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Allows storage of SSH credentials in Jenkins 4 |
5 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest.java: -------------------------------------------------------------------------------- 1 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 2 | 3 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 4 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 5 | import com.cloudbees.plugins.credentials.CredentialsProvider; 6 | import com.cloudbees.plugins.credentials.CredentialsScope; 7 | import com.cloudbees.plugins.credentials.CredentialsStore; 8 | import com.cloudbees.plugins.credentials.domains.Domain; 9 | import hudson.ExtensionList; 10 | import hudson.security.ACL; 11 | import hudson.util.FormValidation; 12 | import org.apache.commons.io.FileUtils; 13 | import org.apache.commons.lang.StringUtils; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.jvnet.hudson.test.Issue; 17 | import org.jvnet.hudson.test.JenkinsRule; 18 | import org.jvnet.hudson.test.RealJenkinsRule; 19 | import org.jvnet.hudson.test.recipes.LocalData; 20 | 21 | import java.io.IOException; 22 | import java.nio.charset.Charset; 23 | import java.nio.file.Paths; 24 | import java.util.Iterator; 25 | 26 | import static org.junit.Assert.*; 27 | 28 | public class BasicSSHUserPrivateKeyFIPSTest { 29 | 30 | @Rule public RealJenkinsRule rule = new RealJenkinsRule().omitPlugins("eddsa-api", "trilead-api") 31 | .javaOptions("-Djenkins.security.FIPS140.COMPLIANCE=true"); 32 | 33 | @Test 34 | @Issue("JENKINS-73408") 35 | public void nonCompliantKeysLaunchExceptionTest() throws Throwable { 36 | rule.then(BasicSSHUserPrivateKeyFIPSTest::checkNonCompliantKeysLaunchException); 37 | } 38 | 39 | private static void checkNonCompliantKeysLaunchException(JenkinsRule r) throws IOException{ 40 | new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "no-key", "user", 41 | null, null, "no key provided doesn't throw exceptions"); 42 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "nopass-openssh-ed25519", "user", 43 | getKey("openssh-ed25519-nopass"), null, "openssh ED25519 with no encryption is not compliant")); 44 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", 45 | getKey("rsa1024"), "fipsvalidpassword", "Invalid size key")); 46 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "openssh-rsa1024", "user", 47 | getKey("openssh-rsa1024"), "fipsvalidpassword", "Invalid key format")); 48 | new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "ed25519", "user", 49 | getKey("ed25519"), "fipsvalidpassword", "Elliptic curve accepted key"); 50 | new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa2048", "user", 51 | getKey("rsa2048"), "fipsvalidpassword", "RSA 2048 accepted key"); 52 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024-short-pass", "user", 53 | getKey("unencrypted-rsa1024"), "password", "password shorter than 14 chatacters is invalid")); 54 | new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "unencrypted-rsa2048", "user", 55 | getKey("unencrypted-rsa2048"), null, "RSA 2048 with no encryption is valid"); 56 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa2048-wrong-pass", "user", 57 | getKey("rsa2048"), "NOT-fipsvalidpassword", "Wrong password avoids getting size or algorithm")); 58 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "dsa2048", "user", 59 | getKey("dsa2048"), "fipsvalidpassword", "DSA is not accepted")); 60 | assertThrows(IllegalArgumentException.class, () -> new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "not-a-key", "user", 61 | getKey("not-a-key"), "fipsvalidpassword", "Provided data is not a key")); 62 | } 63 | 64 | @Test 65 | @Issue("JENKINS-73408") 66 | public void invalidKeyIsNotSavedInFIPSModeTest() throws Throwable { 67 | rule.then(BasicSSHUserPrivateKeyFIPSTest::checkInvalidKeyIsNotSavedInFIPSMode); 68 | } 69 | 70 | private static void checkInvalidKeyIsNotSavedInFIPSMode(JenkinsRule r) throws IOException { 71 | BasicSSHUserPrivateKey entry = new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa2048", "user", getKey("rsa2048"), "fipsvalidpassword", "RSA 1024 accepted key"); 72 | Iterator stores = CredentialsProvider.lookupStores(r.jenkins).iterator(); 73 | assertTrue(stores.hasNext()); 74 | CredentialsStore store = stores.next(); 75 | store.addCredentials(Domain.global(), entry); 76 | store.save(); 77 | // Valid key is saved 78 | SSHUserPrivateKey cred = CredentialsMatchers.firstOrNull( 79 | CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), 80 | CredentialsMatchers.withId("rsa2048")); 81 | assertNotNull(cred); 82 | assertThrows(IllegalArgumentException.class, () -> store.addCredentials(Domain.global(), 83 | new BasicSSHUserPrivateKey(CredentialsScope.GLOBAL, "rsa1024", "user", getKey("rsa1024"), "fipsvalidpassword", "Invalid size key"))); 84 | store.save(); 85 | // Invalid key threw an exception, so it wasn't saved 86 | cred = CredentialsMatchers.firstOrNull( 87 | CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), 88 | CredentialsMatchers.withId("rsa1024")); 89 | assertNull(cred); 90 | } 91 | @Test 92 | @LocalData 93 | @Issue("JENKINS-73408") 94 | public void invalidKeysAreRemovedOnStartupTest() throws Throwable { 95 | rule.then(BasicSSHUserPrivateKeyFIPSTest::checkInvalidKeysAreRemovedOnStartup); 96 | } 97 | 98 | private static void checkInvalidKeysAreRemovedOnStartup(JenkinsRule r) { 99 | SSHUserPrivateKey cred = CredentialsMatchers.firstOrNull( 100 | CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), 101 | CredentialsMatchers.withId("valid-rsa-key")); 102 | assertNotNull(cred); 103 | cred = CredentialsMatchers.firstOrNull( 104 | CredentialsProvider.lookupCredentialsInItem(SSHUserPrivateKey.class, null, ACL.SYSTEM2), 105 | CredentialsMatchers.withId("invalid-rsa-key")); 106 | assertNull(cred); 107 | } 108 | 109 | @Test 110 | @Issue("JENKINS-73408") 111 | public void formValidationTest() throws Throwable { 112 | rule.then(BasicSSHUserPrivateKeyFIPSTest::checkFormValidation); 113 | } 114 | 115 | private static void checkFormValidation(JenkinsRule r) throws IOException { 116 | BasicSSHUserPrivateKey.DirectEntryPrivateKeySource.DescriptorImpl descriptor = ExtensionList.lookupSingleton(BasicSSHUserPrivateKey.DirectEntryPrivateKeySource.DescriptorImpl.class); 117 | FormValidation result = descriptor.doCheckPrivateKey(getKey("rsa2048").getPrivateKey().getPlainText(), "fipsvalidpassword"); 118 | assertTrue(StringUtils.isBlank(result.getMessage())); 119 | result = descriptor.doCheckPrivateKey(getKey("rsa1024").getPrivateKey().getPlainText(), "fipsvalidpassword"); 120 | assertTrue(StringUtils.isNotBlank(result.getMessage())); 121 | } 122 | 123 | private static BasicSSHUserPrivateKey.DirectEntryPrivateKeySource getKey(String file) throws IOException { 124 | String keyText = FileUtils.readFileToString(Paths.get("src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest").resolve(file).toFile(), Charset.defaultCharset()); 125 | return new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(keyText); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Jesse Glick. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 26 | 27 | import com.cloudbees.hudson.plugins.folder.Folder; 28 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 29 | import com.cloudbees.plugins.credentials.CredentialsMatchers; 30 | import com.cloudbees.plugins.credentials.CredentialsProvider; 31 | import com.cloudbees.plugins.credentials.domains.DomainRequirement; 32 | import java.util.List; 33 | import hudson.FilePath; 34 | import hudson.cli.CLICommandInvoker; 35 | import hudson.cli.UpdateJobCommand; 36 | import hudson.model.Hudson; 37 | import hudson.model.Job; 38 | import hudson.security.ACL; 39 | import jenkins.model.Jenkins; 40 | 41 | import org.junit.Test; 42 | 43 | import static hudson.cli.CLICommandInvoker.Matcher.failedWith; 44 | import static hudson.cli.CLICommandInvoker.Matcher.succeeded; 45 | import static org.hamcrest.MatcherAssert.assertThat; 46 | import static org.hamcrest.Matchers.containsString; 47 | import static org.hamcrest.Matchers.not; 48 | import static org.junit.Assert.*; 49 | import org.junit.Rule; 50 | import org.jvnet.hudson.test.Issue; 51 | import org.jvnet.hudson.test.JenkinsRule; 52 | import org.jvnet.hudson.test.recipes.LocalData; 53 | 54 | public class BasicSSHUserPrivateKeyTest { 55 | 56 | final static String TESTKEY_ID = "bc07f814-78bd-4b29-93d4-d25b93285f93"; 57 | final static String TESTKEY_BEGIN = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAu1r+HHzmpybc4iwoP5+44FjvcaMkNEWeGQZlmPwLx70XW8+8"; 58 | final static String TESTKEY_END = "sroT/IHW2jKMD0v8kKLUnKCZYzlw0By7+RvJ8lgzHB0D71f6EC1UWg==\n-----END RSA PRIVATE KEY-----\n"; 59 | 60 | @Rule public JenkinsRule r = new JenkinsRule(); 61 | 62 | @LocalData 63 | @Test 64 | public void readOldCredentials() { 65 | SSHUserPrivateKey supk = CredentialsMatchers.firstOrNull( 66 | CredentialsProvider.lookupCredentials(SSHUserPrivateKey.class, Hudson.get(), ACL.SYSTEM, 67 | (List)null), 68 | CredentialsMatchers.withId(TESTKEY_ID)); 69 | assertNotNull(supk); 70 | List keyList = supk.getPrivateKeys(); 71 | assertNotNull(keyList); 72 | assertEquals(keyList.size(), 1); 73 | String privateKey = keyList.get(0); 74 | assertNotNull(privateKey); 75 | assertTrue(privateKey.startsWith(TESTKEY_BEGIN)); 76 | assertTrue(privateKey.endsWith(TESTKEY_END)); 77 | } 78 | 79 | @Test 80 | public void ensureDirectEntryHasTrailingNewline() { 81 | String key = (new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource("test")).getPrivateKey().getPlainText(); 82 | assertEquals("test\n", key); 83 | } 84 | 85 | // TODO demonstrate that all private key sources are round-tripped in XStream 86 | 87 | @Test 88 | @LocalData 89 | @Issue("SECURITY-440") 90 | public void userWithoutRunScripts_cannotMigrateDangerousPrivateKeySource() throws Exception { 91 | Folder folder = r.jenkins.createProject(Folder.class, "folder1"); 92 | 93 | FilePath updateFolder = r.jenkins.getRootPath().child("update_folder.xml"); 94 | 95 | { // as user with just configure, you cannot migrate 96 | CLICommandInvoker.Result result = new CLICommandInvoker(r, new UpdateJobCommand()) 97 | .authorizedTo(Jenkins.READ, Job.READ, Job.CONFIGURE) 98 | .withStdin(updateFolder.read()) 99 | .invokeWithArgs("folder1"); 100 | 101 | assertThat(result.stderr(), containsString("user is missing the Overall/RunScripts permission")); 102 | assertThat(result, failedWith(1)); 103 | 104 | // config file not touched 105 | String configFileContent = folder.getConfigFile().asString(); 106 | assertThat(configFileContent, not(containsString("FileOnMasterPrivateKeySource"))); 107 | assertThat(configFileContent, not(containsString("BasicSSHUserPrivateKey"))); 108 | } 109 | { // but as admin with RUN_SCRIPTS, you can 110 | CLICommandInvoker.Result result = new CLICommandInvoker(r, new UpdateJobCommand()) 111 | .authorizedTo(Jenkins.ADMINISTER) 112 | .withStdin(updateFolder.read()) 113 | .invokeWithArgs("folder1"); 114 | 115 | assertThat(result, succeeded()); 116 | String configFileContent = folder.getConfigFile().asString(); 117 | assertThat(configFileContent, containsString("BasicSSHUserPrivateKey")); 118 | assertThat(configFileContent, not(containsString("FileOnMasterPrivateKeySource"))); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/TrileadSSHPasswordAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 25 | 26 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticator; 27 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticatorFactory; 28 | import com.cloudbees.plugins.credentials.CredentialsScope; 29 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 30 | import com.trilead.ssh2.Connection; 31 | import com.trilead.ssh2.ServerHostKeyVerifier; 32 | import hudson.model.Computer; 33 | import hudson.model.Items; 34 | import hudson.model.TaskListener; 35 | import hudson.remoting.VirtualChannel; 36 | import hudson.slaves.DumbSlave; 37 | import jenkins.security.MasterToSlaveCallable; 38 | 39 | import org.junit.After; 40 | import org.junit.Before; 41 | import org.junit.Rule; 42 | import org.junit.Test; 43 | import org.jvnet.hudson.test.JenkinsRule; 44 | 45 | import java.lang.reflect.InvocationTargetException; 46 | import java.lang.reflect.Proxy; 47 | import java.util.Collections; 48 | import java.util.List; 49 | import java.util.logging.Level; 50 | import java.util.logging.Logger; 51 | 52 | import static org.hamcrest.CoreMatchers.is; 53 | import static org.junit.Assert.assertNotNull; 54 | import static org.hamcrest.MatcherAssert.assertThat; 55 | 56 | public class TrileadSSHPasswordAuthenticatorTest { 57 | 58 | private Connection connection; 59 | private StandardUsernamePasswordCredentials user; 60 | private Object sshd; 61 | 62 | @Rule public JenkinsRule r = new JenkinsRule(); 63 | 64 | @After 65 | public void tearDown() { 66 | if (connection != null) { 67 | connection.close(); 68 | connection = null; 69 | } 70 | if (sshd!=null) { 71 | try { 72 | invoke(sshd, "stop", new Class[] {Boolean.TYPE}, new Object[] {true}); 73 | } catch (Throwable t) { 74 | Logger.getLogger(getClass().getName()).log(Level.WARNING, "Problems shutting down ssh server", t); 75 | } 76 | } 77 | } 78 | 79 | // disabled as Apache MINA sshd does not provide easy mech for giving a Keyboard Interactive authenticator 80 | // so this test relies on having a local sshd which is keyboard interactive only 81 | public void dontTestKeyboardInteractive() throws Exception { 82 | connection = new Connection("localhost"); 83 | connection.connect(new NoVerifier()); 84 | TrileadSSHPasswordAuthenticator instance = 85 | new TrileadSSHPasswordAuthenticator(connection, new BasicSSHUserPassword(CredentialsScope.SYSTEM, 86 | null, "....", // <---- put your username here 87 | "....", // <---- put your password here 88 | null)); 89 | assertThat(instance.canAuthenticate(), is(true)); 90 | assertThat(instance.authenticate(), is(true)); 91 | assertThat(instance.isAuthenticated(), is(true)); 92 | } 93 | 94 | @Before 95 | public void setUp() { 96 | user =(StandardUsernamePasswordCredentials) Items.XSTREAM.fromXML(Items.XSTREAM.toXML(new BasicSSHUserPassword(CredentialsScope.SYSTEM, null, "foobar", "foomanchu", null))); 97 | } 98 | 99 | @Test 100 | public void testPassword() throws Exception { 101 | sshd = createPasswordAuthenticatedSshServer(); 102 | invoke(sshd, "start", null, null); 103 | int port = (Integer)invoke(sshd, "getPort", null, null); 104 | connection = new Connection("localhost", port); 105 | connection.connect(new NoVerifier()); 106 | TrileadSSHPasswordAuthenticator instance = 107 | new TrileadSSHPasswordAuthenticator(connection, user); 108 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 109 | assertThat(instance.canAuthenticate(), is(true)); 110 | assertThat(instance.authenticate(), is(true)); 111 | assertThat(instance.isAuthenticated(), is(true)); 112 | } 113 | 114 | private Object createPasswordAuthenticatedSshServer() throws InvocationTargetException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException { 115 | return createPasswordAuthenticatedSshServer(null); 116 | } 117 | 118 | private Object createPasswordAuthenticatedSshServer(final String username) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, InstantiationException { 119 | Object sshd = newDefaultSshServer(); 120 | Class keyPairProviderClass = newKeyPairProviderClass(); 121 | Object provider = newProvider(); 122 | Class authenticatorClass = newAuthenticatorClass(); 123 | Object authenticator = newAuthenticator(authenticatorClass, username); 124 | Object factory = newFactory(); 125 | 126 | invoke(sshd, "setPort", new Class[] {Integer.TYPE}, new Object[] {0}); 127 | invoke(sshd, "setKeyPairProvider", new Class[] {keyPairProviderClass}, new Object[] {provider}); 128 | invoke(sshd, "setPasswordAuthenticator", new Class[] {authenticatorClass}, new Object[] {authenticator}); 129 | invoke(sshd, "setUserAuthFactories", new Class[] {List.class}, new Object[] {Collections.singletonList(factory)}); 130 | 131 | return sshd; 132 | } 133 | 134 | @Test 135 | public void testFactory() throws Exception { 136 | sshd = createPasswordAuthenticatedSshServer(); 137 | invoke(sshd, "start", null, null); 138 | int port = (Integer)invoke(sshd, "getPort", null, null); 139 | connection = new Connection("localhost", port); 140 | connection.connect(new NoVerifier()); 141 | SSHAuthenticator instance = SSHAuthenticator.newInstance(connection, user); 142 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 143 | assertThat(instance.canAuthenticate(), is(true)); 144 | assertThat(instance.authenticate(), is(true)); 145 | assertThat(instance.isAuthenticated(), is(true)); 146 | } 147 | 148 | @Test 149 | public void testFactoryAltUsername() throws Exception { 150 | sshd = createPasswordAuthenticatedSshServer("bill"); 151 | invoke(sshd, "start", null, null); 152 | int port = (Integer)invoke(sshd, "getPort", null, null); 153 | connection = new Connection("localhost", port); 154 | connection.connect(new NoVerifier()); 155 | SSHAuthenticator instance = SSHAuthenticator.newInstance(connection, user, null); 156 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 157 | assertThat(instance.canAuthenticate(), is(true)); 158 | assertThat(instance.authenticate(), is(false)); 159 | assertThat(instance.isAuthenticated(), is(false)); 160 | connection = new Connection("localhost", port); 161 | connection.connect(new NoVerifier()); 162 | instance = SSHAuthenticator.newInstance(connection, user, "bill"); 163 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 164 | assertThat(instance.canAuthenticate(), is(true)); 165 | assertThat(instance.authenticate(), is(true)); 166 | assertThat(instance.isAuthenticated(), is(true)); 167 | } 168 | 169 | /** 170 | * Brings the {@link SSHAuthenticatorFactory} to a slave. 171 | */ 172 | @Test 173 | public void testSlave() throws Exception { 174 | Object sshd = createPasswordAuthenticatedSshServer(); 175 | invoke(sshd, "start", null, null); 176 | 177 | DumbSlave s = r.createSlave(); 178 | Computer c = s.toComputer(); 179 | assertNotNull(c); 180 | c.connect(false).get(); 181 | 182 | final int port = (Integer)invoke(sshd, "getPort", null, null); 183 | 184 | TaskListener l = r.createTaskListener(); 185 | VirtualChannel channel = c.getChannel(); 186 | assertNotNull(channel); 187 | channel.call(new RemoteConnectionTest(port, user)); 188 | } 189 | 190 | private static class NoVerifier implements ServerHostKeyVerifier { 191 | public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, 192 | byte[] serverHostKey) { 193 | return true; 194 | } 195 | } 196 | 197 | private static final class RemoteConnectionTest extends MasterToSlaveCallable { 198 | private final int port; 199 | private final StandardUsernamePasswordCredentials user; 200 | 201 | public RemoteConnectionTest(int port, StandardUsernamePasswordCredentials user) { 202 | this.port = port; 203 | this.user = user; 204 | } 205 | 206 | public Void call() throws Exception { 207 | Connection connection = new Connection("localhost", port); 208 | connection.connect(new NoVerifier()); 209 | SSHAuthenticator instance = SSHAuthenticator.newInstance(connection,user); 210 | 211 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 212 | assertThat(instance.canAuthenticate(), is(true)); 213 | instance.authenticateOrFail(); 214 | assertThat(instance.isAuthenticated(), is(true)); 215 | connection.close(); 216 | return null; 217 | } 218 | 219 | private static final long serialVersionUID = 1L; 220 | } 221 | 222 | private Object invoke(Object target, String methodName, Class[] parameterTypes, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 223 | return target.getClass().getMethod(methodName, parameterTypes).invoke(target, args); 224 | } 225 | 226 | private Object newDefaultSshServer() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { 227 | Object server = null; 228 | Class serverClass; 229 | try { 230 | serverClass = Class.forName("org.apache.sshd.SshServer"); 231 | } catch (ClassNotFoundException e) { 232 | serverClass = Class.forName("org.apache.sshd.server.SshServer"); 233 | } 234 | 235 | server = serverClass.getDeclaredMethod("setUpDefaultServer").invoke(null); 236 | assertNotNull(server); 237 | 238 | return server; 239 | } 240 | 241 | private Class newKeyPairProviderClass() throws ClassNotFoundException { 242 | Class keyPairProviderClass; 243 | try { 244 | keyPairProviderClass = Class.forName("org.apache.sshd.common.KeyPairProvider"); 245 | } catch (ClassNotFoundException e) { 246 | keyPairProviderClass = Class.forName("org.apache.sshd.common.keyprovider.KeyPairProvider"); 247 | } 248 | 249 | return keyPairProviderClass; 250 | } 251 | 252 | private Object newProvider() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 253 | Class providerClass = Class.forName("org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider"); 254 | Object provider = providerClass.getConstructor().newInstance(); 255 | assertNotNull(provider); 256 | 257 | return provider; 258 | } 259 | 260 | private Class newAuthenticatorClass() throws ClassNotFoundException { 261 | Class authenticatorClass; 262 | try { 263 | authenticatorClass = Class.forName("org.apache.sshd.server.auth.password.PasswordAuthenticator"); 264 | } catch(ClassNotFoundException e) { 265 | authenticatorClass = Class.forName("org.apache.sshd.server.PasswordAuthenticator"); 266 | } 267 | 268 | return authenticatorClass; 269 | } 270 | 271 | private Object newAuthenticator(Class authenticatorClass, final String userName) throws IllegalArgumentException { 272 | Object authenticator = Proxy.newProxyInstance( 273 | authenticatorClass.getClassLoader(), new Class[]{authenticatorClass}, (proxy, method, args) -> 274 | method.getName().equals("authenticate") ? 275 | (userName == null || userName.equals(args[0])) && "foomanchu".equals(args[1]) : 276 | null); 277 | assertNotNull(authenticator); 278 | return authenticator; 279 | } 280 | 281 | private Object newFactory() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 282 | Object factory = null; 283 | Class factoryClass; 284 | try { 285 | factoryClass = Class.forName("org.apache.sshd.server.auth.UserAuthPassword$Factory"); 286 | } catch (ClassNotFoundException e) { 287 | factoryClass = Class.forName("org.apache.sshd.server.auth.password.UserAuthPasswordFactory"); 288 | } 289 | 290 | factory = factoryClass.getConstructor().newInstance(); 291 | 292 | assertNotNull(factory); 293 | return factory; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/impl/TrileadSSHPublicKeyAuthenticatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2011-2012, CloudBees, Inc., Stephen Connolly. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.cloudbees.jenkins.plugins.sshcredentials.impl; 25 | 26 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticator; 27 | import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; 28 | import com.cloudbees.plugins.credentials.CredentialsDescriptor; 29 | import com.cloudbees.plugins.credentials.CredentialsScope; 30 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 31 | import com.trilead.ssh2.Connection; 32 | import edu.umd.cs.findbugs.annotations.CheckForNull; 33 | import edu.umd.cs.findbugs.annotations.NonNull; 34 | import hudson.util.Secret; 35 | 36 | import org.junit.After; 37 | import org.junit.Before; 38 | import org.junit.Rule; 39 | import org.junit.Test; 40 | import org.jvnet.hudson.test.JenkinsRule; 41 | 42 | import java.lang.reflect.InvocationTargetException; 43 | import java.util.Collections; 44 | import java.util.List; 45 | import java.util.logging.Level; 46 | import java.util.logging.Logger; 47 | 48 | import static java.lang.reflect.Proxy.*; 49 | import static org.hamcrest.CoreMatchers.is; 50 | import static org.hamcrest.MatcherAssert.assertThat; 51 | import static org.junit.Assert.assertNotNull; 52 | 53 | public class TrileadSSHPublicKeyAuthenticatorTest { 54 | 55 | private Connection connection; 56 | private SSHUserPrivateKey user; 57 | 58 | @Rule public JenkinsRule r = new JenkinsRule(); 59 | 60 | @After 61 | public void tearDown() { 62 | if (connection != null) { 63 | connection.close(); 64 | connection = null; 65 | } 66 | } 67 | 68 | @Before 69 | public void setUp() { 70 | user = new SSHUserPrivateKey() { 71 | 72 | @NonNull 73 | public String getUsername() { 74 | return "foobar"; 75 | } 76 | 77 | @NonNull 78 | public String getDescription() { 79 | return ""; 80 | } 81 | 82 | @NonNull 83 | public String getId() { 84 | return ""; 85 | } 86 | 87 | public CredentialsScope getScope() { 88 | return CredentialsScope.SYSTEM; 89 | } 90 | 91 | @NonNull 92 | public CredentialsDescriptor getDescriptor() { 93 | return new CredentialsDescriptor() { 94 | @NonNull 95 | @Override 96 | public String getDisplayName() { 97 | return ""; 98 | } 99 | }; 100 | } 101 | 102 | @CheckForNull 103 | public Secret getPassphrase() { 104 | return null; 105 | } 106 | 107 | @NonNull 108 | public List getPrivateKeys() { 109 | // just want a valid key... I generated this and have thrown it away (other than here) 110 | // do not use other than in this test 111 | return List.of("-----BEGIN RSA PRIVATE KEY-----\n" 112 | + "MIICWQIBAAKBgQDADDwooNPJNQB4N4bJPiBgq/rkWKMABApX0w4trSkkX5q+l+CL\n" 113 | + "CuddGGAsAu6XPari8v49ipbBmHqRLP9+X3ARGWKU2gDvGTBr99/ReUl2YgVjCwy+\n" 114 | + "KMrGCN7SNTgRo6StwVaPhh6pUpNTQciDe/kOwUnQFWSM6/lwkOD1Uod45wIBIwKB\n" 115 | + "gHi3O8HELVnmzRhdaqphkLHLL/0/B18Ye4epPBy1/JqFPLJQ1kjFBnUIAe/HVCSN\n" 116 | + "KZX30wIcmUZ9GdeYoJiTwsfTy9t2KwHjqrapTfiekVZAW+3iDBqRZMxQ5MoK7b6g\n" 117 | + "w5HrrtrtPfYuAsBnYjIS6qsKAVT3vdolJ5eai/RlPO4LAkEA76YuUozC/dW7Ox+R\n" 118 | + "1Njd6cWJsRVXGemkSYY/rSh0SbfHAebqL/bDg8xXim9UiuD9Hc6md3glHQj6iKvl\n" 119 | + "BxWq4QJBAM0moKiM16WFSFJP1wVDj0Bnx6DkJYSpf5u+C0ghBVoqIYKq6/P/gRE2\n" 120 | + "+ColsLu6AYftaEJVpAgxeTU/IsGoJMcCQHRmqMkCipiMYkFJ2R49cxnGWNJa0ojt\n" 121 | + "03QrQ3/9tNNZQ2dS5sbW8UAEKoURgNW9vMVVvpHMpE/uaw8u65W6ESsCQDTAyjn4\n" 122 | + "VLWIrDJsTTveLCaBFhNt3cMHA45ysnGiF1GzD+5mdzAdITBP9qvAjIgLQjjlRrH4\n" 123 | + "w8eXsXQXjJgyjR0CQHfvhiMPG5pWwmXpsEOFo6GKSvOC/5sNEcnddenuO/2T7WWi\n" 124 | + "o1LQh9naeuX8gti0vNR8+KtMEaIcJJeWnk56AVY=\n" 125 | + "-----END RSA PRIVATE KEY-----\n"); 126 | } 127 | }; 128 | } 129 | 130 | @Test 131 | public void testAuthenticate() throws Exception { 132 | Object sshd = newDefaultSshServer(); 133 | Class keyPairProviderClass = newKeyPairProviderClass(); 134 | Object provider = newProvider(); 135 | Class authenticatorClass = newAuthenticatorClass(); 136 | Object authenticator = newAuthenticator(authenticatorClass, "foobar"); 137 | Object factory = newFactory(); 138 | assertNotNull(factory); 139 | 140 | invoke(sshd, "setPort", new Class[] {Integer.TYPE}, new Object[] {0}); 141 | invoke(sshd, "setKeyPairProvider", new Class[] {keyPairProviderClass}, new Object[] {provider}); 142 | invoke(sshd, "setPublickeyAuthenticator", new Class[] {authenticatorClass}, new Object[] {authenticator}); 143 | invoke(sshd, "setUserAuthFactories", new Class[] {List.class}, new Object[] {Collections.singletonList(factory)}); 144 | 145 | try { 146 | invoke(sshd, "start", null, null); 147 | int port = (Integer)invoke(sshd, "getPort", null, null); 148 | connection = new Connection("localhost", port); 149 | connection.connect((hostname, port1, serverHostKeyAlgorithm, serverHostKey) -> true); 150 | TrileadSSHPublicKeyAuthenticator instance = 151 | new TrileadSSHPublicKeyAuthenticator(connection, user); 152 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 153 | assertThat(instance.canAuthenticate(), is(true)); 154 | assertThat(instance.authenticate(), is(true)); 155 | assertThat(instance.isAuthenticated(), is(true)); 156 | } finally { 157 | try { 158 | invoke(sshd, "stop", new Class[] {Boolean.TYPE}, new Object[] {true}); 159 | } catch (Throwable t) { 160 | Logger.getLogger(getClass().getName()).log(Level.WARNING, "Problems shutting down ssh server", t); 161 | } 162 | } 163 | } 164 | 165 | @Test 166 | public void testFactory() throws Exception { 167 | Object sshd = newDefaultSshServer(); 168 | Class keyPairProviderClass = newKeyPairProviderClass(); 169 | Object provider = newProvider(); 170 | Class authenticatorClass = newAuthenticatorClass(); 171 | Object authenticator = newAuthenticator(authenticatorClass, "foobar"); 172 | Object factory = newFactory(); 173 | assertNotNull(factory); 174 | 175 | invoke(sshd, "setPort", new Class[] {Integer.TYPE}, new Object[] {0}); 176 | invoke(sshd, "setKeyPairProvider", new Class[] {keyPairProviderClass}, new Object[] {provider}); 177 | invoke(sshd, "setPublickeyAuthenticator", new Class[] {authenticatorClass}, new Object[] {authenticator}); 178 | invoke(sshd, "setUserAuthFactories", new Class[] {List.class}, new Object[] {Collections.singletonList(factory)}); 179 | try { 180 | invoke(sshd, "start", null, null); 181 | int port = (Integer)invoke(sshd, "getPort", null, null); 182 | connection = new Connection("localhost", port); 183 | connection.connect((hostname, port1, serverHostKeyAlgorithm, serverHostKey) -> true); 184 | SSHAuthenticator instance = SSHAuthenticator.newInstance(connection, user); 185 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 186 | assertThat(instance.canAuthenticate(), is(true)); 187 | assertThat(instance.authenticate(), is(true)); 188 | assertThat(instance.isAuthenticated(), is(true)); 189 | } finally { 190 | try { 191 | invoke(sshd, "stop", new Class[] {Boolean.TYPE}, new Object[] {true}); 192 | } catch (Throwable t) { 193 | Logger.getLogger(getClass().getName()).log(Level.WARNING, "Problems shutting down ssh server", t); 194 | } 195 | } 196 | } 197 | 198 | @Test 199 | public void testAltUsername() throws Exception { 200 | Object sshd = newDefaultSshServer(); 201 | Class keyPairProviderClass = newKeyPairProviderClass(); 202 | Object provider = newProvider(); 203 | Class authenticatorClass = newAuthenticatorClass(); 204 | Object authenticator = newAuthenticator(authenticatorClass, "bill"); 205 | Object factory = newFactory(); 206 | 207 | invoke(sshd, "setPort", new Class[] {Integer.TYPE}, new Object[] {0}); 208 | invoke(sshd, "setKeyPairProvider", new Class[] {keyPairProviderClass}, new Object[] {provider}); 209 | invoke(sshd, "setPublickeyAuthenticator", new Class[] {authenticatorClass}, new Object[] {authenticator}); 210 | invoke(sshd, "setUserAuthFactories", new Class[] {List.class}, new Object[] {Collections.singletonList(factory)}); 211 | try { 212 | invoke(sshd, "start", null, null); 213 | int port = (Integer)invoke(sshd, "getPort", null, null); 214 | connection = new Connection("localhost", port); 215 | connection.connect((hostname, port12, serverHostKeyAlgorithm, serverHostKey) -> true); 216 | SSHAuthenticator instance = SSHAuthenticator.newInstance(connection, user, null); 217 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 218 | assertThat(instance.canAuthenticate(), is(true)); 219 | assertThat(instance.authenticate(), is(false)); 220 | assertThat(instance.isAuthenticated(), is(false)); 221 | connection = new Connection("localhost", port); 222 | connection.connect((hostname, port1, serverHostKeyAlgorithm, serverHostKey) -> true); 223 | instance = SSHAuthenticator.newInstance(connection, user, "bill"); 224 | assertThat(instance.getAuthenticationMode(), is(SSHAuthenticator.Mode.AFTER_CONNECT)); 225 | assertThat(instance.canAuthenticate(), is(true)); 226 | assertThat(instance.authenticate(), is(true)); 227 | assertThat(instance.isAuthenticated(), is(true)); 228 | } finally { 229 | try { 230 | invoke(sshd, "stop", new Class[] {Boolean.TYPE}, new Object[] {true}); 231 | } catch (Throwable t) { 232 | Logger.getLogger(getClass().getName()).log(Level.WARNING, "Problems shutting down ssh server", t); 233 | } 234 | } 235 | } 236 | 237 | private Object invoke(Object target, String methodName, Class[] parameterTypes, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 238 | return target.getClass().getMethod(methodName, parameterTypes).invoke(target, args); 239 | } 240 | 241 | private Object newDefaultSshServer() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { 242 | Object sshd = null; 243 | Class sshdClass; 244 | try { 245 | sshdClass = Class.forName("org.apache.sshd.SshServer"); 246 | } catch (ClassNotFoundException e) { 247 | sshdClass = Class.forName("org.apache.sshd.server.SshServer"); 248 | } 249 | 250 | sshd = sshdClass.getDeclaredMethod("setUpDefaultServer").invoke(null); 251 | assertNotNull(sshd); 252 | 253 | return sshd; 254 | } 255 | 256 | private Class newKeyPairProviderClass() throws ClassNotFoundException { 257 | Class keyPairProviderClass; 258 | try { 259 | keyPairProviderClass = Class.forName("org.apache.sshd.common.KeyPairProvider"); 260 | } catch (ClassNotFoundException e) { 261 | keyPairProviderClass = Class.forName("org.apache.sshd.common.keyprovider.KeyPairProvider"); 262 | } 263 | 264 | return keyPairProviderClass; 265 | } 266 | 267 | private Object newProvider() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 268 | Class providerClass = Class.forName("org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider"); 269 | Object provider = providerClass.getConstructor().newInstance(); 270 | assertNotNull(provider); 271 | 272 | return provider; 273 | } 274 | 275 | private Class newAuthenticatorClass() throws ClassNotFoundException { 276 | Class authenticatorClass; 277 | try { 278 | authenticatorClass = Class.forName("org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator"); 279 | } catch(ClassNotFoundException e) { 280 | authenticatorClass = Class.forName("org.apache.sshd.server.PublickeyAuthenticator"); 281 | } 282 | 283 | return authenticatorClass; 284 | } 285 | 286 | private Object newAuthenticator(Class authenticatorClass, final String userName) throws IllegalArgumentException { 287 | Object authenticator = newProxyInstance( 288 | authenticatorClass.getClassLoader(), new Class[]{authenticatorClass}, 289 | (proxy, method, args) -> method.getName().equals("authenticate") ? userName.equals(args[0]) : null); 290 | assertNotNull(authenticator); 291 | return authenticator; 292 | } 293 | 294 | private Object newFactory() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 295 | Object factory = null; 296 | Class factoryClass; 297 | try { 298 | factoryClass = Class.forName("org.apache.sshd.server.auth.UserAuthPublicKey$Factory"); 299 | } catch (ClassNotFoundException e) { 300 | factoryClass = Class.forName("org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory"); 301 | } 302 | 303 | factory = factoryClass.getConstructor().newInstance(); 304 | 305 | assertNotNull(factory); 306 | return factory; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/jenkins/plugins/sshcredentials/jcasc/ExportImportRoundTripCasCSSHCredentialsTest.java: -------------------------------------------------------------------------------- 1 | package com.cloudbees.jenkins.plugins.sshcredentials.jcasc; 2 | 3 | import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; 4 | import com.cloudbees.plugins.credentials.CredentialsProvider; 5 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 6 | import hudson.util.Secret; 7 | import io.jenkins.plugins.casc.misc.RoundTripAbstractTest; 8 | import jenkins.model.Jenkins; 9 | import org.jvnet.hudson.test.RestartableJenkinsRule; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertNotNull; 16 | 17 | public class ExportImportRoundTripCasCSSHCredentialsTest extends RoundTripAbstractTest { 18 | @Override 19 | public void assertConfiguredAsExpected(RestartableJenkinsRule restartableJenkinsRule, String s) { 20 | List creds = CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, Jenkins.getInstanceOrNull(), null, Collections.emptyList()); 21 | assertEquals(1, creds.size()); 22 | StandardUsernamePasswordCredentials cred = creds.get(0); 23 | assertEquals("userid", cred.getId()); 24 | assertEquals("username-of-userid", cred.getUsername()); 25 | assertEquals("password-of-userid", cred.getPassword().getPlainText()); 26 | 27 | List creds2 = CredentialsProvider.lookupCredentials(BasicSSHUserPrivateKey.class,Jenkins.getInstanceOrNull(), null, Collections.emptyList()); 28 | assertEquals(1, creds2.size()); 29 | BasicSSHUserPrivateKey cred2 = creds2.get(0); 30 | assertEquals("userid2", cred2.getId()); 31 | assertEquals("username-of-userid2", cred2.getUsername()); 32 | Secret passphrase = cred2.getPassphrase(); 33 | assertNotNull(passphrase); 34 | assertEquals("passphrase-of-userid2", passphrase.getPlainText()); 35 | assertEquals("the description of userid2", cred2.getDescription()); 36 | assertEquals(1, cred2.getPrivateKeySource().getPrivateKeys().size()); 37 | String directKey = cred2.getPrivateKeySource().getPrivateKeys().get(0); 38 | assertEquals("sp0ds9d+skkfjf", directKey); 39 | } 40 | 41 | @Override 42 | public String stringInLogExpected() { 43 | return "Setting class com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.passphrase = ****"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/credentials.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | GLOBAL 11 | valid-rsa-key 12 | valid RSA 2048 key 13 | 14 | false 15 | {AQAAABAAAAAgQpXlmd7aog5KHqyd4CslpWwvXrItpGOGihMO+b0g0kTXV7McPM5feOqpUnZmsLPv} 16 | 17 | {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==} 18 | 19 | 20 | 21 | GLOBAL 22 | invalid-rsa-key 23 | invalid rsa 1024 key 24 | 25 | false 26 | {AQAAABAAAAAgUqB2y9bDaBL0LNtjF8YMW3oi9Lu3x1hogk1uaAF1u4QKrJ983/4W5hXa6xPkkz+N} 27 | 28 | {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} 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/ssh-credentials-plugin/b0309091cafd2efa85b2dcc29a50666834c3ff09/src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/hudson.util.Secret -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/invalidKeysAreRemovedOnStartupTest/secrets/master.key: -------------------------------------------------------------------------------- 1 | dca8ecbe52238770405093581f4cd2c5cbb711eea9b3a538aeb0feba7a21868e68b0fa3392b5fc68e3819821f2fedf9291c64e3326ab40f7d4988a032d6071423dc307f874bd58cc744c1d7e080773e11c20684988f8d057a0ea3cfac726d9c0d3cd2b47a88da987aa5170e244c836510d2ff729d095add7bd8002a2cdb037f0 -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/dsa2048: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQDEAdc6tEsWhI6pEdDZ2OscOCP0 3 | OAjGLk3OG2aTTjoeO0lYxCTuaUEmtUkr7z2l8qcadBrukCRLs7bl+LQDjpjRR+Ik 4 | 7BOARhfhChM5GSKwRSVp4SM2gSpJiuunKUzeFEEkyWQoTO03ZoCLa1LH/R3qmruE 5 | TLRkAx9QHAA1NQE8NpRjj3bCIxSlRExfnxsxLSp72ZCKqIPUUXrKoz6d9fU3hy6G 6 | nRPhwtoNb7BSG12EVDFmYkUDUN+Z/HlPMNW1rlHpqGusU9sciWu/Cfedf5SrjH9C 7 | fDNzUt+gwBHJLWHhYjw0VK8VuY/KR4QxbrVT6Akl/8ry36C93AT0RH1STQMZAh0A 8 | liBmm55sKk/ArcFVEBiMMLHvfY8XPhjEVsEy+wKCAQB9JqFtjRG+uSTtRjLmofJ/ 9 | cEVK9WLfVtab+k0RrjMUzRmN07HeK+p5hfpDAziAVoj//ohmxLwfJ4PZ4xnlZ8W2 10 | bBmzL+ak6veTr08WukyTYpMyMRElhsHrWUv674zF7nJk7pmF3WGB2dhUWsMKJX6D 11 | HyalQrSP+z2gjFwa1lWj1pcBNXt19LT4Rb0xIOqVVxkcKHkvNXnnbrL9TKyKOA7B 12 | D+RXKi8/eRb+dCIgRtarDrY+fr7YEvh57bNKlTGfCoCSlDxgEdJqDogiOv5SaAOK 13 | JbA0NgUDZBGNNRrlenu57D3GJ8OowdBA0ME277dZpKWkvlDdz4snNvbca5gB/qvp 14 | BB4CHG9sf6t38uKIK/q3zjKbQMrqMdWSZO0o+ARBtjE= 15 | -----END PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/ed25519: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAbW5KJoN3rrAgoZUcE 3 | AO/GAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQoyo3fFspm3I3J2ko 4 | jmeMYARAMFdbiQX5Fd/IqrIHXlr6bMhc2On8aOsCU1pfFVJ9o2VK/PYQQQnEvkCZ 5 | jnOIE4OSBOG2ABNRclnxAL8CHfNQQw== 6 | -----END ENCRYPTED PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/not-a-key: -------------------------------------------------------------------------------- 1 | I'm not a key -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-ed25519-nopass: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACBijRTQ8qO1R5x2WdTkQxbSlUc0EIVEHGWkToICZf6Z1AAAAKA1h7sHNYe7 4 | BwAAAAtzc2gtZWQyNTUxOQAAACBijRTQ8qO1R5x2WdTkQxbSlUc0EIVEHGWkToICZf6Z1A 5 | AAAEAuKgBGoss8OYtdfaAGbIHh6sULQ6VGPJw4vUvP1xUmJ2KNFNDyo7VHnHZZ1ORDFtKV 6 | RzQQhUQcZaROggJl/pnUAAAAG3BlcmVATUFDLXBidWVub3llcmJlcy5sb2NhbAEC 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/openssh-rsa1024: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBFPydw9e 3 | K2cDWnCXMFL9mVAAAAGAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQCo24dDC5o8 4 | IFfdrM6sRRQaKkyqzSWXpsMT5rSZ1lcZLGwYr4Qs5WoX6YYxfoWIvEwvd9LDJ0h2uAyHQk 5 | yxoKrZgSSOYAGjZlBQLvDQbDUNjFdi3XYERBEQDxvhtiQvoo0F1/YgokljkZHGv9xSL7Br 6 | ovNZZ5ITIP40EFYbQ5uF7QAAAiBEXY+V77fzRSmV5SfSikMB1sDBwyLUtD/HVJbiqYl/3E 7 | OsJYvM1BwL0quFt7u5s6wefY7eT5p2N6y2+Z5Vk6ZGxoRJlPRqS0QHXslyz/8YaAynTV2E 8 | QGFAiyWLfWhE2u51QUcWGDXaf10gW7YyOZwHFnA8iZi8+RikxUk2lRjtFwQ4pcZsz+eT6h 9 | 7TcBPmSe3tgNd/6fgajR4/9G4h9ZqNGOiBEtZvONKN8KXr61rebK+wuMrjUfg1u0BjyqTs 10 | W13DnH4uLlC7r+2/zfGyMjwt/pw/dwhQwilZY7G3Ckp3O3jv3eAb1AmcRP5oOU+obWIcb2 11 | dee5jbRWgMrS6QEI5yGCDE8st6ezglmVECdRoXsSz+YYwAKPFg47Abh9mRSZEKuX0MyxI8 12 | n/U194AW2eO6G4u90TCFyiblJSjcoDRavbaunB3oYwhyBT6QKJh1nSd4f/TbyHWnTSFcOJ 13 | ARn6SOZnS2S2ARk4t1pvNro94Ww6qCKloBGjlQevBfoEkftb9exSI6/0qTmsjFZiMjiADH 14 | WEJQw+df6KZ9mIZ8xERYGXgBz43EvSjD3+D5DuZm+pFqstCtGxoznL8pGQaBWbuoVGHD+V 15 | /Ncuu7SpolnLFQGjyIvDgSZUoQQKKj3MwN4BLhEiy/ok3TwSau4QzZ6OAiawhF4qIb4nIJ 16 | OA/6HO+9ViuxIxrhKoUTJi88FIhGThXV9GLK4nQf7w6lyu3JD8qS 17 | -----END OPENSSH PRIVATE KEY----- 18 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa1024: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIC5TBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQmqBfFEhTkNYDOmt9 3 | C4uwjAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEJEGWoJ0cmwkrkxP 4 | fAE3lZcEggKAnjVdB4IjbyIrMyeJEFZ6MYhmHKyKdX5RtbOUqGRoZiF9+ilJStGq 5 | rwDo5ltASjRpVaNgrJZIY3445BXxRWuJXFIeyxhM9ULpuhDItdt+DzpNCZHIBOCR 6 | dPm4m4K/Ot8v7HFBwMLc2+ttA9/Eif1eGAxC7+7qztH9fUo+ZIvlDxApOkz7MZ/u 7 | PxFNun+uVrzzz3tDYepDvdmQLi1TTUxNgcAOb0b+E810VoBwN/83Pltion4jwhkf 8 | eJHfu7B/M/vj7wGz+BFJZoGjHPOk8BF0T8lxm03DLkVgkXh3suDdEerRB11j10CW 9 | 9ETvDGZg19gAZNMFTzIeiIwpGB1ODnlB0iqgD4yy55fCS42vTROwaBhKKE3d8EIp 10 | C6sy3sfxKn0vOe6bkP2ZgHos0XTriEL6l04UtUpJZHUqG8GMa3031EfD1m4NK190 11 | tS8alFHLD7icci2PZPl5A/Sw5+9DtHI9gM9gZxtIm+eJ0lXCoODmcG5Zuqfcjr1o 12 | FT/Imcf9+lzQp0IX6nK4zd3MzAPxpwDyGCu9bIGiIERvWgtWixknfcv88t7F/05O 13 | TU7uPYCqnUScT6VwmBQyDV5cgDboulBZWaFa2XCR8VbD3xuQwVT8R/dtvpcOHQ4j 14 | B5ScU3aRCEn8eunR6ajs2j4avjVJmFTCdFUtme2z8m/imahK0dtcojaqz8G9jq7l 15 | bkgrtinCKcQ8UdRDVwUwfd6ZFWEHjKhmc32PHJP095VtYQSgPpdwARTwgcd4W55w 16 | TRuaqHif6bz/B2qYQuLCnkla9YJxfwZKwDSbCXaFb34j/1/c+Tu8zhrzv44s6c2Y 17 | x3Sq3CRiOdekpCiD7aM0TSnuw+qb2JBYQw== 18 | -----END ENCRYPTED PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/rsa2048: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIFJDBWBgkqhkiG9w0BBQ0wSTAxBgkqhkiG9w0BBQwwJAQQ8hizqQOjbhxZTbGQ 3 | rv/R5wICCAAwDAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIRxwhpaURx7cEggTI 4 | nIiZhjydZ4kCzb7XQilJhfZThSbXO7JyrYsUT++/rsJ+ZBzeC6n1PoHuqxNDjpls 5 | o66iqBVU1B1nm7LfD4VbutozueZ3T68GZp19ZVjDQBU7ZRHCwDtFTTUPXz1u7Sne 6 | 8H87rXKge/XxeatnmfQFEGnCLYP0DBzztNvPCf3szfFsqYoD2sJmrXdhOkiEgrWr 7 | rqnSoZ3fgUIxWx5iLSZ408hrESbt70RuvNWiDFnXaWbXfYNUQ8pNJiePkspZGVnx 8 | uTg9U0yjRxiKZBiXjO92HOGBxeTeOdtO4UtELBn1Vfg4+zX2V1K4oIdwivSW9aa/ 9 | Pgqs0VdlxVUMalUW6Bj38KtYk0SYfgh/OSzSHYv+GyX2gtNumbUFKjSnuATWLs+r 10 | KsVXFSEMXzNyhIP9FwvKVCMcEutJYAbfVPL/YWdPmSgEBKCNQlrG1JNyD3Pj0G/F 11 | 1VFss953WV9rJb6fNjbxhOlCA3b3sBXdZVsvocTSJtCcwGXBug06XwOMiJ/kYpeC 12 | xNXm0NRRd8z17PeFYnc01KCp2W4cptReD6bxfG9sUgRKDOxfC1MOs8Uc9MspmeAZ 13 | QKJ6ZoCNcB232IeuXN5Y2IERb2DDtYeLtOhxIeZ/yPPTO2khfQ/dThUvLxomdgJa 14 | S9p13OApJjrGAGh2+29nRtD58u/M6oMKPQw48aJxGV8dO+fmusK00iC+4N5Gvqc1 15 | +DLonBY1+zQ5x8WyTXGZNzO1MxIdo3DjcEZ1yKYvwlV2Bh+rUY3t8SuyMuh0i3QG 16 | 1RWQXdVfOJ9FY139S/wLF47B5rFsXEkP7drfEPYWWKvnpaibgtI4xxb3JoNWbe5c 17 | 5xnMtrFPB4XP+2g0tIWEEsRyWVfY/Fhowhg0z1iVZ/ne77slEgKBLoZHcmkMNCQc 18 | MturgIErRd+fqOuiKYFGdtrce204XMvqDwNklfgvxA3PVb5PZREgfkZ5t8VmtoPV 19 | XXEqs71O6iKK+v/n9c4TW0m3Osryj028+o5wLWu1obTRnrpLU0pobA47mLeOB4HW 20 | 87nORN41whDJPSEDuMxVm3yvH5Oy9sHLwF5y+AQZYVU6zuxtkB2fK7uIMvith7nH 21 | jTLDfk0pxRp5H1e2K0SVRmDld0u7jGL01bPQz2qzIqrBB2xNN2owqEibOLM9OEZS 22 | xoGI/zejgJa6xp/geuE9D62sGU7jo2T66NETPmJjd6oZYtX5oEfseZ2z1h9Sv7eL 23 | Au3v8OYwGu+dpbCm91XHnJJplN+yiZP64KNarwl8A25AAetKjBOdJSLEnwKz4Ive 24 | KUSJcvjaPCXWa8cjmEh9ntbIUBbjEShjI3DAwLIzpHvLMrCAPnoX4KialbE8xwOJ 25 | cEnb1fsJjf3O6nFsbSa0UzeEznMMSrES3SZhjqlJtdcnEhMhwpJuZUAJlPsmaRDd 26 | DJ37LkTXzjjidK/IcIVd3qoyxs4V2xxrjI1lVAim3i6/opRTm2G+VzE2aEcTidDP 27 | 17bRJyhu+A0Twsxl8M9xDuE11VAIHqnwkQ3d/A2onNdn/EVPtgRjw9YImMtAKqbT 28 | LvXEiyGlyu/q6TLCrNN+X3WtbTwhXatGnmiY3a6W1GJw8TOJq4kxt84PBHMgOcZU 29 | 5dHthPwLgg6akbcrl5zEeUyt0wfavsKE 30 | -----END ENCRYPTED PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/unencrypted-rsa1024: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANU/5VsOXcefJGtd 3 | yd0iAryR1Xjb4S6AXtgSIXlnzRITuKcaHvHaYgCy1+UWwOjwa88ZgBU7/+VKKf8i 4 | oI082I5pSgLwkmAR592r+pb7iUXp683KIPRZt4PkUlzl4Diq72zb+CRLclflW3H1 5 | HFx7WlUHaOK4sENJAhyyltW7b+8/AgMBAAECgYAKbpPDvpIr38ciUXY8kRtdKi0t 6 | OmRrp+/71fc5PzCy/6/6zLZS0oIU0qrpFBHf01MS0IaJg2PjTZt8Va9Q+XcGeY3u 7 | 8YCMVd5gI8bwd8/UN0oYtuClxvFZ3iviz4Fu2A1cI8NSKD6tfAkDP89IkxGp4Oqs 8 | Q8ksxkY+duvl+MHt4QJBAPe1cwQUK9i8vUBzqZb5NaU/GmfmsmL7E2PZS3Kr4wqg 9 | aCyWPh+2K2efoHHh5OdW1tPMis2TDDJMviBSHSdd8PkCQQDcYyzLeb7PY9HVzS0N 10 | qXQmySCKhedJXvMszzphxxJGD8L1X9RRrVffbkLVl2HUrGFovzk4InDI2lxm9erU 11 | 6qf3AkEA1uii+Af0Hp9pZnCy0xw1sb90zm41mHCS2w8cSUndukt+9igHkAXB1K6G 12 | SiedLCSIT8tnJYINk9pHHc2AI7Z6KQJAOtGRPAETJuCaOTiYRKQsJsnZEH9qWg+o 13 | URZBm6T4wJAmVTytOttLr4sK9VyAtLUJDl6y08OFXGXC8YvCj+7MwwJAff9zDf3R 14 | pxUI+kL1rd1TfpygcGLUo4+V3iQ4Hw5KJcWCyz4fwufaJyb8sClgDZPlEKQqtPqX 15 | ZHuLFx/ytd/1tQ== 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyFIPSTest/nonCompliantKeysLaunchExceptionTest/unencrypted-rsa2048: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDsIgLuzl5X4NaT 3 | x1VZSo8wAOd+Bdq5o00D6oxvOQBkQAx6nJWWhbAF9jp/NW72tDaWa3EoaU0xLsNt 4 | ajBZnI4t/3w7HYfGfoLUvumxBFit35gC73yLCpZdpY7PomTLe3XzV76Chhcl7D1o 5 | otJ/p02KR24SMxpcloBC67a1lzqFBGvu4SvWNSKavzToucdHBOPSkbxzQNJdppJR 6 | XJRernIU417DPEUY683n/XbUaejD/JZS33ZAvUCcE+dq0qVVY8QvgjsfYep6HrBD 7 | Qrt+COQHDjvt+wQ6x+ALsM8anveB+BpxK4bvPtiwDvR46N2pgV6cdlX1MRuHD33p 8 | 5C+K21pFAgMBAAECggEACILYhjdsBFDdvk/cZ+G3GlM+BNEJmp1XN+fq9FG8QODK 9 | Hyh1TKQ4ijTfOn/FJlR49aA2+ogcUl4aiMWseBGbhYiEhGdcSHxKebtnFiYcKCnV 10 | NqESy4djKhbyK2u+cR/1fWlRO/uNtz4C0wdJydrqGyzXiJ8zUqXRRuUGHeXiI+WW 11 | LbX9sWQJnQ/prQU5hH1n4PD/zSHrUVLWf2SCZcCrltI2uR/fNBE3MLClMMqclcnY 12 | +FzBILtiMPjPBZWIHYbDuKk3lqsqdo8uzUO5kp0CXLetXs78tDcL314syDg/3H0F 13 | 6P60e8IXTfmBQhukD+BkrPyNu+zSi13ZHORIyUkaIQKBgQD3JYd9y8FOGJ11Dxsx 14 | KRXpHaTc6vYnL5PgTPGTucCCGHjuiMGgVtYSIocBW5X4mBR0/qw0SnmjopZkzoV8 15 | kqLOWlKB46vZcT3TAGjs9xeNZhxsxXpM/q7/qiGPFzUHlsXre+hGVIXk/obHZ3Hy 16 | x9i4ZlemfZi8JpXY3b1k/7HW3QKBgQD0l3roIYsm0W0IS1BplaxEmf8xy5mq5MCW 17 | 2wS+B/m8G8ddKVAwzhczfbGkzjna7ibnlh8Gf7aKcpCxaDNP67BJiagfB7JjpSXn 18 | CAMMt/OCLl4mzSuQt/OsVqx5FVtoBmkK0wlk4YlY81tkfTLiQ461tgTmr5BjhJYH 19 | rIXkMCP2iQKBgQCpH15jf/4M4h1F0IuMDZB30JhTsNm3IQCxehXKQE9y9yoyGRVf 20 | Em01Rbla/YBX+EHveaL/uYMZrhX6b7S69WFBkl9pkRG1H5/t9xbWKZRNZ3XGHTC+ 21 | 5X19aL/EOl5Iji1sIoNlNUvW0zIJ3EkGmSk9rpMGVGYjQshB+iMzrSHWZQKBgBdm 22 | fq3Ct8to8eN/QRw445hUm2OqNPNymzJTleqQXMYwaixxjWh97x5QAjTgPgzCCBrT 23 | 8/ftNAue1lUCwRX+WIlQkDMXy2tZG300+QW4e6WSxhM0QdzAnKF6UVnsPyh+pIIS 24 | mq16HmfSMIY2rC2VhQvBdEqVtVywDKKDUPP53xbxAoGAQuDSWkPoiPIKQEiKBSkN 25 | gIE5/38ODnVPr7/vXX+CI/vfvlfPRTweJ35W9zv7xJAIBfRn5/rc2fmBPsGJUvdV 26 | 6OJzpMzMZboHly5q9J5Uhbhz40YQZVGJT9cfkuP+syAGpl5lo7TN1cHRuncdSMVJ 27 | X4+nMzQE3yif9ZKhSn540rY= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyTest/readOldCredentials/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.554.1 5 | 2 6 | NORMAL 7 | true 8 | 9 | 10 | false 11 | 12 | ${JENKINS_HOME}/workspace/${ITEM_FULLNAME} 13 | ${ITEM_ROOTDIR}/builds 14 | 15 | 16 | 17 | 18 | 19 | 5 20 | 0 21 | 22 | 23 | 24 | All 25 | false 26 | false 27 | 28 | 29 | 30 | All 31 | 0 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyTest/readOldCredentials/credentials.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | GLOBAL 11 | bc07f814-78bd-4b29-93d4-d25b93285f93 12 | Just testing 13 | testing 14 | 0wJzosMUuj6wb67Cyc8aYw== 15 | 16 | -----BEGIN RSA PRIVATE KEY----- 17 | MIIEpAIBAAKCAQEAu1r+HHzmpybc4iwoP5+44FjvcaMkNEWeGQZlmPwLx70XW8+8 18 | 3d2BMLjhcw1zLsYG3FWpCyn8cB1OmjKiGjvnP5EBoAolvh3qOcWKyWlVGWGgs10B 19 | 0Cgnd3OBXRPQd1cBdiQZmmeCrrH0OjQefYIF2WYN+F8iuNGraAaRsXLgITanjTb1 20 | 6w1dnk+KLpU2J5G6kG1f/Qxl4pgny80S/3TktqoknbOrYDMOSA1Zdww39cpXJHp6 21 | feEs8tavC93rOsR2O4ZfVUCjTFAF5FtIdRv3LXY3Q5W/AyY1h45Wk6mMVnEluFjB 22 | aA+gzVAVaHFQfuhwoj4B7jWCmfHsPG1WmyK0YQIDAQABAoIBADt1qlXiMdV0mP9S 23 | okdm6maQ8xTugKvyODWa+R1vSFHQqhwiNr927+xFkI9SAm8iu8SrjuWTIqF2O57m 24 | WNnYjxB2dbyT29yVY+OH1P8M5cwTVsv1xYCJbdUUHEcs5akqPLWAyXteRHQq1+as 25 | 6cxNOov/PonHr55WNH7kLtLRMV54jZ68nrEh5pWdabFa0f7d/ByIvYRJm7lpjtSp 26 | EBp5AseXzSg2EZP3HDPYYPDHK0tMPginz9+YuQCQFoMYAkVZoKFJGsWICktd5Uk7 27 | wveOJLOfMng1Iww6CEc871GcLn5LbafLWRxjZssK2Z1fC+pZYLLZPeKDMSxoRXm6 28 | PShUC1ECgYEA623dmwJNYfVRgAgOYhhcxzH4TAXUmUIpadEUjOkAhe3w/abDawFT 29 | 9ianhqfhTjSZGBtUttcN40Vy+P4bsqfQKZ7p6KzrdR2zWjlYcICWhZDZPGmMxpMZ 30 | mUFhZXsLRVhn8qed8w1t6eju7S+t9satKIMC/KrhNsFzjrgbU9eC+m0CgYEAy7nN 31 | gMwQeGxAQSJr9H7eKkthnjMe77rLIAZEbDJhcwYVz+Iie/E4hjESQ+IuvXa1VawD 32 | O6cD0wWdOhH2McNdMNIbM4IOaO/TOaR5jfQwBWmb4iG2BZQWQQE/HUBnoJQWUhqm 33 | b+D+s4bHh4J+bs+ptgg9Sd9V+VxJBcu2QDmI6UUCgYBTb1pMJyK5hrFdiH1gcnXe 34 | +myetKpFrlby83AvCBxxWoQ/wKwc7hmNcOGKLVEB4E4pZvY83jZDx0cZyySRyjtR 35 | pMoM9ct0dBQt84jORiQSLeVvLZEAhv1ZfPxBdLvn1Y7xRkoJ60Z60Vxrnqwueva/ 36 | Fr8mQIEUYLbNa53ztrrqeQKBgQCqOY4k2F3KwWjPA9wAZyFrZaEjdsOavBGNqK7z 37 | WQVj/umq0eDOfzgjqE0Cu7MiTFYoR5pL9bmUUVSWePuliQANEwH3f+xackmkGHIY 38 | 0rhtTVkbEd/tuVb+6fO6lV4BJrufzvTS9sTbbPq7l6XdIVdE6o2LdDl6Kko5tYWL 39 | FIf5oQKBgQDLHK/9NTb3VHp+Qriu2Vp8Pnaw6YF6pETfgjyrH2ULSW/R07v+AECC 40 | sPr+d/hx2MQWp54HglY8lv98rOrRjMiRw1GVoXs+Ut9vkupmrpvzNE7ITl0tzBqD 41 | sroT/IHW2jKMD0v8kKLUnKCZYzlw0By7+RvJ8lgzHB0D71f6EC1UWg== 42 | -----END RSA PRIVATE KEY----- 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKeyTest/userWithoutRunScripts_cannotMigrateDangerousPrivateKeySource/update_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | From SSH 15 | from_ssh 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | All 28 | false 29 | false 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | false 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/test/resources/com/cloudbees/jenkins/plugins/sshcredentials/jcasc/configuration-as-code.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | systemMessage: Jenkins with SSH Credentials for JCasC test 3 | 4 | credentials: 5 | system: 6 | domainCredentials: 7 | - credentials: 8 | - usernamePassword: 9 | scope: SYSTEM 10 | id: "userid" 11 | username: "username-of-userid" 12 | password: "password-of-userid" 13 | - basicSSHUserPrivateKey: 14 | scope: SYSTEM 15 | id: "userid2" 16 | username: "username-of-userid2" 17 | passphrase: "passphrase-of-userid2" 18 | description: "the description of userid2" 19 | privateKeySource: 20 | directEntry: 21 | privateKey: "sp0ds9d+skkfjf" 22 | --------------------------------------------------------------------------------