Skip to content

Commit 2745e36

Browse files
authored
Merge pull request #731 from sigstore/signing-matching
Use Matchers in OidcIdentity matching
2 parents 5113aa2 + 977ee78 commit 2745e36

File tree

7 files changed

+213
-63
lines changed

7 files changed

+213
-63
lines changed

sigstore-java/src/main/java/dev/sigstore/KeylessSigner.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import dev.sigstore.oidc.client.OidcClients;
3939
import dev.sigstore.oidc.client.OidcException;
4040
import dev.sigstore.oidc.client.OidcToken;
41+
import dev.sigstore.oidc.client.OidcTokenMatcher;
4142
import dev.sigstore.rekor.client.HashedRekordRequest;
4243
import dev.sigstore.rekor.client.RekorClient;
4344
import dev.sigstore.rekor.client.RekorClientHttp;
@@ -86,7 +87,7 @@ public class KeylessSigner implements AutoCloseable {
8687
private final RekorClient rekorClient;
8788
private final RekorVerifier rekorVerifier;
8889
private final OidcClients oidcClients;
89-
private final List<OidcIdentity> oidcIdentities;
90+
private final List<OidcTokenMatcher> oidcIdentities;
9091
private final Signer signer;
9192
private final Duration minSigningCertificateLifetime;
9293

@@ -109,7 +110,7 @@ private KeylessSigner(
109110
RekorClient rekorClient,
110111
RekorVerifier rekorVerifier,
111112
OidcClients oidcClients,
112-
List<OidcIdentity> oidcIdentities,
113+
List<OidcTokenMatcher> oidcIdentities,
113114
Signer signer,
114115
Duration minSigningCertificateLifetime) {
115116
this.fulcioClient = fulcioClient;
@@ -141,7 +142,7 @@ public static Builder builder() {
141142
public static class Builder {
142143
private TrustedRootProvider trustedRootProvider;
143144
private OidcClients oidcClients;
144-
private List<OidcIdentity> oidcIdentities = Collections.emptyList();
145+
private List<OidcTokenMatcher> oidcIdentities = Collections.emptyList();
145146
private Signer signer;
146147
private Duration minSigningCertificateLifetime = DEFAULT_MIN_SIGNING_CERTIFICATE_LIFETIME;
147148
private URI fulcioUri;
@@ -177,7 +178,7 @@ public Builder oidcClients(OidcClients oidcClients) {
177178
* null but can be an empty list and will allow all identities.
178179
*/
179180
@CanIgnoreReturnValue
180-
public Builder allowedOidcIdentities(List<OidcIdentity> oidcIdentities) {
181+
public Builder allowedOidcIdentities(List<OidcTokenMatcher> oidcIdentities) {
181182
this.oidcIdentities = ImmutableList.copyOf(oidcIdentities);
182183
return this;
183184
}
@@ -378,12 +379,9 @@ private void renewSigningCertificate()
378379

379380
// check if we have an allow list and if so, ensure the provided token is in there
380381
if (!oidcIdentities.isEmpty()) {
381-
var obtainedToken = OidcIdentity.from(tokenInfo);
382-
if (!oidcIdentities.contains(OidcIdentity.from(tokenInfo))) {
382+
if (oidcIdentities.stream().noneMatch(id -> id.test(tokenInfo))) {
383383
throw new KeylessSignerException(
384-
"Obtained Oidc Token "
385-
+ obtainedToken
386-
+ " does not match any identities in allow list");
384+
"Obtained Oidc Token " + tokenInfo + " does not match any identities in allow list");
387385
}
388386
}
389387

sigstore-java/src/main/java/dev/sigstore/OidcIdentity.java

Lines changed: 0 additions & 40 deletions
This file was deleted.

sigstore-java/src/main/java/dev/sigstore/oidc/client/OidcToken.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ public interface OidcToken {
2727
String getIssuer();
2828

2929
/** The full oidc token obtained from the provider. */
30+
@Value.Redacted
3031
String getIdToken();
3132
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2023 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.oidc.client;
17+
18+
import dev.sigstore.strings.StringMatcher;
19+
import java.util.function.Predicate;
20+
21+
/**
22+
* An interface for allowing direct string matching or regular expressions on {@link OidcToken}. Use
23+
* the static factory {@link #of(StringMatcher, StringMatcher)} to instantiate the matcher. Custom
24+
* implementations should override {@link Object#toString} for better error reporting.
25+
*/
26+
public interface OidcTokenMatcher extends Predicate<OidcToken> {
27+
28+
static OidcTokenMatcher of(StringMatcher san, StringMatcher issuer) {
29+
return new OidcTokenMatcher() {
30+
@Override
31+
public boolean test(OidcToken oidcToken) {
32+
return san.test(oidcToken.getSubjectAlternativeName())
33+
&& issuer.test(oidcToken.getIssuer());
34+
}
35+
36+
@Override
37+
public String toString() {
38+
return "{subjectAlternativeName: " + san + ", issuer: " + issuer + "}";
39+
}
40+
};
41+
}
42+
}

sigstore-java/src/test/java/dev/sigstore/KeylessSignerTest.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
*/
1616
package dev.sigstore;
1717

18-
import com.google.api.client.json.gson.GsonFactory;
19-
import com.google.api.client.json.webtoken.JsonWebSignature;
2018
import com.google.common.hash.Hashing;
2119
import dev.sigstore.bundle.Bundle;
22-
import dev.sigstore.oidc.client.GithubActionsOidcClient;
20+
import dev.sigstore.oidc.client.OidcTokenMatcher;
21+
import dev.sigstore.strings.StringMatcher;
2322
import dev.sigstore.testing.matchers.ByteArrayListMatcher;
2423
import dev.sigstore.testkit.annotations.EnabledIfOidcExists;
2524
import dev.sigstore.testkit.annotations.OidcProviderType;
@@ -104,12 +103,15 @@ public void sign_digest() throws Exception {
104103

105104
@Test
106105
@EnabledIfOidcExists(provider = OidcProviderType.GITHUB)
107-
// this test will only pass on the github.com/sigstore/sigstore-java repository
108106
public void sign_failGithubOidcCheck() throws Exception {
109107
var signer =
110108
KeylessSigner.builder()
111109
.sigstorePublicDefaults()
112-
.allowedOidcIdentities(List.of(OidcIdentity.of("goose@goose.com", "goose.com")))
110+
.allowedOidcIdentities(
111+
List.of(
112+
OidcTokenMatcher.of(
113+
StringMatcher.string("goose@goose.com"),
114+
StringMatcher.string("goose.com"))))
113115
.build();
114116
var ex =
115117
Assertions.assertThrows(
@@ -127,20 +129,19 @@ public void sign_failGithubOidcCheck() throws Exception {
127129
@EnabledIfOidcExists(provider = OidcProviderType.GITHUB)
128130
// this test will only pass on the github.com/sigstore/sigstore-java repository
129131
public void sign_passGithubOidcCheck() throws Exception {
130-
// silly way to get the right oidc identity to make sure our simple matcher works
131-
var jws =
132-
JsonWebSignature.parse(
133-
new GsonFactory(),
134-
GithubActionsOidcClient.builder().build().getIDToken(System.getenv()).getIdToken());
135-
var expectedGithubSubject = jws.getPayload().getSubject();
136132
var signer =
137133
KeylessSigner.builder()
138134
.sigstorePublicDefaults()
139135
.allowedOidcIdentities(
140136
List.of(
141-
OidcIdentity.of(
142-
expectedGithubSubject, "https://token.actions.githubusercontent.com"),
143-
OidcIdentity.of("some@other.com", "https://accounts.other.com")))
137+
OidcTokenMatcher.of(
138+
// this is bad matching, do not use it as an example of what to do in a
139+
// production environment
140+
StringMatcher.regex(".*sigstore/sigstore-java.*"),
141+
StringMatcher.string("https://token.actions.githubusercontent.com")),
142+
OidcTokenMatcher.of(
143+
StringMatcher.string("some@other.com"),
144+
StringMatcher.string("https://accounts.other.com"))))
144145
.build();
145146
Assertions.assertDoesNotThrow(
146147
() ->
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.oidc.client;
17+
18+
import dev.sigstore.strings.RegexSyntaxException;
19+
import dev.sigstore.strings.StringMatcher;
20+
import org.junit.jupiter.api.Assertions;
21+
import org.junit.jupiter.api.Test;
22+
23+
public class OidcTokenMatcherTest {
24+
25+
@Test
26+
public void testString() {
27+
var testMatcher =
28+
OidcTokenMatcher.of(StringMatcher.string("test-san"), StringMatcher.string("test-issuer"));
29+
Assertions.assertTrue(
30+
testMatcher.test(
31+
ImmutableOidcToken.builder()
32+
.idToken("hidden stuff")
33+
.subjectAlternativeName("test-san")
34+
.issuer("test-issuer")
35+
.build()));
36+
Assertions.assertTrue(
37+
testMatcher.test(
38+
ImmutableOidcToken.builder()
39+
.idToken("hidden stuff")
40+
.subjectAlternativeName("test-san")
41+
.issuer("test-issuer")
42+
.build()));
43+
Assertions.assertFalse(
44+
testMatcher.test(
45+
ImmutableOidcToken.builder()
46+
.idToken("hidden stuff")
47+
.subjectAlternativeName("wrong-san")
48+
.issuer("test-issuer")
49+
.build()));
50+
Assertions.assertFalse(
51+
testMatcher.test(
52+
ImmutableOidcToken.builder()
53+
.idToken("hidden stuff")
54+
.subjectAlternativeName("test-san")
55+
.issuer("wrong-issuer")
56+
.build()));
57+
Assertions.assertFalse(
58+
testMatcher.test(
59+
ImmutableOidcToken.builder()
60+
.idToken("hidden stuff")
61+
.subjectAlternativeName("")
62+
.issuer("")
63+
.build()));
64+
65+
Assertions.assertEquals(
66+
"{subjectAlternativeName: 'String: test-san', issuer: 'String: test-issuer'}",
67+
testMatcher.toString());
68+
}
69+
70+
@Test
71+
public void testRegex() throws RegexSyntaxException {
72+
var testMatcher =
73+
OidcTokenMatcher.of(StringMatcher.regex("test-..."), StringMatcher.regex("test-.*"));
74+
Assertions.assertTrue(
75+
testMatcher.test(
76+
ImmutableOidcToken.builder()
77+
.idToken("hidden stuff")
78+
.subjectAlternativeName("test-san")
79+
.issuer("test-issuer")
80+
.build()));
81+
Assertions.assertTrue(
82+
testMatcher.test(
83+
ImmutableOidcToken.builder()
84+
.idToken("hidden stuff")
85+
.subjectAlternativeName("test-san")
86+
.issuer("test-issuer")
87+
.build()));
88+
Assertions.assertFalse(
89+
testMatcher.test(
90+
ImmutableOidcToken.builder()
91+
.idToken("hidden stuff")
92+
.subjectAlternativeName("wrong-san")
93+
.issuer("test-issuer")
94+
.build()));
95+
Assertions.assertFalse(
96+
testMatcher.test(
97+
ImmutableOidcToken.builder()
98+
.idToken("hidden stuff")
99+
.subjectAlternativeName("test-san")
100+
.issuer("wrong-issuer")
101+
.build()));
102+
Assertions.assertFalse(
103+
testMatcher.test(
104+
ImmutableOidcToken.builder()
105+
.idToken("hidden stuff")
106+
.subjectAlternativeName("")
107+
.issuer("")
108+
.build()));
109+
110+
Assertions.assertEquals(
111+
"{subjectAlternativeName: 'RegEx: test-...', issuer: 'RegEx: test-.*'}",
112+
testMatcher.toString());
113+
}
114+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.oidc.client;
17+
18+
import org.junit.jupiter.api.Assertions;
19+
import org.junit.jupiter.api.Test;
20+
21+
public class OidcTokenTest {
22+
23+
@Test
24+
public void test_redacted() {
25+
var testToken =
26+
ImmutableOidcToken.builder()
27+
.issuer("issuer")
28+
.idToken("secret")
29+
.subjectAlternativeName("name")
30+
.build();
31+
Assertions.assertEquals(
32+
"OidcToken{subjectAlternativeName=name, issuer=issuer}", testToken.toString());
33+
}
34+
}

0 commit comments

Comments
 (0)