Skip to content
Open
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
Expand Up @@ -30,6 +30,7 @@
import com.nimbusds.jose.crypto.impl.DirectCryptoProvider;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.security.token.jwt.encryption.AbstractEncryptionConfiguration;
import java.util.Base64;

/**
* Secret encryption configuration.
Expand Down Expand Up @@ -59,7 +60,7 @@ public SecretEncryption(SecretEncryptionConfiguration secretEncryptionConfigurat
sb.append(" not supported for Secret encryption");
throw new ConfigurationException(sb.toString());
}
this.secret = secretEncryptionConfiguration.getSecret().getBytes(UTF_8);
this.secret = secretEncryptionConfiguration.isBase64() ? Base64.getDecoder().decode(secretEncryptionConfiguration.getSecret()) : secretEncryptionConfiguration.getSecret().getBytes(UTF_8);
this.method = secretEncryptionConfiguration.getEncryptionMethod();
this.algorithm = secretEncryptionConfiguration.getJweAlgorithm();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class SecretEncryptionConfiguration {
private JWEAlgorithm jweAlgorithm;
private EncryptionMethod encryptionMethod;
private String secret;
private boolean base64 = false;
private final String name;

/**
Expand Down Expand Up @@ -90,6 +91,22 @@ public void setSecret(String secret) {
this.secret = secret;
}

/**
* @return true if the secret is Base64 encoded
*/
public boolean isBase64() {
return base64;
}

/**
* Indicates whether the supplied secret is base64 encoded.
*
* @param base64 boolean flag indicating whether the supplied secret is base64 encoded
*/
public void setBase64(boolean base64) {
this.base64 = base64;
}

/**
*
* @return Bean qualifier name.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.micronaut.docs.encryptionbase64

import com.nimbusds.jose.EncryptionMethod
import com.nimbusds.jose.JOSEException
import com.nimbusds.jose.JWEAlgorithm
import com.nimbusds.jose.JWEHeader
import com.nimbusds.jose.crypto.DirectEncrypter
import com.nimbusds.jwt.EncryptedJWT
import com.nimbusds.jwt.JWTClaimsSet
import io.micronaut.security.testutils.ApplicationContextSpecification
import io.micronaut.security.token.jwt.encryption.EncryptionConfiguration
import io.micronaut.testutils.YamlAsciidocTagCleaner
import org.yaml.snakeyaml.Yaml

import java.nio.charset.StandardCharsets
import java.time.Instant

class JwtEncryptionBase64Spec extends ApplicationContextSpecification implements YamlAsciidocTagCleaner {

private static final String BASE64_SECRET = 'cGxlYXNlQ2hhbmdlVGhpc1NlY3JldEZvckFOZXdPbmU='
private static final String PLAIN_SECRET = 'pleaseChangeThisSecretForANewOne'

private static final String yamlConfig = """
#tag::yamlconfig[]
micronaut:
security:
token:
jwt:
encryptions:
secret:
generator:
secret: 'cGxlYXNlQ2hhbmdlVGhpc1NlY3JldEZvckFOZXdPbmU='
base64: true
jwe-algorithm: dir
encryption-method: A256GCM
#end::yamlconfig[]
"""

private static final Map<String, Object> configMap = [
'micronaut': [
'security': [
'token': [
'jwt': [
'encryptions': [
'secret': [
'generator': [
'secret': BASE64_SECRET,
'base64': true,
'jwe-algorithm': 'dir',
'encryption-method': 'A256GCM'
]
]
]
]
]
]
]
]

@Override
Map<String, Object> getConfiguration() {
super.configuration + flatten(configMap)
}

void "JWT encrypted with base64 encoded secret can be decrypted"() {
given: 'YAML config matches map config and base64 decodes correctly'
new Yaml().load(cleanYamlAsciidocTag(yamlConfig)) == configMap
new String(BASE64_SECRET.decodeBase64()) == PLAIN_SECRET

and:
EncryptionConfiguration encryptionConfiguration = getBean(EncryptionConfiguration)

when: 'create a JWT encrypted with the base64 decoded secret'
byte[] decodedSecret = Base64.getDecoder().decode(BASE64_SECRET)
EncryptedJWT jwt = createEncryptedJWT(decodedSecret)
String token = jwt.serialize()

then: 'the encrypted JWT can be decrypted using the EncryptionConfiguration'
encryptionConfiguration.decrypt(EncryptedJWT.parse(token))
}

void "JWT encrypted with wrong secret fails decryption"() {
given:
EncryptionConfiguration encryptionConfiguration = getBean(EncryptionConfiguration)

when: 'create a JWT encrypted with the plain text secret instead of base64 decoded'
byte[] plainSecret = BASE64_SECRET.getBytes(StandardCharsets.UTF_8)
EncryptedJWT jwt = createEncryptedJWT(plainSecret)
String token = jwt.serialize()

and: 'try to decrypt with our configured encrypter that expects base64-decoded secret'
encryptionConfiguration.decrypt(EncryptedJWT.parse(token))

then: 'decryption fails because the secrets do not match'
thrown(JOSEException)
}

void "JWT encryption works with base64 disabled"() {
given: 'configuration without base64 encoding'
Map<String, Object> plainConfig = [
'micronaut.security.token.jwt.encryptions.secret.generator.secret': PLAIN_SECRET,
'micronaut.security.token.jwt.encryptions.secret.generator.base64': false,
'micronaut.security.token.jwt.encryptions.secret.generator.jwe-algorithm': 'dir',
'micronaut.security.token.jwt.encryptions.secret.generator.encryption-method': 'A256GCM',
]

and:
def ctx = io.micronaut.context.ApplicationContext.run(plainConfig)
EncryptionConfiguration encryptionConfiguration = ctx.getBean(EncryptionConfiguration)

when: 'create a JWT encrypted with the plain text secret'
byte[] secretBytes = PLAIN_SECRET.getBytes(StandardCharsets.UTF_8)
EncryptedJWT jwt = createEncryptedJWT(secretBytes)
String token = jwt.serialize()

then: 'the encrypted JWT can be decrypted'
encryptionConfiguration.decrypt(EncryptedJWT.parse(token))

cleanup:
ctx?.close()
}

private EncryptedJWT createEncryptedJWT(byte[] secret) {
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("1234567890")
.claim("name", "John Doe")
.issueTime(Date.from(Instant.ofEpochSecond(1516239022)))
.build()

JWEHeader header = new JWEHeader(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
EncryptedJWT jwt = new EncryptedJWT(header, claimsSet)
DirectEncrypter encrypter = new DirectEncrypter(secret)
jwt.encrypt(encrypter)
return jwt
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,27 @@ import spock.lang.Specification

class SecretEncryptionSpec extends Specification {

//tag::yamlconfig[]
private final static String yamlConfig = """
micronaut:
security:
token:
jwt:
encryptions:
secret:
generator:
secret: pleaseChangeThisSecretForANewOne
jwe-algorithm: dir
encryption-method: A256GCM
"""
//end::yamlconfig[]

void "SecretEncryption constructor does not raise exception if jwe algorithm and encryption method set are valid"() {
given:
ApplicationContext ctx = ApplicationContext.run([
'micronaut.security.token.jwt.encryptions.secret.generator.secret': 'XXX',
'micronaut.security.token.jwt.encryptions.secret.generator.secret': 'pleaseChangeThisSecretForANewOne',
'micronaut.security.token.jwt.encryptions.secret.generator.jwe-algorithm': 'dir',
'micronaut.security.token.jwt.encryptions.secret.generator.encryption-method': 'A128CBC-HS256',
'micronaut.security.token.jwt.encryptions.secret.generator.encryption-method': 'A256GCM',
])

when:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
You can setup a link:{api}/io/micronaut/security/token/jwt/encryption/secret/SecretEncryptionConfiguration.html[SecretEncryptionConfiguration] qualified with `@Named` `generator` easily via configuration:

[configuration]
----
micronaut:
security:
token:
jwt:
encryptions:
secret:
generator:
secret: pleaseChangeThisSecretForANewOne
jwe-algorithm: dir
encryption-method: A256GCM
----

- **Change the `secret` property to your own secret and keep it safe**.
- `jwe-algorithm` specifies the JSON Web Encryption algorithm. In this example, `dir` (Direct Encryption with a Shared Symmetric Key).
- `encryption-method` specifies the encryption method. In this example, `A256GCM` (AES GCM using 256-bit key).

You can supply the secret with Base64 encoding.

[configuration]
----
include::{testssecurityjwt}/encryptionbase64/JwtEncryptionBase64Spec.groovy[indent=0, tag=yamlconfig]
----

- This example of `secret` is Base64 encoded
- Set `base64` to signal that the secret is Base64 encoded
1 change: 1 addition & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ authenticationStrategies:
jwtGeneratorRSA: Example of JWT Signed with RSA
jwtEncryption:
title: JWT Encryption
jwtEncryptionSecret: Example of JWT Encrypted with Secret
jwtGeneratorEncryption: Example of JWT Signed with Secret and Encrypted with RSA
claimsGeneration: Claims Generation
claimsValidation: Claims Validation
Expand Down
Loading