Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Pichler committed Nov 18, 2022
1 parent 3eace9d commit 018c16b
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 128 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.jenkins.plugins.oidc_provider;

import hudson.util.Secret;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class ECSecretKeyPair implements SecretKeyPair {
private static final long serialVersionUID = 2448941858110252020L;

/**
* Encrypted base64 encoding of a private key in {@link PKCS8EncodedKeySpec}
*/
private final Secret privateKey;

/**
* Encrypted base64 encoding of a public key in {@link X509EncodedKeySpec}
*/
private final Secret publicKey;

public ECSecretKeyPair(KeyPair keyPair) {
this.privateKey = Secret.fromString(Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));
this.publicKey = Secret.fromString(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()));
}

@Override
public KeyPair toKeyPair() throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("EC");

PrivateKey priv = keyFactory.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey.getPlainText())));

PublicKey pub = keyFactory.generatePublic(
new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey.getPlainText())));

return new KeyPair(pub, priv);
}

}
100 changes: 69 additions & 31 deletions src/main/java/io/jenkins/plugins/oidc_provider/IdTokenCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import hudson.model.TaskListener;
import hudson.util.FormValidation;
import hudson.util.Secret;
import io.jenkins.plugins.oidc_provider.Keys.SupportedKeyAlgorithm;
import io.jenkins.plugins.oidc_provider.config.ClaimTemplate;
import io.jenkins.plugins.oidc_provider.config.IdTokenConfiguration;
import io.jsonwebtoken.Claims;
Expand All @@ -45,10 +46,8 @@
import java.net.URISyntaxException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.time.temporal.ChronoUnit;
Expand Down Expand Up @@ -82,51 +81,70 @@ public abstract class IdTokenCredentials extends BaseStandardCredentials {
private transient KeyPair kp;

/**
* Encrypted {@link Base64} encoding of RSA private key in {@link RSAPrivateCrtKey} / {@link PKCS8EncodedKeySpec} format.
* Encrypted {@link Base64} encoding of private key in {@link RSAPrivateCrtKey} / {@link PKCS8EncodedKeySpec}
* format.
* The public key is inferred from this to reload {@link #kp}.
* <br/>
* The field has been replaced by {@link #secretKeyPair} and only stays around for
* compatibility reasons.
*/
private final Secret privateKey;
@Deprecated private transient Secret privateKey;

private SecretKeyPair secretKeyPair;

private @CheckForNull String issuer;

private @CheckForNull String audience;

private transient @CheckForNull Run<?, ?> build;

protected IdTokenCredentials(CredentialsScope scope, String id, String description) {
this(scope, id, description, generatePrivateKey());
}
private @NonNull SupportedKeyAlgorithm algorithm;

private static KeyPair generatePrivateKey() {
KeyPairGenerator gen;
try {
gen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException x) {
throw new AssertionError(x);
}
gen.initialize(2048);
return gen.generateKeyPair();
protected IdTokenCredentials(CredentialsScope scope, String id, String description) {
this(scope, id, description, IdTokenConfiguration.get().getAlgorithm());
}

private IdTokenCredentials(CredentialsScope scope, String id, String description, KeyPair kp) {
this(scope, id, description, kp, serializePrivateKey(kp));
protected IdTokenCredentials(CredentialsScope scope, String id, String description,
SupportedKeyAlgorithm algorithm) {
this(scope, id, description, algorithm.generateKeyPair(), algorithm);
}

private static Secret serializePrivateKey(KeyPair kp) {
assert ((RSAPublicKey) kp.getPublic()).getModulus().equals(((RSAPrivateCrtKey) kp.getPrivate()).getModulus());
return Secret.fromString(Base64.getEncoder().encodeToString(kp.getPrivate().getEncoded()));
private IdTokenCredentials(CredentialsScope scope, String id, String description, KeyPair kp,
SupportedKeyAlgorithm algorithm) {
this(scope, id, description, kp, algorithm, SecretKeyPair.fromKeyPair(algorithm, kp));
}

protected IdTokenCredentials(CredentialsScope scope, String id, String description, KeyPair kp, Secret privateKey) {
protected IdTokenCredentials(CredentialsScope scope, String id, String description, KeyPair kp,
SupportedKeyAlgorithm algorithm, SecretKeyPair secretKeyPair) {
super(scope, id, description);

Objects.requireNonNull(kp);
Objects.requireNonNull(algorithm);
Objects.requireNonNull(secretKeyPair);

this.kp = kp;
this.privateKey = privateKey;
this.algorithm = algorithm;
this.secretKeyPair = secretKeyPair;
}

protected Object readResolve() throws Exception {
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPrivateCrtKey priv = (RSAPrivateCrtKey) kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey.getPlainText())));
kp = new KeyPair(kf.generatePublic(new RSAPublicKeySpec(priv.getModulus(), priv.getPublicExponent())), priv);
// the private key should only be set for old versions of the credentials
// back then, only RSA256 ws supported and this block is handling the conversion
if (privateKey != null) {
KeyFactory kf = KeyFactory.getInstance("RSA");

RSAPrivateCrtKey priv = (RSAPrivateCrtKey) kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey.getPlainText())));
kp = new KeyPair(kf.generatePublic(new RSAPublicKeySpec(priv.getModulus(), priv.getPublicExponent())),
priv);
algorithm = SupportedKeyAlgorithm.RS256;
secretKeyPair = SecretKeyPair.fromKeyPair(algorithm, kp);

return this;
}

kp = secretKeyPair.toKeyPair();

return this;
}

Expand All @@ -146,18 +164,38 @@ public final String getAudience() {
this.audience = Util.fixEmpty(audience);
}

protected abstract IdTokenCredentials clone(KeyPair kp, Secret privateKey);
public void setAlgorithm(SupportedKeyAlgorithm algorithm) {
Objects.requireNonNull(algorithm);

boolean shouldRotate= this.algorithm == algorithm;

this.algorithm = algorithm;

if(shouldRotate) {
rotateKeyPair();
}
}

private void rotateKeyPair() {
this.secretKeyPair = SecretKeyPair.forAlgorithm(algorithm);
}

@NonNull public SupportedKeyAlgorithm getAlgorithm() {
return algorithm;
}

protected abstract IdTokenCredentials clone(KeyPair kp, SupportedKeyAlgorithm algorithm, SecretKeyPair secretKeyPair);

@Override public final Credentials forRun(Run<?, ?> context) {
IdTokenCredentials clone = clone(kp, privateKey);
IdTokenCredentials clone = clone(kp, algorithm, secretKeyPair);
clone.issuer = issuer;
clone.audience = audience;
clone.build = context;
return clone;
}

RSAPublicKey publicKey() {
return (RSAPublicKey) kp.getPublic();
PublicKey publicKey() {
return kp.getPublic();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import com.cloudbees.plugins.credentials.CredentialsScope;
import hudson.Extension;
import hudson.util.Secret;
import io.jenkins.plugins.oidc_provider.Keys.SupportedKeyAlgorithm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -47,8 +47,12 @@ public final class IdTokenFileCredentials extends IdTokenCredentials implements
super(scope, id, description);
}

private IdTokenFileCredentials(CredentialsScope scope, String id, String description, KeyPair kp, Secret privateKey) {
super(scope, id, description, kp, privateKey);
public IdTokenFileCredentials(CredentialsScope scope, String id, String description, SupportedKeyAlgorithm algorithm) {
super(scope, id, description, algorithm);
}

private IdTokenFileCredentials(CredentialsScope scope, String id, String description, KeyPair kp, SupportedKeyAlgorithm algorithm, SecretKeyPair secretKeyPair) {
super(scope, id, description, kp, algorithm, secretKeyPair);
}

@Override public String getFileName() {
Expand All @@ -59,8 +63,8 @@ private IdTokenFileCredentials(CredentialsScope scope, String id, String descrip
return new ByteArrayInputStream(token().getBytes(StandardCharsets.UTF_8));
}

@Override protected IdTokenCredentials clone(KeyPair kp, Secret privateKey) {
return new IdTokenFileCredentials(getScope(), getId(), getDescription(), kp, privateKey);
@Override protected IdTokenCredentials clone(KeyPair kp, SupportedKeyAlgorithm algorithm, SecretKeyPair secretKeyPair) {
return new IdTokenFileCredentials(getScope(), getId(), getDescription(), kp, algorithm, secretKeyPair);
}

@Symbol("idTokenFile")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.cloudbees.plugins.credentials.CredentialsScope;
import hudson.Extension;
import hudson.util.Secret;
import io.jenkins.plugins.oidc_provider.Keys.SupportedKeyAlgorithm;
import java.security.KeyPair;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
Expand All @@ -39,20 +40,24 @@ public final class IdTokenStringCredentials extends IdTokenCredentials implement

private static final long serialVersionUID = 1;

@DataBoundConstructor public IdTokenStringCredentials(CredentialsScope scope, String id, String description) {
@DataBoundConstructor public IdTokenStringCredentials(CredentialsScope scope, String id, String description, SupportedKeyAlgorithm algorithm) {
super(scope, id, description, algorithm);
}

public IdTokenStringCredentials(CredentialsScope scope, String id, String description) {
super(scope, id, description);
}

private IdTokenStringCredentials(CredentialsScope scope, String id, String description, KeyPair kp, Secret privateKey) {
super(scope, id, description, kp, privateKey);
private IdTokenStringCredentials(CredentialsScope scope, String id, String description, KeyPair kp, SupportedKeyAlgorithm algorithm, SecretKeyPair secretKeyPair) {
super(scope, id, description, kp, algorithm, secretKeyPair);
}

@Override public Secret getSecret() {
return Secret.fromString(token());
}

@Override protected IdTokenCredentials clone(KeyPair kp, Secret privateKey) {
return new IdTokenStringCredentials(getScope(), getId(), getDescription(), kp, privateKey);
@Override protected IdTokenCredentials clone(KeyPair kp, SupportedKeyAlgorithm algorithm, SecretKeyPair secretKeyPair) {
return new IdTokenStringCredentials(getScope(), getId(), getDescription(), kp, algorithm, secretKeyPair);
}

@Symbol("idToken")
Expand Down
Loading

0 comments on commit 018c16b

Please sign in to comment.