|
42 | 42 |
|
43 | 43 | @Rule(key = "S3329") |
44 | 44 | public class CipherBlockChainingCheck extends AbstractMethodDetection { |
45 | | - private final SecureByteArrayGeneratorDetector secureByteArrayGeneratorDetector = new SecureByteArrayGeneratorDetector(); |
| 45 | + // A note on detecting secure IV generation across procedures: |
| 46 | + // |
| 47 | + // This check is able to avoid FPs when secure IVs are generated in separate methods, with quite some limitations, for example |
| 48 | + // * nested calls can not be handled. |
| 49 | + // * calls to methods outside the current surrounding top-level class can not be handled. |
| 50 | + // * the flow of (secure) arguments can not be traced |
| 51 | + // * ... |
| 52 | + // |
| 53 | + // In other words, there will be FPs when secure IV generating methods are used that are defined across (top-level) classes or files. |
| 54 | + // This could be handled by doing a pass of SecureByteArrayFactoryFinder across all classes and files first, before performing |
| 55 | + // CipherBlockChainingCheck. |
| 56 | + // |
| 57 | + // However, this is likely not worth the additional complexity. |
| 58 | + // Instead, if we need to be more precise for S3329 across files and methods, we should switch to an analysis that is better suited for |
| 59 | + // following dataflows (either DBD-based, or a taint analysis). |
| 60 | + // These analyses are already cross-procedural and cross-file, can follow chains of calls, etc. |
| 61 | + // |
| 62 | + // Furthermore, we can not simply mute this check as soon as a call is involved in generating IVs because then we will lose a lot of the |
| 63 | + // cases that are already detected by previous iterations of this check. |
| 64 | + private final SecureByteArrayFactoryFinder secureByteArrayFactoryFinder = new SecureByteArrayFactoryFinder(); |
| 65 | + private final SecureByteArrayGeneratorDetector secureByteArrayGeneratorDetector = new SecureByteArrayGeneratorDetector() |
| 66 | + .countParametersAsDynamic() |
| 67 | + .detectCustomFactories(secureByteArrayFactoryFinder); |
46 | 68 |
|
47 | 69 | private static final MethodMatchers SECURE_RANDOM_GENERATE_SEED = MethodMatchers.create() |
48 | 70 | .ofTypes("java.security.SecureRandom") |
@@ -209,16 +231,49 @@ private static Symbol ivSymbol(NewClassTree newClassTree) { |
209 | 231 | } |
210 | 232 |
|
211 | 233 | private static class SecureByteArrayGeneratorDetector { |
| 234 | + private @Nullable SecureByteArrayFactoryFinder secureByteArrayFactoryFinder = null; |
| 235 | + private boolean shouldCountParametersAsDynamic = false; |
| 236 | + |
| 237 | + /** |
| 238 | + * When called, this detector will treat all byte arrays produced by method calls to secure IV-generating functions as secure. |
| 239 | + * It uses the given {@link SecureByteArrayFactoryFinder} to judge whether a method call is secure. |
| 240 | + */ |
| 241 | + public SecureByteArrayGeneratorDetector detectCustomFactories(SecureByteArrayFactoryFinder secureByteArrayFactoryFinder) { |
| 242 | + this.secureByteArrayFactoryFinder = secureByteArrayFactoryFinder; |
| 243 | + return this; |
| 244 | + } |
| 245 | + |
| 246 | + /** |
| 247 | + * If called, this detector will treat parameters as secure byte arrays. |
| 248 | + * This is optional, since we can not trace arguments across methods and hence can not always assume them to be secure. |
| 249 | + * However, for the methods where we see parameters being directly passed into IvParameterSpec, we do want to treat them as secure |
| 250 | + * to match the behaviour of previous iterations of CipherBlockChainingCheck |
| 251 | + */ |
| 252 | + public SecureByteArrayGeneratorDetector countParametersAsDynamic() { |
| 253 | + this.shouldCountParametersAsDynamic = true; |
| 254 | + return this; |
| 255 | + } |
| 256 | + |
212 | 257 | public boolean isDynamicallyGenerated(ExpressionTree tree) { |
213 | | - if (tree instanceof IdentifierTree identifierTree) { |
| 258 | + if (shouldCountParametersAsDynamic && tree instanceof IdentifierTree identifierTree) { |
214 | 259 | Symbol symbol = identifierTree.symbol(); |
215 | 260 | if (symbol.isParameter()) { |
216 | 261 | return true; |
217 | 262 | } |
218 | 263 | } |
219 | 264 |
|
220 | 265 | return findConstructingMethods(tree) |
221 | | - .anyMatch(SECURE_RANDOM_GENERATE_SEED::matches); |
| 266 | + .anyMatch(methodInvocationTree -> { |
| 267 | + if (SECURE_RANDOM_GENERATE_SEED.matches(methodInvocationTree)) { |
| 268 | + return true; |
| 269 | + } |
| 270 | + |
| 271 | + if (secureByteArrayFactoryFinder == null) { |
| 272 | + return false; |
| 273 | + } |
| 274 | + |
| 275 | + return secureByteArrayFactoryFinder.producesSecureBytesArray(methodInvocationTree); |
| 276 | + }); |
222 | 277 | } |
223 | 278 |
|
224 | 279 | private static Stream<MethodInvocationTree> findConstructingMethods(ExpressionTree expressionTree) { |
@@ -257,6 +312,10 @@ private static Stream<MethodInvocationTree> findConstructingMethods(ExpressionTr |
257 | 312 | private static class SecureByteArrayFactoryFinder extends BaseTreeVisitor { |
258 | 313 | private @Nullable MethodTree currentMethodTree = null; |
259 | 314 | private final Set<String> secureByteArrayFactories = new HashSet<>(); |
| 315 | + // Note, that we do not call detectCustomFactories() on secureByteArrayGeneratorDetector. |
| 316 | + // Meaning, that we will not trace whether methods called inside IV factories are secure. |
| 317 | + // In other words, CipherBlockChainingCheck supports a call-depth of at most 1. |
| 318 | + // See also the note on cross-procedural FPs in the surrounding class. |
260 | 319 | private final SecureByteArrayGeneratorDetector secureByteArrayGeneratorDetector = new SecureByteArrayGeneratorDetector(); |
261 | 320 |
|
262 | 321 | public boolean producesSecureBytesArray(MethodInvocationTree methodInvocation) { |
|
0 commit comments