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

fix/jwtSync #71

Closed
wants to merge 7 commits into from
Closed
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
32 changes: 26 additions & 6 deletions src/main/java/com/spotify/github/v3/clients/JwtTokenIssuer.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,51 +30,71 @@
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.function.Supplier;

/** The helper Jwt token issuer. */
public class JwtTokenIssuer {

private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.RS256;
private static final long TOKEN_TTL = 600000;
private static final long TOKEN_TTL = 600_000L;
private static final long TOKEN_ISSUED = 60_000L;

private final PrivateKey signingKey;
private final Supplier<Date> issuedAt;

private JwtTokenIssuer(final PrivateKey signingKey) {
private JwtTokenIssuer(final PrivateKey signingKey, final Supplier<Date> issuedAt) {
this.signingKey = signingKey;
this.issuedAt = issuedAt;
}

/**
* Instantiates a new Jwt token issuer.
*
* @param privateKey the private key to use
* @param issuedAt the way to determine when the jwt is assumed to be issued at. Used to fix drift
* @throws NoSuchAlgorithmException the no such algorithm exception
* @throws InvalidKeySpecException the invalid key spec exception
*/
public static JwtTokenIssuer fromPrivateKey(final byte[] privateKey)
public static JwtTokenIssuer fromPrivateKey(final byte[] privateKey, final Supplier<Date> issuedAt)
throws NoSuchAlgorithmException, InvalidKeySpecException {

KeySpec keySpec = PKCS1PEMKey.loadKeySpec(privateKey)
.orElseGet(() -> new PKCS8EncodedKeySpec(privateKey));

KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey signingKey = kf.generatePrivate(keySpec);
return new JwtTokenIssuer(signingKey);
return new JwtTokenIssuer(signingKey, issuedAt);
}

/**
* Instantiates a new Jwt token issuer.
*
* @param privateKey the private key to use
* @throws NoSuchAlgorithmException the no such algorithm exception
* @throws InvalidKeySpecException the invalid key spec exception
*/
public static JwtTokenIssuer fromPrivateKey(final byte[] privateKey)
throws NoSuchAlgorithmException, InvalidKeySpecException {

Supplier<Date> defaultIssuedAt = () -> new Date(System.currentTimeMillis() - TOKEN_ISSUED);
return fromPrivateKey(privateKey, defaultIssuedAt);
}
/**
* Generates a JWT token for the given APP ID.
*
* @param appId the app id
* @return the token content
*/
public String getToken(final Integer appId) {

Date now = issuedAt.get();
return Jwts.builder()
.setId("github-auth")
.setSubject("authenticating via private key")
.setIssuer(String.valueOf(appId))
.signWith(signingKey, SIGNATURE_ALGORITHM)
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_TTL))
.setIssuedAt(new Date())
.setExpiration(new Date(now.getTime() + TOKEN_TTL))
.setIssuedAt(now)
.compact();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.google.common.io.Resources;
import java.net.URL;
import java.util.Base64;
import java.util.Date;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;

import org.junit.Test;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.impl.DefaultClaims;

public class JwtTokenIssuerTest {

private static final URL DER_KEY_RESOURCE =
Expand All @@ -36,23 +45,47 @@ public class JwtTokenIssuerTest {
// generated using this command: "openssl genrsa -out fake-github-app-key.pem 2048"
private static final URL PEM_KEY_RESOURCE =
Resources.getResource("com/spotify/github/v3/fake-github-app-key.pem");

private static final long TEN_MINUTES_MS = 10*60*1_000;

@Test
public void loadsDERFileWithPKCS8Key() throws Exception {
final byte[] key = Resources.toByteArray(DER_KEY_RESOURCE);
final JwtTokenIssuer tokenIssuer = JwtTokenIssuer.fromPrivateKey(key);

final String token = tokenIssuer.getToken(42);

assertThat(token, not(nullValue()));

// in this usecase the issued at should be 10 minutes (expected length of jwt) before expires
assertEquals(tokenDuration(token), TEN_MINUTES_MS);
}

@Test
public void loadsPEMFile() throws Exception {
final byte[] key = Resources.toByteArray(PEM_KEY_RESOURCE);
final JwtTokenIssuer tokenIssuer = JwtTokenIssuer.fromPrivateKey(key);
final JwtTokenIssuer tokenIssuer = JwtTokenIssuer.fromPrivateKey(key, () -> new Date());

final String token = tokenIssuer.getToken(42);
assertThat(token, not(nullValue()));

// in this usecase the issued at should be 10 minutes (expected length of jwt) before expires
assertEquals(tokenDuration(token), TEN_MINUTES_MS);
}

private long tokenDuration(String token) throws Exception {
// 0 - headers
// 1 - claims/data
// 2 - signature
String[] chunks = token.split("\\.");
String data = new String(Base64.getDecoder().decode(chunks[1]));

Claims claims = new ObjectMapper().readValue(data, DefaultClaims.class);

Date issuedAt = claims.getIssuedAt();
Date expires = claims.getExpiration();

return expires.getTime() - issuedAt.getTime();
}

}