GPG signature verification for databases

TODO: inform user if verification fails or key is unavailable

Signed-off-by: Tad <tad@spotco.us>
This commit is contained in:
Tad 2023-04-16 12:31:12 -04:00
parent 76d06b504f
commit 44d2d1c905
No known key found for this signature in database
GPG key ID: B286E9F57A07424B
17 changed files with 1326 additions and 628 deletions

2
.idea/compiler.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>

2
.idea/misc.xml generated
View file

@ -54,7 +54,7 @@
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View file

@ -43,7 +43,6 @@ Geplante Updates
- Automatische Datenbank-Updates - Automatische Datenbank-Updates
- Automatische Datenbankerstellung - Automatische Datenbankerstellung
- Client-seitige Datenbank-Generierung - Client-seitige Datenbank-Generierung
- Überprüfung der Datenbanksignatur
- Datenbank-Sanity-Checks - Datenbank-Sanity-Checks
- Prüfung - Prüfung
- Bessere GUI - Bessere GUI

View file

@ -45,7 +45,6 @@ Planned Updates
- Automatic database updates - Automatic database updates
- Automatic database generation - Automatic database generation
- Client side database generation - Client side database generation
- Database signature verification
- Database sanity checks - Database sanity checks
- Testing - Testing
- Better GUI - Better GUI
@ -66,7 +65,8 @@ Credits
- Nex (@botherder) for extra databases (CC BY-SA 4.0) - Nex (@botherder) for extra databases (CC BY-SA 4.0)
- Amnesty International for extra databases (CC BY 2.0) - Amnesty International for extra databases (CC BY 2.0)
- Echap for extra databases (CC BY 4.0) - Echap for extra databases (CC BY 4.0)
- RecursiveFileObserver.java (GPLv3): Daniel Gultsch, ownCloud Inc., Bartek Przybylski - RecursiveFileObserver.java (GPL-3.0-or-later): Daniel Gultsch, ownCloud Inc., Bartek Przybylski
- GPGDetachedSignatureVerifier.java (GPL-2.0-or-later): Federico Fissore, Arduino LLC
- Petra Mirelli for the German/Spanish/Italian translations, the app banner/feature graphic, and various tweaks. - Petra Mirelli for the German/Spanish/Italian translations, the app banner/feature graphic, and various tweaks.
- Jean-Luc Tibaux and Petra Mirelli for the French translations. - Jean-Luc Tibaux and Petra Mirelli for the French translations.
- @srccrow for the Italian translations. - @srccrow for the Italian translations.

View file

@ -43,7 +43,6 @@ Planowane aktualizacje
- Automatyczne aktualizacje baz danych - Automatyczne aktualizacje baz danych
- Automatyczne generowanie baz danych - Automatyczne generowanie baz danych
- Generowanie baz danych przez klienta - Generowanie baz danych przez klienta
- Weryfikacja podpisów baz danych
- Kontrola poprawności baz danych - Kontrola poprawności baz danych
- Testy - Testy
- Lepsze GUI - Lepsze GUI

View file

@ -6,8 +6,8 @@ android {
applicationId "us.spotco.malwarescanner" applicationId "us.spotco.malwarescanner"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 32 targetSdkVersion 32
versionCode 96 versionCode 97
versionName "2.30" versionName "2.31"
resConfigs 'en', 'af', 'de', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru' resConfigs 'en', 'af', 'de', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru'
} }
buildTypes { buildTypes {
@ -29,5 +29,6 @@ android {
} }
dependencies { dependencies {
implementation 'commons-io:commons-io:2.11.0'
implementation 'org.bouncycastle:bcpg-jdk15to18:1.73'
} }

View file

@ -20,6 +20,7 @@ package us.spotco.malwarescanner;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -75,9 +76,12 @@ class Database {
if (!Utils.getDatabaseURL(context).equals(Utils.DATABASE_URL_DEFAULT)) { if (!Utils.getDatabaseURL(context).equals(Utils.DATABASE_URL_DEFAULT)) {
log.append(context.getString(R.string.main_database_override, Utils.getDatabaseURL(context)) + "\n"); log.append(context.getString(R.string.main_database_override, Utils.getDatabaseURL(context)) + "\n");
} }
for (SignatureDatabase signatureDatabase : signatureDatabases) {
boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false); boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false);
new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, Utils.getDatabaseURL(context) + "gpg.key", databasePath + "/gpg.key", Utils.getDatabaseURL(context));
for (SignatureDatabase signatureDatabase : signatureDatabases) {
new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl(), databasePath + "/" + signatureDatabase.getName(), signatureDatabase.getBaseUrl()); new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl(), databasePath + "/" + signatureDatabase.getName(), signatureDatabase.getBaseUrl());
new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl() + ".sig", databasePath + "/" + signatureDatabase.getName() + ".sig", signatureDatabase.getBaseUrl());
} }
} }
@ -126,10 +130,16 @@ class Database {
signaturesSHA1.clear(); signaturesSHA1.clear();
signaturesSHA256.clear(); signaturesSHA256.clear();
System.gc(); System.gc();
File publicKey = new File(databasePath + "/gpg.key");
GPGDetachedSignatureVerifier verifier = new GPGDetachedSignatureVerifier(Utils.getSigningKey(context));
for (SignatureDatabase database : signatureDatabases) { for (SignatureDatabase database : signatureDatabases) {
File databaseLocation = new File(databasePath + "/" + database.getName()); File databaseLocation = new File(databasePath + "/" + database.getName());
if (databaseLocation.exists()) { File databaseSigLocation = new File(databasePath + "/" + database.getName() + ".sig");
if (publicKey.exists() && databaseLocation.exists() && databaseSigLocation.exists()) {
try { try {
boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey);
if (validated) {
Log.d("Hypatia", "Successfully validated database");
BufferedReader reader; BufferedReader reader;
if (databaseLocation.getName().endsWith(".gz")) { if (databaseLocation.getName().endsWith(".gz")) {
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation)))); reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation))));
@ -159,6 +169,9 @@ class Database {
} }
} }
reader.close(); reader.close();
} else {
Log.w("Hypatia", "Failed to validate database");
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -0,0 +1,133 @@
/*
* Source: https://github.com/arduino/Arduino/blob/master/arduino-core/src/cc/arduino/contributions/GPGDetachedSignatureVerifier.java
* This file is part of Arduino.
*
* Copyright 2015 Arduino LLC (http://www.arduino.cc/)
*
* Arduino is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* As a special exception, you may use this file as part of a free software
* library without restriction. Specifically, if other files instantiate
* templates or use macros or inline functions from this file, or you compile
* this file and link it with other files to produce an executable, this
* file does not by itself cause the resulting executable to be covered by
* the GNU General Public License. This exception does not however
* invalidate any other reasons why the executable file might be covered by
* the GNU General Public License.
*/
package us.spotco.malwarescanner;
import android.util.Log;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
public class GPGDetachedSignatureVerifier {
private final String keyId;
public GPGDetachedSignatureVerifier() {
this(Utils.SIGNING_KEY_DEFAULT);
}
public GPGDetachedSignatureVerifier(String keyId) {
this.keyId = keyId;
}
public boolean verify(File signedFile, File signature, File publicKey) throws IOException {
FileInputStream signatureInputStream = null;
FileInputStream signedFileInputStream = null;
try {
signatureInputStream = new FileInputStream(signature);
PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(signatureInputStream, new BcKeyFingerprintCalculator());
Object nextObject;
try {
nextObject = pgpObjectFactory.nextObject();
if (!(nextObject instanceof PGPSignatureList)) {
return false;
}
} catch (IOException e) {
return false;
}
PGPSignatureList pgpSignatureList = (PGPSignatureList) nextObject;
assert pgpSignatureList.size() == 1;
PGPSignature pgpSignature = pgpSignatureList.get(0);
PGPPublicKey pgpPublicKey = readPublicKey(publicKey, keyId);
pgpSignature.init(new BcPGPContentVerifierBuilderProvider(), pgpPublicKey);
signedFileInputStream = new FileInputStream(signedFile);
pgpSignature.update(IOUtils.toByteArray(signedFileInputStream));
return pgpSignature.verify();
} catch (PGPException e) {
throw new IOException(e);
} finally {
if (signatureInputStream != null) {
signatureInputStream.close();
}
if (signedFileInputStream != null) {
signedFileInputStream.close();
}
}
}
private PGPPublicKey readPublicKey(File file, String id) throws IOException, PGPException {
try (InputStream keyIn = new BufferedInputStream(new FileInputStream(file))) {
return readPublicKey(keyIn, id);
}
}
private PGPPublicKey readPublicKey(InputStream input, String id) throws IOException, PGPException {
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input), new BcKeyFingerprintCalculator());
Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = keyIter.next();
if (Long.toHexString(key.getKeyID()).toUpperCase().endsWith(id)) {
return key;
} else {
Log.d("Hypatia", "readPublicKey: No match found, have key: " + Long.toHexString(key.getKeyID()).toUpperCase());
}
}
}
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
}

View file

@ -24,7 +24,6 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
@ -213,30 +212,38 @@ public class MainActivity extends Activity {
selectDatabases(); selectDatabases();
break; break;
case R.id.mnuDatabaseServer: case R.id.mnuDatabaseServer:
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.lblDatabaseServer)); builderServerOverride.setTitle(getString(R.string.lblDatabaseServer));
final EditText input = new EditText(this); final EditText inputServerOverride = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT); inputServerOverride.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(Utils.getDatabaseURL(this)); inputServerOverride.setText(Utils.getDatabaseURL(this));
builder.setView(input); builderServerOverride.setView(inputServerOverride);
builder.setPositiveButton(getString(R.string.lblOverride), new DialogInterface.OnClickListener() { builderServerOverride.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> {
@Override String newServer = inputServerOverride.getText().toString();
public void onClick(DialogInterface dialog, int which) {
String newServer = input.getText().toString();
if (!newServer.endsWith("/")) { if (!newServer.endsWith("/")) {
newServer += "/"; newServer += "/";
} }
prefs.edit().putString("DATABASE_SERVER", newServer).apply(); prefs.edit().putString("DATABASE_SERVER", newServer).apply();
}
}); });
builder.setNegativeButton(getString(R.string.lblReset), new DialogInterface.OnClickListener() { builderServerOverride.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply(); prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply();
dialog.cancel(); dialog.cancel();
}
}); });
builder.show(); builderServerOverride.show();
break;
case R.id.mnuSigningKey:
AlertDialog.Builder builderKey = new AlertDialog.Builder(this);
builderKey.setTitle(getString(R.string.lblSigningKey));
final EditText inputKey = new EditText(this);
inputKey.setInputType(InputType.TYPE_CLASS_TEXT);
inputKey.setText(Utils.getSigningKey(this));
builderKey.setView(inputKey);
builderKey.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> prefs.edit().putString("SIGNING_KEY", inputKey.getText().toString()).apply());
builderKey.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> {
prefs.edit().putString("SIGNING_KEY", Utils.SIGNING_KEY_DEFAULT).apply();
dialog.cancel();
});
builderKey.show();
break; break;
case R.id.toggleRealtime: case R.id.toggleRealtime:
if (malwareScanner.running) { if (malwareScanner.running) {
@ -319,7 +326,7 @@ public class MainActivity extends Activity {
} }
private void updateDatabase() { private void updateDatabase() {
new Database((TextView) findViewById(R.id.txtLogOutput)); new Database(findViewById(R.id.txtLogOutput));
Database.updateDatabase(this, Database.signatureDatabases); Database.updateDatabase(this, Database.signatureDatabases);
if (Database.isDatabaseLoaded()) { if (Database.isDatabaseLoaded()) {
Utils.getThreadPoolExecutor().execute(() -> Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases)); Utils.getThreadPoolExecutor().execute(() -> Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases));

View file

@ -35,6 +35,8 @@ class SignatureDatabase {
return name; return name;
} }
public final String getUrl() { return baseURL + name; } public final String getUrl() {
return baseURL + name;
}
} }

View file

@ -38,6 +38,7 @@ class Utils {
public final static int MAX_SCAN_SIZE = (1000 * 1000) * 80; //80MB public final static int MAX_SCAN_SIZE = (1000 * 1000) * 80; //80MB
public final static int MAX_SCAN_SIZE_REALTIME = MAX_SCAN_SIZE / 2; //40MB public final static int MAX_SCAN_SIZE_REALTIME = MAX_SCAN_SIZE / 2; //40MB
public final static String DATABASE_URL_DEFAULT = "https://divested.dev/MalwareScannerSignatures/"; public final static String DATABASE_URL_DEFAULT = "https://divested.dev/MalwareScannerSignatures/";
public final static String SIGNING_KEY_DEFAULT = "BADFCABDDBF5B694";
public final static int MAX_HASH_LENGTH = 12; public final static int MAX_HASH_LENGTH = 12;
@ -104,6 +105,11 @@ class Utils {
return prefs.getString("DATABASE_SERVER", DATABASE_URL_DEFAULT); return prefs.getString("DATABASE_SERVER", DATABASE_URL_DEFAULT);
} }
public static String getSigningKey(Context context) {
SharedPreferences prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
return prefs.getString("SIGNING_KEY", SIGNING_KEY_DEFAULT);
}
public static void considerStartService(Context context) { public static void considerStartService(Context context) {
if (!Utils.isServiceRunning(MalwareScannerService.class, context)) { if (!Utils.isServiceRunning(MalwareScannerService.class, context)) {
SharedPreferences prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);

View file

@ -20,6 +20,9 @@
<item <item
android:id="@+id/mnuDatabaseServer" android:id="@+id/mnuDatabaseServer"
android:title="@string/lblDatabaseServer" /> android:title="@string/lblDatabaseServer" />
<item
android:id="@+id/mnuSigningKey"
android:title="@string/lblSigningKey" />
<item <item
android:id="@+id/toggleRealtime" android:id="@+id/toggleRealtime"
android:title="@string/lblRealtimeScannerToggle" android:title="@string/lblRealtimeScannerToggle"

View file

@ -61,4 +61,5 @@
<string name="db_desc_size_large">Large</string> <string name="db_desc_size_large">Large</string>
<string name="scan_control">Scan Control</string> <string name="scan_control">Scan Control</string>
<string name="lblScanRunning">Skipping action, a scan is running!</string> <string name="lblScanRunning">Skipping action, a scan is running!</string>
<string name="lblSigningKey">Database signing key</string>
</resources> </resources>

View file

@ -0,0 +1,4 @@
* Databases are now verified using GPG signatures
* Users must use "Update databases" before use after installing this update
* Databases that are not signed or fail to verify will be ignored
* A custom database key is allowed to maintain support for third party database repos

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -60,6 +60,7 @@
<trusted-key id="aa70c7c433d501636392ec02153e7a3c2b4e5118" group="org.eclipse.ee4j" name="project"/> <trusted-key id="aa70c7c433d501636392ec02153e7a3c2b4e5118" group="org.eclipse.ee4j" name="project"/>
<trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/> <trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/>
<trusted-key id="b02335aa54ccf21e52bbf9abd9c565aa72ba2fdd" group="io.grpc"/> <trusted-key id="b02335aa54ccf21e52bbf9abd9c565aa72ba2fdd" group="io.grpc"/>
<trusted-key id="b6e73d84ea4fcc47166087253faad2cd5ecbb314" group="org.apache.commons" name="commons-parent"/>
<trusted-key id="b801e2f8ef035068ec1139cc29579f18fa8fd93b" group="com.google.j2objc" name="j2objc-annotations" version="1.3"/> <trusted-key id="b801e2f8ef035068ec1139cc29579f18fa8fd93b" group="com.google.j2objc" name="j2objc-annotations" version="1.3"/>
<trusted-key id="bcc135fc7ed8214f823d73e97fe9900f412d622e" group="com.google.flatbuffers" name="flatbuffers-java" version="1.12.0"/> <trusted-key id="bcc135fc7ed8214f823d73e97fe9900f412d622e" group="com.google.flatbuffers" name="flatbuffers-java" version="1.12.0"/>
<trusted-key id="bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae" group="com.google.guava"/> <trusted-key id="bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae" group="com.google.guava"/>
@ -72,6 +73,7 @@
<trusted-key id="ee0ca873074092f806f59b65d364abaa39a47320" group="com.google.errorprone"/> <trusted-key id="ee0ca873074092f806f59b65d364abaa39a47320" group="com.google.errorprone"/>
<trusted-key id="f254b35617dc255d9344bcfa873a8e86b4372146" group="org.codehaus.mojo"/> <trusted-key id="f254b35617dc255d9344bcfa873a8e86b4372146" group="org.codehaus.mojo"/>
<trusted-key id="f3184bcd55f4d016e30d4c9bf42e87f9665015c9" group="org.jsoup" name="jsoup" version="1.13.1"/> <trusted-key id="f3184bcd55f4d016e30d4c9bf42e87f9665015c9" group="org.jsoup" name="jsoup" version="1.13.1"/>
<trusted-key id="fa77dcfef2ee6eb2debedd2c012579464d01c06a" group="org.apache" name="apache"/>
<trusted-key id="fa7929f83ad44c4590f6cc6815c71c0a4e0b8edd" group="net.java.dev.jna"/> <trusted-key id="fa7929f83ad44c4590f6cc6815c71c0a4e0b8edd" group="net.java.dev.jna"/>
<trusted-key id="fc411cd3cb7dcb0abc9801058118b3bcdb1a5000" group="jakarta.xml.bind"/> <trusted-key id="fc411cd3cb7dcb0abc9801058118b3bcdb1a5000" group="jakarta.xml.bind"/>
</trusted-keys> </trusted-keys>
@ -1155,6 +1157,11 @@
<sha512 value="c675dc20d3d192a4193d651a6fa3ddac3bfe97844be536146ca6e78c29c1559b06fe9495be39d4dbf606b8a2cb0720391a6fe53f37d628949659c3224e4eaa8d" origin="Generated by Gradle"/> <sha512 value="c675dc20d3d192a4193d651a6fa3ddac3bfe97844be536146ca6e78c29c1559b06fe9495be39d4dbf606b8a2cb0720391a6fe53f37d628949659c3224e4eaa8d" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="org.junit" name="junit-bom" version="5.7.2">
<artifact name="junit-bom-5.7.2.pom">
<pgp value="ff6e2c001948c5f2f38b0cc385911f425ec61b51"/>
</artifact>
</component>
<component group="org.ow2" name="ow2" version="1.5"> <component group="org.ow2" name="ow2" version="1.5">
<artifact name="ow2-1.5.pom"> <artifact name="ow2-1.5.pom">
<sha512 value="5445748e294cf9f23fe8f1e18e2ebb7108800d40f81a4566a73f9434fe21d2058d05acf3bc4d15f629151df47c42bcf948de3bba0b6a37982dfc3a8f1baf244d" origin="Generated by Gradle because artifact wasn't signed"/> <sha512 value="5445748e294cf9f23fe8f1e18e2ebb7108800d40f81a4566a73f9434fe21d2058d05acf3bc4d15f629151df47c42bcf948de3bba0b6a37982dfc3a8f1baf244d" origin="Generated by Gradle because artifact wasn't signed"/>