Skip to content

Commit

Permalink
Add midpoint.jar signing
Browse files Browse the repository at this point in the history
This is to distinguish between official and unofficial builds.
The checking is currently very primitive, though.
  • Loading branch information
mederly committed Jan 23, 2024
1 parent c1ac6c6 commit e46c8d1
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2010-2024 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.web.boot;

import com.evolveum.midpoint.repo.common.subscription.JarSignatureHolder;
import com.evolveum.midpoint.repo.common.subscription.JarSignatureHolder.Validity;
import com.evolveum.midpoint.util.logging.LoggingUtils;

import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;

import org.jetbrains.annotations.NotNull;
import org.springframework.boot.system.ApplicationHome;

import java.io.IOException;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/** Checks the signature of `midpoint.jar` file. Currently, it uses enclosed certificate file to check the authenticity. */
public class MidPointJarSignatureChecker {

private static final Trace LOGGER = TraceManager.getTrace(MidPointJarSignatureChecker.class);

public static void setupJarSignature() {
JarSignatureHolder.setJarSignatureValidity(
checkJarSignature());
}

private static @NotNull Validity checkJarSignature() {
try {
var home = new ApplicationHome(MidPointSpringApplication.class);
var source = home.getSource();
if (!source.isFile() || !source.getName().toLowerCase().endsWith(".jar")) {
LOGGER.info("Application is not running from a JAR file, skipping JAR signature check: {}", source);
return Validity.NOT_APPLICABLE;
}
try (JarFile jar = new JarFile(source)) {
return verify(jar);
}
} catch (Exception e) {
LoggingUtils.logException(LOGGER, "Couldn't verify JAR file signature", e);
return Validity.ERROR;
}
}

private static Validity verify(JarFile jar) throws IOException, CertificateException {

X509Certificate ourCertificate;
try (var cert = MidPointJarSignatureChecker.class.getClassLoader().getResourceAsStream("jar-signing.cer")) {
if (cert == null) {
LOGGER.info("No jar signing certificate found");
return Validity.ERROR;
}
var certFactory = CertificateFactory.getInstance("X.509");
ourCertificate = (X509Certificate) certFactory.generateCertificate(cert);
}

int checkedFiles = 0;
byte[] scratchBuffer = new byte[8192];
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.isDirectory()) {
continue;
}
LOGGER.trace("Checking JAR entry {}", entry);

// First, we need to check the signature (~ checksum). This is done simply by reading the entry.
try (InputStream is = jar.getInputStream(entry)) {
while (is.read(scratchBuffer, 0, scratchBuffer.length) != -1) {
// If there is a signature, the reading will fail if it is not matching.
}
} catch (SecurityException e) {
LOGGER.info("JAR signature verification failed for entry {}", entry, e);
return Validity.INVALID;
}

// The signature is OK. Now let's check the certificate.
Certificate[] entryCertificates = entry.getCertificates();
if (entryCertificates == null || entryCertificates.length == 0) {
if (!entry.getName().startsWith("META-INF/")) {
LOGGER.info("Unsigned file in JAR (only those in META-INF are allowed to be unsigned): {}", entry);
return Validity.INVALID;
}
} else {
boolean found = false;
for (Certificate entryCertificate : entryCertificates) {
// Very crude way. We ignore the certificate chaining here. We just look for the particular certificate.
if (entryCertificate.equals(ourCertificate)) {
found = true;
break;
}
}
if (!found) {
LOGGER.info("File without matching certificate in JAR: {}", entry);
return Validity.INVALID;
}
checkedFiles++;
}
}
LOGGER.info("JAR signature verification succeeded for all {} relevant entries in {}", checkedFiles, jar.getName());
return Validity.VALID;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import java.lang.management.ManagementFactory;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.*;

import org.apache.catalina.Context;
import org.apache.catalina.Manager;
Expand Down Expand Up @@ -119,6 +119,7 @@ public static void main(String[] args) {

} else {
try {
MidPointJarSignatureChecker.setupJarSignature();
applicationContext = configureApplication(new SpringApplicationBuilder()).run(args);
} catch (Throwable e) {
reportFatalErrorToStdErr(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,18 @@ <h3 class="card-title"><wicket:message key="PageAbout.title.basic"/></h3>
<td>
<wicket:message key="PageAbout.builtAt"/>
</td>
<td><span wicket:id="buildTimestamp"/></td>
<td>
<span wicket:id="buildTimestamp"/>
<br/>
<span wicket:id="officialBuild">
<i class="fa-regular fa-circle-check color-green"></i>
<wicket:message key="PageAbout.officialBuild"/>
</span>
<span wicket:id="unofficialBuild">
<i class="fa fa-exclamation-circle color-yellow"></i>
<wicket:message key="PageAbout.unofficialBuild"/>
</span>
</td>
</tr>
</table>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@

import com.evolveum.midpoint.model.api.ActivitySubmissionOptions;

import com.evolveum.midpoint.repo.common.subscription.JarSignatureHolder;
import com.evolveum.midpoint.schema.util.task.ActivityDefinitionBuilder;

import org.apache.catalina.util.ServerInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
Expand Down Expand Up @@ -103,6 +105,8 @@ public class PageAbout extends PageAdminConfiguration {
private static final String ID_BRANCH = "branch";
private static final String ID_PROPERTY = "property";
private static final String ID_VALUE = "value";
private static final String ID_OFFICIAL_BUILD = "officialBuild";
private static final String ID_UNOFFICIAL_BUILD = "unofficialBuild";
private static final String ID_LIST_SYSTEM_ITEMS = "listSystemItems";
private static final String ID_TEST_REPOSITORY = "testRepository";
private static final String ID_TEST_REPOSITORY_CHECK_ORG_CLOSURE = "testRepositoryCheckOrgClosure";
Expand Down Expand Up @@ -205,6 +209,14 @@ private void initLayout() {
build.setRenderBodyOnly(true);
add(build);

boolean jarSignatureValid = JarSignatureHolder.isJarSignatureValid();
add(new WebMarkupContainer(ID_OFFICIAL_BUILD)
.setRenderBodyOnly(true)
.setVisible(jarSignatureValid));
add(new WebMarkupContainer(ID_UNOFFICIAL_BUILD)
.setRenderBodyOnly(true)
.setVisible(!jarSignatureValid));

ListView<LabeledString> listSystemItems = new ListView<>(ID_LIST_SYSTEM_ITEMS, getItems()) {
private static final long serialVersionUID = 1L;

Expand Down
Binary file added gui/admin-gui/src/main/resources/jar-signing.cer
Binary file not shown.
29 changes: 29 additions & 0 deletions gui/midpoint-jar/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@
</dependency>
</dependencies>

<profiles>
<profile>
<id>jar-signing</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>sign</id>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<keystore>${env.MIDPOINT_BUILD_KEYSTORE}</keystore>
<alias>build-signing-key</alias>
<storepass>password</storepass>
<keypass>password</keypass>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<build>
<finalName>midpoint</finalName>
<resources>
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@
repository test setup (config.xml).
This is not supported by new repository, but standard config.xml override mechanism can be used,
e.g. -Dmidpoint.repository.jdbcUrl=... and other properties.
CODE SIGNING:
- There is a profile `jar-signing` that causes `midpoint.jar` to be signed, as described in `midpoint-jar/pom.xml`.
In particular, the `MIDPOINT_BUILD_KEYSTORE` env variable must point to the keystore that contains a `build-signing-key`
that will be used for signing.
-->

<parent>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2010-2024 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.repo.common.subscription;

/**
* Just to (statically) hold the information about `midpoint.jar` file signature validity.
*
* Temporary solution.
*/
public class JarSignatureHolder {

public static Validity jarSignatureValidity;

public static void setJarSignatureValidity(Validity validity) {
jarSignatureValidity = validity;
}

public static boolean isJarSignatureValid() {
return jarSignatureValidity == Validity.VALID;
}

public enum Validity {

/** The signature is present and valid. */
VALID,

/** The signature is either missing or not valid. */
INVALID,

/** There was an error while verifying the signature. */
ERROR,

/** The signature checking is not applicable, e.g. because we are not running from a JAR file. */
NOT_APPLICABLE
}
}

0 comments on commit e46c8d1

Please sign in to comment.