From 3047c4e1416f00e96ccbf96a7522ef14e648b2f6 Mon Sep 17 00:00:00 2001 From: Esta Nagy Date: Thu, 17 Feb 2022 22:16:38 +0100 Subject: [PATCH] Add request logging option (#22) - 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} --- .github/workflows/codeql-analysis.yml | 4 + .github/workflows/gradle-ci.yml | 15 +++ .github/workflows/gradle.yml | 15 +++ README.md | 9 +- build.gradle | 6 +- codecov.yml | 21 +++- lowkey-vault-app/README.md | 2 +- lowkey-vault-app/build.gradle | 2 +- .../lowkeyvault/AppConfiguration.java | 14 +++ .../key/KeyEntityToV72ModelConverter.java | 3 +- .../key/constants/SignatureAlgorithm.java | 37 ++++++- .../key/ReadOnlyKeyVaultKeyEntity.java | 4 +- .../key/impl/AesKeyVaultKeyEntity.java | 6 +- .../service/key/impl/EcKeyVaultKeyEntity.java | 35 ++++--- .../key/impl/RsaKeyVaultKeyEntity.java | 14 +-- .../src/main/resources/application.properties | 2 + .../key/KeyEntityToV72ModelConverterTest.java | 2 + .../key/constants/SignatureAlgorithmTest.java | 13 +++ .../key/impl/EcKeyVaultKeyEntityTest.java | 98 ++++++++++++++++--- lowkey-vault-client/README.md | 2 +- lowkey-vault-docker/build.gradle | 2 +- .../lowkeyvault/steps/KeysStepDefs.java | 8 +- lowkey-vault-testcontainers/README.md | 2 +- 23 files changed, 261 insertions(+), 55 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9d5cedb7..30446383 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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 ] @@ -32,6 +34,8 @@ on: - 'LICENSE' - '.github/ISSUE_TEMPLATE/**' - '.github/assets/**' + - '.github/dependabot.yml' + - '.github/workflows/**' permissions: # required for all workflows diff --git a/.github/workflows/gradle-ci.yml b/.github/workflows/gradle-ci.yml index 14958687..75500417 100644 --- a/.github/workflows/gradle-ci.yml +++ b/.github/workflows/gradle-ci.yml @@ -15,6 +15,8 @@ on: - 'LICENSE' - '.github/ISSUE_TEMPLATE/**' - '.github/assets/**' + - '.github/dependabot.yml' + - '.github/workflows/**' permissions: read-all @@ -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 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index da969978..e3055590 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -15,6 +15,8 @@ on: - 'LICENSE' - '.github/ISSUE_TEMPLATE/**' - '.github/assets/**' + - '.github/dependabot.yml' + - '.github/workflows/**' permissions: read-all @@ -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 diff --git a/README.md b/README.md index f2219c05..018d4d8b 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/build.gradle b/build.gradle index 9692c652..61d0d9d2 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/codecov.yml b/codecov.yml index 1013b4cb..7b9cce74 100644 --- a/codecov.yml +++ b/codecov.yml @@ -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 diff --git a/lowkey-vault-app/README.md b/lowkey-vault-app/README.md index edbcc937..ebacffd8 100644 --- a/lowkey-vault-app/README.md +++ b/lowkey-vault-app/README.md @@ -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 diff --git a/lowkey-vault-app/build.gradle b/lowkey-vault-app/build.gradle index 32b2a5a2..01afd615 100644 --- a/lowkey-vault-app/build.gradle +++ b/lowkey-vault-app/build.gradle @@ -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' } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/AppConfiguration.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/AppConfiguration.java index db8da5a3..35a01d1e 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/AppConfiguration.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/AppConfiguration.java @@ -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; @@ -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}") @@ -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; + } } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverter.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverter.java index 11f12d07..e910d048 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverter.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverter.java @@ -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; } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithm.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithm.java index 63a0838f..d9b1bd75 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithm.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithm.java @@ -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), @@ -76,4 +100,9 @@ public boolean isCompatibleWithCurve(final KeyCurveName keyCurveName) { return false; } + @JsonIgnore + public boolean supportsDigestLength(final int digestLength) { + return false; + } + } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyKeyVaultKeyEntity.java index bd13408b..c7a03950 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyKeyVaultKeyEntity.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyKeyVaultKeyEntity.java @@ -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(); diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java index 209c94a3..843aa3ff 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java @@ -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."); } } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java index 1b28c1b6..d8ad89be 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java @@ -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 implements ReadOnlyEcKeyVaultKeyEntity { @@ -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 @@ -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); } } diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java index 3d8d3e21..3791e126 100644 --- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java +++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java @@ -87,28 +87,28 @@ public byte[] decryptToBytes(@NonNull final byte[] encrypted, @NonNull final Enc } @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."); return doCrypto(() -> { final Signature rsaSign = Signature.getInstance(signatureAlgorithm.getAlg(), new BouncyCastleProvider()); rsaSign.initSign(getKey().getPrivate()); - rsaSign.update(clear); + rsaSign.update(digest); return rsaSign.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."); return doCrypto(() -> { final Signature rsaVerify = Signature.getInstance(signatureAlgorithm.getAlg(), new BouncyCastleProvider()); rsaVerify.initVerify(getKey().getPublic()); - rsaVerify.update(signed); - return rsaVerify.verify(digest); - }, "Cannot verify signed message.", log); + rsaVerify.update(digest); + return rsaVerify.verify(signature); + }, "Cannot verify digest message.", log); } } diff --git a/lowkey-vault-app/src/main/resources/application.properties b/lowkey-vault-app/src/main/resources/application.properties index 787dd5ed..c0ba67ee 100644 --- a/lowkey-vault-app/src/main/resources/application.properties +++ b/lowkey-vault-app/src/main/resources/application.properties @@ -11,3 +11,5 @@ server.error.include-binding-errors=always server.error.include-message=always # spring.jackson.generator.flush-passed-to-stream=true +# +logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverterTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverterTest.java index a8761c56..d79e5e28 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverterTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72ModelConverterTest.java @@ -143,6 +143,8 @@ void testConvertShouldConvertAllEcFieldsWhenCalledWithEcKeyEntity( Assertions.assertEquals(KeyType.EC, actual.getKey().getKeyType()); } Assertions.assertEquals(input.getKeyCurveName(), actual.getKey().getCurveName()); + Assertions.assertArrayEquals(input.getX(), actual.getKey().getX()); + Assertions.assertArrayEquals(input.getY(), actual.getKey().getY()); assertRsaFieldsAreNull(actual); assertOctFieldsAreNull(actual); diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithmTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithmTest.java index 6fca2ae4..8939fb80 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithmTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/constants/SignatureAlgorithmTest.java @@ -32,6 +32,19 @@ void testIsCompatibleWithCurveShouldReturnFalseInCaseOfRsaAlgorithm() { Assertions.assertFalse(actual); } + + @Test + void testSupportsDigestLengthShouldReturnFalseInCaseOfRsaAlgorithm() { + //given + final SignatureAlgorithm underTest = SignatureAlgorithm.PS256; + + //when + final boolean actual = underTest.supportsDigestLength(0); + + //then + Assertions.assertFalse(actual); + } + @ParameterizedTest @MethodSource("valueProvider") void forValue(final String inout, final SignatureAlgorithm expected) { diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java index f3ff22ac..9a8e073d 100644 --- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java +++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java @@ -12,10 +12,14 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import static com.github.nagyesta.lowkeyvault.TestConstants.*; @@ -25,6 +29,15 @@ class EcKeyVaultKeyEntityTest { + private static final String SHA_256 = "SHA-256"; + private static final String SHA_384 = "SHA-384"; + private static final String SHA_512 = "SHA-512"; + private static final Map HASH_ALGORITHMS = Map.of( + SignatureAlgorithm.ES256, SHA_256, + SignatureAlgorithm.ES256K, SHA_256, + SignatureAlgorithm.ES384, SHA_384, + SignatureAlgorithm.ES512, SHA_512); + public static Stream invalidValueProvider() { return Stream.builder() .add(Arguments.of(null, null, null)) @@ -52,6 +65,13 @@ public static Stream stringSignSource() { .build())); } + public static Stream digestSource() { + final Object bytes = DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8); + return Stream.builder() + .add(Arguments.of(bytes)) + .build(); + } + @ParameterizedTest @MethodSource("invalidValueProvider") void testConstructorShouldThrowExceptionWhenCalledWithNull( @@ -90,10 +110,12 @@ void testSignThenVerifyShouldReturnTrueWhenVerificationAndSignAreUsingTheSameDat VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, keyCurveName, false); underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); underTest.setEnabled(true); + final byte[] cipherSign = hash(clearSign.getBytes(StandardCharsets.UTF_8), algorithm); + final byte[] cipherVerify = hash(clearVerify.getBytes(StandardCharsets.UTF_8), algorithm); //when - final byte[] signature = underTest.signBytes(clearSign.getBytes(StandardCharsets.UTF_8), algorithm); - final boolean actual = underTest.verifySignedBytes(clearVerify.getBytes(StandardCharsets.UTF_8), algorithm, signature); + final byte[] signature = underTest.signBytes(cipherSign, algorithm); + final boolean actual = underTest.verifySignedBytes(cipherVerify, algorithm, signature); //then Assertions.assertEquals(clearSign.equals(clearVerify), actual); @@ -107,10 +129,11 @@ void testSignShouldThrowExceptionWhenOperationIsNotAllowed() { VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); underTest.setOperations(List.of(KeyOperation.VERIFY)); underTest.setEnabled(true); + final byte[] digest = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); //when Assertions.assertThrows(IllegalStateException.class, - () -> underTest.signBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256)); + () -> underTest.signBytes(digest, SignatureAlgorithm.ES256)); //then + exception } @@ -123,11 +146,12 @@ void testVerifyShouldThrowExceptionWhenOperationIsNotAllowed() { VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); underTest.setOperations(List.of(KeyOperation.SIGN)); underTest.setEnabled(true); + final byte[] digest = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); //when - final byte[] signature = underTest.signBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); + final byte[] signature = underTest.signBytes(digest, SignatureAlgorithm.ES256); Assertions.assertThrows(IllegalStateException.class, - () -> underTest.verifySignedBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256, signature)); + () -> underTest.verifySignedBytes(digest, SignatureAlgorithm.ES256, signature)); //then + exception } @@ -140,10 +164,11 @@ void testSignShouldThrowExceptionWhenOperationIsNotEnabled() { VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); underTest.setEnabled(false); + final byte[] digest = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); //when Assertions.assertThrows(IllegalStateException.class, - () -> underTest.signBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256)); + () -> underTest.signBytes(digest, SignatureAlgorithm.ES256)); //then + exception } @@ -156,12 +181,13 @@ void testVerifyShouldThrowExceptionWhenKeyIsNotEnabled() { VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); underTest.setEnabled(true); + final byte[] digest = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); //when - final byte[] signature = underTest.signBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); + final byte[] signature = underTest.signBytes(digest, SignatureAlgorithm.ES256); underTest.setEnabled(false); Assertions.assertThrows(IllegalStateException.class, - () -> underTest.verifySignedBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256, signature)); + () -> underTest.verifySignedBytes(digest, SignatureAlgorithm.ES256, signature)); //then + exception } @@ -174,10 +200,11 @@ void testSignShouldThrowExceptionWhenKeyCurveIsNotCompatible() { VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); underTest.setEnabled(true); + final byte[] digest = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); //when Assertions.assertThrows(IllegalStateException.class, - () -> underTest.signBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256K)); + () -> underTest.signBytes(digest, SignatureAlgorithm.ES256K)); //then + exception } @@ -190,12 +217,61 @@ void testVerifyShouldThrowExceptionWhenWhenKeyCurveIsNotCompatible() { VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); underTest.setEnabled(true); + final byte[] digest = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); //when - final byte[] signature = underTest.signBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); + final byte[] signature = underTest.signBytes(digest, SignatureAlgorithm.ES256); Assertions.assertThrows(IllegalStateException.class, - () -> underTest.verifySignedBytes(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256K, signature)); + () -> underTest.verifySignedBytes(digest, SignatureAlgorithm.ES256K, signature)); //then + exception } + + @ParameterizedTest + @MethodSource("digestSource") + @NullSource + void testSignShouldThrowExceptionWhenWhenDigestSizeIsNotCompatible(final byte[] digest) { + //given + final VaultFake vaultFake = new VaultFakeImpl(HTTPS_LOWKEY_VAULT); + final EcKeyVaultKeyEntity underTest = new EcKeyVaultKeyEntity( + VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); + underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); + underTest.setEnabled(true); + + //when + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.signBytes(digest, SignatureAlgorithm.ES256)); + + //then + exception + } + + @ParameterizedTest + @MethodSource("digestSource") + @NullSource + void testVerifyShouldThrowExceptionWhenWhenDigestSizeIsNotCompatible(final byte[] digest) { + //given + final VaultFake vaultFake = new VaultFakeImpl(HTTPS_LOWKEY_VAULT); + final EcKeyVaultKeyEntity underTest = new EcKeyVaultKeyEntity( + VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, KeyCurveName.P_256, false); + underTest.setOperations(List.of(KeyOperation.SIGN, KeyOperation.VERIFY)); + underTest.setEnabled(true); + final byte[] hash = hash(DEFAULT_VAULT.getBytes(StandardCharsets.UTF_8), SignatureAlgorithm.ES256); + + //when + final byte[] signature = underTest.signBytes(hash, SignatureAlgorithm.ES256); + Assertions.assertThrows(IllegalArgumentException.class, + () -> underTest.verifySignedBytes(digest, SignatureAlgorithm.ES256, signature)); + + //then + exception + } + + private byte[] hash(final byte[] text, final SignatureAlgorithm algorithm) { + try { + final MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHMS.get(algorithm)); + md.update(text); + return md.digest(); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } } diff --git a/lowkey-vault-client/README.md b/lowkey-vault-client/README.md index d19943ce..ee0430de 100644 --- a/lowkey-vault-client/README.md +++ b/lowkey-vault-client/README.md @@ -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=client&token=3ZZ9Q4S5WW)](https://img.shields.io/codecov/c/github/nagyesta/lowkey-vault?label=Coverage&flag=client&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 - Client diff --git a/lowkey-vault-docker/build.gradle b/lowkey-vault-docker/build.gradle index ec2936a1..1ea6d476 100644 --- a/lowkey-vault-docker/build.gradle +++ b/lowkey-vault-docker/build.gradle @@ -61,7 +61,7 @@ dockerRun { ports "8444:8443" daemonize true arguments '--rm' - env 'LOWKEY_ARGS': '--LOWKEY_VAULT_NAMES=keys-generic,keys-delete,' + + env 'LOWKEY_ARGS': '--LOWKEY_DEBUG_REQUEST_LOG=false --LOWKEY_VAULT_NAMES=keys-generic,keys-delete,' + 'keys-list-rsa-1,keys-list-rsa-2,keys-list-rsa-3,keys-list-rsa-5,keys-list-rsa-25,keys-list-rsa-42,' + 'keys-list-oct-1,keys-list-oct-2,keys-list-oct-3,keys-list-oct-5,keys-list-oct-25,keys-list-oct-42,' + 'keys-list-ec-1,keys-list-ec-2,keys-list-ec-3,keys-list-ec-5,keys-list-ec-25,keys-list-ec-42,' + diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java index 34109f87..64a02ae6 100644 --- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java +++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java @@ -271,7 +271,7 @@ public void theCreatedKeyIsUsedToEncryptClearTextWithAlgorithm(final byte[] text public void theCreatedKeyIsUsedToSignClearTextWithAlgorithm(final byte[] text, final SignatureAlgorithm algorithm) { final String keyId = context.getLastResult().getKey().getId(); context.setCryptographyClient(context.getProvider().getCryptoClient(keyId)); - final byte[] digest = hash(text); + final byte[] digest = hash(text, algorithm.toString()); final SignResult signResult = context.getCryptographyClient() .sign(algorithm, digest); context.setSignatureResult(signResult.getSignature()); @@ -302,14 +302,14 @@ public void theEncryptedValueIsDecryptedWithAlgorithm(final EncryptionAlgorithm public void theSignValueIsVerifiedWithAlgorithm(final byte[] text, final SignatureAlgorithm algorithm) { final String keyId = context.getLastResult().getKey().getId(); context.setCryptographyClient(context.getProvider().getCryptoClient(keyId)); - final byte[] digest = hash(text); + final byte[] digest = hash(text, algorithm.toString()); final VerifyResult verifyResult = context.getCryptographyClient().verify(algorithm, digest, context.getSignatureResult()); context.setVerifyResult(verifyResult.isValid()); } - private byte[] hash(final byte[] text) { + private byte[] hash(final byte[] text, final String algorithm) { try { - final MessageDigest md = MessageDigest.getInstance("SHA-256"); + final MessageDigest md = MessageDigest.getInstance("SHA-" + algorithm.substring(2, 5)); md.update(text); return md.digest(); } catch (final NoSuchAlgorithmException e) { diff --git a/lowkey-vault-testcontainers/README.md b/lowkey-vault-testcontainers/README.md index 84444385..5d0ed114 100644 --- a/lowkey-vault-testcontainers/README.md +++ b/lowkey-vault-testcontainers/README.md @@ -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=testcontainers&token=3ZZ9Q4S5WW)](https://img.shields.io/codecov/c/github/nagyesta/lowkey-vault?label=Coverage&flag=testcontainers&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 - Testcontainers