Skip to content

Commit

Permalink
Integrate sigstore/sigstore-maven-plugin into repo
Browse files Browse the repository at this point in the history
- Move sigstore-maven-plugin in sigstore/sigstore-java
- Make it a gradle module (and build with gradle :o! sorry)

Signed-off-by: Appu Goundan <appu@google.com>
  • Loading branch information
loosebazooka committed Jul 8, 2024
1 parent 4905f83 commit 61d91a9
Show file tree
Hide file tree
Showing 16 changed files with 667 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.kotlin.dsl.*
import java.io.File

plugins {
java
}

val sigstoreJavaRuntime by configurations.creating {
description = "declares dependencies that will be useful for testing purposes"
isCanBeConsumed = false
isCanBeResolved = false
}

val sigstoreJavaTestClasspath by configurations.creating {
description = "sigstore-java in local repository for testing purposes"
isCanBeConsumed = false
isCanBeResolved = true
extendsFrom(sigstoreJavaRuntime)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named("maven-repository"))
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
}
}

tasks.test {
dependsOn(sigstoreJavaTestClasspath)
systemProperty("sigstore.test.current.version", version)
val projectDir = layout.projectDirectory.asFile
// This adds paths to the local repositories that contain currently-built sigstore-java
// It enables testing both "sigstore-java from Central" and "sigstore-java build locally" in the plugin tests
jvmArgumentProviders.add(
// Gradle does not support Provider for systemProperties yet, see https://github.com/gradle/gradle/issues/12247
CommandLineArgumentProvider {
listOf(
"-Dsigstore.test.local.maven.repo=" +
sigstoreJavaTestClasspath.joinToString(File.pathSeparator) {
it.toRelativeString(projectDir)
},
)
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.kotlin.dsl.*
import java.io.File

plugins {
java
}

val sigstoreMavenPluginRuntime by configurations.creating {
description = "declares dependencies that will be useful for testing purposes"
isCanBeConsumed = false
isCanBeResolved = false
}

val sigstoreMavenPluginTestClasspath by configurations.creating {
description = "sigstore-maven-plugin in local repository for testing purposes"
isCanBeConsumed = false
isCanBeResolved = true
extendsFrom(sigstoreMavenPluginRuntime)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named("maven-repository"))
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
}
}

tasks.test {
dependsOn(sigstoreMavenPluginTestClasspath)
systemProperty("sigstore.test.current.maven.plugin.version", version)
val projectDir = layout.projectDirectory.asFile
// This adds paths to the local repositories that contain currently-built sigstore-maven-plugin
jvmArgumentProviders.add(
// Gradle does not support Provider for systemProperties yet, see https://github.com/gradle/gradle/issues/12247
CommandLineArgumentProvider {
listOf(
"-Dsigstore.test.local.maven.plugin.repo=" +
sigstoreMavenPluginTestClasspath.joinToString(File.pathSeparator) {
it.toRelativeString(projectDir)
},
)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ java {

publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
if (findByName("mavenJava") == null) {
// don't create a new publication if one already exists
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,5 @@ plugins {
id("build-logic.reproducible-builds")
id("build-logic.dokka-javadoc")
id("build-logic.publish-to-central")
}

val sigstoreJavaRuntime by configurations.creating {
description = "declares dependencies that will be useful for testing purposes"
isCanBeConsumed = false
isCanBeResolved = false
}

val sigstoreJavaTestClasspath by configurations.creating {
description = "sigstore-java in local repository for testing purposes"
isCanBeConsumed = false
isCanBeResolved = true
extendsFrom(sigstoreJavaRuntime)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named("maven-repository"))
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
}
}

tasks.test {
dependsOn(sigstoreJavaTestClasspath)
systemProperty("sigstore.test.current.version", version)
val projectDir = layout.projectDirectory.asFile
// This adds paths to the local repositories that contain currently-built sigstore-java
// It enables testing both "sigstore-java from Central" and "sigstore-java build locally" in the plugin tests
jvmArgumentProviders.add(
// Gradle does not support Provider for systemProperties yet, see https://github.com/gradle/gradle/issues/12247
CommandLineArgumentProvider {
listOf(
"-Dsigstore.test.local.maven.repo=" +
sigstoreJavaTestClasspath.joinToString(File.pathSeparator) {
it.toRelativeString(projectDir)
}
)
}
)
id("build-logic.depends-on-local-sigstore-java-repo")
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.kotlin.dsl.registering

plugins {
id("java-library")
id("maven-publish")
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ include("sigstore-gradle:sigstore-gradle-sign-base-plugin")
include("sigstore-gradle:sigstore-gradle-sign-plugin")
include("sigstore-testkit")
include("sigstore-cli")
include("sigstore-maven-plugin")

include("fuzzing")
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.cert.*;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -208,4 +209,9 @@ public static boolean isSelfSigned(CertPath certPath) {
public static X509Certificate getLeaf(CertPath certPath) {
return (X509Certificate) certPath.getCertificates().get(0);
}

public static long validity(X509Certificate certificate, ChronoUnit unit) {
return unit.between(
certificate.getNotAfter().toInstant(), certificate.getNotBefore().toInstant());
}
}
24 changes: 24 additions & 0 deletions sigstore-maven-plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.vscode
.factorypath
.project
.classpath
.settings/
*.iml
*.ipr
.idea
*.class
*.jar
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
pom.xml.bak
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.apt_generated/
.apt_generated_tests/
bin/
36 changes: 36 additions & 0 deletions sigstore-maven-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
sigstore-maven-plugin
=====================

[![Maven Central](https://img.shields.io/maven-central/v/dev.sigstore/sigstore-maven-plugin.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/dev.sigstore/sigstore-maven-plugin)

This is a Maven plugin that can be used to use the "keyless" signing paradigm supported by Sigstore.
This plugin is still in early phases, then has known limitations described below.

sign
----

```xml
<plugin>
<groupId>dev.sigstore</groupId>
<artifactId>sigstore-maven-plugin</artifactId>
<version>0.4.0</version>
<executions>
<execution>
<id>sign</id>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
```

Notes:

- GPG: Maven Central publication rules require GPG signing each files: to avoid GPG signing of `.sigstore.json` files, just use version 3.1.0 minimum of [maven-gpg-plugin](https://maven.apache.org/plugins/maven-gpg-plugin/).
- `.md5`/`.sha1`: to avoid unneeded checksum files for `.sigstore.java` files, use Maven 3.9.2 minimum or create `.mvn/maven.config` file containing `-Daether.checksums.omitChecksumsForExtensions=.asc,.sigstore.java`

Known limitations:

- Maven multi-module build: each module will require an OIDC authentication,
- 10 minutes signing session: if a build takes more than 10 minutes, a new OIDC authentication will be required each 10 minutes.
29 changes: 29 additions & 0 deletions sigstore-maven-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("io.freefair.maven-plugin") version "8.6"
id("build-logic.java-published-library")
id("build-logic.test-junit5")
id("build-logic.depends-on-local-sigstore-java-repo")
id("build-logic.depends-on-local-sigstore-maven-plugin-repo")
}

dependencies {
compileOnly("org.apache.maven:maven-plugin-api:3.9.8")
compileOnly("org.apache.maven:maven-core:3.9.8")
compileOnly("org.apache.maven:maven-core:3.9.8")
compileOnly("org.apache.maven.plugin-tools:maven-plugin-annotations:3.13.1")

implementation(project(":sigstore-java"))
implementation("org.bouncycastle:bcutil-jdk18on:1.78.1")
implementation("org.apache.maven.plugins:maven-gpg-plugin:3.1.0")

testImplementation("org.apache.maven.shared:maven-verifier:1.8.0")

testImplementation(project(":sigstore-testkit"))

sigstoreJavaRuntime(project(":sigstore-java")) {
because("Test code needs access locally-built sigstore-java as a Maven repository")
}
sigstoreMavenPluginRuntime(project(":sigstore-maven-plugin")) {
because("Test code needs access locally-built sigstore-java as a Maven repository")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2023 The Sigstore Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.sigstore.plugin;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DEROctetString;

/**
* Helper to decode Fulcio OID data, see <a
* href="https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md">Sigstore OID
* information</a>.
*/
public class FulcioOidHelper {
private static final String SIGSTORE_OID_ROOT = "1.3.6.1.4.1.57264";
private static final String FULCIO_OID_ROOT = SIGSTORE_OID_ROOT + ".1";

@Deprecated private static final String FULCIO_ISSUER_OID = FULCIO_OID_ROOT + ".1";

private static final String FULCIO_ISSUER_V2_OID = FULCIO_OID_ROOT + ".8";

public static String getIssuer(X509Certificate cert) {
String issuerV2 = getIssuerV2(cert);
if (issuerV2 == null) {
return getIssuerV1(cert);
}
return issuerV2;
}

@Deprecated
public static String getIssuerV1(X509Certificate cert) {
return getExtensionValue(cert, FULCIO_ISSUER_OID, true);
}

public static String getIssuerV2(X509Certificate cert) {
return getExtensionValue(cert, FULCIO_ISSUER_V2_OID, false);
}

/* Extracts the octets from an extension value and converts to utf-8 directly, it does NOT
* account for any ASN1 encoded value. If the extension value is an ASN1 object (like an
* ASN1 encoded string), you need to write a new extraction helper. */
private static String getExtensionValue(X509Certificate cert, String oid, boolean rawUtf8) {
byte[] extensionValue = cert.getExtensionValue(oid);

if (extensionValue == null) {
return null;
}
try {
ASN1Primitive derObject = ASN1Sequence.fromByteArray(cert.getExtensionValue(oid));
if (derObject instanceof DEROctetString) {
DEROctetString derOctetString = (DEROctetString) derObject;
if (rawUtf8) {
// this is unusual, but the octet is a raw utf8 string in fulcio land (no prefix of type)
// and not an ASN1 object.
return new String(derOctetString.getOctets(), StandardCharsets.UTF_8);
}

derObject = ASN1Sequence.fromByteArray(derOctetString.getOctets());
if (derObject instanceof ASN1String) {
ASN1String s = (ASN1String) derObject;
return s.getString();
}
}
throw new RuntimeException(
"Could not parse extension "
+ oid
+ " in certificate because it was not an octet string");
} catch (IOException ioe) {
throw new RuntimeException("Could not parse extension " + oid + " in certificate", ioe);
}
}
}
Loading

0 comments on commit 61d91a9

Please sign in to comment.