Skip to content

Commit

Permalink
[Fixes #19] Implements a KeypairReader bean
Browse files Browse the repository at this point in the history
  • Loading branch information
massenz committed Jan 25, 2022
1 parent a272628 commit ff6fbc2
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 45 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ task logCoverageRatio {

allprojects {
group 'com.alertavert'
version '0.7.0'
version '0.7.1'

sourceCompatibility = JavaVersion.VERSION_17

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,19 @@

package com.alertavert.opa.configuration;

import com.alertavert.opa.thirdparty.PemUtils;
import com.alertavert.opa.security.crypto.KeypairFileReader;
import com.alertavert.opa.security.crypto.KeypairReader;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.alertavert.opa.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;

Expand All @@ -46,7 +43,6 @@
@EnableConfigurationProperties(KeyProperties.class)
public class KeyMaterialConfiguration {

// @Getter(onMethod=@__({@Bean}))
private final KeyProperties keyProperties;

public KeyMaterialConfiguration(KeyProperties properties) {
Expand Down Expand Up @@ -75,45 +71,46 @@ Algorithm hmac(KeyPair keyPair) {
}

@Bean
JWTVerifier verifier() throws IOException {
return JWT.require(hmac(keyPair()))
JWTVerifier verifier(KeypairReader reader) throws IOException {
return JWT.require(hmac(keyPair(reader)))
.withIssuer(issuer())
.build();
}

@Bean
public KeyPair keyPair() throws IOException {
return new KeyPair(loadPublicKey(), loadPrivateKey());
public KeyPair keyPair(KeypairReader reader) throws IOException {
return reader.loadKeys();
}

private PrivateKey loadPrivateKey() throws IOException {
KeyProperties.SignatureProperties properties = keyProperties.getSignature();

Path p = Paths.get(properties.getKeypair().getPriv());
log.info("Reading private key from file {}", p.toAbsolutePath());

PrivateKey pk = PemUtils.readPrivateKeyFromFile(p.toString(), properties.getAlgorithm());
if (pk == null) {
log.error("Could not read Public key");
throw new IllegalStateException(
String.format("Not a valid EC Private key file %s", p.toAbsolutePath()));
}
log.info("Read private key, format: {}", pk.getFormat());
return pk;
}

private PublicKey loadPublicKey() throws IOException {
KeyProperties.SignatureProperties properties = keyProperties.getSignature();
Path p = Paths.get(properties.getKeypair().getPub());
log.info("Reading public key from file {}", p.toAbsolutePath());

PublicKey pk = PemUtils.readPublicKeyFromFile(p.toString(), properties.getAlgorithm());
if (pk == null) {
log.error("Could not read Public key");
throw new IllegalStateException(
String.format("Not a valid EC Public key file %s", p.toAbsolutePath()));
}
log.info("Read public key, format: {}", pk.getFormat());
return pk;
/**
* Default key pair reader from the file system; to load a key pair from a different storage
* (e.g., Vault) implement your custom {@link KeypairReader} and inject it as a {@literal
* reader} bean.
*
* <p>This reader will interpret the {@literal keypair.priv,pub} properties as paths.
*
* <p>To use your custom {@link KeypairReader} implementation, define your bean as primary:
*
<pre>
&#64;Bean &#64;Primary
public KeypairReader reader() {
return new KeypairReader() {
&#64;Override
public KeyPair loadKeys() throws KeyLoadException {
// do something here
return someKeypair;
}
};
</pre>
*
* @return a reader which will try and load the key pair from the filesystem.
*/
@Bean
public KeypairReader filereader() {
KeyProperties.SignatureProperties props = keyProperties.getSignature();
return new KeypairFileReader(
props.getAlgorithm(),
Paths.get(props.getKeypair().getPriv()),
Paths.get(props.getKeypair().getPub()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 AlertAvert.com. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Marco Massenzio (marco@alertavert.com)
*/

package com.alertavert.opa.security.crypto;

import java.io.IOException;

/**
* <H2>KeyLoadException</H2>
*
* <p>Thrown when attempting to retrieve keys causes a failure.
*
* @author M. Massenzio, 2022-01-24
*/
public class KeyLoadException extends RuntimeException {
public KeyLoadException(String reason) {
super(reason);
}

public KeyLoadException(Throwable throwable) {
super(throwable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2022 AlertAvert.com. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Marco Massenzio (marco@alertavert.com)
*/

package com.alertavert.opa.security.crypto;

import com.alertavert.opa.thirdparty.PemUtils;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.helpers.MessageFormatter;

import java.io.IOException;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
* <H2>KeypairFileReader</H2>
*
* <p>Loads a Private/Public keypair from the filesystem.
*
* <p>This will interpret the key names as paths (if not absolute paths, relative to the
* directory from where the server was launched:
* <pre>
* tokens:
* signature:
* algorithm: "EC"
* keypair:
* priv: "../testdata/test-key.pem"
* pub: "../testdata/test-key-pub.pem"
* </pre>
*
* @author M. Massenzio, 2022-01-24
*/
@Slf4j @Value
public class KeypairFileReader implements KeypairReader {

public static final String ERROR_CANNOT_READ_KEY =
"Could not read key: path = {}, algorithm = {}";

String algorithm;
Path secretKeyPath;
Path publicKeyPath;


@Override
public KeyPair loadKeys() throws KeyLoadException {
return new KeyPair(loadPublicKey(), loadPrivateKey());
}

private PrivateKey loadPrivateKey() {
log.info("Reading private key from file {}", secretKeyPath.toAbsolutePath());
PrivateKey pk;
try {
pk = PemUtils.readPrivateKeyFromFile(secretKeyPath.toString(), algorithm);
} catch (IOException e) {
throw new KeyLoadException(e);
}
if (pk == null) {
log.error(ERROR_CANNOT_READ_KEY, secretKeyPath, algorithm);
throw new KeyLoadException(
MessageFormatter.format(ERROR_CANNOT_READ_KEY, secretKeyPath, algorithm).getMessage());
}
log.info("Read private key, format: {}", pk.getFormat());
return pk;
}

private PublicKey loadPublicKey() {
log.info("Reading public key from file {}", publicKeyPath.toAbsolutePath());
PublicKey pk;
try {
pk = PemUtils.readPublicKeyFromFile(publicKeyPath.toString(), algorithm);
} catch (IOException e) {
throw new KeyLoadException(e);
}
if (pk == null) {
log.error(ERROR_CANNOT_READ_KEY, publicKeyPath, algorithm);
throw new KeyLoadException(
MessageFormatter.format(ERROR_CANNOT_READ_KEY, publicKeyPath, algorithm).getMessage());
}
log.info("Read public key, format: {}", pk.getFormat());
return pk;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2022 AlertAvert.com. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Marco Massenzio (marco@alertavert.com)
*/

package com.alertavert.opa.security.crypto;

import org.springframework.context.annotation.Bean;

import java.io.IOException;
import java.security.KeyPair;

/**
* <H2>KeypairReader</H2>
*
* <p>Classes implementing this interface will retrieve keys from their storage for use with the
* application.
*
* @author M. Massenzio, 2022-01-24
*/
public interface KeypairReader {
KeyPair loadKeys() throws KeyLoadException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.alertavert.opa.AbstractTestBase;
import com.alertavert.opa.Constants;
import com.alertavert.opa.jwt.JwtTokenProvider;
import com.alertavert.opa.security.crypto.KeypairReader;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
Expand All @@ -39,6 +40,9 @@ class KeyMaterialConfigurationTest extends AbstractTestBase {
@Autowired
KeyMaterialConfiguration configuration;

@Autowired
KeypairReader reader;

@Value("${tokens.issuer}")
private String issuer;

Expand All @@ -49,7 +53,7 @@ void issuer() {

@Test
void hmac() throws IOException {
KeyPair pair = configuration.keyPair();
KeyPair pair = configuration.keyPair(reader);
assertThat(pair).isNotNull();
Algorithm hmac = configuration.hmac(pair);
assertThat(hmac).isNotNull();
Expand All @@ -58,21 +62,21 @@ void hmac() throws IOException {

@Test
void verifier() throws IOException {
JWTVerifier verifier = configuration.verifier();
JWTVerifier verifier = configuration.verifier(reader);
assertThat(verifier).isNotNull();
}

@Test
void keyPair() throws IOException {
KeyPair pair = configuration.keyPair();
KeyPair pair = configuration.keyPair(reader);
assertThat(pair).isNotNull();
assertThat(pair.getPrivate().getFormat()).isEqualTo("PKCS#8");
assertThat(pair.getPrivate().getAlgorithm()).isEqualTo(Constants.ELLIPTIC_CURVE);
}

@Test
void signVerify() throws IOException {
KeyPair pair = configuration.keyPair();
KeyPair pair = configuration.keyPair(reader);
Algorithm hmac = configuration.hmac(pair);

String token = JWT.create()
Expand All @@ -81,7 +85,7 @@ void signVerify() throws IOException {
.withClaim(JwtTokenProvider.ROLES, Lists.list("TEST"))
.sign(hmac);

JWTVerifier verifier = configuration.verifier();
JWTVerifier verifier = configuration.verifier(reader);
assertThat(verifier.verify(token)).isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@

package com.alertavert.opademo.configuration;

import com.alertavert.opa.security.crypto.KeyLoadException;
import com.alertavert.opa.security.crypto.KeypairReader;
import com.alertavert.opademo.data.ReactiveUsersRepository;
import com.alertavert.opademo.data.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;

import java.security.KeyPair;

import static com.alertavert.opa.Constants.EMPTY_USERDETAILS;

/**
Expand Down

0 comments on commit ff6fbc2

Please sign in to comment.