Skip to content

Commit 4279e27

Browse files
authored
Sonar codemod to Add a private constructor to hide the implicit public one. (#256)
Utility classes should not have public constructors
1 parent 25e81b8 commit 4279e27

File tree

7 files changed

+453
-0
lines changed

7 files changed

+453
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.codemodder.codemods;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import com.github.javaparser.ast.Modifier;
5+
import com.github.javaparser.ast.Node;
6+
import com.github.javaparser.ast.NodeList;
7+
import com.github.javaparser.ast.body.BodyDeclaration;
8+
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
9+
import com.github.javaparser.ast.body.ConstructorDeclaration;
10+
import com.github.javaparser.ast.expr.SimpleName;
11+
import io.codemodder.*;
12+
import io.codemodder.providers.sonar.ProvidedSonarScan;
13+
import io.codemodder.providers.sonar.RuleIssues;
14+
import io.codemodder.providers.sonar.SonarPluginJavaParserChanger;
15+
import io.codemodder.providers.sonar.api.Issue;
16+
import java.util.Optional;
17+
import javax.inject.Inject;
18+
19+
/** A codemod for setting a private constructor to hide implicit public constructor (Sonar) */
20+
@Codemod(
21+
id = "sonar:java/avoid-implicit-public-constructor-s1118",
22+
reviewGuidance = ReviewGuidance.MERGE_AFTER_REVIEW,
23+
executionPriority = CodemodExecutionPriority.HIGH)
24+
public final class AvoidImplicitPublicConstructorCodemod
25+
extends SonarPluginJavaParserChanger<SimpleName> {
26+
27+
@Inject
28+
public AvoidImplicitPublicConstructorCodemod(
29+
@ProvidedSonarScan(ruleId = "java:S1118") final RuleIssues issues) {
30+
super(issues, SimpleName.class, RegionNodeMatcher.MATCHES_START);
31+
}
32+
33+
@Override
34+
public boolean onIssueFound(
35+
final CodemodInvocationContext context,
36+
final CompilationUnit cu,
37+
final SimpleName simpleName,
38+
final Issue issue) {
39+
40+
final Optional<Node> classOptional = simpleName.getParentNode();
41+
42+
if (classOptional.isPresent()
43+
&& classOptional.get() instanceof ClassOrInterfaceDeclaration classNode) {
44+
// Create a constructor
45+
final ConstructorDeclaration constructor = new ConstructorDeclaration();
46+
constructor.setName(classNode.getName());
47+
constructor.setModifiers(Modifier.privateModifier().getKeyword());
48+
49+
// Add the constructor at the beginning of the class node's members
50+
NodeList<BodyDeclaration<?>> members = classNode.getMembers();
51+
members.add(0, constructor);
52+
53+
// Update the class node's members
54+
classNode.setMembers(members);
55+
return true; // Return true if the modification was successful
56+
}
57+
58+
return false;
59+
}
60+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
This change adds private constructors to utility classes. Utility classes are only meant to be accessed statically. Since they're not meant to be instantiated, we can use the Java's code visibility protections to hide the constructor and prevent unintended or malicious access.
2+
3+
Our changes look something like this:
4+
5+
```diff
6+
public class Utils {
7+
+ private Utils() {}
8+
...
9+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"summary" : "Set private constructor to hide implicit public constructor (Sonar)",
3+
"change" : "Set private constructor to hide implicit public constructor",
4+
"references" : [
5+
"https://rules.sonarsource.com/java/RSPEC-1118/"
6+
]
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.codemodder.codemods;
2+
3+
import io.codemodder.testutils.CodemodTestMixin;
4+
import io.codemodder.testutils.Metadata;
5+
6+
@Metadata(
7+
codemodType = AvoidImplicitPublicConstructorCodemod.class,
8+
testResourceDir = "avoid-implicit-public-constructor-s1118",
9+
renameTestFile = "src/main/java/org/owasp/webgoat/lessons/cryptography/CryptoUtil.java",
10+
dependencies = {})
11+
final class AvoidImplicitPublicConstructorCodemodTest implements CodemodTestMixin {}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package org.owasp.webgoat.lessons.cryptography;
2+
3+
import java.math.BigInteger;
4+
import java.nio.charset.Charset;
5+
import java.security.InvalidAlgorithmParameterException;
6+
import java.security.KeyFactory;
7+
import java.security.KeyPair;
8+
import java.security.KeyPairGenerator;
9+
import java.security.NoSuchAlgorithmException;
10+
import java.security.PrivateKey;
11+
import java.security.PublicKey;
12+
import java.security.SecureRandom;
13+
import java.security.Signature;
14+
import java.security.interfaces.RSAPublicKey;
15+
import java.security.spec.InvalidKeySpecException;
16+
import java.security.spec.PKCS8EncodedKeySpec;
17+
import java.security.spec.RSAKeyGenParameterSpec;
18+
import java.util.Base64;
19+
import javax.xml.bind.DatatypeConverter;
20+
import lombok.extern.slf4j.Slf4j;
21+
22+
@Slf4j
23+
public class CryptoUtil {
24+
25+
private CryptoUtil() {
26+
}
27+
28+
private static final BigInteger[] FERMAT_PRIMES = {
29+
BigInteger.valueOf(3),
30+
BigInteger.valueOf(5),
31+
BigInteger.valueOf(17),
32+
BigInteger.valueOf(257),
33+
BigInteger.valueOf(65537)
34+
};
35+
36+
public static KeyPair generateKeyPair()
37+
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
38+
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
39+
RSAKeyGenParameterSpec kpgSpec =
40+
new RSAKeyGenParameterSpec(
41+
2048, FERMAT_PRIMES[new SecureRandom().nextInt(FERMAT_PRIMES.length)]);
42+
keyPairGenerator.initialize(kpgSpec);
43+
// keyPairGenerator.initialize(2048);
44+
return keyPairGenerator.generateKeyPair();
45+
}
46+
47+
public static String getPrivateKeyInPEM(KeyPair keyPair) {
48+
String encodedString = "-----BEGIN PRIVATE KEY-----\n";
49+
encodedString =
50+
encodedString
51+
+ new String(
52+
Base64.getEncoder().encode(keyPair.getPrivate().getEncoded()),
53+
Charset.forName("UTF-8"))
54+
+ "\n";
55+
encodedString = encodedString + "-----END PRIVATE KEY-----\n";
56+
return encodedString;
57+
}
58+
59+
public static String signMessage(String message, PrivateKey privateKey) {
60+
61+
log.debug("start signMessage");
62+
String signature = null;
63+
64+
try {
65+
// Initiate signature verification
66+
Signature instance = Signature.getInstance("SHA256withRSA");
67+
instance.initSign(privateKey);
68+
instance.update(message.getBytes("UTF-8"));
69+
70+
// actual verification against signature
71+
signature = new String(Base64.getEncoder().encode(instance.sign()), Charset.forName("UTF-8"));
72+
73+
log.info("signe the signature with result: {}", signature);
74+
} catch (Exception e) {
75+
log.error("Signature signing failed", e);
76+
}
77+
78+
log.debug("end signMessage");
79+
return signature;
80+
}
81+
82+
public static boolean verifyMessage(
83+
String message, String base64EncSignature, PublicKey publicKey) {
84+
85+
log.debug("start verifyMessage");
86+
boolean result = false;
87+
88+
try {
89+
90+
base64EncSignature = base64EncSignature.replace("\r", "").replace("\n", "").replace(" ", "");
91+
// get raw signature from base64 encrypted string in header
92+
byte[] decodedSignature = Base64.getDecoder().decode(base64EncSignature);
93+
94+
// Initiate signature verification
95+
Signature instance = Signature.getInstance("SHA256withRSA");
96+
instance.initVerify(publicKey);
97+
instance.update(message.getBytes("UTF-8"));
98+
99+
// actual verification against signature
100+
result = instance.verify(decodedSignature);
101+
102+
log.info("Verified the signature with result: {}", result);
103+
} catch (Exception e) {
104+
log.error("Signature verification failed", e);
105+
}
106+
107+
log.debug("end verifyMessage");
108+
return result;
109+
}
110+
111+
public static boolean verifyAssignment(String modulus, String signature, PublicKey publicKey) {
112+
113+
/* first check if the signature is correct, i.e. right private key and right hash */
114+
boolean result = false;
115+
116+
if (modulus != null && signature != null) {
117+
result = verifyMessage(modulus, signature, publicKey);
118+
119+
/*
120+
* next check if the submitted modulus is the correct modulus of the public key
121+
*/
122+
RSAPublicKey rsaPubKey = (RSAPublicKey) publicKey;
123+
if (modulus.length() == 512) {
124+
modulus = "00".concat(modulus);
125+
}
126+
result =
127+
result
128+
&& (DatatypeConverter.printHexBinary(rsaPubKey.getModulus().toByteArray())
129+
.equals(modulus.toUpperCase()));
130+
}
131+
return result;
132+
}
133+
134+
public static PrivateKey getPrivateKeyFromPEM(String privateKeyPem)
135+
throws NoSuchAlgorithmException, InvalidKeySpecException {
136+
privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "");
137+
privateKeyPem = privateKeyPem.replace("-----END PRIVATE KEY-----", "");
138+
privateKeyPem = privateKeyPem.replace("\n", "").replace("\r", "");
139+
140+
byte[] decoded = Base64.getDecoder().decode(privateKeyPem);
141+
142+
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
143+
KeyFactory kf = KeyFactory.getInstance("RSA");
144+
return kf.generatePrivate(spec);
145+
}
146+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package org.owasp.webgoat.lessons.cryptography;
2+
3+
import java.math.BigInteger;
4+
import java.nio.charset.Charset;
5+
import java.security.InvalidAlgorithmParameterException;
6+
import java.security.KeyFactory;
7+
import java.security.KeyPair;
8+
import java.security.KeyPairGenerator;
9+
import java.security.NoSuchAlgorithmException;
10+
import java.security.PrivateKey;
11+
import java.security.PublicKey;
12+
import java.security.SecureRandom;
13+
import java.security.Signature;
14+
import java.security.interfaces.RSAPublicKey;
15+
import java.security.spec.InvalidKeySpecException;
16+
import java.security.spec.PKCS8EncodedKeySpec;
17+
import java.security.spec.RSAKeyGenParameterSpec;
18+
import java.util.Base64;
19+
import javax.xml.bind.DatatypeConverter;
20+
import lombok.extern.slf4j.Slf4j;
21+
22+
@Slf4j
23+
public class CryptoUtil {
24+
25+
private static final BigInteger[] FERMAT_PRIMES = {
26+
BigInteger.valueOf(3),
27+
BigInteger.valueOf(5),
28+
BigInteger.valueOf(17),
29+
BigInteger.valueOf(257),
30+
BigInteger.valueOf(65537)
31+
};
32+
33+
public static KeyPair generateKeyPair()
34+
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
35+
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
36+
RSAKeyGenParameterSpec kpgSpec =
37+
new RSAKeyGenParameterSpec(
38+
2048, FERMAT_PRIMES[new SecureRandom().nextInt(FERMAT_PRIMES.length)]);
39+
keyPairGenerator.initialize(kpgSpec);
40+
// keyPairGenerator.initialize(2048);
41+
return keyPairGenerator.generateKeyPair();
42+
}
43+
44+
public static String getPrivateKeyInPEM(KeyPair keyPair) {
45+
String encodedString = "-----BEGIN PRIVATE KEY-----\n";
46+
encodedString =
47+
encodedString
48+
+ new String(
49+
Base64.getEncoder().encode(keyPair.getPrivate().getEncoded()),
50+
Charset.forName("UTF-8"))
51+
+ "\n";
52+
encodedString = encodedString + "-----END PRIVATE KEY-----\n";
53+
return encodedString;
54+
}
55+
56+
public static String signMessage(String message, PrivateKey privateKey) {
57+
58+
log.debug("start signMessage");
59+
String signature = null;
60+
61+
try {
62+
// Initiate signature verification
63+
Signature instance = Signature.getInstance("SHA256withRSA");
64+
instance.initSign(privateKey);
65+
instance.update(message.getBytes("UTF-8"));
66+
67+
// actual verification against signature
68+
signature = new String(Base64.getEncoder().encode(instance.sign()), Charset.forName("UTF-8"));
69+
70+
log.info("signe the signature with result: {}", signature);
71+
} catch (Exception e) {
72+
log.error("Signature signing failed", e);
73+
}
74+
75+
log.debug("end signMessage");
76+
return signature;
77+
}
78+
79+
public static boolean verifyMessage(
80+
String message, String base64EncSignature, PublicKey publicKey) {
81+
82+
log.debug("start verifyMessage");
83+
boolean result = false;
84+
85+
try {
86+
87+
base64EncSignature = base64EncSignature.replace("\r", "").replace("\n", "").replace(" ", "");
88+
// get raw signature from base64 encrypted string in header
89+
byte[] decodedSignature = Base64.getDecoder().decode(base64EncSignature);
90+
91+
// Initiate signature verification
92+
Signature instance = Signature.getInstance("SHA256withRSA");
93+
instance.initVerify(publicKey);
94+
instance.update(message.getBytes("UTF-8"));
95+
96+
// actual verification against signature
97+
result = instance.verify(decodedSignature);
98+
99+
log.info("Verified the signature with result: {}", result);
100+
} catch (Exception e) {
101+
log.error("Signature verification failed", e);
102+
}
103+
104+
log.debug("end verifyMessage");
105+
return result;
106+
}
107+
108+
public static boolean verifyAssignment(String modulus, String signature, PublicKey publicKey) {
109+
110+
/* first check if the signature is correct, i.e. right private key and right hash */
111+
boolean result = false;
112+
113+
if (modulus != null && signature != null) {
114+
result = verifyMessage(modulus, signature, publicKey);
115+
116+
/*
117+
* next check if the submitted modulus is the correct modulus of the public key
118+
*/
119+
RSAPublicKey rsaPubKey = (RSAPublicKey) publicKey;
120+
if (modulus.length() == 512) {
121+
modulus = "00".concat(modulus);
122+
}
123+
result =
124+
result
125+
&& (DatatypeConverter.printHexBinary(rsaPubKey.getModulus().toByteArray())
126+
.equals(modulus.toUpperCase()));
127+
}
128+
return result;
129+
}
130+
131+
public static PrivateKey getPrivateKeyFromPEM(String privateKeyPem)
132+
throws NoSuchAlgorithmException, InvalidKeySpecException {
133+
privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "");
134+
privateKeyPem = privateKeyPem.replace("-----END PRIVATE KEY-----", "");
135+
privateKeyPem = privateKeyPem.replace("\n", "").replace("\r", "");
136+
137+
byte[] decoded = Base64.getDecoder().decode(privateKeyPem);
138+
139+
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
140+
KeyFactory kf = KeyFactory.getInstance("RSA");
141+
return kf.generatePrivate(spec);
142+
}
143+
}

0 commit comments

Comments
 (0)