Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make keypair format configurable #17

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}

}
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.Instant;
Expand Down Expand Up @@ -83,51 +82,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 @@ -147,18 +165,22 @@ public final String getAudience() {
this.audience = Util.fixEmpty(audience);
}

protected abstract IdTokenCredentials clone(KeyPair kp, Secret privateKey);
@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;

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

@DataBoundConstructor 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
Loading