Skip to content

Commit 5dfd475

Browse files
committed
Ingest plexus-cipher
1 parent 85a46a6 commit 5dfd475

File tree

12 files changed

+401
-117
lines changed

12 files changed

+401
-117
lines changed

pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@
4545
<artifactId>slf4j-api</artifactId>
4646
<version>${version.slf4j}</version>
4747
</dependency>
48-
<dependency>
49-
<groupId>org.codehaus.plexus</groupId>
50-
<artifactId>plexus-cipher</artifactId>
51-
<version>3.0.0</version>
52-
</dependency>
5348

5449
<dependency>
5550
<groupId>javax.inject</groupId>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
*/
19+
20+
package org.codehaus.plexus.components.secdispatcher;
21+
22+
/**
23+
* Cipher interface.
24+
*
25+
* @since 4.0.1
26+
*/
27+
public interface Cipher {
28+
/**
29+
* Encrypts the clear text data with password and returns result. No argument is allowed to be {@code null}.
30+
*
31+
* @throws CipherException if encryption failed (is unexpected to happen, as it would mean that Java Runtime
32+
* lacks some Crypto elements).
33+
*/
34+
String encrypt(final String clearText, final String password) throws CipherException;
35+
36+
/**
37+
* Decrypts the encrypted text with password and returns clear text result. No argument is allowed to be {@code null}.
38+
*
39+
* @throws CipherException if decryption failed. It may happen as with {@link #encrypt(String, String)} due Java
40+
* Runtime lacking some Crypto elements (less likely). Most likely decrypt will fail due wrong provided password
41+
* or maybe corrupted encrypted text.
42+
*/
43+
String decrypt(final String encryptedText, final String password) throws CipherException;
44+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2008 Sonatype, Inc. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
7+
*
8+
* Unless required by applicable law or agreed to in writing,
9+
* software distributed under the Apache License Version 2.0 is distributed on an
10+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
12+
*/
13+
package org.codehaus.plexus.components.secdispatcher;
14+
15+
/**
16+
* Exception thrown by {@link Cipher}.
17+
*
18+
* @since 4.0.1
19+
*/
20+
public class CipherException extends SecDispatcherException {
21+
public CipherException(String message) {
22+
super(message);
23+
}
24+
25+
public CipherException(String message, Throwable cause) {
26+
super(message, cause);
27+
}
28+
}

src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,20 @@ public interface SecDispatcher {
6464
String decrypt(String str) throws SecDispatcherException, IOException;
6565

6666
/**
67-
* Returns {@code true} if passed in string contains "legacy" password (Maven3 kind).
67+
* Returns {@code true} if passed in string is POSSIBLY encrypted string. Forms of encrypted strings are:
68+
* <ul>
69+
* <li>Legacy: {jSMOWnoPFgsHVpMvz5VrIt5kRbzGpI8u+9EF1iFQyJQ=}</li>
70+
* <li>Current: {[name=master,cipher=AES/GCM/NoPadding,version=4.0]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==}</li>
71+
* </ul>
6872
*/
69-
boolean isLegacyPassword(String str);
73+
boolean isEncryptedString(String str);
74+
75+
/**
76+
* Returns {@code true} if method {@link #isEncryptedString(String)} returns {@code true} with passed in string, and
77+
* there are no attributes detected. In other words, returns {@code true} if passed in string contains
78+
* "legacy" (Maven3 kind) password.
79+
*/
80+
boolean isLegacyEncryptedString(String str);
7081

7182
/**
7283
* Reads the effective configuration, eventually creating new instance if not present.

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import java.util.StringTokenizer;
2626
import java.util.stream.Collectors;
2727

28-
import org.codehaus.plexus.components.cipher.PlexusCipher;
29-
import org.codehaus.plexus.components.cipher.PlexusCipherException;
3028
import org.codehaus.plexus.components.secdispatcher.Dispatcher;
3129
import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
3230
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
@@ -48,15 +46,15 @@
4846
* @author Oleg Gusakov
4947
*/
5048
public class DefaultSecDispatcher implements SecDispatcher {
49+
public static final String SHIELD_BEGIN = "{";
50+
public static final String SHIELD_END = "}";
5151
public static final String ATTR_START = "[";
5252
public static final String ATTR_STOP = "]";
5353

54-
protected final PlexusCipher cipher;
5554
protected final Map<String, Dispatcher> dispatchers;
5655
protected final Path configurationFile;
5756

58-
public DefaultSecDispatcher(PlexusCipher cipher, Map<String, Dispatcher> dispatchers, Path configurationFile) {
59-
this.cipher = requireNonNull(cipher);
57+
public DefaultSecDispatcher(Map<String, Dispatcher> dispatchers, Path configurationFile) {
6058
this.dispatchers = requireNonNull(dispatchers);
6159
this.configurationFile = requireNonNull(configurationFile);
6260

@@ -100,65 +98,67 @@ public Collection<Field> fields() {
10098
@Override
10199
public String encrypt(String str, Map<String, String> attr) throws SecDispatcherException, IOException {
102100
if (isEncryptedString(str)) return str;
103-
104-
try {
105-
if (attr == null) {
106-
attr = new HashMap<>();
107-
} else {
108-
attr = new HashMap<>(attr);
101+
if (attr == null) {
102+
attr = new HashMap<>();
103+
} else {
104+
attr = new HashMap<>(attr);
105+
}
106+
if (attr.get(DISPATCHER_NAME_ATTR) == null) {
107+
SettingsSecurity conf = readConfiguration(false);
108+
if (conf == null) {
109+
throw new SecDispatcherException("No configuration found");
109110
}
110-
if (attr.get(DISPATCHER_NAME_ATTR) == null) {
111-
SettingsSecurity conf = readConfiguration(false);
112-
if (conf == null) {
113-
throw new SecDispatcherException("No configuration found");
114-
}
115-
String defaultDispatcher = conf.getDefaultDispatcher();
116-
if (defaultDispatcher == null) {
117-
throw new SecDispatcherException("No defaultDispatcher set in configuration");
118-
}
119-
attr.put(DISPATCHER_NAME_ATTR, defaultDispatcher);
111+
String defaultDispatcher = conf.getDefaultDispatcher();
112+
if (defaultDispatcher == null) {
113+
throw new SecDispatcherException("No defaultDispatcher set in configuration");
120114
}
121-
String name = attr.get(DISPATCHER_NAME_ATTR);
122-
Dispatcher dispatcher = dispatchers.get(name);
123-
if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name);
124-
Dispatcher.EncryptPayload payload = dispatcher.encrypt(str, attr, prepareDispatcherConfig(name));
125-
HashMap<String, String> resultAttributes = new HashMap<>(payload.getAttributes());
126-
resultAttributes.put(SecDispatcher.DISPATCHER_NAME_ATTR, name);
127-
resultAttributes.put(SecDispatcher.DISPATCHER_VERSION_ATTR, SecUtil.specVersion());
128-
String res = ATTR_START
129-
+ resultAttributes.entrySet().stream()
130-
.map(e -> e.getKey() + "=" + e.getValue())
131-
.collect(Collectors.joining(","))
132-
+ ATTR_STOP;
133-
res += payload.getEncrypted();
134-
return cipher.decorate(res);
135-
} catch (PlexusCipherException e) {
136-
throw new SecDispatcherException(e.getMessage(), e);
115+
attr.put(DISPATCHER_NAME_ATTR, defaultDispatcher);
137116
}
117+
String name = attr.get(DISPATCHER_NAME_ATTR);
118+
Dispatcher dispatcher = dispatchers.get(name);
119+
if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name);
120+
Dispatcher.EncryptPayload payload = dispatcher.encrypt(str, attr, prepareDispatcherConfig(name));
121+
HashMap<String, String> resultAttributes = new HashMap<>(payload.getAttributes());
122+
resultAttributes.put(SecDispatcher.DISPATCHER_NAME_ATTR, name);
123+
resultAttributes.put(SecDispatcher.DISPATCHER_VERSION_ATTR, SecUtil.specVersion());
124+
return SHIELD_BEGIN
125+
+ ATTR_START
126+
+ resultAttributes.entrySet().stream()
127+
.map(e -> e.getKey() + "=" + e.getValue())
128+
.collect(Collectors.joining(","))
129+
+ ATTR_STOP
130+
+ payload.getEncrypted()
131+
+ SHIELD_END;
138132
}
139133

140134
@Override
141135
public String decrypt(String str) throws SecDispatcherException, IOException {
142136
if (!isEncryptedString(str)) return str;
143-
try {
144-
String bare = cipher.unDecorate(str);
145-
Map<String, String> attr = requireNonNull(stripAttributes(bare));
146-
if (isLegacyPassword(str)) {
147-
attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME);
148-
}
149-
String name = attr.get(DISPATCHER_NAME_ATTR);
150-
Dispatcher dispatcher = dispatchers.get(name);
151-
if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name);
152-
return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(name));
153-
} catch (PlexusCipherException e) {
154-
throw new SecDispatcherException(e.getMessage(), e);
137+
String bare = unDecorate(str);
138+
Map<String, String> attr = requireNonNull(stripAttributes(bare));
139+
if (isLegacyEncryptedString(str)) {
140+
attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME);
155141
}
142+
String name = attr.get(DISPATCHER_NAME_ATTR);
143+
Dispatcher dispatcher = dispatchers.get(name);
144+
if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name);
145+
return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(name));
146+
}
147+
148+
@Override
149+
public boolean isEncryptedString(String str) {
150+
return str != null
151+
&& !str.isBlank()
152+
&& str.startsWith(SHIELD_BEGIN)
153+
&& str.endsWith(SHIELD_END)
154+
&& !unDecorate(str).contains(SHIELD_BEGIN)
155+
&& !unDecorate(str).contains(SHIELD_END);
156156
}
157157

158158
@Override
159-
public boolean isLegacyPassword(String str) {
159+
public boolean isLegacyEncryptedString(String str) {
160160
if (!isEncryptedString(str)) return false;
161-
Map<String, String> attr = requireNonNull(stripAttributes(cipher.unDecorate(str)));
161+
Map<String, String> attr = requireNonNull(stripAttributes(unDecorate(str)));
162162
return !attr.containsKey(DISPATCHER_NAME_ATTR);
163163
}
164164

@@ -284,7 +284,7 @@ protected Map<String, String> stripAttributes(String str) {
284284
return result;
285285
}
286286

287-
protected boolean isEncryptedString(String str) {
288-
return cipher.isEncryptedString(str);
287+
protected String unDecorate(String str) {
288+
return str.substring(SHIELD_BEGIN.length(), str.length() - SHIELD_END.length());
289289
}
290290
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
*/
19+
20+
package org.codehaus.plexus.components.secdispatcher.internal.cipher;
21+
22+
import javax.crypto.Cipher;
23+
import javax.crypto.SecretKey;
24+
import javax.crypto.SecretKeyFactory;
25+
import javax.crypto.spec.GCMParameterSpec;
26+
import javax.crypto.spec.PBEKeySpec;
27+
import javax.crypto.spec.SecretKeySpec;
28+
import javax.inject.Named;
29+
import javax.inject.Singleton;
30+
31+
import java.nio.ByteBuffer;
32+
import java.nio.charset.StandardCharsets;
33+
import java.security.NoSuchAlgorithmException;
34+
import java.security.SecureRandom;
35+
import java.security.spec.InvalidKeySpecException;
36+
import java.security.spec.KeySpec;
37+
import java.util.Base64;
38+
39+
import org.codehaus.plexus.components.secdispatcher.CipherException;
40+
41+
@Singleton
42+
@Named(AESGCMNoPadding.CIPHER_ALG)
43+
public class AESGCMNoPadding implements org.codehaus.plexus.components.secdispatcher.Cipher {
44+
public static final String CIPHER_ALG = "AES/GCM/NoPadding";
45+
46+
private static final int TAG_LENGTH_BIT = 128;
47+
private static final int IV_LENGTH_BYTE = 12;
48+
private static final int SALT_LENGTH_BYTE = 16;
49+
private static final int PBE_ITERATIONS = 310000;
50+
private static final int PBE_KEY_SIZE = SALT_LENGTH_BYTE * 16;
51+
private static final String KEY_FACTORY = "PBKDF2WithHmacSHA512";
52+
private static final String KEY_ALGORITHM = "AES";
53+
54+
@Override
55+
public String encrypt(String clearText, String password) throws CipherException {
56+
try {
57+
byte[] salt = getRandomNonce(SALT_LENGTH_BYTE);
58+
byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
59+
SecretKey secretKey = getAESKeyFromPassword(password.toCharArray(), salt);
60+
Cipher cipher = Cipher.getInstance(CIPHER_ALG);
61+
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
62+
byte[] cipherText = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8));
63+
byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
64+
.put(iv)
65+
.put(salt)
66+
.put(cipherText)
67+
.array();
68+
return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);
69+
} catch (Exception e) {
70+
throw new CipherException("Failed encrypting", e);
71+
}
72+
}
73+
74+
@Override
75+
public String decrypt(String encryptedText, String password) throws CipherException {
76+
try {
77+
byte[] material = Base64.getDecoder().decode(encryptedText.getBytes(StandardCharsets.UTF_8));
78+
ByteBuffer buffer = ByteBuffer.wrap(material);
79+
byte[] iv = new byte[IV_LENGTH_BYTE];
80+
buffer.get(iv);
81+
byte[] salt = new byte[SALT_LENGTH_BYTE];
82+
buffer.get(salt);
83+
byte[] cipherText = new byte[buffer.remaining()];
84+
buffer.get(cipherText);
85+
SecretKey secretKey = getAESKeyFromPassword(password.toCharArray(), salt);
86+
Cipher cipher = Cipher.getInstance(CIPHER_ALG);
87+
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
88+
byte[] plainText = cipher.doFinal(cipherText);
89+
return new String(plainText, StandardCharsets.UTF_8);
90+
} catch (Exception e) {
91+
throw new CipherException("Failed decrypting", e);
92+
}
93+
}
94+
95+
private static byte[] getRandomNonce(int numBytes) throws NoSuchAlgorithmException {
96+
byte[] nonce = new byte[numBytes];
97+
SecureRandom.getInstanceStrong().nextBytes(nonce);
98+
return nonce;
99+
}
100+
101+
private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
102+
throws NoSuchAlgorithmException, InvalidKeySpecException {
103+
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_FACTORY);
104+
KeySpec spec = new PBEKeySpec(password, salt, PBE_ITERATIONS, PBE_KEY_SIZE);
105+
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), KEY_ALGORITHM);
106+
}
107+
}

0 commit comments

Comments
 (0)