/* * The MIT License * * Copyright (c) 2016, CloudBees, Inc.. * * 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. */ /* * createCipher routine was adopted from http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3 * .10/src/java/org/apache/commons/ssl/OpenSSL.java * which is distributed under APL-2.0 license: http://www.apache.org/licenses/LICENSE-2.0 */ /* Portions Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; and to You under the Apache License, Version 2.0. */ package com.cloudbees.plugins.credentials; import hudson.util.Secret; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import jenkins.security.ConfidentialKey; import jenkins.security.ConfidentialStore; /** * {@link ConfidentialKey} that stores a {@link SecretKey} for shared-secret cryptography (AES). * * @since 2.1.5 */ public class CredentialsConfidentialKey extends ConfidentialKey { /** * The secret key. */ private volatile SecretKey secret; /** * The spice size used to seed the IV. */ private static final int SPICE_SIZE = 16; /** * The digest algorithm to use. */ private static final String DIGEST_ALG = "SHA-256"; /** * The key algorithm to use. */ private static final String KEY_ALG = "AES"; /** * The Cipher algorithm to use. */ private static final String CIPHER_ALG = "AES/CBC/PKCS5Padding"; /** * {@inheritDoc} */ public CredentialsConfidentialKey(String id) { super(id); } /** * Constructor. * * @param owner the owning class name. * @param shortName the name. */ public CredentialsConfidentialKey(Class owner, String shortName) { this(owner.getName() + '.' + shortName); } /** * Gets the key used for encryption. * * @return the key used for encryption. */ private SecretKey getKey() { try { if (secret == null) { synchronized (this) { if (secret == null) { byte[] payload = load(); if (payload == null) { payload = ConfidentialStore.get().randomBytes(256); store(payload); } // Due to the stupid US export restriction JDK only ships 128bit version. secret = new SecretKeySpec(payload, 0, 128 / 8, KEY_ALG); } } } return secret; } catch (IOException e) { throw new Error("Failed to load the key: " + getId(), e); } } /** * Returns a {@link Cipher} object for encrypting with this key. * * @param salt the salt to use for the {@link Cipher} * @return the {@link Cipher} */ public Cipher encrypt(byte[] salt) { try { return createCipher(getKey().getEncoded(), salt, Cipher.ENCRYPT_MODE); } catch (GeneralSecurityException e) { throw new AssertionError(e); } } /** * Returns a {@link Cipher} object for decrypting with this key. * * @param salt the salt to use for the {@link Cipher} * @return the {@link Cipher} */ public Cipher decrypt(byte[] salt) { try { return createCipher(getKey().getEncoded(), salt, Cipher.DECRYPT_MODE); } catch (GeneralSecurityException e) { throw new AssertionError(e); } } // copied from https://github.com/codehaus-plexus/plexus-cipher/blob/6ab0e38df80beed9ab3227ffab938b21dcdf5505/src // /main/java/org/sonatype/plexus/components/cipher/PBECipher.java private Cipher createCipher(final byte[] pwdAsBytes, byte[] salt, final int mode) throws GeneralSecurityException { MessageDigest _digester = MessageDigest.getInstance(DIGEST_ALG); _digester.reset(); byte[] keyAndIv = new byte[SPICE_SIZE * 2]; if (salt == null || salt.length == 0) { // Unsalted! Bad idea! salt = null; } byte[] result; int currentPos = 0; while (currentPos < keyAndIv.length) { _digester.update(pwdAsBytes); if (salt != null) { // First 8 bytes of salt ONLY! That wasn't obvious to me // when using AES encrypted private keys in "Traditional // SSLeay Format". // // Example: // DEK-Info: AES-128-CBC,8DA91D5A71988E3D4431D9C2C009F249 // // Only the first 8 bytes are salt, but the whole thing is // re-used again later as the IV. MUCH gnashing of teeth! _digester.update(salt, 0, 8); } result = _digester.digest(); int stillNeed = keyAndIv.length - currentPos; // Digest gave us more than we need. Let's truncate it. if (result.length > stillNeed) { byte[] b = new byte[stillNeed]; System.arraycopy(result, 0, b, 0, b.length); result = b; } System.arraycopy(result, 0, keyAndIv, currentPos, result.length); currentPos += result.length; if (currentPos < keyAndIv.length) { // Next round starts with a hash of the hash. _digester.reset(); _digester.update(result); } } byte[] key = new byte[SPICE_SIZE]; byte[] iv = new byte[SPICE_SIZE]; System.arraycopy(keyAndIv, 0, key, 0, key.length); System.arraycopy(keyAndIv, key.length, iv, 0, iv.length); Cipher cipher = Secret.getCipher(CIPHER_ALG); cipher.init(mode, new SecretKeySpec(key, KEY_ALG), new IvParameterSpec(iv)); return cipher; } }