Skip to content

Commit

Permalink
feat: add secretBase64Encoded for HMAC algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
ThibaudAV committed Oct 7, 2024
1 parent 54afd3b commit dd9cfc9
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static java.security.KeyStore.*;

import com.google.common.primitives.Bytes;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
Expand All @@ -43,6 +44,7 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
Expand Down Expand Up @@ -117,7 +119,11 @@ public void onRequest(Request request, Response response, ExecutionContext execu
) {
jwsHeader = new JWSHeader.Builder(configuration.getSignature().getAlg()).keyID(configuration.getKid()).build();

signer = new MACSigner(configuration.getContent());
if (configuration.getSecretBase64Encoded()) {
signer = new MACSigner(java.util.Base64.getDecoder().decode(configuration.getContent()));
} else {
signer = new MACSigner(configuration.getContent().getBytes(StandardCharsets.UTF_8));
}
}

JWTClaimsSet claimsSet = buildClaims(executionContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@
import io.gravitee.policy.generatejwt.model.Claim;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.Setter;

/**
* @author David BRASSELY (david.brassely at graviteesource.com)
* @author GraviteeSource Team
*/
@Setter
@Getter
public class GenerateJwtPolicyConfiguration implements PolicyConfiguration {

private KeyResolver keyResolver = KeyResolver.INLINE;
Expand Down Expand Up @@ -55,125 +59,7 @@ public class GenerateJwtPolicyConfiguration implements PolicyConfiguration {

private String subject;

private List<Claim> customClaims;

public KeyResolver getKeyResolver() {
return keyResolver;
}

public void setKeyResolver(KeyResolver keyResolver) {
this.keyResolver = keyResolver;
}

public Signature getSignature() {
return signature;
}

public void setSignature(Signature signature) {
this.signature = signature;
}

public String getKid() {
return kid;
}

public void setKid(String kid) {
this.kid = kid;
}

public X509CertificateChain getX509CertificateChain() {
return x509CertificateChain;
}

public void setX509CertificateChain(X509CertificateChain x509CertificateChain) {
this.x509CertificateChain = x509CertificateChain;
}

public List<String> getAudiences() {
return audiences;
}

public void setAudiences(List<String> audiences) {
this.audiences = audiences;
}

public long getExpiresIn() {
return expiresIn;
}

public void setExpiresIn(long expiresIn) {
this.expiresIn = expiresIn;
}

public TimeUnit getExpiresInUnit() {
return expiresInUnit;
}

public void setExpiresInUnit(TimeUnit expiresInUnit) {
this.expiresInUnit = expiresInUnit;
}

public String getId() {
return id;
}
private Boolean secretBase64Encoded = false;

public void setId(String id) {
this.id = id;
}

public String getIssuer() {
return issuer;
}

public void setIssuer(String issuer) {
this.issuer = issuer;
}

public String getSubject() {
return subject;
}

public void setSubject(String subject) {
this.subject = subject;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public List<Claim> getCustomClaims() {
return customClaims;
}

public void setCustomClaims(List<Claim> customClaims) {
this.customClaims = customClaims;
}

public String getAlias() {
return alias;
}

public void setAlias(String alias) {
this.alias = alias;
}

public String getStorepass() {
return storepass;
}

public void setStorepass(String storepass) {
this.storepass = storepass;
}

public String getKeypass() {
return keypass;
}

public void setKeypass(String keypass) {
this.keypass = keypass;
}
private List<Claim> customClaims;
}
41 changes: 35 additions & 6 deletions src/main/resources/schemas/schema-form.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,38 @@
"alias" : {
"type" : "string",
"title": "Key alias",
"description": "Alias used to access a keystore entry. (only for JKS and PKCS#12)"
"description": "Alias used to access a keystore entry. (only for JKS and PKCS#12)",
"gioConfig": {
"displayIf": {
"$eq": {
"value.keyResolver": ["JKS", "PKCS12"]
}
}
}
},
"storepass" : {
"type" : "string",
"title": "Store password",
"description": "Pass used to access the key store. (only for JKS and PKCS#12)"
"description": "Pass used to access the key store. (only for JKS and PKCS#12)",
"gioConfig": {
"displayIf": {
"$eq": {
"value.keyResolver": ["JKS", "PKCS12"]
}
}
}
},
"keypass" : {
"type" : "string",
"title": "Key password",
"description": "Pass used to access the particular key pair's private key. (only for JKS)"
"description": "Pass used to access the particular key pair's private key. (only for JKS)",
"gioConfig": {
"displayIf": {
"$eq": {
"value.keyResolver": ["JKS"]
}
}
}
},
"kid" : {
"type" : "string",
Expand Down Expand Up @@ -163,10 +184,18 @@
"allowDropFileTypes": true
}
},
"format": "gio-code-editor",
"format": "gio-code-editor"
},
"secretBase64Encoded": {
"type" : "boolean",
"title": "Secret base64 encoded",
"description": "Whether the secret key is base64 encoded. (only for HMAC algorithms)",
"default": false,
"gioConfig": {
"monacoEditorConfig": {
"language": "json"
"displayIf": {
"$eq": {
"value.signature": ["HMAC_HS256", "HMAC_HS384", "HMAC_HS512"]
}
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.verification.LoggedRequest;
import com.google.common.primitives.Bytes;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.gravitee.apim.gateway.tests.sdk.AbstractPolicyTest;
Expand All @@ -33,7 +37,9 @@
import io.vertx.core.http.HttpMethod;
import io.vertx.rxjava3.core.http.HttpClient;
import io.vertx.rxjava3.core.http.HttpClientRequest;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -79,4 +85,35 @@ void shouldGenerateJWTWithCustomClaims(HttpClient httpClient) throws Interrupted
assertThat(claimsSet.getStringClaim("claim1")).isEqualTo("claim1-value");
assertThat(claimsSet.getClaim("claim2")).isEqualTo("/test");
}

@Test
@DisplayName("Should generate a JWT token with secret base64 encoded")
@DeployApi("/apis/generate-jwt-secret-base64.json")
void shouldGenerateJWTWithSecretBase64Encoded(HttpClient httpClient) throws InterruptedException, ParseException, JOSEException {
wiremock.stubFor(get("/endpoint").willReturn(ok()));

httpClient
.rxRequest(HttpMethod.GET, "/test-jwt-secret-base64")
.flatMap(HttpClientRequest::rxSend)
.test()
.await()
.assertComplete()
.assertValue(response -> {
assertThat(response.statusCode()).isEqualTo(200);
return true;
})
.assertNoErrors();

List<LoggedRequest> requests = wiremock.findRequestsMatching(getRequestedFor(urlPathEqualTo("/endpoint")).build()).getRequests();
assertThat(requests).hasSize(1);
String generatedJwt = requests.get(0).getHeader(GENERATED_JWT_HEADER_NAME);
SignedJWT signedJWT = SignedJWT.parse(generatedJwt);
JWSHeader jwsHeader = signedJWT.getHeader();
JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();

JWSVerifier verifier = new MACVerifier("I'm a valid Base64 key with at least 256 bits\n");
assertThat(signedJWT.verify(verifier)).isTrue();

assertThat(jwsHeader.getAlgorithm()).isEqualTo(JWSAlgorithm.HS256);
}
}
72 changes: 72 additions & 0 deletions src/test/resources/apis/generate-jwt-secret-base64.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"id": "my-api-jwt-secret-base64",
"name": "my-api",
"gravitee": "2.0.0",
"proxy": {
"context_path": "/test-jwt-secret-base64",
"endpoints": [
{
"name": "default",
"target": "http://localhost:8080/endpoint",
"http": {
"connectTimeout": 3000,
"readTimeout": 60000
}
}
]
},
"flows": [
{
"name": "flow-1",
"methods": [
"GET"
],
"enabled": true,
"path-operator": {
"path": "/",
"operator": "STARTS_WITH"
},
"pre": [
{
"name": "Generate JWT",
"description": "",
"enabled": true,
"policy": "policy-generate-jwt",
"configuration": {
"signature": "HMAC_HS256",
"expiresIn": 30,
"expiresInUnit": "SECONDS",
"issuer": "urn://gravitee-api-gw",
"audiences": [
"graviteeam"
],
"customClaims": [
{
"name": "claim1",
"value": "claim1-value"
},
{
"name": "claim2",
"value": "{#request.path}"
}
],
"id": "817c6cfa-6ae6-446e-a631-5ded215b404b",
"kid": "my-kid",
"//📝 Content Decoded": "I'm a valid Base64 key with at least 256 bits",
"content": "SSdtIGEgdmFsaWQgQmFzZTY0IGtleSB3aXRoIGF0IGxlYXN0IDI1NiBiaXRzCg==",
"secretBase64Encoded": true
}
},
{
"name": "Generate JWT",
"description": "",
"enabled": true,
"policy": "jwt-attributes-to-headers",
"configuration": {
}
}
],
"post": []
}
]
}

0 comments on commit dd9cfc9

Please sign in to comment.