Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

NEXUS-6323: Plexus cipher reimplementation #27

Closed
wants to merge 16 commits into from

4 participants

@cstamas
Owner

Adding a re-implementation of PlexusCipher that uses CryptoHelper.

Issue
https://issues.sonatype.org/browse/NEXUS-6323

CI
NA

@cstamas cstamas referenced this pull request in sonatype/nexus-oss
Merged

REVIEW NEXUS-6323: Replace plx cipher #636

...su/goodies/crypto/internal/DefaultPasswordCipher.java
((77 lines not shown))
+ final @Named("${passwordCipher.algorithm:-PBEWithSHAAnd128BitRC4}") String algorithm,
+ final @Named("${passwordCipher.iterationCount:-23}") int iterationCount)
+ {
+ this.cryptoHelper = checkNotNull(cryptoHelper);
+ this.algorithm = checkNotNull(algorithm);
+ this.iterationCount = iterationCount;
+ this.base64Encoder = new Base64Encoder();
+ }
+
+ @Override
+ public String encrypt(final String str, final String passPhrase) {
+ checkNotNull(str);
+ checkNotNull(passPhrase);
+ try {
+ SecureRandom secureRandom = cryptoHelper.createSecureRandom();
+ secureRandom.setSeed(System.nanoTime());
@jdillon Owner
jdillon added a note

can probably avoid creating a new secure random each time, init this in ctor.

@cstamas Owner
cstamas added a note

Will do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cstamas added some commits
@cstamas cstamas NEXUS-6323: Implement plexus-cipher drop-in replacement
Also supporting legacy and current implementations, as
seemingly legacy is still used somewhere in NX.
48052f7
@cstamas cstamas NEXUS-6323: Undo POM change got in by mistake 829a8cf
@cstamas cstamas NEXUS-6323: The "current" should be default
To allow simple injection without need for @Named. Still,
"legaci" is injectable using provided name in iface.
f804024
@cstamas cstamas NEXUS-6323: Detect base64 problems and emit them as IAEx
As otherwise it would be a "generic" runtime ex wrapped by
Throwables, and reasoning about "bad input" would be lost.
afb1ca1
.../sisu/goodies/crypto/internal/MavenCipherSupport.java
((15 lines not shown))
+import org.sonatype.sisu.goodies.common.ComponentSupport;
+import org.sonatype.sisu.goodies.crypto.MavenCipher;
+import org.sonatype.sisu.goodies.crypto.PasswordCipher;
+
+import com.google.common.base.Charsets;
+
+import com.google.common.base.Strings;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Support class for {@link MavenCipher} implementations.
+ *
+ * @since 1.10
+ */
+public abstract class MavenCipherSupport
@mrprescott Owner

I'm probably missing something, but it seems this abstract class has two identical subclasses that don't add any functionality - could that be collapsed? (Or is the class name used as a lookup for something elsewhere?)

@mcculls Owner
mcculls added a note

The two subclasses have different injected constructor parameters, so while the base is the same they're configured differently (see PasswordCipherMavenLegacyImpl vs PasswordCipherMavenImpl which setup different algorithms).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../sisu/goodies/crypto/internal/MavenCipherSupport.java
((62 lines not shown))
+ }
+
+ protected String doDecrypt(final String str, final String passPhrase) {
+ return new String(passwordCipher.decrypt(str.getBytes(Charsets.UTF_8), passPhrase), Charsets.UTF_8);
+ }
+
+ @Override
+ public boolean isPasswordCipher(final String str) {
+ return peel(str) != null;
+ }
+
+ /**
+ * Peels of the start and stop "shield" braces from payload if possible, otherwise returns {@code null} signaling that
+ * input is invalid.
+ */
+ protected String peel(final String str) {
@mrprescott Owner

(Minor) @Nullable?

@cstamas Owner
cstamas added a note

Check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mrprescott
Owner

+1

...pe/sisu/goodies/crypto/internal/CryptoHelperImpl.java
@@ -143,7 +143,13 @@ public SecureRandom createSecureRandom() {
}
return obj;
}
-
+
+ @Override
+ public SecretKeyFactory createSecretKeyFactory(String transformation) throws NoSuchAlgorithmException {
+ checkNotNull(transformation);
+ return SecretKeyFactory.getInstance(transformation, getProvider());
+ }
@jdillon Owner
jdillon added a note

btw I've implemented this already on master, the impl here wasn't even following the same practice as the rest of the factory methods in this impl... did you not actually look to see what the others were doing?

@cstamas Owner
cstamas added a note

I had this change made on branch 5 days ago, but I did it only as I suspected that it belongs to CryptoHelper (and probably missed the part you ask about). Thanks for doing it on the master, will merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cstamas and others added some commits
@jdillon
Owner

manually merged

@jdillon jdillon closed this
@jdillon jdillon deleted the NEXUS-6323-plexus-cipher branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 17, 2014
  1. @cstamas
  2. @cstamas
  3. @cstamas
Commits on Jul 18, 2014
  1. @cstamas

    NEXUS-6323: Implement plexus-cipher drop-in replacement

    cstamas authored
    Also supporting legacy and current implementations, as
    seemingly legacy is still used somewhere in NX.
  2. @cstamas
  3. @cstamas

    NEXUS-6323: The "current" should be default

    cstamas authored
    To allow simple injection without need for @Named. Still,
    "legaci" is injectable using provided name in iface.
  4. @cstamas

    NEXUS-6323: Detect base64 problems and emit them as IAEx

    cstamas authored
    As otherwise it would be a "generic" runtime ex wrapped by
    Throwables, and reasoning about "bad input" would be lost.
Commits on Jul 23, 2014
  1. @cstamas
  2. @cstamas
  3. @cstamas

    NEXUS-6323: Simplification

    cstamas authored
    Changes:
    * MavenCipher is a "wrapper" class that is able to use required PasswordCipher
    * PasswordCipher implementations moved out of internal package
    * none of these are components, as they are easily constructed by integrator as needed
  4. @cstamas

    NEXUS-6323: Fix formatting

    cstamas authored
  5. @cstamas

    NEXUS-6323: More javadoc

    cstamas authored
Commits on Jul 25, 2014
  1. @jdillon

    refinement

    jdillon authored
  2. @jdillon

    avoid duplicate

    jdillon authored
  3. @jdillon

    normalize test class names

    jdillon authored
  4. @jdillon
This page is out of date. Refresh to see the latest.
View
33 crypto/src/main/java/org/sonatype/sisu/goodies/crypto/PasswordCipher.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.sonatype.sisu.goodies.crypto;
+
+/**
+ * Component for password based encryption (PBE).
+ *
+ * To be used on smaller payloads like user passwords, due to use of byte arrays for payload.
+ *
+ * @since 1.10
+ */
+public interface PasswordCipher
+{
+ /**
+ * Encrypt the provided plaintext payload using provided pass phrase.
+ */
+ byte[] encrypt(byte[] payload, String passPhrase);
+
+ /**
+ * Decrypt the provided input payload using provided pass phrase.
+ */
+ byte[] decrypt(byte[] payload, String passPhrase);
+}
View
89 crypto/src/main/java/org/sonatype/sisu/goodies/crypto/maven/MavenCipher.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.sonatype.sisu.goodies.crypto.maven;
+
+import javax.annotation.Nullable;
+
+import org.sonatype.sisu.goodies.crypto.PasswordCipher;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * {@link PasswordCipher} wrapper that uses Apache Maven format (aka Plexus Cipher).
+ *
+ * Is meant to be a drop-in replacement for plexus-cipher. Compatibility with given version of Plexus Cipher depends
+ * on which {@link PasswordCipher} is used with this class.
+ *
+ * @see PasswordCipherMavenImpl
+ * @see PasswordCipherMavenLegacyImpl
+ *
+ * @since 1.10
+ */
+public class MavenCipher
+{
+ private static final char SHIELD_BEGIN = '{';
+
+ private static final char SHIELD_END = '}';
+
+ private final PasswordCipher passwordCipher;
+
+ public MavenCipher(final PasswordCipher passwordCipher) {
+ this.passwordCipher = checkNotNull(passwordCipher);
+ }
+
+ public String encrypt(final String str, final String passPhrase) {
+ checkNotNull(str);
+ checkNotNull(passPhrase);
+ return SHIELD_BEGIN + doEncrypt(str, passPhrase) + SHIELD_END;
+ }
+
+ private String doEncrypt(final String str, final String passPhrase) {
+ return new String(passwordCipher.encrypt(str.getBytes(Charsets.UTF_8), passPhrase), Charsets.UTF_8);
+ }
+
+ public String decrypt(final String str, final String passPhrase) {
+ checkNotNull(str);
+ checkNotNull(passPhrase);
+ String payload = peel(str);
+ checkArgument(payload != null, "Input string is not a password cipher");
+ return doDecrypt(payload, passPhrase);
+ }
+
+ private String doDecrypt(final String str, final String passPhrase) {
+ return new String(passwordCipher.decrypt(str.getBytes(Charsets.UTF_8), passPhrase), Charsets.UTF_8);
+ }
+
+ public boolean isPasswordCipher(final String str) {
+ return peel(str) != null;
+ }
+
+ /**
+ * Peels of the start and stop "shield" braces from payload if possible, otherwise returns {@code null} signaling that
+ * input is invalid.
+ */
+ @Nullable
+ private String peel(final String str) {
+ if (Strings.isNullOrEmpty(str)) {
+ return null;
+ }
+ int start = str.indexOf(SHIELD_BEGIN);
+ int stop = str.indexOf(SHIELD_END);
+ if (start != -1 && stop != -1 && stop > start + 1) {
+ return str.substring(start + 1, stop);
+ }
+ return null;
+ }
+}
View
168 crypto/src/main/java/org/sonatype/sisu/goodies/crypto/maven/PasswordCipherMavenImpl.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.sonatype.sisu.goodies.crypto.maven;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.sonatype.sisu.goodies.crypto.CryptoHelper;
+import org.sonatype.sisu.goodies.crypto.PasswordCipher;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import org.bouncycastle.util.encoders.Base64Encoder;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Maven implementation of {@link PasswordCipher} compatible with encryption used by plexus-cipher versions [1.6,].
+ *
+ * @since 1.10
+ */
+@ThreadSafe
+public class PasswordCipherMavenImpl
+ implements PasswordCipher
+{
+ private static final int SPICE_SIZE = 16;
+
+ private static final int SALT_SIZE = 8;
+
+ private static final int CHUNK_SIZE = 16;
+
+ private static final String DIGEST_ALG = "SHA-256";
+
+ private static final String KEY_ALG = "AES";
+
+ private static final String CIPHER_ALG = "AES/CBC/PKCS5Padding";
+
+ private final CryptoHelper cryptoHelper;
+
+ private final Base64Encoder base64Encoder;
+
+ private final SecureRandom secureRandom;
+
+ public PasswordCipherMavenImpl(final CryptoHelper cryptoHelper) {
+ this.cryptoHelper = checkNotNull(cryptoHelper);
+ this.base64Encoder = new Base64Encoder();
+ this.secureRandom = cryptoHelper.createSecureRandom();
+ this.secureRandom.setSeed(System.nanoTime());
+ }
+
+ @Override
+ public byte[] encrypt(final byte[] payload, final String passPhrase) {
+ checkNotNull(payload);
+ checkNotNull(passPhrase);
+ try {
+ byte[] salt = new byte[SALT_SIZE];
+ secureRandom.nextBytes(salt);
+ Cipher cipher = createCipher(passPhrase, salt, Cipher.ENCRYPT_MODE);
+ byte[] encryptedBytes = cipher.doFinal(payload);
+ int len = encryptedBytes.length;
+ byte padLen = (byte) (CHUNK_SIZE - (SALT_SIZE + len + 1) % CHUNK_SIZE);
+ int totalLen = SALT_SIZE + len + padLen + 1;
+ byte[] allEncryptedBytes = new byte[totalLen];
+ secureRandom.nextBytes(allEncryptedBytes);
+ System.arraycopy(salt, 0, allEncryptedBytes, 0, SALT_SIZE);
+ allEncryptedBytes[SALT_SIZE] = padLen;
+ System.arraycopy(encryptedBytes, 0, allEncryptedBytes, SALT_SIZE + 1, len);
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(allEncryptedBytes.length * 2);
+ base64Encoder.encode(allEncryptedBytes, 0, allEncryptedBytes.length, bout);
+ return bout.toByteArray();
+ }
+ catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ public byte[] decrypt(final byte[] payload, final String passPhrase) {
+ checkNotNull(payload);
+ checkNotNull(passPhrase);
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ base64Encoder.decode(payload, 0, payload.length, baos);
+ byte[] allEncryptedBytes = baos.toByteArray();
+ int totalLen = allEncryptedBytes.length;
+ byte[] salt = new byte[SALT_SIZE];
+ System.arraycopy(allEncryptedBytes, 0, salt, 0, SALT_SIZE);
+ byte padLen = allEncryptedBytes[SALT_SIZE];
+ byte[] encryptedBytes = new byte[totalLen - SALT_SIZE - 1 - padLen];
+ System.arraycopy(allEncryptedBytes, SALT_SIZE + 1, encryptedBytes, 0, encryptedBytes.length);
+ Cipher cipher = createCipher(passPhrase, salt, Cipher.DECRYPT_MODE);
+ return cipher.doFinal(encryptedBytes);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Invalid payload (base64 problem)", e);
+ }
+ catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ // ==
+
+ private Cipher createCipher(final String passPhrase, byte[] salt, final int mode) throws Exception {
+ final MessageDigest digester = cryptoHelper.createDigest(DIGEST_ALG); // construction of this is cheap
+ 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(passPhrase.getBytes(Charsets.UTF_8));
+ 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 = Cipher.getInstance(CIPHER_ALG);
+ cipher.init(mode, new SecretKeySpec(key, KEY_ALG), new IvParameterSpec(iv));
+ return cipher;
+ }
+}
View
148 ...o/src/main/java/org/sonatype/sisu/goodies/crypto/maven/PasswordCipherMavenLegacyImpl.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+
+package org.sonatype.sisu.goodies.crypto.maven;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.security.spec.KeySpec;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.sonatype.sisu.goodies.crypto.CryptoHelper;
+import org.sonatype.sisu.goodies.crypto.PasswordCipher;
+
+import com.google.common.base.Throwables;
+import org.bouncycastle.util.encoders.Base64Encoder;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Maven legacy impl of {@link PasswordCipher} compatible with encryption used by plexus-cipher versions [1.0,1.5].
+ *
+ * @since 1.10
+ */
+@ThreadSafe
+public class PasswordCipherMavenLegacyImpl
+ implements PasswordCipher
+{
+ private static final String DEFAULT_ALGORITHM = "PBEWithSHAAnd128BitRC4";
+
+ private static final int DEFAULT_ITERATION_COUNT = 23;
+
+ private static final int DEFAULT_SALT_SIZE = 8;
+
+ private final String algorithm;
+
+ private final int iterationCount;
+
+ private final int saltSize;
+
+ private final CryptoHelper cryptoHelper;
+
+ private final Base64Encoder base64Encoder;
+
+ private final SecureRandom secureRandom;
+
+ /**
+ * Creates instance using "defaults" (from Plexus Cipher).
+ */
+ public PasswordCipherMavenLegacyImpl(final CryptoHelper cryptoHelper) {
+ this(cryptoHelper, DEFAULT_ALGORITHM, DEFAULT_ITERATION_COUNT, DEFAULT_SALT_SIZE);
+ }
+
+ /**
+ * Allows customization of algorithm, iteration count and salt size as DefaultPlexusCipher did.
+ */
+ public PasswordCipherMavenLegacyImpl(final CryptoHelper cryptoHelper,
+ final String algorithm,
+ final int iterationCount,
+ final int saltSize)
+ {
+ this.cryptoHelper = checkNotNull(cryptoHelper);
+ this.algorithm = checkNotNull(algorithm);
+ this.iterationCount = iterationCount;
+ this.saltSize = saltSize;
+ this.base64Encoder = new Base64Encoder();
+ this.secureRandom = cryptoHelper.createSecureRandom();
+ this.secureRandom.setSeed(System.nanoTime());
+ }
+
+ @Override
+ public byte[] encrypt(final byte[] payload, final String passPhrase) {
+ checkNotNull(payload);
+ checkNotNull(passPhrase);
+ try {
+ byte[] salt = new byte[saltSize];
+ secureRandom.nextBytes(salt);
+ byte[] enc = createCipher(passPhrase, salt, Cipher.ENCRYPT_MODE).doFinal(payload);
+ byte saltLen = (byte) (salt.length & 0x00ff);
+ int encLen = enc.length;
+ byte[] res = new byte[salt.length + encLen + 1];
+ res[0] = saltLen;
+ System.arraycopy(salt, 0, res, 1, saltLen);
+ System.arraycopy(enc, 0, res, saltLen + 1, encLen);
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(res.length * 2);
+ base64Encoder.encode(res, 0, res.length, bout);
+ return bout.toByteArray();
+ }
+ catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ public byte[] decrypt(final byte[] payload, final String passPhrase) {
+ checkNotNull(payload);
+ checkNotNull(passPhrase);
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ base64Encoder.decode(payload, 0, payload.length, baos);
+ byte[] res = baos.toByteArray();
+ int saltLen = res[0] & 0x00ff;
+ checkArgument(saltLen == saltSize, "Encrypted string corrupted (salt size)");
+ checkArgument(res.length >= (saltLen + 2), "Encrypted string corrupted (payload size)");
+ byte[] salt = new byte[saltLen];
+ System.arraycopy(res, 1, salt, 0, saltLen);
+ int decLen = res.length - saltLen - 1;
+ checkArgument(decLen >= 1, "Encrypted string corrupted (payload segment)");
+ byte[] dec = new byte[decLen];
+ System.arraycopy(res, saltLen + 1, dec, 0, decLen);
+ Cipher cipher = createCipher(passPhrase, salt, Cipher.DECRYPT_MODE);
+ return cipher.doFinal(dec);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Invalid payload (base64 problem)", e);
+ }
+ catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ // ==
+
+ private Cipher createCipher(final String passPhrase, final byte[] salt, final int mode) throws Exception {
+ Cipher cipher = cryptoHelper.createCipher(algorithm);
+ KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray());
+ SecretKey key = cryptoHelper.createSecretKeyFactory(algorithm).generateSecret(keySpec);
+ PBEParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
+ cipher.init(mode, key, paramSpec);
+ return cipher;
+ }
+}
View
105 crypto/src/test/java/org/sonatype/sisu/goodies/crypto/maven/MavenCipherTestSupport.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.sonatype.sisu.goodies.crypto.maven;
+
+import java.security.Security;
+
+import org.sonatype.sisu.goodies.crypto.maven.MavenCipher;
+import org.sonatype.sisu.litmus.testsupport.TestSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * UT support for {@link MavenCipher} implementations containing simple set of excersises.
+ */
+public abstract class MavenCipherTestSupport
+ extends TestSupport
+{
+ protected final String passPhrase;
+
+ protected final String plaintext;
+
+ protected final String encrypted;
+
+ protected final MavenCipher testSubject;
+
+ protected MavenCipherTestSupport(final String passPhrase, final String plaintext, final String encrypted,
+ final MavenCipher testSubject)
+ {
+ this.passPhrase = checkNotNull(passPhrase);
+ this.plaintext = checkNotNull(plaintext);
+ this.encrypted = checkNotNull(encrypted);
+ this.testSubject = checkNotNull(testSubject);
+ }
+
+ @Before
+ public void prepare() {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @After
+ public void cleanup() {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ }
+
+ @Test
+ public void payloadDetection() {
+ assertThat(testSubject.isPasswordCipher(plaintext), is(false));
+ assertThat(testSubject.isPasswordCipher(""), is(false));
+ assertThat(testSubject.isPasswordCipher("{}"), is(false));
+ assertThat(testSubject.isPasswordCipher(null), is(false));
+ assertThat(testSubject.isPasswordCipher(encrypted), is(true));
+ assertThat(testSubject.isPasswordCipher("{ }"), is(true));
+ }
+
+ @Test
+ public void encrypt() throws Exception {
+ String enc = testSubject.encrypt(plaintext, passPhrase);
+ assertThat(enc, notNullValue());
+ }
+
+ @Test
+ public void decrypt() throws Exception {
+ String dec = testSubject.decrypt(encrypted, passPhrase);
+ assertThat(dec, equalTo(plaintext));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void decryptCorruptedMissingEnd() throws Exception {
+ testSubject.decrypt("{CFUju8n8eKQHj8u0HI9uQMRm", passPhrase);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void decryptNull() throws Exception {
+ testSubject.decrypt(null, passPhrase);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void decryptNullPassPhrase() throws Exception {
+ testSubject.decrypt(encrypted, null);
+ }
+
+ @Test
+ public void roundTrip() throws Exception {
+ String dec = testSubject.decrypt(testSubject.encrypt(plaintext, passPhrase), passPhrase);
+ assertThat(dec, equalTo(plaintext));
+ }
+}
View
61 crypto/src/test/java/org/sonatype/sisu/goodies/crypto/maven/PasswordCipherMavenImplTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.sonatype.sisu.goodies.crypto.maven;
+
+import org.sonatype.sisu.goodies.crypto.internal.CryptoHelperImpl;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * UT for {@link MavenCipher} with {@link PasswordCipherMavenImpl}
+ */
+public class PasswordCipherMavenImplTest
+ extends MavenCipherTestSupport
+{
+ private static final String passPhrase = "foofoo";
+
+ private static final String plaintext = "my testing phrase";
+
+ private static final String encrypted = "{5FjvnZvhNDMHHnxXoPu1a0WcgZzaArKRCnGBnsA83R7rYQHKGFrprtAM4Qyr4diV}";
+
+ public PasswordCipherMavenImplTest() {
+ super(passPhrase, plaintext, encrypted, new MavenCipher(new PasswordCipherMavenImpl(new CryptoHelperImpl())));
+ }
+
+ /**
+ * This is a "master password" string created using Maven 3.0.4 on CLI as described here:
+ * http://maven.apache.org/guides/mini/guide-encryption.html#How_to_create_a_master_password
+ */
+ @Test
+ public void masterPasswordCreatedWithMaven304Cli() {
+ String passPhrase = "settings.security";
+ String plaintext = "123321";
+ String encrypted = "{KW5k/vol4xMHusz6ikZqdj0t4YRClp4/5Dsb30+M9R0=}";
+ assertThat(testSubject.decrypt(encrypted, passPhrase), equalTo(plaintext));
+ }
+
+ /**
+ * This is a "master password" string created using Maven 3.2.2 on CLI as described here:
+ * http://maven.apache.org/guides/mini/guide-encryption.html#How_to_create_a_master_password
+ */
+ @Test
+ public void masterPasswordCreatedWithMaven322Cli() {
+ String passPhrase = "settings.security";
+ String plaintext = "123321";
+ String encrypted = "{eO8Yc66/I/IHaeg4CoF+/o5bwS5IIyfWcgsYhS0s9W8=}";
+ assertThat(testSubject.decrypt(encrypted, passPhrase), equalTo(plaintext));
+ }
+}
View
33 ...c/test/java/org/sonatype/sisu/goodies/crypto/maven/PasswordCipherMavenLegacyImplTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2007-2014 Sonatype, Inc. All rights reserved.
+ *
+ * This program is licensed to you under the Apache License Version 2.0,
+ * and you may not use this file except in compliance with the Apache License Version 2.0.
+ * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the Apache License Version 2.0 is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
+ */
+package org.sonatype.sisu.goodies.crypto.maven;
+
+import org.sonatype.sisu.goodies.crypto.internal.CryptoHelperImpl;
+
+/**
+ * UT for {@link MavenCipher} with {@link PasswordCipherMavenLegacyImpl}.
+ */
+public class PasswordCipherMavenLegacyImplTest
+ extends MavenCipherTestSupport
+{
+ private static final String passPhrase = "foofoo";
+
+ private static final String plaintext = "my testing phrase";
+
+ private static final String encrypted = "{CFUju8n8eKQHj8u0HI9uQMRmKQALtoXH7lY=}";
+
+ public PasswordCipherMavenLegacyImplTest() {
+ super(passPhrase, plaintext, encrypted, new MavenCipher(new PasswordCipherMavenLegacyImpl(
+ new CryptoHelperImpl())));
+ }
+}
Something went wrong with that request. Please try again.