Skip to content

Commit

Permalink
Add request logging option (#22)
Browse files Browse the repository at this point in the history
- Adds new startup parameter turning on/off request logging
- Updates workflows to prevent triggering them in case of unrelated workflow changes
- Separates codecov coverages of subprojects
- Fixes EC key sign/verify operations
- Restores EC key mapping to return public key X, Y components
- Updates gradle plugin versions

Resolves #21
{patch}
  • Loading branch information
nagyesta authored Feb 17, 2022
1 parent 8acd8be commit 3047c4e
Show file tree
Hide file tree
Showing 23 changed files with 261 additions and 55 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ on:
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.github/assets/**'
- '.github/dependabot.yml'
- '.github/workflows/**'
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
Expand All @@ -32,6 +34,8 @@ on:
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.github/assets/**'
- '.github/dependabot.yml'
- '.github/workflows/**'

permissions:
# required for all workflows
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/gradle-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ on:
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.github/assets/**'
- '.github/dependabot.yml'
- '.github/workflows/**'

permissions: read-all

Expand Down Expand Up @@ -50,3 +52,16 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./lowkey-vault-app/build/reports/jacoco/report.xml
flags: app
- name: Upload coverage to Codecov - Client
uses: codecov/codecov-action@11d69070bf0bb19a473235e011c7890707db52de # v2.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./lowkey-vault-client/build/reports/jacoco/report.xml
flags: client
- name: Upload coverage to Codecov - Testcontainers
uses: codecov/codecov-action@11d69070bf0bb19a473235e011c7890707db52de # v2.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./lowkey-vault-testcontainers/build/reports/jacoco/report.xml
flags: testcontainers
15 changes: 15 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ on:
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.github/assets/**'
- '.github/dependabot.yml'
- '.github/workflows/**'

permissions: read-all

Expand Down Expand Up @@ -67,3 +69,16 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./lowkey-vault-app/build/reports/jacoco/report.xml
flags: app
- name: Upload coverage to Codecov - Client
uses: codecov/codecov-action@11d69070bf0bb19a473235e011c7890707db52de # v2.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./lowkey-vault-client/build/reports/jacoco/report.xml
flags: client
- name: Upload coverage to Codecov - Testcontainers
uses: codecov/codecov-action@11d69070bf0bb19a473235e011c7890707db52de # v2.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./lowkey-vault-testcontainers/build/reports/jacoco/report.xml
flags: testcontainers
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ Lowkey Vault is far from supporting all Azure Key Vault features. The list suppo
- Update secret
- Recover deleted secret

## Startup parameters

### Log requests

In order to support debugging integration, Lowkey Vault can log request data. To turn on this feature,
you must pass ```--LOWKEY_DEBUG_REQUEST_LOG=true``` as startup argument in the
```LOWKEY_ARGS``` env variable when starting the Docker container. [Example](lowkey-vault-docker/build.gradle#L64)

### Non-default vaults

In case you wish to use more than one vaults, you should consider registering additional vaults using
Expand Down Expand Up @@ -164,7 +172,6 @@ This issue should not happen while using Testcontainers. See examples under [Low
# Limitations

- Some encryption/signature algorithms are not supported. Please refer to the ["Features"](#features) section for the up-to-date list of supported algorithms.
- ```EC``` public keys are not exposed by the server to make Azure Cryptography Client always go to the server during encrypt/verify operations.
- Backup and restore features are not supported at the moment
- Certificate Vault features are not supported at the moment
- Recovery options cannot be set as vault creation is implicit during start-up
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id 'io.toolebox.git-versioner' version '1.6.5'
id 'org.sonatype.gradle.plugins.scan' version '2.2.2' apply false
id 'org.owasp.dependencycheck' version '6.5.2' apply false
id 'io.toolebox.git-versioner' version '1.6.7'
id 'org.sonatype.gradle.plugins.scan' version '2.2.3' apply false
id 'org.owasp.dependencycheck' version '6.5.3' apply false
}

group = 'com.github.nagyesta.lowkey-vault'
Expand Down
21 changes: 20 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,23 @@ coverage:
project:
default:
threshold: 0.50%
patch: off
app:
flags:
- app
client:
flags:
- client
testcontainers:
flags:
- testcontainers
patch: false
flags:
app:
paths:
- ./lowkey-vault-app
client:
paths:
- ./lowkey-vault-client
testcontainers:
paths:
- ./lowkey-vault-testcontainers
2 changes: 1 addition & 1 deletion lowkey-vault-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![latest-release](https://img.shields.io/github/v/tag/nagyesta/lowkey-vault?color=blue&logo=git&label=releases&sort=semver)](https://github.com/nagyesta/lowkey-vault/releases)
[![Docker Hub](https://img.shields.io/docker/v/nagyesta/lowkey-vault?label=docker%20hub&logo=docker&sort=semver)](https://hub.docker.com/repository/docker/nagyesta/lowkey-vault)
[![JavaCI](https://img.shields.io/github/workflow/status/nagyesta/lowkey-vault/JavaCI?logo=github)](https://img.shields.io/github/workflow/status/nagyesta/lowkey-vault/JavaCI?logo=github)
[![codecov](https://img.shields.io/codecov/c/github/nagyesta/lowkey-vault?label=Coverage&token=3ZZ9Q4S5WW)](https://img.shields.io/codecov/c/github/nagyesta/lowkey-vault?label=Coverage&token=3ZZ9Q4S5WW)
[![codecov](https://img.shields.io/codecov/c/github/nagyesta/lowkey-vault?label=Coverage&flag=app&token=3ZZ9Q4S5WW)](https://img.shields.io/codecov/c/github/nagyesta/lowkey-vault?label=Coverage&flag=app&token=3ZZ9Q4S5WW)
[![badge-abort-mission-armed-green](https://raw.githubusercontent.com/nagyesta/abort-mission/wiki_assets/.github/assets/badge-abort-mission-armed-green.svg)](https://github.com/nagyesta/abort-mission)

# Lowkey Vault - App
Expand Down
2 changes: 1 addition & 1 deletion lowkey-vault-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'org.springframework.boot' version '2.5.5'
id 'java'
//noinspection SpellCheckingInspection
id "io.freefair.lombok" version '6.2.0'
id 'io.freefair.lombok' version '6.4.0'
id 'com.github.nagyesta.abort-mission-gradle-plugin' version '2.1.0'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import com.github.nagyesta.lowkeyvault.service.vault.impl.VaultServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

import java.net.URI;
import java.util.Arrays;
Expand All @@ -17,6 +19,7 @@
@Slf4j
public class AppConfiguration {

private static final int PAYLOAD_LENGTH_BYTES = 512 * 1024;
@Value("${LOWKEY_VAULT_NAMES:primary,secondary}")
private String autoRegisterVaults;
@Value("${server.port}")
Expand Down Expand Up @@ -45,4 +48,15 @@ public VaultService vaultService() {

return service;
}

@Bean
@ConditionalOnExpression("${LOWKEY_DEBUG_REQUEST_LOG:false}")
public CommonsRequestLoggingFilter requestLoggingFilter() {
final CommonsRequestLoggingFilter requestLoggingFilter = new CommonsRequestLoggingFilter();
requestLoggingFilter.setIncludeClientInfo(true);
requestLoggingFilter.setIncludeQueryString(true);
requestLoggingFilter.setIncludePayload(true);
requestLoggingFilter.setMaxPayloadLength(PAYLOAD_LENGTH_BYTES);
return requestLoggingFilter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ private JsonWebKeyModel mapRsaFields(final ReadOnlyRsaKeyVaultKeyEntity entity)
private JsonWebKeyModel mapEcFields(final ReadOnlyEcKeyVaultKeyEntity entity) {
final JsonWebKeyModel jsonWebKeyModel = mapCommonKeyProperties(entity);
jsonWebKeyModel.setCurveName(entity.getKeyCurveName());
//skip mapping X and Y to not expose public key to client
jsonWebKeyModel.setX(entity.getX());
jsonWebKeyModel.setY(entity.getY());
return jsonWebKeyModel;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,53 @@
@SuppressWarnings("checkstyle:JavadocVariable")
public enum SignatureAlgorithm {

ES256("ES256", "NONEwithECDSA", KeyType.EC) {
ES256("ES256", "NONEwithECDSAinP1363Format", KeyType.EC) {
@Override
public boolean isCompatibleWithCurve(final KeyCurveName keyCurveName) {
return KeyCurveName.P_256 == keyCurveName;
}

@SuppressWarnings("checkstyle:MagicNumber")
@Override
public boolean supportsDigestLength(final int digestLength) {
return digestLength == 32;
}
},
ES256K("ES256K", "NONEwithECDSA", KeyType.EC) {
ES256K("ES256K", "NONEwithECDSAinP1363Format", KeyType.EC) {
@Override
public boolean isCompatibleWithCurve(final KeyCurveName keyCurveName) {
return KeyCurveName.P_256K == keyCurveName;
}

@SuppressWarnings("checkstyle:MagicNumber")
@Override
public boolean supportsDigestLength(final int digestLength) {
return digestLength == 32;
}
},
ES384("ES384", "NONEwithECDSA", KeyType.EC) {
ES384("ES384", "NONEwithECDSAinP1363Format", KeyType.EC) {
@Override
public boolean isCompatibleWithCurve(final KeyCurveName keyCurveName) {
return KeyCurveName.P_384 == keyCurveName;
}

@SuppressWarnings("checkstyle:MagicNumber")
@Override
public boolean supportsDigestLength(final int digestLength) {
return digestLength == 48;
}
},
ES512("ES512", "NONEwithECDSA", KeyType.EC) {
ES512("ES512", "NONEwithECDSAinP1363Format", KeyType.EC) {
@Override
public boolean isCompatibleWithCurve(final KeyCurveName keyCurveName) {
return KeyCurveName.P_521 == keyCurveName;
}

@SuppressWarnings("checkstyle:MagicNumber")
@Override
public boolean supportsDigestLength(final int digestLength) {
return digestLength == 64;
}
},
PS256("PS256", "SHA256withRSAandMGF1", KeyType.RSA),
PS384("PS384", "SHA384withRSAandMGF1", KeyType.RSA),
Expand Down Expand Up @@ -76,4 +100,9 @@ public boolean isCompatibleWithCurve(final KeyCurveName keyCurveName) {
return false;
}

@JsonIgnore
public boolean supportsDigestLength(final int digestLength) {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ default String decrypt(final byte[] encrypted, final EncryptionAlgorithm encrypt

byte[] decryptToBytes(byte[] encrypted, EncryptionAlgorithm encryptionAlgorithm, byte[] iv);

byte[] signBytes(byte[] clear, SignatureAlgorithm encryptionAlgorithm);
byte[] signBytes(byte[] digest, SignatureAlgorithm encryptionAlgorithm);

boolean verifySignedBytes(byte[] signed, SignatureAlgorithm encryptionAlgorithm, byte[] digest);
boolean verifySignedBytes(byte[] digest, SignatureAlgorithm encryptionAlgorithm, byte[] signature);

VersionedKeyEntityId getId();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ public byte[] decryptToBytes(@NonNull final byte[] encrypted, @NonNull final Enc
}

@Override
public byte[] signBytes(final byte[] clear, final SignatureAlgorithm encryptionAlgorithm) {
public byte[] signBytes(final byte[] digest, final SignatureAlgorithm encryptionAlgorithm) {
throw new UnsupportedOperationException("Sign is not supported for OCT keys.");
}

@Override
public boolean verifySignedBytes(final byte[] signed,
public boolean verifySignedBytes(final byte[] digest,
final SignatureAlgorithm encryptionAlgorithm,
final byte[] digest) {
final byte[] signature) {
throw new UnsupportedOperationException("Verify is not supported for OCT keys.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId;
import com.github.nagyesta.lowkeyvault.service.vault.VaultFake;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;

import java.lang.reflect.Array;
import java.security.KeyPair;
import java.security.Signature;
import java.security.interfaces.ECPublicKey;
import java.util.Optional;

@Slf4j
public class EcKeyVaultKeyEntity extends KeyVaultKeyEntity<KeyPair, KeyCurveName> implements ReadOnlyEcKeyVaultKeyEntity {
Expand All @@ -38,12 +39,12 @@ public KeyType getKeyType() {

@Override
public byte[] getX() {
return ((ECPublicKey) getKey().getPublic()).getW().getAffineX().toByteArray();
return ((BCECPublicKey) getKey().getPublic()).getQ().getAffineXCoord().getEncoded();
}

@Override
public byte[] getY() {
return ((ECPublicKey) getKey().getPublic()).getW().getAffineY().toByteArray();
return ((BCECPublicKey) getKey().getPublic()).getQ().getAffineYCoord().getEncoded();
}

@Override
Expand All @@ -62,30 +63,38 @@ public byte[] decryptToBytes(final byte[] encrypted, final EncryptionAlgorithm e
}

@Override
public byte[] signBytes(final byte[] clear, final SignatureAlgorithm signatureAlgorithm) {
public byte[] signBytes(final byte[] digest, final SignatureAlgorithm signatureAlgorithm) {
Assert.state(getOperations().contains(KeyOperation.SIGN), getId() + " does not have SIGN operation assigned.");
Assert.state(isEnabled(), getId() + " is not enabled.");
Assert.state(signatureAlgorithm.isCompatibleWithCurve(getKeyCurveName()), getId() + " is not using the right key curve.");
final int length = Optional.ofNullable(digest)
.map(Array::getLength)
.orElseThrow(() -> new IllegalArgumentException("Digest is null."));
Assert.isTrue(signatureAlgorithm.supportsDigestLength(length), getId() + " does not support digest length: " + length + ".");
return doCrypto(() -> {
final Signature ecSign = Signature.getInstance(signatureAlgorithm.getAlg(), new BouncyCastleProvider());
final Signature ecSign = Signature.getInstance(signatureAlgorithm.getAlg());
ecSign.initSign(getKey().getPrivate());
ecSign.update(clear);
ecSign.update(digest);
return ecSign.sign();
}, "Cannot sign message.", log);
}

@Override
public boolean verifySignedBytes(final byte[] signed,
public boolean verifySignedBytes(final byte[] digest,
final SignatureAlgorithm signatureAlgorithm,
final byte[] digest) {
final byte[] signature) {
Assert.state(getOperations().contains(KeyOperation.VERIFY), getId() + " does not have VERIFY operation assigned.");
Assert.state(isEnabled(), getId() + " is not enabled.");
Assert.state(signatureAlgorithm.isCompatibleWithCurve(getKeyCurveName()), getId() + " is not using the right key curve.");
final int length = Optional.ofNullable(digest)
.map(Array::getLength)
.orElseThrow(() -> new IllegalArgumentException("Digest is null."));
Assert.isTrue(signatureAlgorithm.supportsDigestLength(length), getId() + " does not support digest length: " + length + ".");
return doCrypto(() -> {
final Signature ecVerify = Signature.getInstance(signatureAlgorithm.getAlg(), new BouncyCastleProvider());
final Signature ecVerify = Signature.getInstance(signatureAlgorithm.getAlg());
ecVerify.initVerify(getKey().getPublic());
ecVerify.update(signed);
return ecVerify.verify(digest);
}, "Cannot verify signed message.", log);
ecVerify.update(digest);
return ecVerify.verify(signature);
}, "Cannot verify digest message.", log);
}
}
Loading

0 comments on commit 3047c4e

Please sign in to comment.