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

[Fixes #19] Implements a KeypairReader bean #20

Merged
merged 1 commit into from
Jan 25, 2022
Merged
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 @@ -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();
}
}
Loading