diff --git a/tycho-extras/pom.xml b/tycho-extras/pom.xml
index 0123fcf5e4..e258f59607 100644
--- a/tycho-extras/pom.xml
+++ b/tycho-extras/pom.xml
@@ -42,6 +42,7 @@
 		<module>target-platform-validation-plugin</module>
 		<module>tycho-pomless</module>
 		<module>tycho-dependency-tools-plugin</module>
+		<module>tycho-sbom</module>
 	</modules>
 
 	<dependencyManagement>
diff --git a/tycho-extras/tycho-sbom/.settings/org.eclipse.jdt.core.prefs b/tycho-extras/tycho-sbom/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000..cf2cd4590a
--- /dev/null
+++ b/tycho-extras/tycho-sbom/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/tycho-extras/tycho-sbom/pom.xml b/tycho-extras/tycho-sbom/pom.xml
new file mode 100644
index 0000000000..caac2ab4de
--- /dev/null
+++ b/tycho-extras/tycho-sbom/pom.xml
@@ -0,0 +1,55 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.eclipse.tycho.extras</groupId>
+		<artifactId>tycho-extras</artifactId>
+		<version>5.0.0-SNAPSHOT</version>
+	</parent>
+	<artifactId>tycho-sbom</artifactId>
+	<name>Tycho SBOM model extension</name>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.eclipse.tycho</groupId>
+			<artifactId>tycho-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.tycho</groupId>
+			<artifactId>tycho-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.tycho</groupId>
+			<artifactId>p2-maven-plugin</artifactId>
+			<version>${project.version}</version>
+			<type>maven-plugin</type>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.tycho</groupId>
+			<artifactId>tycho-versions-plugin</artifactId>
+			<version>${project.version}</version>
+			<type>maven-plugin</type>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.maven</groupId>
+			<artifactId>maven-plugin-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.plexus</groupId>
+			<artifactId>plexus-component-annotations</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.cyclonedx</groupId>
+			<artifactId>cyclonedx-maven-plugin</artifactId>
+			<version>2.7.10</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/tycho-extras/tycho-sbom/src/main/java/org/eclipse/tycho/sbom/P2ModelConverter.java b/tycho-extras/tycho-sbom/src/main/java/org/eclipse/tycho/sbom/P2ModelConverter.java
new file mode 100644
index 0000000000..ba31336985
--- /dev/null
+++ b/tycho-extras/tycho-sbom/src/main/java/org/eclipse/tycho/sbom/P2ModelConverter.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Patrick Ziegler and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *    Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.sbom;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.Artifact;
+import org.codehaus.plexus.component.annotations.Component;
+import org.cyclonedx.maven.DefaultModelConverter;
+import org.cyclonedx.maven.ModelConverter;
+import org.eclipse.equinox.p2.metadata.IArtifactKey;
+import org.eclipse.tycho.ArtifactKey;
+import org.eclipse.tycho.ArtifactType;
+import org.eclipse.tycho.DefaultArtifactKey;
+import org.eclipse.tycho.core.resolver.target.ArtifactTypeHelper;
+import org.eclipse.tycho.versions.engine.Versions;
+
+/**
+ * Custom implementation of the CycloneDX model converter with support for both
+ * Maven and p2 artifacts. The generated PURL is usually of the form:
+ * 
+ * <pre>
+ * pkg:/p2/&lt;id&gt;@&lt;version&gt;?classifier=&lt;classifier&gt;&amp;location=&lt;download-url&gt;
+ * </pre>
+ * 
+ * This converter can be used with the {@code cyclonedx-maven-plugin} by adding
+ * it as a dependency as follows:
+ * 
+ * <pre>
+ * &lt;plugin>
+ *   &lt;groupId&gt;org.cyclonedx&lt;/groupId&gt;
+ *   &lt;artifactId&gt;cyclonedx-maven-plugin&lt;/artifactId&gt;
+ *   &lt;dependencies&gt;
+ *     &lt;dependency&gt;
+ *       &lt;groupId&gt;org.eclipse.tycho.extras&lt;/groupId&gt;
+ *       &lt;artifactId&gt;tycho-sbom&lt;/artifactId&gt;
+ *     &lt;/dependency&gt;
+ *   &lt;/dependencies&gt;
+ * &lt;/plugin&gt;
+ * </pre>
+ */
+@Component(role = ModelConverter.class, hint = "p2")
+public class P2ModelConverter extends DefaultModelConverter {
+	private static final Set<String> SUPPORTED_TYPES = Set.of(ArtifactType.TYPE_BUNDLE_FRAGMENT,
+			ArtifactType.TYPE_ECLIPSE_PLUGIN, ArtifactType.TYPE_ECLIPSE_FEATURE);
+
+	@Override
+	public String generatePackageUrl(Artifact artifact) {
+		if (SUPPORTED_TYPES.contains(artifact.getType())) {
+			return generateP2PackageUrl(artifact, true, true);
+		}
+		return super.generatePackageUrl(artifact);
+	}
+
+	@Override
+	public String generatePackageUrl(org.eclipse.aether.artifact.Artifact artifact) {
+		return generatePackageUrl(RepositoryUtils.toArtifact(artifact));
+	}
+
+	@Override
+	public String generateVersionlessPackageUrl(Artifact artifact) {
+		if (SUPPORTED_TYPES.contains(artifact.getType())) {
+			return generateP2PackageUrl(artifact, false, true);
+		}
+		return super.generateVersionlessPackageUrl(artifact);
+	}
+
+	@Override
+	public String generateVersionlessPackageUrl(org.eclipse.aether.artifact.Artifact artifact) {
+		return generateVersionlessPackageUrl(RepositoryUtils.toArtifact(artifact));
+	}
+
+	@Override
+	public String generateClassifierlessPackageUrl(org.eclipse.aether.artifact.Artifact artifact) {
+		Artifact mavenArtifact = RepositoryUtils.toArtifact(artifact);
+		if (SUPPORTED_TYPES.contains(mavenArtifact.getType())) {
+			return generateP2PackageUrl(mavenArtifact, true, false);
+		}
+		return super.generateClassifierlessPackageUrl(artifact);
+	}
+
+	private String generateP2PackageUrl(Artifact artifact, boolean withVersion, boolean withClassifier) {
+		// TODO Resolve "artifact" and use e.g. TychoProjectManager w/ proper qualifier
+		ArtifactKey artifactKey = new DefaultArtifactKey(artifact.getType(), artifact.getArtifactId(),
+				Versions.toCanonicalVersion(artifact.getVersion()));
+		IArtifactKey p2artifactKey = ArtifactTypeHelper.toP2ArtifactKey(artifactKey);
+		// TODO Try to find p2 location
+		String location = "unknown";
+		if (artifact.getFile() != null) {
+			location = artifact.getFile().toURI().toString();
+		}
+		if (artifact.getDownloadUrl() != null) {
+			location = artifact.getDownloadUrl();
+		}
+		String encodedLocation = URLEncoder.encode(location, StandardCharsets.UTF_8);
+		//
+		StringBuilder builder = new StringBuilder();
+		builder.append("pkg:p2/");
+		builder.append(p2artifactKey.getId());
+		if (withVersion) {
+			builder.append('@');
+			builder.append(p2artifactKey.getVersion());
+		}
+		builder.append('?');
+		if (withClassifier) {
+			builder.append("classifier=");
+			builder.append(p2artifactKey.getClassifier());
+			builder.append('&');
+		}
+		builder.append("location=");
+		builder.append(encodedLocation);
+		return builder.toString();
+	}
+}
diff --git a/tycho-extras/tycho-sbom/src/main/resources/META-INF/plexus/components.xml b/tycho-extras/tycho-sbom/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 0000000000..9e4c319a81
--- /dev/null
+++ b/tycho-extras/tycho-sbom/src/main/resources/META-INF/plexus/components.xml
@@ -0,0 +1,9 @@
+<component-set>
+	<components>
+		<component>
+			<role>org.cyclonedx.maven.ModelConverter</role>
+			<hint>p2</hint>
+			<implementation>org.eclipse.tycho.sbom.P2ModelConverter</implementation>
+		</component>
+	</components>
+</component-set>
\ No newline at end of file
diff --git a/tycho-extras/tycho-sbom/src/test/java/org/eclipse/tycho/sbom/P2ModelConverterTest.java b/tycho-extras/tycho-sbom/src/test/java/org/eclipse/tycho/sbom/P2ModelConverterTest.java
new file mode 100644
index 0000000000..540fda3255
--- /dev/null
+++ b/tycho-extras/tycho-sbom/src/test/java/org/eclipse/tycho/sbom/P2ModelConverterTest.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Patrick Ziegler and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *    Patrick Ziegler - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.sbom;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.cyclonedx.maven.ModelConverter;
+import org.eclipse.tycho.ArtifactType;
+import org.eclipse.tycho.p2maven.repository.EclipsePluginArtifactHandler;
+import org.junit.Before;
+import org.junit.Test;
+
+public class P2ModelConverterTest {
+	private Artifact artifact;
+	private ModelConverter modelConverter;
+
+	@Before
+	public void setUp() {
+		artifact = new DefaultArtifact("p2.eclipse.plugin", "org.eclipse.platform", "4.30.0.v20231201-0110", "compile",
+				ArtifactType.TYPE_ECLIPSE_PLUGIN, null, new EclipsePluginArtifactHandler());
+		artifact.setDownloadUrl(
+				"https://download.eclipse.org/releases/2023-12/202312061001/plugins/org.eclipse.platform_4.30.0.v20231201-0110.jar");
+		modelConverter = new P2ModelConverter();
+	}
+
+	@Test
+	public void testGeneratePackageUrl() {
+		String purl = modelConverter.generatePackageUrl(artifact);
+		String location = URLEncoder.encode(artifact.getDownloadUrl(), StandardCharsets.UTF_8);
+		assertEquals(purl,
+				"pkg:p2/org.eclipse.platform@4.30.0.v20231201-0110?classifier=osgi.bundle&location=" + location);
+	}
+
+	@Test
+	public void testGeneratePackageUrlWithoutVersion() {
+		String purl = modelConverter.generateVersionlessPackageUrl(artifact);
+		String location = URLEncoder.encode(artifact.getDownloadUrl(), StandardCharsets.UTF_8);
+		assertEquals(purl, "pkg:p2/org.eclipse.platform?classifier=osgi.bundle&location=" + location);
+	}
+}