com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl.java

Source

/*
 * The MIT License
 *
 * Copyright (c) 2011-2016, CloudBees, Inc., Stephen Connolly.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.cloudbees.plugins.credentials.impl;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsSnapshotTaker;
import com.cloudbees.plugins.credentials.SecretBytes;
import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.remoting.Channel;
import hudson.util.FormValidation;
import hudson.util.HttpResponses;
import hudson.util.IOUtils;
import hudson.util.Secret;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import net.jcip.annotations.GuardedBy;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

public class CertificateCredentialsImpl extends BaseStandardCredentials implements StandardCertificateCredentials {

    /**
     * Ensure consistent serialization.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Our logger.
     */
    private static final Logger LOGGER = Logger.getLogger(CertificateCredentialsImpl.class.getName());

    /**
     * The source of the keystore.
     */
    private final KeyStoreSource keyStoreSource;

    /**
     * The password.
     */
    private final Secret password;

    /**
     * The keystore.
     */
    @GuardedBy("this")
    @CheckForNull
    private transient KeyStore keyStore;

    /**
     * Timestamp of the last time the keystore was modified so that we can track if need to refresh {@link #keyStore}.
     */
    @GuardedBy("this")
    private transient long keyStoreLastModified;

    /**
     * Our constructor.
     *
     * @param scope          the scope.
     * @param id             the id.
     * @param description    the description.
     * @param password       the password.
     * @param keyStoreSource the source of the keystore that holds the certificate.
     */
    @DataBoundConstructor
    public CertificateCredentialsImpl(@CheckForNull CredentialsScope scope, @CheckForNull String id,
            @CheckForNull String description, @CheckForNull String password,
            @NonNull KeyStoreSource keyStoreSource) {
        super(scope, id, description);
        keyStoreSource.getClass();
        this.password = Secret.fromString(password);
        this.keyStoreSource = keyStoreSource;
    }

    /**
     * Helper to convert a {@link Secret} password into a {@code char[]}
     *
     * @param password the password.
     * @return a {@code char[]} containing the password or {@code null}
     */
    @CheckForNull
    private static char[] toCharArray(@NonNull Secret password) {
        String plainText = Util.fixEmpty(password.getPlainText());
        return plainText == null ? null : plainText.toCharArray();
    }

    /**
     * When serializing over a {@link Channel} ensure that we send a self-contained version.
     *
     * @return the object instance to write to the stream.
     */
    private Object writeReplace() {
        if (/* XStream */Channel.current() == null
                || /* already safe to serialize */ keyStoreSource.isSnapshotSource()) {
            return this;
        }
        return CredentialsProvider.snapshot(this);
    }

    /**
     * Returns the {@link KeyStore} containing the certificate.
     *
     * @return the {@link KeyStore} containing the certificate.
     */
    @NonNull
    public synchronized KeyStore getKeyStore() {
        long lastModified = keyStoreSource.getKeyStoreLastModified();
        if (keyStore == null || keyStoreLastModified < lastModified) {
            KeyStore keyStore;
            try {
                keyStore = KeyStore.getInstance("PKCS12");
            } catch (KeyStoreException e) {
                throw new IllegalStateException("PKCS12 is a keystore type per the JLS spec", e);
            }
            try {
                keyStore.load(new ByteArrayInputStream(keyStoreSource.getKeyStoreBytes()), toCharArray(password));
            } catch (CertificateException e) {
                LogRecord lr = new LogRecord(Level.WARNING, "Credentials ID {0}: Could not load keystore from {1}");
                lr.setParameters(new Object[] { getId(), keyStoreSource });
                lr.setThrown(e);
                LOGGER.log(lr);
            } catch (NoSuchAlgorithmException e) {
                LogRecord lr = new LogRecord(Level.WARNING, "Credentials ID {0}: Could not load keystore from {1}");
                lr.setParameters(new Object[] { getId(), keyStoreSource });
                lr.setThrown(e);
                LOGGER.log(lr);
            } catch (IOException e) {
                LogRecord lr = new LogRecord(Level.WARNING, "Credentials ID {0}: Could not load keystore from {1}");
                lr.setParameters(new Object[] { getId(), keyStoreSource });
                lr.setThrown(e);
                LOGGER.log(lr);
            }
            this.keyStore = keyStore;
            this.keyStoreLastModified = lastModified;
        }
        return keyStore;
    }

    /**
     * Returns the password used to protect the certificate's private key in {@link #getKeyStore()}.
     *
     * @return the password used to protect the certificate's private key in {@link #getKeyStore()}.
     */
    @NonNull
    public Secret getPassword() {
        return password;
    }

    /**
     * Whether there is actually a password protecting the certificate's private key in {@link #getKeyStore()}.
     *
     * @return {@code true} if there is a password protecting the certificate's private key in {@link #getKeyStore()}.
     */
    public boolean isPasswordEmpty() {
        return StringUtils.isEmpty(password.getPlainText());
    }

    /**
     * Returns the source of the {@link #getKeyStore()}.
     *
     * @return the source of the {@link #getKeyStore()}.
     */
    public KeyStoreSource getKeyStoreSource() {
        return keyStoreSource;
    }

    /**
     * Our descriptor.
     */
    @Extension(ordinal = -1)
    public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {

        /**
         * {@inheritDoc}
         */
        @Override
        public String getDisplayName() {
            return Messages.CertificateCredentialsImpl_DisplayName();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getIconClassName() {
            return "icon-credentials-certificate";
        }
    }

    /**
     * Represents a source of a {@link KeyStore}.
     */
    public static abstract class KeyStoreSource extends AbstractDescribableImpl<KeyStoreSource> {

        /**
         * Returns the {@link byte[]} content of the {@link KeyStore}.
         *
         * @return the {@link byte[]} content of the {@link KeyStore}.
         */
        @NonNull
        public abstract byte[] getKeyStoreBytes();

        /**
         * Returns a {@link System#currentTimeMillis()} comparable timestamp of when the content was last modified.
         * Used to track refreshing the {@link CertificateCredentialsImpl#keyStore} cache for sources that pull
         * from an external source.
         *
         * @return a {@link System#currentTimeMillis()} comparable timestamp of when the content was last modified.
         */
        public abstract long getKeyStoreLastModified();

        /**
         * Returns {@code true} if and only if the source is self contained.
         *
         * @return {@code true} if and only if the source is self contained.
         * @since 1.14
         */
        public boolean isSnapshotSource() {
            return false;
        }

    }

    /**
     * The base class for all {@link KeyStoreSource} {@link Descriptor} instances.
     */
    public static abstract class KeyStoreSourceDescriptor extends Descriptor<KeyStoreSource> {
        /**
         * {@inheritDoc}
         */
        protected KeyStoreSourceDescriptor() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        protected KeyStoreSourceDescriptor(Class<? extends KeyStoreSource> clazz) {
            super(clazz);
        }

        /**
         * Helper method that performs form validation on a {@link KeyStore}.
         *
         * @param type          the type of keystore to instantiate, see {@link KeyStore#getInstance(String)}.
         * @param keystoreBytes the {@link byte[]} content of the {@link KeyStore}.
         * @param password      the password to use when loading the {@link KeyStore} and recovering the key from the
         *                      {@link KeyStore}.
         * @return the validation results.
         */
        @NonNull
        protected static FormValidation validateCertificateKeystore(String type, byte[] keystoreBytes,
                String password) {

            if (keystoreBytes == null || keystoreBytes.length == 0) {
                return FormValidation.warning(Messages.CertificateCredentialsImpl_LoadKeystoreFailed());
            }

            char[] passwordChars = toCharArray(Secret.fromString(password));
            try {
                KeyStore keyStore = KeyStore.getInstance(type);
                keyStore.load(new ByteArrayInputStream(keystoreBytes), passwordChars);
                int size = keyStore.size();
                if (size == 0) {
                    return FormValidation.warning(Messages.CertificateCredentialsImpl_EmptyKeystore());
                }
                StringBuilder buf = new StringBuilder();
                boolean first = true;
                for (Enumeration<String> enumeration = keyStore.aliases(); enumeration.hasMoreElements();) {
                    String alias = enumeration.nextElement();
                    if (first) {
                        first = false;
                    } else {
                        buf.append(", ");
                    }
                    buf.append(alias);
                    if (keyStore.isCertificateEntry(alias)) {
                        keyStore.getCertificate(alias);
                    } else if (keyStore.isKeyEntry(alias)) {
                        if (passwordChars == null) {
                            return FormValidation.warning(
                                    Messages.CertificateCredentialsImpl_LoadKeyFailedQueryEmptyPassword(alias));
                        }
                        try {
                            keyStore.getKey(alias, passwordChars);
                        } catch (UnrecoverableEntryException e) {
                            return FormValidation.warning(e,
                                    Messages.CertificateCredentialsImpl_LoadKeyFailed(alias));
                        }
                    }
                }
                return FormValidation.ok(StringUtils.defaultIfEmpty(
                        StandardCertificateCredentials.NameProvider.getSubjectDN(keyStore), buf.toString()));
            } catch (KeyStoreException e) {
                return FormValidation.warning(e, Messages.CertificateCredentialsImpl_LoadKeystoreFailed());
            } catch (CertificateException e) {
                return FormValidation.warning(e, Messages.CertificateCredentialsImpl_LoadKeystoreFailed());
            } catch (NoSuchAlgorithmException e) {
                return FormValidation.warning(e, Messages.CertificateCredentialsImpl_LoadKeystoreFailed());
            } catch (IOException e) {
                return FormValidation.warning(e, Messages.CertificateCredentialsImpl_LoadKeystoreFailed());
            } finally {
                if (passwordChars != null) {
                    Arrays.fill(passwordChars, ' ');
                }
            }
        }
    }

    /**
     * Let the user reference a file on the disk.
     */
    public static class FileOnMasterKeyStoreSource extends KeyStoreSource {

        /**
         * Our logger.
         */
        private static final Logger LOGGER = Logger.getLogger(FileOnMasterKeyStoreSource.class.getName());

        /**
         * The path of the file on the master.
         */
        private final String keyStoreFile;

        /**
         * Our constructor.
         *
         * @param keyStoreFile the path of the file on the master.
         */
        @SuppressWarnings("unused") // by stapler
        @DataBoundConstructor
        public FileOnMasterKeyStoreSource(String keyStoreFile) {
            this.keyStoreFile = keyStoreFile;
        }

        /**
         * {@inheritDoc}
         */
        @NonNull
        @Override
        public byte[] getKeyStoreBytes() {
            try {
                InputStream inputStream = new FileInputStream(new File(keyStoreFile));
                try {
                    return IOUtils.toByteArray(inputStream);
                } finally {
                    IOUtils.closeQuietly(inputStream);
                }
            } catch (IOException e) {
                LOGGER.log(Level.WARNING, "Could not read private key file " + keyStoreFile, e);
                return new byte[0];
            }
        }

        /**
         * Returns the private key file name.
         *
         * @return the private key file name.
         */
        public String getKeyStoreFile() {
            return keyStoreFile;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public long getKeyStoreLastModified() {
            return new File(keyStoreFile).lastModified();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return "FileOnMasterKeyStoreSource{" + "keyStoreFile='" + keyStoreFile + '\'' + "}";
        }

        /**
         * {@inheritDoc}
         */
        @Extension
        public static class DescriptorImpl extends KeyStoreSourceDescriptor {

            /**
             * {@inheritDoc}
             */
            @Override
            public String getDisplayName() {
                return Messages.CertificateCredentialsImpl_FileOnMasterKeyStoreSourceDisplayName();
            }

            /**
             * Checks the keystore file path.
             *
             * @param value    the file path.
             * @param password the password.
             * @return the {@link FormValidation} results.
             */
            @SuppressWarnings("unused") // stapler form validation
            @Restricted(NoExternalUse.class)
            public FormValidation doCheckKeyStoreFile(@QueryParameter String value,
                    @QueryParameter String password) {
                if (StringUtils.isBlank(value)) {
                    return FormValidation.error(Messages.CertificateCredentialsImpl_KeyStoreFileUnspecified());
                }
                File file = new File(value);
                if (file.isFile()) {
                    try {
                        return validateCertificateKeystore("PKCS12", FileUtils.readFileToByteArray(file), password);
                    } catch (IOException e) {
                        return FormValidation
                                .error(Messages.CertificateCredentialsImpl_KeyStoreFileUnreadable(value), e);
                    }
                } else {
                    return FormValidation
                            .error(Messages.CertificateCredentialsImpl_KeyStoreFileDoesNotExist(value));
                }
            }
        }
    }

    /**
     * Let the user reference a file on the disk.
     */
    public static class UploadedKeyStoreSource extends KeyStoreSource implements Serializable {
        /**
         * Ensure consistent serialization.
         */
        private static final long serialVersionUID = 1L;

        /**
         * Our logger.
         */
        private static final Logger LOGGER = Logger.getLogger(FileOnMasterKeyStoreSource.class.getName());

        /**
         * The old uploaded keystore.
         */
        @CheckForNull
        @Deprecated
        private transient Secret uploadedKeystore;
        /**
         * The uploaded keystore.
         *
         * @since 2.1.5
         */
        @CheckForNull
        private final SecretBytes uploadedKeystoreBytes;

        /**
         * Our constructor.
         *
         * @param uploadedKeystore the keystore content.
         * @deprecated
         */
        @SuppressWarnings("unused") // by stapler
        @Deprecated
        public UploadedKeyStoreSource(String uploadedKeystore) {
            this.uploadedKeystoreBytes = StringUtils.isBlank(uploadedKeystore) ? null
                    : SecretBytes.fromBytes(DescriptorImpl.toByteArray(Secret.fromString(uploadedKeystore)));
        }

        /**
         * Our constructor.
         *
         * @param uploadedKeystore the keystore content.
         */
        @SuppressWarnings("unused") // by stapler
        @DataBoundConstructor
        public UploadedKeyStoreSource(SecretBytes uploadedKeystore) {
            this.uploadedKeystoreBytes = uploadedKeystore;
        }

        /**
         * Migrate to the new field.
         *
         * @return the deserialized object.
         * @throws ObjectStreamException if something didn't work.
         * @since 2.1.5
         */
        private Object readResolve() throws ObjectStreamException {
            if (uploadedKeystore != null && uploadedKeystoreBytes == null) {
                return new UploadedKeyStoreSource(
                        SecretBytes.fromBytes(DescriptorImpl.toByteArray(uploadedKeystore)));
            }
            return this;
        }

        /**
         * Returns the private key file name.
         *
         * @return the private key file name.
         */
        public SecretBytes getUploadedKeystore() {
            return uploadedKeystoreBytes;
        }

        /**
         * {@inheritDoc}
         */
        @NonNull
        @Override
        public byte[] getKeyStoreBytes() {
            return SecretBytes.getPlainData(uploadedKeystoreBytes);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public long getKeyStoreLastModified() {
            return 0L; // our content is final so it will never change
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isSnapshotSource() {
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return "UploadedKeyStoreSource{uploadedKeystoreBytes=******}";
        }

        /**
         * {@inheritDoc}
         */
        @Extension
        public static class DescriptorImpl extends KeyStoreSourceDescriptor {

            /**
             * Decode the {@link Base64} keystore wrapped in a {@link Secret}.
             *
             * @param secret the keystore as a secret.
             * @return the keystore bytes.
             * @see #toSecret(byte[])
             */
            @NonNull
            public static byte[] toByteArray(@Nullable Secret secret) {
                if (secret != null) {
                    byte[] decoded = Base64.decodeBase64(secret.getPlainText());
                    if (null != decoded) {
                        return decoded;
                    }
                }
                return new byte[0];
            }

            /**
             * Encodes the keystore bytes into {@link Base64} and wraps in a {@link Secret}
             *
             * @param contents the keystore bytes.
             * @return the keystore as a secret.
             * @see #toByteArray(Secret)
             * @deprecated use {@link SecretBytes#fromBytes(byte[])}
             */
            @Deprecated
            @CheckForNull
            public static Secret toSecret(@Nullable byte[] contents) {
                return contents == null || contents.length == 0 ? null
                        : Secret.fromString(Base64.encodeBase64String(contents));
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public String getDisplayName() {
                return Messages.CertificateCredentialsImpl_UploadedKeyStoreSourceDisplayName();
            }

            /**
             * Checks the keystore content.
             *
             * @param value    the keystore content.
             * @param password the password.
             * @return the {@link FormValidation} results.
             */
            @SuppressWarnings("unused") // stapler form validation
            @Restricted(NoExternalUse.class)
            public FormValidation doCheckUploadedKeystore(@QueryParameter String value,
                    @QueryParameter String password) {
                if (StringUtils.isBlank(value)) {
                    return FormValidation.error(Messages.CertificateCredentialsImpl_NoCertificateUploaded());
                }

                SecretBytes secretBytes = SecretBytes.fromString(value);
                byte[] keystoreBytes = secretBytes.getPlainData();
                if (keystoreBytes == null || keystoreBytes.length == 0) {
                    return FormValidation.error(Messages.CertificateCredentialsImpl_LoadKeystoreFailed());
                }

                return validateCertificateKeystore("PKCS12", keystoreBytes, password);
            }

            /**
             * Creates a new {@link Upload} for the specified {@literal <input id="..."/>}
             *
             * @param divId the id if the form input element to inject the uploaded content into.
             * @return the {@link Upload}
             */
            @SuppressWarnings("unused") // invoked by stapler binding
            @Restricted(NoExternalUse.class)
            public Upload getUpload(String divId) {
                return new Upload(divId, null);
            }

        }

        /**
         * Stapler binding object to handle a pop-up window for file upload.
         */
        public static class Upload {

            /**
             * The id of the {@literal <input>} element on the {@code window.opener} of the pop-up to inject the
             * uploaded content into.
             */
            @NonNull
            private final String divId;

            /**
             * The uploaded content.
             */
            @CheckForNull
            private final SecretBytes uploadedKeystore;

            /**
             * Our constructor.
             *
             * @param divId            id of the {@literal <input>} element on the {@code window.opener} of the
             *                         pop-up to inject the uploaded content into.
             * @param uploadedKeystore the content.
             */
            public Upload(@NonNull String divId, @CheckForNull SecretBytes uploadedKeystore) {
                this.divId = divId;
                this.uploadedKeystore = uploadedKeystore;
            }

            /**
             * Gets the id of the {@literal <input>} element on the {@code window.opener} of the pop-up to inject the
             * uploaded content into.
             *
             * @return the id of the {@literal <input>} element on the {@code window.opener} of the pop-up to inject the
             * uploaded content into.
             */
            @NonNull
            public String getDivId() {
                return divId;
            }

            /**
             * Returns the content.
             *
             * @return the content.
             */
            @SuppressWarnings("unused") // used by Jelly EL
            public SecretBytes getUploadedKeystore() {
                return uploadedKeystore;
            }

            /**
             * Performs the actual upload.
             *
             * @param req the request.
             * @return the response.
             * @throws ServletException if something goes wrong.
             * @throws IOException      if something goes wrong.
             */
            @NonNull
            public HttpResponse doUpload(@NonNull StaplerRequest req) throws ServletException, IOException {
                FileItem file = req.getFileItem("certificate.file");
                if (file == null) {
                    throw new ServletException("no file upload");
                }
                // Here is the trick, if we have a successful upload we replace ourselves in the stapler view
                // with an instance that has the uploaded content and request stapler to render the "complete"
                // view for that instance. The "complete" view can then do the injection and close itself so that
                // the user experience is the pop-up then click upload and finally we inject back in the content to
                // the form.
                SecretBytes uploadedKeystore = SecretBytes.fromBytes(file.get());
                return HttpResponses.forwardToView(new Upload(getDivId(), uploadedKeystore), "complete");
            }
        }
    }

    /**
     * The {@link CredentialsSnapshotTaker} for {@link StandardCertificateCredentials}.
     *
     * @since 1.14
     */
    @Extension
    public static class CredentialsSnapshotTakerImpl
            extends CredentialsSnapshotTaker<StandardCertificateCredentials> {

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<StandardCertificateCredentials> type() {
            return StandardCertificateCredentials.class;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public StandardCertificateCredentials snapshot(StandardCertificateCredentials credentials) {
            if (credentials instanceof CertificateCredentialsImpl) {
                final KeyStoreSource keyStoreSource = ((CertificateCredentialsImpl) credentials)
                        .getKeyStoreSource();
                if (keyStoreSource.isSnapshotSource()) {
                    return credentials;
                }
                return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(),
                        credentials.getDescription(), credentials.getPassword().getEncryptedValue(),
                        new UploadedKeyStoreSource(SecretBytes.fromBytes(keyStoreSource.getKeyStoreBytes())));
            }
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            final char[] password = credentials.getPassword().getPlainText().toCharArray();
            try {
                credentials.getKeyStore().store(bos, password);
                bos.close();
            } catch (KeyStoreException e) {
                return credentials;
            } catch (IOException e) {
                return credentials;
            } catch (NoSuchAlgorithmException e) {
                return credentials;
            } catch (CertificateException e) {
                return credentials;
            } finally {
                Arrays.fill(password, (char) 0);
            }
            return new CertificateCredentialsImpl(credentials.getScope(), credentials.getId(),
                    credentials.getDescription(), credentials.getPassword().getEncryptedValue(),
                    new UploadedKeyStoreSource(SecretBytes.fromBytes(bos.toByteArray())));
        }
    }
}