diff --git a/.github/workflows/mvn.yml b/.github/workflows/mvn.yml index f0ad74c..e76d9ca 100644 --- a/.github/workflows/mvn.yml +++ b/.github/workflows/mvn.yml @@ -6,35 +6,38 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 + - name: Install socat tool + run: | + sudo apt-get update + sudo apt-get install socat - name: Cache Docker layers - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ./parsec-docker-test-image/parsec_docker_cache key: ${{ runner.os }}-parsec_docker_cache-${{ github.sha }} restore-keys: | ${{ runner.os }}-parsec_docker_cache- - name: Set up JDK 16 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: '16' - distribution: 'zulu' + java-version: "16" + distribution: "zulu" architecture: x64 cache: maven - name: Build with Maven # still needs work to get tests running on java 16 run: ./mvnw --batch-mode clean verify -DskipTests=true - name: Set up JDK 8 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: '8' - distribution: 'zulu' + java-version: "8" + distribution: "zulu" architecture: x64 cache: maven - name: Build with Maven run: ./mvnw --batch-mode clean verify - diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f5eb29b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "maven.view": "hierarchical" +} diff --git a/README.md b/README.md index 3f5f08d..3876541 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains a Java Client and a [JCA provider](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html) for Parsec. The client exposes the [PSA Crypto API](https://github.com/ARMmbed/mbed-crypto/blob/psa-crypto-api/docs/PSA_Cryptography_API_Specification.pdf) to Java Applications and the JCA Provider allows existing applications that employ JCA to switch to Parsec. -*Note*: this client is at an early stage of development and not yet ready for production use. We welcome contributions! +_Note_: this client is at an early stage of development and not yet ready for production use. We welcome contributions! ## In this repository @@ -12,16 +12,31 @@ The repository contains the following packages: - parsec-jca-java: JCA Provider - parsec-interface-java: Private wrapper for protobuf classes and socket communication - parsec-protobuf-java: Java Protobuf classes (generated) -- parsec-testcontainers: Collection of Docker test containers for development & testing +- parsec-testcontainers: Collection of Docker test containers for development & testing # How to use this library + TODO # How to develop the Parsec Java Client -TODO + +Check out this repo's submodules: + +```sh +git submodule update --init --recursive +``` + +You can use `act` to run the github action locally. On OSX, you need to set the container architecture, and for testcontainers to work, you may need to set the env var `TESTCONTAINERS_HOST_OVERRIDE`. + +Example CLI input: + +```sh +act --container-architecture linux/amd64 --env TESTCONTAINERS_HOST_OVERRIDE=`ipconfig getifaddr en0` +``` # Example Implementations -There are a number of example implementations of both the basic java client and JCA provider along with a demo (separate repository) + +There are a number of example implementations of both the basic java client and JCA provider along with a demo (separate repository) Both the tests and workshop demo cover the basic functionality of the current implementation: - Parsec JCA Tests [**Link**](/parsec-jca-test) @@ -34,10 +49,8 @@ The software is provided under Apache-2.0. Contributions to this project are acc ## Contributing -We welcome contributing, both in the use of this client library and programming,extending of this library code base. +We welcome contributing, both in the use of this client library and programming,extending of this library code base. Please check the [**Contribution Guidelines**](https://parallaxsecond.github.io/parsec-book/contributing/index.html) to know more about the contribution process. -*Copyright 2021 Contributors to the Parsec project.* - - +_Copyright 2021 Contributors to the Parsec project._ diff --git a/build_demo.sh b/build_demo.sh index 6dbda81..a0d9ea8 100755 --- a/build_demo.sh +++ b/build_demo.sh @@ -16,58 +16,57 @@ function dirty_build_on_new_comits() { awslabs/aws-crt-java \ aws/aws-iot-device-sdk-java-v2 \ revaultch/aws-greengrass-nucleus; do - curl -S https://api.github.com/repos/${repo}/commits/key-op-prototype - done | ${md5_cmd} | cut -d" " -f1 > greengrass_demo/dirty_repo.txt + curl -S https://api.github.com/repos/${repo}/commits/key-op-prototype + done | ${md5_cmd} | cut -d" " -f1 >greengrass_demo/dirty_repo.txt touch -t 190001010000 greengrass_demo/dirty_repo.txt export DIRTY_TS=$(cat greengrass_demo/dirty_repo.txt) } function build_greengrass_patched() { -pushd examples/greengrass/parsec-greengrass-run-config/docker/ -docker build . \ - --build-arg BUILD_TS=${DIRTY_TS} \ - --tag parallaxsecond/greengrass_patched:latest \ - --progress plain -popd + pushd examples/greengrass/parsec-greengrass-run-config/docker/ + docker build . \ + --build-arg BUILD_TS=${DIRTY_TS} \ + --tag parallaxsecond/greengrass_patched:latest \ + --progress plain + popd } function copy_deps_from_greengrass_patched_to_local() { docker run -v ~/.m2/repository:/host_m2_repository parallaxsecond/greengrass_patched:latest \ - /bin/bash -c "cp -r ~/.m2/repository/* /host_m2_repository" + /bin/bash -c "cp -r ~/.m2/repository/* /host_m2_repository" } function build_parsec_containers() { -pushd ./parsec-testcontainers/ -./build.sh -popd + pushd ./parsec-testcontainers/ + ./build.sh + popd } function build_greengrass_with_provider() { - docker build . -f greengrass_demo/Dockerfile --tag parallaxsecond/greengrass_demo:latest --progress plain + docker build . -f greengrass_demo/Dockerfile --tag parallaxsecond/greengrass_demo:latest --progress plain } function parsec_run() { - docker rm -f parsec_docker_run 2> /dev/null - docker run -d --name parsec_docker_run \ - -ti \ - -v GG_PARSEC_STORE:/var/lib/parsec/mappings \ - -v GG_PARSEC_SOCK:/run/parsec \ - parallaxsecond/parsec:0.8.1 + docker rm -f parsec_docker_run 2>/dev/null + docker run -d --name parsec_docker_run \ + -ti \ + -v GG_PARSEC_STORE:/parsec/quickstart/mappings\ -v GG_PARSEC_SOCK:/run/parsec \ + parallaxsecond/parsec:latest } function gg_run() { - docker rm -f "${1}" 2> /dev/null + docker rm -f "${1}" 2>/dev/null # shellcheck disable=SC2086 docker run ${3} \ - --name "${1}" \ - -e GG_THING_NAME="${GG_THING_NAME}" \ - -e GG_ADDITIONAL_CMD_ARGS="--trusted-plugin /provider.jar" \ - -e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" \ - -e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" \ - -e AWS_REGION="${AWS_REGION}" \ - -e AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN}" \ - -v GG_PARSEC_SOCK:/run/parsec \ - -v GG_HOME:/home/ggc_user \ - parallaxsecond/greengrass_demo:latest "${2}" + --name "${1}" \ + -e GG_THING_NAME="${GG_THING_NAME}" \ + -e GG_ADDITIONAL_CMD_ARGS="--trusted-plugin /provider.jar" \ + -e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" \ + -e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" \ + -e AWS_REGION="${AWS_REGION}" \ + -e AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN}" \ + -v GG_PARSEC_SOCK:/run/parsec \ + -v GG_HOME:/home/ggc_user \ + parallaxsecond/greengrass_demo:latest "${2}" } function run_demo() { parsec_run diff --git a/parsec-client-java/pom.xml b/parsec-client-java/pom.xml index ed6d357..c84960e 100644 --- a/parsec-client-java/pom.xml +++ b/parsec-client-java/pom.xml @@ -1,7 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -15,7 +14,6 @@ 1.8 1.8 - ${project.groupId} @@ -25,17 +23,22 @@ org.slf4j slf4j-api - 1.7.32 + ${slf4j.version} net.java.dev.jna jna - 5.9.0 + 5.17.0 io.spiffe java-spiffe-provider - 0.7.0 + 0.8.11 + + + ch.qos.logback + logback-classic + ${logback.version} @@ -45,16 +48,29 @@ test - ch.qos.logback - logback-classic - 1.2.5 + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} test org.junit.jupiter - junit-jupiter-api - 5.8.0 + junit-jupiter-engine + ${junit.jupiter.version} test + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + none + + + + \ No newline at end of file diff --git a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/BasicClient.java b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/BasicClient.java index 18496a9..71b22b2 100644 --- a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/BasicClient.java +++ b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/BasicClient.java @@ -1,38 +1,47 @@ package org.parallaxsecond.parsec.client.core; +import java.time.Duration; +import static java.util.Optional.ofNullable; + import org.parallaxsecond.parsec.client.Authentication; import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; -import org.parallaxsecond.parsec.client.exceptions.*; +import org.parallaxsecond.parsec.client.exceptions.InvalidProviderException; +import org.parallaxsecond.parsec.client.exceptions.InvalidServiceResponseTypeException; +import org.parallaxsecond.parsec.client.exceptions.MissingParamException; +import org.parallaxsecond.parsec.client.exceptions.NoAuthenticatorException; +import org.parallaxsecond.parsec.client.exceptions.NoProviderException; +import org.parallaxsecond.parsec.client.exceptions.NotFoundException; +import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm; +import org.parallaxsecond.parsec.protobuf.psa_key_attributes.PsaKeyAttributes; +import org.parallaxsecond.parsec.protobuf.psa_raw_key_agreement.PsaRawKeyAgreement; import org.parallaxsecond.parsec.protocol.operations.NativeOperation; import org.parallaxsecond.parsec.protocol.operations.NativeResult; import org.parallaxsecond.parsec.protocol.requests.ProviderId; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm; -import org.parallaxsecond.parsec.protobuf.psa_key_attributes.PsaKeyAttributes; -import org.parallaxsecond.parsec.protobuf.psa_raw_key_agreement.PsaRawKeyAgreement; - -import java.time.Duration; - -import static java.util.Optional.ofNullable; /** * Core client for the Parsec service * - *

The client exposes low-level functionality for using the Parsec service. Below you can see - * code examples for a few of the operations supported. + *

+ * The client exposes low-level functionality for using the Parsec service. Below you can see code + * examples for a few of the operations supported. * - *

Providers are abstracted representations of the secure elements that Parsec offers abstraction + *

+ * Providers are abstracted representations of the secure elements that Parsec offers abstraction * over. Providers are the ones to execute the cryptographic operations requested by the user. * - *

For all cryptographic operations an implicit provider is used which can be changed between + *

+ * For all cryptographic operations an implicit provider is used which can be changed between * operations. The client starts with the default provider, the first one returned by the * ListProviders operation. * - *

For crypto operations, if the implicit client provider is `ProviderId.CORE`, a client error of + *

+ * For crypto operations, if the implicit client provider is `ProviderId.CORE`, a client error of * `InvalidProvider` type is returned. See the operation-specific response codes returned by the * service in the operation's page * [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/index.html). @@ -44,27 +53,35 @@ @Slf4j @SuppressWarnings("unused") public class BasicClient { + private OperationClient operationClient; private Authentication authData; private ProviderId implicitProvider; + /** * Create a new Parsec client. * - *

The client will be initialised with default values obtained from the service for the - * implicit provider and for application identity. + *

+ * The client will be initialised with default values obtained from the service for the implicit + * provider and for application identity. * - *

* `app_name` is the application name to be used if direct authentication is the default + *

+ * * `app_name` is the application name to be used if direct authentication is the default * authentication choice * - *

This client will use the default configuration. That includes using a Protobuf converter for + *

+ * This client will use the default configuration. That includes using a Protobuf converter for * message bodies and a Unix Domain Socket IPC handler. The default timeout length is 60 seconds. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); # Ok(())} ``` + *

+ * let client: BasicClient = BasicClient::new(None); # Ok(())} ``` */ public static BasicClient client(String appName) { return client(appName, null); @@ -78,10 +95,6 @@ public static BasicClient client(String appName, IpcHandler ipcHandler) { } client.setDefaultProvider(); client.setDefaultAuth(appName); - log.debug( - "Parsec BasicClient created with implicit provider \"{}\" and authentication data \"{}\"", - client.implicitProvider(), - client.authData()); return client; } @@ -89,37 +102,48 @@ public static BasicClient client(String appName, IpcHandler ipcHandler) { * Create a client that can initially only be used with Core operations not necessitating * authentication (eg ping). * - *

Setting an authentication method and an implicit provider is needed before calling crypto + *

+ * Setting an authentication method and an implicit provider is needed before calling crypto * operations. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; let client = BasicClient::new_naked(); let (major, minor) = * client.ping(); # Ok(())} ``` */ public static BasicClient naked() { - return new BasicClient( - OperationClient.withDefaults(), new Authentication.None(), ProviderId.CORE); + return new BasicClient(OperationClient.withDefaults(), new Authentication.None(), + ProviderId.CORE); } + /** * Query the service for the list of authenticators provided and use the first one as default * - *

* `app_name` is to be used if direct authentication is the default choice + *

+ * * `app_name` is to be used if direct authentication is the default choice * - *

# Errors + *

+ * # Errors * - *

If no authenticator is reported by the service, a `NoAuthenticator` error kind is returned. + *

+ * If no authenticator is reported by the service, a `NoAuthenticator` error kind is returned. * - *

If the default authenticator is `DirectAuthenticator` and `app_name` was set to `None`, an + *

+ * If the default authenticator is `DirectAuthenticator` and `app_name` was set to `None`, an * error of type `MissingParam` is returned. * - *

If none of the authenticators returned by the service is supported, `NoAuthenticator` is + *

+ * If none of the authenticators returned by the service is supported, `NoAuthenticator` is * returned. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use parsec_client::core::interface::requests::ProviderId; let mut * client = BasicClient::new_naked(); Set the default authenticator but choose a specific * provider. client.set_implicitProvider(ProviderId::Pkcs11); @@ -130,14 +154,12 @@ public void setDefaultAuth(String appName) { if (authenticators.getAuthenticators().isEmpty()) { throw new NoAuthenticatorException(null); } - for (NativeResult.ListAuthenticatorsResult.AuthenticatorInfo authenticator : - authenticators.getAuthenticators()) { + for (NativeResult.ListAuthenticatorsResult.AuthenticatorInfo authenticator : authenticators + .getAuthenticators()) { switch (authenticator.getId()) { case DIRECT: - this.authData = - ofNullable(appName) - .map(Authentication.Direct::new) - .orElseThrow(MissingParamException::new); + this.authData = ofNullable(appName).map(Authentication.Direct::new) + .orElseThrow(MissingParamException::new); break; case UNIX_PEER_CREDENTIALS: this.authData = new Authentication.UnixPeerCredentials(); @@ -147,8 +169,7 @@ public void setDefaultAuth(String appName) { this.authData = new Authentication.JwtSvid(); break; default: - log.warn( - "Authenticator of type \"{}\" not supported by this client library", + log.warn("Authenticator of type \"{}\" not supported by this client library", authenticator.getId()); continue; } @@ -156,14 +177,18 @@ public void setDefaultAuth(String appName) { } throw new NoAuthenticatorException(null); } + /** * Update the authentication data of the client. * - *

This is useful if you want to use a different authentication method than the default one. + *

+ * This is useful if you want to use a different authentication method than the default one. * - *

# Example + *

+ * # Example * - *

See [`set_default_provider`]. + *

+ * See [`set_default_provider`]. */ public void setAuthData(Authentication authData) { this.authData = authData; @@ -172,9 +197,11 @@ public void setAuthData(Authentication authData) { /** * Retrieve authentication data of the client. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use parsec_client::auth::Authentication; let mut client = * BasicClient::new_naked(); client.set_authData(Authentication::UnixPeerCredentials); * assert_eq!(Authentication::UnixPeerCredentials, client.authData()); # Ok(())} ``` @@ -182,16 +209,21 @@ public void setAuthData(Authentication authData) { public Authentication authData() { return this.authData; } + /** * Query for the service provider list and set the default one as implicit * - *

# Errors + *

+ * # Errors * - *

If no provider is returned by the service, an client error of `NoProvider` type is returned. + *

+ * If no provider is returned by the service, an client error of `NoProvider` type is returned. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use parsec_client::auth::Authentication; let mut client = * BasicClient::new_naked(); Use the default provider but use a specific authentication. * client.set_default_provider(); client.set_authData(Authentication::UnixPeerCredentials); # @@ -204,12 +236,15 @@ public void setDefaultProvider() { } this.implicitProvider = providers.getProviders().get(0).getId(); } + /** * Set the provider that the client will be implicitly working with. * - *

# Example + *

+ * # Example * - *

See [`set_default_auth`]. + *

+ * See [`set_default_auth`]. */ public void setImplicitProvider(ProviderId provider) { this.implicitProvider = provider; @@ -218,9 +253,11 @@ public void setImplicitProvider(ProviderId provider) { /** * Retrieve client's implicit provider. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use parsec_client::core::interface::requests::ProviderId; let mut * client = BasicClient::new_naked(); client.set_implicitProvider(ProviderId::Pkcs11); * assert_eq!(ProviderId::Pkcs11, client.implicitProvider()); # Ok(())} ``` @@ -232,26 +269,28 @@ public ProviderId implicitProvider() { /** * **[Core Operation]** List the opcodes supported by the specified provider. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { # use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { # use * std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use parsec_client::core::interface::requests::{Opcode, ProviderId}; * - *

let client: BasicClient = BasicClient::new(None); let opcodes = + *

+ * let client: BasicClient = BasicClient::new(None); let opcodes = * client.list_opcodes(ProviderId::Pkcs11); if opcodes.contains(&Opcode::PsaGenerateRandom) { let * random_bytes = client.psa_generate_random(10); } # Ok(())} # Ok(())} ``` */ public NativeResult.ListOpcodesResult listOpcodes(ProviderId provider) { - NativeResult res = - this.operationClient.processOperation( - NativeOperation.ListOpcodesOperation.builder().providerId(provider).build(), - ProviderId.CORE, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.ListOpcodesOperation.builder().providerId(provider).build(), + ProviderId.CORE, this.authData); if (res instanceof NativeResult.ListOpcodesResult) { return (NativeResult.ListOpcodesResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -260,25 +299,26 @@ public NativeResult.ListOpcodesResult listOpcodes(ProviderId provider) { /** * **[Core Operation]** List the providers that are supported by the service. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let mut client: BasicClient = BasicClient::new_naked(); let providers = + *

+ * let mut client: BasicClient = BasicClient::new_naked(); let providers = * client.list_providers(); Set the second most prioritary provider * client.set_implicitProvider(providers[1].id); # Ok(())} ``` */ public NativeResult.ListProvidersResult listProviders() { - NativeResult res = - this.operationClient.processOperation( - NativeOperation.ListProvidersOperation.builder().build(), - ProviderId.CORE, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.ListProvidersOperation.builder().build(), ProviderId.CORE, this.authData); if (res instanceof NativeResult.ListProvidersResult) { return (NativeResult.ListProvidersResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -287,142 +327,161 @@ public NativeResult.ListProvidersResult listProviders() { /** * **[Core Operation]** List the authenticators that are supported by the service. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); let opcodes = - * client.list_authenticators(); # Ok(())} ``` + *

+ * let client: BasicClient = BasicClient::new(None); let opcodes = client.list_authenticators(); # + * Ok(())} ``` */ public NativeResult.ListAuthenticatorsResult listAuthenticators() { - NativeResult res = - this.operationClient.processOperation( - NativeOperation.ListAuthenticatorsOperation.builder().build(), - ProviderId.CORE, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.ListAuthenticatorsOperation.builder().build(), ProviderId.CORE, + this.authData); if (res instanceof NativeResult.ListAuthenticatorsResult) { return (NativeResult.ListAuthenticatorsResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } } + /** * **[Core Operation]** List all keys belonging to the application. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); let keys = client.list_keys(); # Ok(())} - * ``` + *

+ * let client: BasicClient = BasicClient::new(None); let keys = client.list_keys(); # Ok(())} ``` */ public NativeResult.ListKeysResult listKeys() { - NativeResult res = - this.operationClient.processOperation( - NativeOperation.ListKeysOperation.builder().build(), ProviderId.CORE, this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.ListKeysOperation.builder().build(), ProviderId.CORE, this.authData); if (res instanceof NativeResult.ListKeysResult) { return (NativeResult.ListKeysResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } } + /** * Get the key attributes. * - *

This is a convenience method that uses `list_keys` underneath. + *

+ * This is a convenience method that uses `list_keys` underneath. * - *

# Errors + *

+ * # Errors * - *

Returns `NotFound` if a key with this name does not exist. + *

+ * Returns `NotFound` if a key with this name does not exist. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); let attributes = + *

+ * let client: BasicClient = BasicClient::new(None); let attributes = * client.key_attributes("my_key"); # Ok(())} ``` */ public PsaKeyAttributes.KeyAttributes keyAttributes(String keyName) { - return listKeys().getKeys().stream() - .filter(ki -> ki.getName().equals(keyName)) - .findFirst() - .orElseThrow(NotFoundException::new) - .getAttributes(); + return listKeys().getKeys().stream().filter(ki -> ki.getName().equals(keyName)).findFirst() + .orElseThrow(NotFoundException::new).getAttributes(); } + /** * **[Core Operation, Admin Operation]** Lists all clients currently having data in the service. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); let clients = client.list_clients(); # + *

+ * let client: BasicClient = BasicClient::new(None); let clients = client.list_clients(); # * Ok(())} ``` */ public NativeResult.ListClientsResult listClients() { - NativeResult res = - this.operationClient.processOperation( - NativeOperation.ListClientsOperation.builder().build(), ProviderId.CORE, this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.ListClientsOperation.builder().build(), ProviderId.CORE, this.authData); if (res instanceof NativeResult.ListClientsResult) { return (NativeResult.ListClientsResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } } + /** * **[Core Operation, Admin Operation]** Delete all data a client has in the service. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); client.delete_client("main_client"); # + *

+ * let client: BasicClient = BasicClient::new(None); client.delete_client("main_client"); # * Ok(())} ``` */ public void deleteClient(String client) { - NativeResult res = - this.operationClient.processOperation( - NativeOperation.DeleteClientOperation.builder().client(client).build(), - ProviderId.CORE, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.DeleteClientOperation.builder().client(client).build(), ProviderId.CORE, + this.authData); if (!(res instanceof NativeResult.DeleteClientResult)) { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } } + /** * **[Core Operation]** Send a ping request to the service. * - *

This operation is intended for testing connectivity to the service and for retrieving the + *

+ * This operation is intended for testing connectivity to the service and for retrieving the * maximum wire protocol version it supports. * - *

# Example + *

+ * # Example * - *

See [`new_naked`]. + *

+ * See [`new_naked`]. */ public NativeResult.PingResult ping() { NativeResult res = - this.operationClient.processOperation( - NativeOperation.PingOperation.builder().build(), - ProviderId.CORE, - new Authentication.None()); + this.operationClient.processOperation(NativeOperation.PingOperation.builder().build(), + ProviderId.CORE, new Authentication.None()); if (res instanceof NativeResult.PingResult) { return (NativeResult.PingResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -431,24 +490,30 @@ public NativeResult.PingResult ping() { /** * **[Cryptographic Operation]** Generate a key. * - *

Creates a new key with the given name within the namespace of the implicit client provider. - * Any UTF-8 string is considered a valid key name, however names must be unique per provider. + *

+ * Creates a new key with the given name within the namespace of the implicit client provider. Any + * UTF-8 string is considered a valid key name, however names must be unique per provider. * - *

Persistence of keys is implemented at provider level, and currently all providers persist - * all the keys users create. + *

+ * Persistence of keys is implemented at provider level, and currently all providers persist all + * the keys users create. * - *

If this method returns an error, no key will have been generated and the name used will - * still be available for another key. + *

+ * If this method returns an error, no key will have been generated and the name used will still + * be available for another key. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use * parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, * Type, UsageFlags}; use * parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; * - *

let client: BasicClient = BasicClient::new(None); let key_attrs = Attributes { lifetime: + *

+ * let client: BasicClient = BasicClient::new(None); let key_attrs = Attributes { lifetime: * Lifetime::Persistent, key_type: Type::RsaKeyPair, bits: 2048, policy: Policy { usage_flags: * UsageFlags::default(), permitted_algorithms: AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: * Hash::Sha256.into(), }.into(), }, }; client.psa_generate_key("my_key", key_attrs); # Ok(())} @@ -457,15 +522,9 @@ public NativeResult.PingResult ping() { public void psaGenerateKey(String keyName, PsaKeyAttributes.KeyAttributes keyAttributes) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaGenerateKeyOperation.builder() - .keyName(keyName) - .attributes(keyAttributes) - .build(), - cryptoProvider, - this.authData); + this.operationClient.processOperation(NativeOperation.PsaGenerateKeyOperation.builder() + .keyName(keyName).attributes(keyAttributes).build(), cryptoProvider, this.authData); if (res instanceof NativeResult.PsaGenerateKeyResult) { return; } @@ -475,25 +534,27 @@ public void psaGenerateKey(String keyName, PsaKeyAttributes.KeyAttributes keyAtt /** * **[Cryptographic Operation]** Destroy a key. * - *

Given that keys are namespaced at a provider level, it is important to call - * `psa_destroy_key` on the correct combination of implicit client provider and `keyName`. + *

+ * Given that keys are namespaced at a provider level, it is important to call `psa_destroy_key` + * on the correct combination of implicit client provider and `keyName`. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); client.psa_destroy_key("my_key"); # - * Ok(())} ``` + *

+ * let client: BasicClient = BasicClient::new(None); client.psa_destroy_key("my_key"); # Ok(())} + * ``` */ public void psaDestroyKey(String keyName) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaDestroyKeyOperation.builder().keyName(keyName).build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaDestroyKeyOperation.builder().keyName(keyName).build(), cryptoProvider, + this.authData); if (res instanceof NativeResult.PsaDestroyKeyResult) { return; } @@ -503,46 +564,48 @@ public void psaDestroyKey(String keyName) { /** * **[Cryptographic Operation]** Import a key. * - *

Creates a new key with the given name within the namespace of the implicit client provider + *

+ * Creates a new key with the given name within the namespace of the implicit client provider * using the user-provided data. Any UTF-8 string is considered a valid key name, however names * must be unique per provider. * - *

The key material should follow the appropriate binary format expressed + *

+ * The key material should follow the appropriate binary format expressed * [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). * Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) can greatly help in * dealing with binary encodings. * - *

If this method returns an error, no key will have been imported and the name used will still - * be available for another key. + *

+ * If this method returns an error, no key will have been imported and the name used will still be + * available for another key. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use * parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, * Type, UsageFlags, EccFamily}; use * parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; * - *

let client: BasicClient = BasicClient::new(None); let ecc_private_key = vec![ 0x26, 0xc8, - * 0x82, 0x9e, 0x22, 0xe3, 0x0c, 0xa6, 0x3d, 0x29, 0xf5, 0xf7, 0x27, 0x39, 0x58, 0x47, 0x41, 0x81, - * 0xf6, 0x57, 0x4f, 0xdb, 0xcb, 0x4d, 0xbb, 0xdd, 0x52, 0xff, 0x3a, 0xc0, 0xf6, 0x0d, ]; let - * key_attrs = Attributes { lifetime: Lifetime::Persistent, key_type: Type::EccKeyPair { - * curve_family: EccFamily::SecpR1, }, bits: 256, policy: Policy { usage_flags: - * UsageFlags::default(), permitted_algorithms: AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: - * Hash::Sha256.into(), }.into(), }, }; client.psa_import_key("my_key", &ecc_private_key, - * key_attrs); # Ok(())} ``` + *

+ * let client: BasicClient = BasicClient::new(None); let ecc_private_key = vec![ 0x26, 0xc8, 0x82, + * 0x9e, 0x22, 0xe3, 0x0c, 0xa6, 0x3d, 0x29, 0xf5, 0xf7, 0x27, 0x39, 0x58, 0x47, 0x41, 0x81, 0xf6, + * 0x57, 0x4f, 0xdb, 0xcb, 0x4d, 0xbb, 0xdd, 0x52, 0xff, 0x3a, 0xc0, 0xf6, 0x0d, ]; let key_attrs + * = Attributes { lifetime: Lifetime::Persistent, key_type: Type::EccKeyPair { curve_family: + * EccFamily::SecpR1, }, bits: 256, policy: Policy { usage_flags: UsageFlags::default(), + * permitted_algorithms: AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: Hash::Sha256.into(), + * }.into(), }, }; client.psa_import_key("my_key", &ecc_private_key, key_attrs); # Ok(())} ``` */ - public void psaImportKey(String keyName, byte[] keyMaterial, PsaKeyAttributes.KeyAttributes keyAttributes) { + public void psaImportKey(String keyName, byte[] keyMaterial, + PsaKeyAttributes.KeyAttributes keyAttributes) { ProviderId cryptoProvider = this.canProvideCrypto(); NativeResult res = this.operationClient.processOperation( - NativeOperation.PsaImportKeyOperation.builder() - .keyName(keyName) - .attributes(keyAttributes) - .data(keyMaterial) - .build(), - cryptoProvider, - this.authData); + NativeOperation.PsaImportKeyOperation.builder().keyName(keyName) + .attributes(keyAttributes).data(keyMaterial).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaImportKeyResult) { return; } @@ -552,32 +615,35 @@ public void psaImportKey(String keyName, byte[] keyMaterial, PsaKeyAttributes.Ke /** * **[Cryptographic Operation]** Export a public key or the public part of a key pair. * - *

The returned key material will follow the appropriate binary format expressed + *

+ * The returned key material will follow the appropriate binary format expressed * [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). * Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) can greatly help in * dealing with binary encodings. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); let public_key_data = + *

+ * let client: BasicClient = BasicClient::new(None); let public_key_data = * client.psa_export_public_key("my_key"); # Ok(())} ``` */ public NativeResult.PsaExportPublicKeyResult psaExportPublicKey(String keyName) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaExportPublicKeyOperation.builder().keyName(keyName).build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaExportPublicKeyOperation.builder().keyName(keyName).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaExportPublicKeyResult) { return (NativeResult.PsaExportPublicKeyResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -586,81 +652,86 @@ public NativeResult.PsaExportPublicKeyResult psaExportPublicKey(String keyName) /** * **[Cryptographic Operation]** Export a key. * - *

The returned key material will follow the appropriate binary format expressed + *

+ * The returned key material will follow the appropriate binary format expressed * [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_key.html). * Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) can greatly help in * dealing with binary encodings. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; * - *

let client: BasicClient = BasicClient::new(None); let key_data = + *

+ * let client: BasicClient = BasicClient::new(None); let key_data = * client.psa_export_key("my_key"); # Ok(())} ``` */ public NativeResult.PsaExportKeyResult psaExportKey(String keyName) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaExportKeyOperation.builder().keyName(keyName).build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaExportKeyOperation.builder().keyName(keyName).build(), cryptoProvider, + this.authData); if (res instanceof NativeResult.PsaExportKeyResult) { return (NativeResult.PsaExportKeyResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } } + /** * **[Cryptographic Operation]** Create an asymmetric signature on a pre-computed message digest. * - *

The key intended for signing **must** have its `sign_hash` flag set to `true` in its [key + *

+ * The key intended for signing **must** have its `sign_hash` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The signature will be created with the algorithm defined in `signAlgorithm`, but only after + *

+ * The signature will be created with the algorithm defined in `signAlgorithm`, but only after * checking that the key policy and type conform with it. * - *

`hash` must be a hash pre-computed over the message of interest with the algorithm specified + *

+ * `hash` must be a hash pre-computed over the message of interest with the algorithm specified * within `signAlgorithm`. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use * parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, * Type, UsageFlags}; use * parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; * - *

let client: BasicClient = BasicClient::new(None); Hash of a message pre-calculated with + *

+ * let client: BasicClient = BasicClient::new(None); Hash of a message pre-calculated with * SHA-256. let hash = vec![ 0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, * 0x91, 0x76, 0x37, 0x84, 0xA2, 0x94, 0x8E, 0x92, 0x50, 0x35, 0xC2, 0x8C, 0x5C, 0x3C, 0xCA, 0xFE, * 0x18, 0xE8, 0x81, 0x37, 0x78, ]; let signature = client.psa_sign_hash("my_key", &hash, * AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: Hash::Sha256.into(), }); # Ok(())} ``` */ - public NativeResult.PsaSignHashResult psaSignHash( - String keyName, byte[] hash, PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm) { + public NativeResult.PsaSignHashResult psaSignHash(String keyName, byte[] hash, + PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm) { ProviderId cryptoProvider = this.canProvideCrypto(); NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaSignHashOperation.builder() - .keyName(keyName) - .alg(signAlgorithm) - .hash(hash) - .build(), - cryptoProvider, - this.authData); + this.operationClient.processOperation(NativeOperation.PsaSignHashOperation.builder() + .keyName(keyName).alg(signAlgorithm).hash(hash).build(), cryptoProvider, this.authData); if (res instanceof NativeResult.PsaSignHashResult) { return (NativeResult.PsaSignHashResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -670,48 +741,44 @@ public NativeResult.PsaSignHashResult psaSignHash( * **[Cryptographic Operation]** Verify an existing asymmetric signature over a pre-computed * message digest. * - *

The key intended for signing **must** have its `verify_hash` flag set to `true` in its [key + *

+ * The key intended for signing **must** have its `verify_hash` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The signature will be verifyied with the algorithm defined in `sign_algorithm`, but only - * after checking that the key policy and type conform with it. + *

+ * The signature will be verifyied with the algorithm defined in `sign_algorithm`, but only after + * checking that the key policy and type conform with it. * - *

`hash` must be a hash pre-computed over the message of interest with the algorithm specified + *

+ * `hash` must be a hash pre-computed over the message of interest with the algorithm specified * within `sign_algorithm`. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use * parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, * Type, UsageFlags}; use * parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; * - *

let client: BasicClient = BasicClient::new(None); Hash of a message pre-calculated with + *

+ * let client: BasicClient = BasicClient::new(None); Hash of a message pre-calculated with * SHA-256. let hash = vec![ 0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, * 0x91, 0x76, 0x37, 0x84, 0xA2, 0x94, 0x8E, 0x92, 0x50, 0x35, 0xC2, 0x8C, 0x5C, 0x3C, 0xCA, 0xFE, * 0x18, 0xE8, 0x81, 0x37, 0x78, ]; let alg = AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: * Hash::Sha256.into(), }; let signature = client.psa_sign_hash("my_key", &hash, alg); * client.psa_verify_hash("my_key", &hash, alg, &signature); # Ok(())} ``` */ - public NativeResult.PsaVerifyHashResult psaVerifyHash( - String keyName, - byte[] hash, - PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm, - byte[] signature) { + public NativeResult.PsaVerifyHashResult psaVerifyHash(String keyName, byte[] hash, + PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm, byte[] signature) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaVerifyHashOperation.builder() - .keyName(keyName) - .alg(signAlgorithm) - .hash(hash) - .signature(signature) - .build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation(NativeOperation.PsaVerifyHashOperation + .builder().keyName(keyName).alg(signAlgorithm).hash(hash).signature(signature).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaVerifyHashResult) { return (NativeResult.PsaVerifyHashResult) res; } @@ -721,43 +788,43 @@ public NativeResult.PsaVerifyHashResult psaVerifyHash( /** * [Cryptographic Operation]** Create an asymmetric signature on a message. * - *

The key intended for signing **must** have its `sign_message` flag set to `true` in its [key + *

+ * The key intended for signing **must** have its `sign_message` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The signature will be created with the algorithm defined in `sign_algorithm`, but only after + *

+ * The signature will be created with the algorithm defined in `sign_algorithm`, but only after * checking that the key policy and type conform with it. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use * parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, * Type, UsageFlags}; use * parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; * - *

let client: BasicClient = BasicClient::new(None); let message = "This is the message to sign + *

+ * let client: BasicClient = BasicClient::new(None); let message = "This is the message to sign * which can be of any size!".as_bytes(); let signature = client.psa_sign_message( "my_key", * message, AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: Hash::Sha256.into(), } ); # Ok(())} * ``` */ - public NativeResult.PsaSignMessageResult psaSignMessage( - String keyName, byte[] message, PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm) { + public NativeResult.PsaSignMessageResult psaSignMessage(String keyName, byte[] message, + PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaSignMessageOperation.builder() - .keyName(keyName) - .alg(signAlgorithm) - .message(message) - .build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation(NativeOperation.PsaSignMessageOperation + .builder().keyName(keyName).alg(signAlgorithm).message(message).build(), cryptoProvider, + this.authData); if (res instanceof NativeResult.PsaSignMessageResult) { return (NativeResult.PsaSignMessageResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -766,43 +833,40 @@ public NativeResult.PsaSignMessageResult psaSignMessage( /** * [Cryptographic Operation]** Verify an existing asymmetric signature over a message. * - *

The key intended for signing **must** have its `verify_message` flag set to `true` in its - * [key + *

+ * The key intended for signing **must** have its `verify_message` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The signature will be verifyied with the algorithm defined in `sign_algorithm`, but only - * after checking that the key policy and type conform with it. + *

+ * The signature will be verifyied with the algorithm defined in `sign_algorithm`, but only after + * checking that the key policy and type conform with it. * - *

# Example + *

+ * # Example * - *

```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use + *

+ * ```no_run # use std::error::Error; # # fn main() -> Result<(), Box> { use * parsec_client::BasicClient; use * parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, * Type, UsageFlags}; use * parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; * - *

let client: BasicClient = BasicClient::new(None); let message = "This is the message to sign + *

+ * let client: BasicClient = BasicClient::new(None); let message = "This is the message to sign * which can be of any size!".as_bytes(); let alg = AsymmetricSignature::RsaPkcs1v15Sign { * hash_alg: Hash::Sha256.into(), }; let signature = client.psa_sign_message("my_key", message, * alg); client.psa_verify_message("my_key", message, alg, &signature); # Ok(())} ``` */ - public NativeResult psaVerifyMessage( - String keyName, - byte[] msg, - PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm, - byte[] signature) { + public NativeResult psaVerifyMessage(String keyName, byte[] msg, + PsaAlgorithm.Algorithm.AsymmetricSignature signAlgorithm, byte[] signature) { ProviderId cryptoProvider = this.canProvideCrypto(); NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaVerifyMessageOperation.builder() - .keyName(keyName) - .alg(signAlgorithm) - .message(msg) - .signature(signature) - .build(), - cryptoProvider, - this.authData); + this.operationClient + .processOperation( + NativeOperation.PsaVerifyMessageOperation.builder().keyName(keyName) + .alg(signAlgorithm).message(msg).signature(signature).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaVerifyMessageResult) { return res; @@ -813,39 +877,36 @@ public NativeResult psaVerifyMessage( /** * [Cryptographic Operation]** Encrypt a short message. * - *

The key intended for encrypting **must** have its `encrypt` flag set to `true` in its [key + *

+ * The key intended for encrypting **must** have its `encrypt` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The encryption will be performed with the algorithm defined in `alg`, but only after - * checking that the key policy and type conform with it. + *

+ * The encryption will be performed with the algorithm defined in `alg`, but only after checking + * that the key policy and type conform with it. * - *

`salt` can be provided if supported by the algorithm. If the algorithm does not support - * salt, pass an empty vector. If the algorithm supports optional salt, pass an empty vector to - * indicate no salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. + *

+ * `salt` can be provided if supported by the algorithm. If the algorithm does not support salt, + * pass an empty vector. If the algorithm supports optional salt, pass an empty vector to indicate + * no salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. */ - public NativeResult.PsaAsymmetricEncryptResult psaAsymmetricEncrypt( - String keyName, - PsaAlgorithm.Algorithm.AsymmetricEncryption encryptAlg, - byte[] plaintext, - byte[] salt) { + public NativeResult.PsaAsymmetricEncryptResult psaAsymmetricEncrypt(String keyName, + PsaAlgorithm.Algorithm.AsymmetricEncryption encryptAlg, byte[] plaintext, byte[] salt) { ProviderId cryptoProvider = this.canProvideCrypto(); NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaAsymmetricEncryptOperation.builder() - .keyName(keyName) - .alg(encryptAlg) - .plaintext(plaintext) - .salt(salt) - .build(), - cryptoProvider, - this.authData); + this.operationClient + .processOperation( + NativeOperation.PsaAsymmetricEncryptOperation.builder().keyName(keyName) + .alg(encryptAlg).plaintext(plaintext).salt(salt).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaAsymmetricEncryptResult) { return (NativeResult.PsaAsymmetricEncryptResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -854,61 +915,59 @@ public NativeResult.PsaAsymmetricEncryptResult psaAsymmetricEncrypt( /** * [Cryptographic Operation]** Decrypt a short message. * - *

The key intended for decrypting **must** have its `decrypt` flag set to `true` in its [key + *

+ * The key intended for decrypting **must** have its `decrypt` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

`salt` can be provided if supported by the algorithm. If the algorithm does not support - * salt, pass an empty vector. If the algorithm supports optional salt, pass an empty vector to - * indicate no salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. + *

+ * `salt` can be provided if supported by the algorithm. If the algorithm does not support salt, + * pass an empty vector. If the algorithm supports optional salt, pass an empty vector to indicate + * no salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. * - *

The decryption will be performed with the algorithm defined in `alg`, but only after - * checking that the key policy and type conform with it. + *

+ * The decryption will be performed with the algorithm defined in `alg`, but only after checking + * that the key policy and type conform with it. */ - public NativeResult.PsaAeadDecryptResult psaAsymmetricDecrypt( - String keyName, - PsaAlgorithm.Algorithm.AsymmetricEncryption encryptAlg, - byte[] ciphertext, - byte[] salt) { + public NativeResult.PsaAeadDecryptResult psaAsymmetricDecrypt(String keyName, + PsaAlgorithm.Algorithm.AsymmetricEncryption encryptAlg, byte[] ciphertext, byte[] salt) { ProviderId cryptoProvider = this.canProvideCrypto(); NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaAsymmetricDecryptOperation.builder() - .keyName(keyName) - .alg(encryptAlg) - .ciphertext(ciphertext) - .salt(salt) - .build(), - cryptoProvider, - this.authData); + this.operationClient + .processOperation( + NativeOperation.PsaAsymmetricDecryptOperation.builder().keyName(keyName) + .alg(encryptAlg).ciphertext(ciphertext).salt(salt).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaAeadDecryptResult) { return (NativeResult.PsaAeadDecryptResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } } + /** * [Cryptographic Operation]** Compute hash of a message. * - *

The hash computation will be performed with the algorithm defined in `alg`. + *

+ * The hash computation will be performed with the algorithm defined in `alg`. */ - public NativeResult.PsaHashComputeResult psaHashCompute( - PsaAlgorithm.Algorithm.Hash alg, byte[] input) { + public NativeResult.PsaHashComputeResult psaHashCompute(PsaAlgorithm.Algorithm.Hash alg, + byte[] input) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaHashComputeOperation.builder().alg(alg).input(input).build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaHashComputeOperation.builder().alg(alg).input(input).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaHashComputeResult) { return (NativeResult.PsaHashComputeResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -917,23 +976,19 @@ public NativeResult.PsaHashComputeResult psaHashCompute( /** * [Cryptographic Operation]** Compute hash of a message and compare it with a reference value. * - *

The hash computation will be performed with the algorithm defined in `alg`. + *

+ * The hash computation will be performed with the algorithm defined in `alg`. * - *

If this operation returns no error, the hash was computed successfully and it matches the + *

+ * If this operation returns no error, the hash was computed successfully and it matches the * reference value. */ - public NativeResult.PsaHashCompareResult psaHashCompare( - PsaAlgorithm.Algorithm.Hash alg, byte[] input, byte[] hash) { + public NativeResult.PsaHashCompareResult psaHashCompare(PsaAlgorithm.Algorithm.Hash alg, + byte[] input, byte[] hash) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaHashCompareOperation.builder() - .alg(alg) - .input(input) - .hash(hash) - .build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaHashCompareOperation.builder().alg(alg).input(input).hash(hash).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaHashCompareResult) { return (NativeResult.PsaHashCompareResult) res; } @@ -943,42 +998,37 @@ public NativeResult.PsaHashCompareResult psaHashCompare( /** * [Cryptographic Operation]** Authenticate and encrypt a short message. * - *

The key intended for decrypting **must** have its `encrypt` flag set to `true` in its [key + *

+ * The key intended for decrypting **must** have its `encrypt` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The encryption will be performed with the algorithm defined in `alg`, but only after - * checking that the key policy and type conform with it. + *

+ * The encryption will be performed with the algorithm defined in `alg`, but only after checking + * that the key policy and type conform with it. * - *

`nonce` must be appropriate for the selected `alg`. + *

+ * `nonce` must be appropriate for the selected `alg`. * - *

For algorithms where the encrypted data and the authentication tag are defined as separate + *

+ * For algorithms where the encrypted data and the authentication tag are defined as separate * outputs, the returned buffer will contain the encrypted data followed by the authentication * data. */ - public NativeResult.PsaAeadEncryptResult psaAeadEncrypt( - String keyName, - PsaAlgorithm.Algorithm.Aead encryptAlg, - byte[] nonce, - byte[] additionalData, + public NativeResult.PsaAeadEncryptResult psaAeadEncrypt(String keyName, + PsaAlgorithm.Algorithm.Aead encryptAlg, byte[] nonce, byte[] additionalData, byte[] plaintext) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaAeadEncryptOperation.builder() - .keyName(keyName) - .alg(encryptAlg) - .nonce(nonce) - .additionalData(additionalData) - .plaintext(plaintext) - .build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaAeadEncryptOperation.builder().keyName(keyName).alg(encryptAlg) + .nonce(nonce).additionalData(additionalData).plaintext(plaintext).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaAeadEncryptResult) { return (NativeResult.PsaAeadEncryptResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -987,41 +1037,36 @@ public NativeResult.PsaAeadEncryptResult psaAeadEncrypt( /** * [Cryptographic Operation]** Decrypt and authenticate a short message. * - *

The key intended for decrypting **must** have its `decrypt` flag set to `true` in its [key + *

+ * The key intended for decrypting **must** have its `decrypt` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The decryption will be performed with the algorithm defined in `alg`, but only after - * checking that the key policy and type conform with it. + *

+ * The decryption will be performed with the algorithm defined in `alg`, but only after checking + * that the key policy and type conform with it. * - *

`nonce` must be appropriate for the selected `alg`. + *

+ * `nonce` must be appropriate for the selected `alg`. * - *

For algorithms where the encrypted data and the authentication tag are defined as separate + *

+ * For algorithms where the encrypted data and the authentication tag are defined as separate * inputs, `ciphertext` must contain the encrypted data followed by the authentication data. */ - public NativeResult.PsaAeadDecryptResult psaAeadDecrypt( - String keyName, - PsaAlgorithm.Algorithm.Aead encryptAlg, - byte[] nonce, - byte[] additionalData, + public NativeResult.PsaAeadDecryptResult psaAeadDecrypt(String keyName, + PsaAlgorithm.Algorithm.Aead encryptAlg, byte[] nonce, byte[] additionalData, byte[] ciphertext) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaAeadDecryptOperation.builder() - .keyName(keyName) - .alg(encryptAlg) - .nonce(nonce) - .additionalData(additionalData) - .ciphertext(ciphertext) - .build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaAeadDecryptOperation.builder().keyName(keyName).alg(encryptAlg) + .nonce(nonce).additionalData(additionalData).ciphertext(ciphertext).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaAeadDecryptResult) { return (NativeResult.PsaAeadDecryptResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -1030,32 +1075,31 @@ public NativeResult.PsaAeadDecryptResult psaAeadDecrypt( /** * [Cryptographic Operation]** Perform a raw key agreement. * - *

The provided private key **must** have its `derive` flag set to `true` in its [key + *

+ * The provided private key **must** have its `derive` flag set to `true` in its [key * policy](https://docs.rs/parsec-interface//parsec_interface/operations/psa_key_attributes/struct.Policy.html). * - *

The raw_key_agreement will be performed with the algorithm defined in `alg`, but only after + *

+ * The raw_key_agreement will be performed with the algorithm defined in `alg`, but only after * checking that the key policy and type conform with it. * - *

`peer_key` must be the peer public key to use in the raw key derivation. It must be in a - * format supported by + *

+ * `peer_key` must be the peer public key to use in the raw key derivation. It must be in a format + * supported by * [`PsaImportKey`](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_import_key.html). */ - public NativeResult.PsaRawKeyAgreementResult psaRawKeyAgreement( - PsaRawKeyAgreement alg, String privateKeyName, byte[] peerKey) { + public NativeResult.PsaRawKeyAgreementResult psaRawKeyAgreement(PsaRawKeyAgreement alg, + String privateKeyName, byte[] peerKey) { ProviderId cryptoProvider = this.canProvideCrypto(); - NativeResult res = - this.operationClient.processOperation( - NativeOperation.PsaRawKeyAgreementOperation.builder() - .alg(alg) - .peerKey(peerKey) - .privateKeyName(privateKeyName) - .build(), - cryptoProvider, - this.authData); + NativeResult res = this.operationClient.processOperation( + NativeOperation.PsaRawKeyAgreementOperation.builder().alg(alg).peerKey(peerKey) + .privateKeyName(privateKeyName).build(), + cryptoProvider, this.authData); if (res instanceof NativeResult.PsaRawKeyAgreementResult) { return (NativeResult.PsaRawKeyAgreementResult) res; } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } @@ -1064,13 +1108,17 @@ public NativeResult.PsaRawKeyAgreementResult psaRawKeyAgreement( /** * [Cryptographic Operation]** Generate some random bytes. * - *

Generates a sequence of random bytes and returns them to the user. + *

+ * Generates a sequence of random bytes and returns them to the user. * - *

If this method returns an error, no bytes will have been generated. + *

+ * If this method returns an error, no bytes will have been generated. * - *

# Example + *

+ * # Example * - *

See [`list_opcodes`]. + *

+ * See [`list_opcodes`]. */ public byte[] psaGenerateRandom(long nbytes) { ProviderId cryptoProvider = this.canProvideCrypto(); @@ -1083,7 +1131,8 @@ public byte[] psaGenerateRandom(long nbytes) { if (res instanceof NativeResult.PsaGenerateRandomResult) { return ((NativeResult.PsaGenerateRandomResult) res).getRandomBytes(); } else { - // Should really not be reached given the checks we do, but it's not impossible if some + // Should really not be reached given the checks we do, but it's not impossible + // if some // changes happen in the interface throw new InvalidServiceResponseTypeException(); } diff --git a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/OperationClient.java b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/OperationClient.java index 0511b3e..4b4e0e3 100644 --- a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/OperationClient.java +++ b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/OperationClient.java @@ -1,15 +1,18 @@ package org.parallaxsecond.parsec.client.core; +import java.io.IOException; +import java.time.Duration; + import org.parallaxsecond.parsec.client.Authentication; import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; import org.parallaxsecond.parsec.client.exceptions.ClientException; -import org.parallaxsecond.parsec.protocol.requests.InterfaceException; import org.parallaxsecond.parsec.client.exceptions.InvalidServiceResponseTypeException; import org.parallaxsecond.parsec.client.exceptions.ServiceException; import org.parallaxsecond.parsec.protocol.operations.Convert; import org.parallaxsecond.parsec.protocol.operations.NativeOperation; import org.parallaxsecond.parsec.protocol.operations.NativeResult; import org.parallaxsecond.parsec.protocol.operations_protobuf.ProtobufConverter; +import org.parallaxsecond.parsec.protocol.requests.InterfaceException; import org.parallaxsecond.parsec.protocol.requests.Opcode; import org.parallaxsecond.parsec.protocol.requests.ProviderId; import org.parallaxsecond.parsec.protocol.requests.ResponseStatus; @@ -17,14 +20,14 @@ import org.parallaxsecond.parsec.protocol.requests.request.RequestBody; import org.parallaxsecond.parsec.protocol.requests.request.RequestHeader; import org.parallaxsecond.parsec.protocol.requests.response.Response; + import lombok.Builder; import lombok.RequiredArgsConstructor; - -import java.io.IOException; -import java.time.Duration; +import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor @Builder +@Slf4j public class OperationClient { /** Converter that manages request body conversions Defaults to a Protobuf converter */ private final Convert contentConverter; @@ -34,15 +37,13 @@ public class OperationClient { private final RequestClient requestClient; public static OperationClient withDefaults() { - return OperationClient.builder() - .contentConverter(new ProtobufConverter()) - .acceptConverter(new ProtobufConverter()) - .requestClient(RequestClient.withDefaults()) + return OperationClient.builder().contentConverter(new ProtobufConverter()) + .acceptConverter(new ProtobufConverter()).requestClient(RequestClient.withDefaults()) .build(); } - public NativeResult processOperation( - NativeOperation operation, ProviderId providerId, Authentication auth) { + public NativeResult processOperation(NativeOperation operation, ProviderId providerId, + Authentication auth) { Opcode reqOpCode = operation.getOpcode(); Request request = operationToRequest(operation, providerId, auth); Response response = null; @@ -54,8 +55,8 @@ public NativeResult processOperation( return responseToResult(response, reqOpCode); } - private Request operationToRequest( - NativeOperation operation, ProviderId providerId, Authentication auth) { + private Request operationToRequest(NativeOperation operation, ProviderId providerId, + Authentication auth) { Opcode opcode = operation.getOpcode(); final RequestBody body; @@ -64,15 +65,10 @@ private Request operationToRequest( } catch (Exception e) { throw new InterfaceException(e); } - RequestHeader header = - RequestHeader.builder() - .provider(providerId) - .session(0) // no provisioning of sessions yet - .contentType(contentConverter.bodyType()) - .acceptType(acceptConverter.bodyType()) - .authType(auth.getAuthType()) - .opcode(opcode) - .build(); + // no provisioning of sessions yet + RequestHeader header = RequestHeader.builder().provider(providerId).session(0) + .contentType(contentConverter.bodyType()).acceptType(acceptConverter.bodyType()) + .authType(auth.getAuthType()).opcode(opcode).build(); return Request.builder().header(header).body(body).auth(auth.createRequestAuth()).build(); } diff --git a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/RequestClient.java b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/RequestClient.java index 5579745..e983a4e 100644 --- a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/RequestClient.java +++ b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/RequestClient.java @@ -1,19 +1,17 @@ package org.parallaxsecond.parsec.client.core; +import java.io.IOException; +import java.nio.channels.ByteChannel; +import java.time.Duration; + import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; import org.parallaxsecond.parsec.client.core.ipc_handler.UnixSocket; import org.parallaxsecond.parsec.protocol.requests.request.Request; import org.parallaxsecond.parsec.protocol.requests.response.Response; + import lombok.AllArgsConstructor; import lombok.Builder; - -import java.io.IOException; -import java.net.URI; -import java.nio.channels.ByteChannel; -import java.text.MessageFormat; -import java.time.Duration; - -import static java.util.Optional.ofNullable; +import lombok.extern.slf4j.Slf4j; /** * Low level client structure optimised for communicating with the service at a request level of @@ -22,35 +20,32 @@ */ @Builder @AllArgsConstructor +@Slf4j public class RequestClient { // FIXME taken from the rust code, seems unreasonably big private static final long DEFAULT_MAX_BODY_SIZE = Long.MAX_VALUE; - /** Max size for response bodies Defaults to the max value of `usize` on the current platform */ + /** + * Max size for response bodies Defaults to the max value of `usize` on the current platform + */ private long maxBodySize; - /** Handler for IPC-related functionality Defaults to using Unix domain sockets */ + /** + * Handler for IPC-related functionality Defaults to using Unix domain sockets + */ private IpcHandler ipcHandler; - public RequestClient() { - this( - DEFAULT_MAX_BODY_SIZE, - IpcHandler.connectFromUrl( - URI.create( - ofNullable(System.getenv("PARSEC_SERVICE_ENDPOINT")) - .orElse(MessageFormat.format("unix:{0}", UnixSocket.DEFAULT_SOCKET_PATH))))); - } - public static RequestClient withDefaults() { - return RequestClient.builder() - .maxBodySize(DEFAULT_MAX_BODY_SIZE) - .ipcHandler(UnixSocket.withDefaults()) - .build(); + return RequestClient.builder().maxBodySize(DEFAULT_MAX_BODY_SIZE) + .ipcHandler(UnixSocket.withDefaults()).build(); } /** Send a request and get a response. */ public Response processRequest(Request request) throws IOException { + log.info("Processing request: " + request); // Try to connect once, wait for a timeout until trying again. try (ByteChannel stream = ipcHandler.connect()) { + log.info("Connected to stream"); request.writeToStream(stream); + log.info("Wrote request to stream"); return Response.readFromStream(stream, maxBodySize); } } diff --git a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/IpcHandler.java b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/IpcHandler.java index 0cabffb..65cb769 100644 --- a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/IpcHandler.java +++ b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/IpcHandler.java @@ -1,15 +1,16 @@ package org.parallaxsecond.parsec.client.core.ipc_handler; -import org.parallaxsecond.parsec.client.exceptions.InvalidSocketUrlException; -import lombok.NonNull; - import java.net.URI; import java.nio.channels.ByteChannel; import java.time.Duration; +import org.parallaxsecond.parsec.client.exceptions.InvalidSocketUrlException; + +import lombok.NonNull; + public interface IpcHandler { /// Default timeout for client IPC requests. - Duration DEFAULT_TIMEOUT = Duration.ofSeconds(60); + Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10); static IpcHandler connectFromUrl(@NonNull URI uri) { switch (uri.getScheme()) { diff --git a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/UnixSocket.java b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/UnixSocket.java index 6434573..fa0913c 100644 --- a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/UnixSocket.java +++ b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/core/ipc_handler/UnixSocket.java @@ -1,14 +1,15 @@ package org.parallaxsecond.parsec.client.core.ipc_handler; -import org.parallaxsecond.parsec.client.jna.UnixSocketChannel; -import lombok.Setter; - import java.nio.channels.ByteChannel; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.Objects; +import org.parallaxsecond.parsec.client.jna.UnixSocketChannel; + +import lombok.Setter; + /** IPC handler for Unix domain sockets */ public class UnixSocket implements IpcHandler { /** Default socket path used by the service. */ @@ -16,7 +17,8 @@ public class UnixSocket implements IpcHandler { /** Path at which the socket can be found */ private final Path path; /** Timeout for reads and writes on the streams */ - @Setter private Duration timeout = DEFAULT_TIMEOUT; + @Setter + private Duration timeout = DEFAULT_TIMEOUT; public UnixSocket(String path) { this(path, DEFAULT_TIMEOUT); diff --git a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/jna/UnixSocketChannel.java b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/jna/UnixSocketChannel.java index 712c6fd..e2b33cf 100644 --- a/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/jna/UnixSocketChannel.java +++ b/parsec-client-java/src/main/java/org/parallaxsecond/parsec/client/jna/UnixSocketChannel.java @@ -1,10 +1,5 @@ package org.parallaxsecond.parsec.client.jna; -import org.parallaxsecond.parsec.client.core.FileStat; -import org.parallaxsecond.parsec.client.exceptions.InvalidSocketAddressException; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; @@ -12,6 +7,12 @@ import java.nio.file.Path; import java.time.Duration; +import org.parallaxsecond.parsec.client.core.FileStat; +import org.parallaxsecond.parsec.client.exceptions.InvalidSocketAddressException; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + @Slf4j public class UnixSocketChannel implements ByteChannel { @@ -37,7 +38,7 @@ public int read(ByteBuffer dst) { while (read < toRead) { read += (int) UnixSocket.readSocket(this.socket, dst, toRead - read); log.debug("expected: {}, read: {}", toRead, read); - ((Buffer)dst).position(pos + read); + ((Buffer) dst).position(pos + read); } return toRead; } @@ -49,7 +50,7 @@ public int write(ByteBuffer src) { int pos = src.position(); while (written < toWrite) { written += (int) UnixSocket.writeSocket(socket, src, toWrite - written); - ((Buffer)src).position(pos + written); + ((Buffer) src).position(pos + written); } return written; } diff --git a/parsec-client-java/src/test/java/org/parallaxsecond/parsec/client/core/BasicClientTest.java b/parsec-client-java/src/test/java/org/parallaxsecond/parsec/client/core/BasicClientTest.java index 193c1a1..c271ca6 100644 --- a/parsec-client-java/src/test/java/org/parallaxsecond/parsec/client/core/BasicClientTest.java +++ b/parsec-client-java/src/test/java/org/parallaxsecond/parsec/client/core/BasicClientTest.java @@ -1,48 +1,128 @@ package org.parallaxsecond.parsec.client.core; -import lombok.SneakyThrows; +import java.security.SecureRandom; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; import org.parallaxsecond.parsec.client.jna.Uid; -import org.parallaxsecond.parsec.protocol.operations.NativeResult; import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm; +import org.parallaxsecond.parsec.protobuf.psa_key_attributes.PsaKeyAttributes; +import org.parallaxsecond.parsec.protocol.operations.NativeResult; import org.parallaxsecond.parsec.protocol.requests.Opcode; import org.parallaxsecond.testcontainers.ParsecContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.shaded.org.awaitility.Awaitility; -import java.io.File; -import java.security.SecureRandom; - -import static org.junit.jupiter.api.Assertions.*; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; @Testcontainers +@Slf4j class BasicClientTest { @Container - ParsecContainer parsecContainer = - ParsecContainer.withVersion("0.8.1") - .withFileSystemBind( - new File("src/test/resources/mbed-crypto-config.toml").getAbsolutePath(), - "/etc/parsec/config.toml"); + ParsecContainer parsecContainer = ParsecContainer.withVersion("latest"); + // .withCopyToContainer(CONFIG_FILE, "/etc/parsec/config.toml"); private BasicClient client; private final String eccKey = "eccKey"; private final String rsaKey = "rsaKey"; + PsaKeyAttributes.KeyAttributes eccKeyAttributes = + PsaKeyAttributes.KeyAttributes.newBuilder().setKeyBits(256) + .setKeyType(PsaKeyAttributes.KeyType.newBuilder() + .setEccKeyPair(PsaKeyAttributes.KeyType.EccKeyPair.newBuilder() + .setCurveFamily(PsaKeyAttributes.KeyType.EccFamily.SECP_R1).build()) + .build()) + .setKeyPolicy( + PsaKeyAttributes.KeyPolicy.newBuilder() + .setKeyUsageFlags( + PsaKeyAttributes.UsageFlags.newBuilder().setSignHash(true).setVerifyHash(true) + .setSignMessage(true).setVerifyMessage(true).setExport(true).build()) + .setKeyAlgorithm( + PsaAlgorithm.Algorithm.newBuilder() + .setAsymmetricSignature(PsaAlgorithm.Algorithm.AsymmetricSignature + .newBuilder() + .setEcdsa(PsaAlgorithm.Algorithm.AsymmetricSignature.Ecdsa + .newBuilder() + .setHashAlg(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash + .newBuilder().setSpecific(PsaAlgorithm.Algorithm.Hash.SHA_256) + .build()) + .build()) + .build())) + .build()) + .build(); + + + PsaAlgorithm.Algorithm.AsymmetricSignature eccKeyArgs = + PsaAlgorithm.Algorithm.AsymmetricSignature.newBuilder() + .setEcdsa( + PsaAlgorithm.Algorithm.AsymmetricSignature.Ecdsa + .newBuilder().setHashAlg(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash + .newBuilder().setSpecific(PsaAlgorithm.Algorithm.Hash.SHA_256).build()) + .build()) + .build(); + + + PsaKeyAttributes.KeyAttributes rsaKeyAttributes = PsaKeyAttributes.KeyAttributes.newBuilder() + .setKeyBits(1024) + .setKeyType(PsaKeyAttributes.KeyType.newBuilder() + .setRsaKeyPair(PsaKeyAttributes.KeyType.RsaKeyPair.newBuilder().build()).build()) + .setKeyPolicy(PsaKeyAttributes.KeyPolicy.newBuilder() + .setKeyUsageFlags( + PsaKeyAttributes.UsageFlags.newBuilder().setSignHash(true).setVerifyHash(true) + .setSignMessage(true).setVerifyMessage(true).setExport(true).build()) + .setKeyAlgorithm(PsaAlgorithm.Algorithm.newBuilder() + .setAsymmetricSignature(PsaAlgorithm.Algorithm.AsymmetricSignature.newBuilder() + .setRsaPkcs1V15Sign(PsaAlgorithm.Algorithm.AsymmetricSignature.RsaPkcs1v15Sign + .newBuilder() + .setHashAlg(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash.newBuilder() + .setSpecific(PsaAlgorithm.Algorithm.Hash.SHA_256).build()) + .build()) + .build())) + .build()) + .build(); + + + PsaAlgorithm.Algorithm.AsymmetricSignature rsaKeyArgs = + PsaAlgorithm.Algorithm.AsymmetricSignature.newBuilder() + .setRsaPkcs1V15Sign( + PsaAlgorithm.Algorithm.AsymmetricSignature.RsaPkcs1v15Sign + .newBuilder().setHashAlg(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash + .newBuilder().setSpecific(PsaAlgorithm.Algorithm.Hash.SHA_256).build()) + .build()) + .build(); + + @BeforeEach void setup() { + + log.info("Setting up test"); // uid of the parse user in docker Uid.IMPL.set(() -> 4000); + + // Wait for container to be running Awaitility.await().until(parsecContainer::isRunning); - this.client = - BasicClient.client( - "parsec-tool", IpcHandler.connectFromUrl(parsecContainer.getSocketUri())); + + // Print container logs for debugging + log.info("Container logs:"); + log.info(parsecContainer.getLogs()); + + // Wait a bit for socat to be ready + Awaitility.await().pollDelay(Duration.ofSeconds(1)).until(() -> true); + + this.client = BasicClient.client("parsec-tool", + IpcHandler.connectFromUrl(parsecContainer.getSocketUri())); parsecContainer.parsecTool("create-ecc-key", "--key-name", eccKey); parsecContainer.parsecTool("create-rsa-key", "--key-name", rsaKey); } + /** * would be good to have this dockerized ssh can forward AF_UNIX sockets * @@ -70,34 +150,63 @@ void listKeys() { @Test @SneakyThrows - void hash() { - PsaAlgorithm.Algorithm.AsymmetricSignature keyargs = - PsaAlgorithm.Algorithm.AsymmetricSignature.newBuilder() - .setEcdsa( - PsaAlgorithm.Algorithm.AsymmetricSignature.Ecdsa.newBuilder() - .setHashAlg( - PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash.newBuilder() - .setSpecific(PsaAlgorithm.Algorithm.Hash.SHA_256) - .build()) - .build()) - .build(); + void hashRSAKey() { + + String keyName = "rsa-test-key"; + client.psaGenerateKey(keyName, rsaKeyAttributes); byte[] bytes = new byte[1024]; new SecureRandom().nextBytes(bytes); - NativeResult.PsaSignHashResult hashResult = client.psaSignHash(eccKey, bytes, keyargs); - byte[] signature = hashResult.getSignature(); - assertNotNull(signature); + try { + NativeResult.PsaSignHashResult hashResult = client.psaSignHash(keyName, bytes, rsaKeyArgs); + byte[] signature = hashResult.getSignature(); + assertNotNull(signature); + + NativeResult.PsaVerifyHashResult verifiedResult = + client.psaVerifyHash(keyName, bytes, rsaKeyArgs, signature); + assertNotNull(verifiedResult); + + try { + bytes[0] += 1; + client.psaVerifyHash(keyName, bytes, rsaKeyArgs, signature); + fail("signature must no verify"); + } catch (Exception e) { + // OK + } + } catch (Exception e) { + // wait 60 seconds so I can analyze docker output, then end + Thread.sleep(30000L); + } + } - NativeResult.PsaVerifyHashResult verifiedResult = - client.psaVerifyHash(eccKey, bytes, keyargs, signature); - assertNotNull(verifiedResult); + @Test + @SneakyThrows + void hashECCKey() { + + String keyName = "ecc-test-key"; + client.psaGenerateKey(keyName, eccKeyAttributes); + byte[] bytes = new byte[1024]; + new SecureRandom().nextBytes(bytes); try { - bytes[0] += 1; - client.psaVerifyHash(eccKey, bytes, keyargs, signature); - fail("signature must no verify"); + NativeResult.PsaSignHashResult hashResult = client.psaSignHash(keyName, bytes, eccKeyArgs); + byte[] signature = hashResult.getSignature(); + assertNotNull(signature); + + NativeResult.PsaVerifyHashResult verifiedResult = + client.psaVerifyHash(keyName, bytes, eccKeyArgs, signature); + assertNotNull(verifiedResult); + + try { + bytes[0] += 1; + client.psaVerifyHash(keyName, bytes, rsaKeyArgs, signature); + fail("signature must no verify"); + } catch (Exception e) { + // OK + } } catch (Exception e) { - // OK + // wait 60 seconds so I can analyze docker output, then end + Thread.sleep(30000L); } } @@ -105,10 +214,9 @@ void hash() { void generateRandom() { long length = 512L; - byte[] randomBytes = client.psaGenerateRandom(length); + byte[] randomBytes = client.psaGenerateRandom(length); assertNotNull(randomBytes); - assertEquals((long)randomBytes.length, length); + assertEquals((long) randomBytes.length, length); } - } diff --git a/parsec-client-java/src/test/resources/cryptoauthlib-config.toml.bak b/parsec-client-java/src/test/resources/cryptoauthlib-config.toml.bak index 71d0d8f..8d8e5de 100644 --- a/parsec-client-java/src/test/resources/cryptoauthlib-config.toml.bak +++ b/parsec-client-java/src/test/resources/cryptoauthlib-config.toml.bak @@ -2,6 +2,7 @@ # The CI already timestamps the logs log_timestamp = false log_error_details = true +log_level = "debug" # Possible values: "debug", "info", "warn", "error", "trace" # The container runs the Parsec service as root, so make sure we disable root # checks. @@ -20,7 +21,7 @@ auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" -store_path = "/var/lib/parsec/mappings" +store_path = "/parsec/quickstart/mappings\" [[provider]] provider_type = "CryptoAuthLib" diff --git a/parsec-client-java/src/test/resources/logback-test.xml b/parsec-client-java/src/test/resources/logback-test.xml new file mode 100644 index 0000000..575807d --- /dev/null +++ b/parsec-client-java/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/parsec-client-java/src/test/resources/logback.xml.bak b/parsec-client-java/src/test/resources/logback.xml.bak deleted file mode 100644 index b1f9bfe..0000000 --- a/parsec-client-java/src/test/resources/logback.xml.bak +++ /dev/null @@ -1,11 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - diff --git a/parsec-client-java/src/test/resources/mbed-crypto-config.toml b/parsec-client-java/src/test/resources/mbed-crypto-config.toml index 2f12760..2a10bd2 100644 --- a/parsec-client-java/src/test/resources/mbed-crypto-config.toml +++ b/parsec-client-java/src/test/resources/mbed-crypto-config.toml @@ -1,10 +1,12 @@ +# See https://parallaxsecond.github.io/parsec-book/parsec_service/configuration.html for a full +# example. + [core_settings] # The CI already timestamps the logs log_timestamp = false log_error_details = true - -# The container runs the Parsec service as root, so make sure we disable root -# checks. +log_level = "debug" # Possible values: "debug", "info", "warn", "error", "trace" +# The container runs the Parsec service as root, so make sure we disable root checks. allow_root = true [listener] @@ -20,9 +22,8 @@ auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" -store_path = "/var/lib/parsec/mappings" +store_path = "/parsec/quickstart/mappings" [[provider]] provider_type = "MbedCrypto" key_info_manager = "on-disk-manager" - diff --git a/parsec-interface-java/pom.xml b/parsec-interface-java/pom.xml index ecb6488..d696f57 100644 --- a/parsec-interface-java/pom.xml +++ b/parsec-interface-java/pom.xml @@ -25,7 +25,7 @@ org.slf4j slf4j-api - 1.7.32 + ${slf4j.version} diff --git a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/Request.java b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/Request.java index a2b1dba..ddd2d55 100644 --- a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/Request.java +++ b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/Request.java @@ -1,16 +1,17 @@ package org.parallaxsecond.parsec.protocol.requests.request; +import java.io.IOException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.text.MessageFormat; + import org.parallaxsecond.parsec.protocol.requests.InterfaceException; import org.parallaxsecond.parsec.protocol.requests.ResponseStatus; import org.parallaxsecond.parsec.protocol.requests.request.common.WireHeader_1_0; + import lombok.Builder; import lombok.RequiredArgsConstructor; -import java.io.IOException; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; -import java.text.MessageFormat; - /** Representation of the request wire format. */ @Builder @RequiredArgsConstructor @@ -33,11 +34,13 @@ public class Request { /** * Deserialise request from given stream. * - *

Request header is parsed from its raw form, ensuring that all fields are valid. The + *

+ * Request header is parsed from its raw form, ensuring that all fields are valid. The * `body_len_limit` parameter allows the interface client to reject requests that are longer than * a predefined limit. The length limit is in bytes. * - *

# Errors - if reading any of the subfields (header, body or auth) fails, the corresponding + *

+ * # Errors - if reading any of the subfields (header, body or auth) fails, the corresponding * `ResponseStatus` will be returned. - if the request body size specified in the header is larger * than the limit passed as a parameter, `BodySizeExceedsLimit` will be returned. */ @@ -46,11 +49,8 @@ public static Request readFromStream(ReadableByteChannel channel, int bodyLenLim WireHeader_1_0 rawHeader = WireHeader_1_0.readFromStream(channel); int bodyLen = rawHeader.getBodyLen(); if (bodyLen > bodyLenLimit) { - throw new InterfaceException( - ResponseStatus.BodySizeExceedsLimit, - MessageFormat.format( - "Request body length ({0}) bigger than the limit given ({1}).", - bodyLen, bodyLenLimit)); + throw new InterfaceException(ResponseStatus.BodySizeExceedsLimit, MessageFormat.format( + "Request body length ({0}) bigger than the limit given ({1}).", bodyLen, bodyLenLimit)); } RequestBody body = RequestBody.readFromStream(channel, bodyLen); RequestAuth auth = RequestAuth.readFromStream(channel, rawHeader.getAuthLen()); @@ -61,19 +61,22 @@ public static Request readFromStream(ReadableByteChannel channel, int bodyLenLim /** * Serialise request and write it to given stream. * - *

Request header is first converted to its raw format before serialization. # Errors - if an - * IO operation fails while writing any of the subfields of the request, + *

+ * Request header is first converted to its raw format before serialization. # Errors - if an IO + * operation fails while writing any of the subfields of the request, * `ResponseStatus::ConnectionError` is returned. - if encoding any of the fields in the header * fails, `ResponseStatus::InvalidEncoding` is returned. */ public void writeToStream(WritableByteChannel channel) throws IOException { - header - .toRaw() - .bodyLen(body.length()) - .authLen((short) auth.getBuffer().length()) - .build() + header.toRaw().bodyLen(body.length()).authLen((short) auth.getBuffer().length()).build() .writeToStream(channel); body.writeToStream(channel); auth.writeToStream(channel); } + + @Override + public String toString() { + return "Request{" + "header=" + header + ", body=" + body + ", auth=" + auth + '}'; + } + } diff --git a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestBody.java b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestBody.java index 91aa5a4..c60067d 100644 --- a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestBody.java +++ b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestBody.java @@ -1,8 +1,5 @@ package org.parallaxsecond.parsec.protocol.requests.request; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -10,9 +7,13 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + @RequiredArgsConstructor public class RequestBody { - @Getter private final ByteBuffer buffer; + @Getter + private final ByteBuffer buffer; public RequestBody(byte[] buf) { this(ByteBuffer.wrap(buf)); @@ -22,7 +23,7 @@ public static RequestBody readFromStream(ReadableByteChannel channel, int len) throws IOException { ByteBuffer buf = ByteBuffer.allocate(len).order(ByteOrder.LITTLE_ENDIAN); channel.read(buf); - ((Buffer)buf).flip(); + ((Buffer) buf).flip(); return new RequestBody(buf); } @@ -33,4 +34,9 @@ public void writeToStream(WritableByteChannel channel) throws IOException { public int length() { return buffer.remaining(); } + + @Override + public String toString() { + return "RequestBody current length=" + length(); + } } diff --git a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestHeader.java b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestHeader.java index a0b55f1..8404b08 100644 --- a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestHeader.java +++ b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/RequestHeader.java @@ -5,6 +5,7 @@ import org.parallaxsecond.parsec.protocol.requests.Opcode; import org.parallaxsecond.parsec.protocol.requests.ProviderId; import org.parallaxsecond.parsec.protocol.requests.request.common.WireHeader_1_0; + import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -12,7 +13,8 @@ /** * A native representation of the request header. * - *

Fields that are not relevant for application development (e.g. magic number) are not copied + *

+ * Fields that are not relevant for application development (e.g. magic number) are not copied * across from the raw header. */ @RequiredArgsConstructor @@ -33,29 +35,26 @@ public class RequestHeader { private final Opcode opcode; public static RequestHeader fromRaw(WireHeader_1_0 wireHeader) { - return RequestHeader.builder() - .provider(ProviderId.fromCode(wireHeader.getProvider())) + return RequestHeader.builder().provider(ProviderId.fromCode(wireHeader.getProvider())) .session(wireHeader.getSession()) .contentType(BodyType.fromCode(wireHeader.getContentType())) .acceptType(BodyType.fromCode(wireHeader.getAcceptType())) .authType(AuthType.fromCode(wireHeader.getAuthType())) - .opcode(Opcode.fromCode(wireHeader.getOpcode())) - .build(); + .opcode(Opcode.fromCode(wireHeader.getOpcode())).build(); } public WireHeader_1_0.WireHeader_1_0Builder toRaw() { - return WireHeader_1_0.builder() - .flags((short) 0) - .provider(getProvider().getId()) - .session(getSession()) - .contentType(getContentType().getId()) - .acceptType(getAcceptType().getId()) - .authType(getAuthType().getId()) - .bodyLen(0) - .authLen((short) 0) - .opcode(getOpcode().getCode()) - .status((short) 0) - .reserved1((byte) 0) + return WireHeader_1_0.builder().flags((short) 0).provider(getProvider().getId()) + .session(getSession()).contentType(getContentType().getId()) + .acceptType(getAcceptType().getId()).authType(getAuthType().getId()).bodyLen(0) + .authLen((short) 0).opcode(getOpcode().getCode()).status((short) 0).reserved1((byte) 0) .reserved2((byte) 0); } + + @Override + public String toString() { + return "RequestHeader{" + "provider=" + provider + ", session=" + session + ", contentType=" + + contentType + ", acceptType=" + acceptType + ", authType=" + authType + ", opcode=" + + opcode + '}'; + } } diff --git a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/common/WireHeader_1_0.java b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/common/WireHeader_1_0.java index bc3de24..3e0a66b 100644 --- a/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/common/WireHeader_1_0.java +++ b/parsec-interface-java/src/main/java/org/parallaxsecond/parsec/protocol/requests/request/common/WireHeader_1_0.java @@ -1,11 +1,5 @@ package org.parallaxsecond.parsec.protocol.requests.request.common; -import org.parallaxsecond.parsec.protocol.requests.InterfaceException; -import org.parallaxsecond.parsec.protocol.requests.ResponseStatus; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -14,18 +8,29 @@ import java.nio.channels.WritableByteChannel; import java.text.MessageFormat; +import org.parallaxsecond.parsec.protocol.requests.InterfaceException; +import org.parallaxsecond.parsec.protocol.requests.ResponseStatus; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + /** * This module defines and implements the raw wire protocol header frame for version 1.0 of the * protocol. * - *

Raw representation of a common request/response header, as defined for the wire format. + *

+ * Raw representation of a common request/response header, as defined for the wire format. * - *

Serialisation and deserialisation are handled by `serde`, also in tune with the wire format - * (i.e. little-endian, native encoding). + *

+ * Serialisation and deserialisation are handled by `serde`, also in tune with the wire format (i.e. + * little-endian, native encoding). */ @RequiredArgsConstructor @Builder @Getter +@Slf4j public class WireHeader_1_0 { public static final int MAGIC_NUMBER = 0x5EC0_A710; public static final byte WIRE_PROTOCOL_VERSION_MAJ = 1; @@ -63,53 +68,35 @@ public static WireHeader_1_0 readFromStream(ReadableByteChannel channel) throws ByteBuffer buf = ByteBuffer.allocate(REQUEST_HDR_SIZE + 6).order(ByteOrder.LITTLE_ENDIAN); channel.read(buf); - ((Buffer)buf).flip(); + ((Buffer) buf).flip(); int magicNumber = buf.getInt(); if (magicNumber != MAGIC_NUMBER) { - throw new InterfaceException( - ResponseStatus.InvalidHeader, + throw new InterfaceException(ResponseStatus.InvalidHeader, MessageFormat.format("Expected magic number {0}, got {1}", MAGIC_NUMBER, magicNumber)); } short hdrSize = buf.getShort(); if (hdrSize != REQUEST_HDR_SIZE || buf.remaining() < hdrSize) { - throw new InterfaceException( - ResponseStatus.InvalidHeader, - MessageFormat.format( - "Expected request header size {0}, got {1}, remaining {2}", + throw new InterfaceException(ResponseStatus.InvalidHeader, + MessageFormat.format("Expected request header size {0}, got {1}, remaining {2}", REQUEST_HDR_SIZE, hdrSize, buf.remaining())); } int versionMaj = buf.get(); int versionMin = buf.get(); if (versionMaj != WIRE_PROTOCOL_VERSION_MAJ || versionMin != WIRE_PROTOCOL_VERSION_MIN) { - throw new InterfaceException( - ResponseStatus.WireProtocolVersionNotSupported, - MessageFormat.format( - "Expected wire protocol version {0}.{1}, got {2}.{3} instead", + throw new InterfaceException(ResponseStatus.WireProtocolVersionNotSupported, + MessageFormat.format("Expected wire protocol version {0}.{1}, got {2}.{3} instead", WIRE_PROTOCOL_VERSION_MAJ, WIRE_PROTOCOL_VERSION_MIN, versionMaj, versionMin)); } - WireHeader_1_0 wireHeader = - WireHeader_1_0.builder() - .flags(buf.getShort()) - .provider(buf.get()) - .session(buf.getLong()) - .contentType(buf.get()) - .acceptType(buf.get()) - .authType(buf.get()) - .bodyLen(buf.getInt()) - .authLen(buf.getShort()) - .opcode(buf.getInt()) - .status(buf.getShort()) - .reserved1(buf.get()) - .reserved2(buf.get()) - .build(); + WireHeader_1_0 wireHeader = WireHeader_1_0.builder().flags(buf.getShort()).provider(buf.get()) + .session(buf.getLong()).contentType(buf.get()).acceptType(buf.get()).authType(buf.get()) + .bodyLen(buf.getInt()).authLen(buf.getShort()).opcode(buf.getInt()).status(buf.getShort()) + .reserved1(buf.get()).reserved2(buf.get()).build(); if (wireHeader.reserved1 != 0x00 || wireHeader.reserved2 != 0x00) { - throw new InterfaceException( - ResponseStatus.InvalidHeader, - MessageFormat.format( - "expected reserved1 0, got {0}, reserved2 0, got {1}", + throw new InterfaceException(ResponseStatus.InvalidHeader, + MessageFormat.format("expected reserved1 0, got {0}, reserved2 0, got {1}", wireHeader.reserved1, wireHeader.reserved2)); } return wireHeader; @@ -118,30 +105,29 @@ public static WireHeader_1_0 readFromStream(ReadableByteChannel channel) throws /** * Serialise the request header and write the corresponding bytes to the given stream. * - *

# Errors - if marshalling the header fails, `ResponseStatus::InvalidEncoding` is returned. - - * if writing the header bytes fails, `ResponseStatus::ConnectionError` is returned. + *

+ * # Errors - if marshalling the header fails, `ResponseStatus::InvalidEncoding` is returned. - if + * writing the header bytes fails, `ResponseStatus::ConnectionError` is returned. */ public void writeToStream(WritableByteChannel channel) throws IOException { - ByteBuffer buf = - ByteBuffer.allocate(REQUEST_HDR_SIZE + 6) - .order(ByteOrder.LITTLE_ENDIAN) - .putInt(MAGIC_NUMBER) // 4 - .putShort(REQUEST_HDR_SIZE) // 6 - .put(WIRE_PROTOCOL_VERSION_MAJ) // 7 - .put(WIRE_PROTOCOL_VERSION_MIN) // 8 - .putShort(flags) // 10 - .put(provider) // 11 - .putLong(session) // 19 - .put(contentType) // 20 - .put(acceptType) // 21 - .put(authType) // 22 - .putInt(bodyLen) // 26 - .putShort(authLen) // 28 - .putInt(opcode) // 32 - .putShort(status) // 34 - .put(reserved1) // 35 - .put(reserved2); // 36 - ((Buffer)buf).flip(); + ByteBuffer buf = ByteBuffer.allocate(REQUEST_HDR_SIZE + 6).order(ByteOrder.LITTLE_ENDIAN) + .putInt(MAGIC_NUMBER) // 4 + .putShort(REQUEST_HDR_SIZE) // 6 + .put(WIRE_PROTOCOL_VERSION_MAJ) // 7 + .put(WIRE_PROTOCOL_VERSION_MIN) // 8 + .putShort(flags) // 10 + .put(provider) // 11 + .putLong(session) // 19 + .put(contentType) // 20 + .put(acceptType) // 21 + .put(authType) // 22 + .putInt(bodyLen) // 26 + .putShort(authLen) // 28 + .putInt(opcode) // 32 + .putShort(status) // 34 + .put(reserved1) // 35 + .put(reserved2); // 36 + ((Buffer) buf).flip(); channel.write(buf); } diff --git a/parsec-jca-java-test/pom.xml b/parsec-jca-java-test/pom.xml index 8aa1a15..30d5c5a 100644 --- a/parsec-jca-java-test/pom.xml +++ b/parsec-jca-java-test/pom.xml @@ -1,7 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.parallaxsecond @@ -26,7 +25,7 @@ org.junit.jupiter junit-jupiter-api - 5.8.0 + ${junit.jupiter.version} test @@ -35,15 +34,15 @@ 5.1.1 - org.parallaxsecond + ${project.groupId} parsec-jca-java - 1.0.0-SNAPSHOT + ${project.version} test ch.qos.logback logback-classic - 1.2.5 + ${logback.version} test @@ -54,6 +53,17 @@ true + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + none + + + \ No newline at end of file diff --git a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/HttpClientTlsTest.java b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/HttpClientTlsTest.java index f8c8f8d..1f954c8 100644 --- a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/HttpClientTlsTest.java +++ b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/HttpClientTlsTest.java @@ -1,7 +1,25 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import static java.lang.String.format; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import static java.util.Optional.ofNullable; +import java.util.stream.Stream; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.KeyStoreBuilderParameters; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManagerFactory; + import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -9,11 +27,11 @@ import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; -import org.apache.hc.client5.http.ssl.HttpClientHostnameVerifier; -import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.http.config.Registry; import org.apache.hc.core5.http.config.RegistryBuilder; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -21,8 +39,6 @@ import org.parallaxsecond.parsec.client.core.BasicClient; import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; import org.parallaxsecond.parsec.client.jna.Uid; -import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm; -import org.parallaxsecond.parsec.protobuf.psa_key_attributes.PsaKeyAttributes; import org.parallaxsecond.testcontainers.ParsecContainer; import org.testcontainers.containers.Container.ExecResult; import org.testcontainers.containers.GenericContainer; @@ -31,169 +47,154 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.shaded.org.awaitility.Awaitility; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.KeyStoreBuilderParameters; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyStore; -import java.security.Provider; -import java.security.Security; -import java.util.stream.Stream; - -import static java.lang.String.format; -import static java.util.Optional.ofNullable; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; /** */ -@Testcontainers @Slf4j +@Testcontainers class HttpClientTlsTest { - private static final char[] keystorePassword = "changeme".toCharArray(); - - @Container - GenericContainer nginxContainer = - new GenericContainer<>("parallaxsecond/nginx-test:latest") - .withExposedPorts(80, 443) - .waitingFor(new HttpWaitStrategy().forPort(80).forStatusCode(200)) - .withFileSystemBind( - absFile("src/test/resources/nginx-client-auth/init.sh"), "/init.sh"); - - @Container - ParsecContainer parsecContainer = - ParsecContainer.withVersion("0.8.1") - .withFileSystemBind( - absFile("src/test/resources/mbed-crypto-config.toml"), - "/etc/parsec/config.toml"); - - String hostPort; - private TrustManagerFactory tmf; - - @SneakyThrows - private static KeyStore defaultKeystoreFromFile(Path file) { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream in = new FileInputStream(file.toFile())) { - keyStore.load(in, keystorePassword); + private static final char[] keystorePassword = "changeme".toCharArray(); + + @Container + GenericContainer nginxContainer = + new GenericContainer<>("parallaxsecond/nginx-test:latest") + .withExposedPorts(80, 443) + .waitingFor(new HttpWaitStrategy().forPort(80) + .forStatusCode(200)) + .withFileSystemBind(absFile( + "src/test/resources/nginx-client-auth/init.sh"), + "/init.sh"); + + @Container + ParsecContainer parsecContainer = ParsecContainer.withVersion("latest").withFileSystemBind( + absFile("src/test/resources/mbed-crypto-config.toml"), + "/etc/parsec/config.toml"); + + String hostPort; + private TrustManagerFactory tmf; + + @SneakyThrows + private static KeyStore defaultKeystoreFromFile(Path file) { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream in = new FileInputStream(file.toFile())) { + keyStore.load(in, keystorePassword); + } + return keyStore; } - return keyStore; - } - - @SneakyThrows - private KeyManagerFactory prepareParsecTest() { - Path clientKeyDer = copyFromNginx("/keys/client.der"); - BasicClient client = - BasicClient.client( - "parsec-jca-provider", - IpcHandler.connectFromUrl(parsecContainer.getSocketUri())); - client.psaImportKey( - "client", - Files.readAllBytes(clientKeyDer), - ParsecCipherSuites.RSA_WITH_PKCS1.getKeyAttributes()); - - URI socketUri = parsecContainer.getSocketUri(); - Provider parsec = ParsecProvider.builder().socketUri(socketUri).build(); - Security.insertProviderAt(parsec, 0); - - Path clientCertStoreFile = copyFromNginx("/keys/client_cert.jks"); - KeyStore clientCertStore = defaultKeystoreFromFile(clientCertStoreFile); - - // ParsecProvider.init(socketUri); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509", "PARSEC"); - kmf.init( - new KeyStoreBuilderParameters( - KeyStore.Builder.newInstance( - clientCertStore, + + @SneakyThrows + private KeyManagerFactory prepareParsecTest() { + Path clientKeyDer = copyFromNginx("/keys/client.der"); + BasicClient client = BasicClient.client("parsec-jca-provider", + IpcHandler.connectFromUrl(parsecContainer.getSocketUri())); + client.psaImportKey("client", Files.readAllBytes(clientKeyDer), + ParsecCipherSuites.RSA_WITH_PKCS1.getKeyAttributes()); + + URI socketUri = parsecContainer.getSocketUri(); + Provider parsec = ParsecProvider.builder().socketUri(socketUri).build(); + Security.insertProviderAt(parsec, 0); + + Path clientCertStoreFile = copyFromNginx("/keys/client_cert.jks"); + KeyStore clientCertStore = defaultKeystoreFromFile(clientCertStoreFile); + + // ParsecProvider.init(socketUri); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509", "PARSEC"); + kmf.init(new KeyStoreBuilderParameters(KeyStore.Builder.newInstance(clientCertStore, new KeyStore.PasswordProtection(keystorePassword)))); - return kmf; - } - - @SneakyThrows - private KeyManagerFactory onFileJksTest() { - Path clientKeyStore = copyFromNginx("/keys/client.jks"); - KeyManagerFactory kmf = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(defaultKeystoreFromFile(clientKeyStore), keystorePassword); - return kmf; - } - - private String absFile(String f) { - return new File(f).getAbsolutePath(); - } - - @BeforeEach - @SneakyThrows - void setup() { - Awaitility.await().until(nginxContainer::isRunning); - ExecResult r = - nginxContainer.execInContainer( - "sh", - "-c", - format("/init.sh %s %s /", nginxContainer.getHost(), new String(keystorePassword))); - assertEquals(0, r.getExitCode(), r.getStderr() + r.getStdout()); - - Path serverTrustStore = copyFromNginx("/keys/server_chain.jks"); - tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(defaultKeystoreFromFile(serverTrustStore)); - - // uid of the parse user in docker - Uid.IMPL.set(() -> 4000); - Awaitility.await().until(parsecContainer::isRunning); - } - - @SneakyThrows - private Path copyFromNginx(String path) { - Path tempFile = Files.createTempFile("file", ".temp"); - nginxContainer.copyFileFromContainer(path, tempFile.toFile().getAbsolutePath()); - return tempFile; - } - - - @SneakyThrows - static Stream testHttpClient() { - return Stream.of( - //Arguments.of("No keys", 403, (KmfTestFactory) t -> null), - Arguments.of("JCA/Parsec", 200, (KmfTestFactory) HttpClientTlsTest::prepareParsecTest) - // Arguments.of("JCA/Default/FS", 200, (KmfTestFactory) HttpClientTlsTest::onFileJksTest) - ); - } - - @MethodSource - @ParameterizedTest - @SneakyThrows - void testHttpClient(String description, int expectedResponseCode, KmfTestFactory kmfFactory) { - log.info("running test {}", description); - KeyManagerFactory kmf = kmfFactory.get(this); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init( - ofNullable(kmf).map(KeyManagerFactory::getKeyManagers).orElse(null), - tmf.getTrustManagers(), - null); - - assertNotNull(sslContext.getProvider()); - - SSLConnectionSocketFactory sslsf = - new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier()); - - Registry socketFactoryRegistry = - RegistryBuilder.create().register("https", sslsf).build(); - BasicHttpClientConnectionManager connectionManager = - new BasicHttpClientConnectionManager(socketFactoryRegistry); - CloseableHttpClient httpClient = - HttpClients.custom().setConnectionManager(connectionManager).build(); - CloseableHttpResponse r = httpClient.execute( - new HttpGet(format("https://%s:%s", nginxContainer.getHost(), nginxContainer.getMappedPort(443)))); - assertEquals(expectedResponseCode, r.getCode()); - } - - interface KmfTestFactory { - KeyManagerFactory get(HttpClientTlsTest t) throws Exception; - } + return kmf; + } + + @SneakyThrows + private KeyManagerFactory onFileJksTest() { + Path clientKeyStore = copyFromNginx("/keys/client.jks"); + KeyManagerFactory kmf = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(defaultKeystoreFromFile(clientKeyStore), keystorePassword); + return kmf; + } + + private String absFile(String f) { + return new File(f).getAbsolutePath(); + } + + // Allow all hostnames for testing purposes + HostnameVerifier allowAll = new HostnameVerifier() { + @Override + public boolean verify(String hostName, SSLSession session) { + return true; + } + }; + + @BeforeEach + @SneakyThrows + void setup() { + // Enable SSL debugging + System.setProperty("javax.net.debug", "ssl,handshake"); + + Awaitility.await().until(nginxContainer::isRunning); + ExecResult r = nginxContainer.execInContainer("sh", "-c", format("/init.sh %s %s /", + nginxContainer.getHost(), new String(keystorePassword))); + assertEquals(0, r.getExitCode(), r.getStderr() + r.getStdout()); + + Path serverTrustStore = copyFromNginx("/keys/server_chain.jks"); + tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(defaultKeystoreFromFile(serverTrustStore)); + + // uid of the parse user in docker + Uid.IMPL.set(() -> 4000); + Awaitility.await().until(parsecContainer::isRunning); + } + + @SneakyThrows + private Path copyFromNginx(String path) { + Path tempFile = Files.createTempFile("file", ".temp"); + nginxContainer.copyFileFromContainer(path, tempFile.toFile().getAbsolutePath()); + return tempFile; + } + + + @SneakyThrows + static Stream testHttpClient() { + return Stream.of( + // Arguments.of("No keys", 403, (KmfTestFactory) t -> null), + Arguments.of("JCA/Parsec", 200, + (KmfTestFactory) HttpClientTlsTest::prepareParsecTest) + // Arguments.of("JCA/Default/FS", 200, (KmfTestFactory) + // HttpClientTlsTest::onFileJksTest) + ); + } + + @MethodSource + @ParameterizedTest + @SneakyThrows + void testHttpClient(String description, int expectedResponseCode, + KmfTestFactory kmfFactory) { + log.info("running test {}", description); + KeyManagerFactory kmf = kmfFactory.get(this); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(ofNullable(kmf).map(KeyManagerFactory::getKeyManagers).orElse(null), + tmf.getTrustManagers(), null); + + assertNotNull(sslContext.getProvider()); + + SSLConnectionSocketFactory sslsf = + new SSLConnectionSocketFactory(sslContext, allowAll); + + Registry socketFactoryRegistry = RegistryBuilder + .create().register("https", sslsf).build(); + BasicHttpClientConnectionManager connectionManager = + new BasicHttpClientConnectionManager(socketFactoryRegistry); + CloseableHttpClient httpClient = HttpClients.custom() + .setConnectionManager(connectionManager).build(); + CloseableHttpResponse r = httpClient.execute(new HttpGet(format("https://%s:%s", + nginxContainer.getHost(), nginxContainer.getMappedPort(443)))); + assertEquals(expectedResponseCode, r.getCode()); + } + + interface KmfTestFactory { + KeyManagerFactory get(HttpClientTlsTest t) throws Exception; + } } diff --git a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignatureTest.java b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignatureTest.java index a426d8d..f741eaf 100644 --- a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignatureTest.java +++ b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignatureTest.java @@ -1,27 +1,59 @@ package org.parallaxsecond.parsec.jce.provider; -import org.junit.jupiter.api.Test; - +import java.io.File; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.parallaxsecond.parsec.client.jna.Uid; +import org.parallaxsecond.testcontainers.ParsecContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.shaded.org.awaitility.Awaitility; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +@Testcontainers +@Slf4j class ParsecRsaSignatureTest { + private String absFile(String f) { + return new File(f).getAbsolutePath(); + } + + @Container + ParsecContainer parsecContainer = ParsecContainer.withVersion("latest").withFileSystemBind( + absFile("src/test/resources/mbed-crypto-config.toml"), "/etc/parsec/config.toml"); + + @BeforeEach + @SneakyThrows + void setup() { + // uid of the parsec user in docker + Uid.IMPL.set(() -> 4000); + Awaitility.await().until(parsecContainer::isRunning); + + // Create and install the Parsec provider at highest priority + Provider provider = ParsecProvider.builder().socketUri(parsecContainer.getSocketUri()) + .parsecAppName("parsec-test").build(); + + // Insert at position 0 for highest priority + Security.insertProviderAt(provider, 0); + } + @Test void testMesageDigests() { - Arrays.stream(ParsecRsaSignature.values()) - .forEach( - e -> { - try { - e.createMessageDigest(); - } catch (NoSuchAlgorithmException ex) { - fail( - e - + " message digest factory not working " - + e.getAlgorithmName()); - } - }); + Arrays.stream(ParsecRsaSignature.values()).forEach(e -> { + try { + e.createMessageDigest(); + } catch (NoSuchAlgorithmException ex) { + log.error("message digest factory not working", ex); + fail(e + " message digest factory not working " + e.getAlgorithmName()); + } + }); } } diff --git a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecSignatureTest.java b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecSignatureTest.java index 6f8f113..5ce5b1a 100644 --- a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecSignatureTest.java +++ b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/ParsecSignatureTest.java @@ -1,69 +1,64 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; +import java.io.File; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.parallaxsecond.parsec.client.core.BasicClient; import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; +import static org.parallaxsecond.parsec.jce.provider.ParsecCipherSuites.RSA_WITH_PKCS1; import org.parallaxsecond.testcontainers.ParsecContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.File; -import java.util.Random; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.parallaxsecond.parsec.jce.provider.ParsecCipherSuites.RSA_WITH_PKCS1; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; @Testcontainers @Slf4j class ParsecSignatureTest { - @Container - ParsecContainer parsecContainer = - ParsecContainer.withVersion("0.8.1") - .withFileSystemBind( - new File("src/test/resources/mbed-crypto-config.toml").getAbsolutePath(), - "/etc/parsec/config.toml"); + @Container + ParsecContainer parsecContainer = ParsecContainer.withVersion("latest").withFileSystemBind( + new File("src/test/resources/mbed-crypto-config.toml").getAbsolutePath(), + "/etc/parsec/config.toml"); - @SneakyThrows - @Test - void signVerify() { + @SneakyThrows + @Test + void signVerify() { - byte[] payload = new byte[1024]; - new Random().nextBytes(payload); + byte[] payload = new byte[1024]; + new Random().nextBytes(payload); - String keyName="testSignVerify"; + String keyName = "testSignVerify"; - BasicClient client = BasicClient.client("parsec-tool", IpcHandler.connectFromUrl(parsecContainer.getSocketUri())); - ParsecClientAccessor parsecClient= () -> client; - ParsecSignatureInfo signatureInfo = ParsecRsaSignature.SHA256_WITH_RSA; + BasicClient client = BasicClient.client("parsec-tool", + IpcHandler.connectFromUrl(parsecContainer.getSocketUri())); + ParsecClientAccessor parsecClient = () -> client; + ParsecSignatureInfo signatureInfo = ParsecRsaSignature.SHA256_WITH_RSA; - client.psaGenerateKey(keyName, RSA_WITH_PKCS1.getKeyAttributes()); + client.psaGenerateKey(keyName, RSA_WITH_PKCS1.getKeyAttributes()); - ParsecRsaPrivateKey privKey = ParsecRsaPrivateKey.builder() + ParsecRsaPrivateKey privKey = ParsecRsaPrivateKey.builder() - .parsecName(keyName) - .algorithm("RSA") - .format("RAW") - .build(); + .parsecName(keyName).algorithm("RSA").format("RAW").build(); - ParsecSignature signature = new ParsecSignature(signatureInfo, parsecClient); - signature.engineInitSign(privKey); - signature.engineUpdate(payload, 0, payload.length); - byte[] signedBytes = signature.engineSign(); + ParsecSignature signature = new ParsecSignature(signatureInfo, parsecClient); + signature.engineInitSign(privKey); + signature.engineUpdate(payload, 0, payload.length); + byte[] signedBytes = signature.engineSign(); - ParsecPublicKey.ParsecPublicKeyImpl publicKey = ParsecPublicKey.builder() - .parsecName(keyName) - .build(); + ParsecPublicKey.ParsecPublicKeyImpl publicKey = + ParsecPublicKey.builder().parsecName(keyName).build(); - ParsecSignature verification = new ParsecSignature(signatureInfo, parsecClient); - verification.engineInitVerify(publicKey); - verification.engineUpdate(payload, 0, payload.length); - assertTrue(verification.engineVerify(signedBytes)); + ParsecSignature verification = new ParsecSignature(signatureInfo, parsecClient); + verification.engineInitVerify(publicKey); + verification.engineUpdate(payload, 0, payload.length); + assertTrue(verification.engineVerify(signedBytes)); - } + } -} \ No newline at end of file +} diff --git a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsecTest.java b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsecTest.java index fb6f8e1..3ee1555 100644 --- a/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsecTest.java +++ b/parsec-jca-java-test/src/test/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsecTest.java @@ -1,38 +1,42 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; +import java.net.URI; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Arrays; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.parallaxsecond.parsec.client.jna.Uid; import org.parallaxsecond.testcontainers.ParsecContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.shaded.org.awaitility.Awaitility; -import java.io.File; -import java.net.URI; -import java.nio.ByteBuffer; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; /** * */ @Testcontainers +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Slf4j class SecureRandomParsecTest { @Container - ParsecContainer parsecContainer = - ParsecContainer.withVersion("0.8.1") - .withFileSystemBind( - new File("src/test/resources/mbed-crypto-config.toml").getAbsolutePath(), - "/etc/parsec/config.toml"); + ParsecContainer parsecContainer = ParsecContainer.withVersion("latest"); Provider parsec; private final String[] algorithms = {"NativePRNG", "NativePRNGBlocking", "NativePRNGNonBlocking"}; @@ -44,9 +48,9 @@ void setup() { Uid.IMPL.set(() -> 4000); Awaitility.await().until(parsecContainer::isRunning); URI socketUri = parsecContainer.getSocketUri(); - parsec = ParsecProvider.builder() - .socketUri(socketUri) - .build(); + parsec = ParsecProvider.builder().socketUri(socketUri).build(); + + String eccKey = "eccKey"; parsecContainer.parsecTool("create-ecc-key", "--key-name", eccKey); String rsaKey = "rsaKey"; @@ -55,8 +59,20 @@ void setup() { Security.getProvider(parsec.getName()); } + @AfterEach + void cleanup() { + if (parsec != null) { + Security.removeProvider(parsec.getName()); + } + // Force close any lingering connections + if (parsecContainer != null) { + parsecContainer.close(); + } + } + @Test @SneakyThrows + @Order(1) void setSeedBytes() { byte[] seed = new byte[512]; for (String algorithm : algorithms) { @@ -66,12 +82,15 @@ void setSeedBytes() { secureRandom.setSeed(seed); } catch (UnsupportedOperationException e) { assertEquals(e.getClass(), UnsupportedOperationException.class); + } catch (NoSuchAlgorithmException e) { + fail("No such algorithm: " + algorithm); } } } @Test @SneakyThrows + @Order(1) void nextBytes() { byte[] rand = new byte[4]; byte[] originalRand = new byte[4]; @@ -81,13 +100,14 @@ void nextBytes() { SecureRandom secureRandom = SecureRandom.getInstance(algorithm, parsec.getName()); secureRandom.nextBytes(rand); assertFalse(Arrays.equals(rand, originalRand)); - log.info("Generated random number: " + ByteBuffer.wrap(rand).getInt()); } - } + + @Test @SneakyThrows + @Order(2) void engineGenerateSeed() { byte[] seed = new byte[4]; byte[] originalRand = new byte[4]; @@ -97,8 +117,7 @@ void engineGenerateSeed() { SecureRandom secureRandom = SecureRandom.getInstance(algorithm, parsec.getName()); seed = secureRandom.generateSeed(4); assertEquals(4, seed.length); - log.info("Generated random seed: " + ByteBuffer.wrap(seed).getInt()); } } -} \ No newline at end of file +} diff --git a/parsec-jca-java-test/src/test/resources/cryptoauthlib-config.toml b/parsec-jca-java-test/src/test/resources/cryptoauthlib-config.toml index 71d0d8f..8d8e5de 100644 --- a/parsec-jca-java-test/src/test/resources/cryptoauthlib-config.toml +++ b/parsec-jca-java-test/src/test/resources/cryptoauthlib-config.toml @@ -2,6 +2,7 @@ # The CI already timestamps the logs log_timestamp = false log_error_details = true +log_level = "debug" # Possible values: "debug", "info", "warn", "error", "trace" # The container runs the Parsec service as root, so make sure we disable root # checks. @@ -20,7 +21,7 @@ auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" -store_path = "/var/lib/parsec/mappings" +store_path = "/parsec/quickstart/mappings\" [[provider]] provider_type = "CryptoAuthLib" diff --git a/parsec-jca-java-test/src/test/resources/logback-test.xml b/parsec-jca-java-test/src/test/resources/logback-test.xml new file mode 100644 index 0000000..575807d --- /dev/null +++ b/parsec-jca-java-test/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/parsec-jca-java-test/src/test/resources/logback.xml b/parsec-jca-java-test/src/test/resources/logback.xml deleted file mode 100644 index b1f9bfe..0000000 --- a/parsec-jca-java-test/src/test/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - diff --git a/parsec-jca-java-test/src/test/resources/mbed-crypto-config.toml b/parsec-jca-java-test/src/test/resources/mbed-crypto-config.toml index 2f12760..7297c34 100644 --- a/parsec-jca-java-test/src/test/resources/mbed-crypto-config.toml +++ b/parsec-jca-java-test/src/test/resources/mbed-crypto-config.toml @@ -2,6 +2,7 @@ # The CI already timestamps the logs log_timestamp = false log_error_details = true +log_level = "debug" # Possible values: "debug", "info", "warn", "error", "trace" # The container runs the Parsec service as root, so make sure we disable root # checks. @@ -20,7 +21,7 @@ auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" -store_path = "/var/lib/parsec/mappings" +store_path = "/parsec/quickstart/mappings\" [[provider]] provider_type = "MbedCrypto" diff --git a/parsec-jca-java-test/src/test/resources/nginx-client-auth/init.sh b/parsec-jca-java-test/src/test/resources/nginx-client-auth/init.sh index bb6e34a..97e98a7 100755 --- a/parsec-jca-java-test/src/test/resources/nginx-client-auth/init.sh +++ b/parsec-jca-java-test/src/test/resources/nginx-client-auth/init.sh @@ -9,7 +9,7 @@ mkdir -p "${ROOT}"/keys cd "${ROOT}"/keys mkdir -p "${ROOT}"/etc/nginx/conf.d -cat >"${ROOT}"/etc/nginx/conf.d/ssl.conf << 'EOF' +cat >"${ROOT}"/etc/nginx/conf.d/ssl.conf <<'EOF' server { listen 443 ssl; server_name example.com; @@ -29,7 +29,7 @@ server { } EOF -cat >ca.cnf << 'EOF' +cat >ca.cnf <<'EOF' FQDN = ca_cert ORGNAME = ParsecTest ALTNAMES = DNS:$FQDN # , DNS:bar.example.org , DNS:www.foo.example.org @@ -49,7 +49,7 @@ CN = $FQDN subjectAltName = $ALTNAMES EOF -cat >client.cnf << 'EOF' +cat >client.cnf <<'EOF' FQDN = client_cert ORGNAME = ParsecTest ALTNAMES = DNS:$FQDN # , DNS:bar.example.org , DNS:www.foo.example.org @@ -72,7 +72,7 @@ CN = $FQDN subjectAltName = $ALTNAMES EOF -cat >server.cnf << EOF +cat >server.cnf < client_chain.crt - +cat client.crt ca.crt >client_chain.crt # contains only certs openssl pkcs12 -export -nokeys -in client.crt \ - -name client -CAfile cclient_chain.pem \ - -passout "pass:${PASSWORD}" -out client.cert.p12 + -name client -CAfile cclient_chain.pem \ + -passout "pass:${PASSWORD}" -out client.cert.p12 rm -f client_cert.jks keytool -importcert -file client_chain.crt -alias client \ - -keystore client_cert.jks -storepass "${PASSWORD}" -noprompt + -keystore client_cert.jks -storepass "${PASSWORD}" -noprompt keytool -importcert -file ca.crt -alias ca \ - -keystore client_cert.jks -storepass "${PASSWORD}" -noprompt + -keystore client_cert.jks -storepass "${PASSWORD}" -noprompt # contains cert, certchain and keys openssl pkcs12 -export -in client_chain.crt -inkey client.key \ - -out client.p12 -name client \ - -CAfile ca.crt -caname root -password "pass:${PASSWORD}" + -out client.p12 -name client \ + -CAfile ca.crt -caname root -password "pass:${PASSWORD}" rm -f client.jks keytool -importkeystore \ - -deststorepass "${PASSWORD}" -destkeypass "${PASSWORD}" -destkeystore client.jks \ - -srckeystore client.p12 -srcstoretype PKCS12 -srcstorepass "${PASSWORD}" \ - -alias client + -deststorepass "${PASSWORD}" -destkeypass "${PASSWORD}" -destkeystore client.jks \ + -srckeystore client.p12 -srcstoretype PKCS12 -srcstorepass "${PASSWORD}" \ + -alias client # Server echo "creating server key" @@ -144,13 +143,13 @@ echo "signing server csr" openssl x509 -req -days 10 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt # Server Chain -cat server.crt ca.crt > server_chain.crt +cat server.crt ca.crt >server_chain.crt # writing java truststore rm -f server_chain.jks keytool -import -file server_chain.crt -alias server \ - -keystore server_chain.jks -storepass "${PASSWORD}" -trustcacerts -noprompt + -keystore server_chain.jks -storepass "${PASSWORD}" -trustcacerts -noprompt # reload nginx nginx -s reload echo -echo "DONE" \ No newline at end of file +echo "DONE" diff --git a/parsec-jca-java/pom.xml b/parsec-jca-java/pom.xml index daba74a..2ddbfd3 100644 --- a/parsec-jca-java/pom.xml +++ b/parsec-jca-java/pom.xml @@ -25,7 +25,7 @@ org.slf4j slf4j-api - 1.7.32 + ${slf4j.version} diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/NoneMessageDigest.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/NoneMessageDigest.java index 3132abf..87cd8b0 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/NoneMessageDigest.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/NoneMessageDigest.java @@ -6,11 +6,18 @@ public class NoneMessageDigest extends MessageDigest { public NoneMessageDigest(ParsecClientAccessor unused) { this(); } - protected NoneMessageDigest() { + + public NoneMessageDigest() { super("None"); + init(); + } + + private void init() { + this.buf = new DynamicByteBuffer(1024, 2); } - private final DynamicByteBuffer buf = new DynamicByteBuffer(1024, 2); + private DynamicByteBuffer buf; + @Override protected void engineUpdate(byte input) { buf.put(input); @@ -23,12 +30,17 @@ protected void engineUpdate(byte[] input, int offset, int len) { @Override protected byte[] engineDigest() { - byte[] copy=new byte[buf.position()]; + byte[] copy = new byte[buf.position()]; buf.rewind(); buf.get(copy); + buf.clear(); return copy; } @Override - protected void engineReset() {} + protected void engineReset() { + if (buf != null) { + buf.clear(); + } + } } diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecCipherSuites.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecCipherSuites.java index c8ef4e5..794a842 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecCipherSuites.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecCipherSuites.java @@ -1,53 +1,44 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm; import org.parallaxsecond.parsec.protobuf.psa_key_attributes.PsaKeyAttributes; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + @RequiredArgsConstructor public enum ParsecCipherSuites { - RSA_WITH_PKCS1( - PsaKeyAttributes.KeyAttributes.newBuilder() - .setKeyPolicy( - PsaKeyAttributes.KeyPolicy.newBuilder() - - .setKeyAlgorithm( - - PsaAlgorithm.Algorithm.newBuilder() + RSA_WITH_PKCS1(PsaKeyAttributes.KeyAttributes.newBuilder() + .setKeyPolicy(PsaKeyAttributes.KeyPolicy.newBuilder().setKeyAlgorithm( + PsaAlgorithm.Algorithm.newBuilder().setAsymmetricSignature( + PsaAlgorithm.Algorithm.AsymmetricSignature + .newBuilder() + .setRsaPkcs1V15Sign( + PsaAlgorithm.Algorithm.AsymmetricSignature.RsaPkcs1v15Sign + .newBuilder() + .setHashAlg(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash + .newBuilder() + .setAny(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash.Any + .newBuilder() + .build()) + .build()) + .build()) + .build()) + .build()) + .setKeyUsageFlags(PsaKeyAttributes.UsageFlags.newBuilder() + .setDecrypt(true).setEncrypt(true) + .setSignMessage(true).setVerifyMessage(true) + .setVerifyHash(true).setSignHash(true) + .build()) + .build()) + .setKeyType(PsaKeyAttributes.KeyType.newBuilder() + .setRsaKeyPair(PsaKeyAttributes.KeyType.RsaKeyPair + .newBuilder().build()) + .build()) + .setKeyBits(4096).build()); - .setAsymmetricSignature( - PsaAlgorithm.Algorithm - .AsymmetricSignature - .newBuilder() - .setRsaPkcs1V15Sign( - PsaAlgorithm.Algorithm.AsymmetricSignature.RsaPkcs1v15Sign.newBuilder() - .setHashAlg(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash.newBuilder() - .setAny(PsaAlgorithm.Algorithm.AsymmetricSignature.SignHash.Any.newBuilder().build()) - .build()) - .build() - ) - .build()) - .build()) - .setKeyUsageFlags( - PsaKeyAttributes.UsageFlags.newBuilder() - .setDecrypt(true) - .setEncrypt(true) - .setSignMessage(true) - .setVerifyMessage(true) - .setVerifyHash(true) - .setSignHash(true) - .build()) - .build()) - .setKeyType( - PsaKeyAttributes.KeyType.newBuilder() - .setRsaKeyPair( - PsaKeyAttributes.KeyType.RsaKeyPair.newBuilder() - .build()) - .build()) - .build()); - @Getter - private final PsaKeyAttributes.KeyAttributes keyAttributes; + @Getter + private final PsaKeyAttributes.KeyAttributes keyAttributes; } diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecProvider.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecProvider.java index f5081ee..80c9dc0 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecProvider.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecProvider.java @@ -1,11 +1,5 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.Builder; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.parallaxsecond.parsec.client.core.BasicClient; -import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; - import java.net.URI; import java.security.NoSuchAlgorithmException; import java.security.Provider; @@ -13,113 +7,97 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.parallaxsecond.parsec.client.core.BasicClient; +import org.parallaxsecond.parsec.client.core.ipc_handler.IpcHandler; + +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + /** Parsec JCA Security Provider */ @Slf4j public final class ParsecProvider extends Provider { - public static final String MESSAGE_DIGEST = "MessageDigest"; - public static final String SECURE_RANDOM = "SecureRandom"; - public static final String PROVIDER_NAME = "PARSEC"; - private static final double VERSION = 648000 / Math.PI; - @Getter private final ParsecClientAccessor parsecClientAccessor; - - /** - * Constructs a provider with . - * - * @param socketUri URI of the domain socket the parsec daemon listens on. - */ - @Builder - public ParsecProvider(URI socketUri, String parsecAppName) { - super( - PROVIDER_NAME, - VERSION, - String.format("%s provider, version %s.", PROVIDER_NAME, VERSION)); - // create a new client each time for now - - if (parsecAppName == null) { - parsecAppName = "parsec-jca-provider"; + public static final String MESSAGE_DIGEST = "MessageDigest"; + public static final String SECURE_RANDOM = "SecureRandom"; + public static final String PROVIDER_NAME = "PARSEC"; + private static final double VERSION = 648000 / Math.PI; + @Getter + private final ParsecClientAccessor parsecClientAccessor; + + /** + * Constructs a provider with . + * + * @param socketUri URI of the domain socket the parsec daemon listens on. + */ + @Builder + public ParsecProvider(URI socketUri, String parsecAppName) { + super(PROVIDER_NAME, VERSION, + String.format("%s provider, version %s.", PROVIDER_NAME, VERSION)); + // create a new client each time for now + + if (parsecAppName == null) { + parsecAppName = "parsec-jca-provider"; + } + final String parsecAppName_ = parsecAppName; + + this.parsecClientAccessor = () -> BasicClient.client(parsecAppName_, + IpcHandler.connectFromUrl(socketUri)); + + ps(MESSAGE_DIGEST, "None", NoneMessageDigest.class.getCanonicalName(), + NoneMessageDigest::new); + + ps(SECURE_RANDOM, "NativePRNG", SecureRandomParsec.class.getCanonicalName(), + SecureRandomParsec::new); + ps(SECURE_RANDOM, "NativePRNGBlocking", SecureRandomParsec.class.getCanonicalName(), + SecureRandomParsec::new); + ps(SECURE_RANDOM, "NativePRNGNonBlocking", + SecureRandomParsec.class.getCanonicalName(), + SecureRandomParsec::new); + + ps("KeyManagerFactory", "X509", KeyManagerFactoryImpl.class.getCanonicalName(), + KeyManagerFactoryImpl::new); + + Stream.of(ParsecRsaSignature.values()).forEach(this::signature); + + } + + private void signature(ParsecSignatureInfo parsecSignatureInfo) { + ps("Signature", parsecSignatureInfo.getAlgorithmName(), + ParsecSignature.class.getCanonicalName(), + parsecSignatureInfo::create); } - final String parsecAppName_ = parsecAppName; - - this.parsecClientAccessor = - () -> - BasicClient.client(parsecAppName_, IpcHandler.connectFromUrl(socketUri)); - - ps( - MESSAGE_DIGEST, - "None", - NoneMessageDigest.class.getCanonicalName(), - NoneMessageDigest::new); - ps( - SECURE_RANDOM, - "NativePRNG", - SecureRandomParsec.class.getCanonicalName(), - SecureRandomParsec::new); - ps( - SECURE_RANDOM, - "NativePRNGBlocking", - SecureRandomParsec.class.getCanonicalName(), - SecureRandomParsec::new); - ps( - SECURE_RANDOM, - "NativePRNGNonBlocking", - SecureRandomParsec.class.getCanonicalName(), - SecureRandomParsec::new); - - ps( - "KeyManagerFactory", - "X509", - KeyManagerFactoryImpl.class.getCanonicalName(), - KeyManagerFactoryImpl::new); - - Stream.of(ParsecRsaSignature.values()).forEach(this::signature); - } - - private void signature(ParsecSignatureInfo parsecSignatureInfo) { - ps("Signature", - parsecSignatureInfo.getAlgorithmName(), - ParsecSignature.class.getCanonicalName(), - parsecSignatureInfo::create); - } - private void ps( - String type, - String algorithm, - String className, - Function parsecClientFactory) { - putService( - new ParsecProviderService(this, type, algorithm, className, parsecClientFactory)); - } - - private static final class ParsecProviderService extends Provider.Service { - private final Function objectFactory; - - ParsecProviderService( - Provider p, - String type, - String algo, - String className, - Function objectFactory) { - super(p, type, algo, className, null, null); - this.objectFactory = objectFactory; + + private void ps(String type, String algorithm, String className, + Function parsecClientFactory) { + putService(new ParsecProviderService(this, type, algorithm, className, + parsecClientFactory)); } - @Override - public Object newInstance(Object ctrParamObj) throws NoSuchAlgorithmException { - try { - if (getProvider() instanceof ParsecProvider) { - return this.objectFactory.apply( - ((ParsecProvider) getProvider()).getParsecClientAccessor()); + private static final class ParsecProviderService extends Provider.Service { + private final Function objectFactory; + + ParsecProviderService(Provider p, String type, String algo, String className, + Function objectFactory) { + super(p, type, algo, className, null, null); + this.objectFactory = objectFactory; + } + + @Override + public Object newInstance(Object ctrParamObj) throws NoSuchAlgorithmException { + try { + if (getProvider() instanceof ParsecProvider) { + return this.objectFactory + .apply(((ParsecProvider) getProvider()) + .getParsecClientAccessor()); + } + } catch (Exception e) { + throw new ProviderException(String.format( + "Error constructing object for algorithm: %s, type: %s, provider %s", + getAlgorithm(), getType(), getProvider()), e); + } + throw new ProviderException(String.format( + "No implementation for algorithm: %s, type: %s, provider %s", + getAlgorithm(), getType(), getProvider())); } - } catch (Exception e) { - throw new ProviderException( - String.format( - "Error constructing object for algorithm: %s, type: %s, provider %s", - getAlgorithm(), getType(), getProvider()), - e); - } - throw new ProviderException( - String.format( - "No implementation for algorithm: %s, type: %s, provider %s", - getAlgorithm(), getType(), getProvider())); } - } } diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaPrivateKey.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaPrivateKey.java index 3553800..3aab7a8 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaPrivateKey.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaPrivateKey.java @@ -1,38 +1,95 @@ package org.parallaxsecond.parsec.jce.provider; +import java.math.BigInteger; +import java.security.interfaces.RSAPrivateCrtKey; + import lombok.Builder; import lombok.NonNull; import lombok.Value; -import java.math.BigInteger; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - -// FIXME verify if we can get rid off the RSAPrivateKey inheritance -@Builder +/** + * A private key implementation for RSA keys stored in a secure element through Parsec. + * + * This class implements RSAPrivateCrtKey to provide all necessary key parameters to the TLS stack, + * while ensuring private key material remains secure in the secure element. + */ @Value -public class ParsecRsaPrivateKey implements RSAPrivateKey { +public class ParsecRsaPrivateKey implements RSAPrivateCrtKey { + private static final long serialVersionUID = 1234567L; + @NonNull String parsecName; @NonNull String algorithm; @NonNull String format; - // FIXME should get rid of this - RSAPublicKey rsaPublicKey; + @NonNull + BigInteger publicExponent; + @NonNull + BigInteger modulus; + + @Builder + private ParsecRsaPrivateKey(String parsecName, String algorithm, String format, + BigInteger publicExponent, BigInteger modulus) { + this.parsecName = parsecName; + this.algorithm = algorithm; + this.format = format; + this.publicExponent = publicExponent; + this.modulus = modulus; + } @Override - public BigInteger getPrivateExponent() { - throw new IllegalStateException("cannot be called"); + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return format; } @Override public byte[] getEncoded() { - throw new IllegalStateException("cannot be called"); + return null; // Private key material cannot be exported from secure element + } + + @Override + public BigInteger getPrivateExponent() { + return null; // Private key material cannot be exported from secure element } @Override public BigInteger getModulus() { - return rsaPublicKey.getModulus(); + return modulus; + } + + @Override + public BigInteger getPublicExponent() { + return publicExponent; + } + + @Override + public BigInteger getPrimeP() { + return null; // Private key material cannot be exported from secure element + } + + @Override + public BigInteger getPrimeQ() { + return null; // Private key material cannot be exported from secure element + } + + @Override + public BigInteger getPrimeExponentP() { + return null; // Private key material cannot be exported from secure element + } + + @Override + public BigInteger getPrimeExponentQ() { + return null; // Private key material cannot be exported from secure element + } + + @Override + public BigInteger getCrtCoefficient() { + return null; // Private key material cannot be exported from secure element } } diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignature.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignature.java index d3f2cbe..dc217cf 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignature.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecRsaSignature.java @@ -1,52 +1,51 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import java.security.MessageDigest; + import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm; import org.parallaxsecond.parsec.protobuf.psa_algorithm.PsaAlgorithm.Algorithm.AsymmetricSignature; -import java.security.MessageDigest; +import lombok.Getter; +import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum ParsecRsaSignature implements ParsecSignatureInfo { // keep this in order of priority - SHA512_WITH_RSA("SHA512withRSA", - pkcs1WithHash(PsaAlgorithm.Algorithm.Hash.SHA_512), - () -> MessageDigest.getInstance("SHA-512")), + SHA512_WITH_RSA("SHA512withRSA", pkcs1WithHash(PsaAlgorithm.Algorithm.Hash.SHA_512), + () -> MessageDigest.getInstance("SHA-512")), - SHA256_WITH_RSA("SHA256withRSA", - pkcs1WithHash(PsaAlgorithm.Algorithm.Hash.SHA_256), - () -> MessageDigest.getInstance("SHA-256")), + SHA256_WITH_RSA("SHA256withRSA", pkcs1WithHash(PsaAlgorithm.Algorithm.Hash.SHA_256), + () -> MessageDigest.getInstance("SHA-256")), SHA256_PRECALCULATED_WITH_RSA("SHA256PrecalculatedWithRSA", - pkcs1WithHash(PsaAlgorithm.Algorithm.Hash.SHA_256), - () -> MessageDigest.getInstance("None")), - ; + pkcs1WithHash(PsaAlgorithm.Algorithm.Hash.SHA_256), + () -> MessageDigest.getInstance("None")),; - @Getter private final String algorithmName; - @Getter private final AsymmetricSignature parsecAlgorithm; - @Getter private final MessageDigestFactory messageDigestFactory; + @Getter + private final String algorithmName; + @Getter + private final AsymmetricSignature parsecAlgorithm; + @Getter + private final MessageDigestFactory messageDigestFactory; private static AsymmetricSignature pkcs1WithHash(PsaAlgorithm.Algorithm.Hash hash) { return AsymmetricSignature.newBuilder() - .setRsaPkcs1V15Sign(AsymmetricSignature.RsaPkcs1v15Sign.newBuilder() - .setHashAlg(AsymmetricSignature.SignHash.newBuilder() - .setSpecific(hash) - .build()) - .build()) - .build(); + .setRsaPkcs1V15Sign(AsymmetricSignature.RsaPkcs1v15Sign.newBuilder() + .setHashAlg( + AsymmetricSignature.SignHash.newBuilder().setSpecific(hash).build()) + .build()) + .build(); } private static AsymmetricSignature pkcs1() { return AsymmetricSignature.newBuilder() - .setRsaPkcs1V15Sign(AsymmetricSignature.RsaPkcs1v15Sign.newBuilder() - .build()) - .build(); + .setRsaPkcs1V15Sign(AsymmetricSignature.RsaPkcs1v15Sign.newBuilder().build()) + .build(); } @Override public ParsecSignature create(ParsecClientAccessor parsecClientAccessor) { - //AsymmetricSignature.newBuilder().setRsaPss(AsymmetricSignature.RsaPss.newBuilder() + // AsymmetricSignature.newBuilder().setRsaPss(AsymmetricSignature.RsaPss.newBuilder() return new ParsecSignature(this, parsecClientAccessor); } diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecSignature.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecSignature.java index f2398b4..6a02657 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecSignature.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/ParsecSignature.java @@ -1,14 +1,25 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Security; +import java.security.Signature; +import java.security.SignatureException; +import java.security.SignatureSpi; +import java.util.Arrays; + import org.parallaxsecond.parsec.client.exceptions.ClientException; import org.parallaxsecond.parsec.client.exceptions.ServiceException; import org.parallaxsecond.parsec.protocol.operations.NativeResult; -import org.slf4j.Logger; -import sun.security.jca.Providers; -import java.security.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + @RequiredArgsConstructor @Slf4j @@ -22,9 +33,8 @@ public final class ParsecSignature extends SignatureSpi { @Override protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { if (!(privateKey instanceof ParsecRsaPrivateKey)) { - throw new InvalidKeyException( - String.format( - "Invalid key, expected a key of type %s.", ParsecRsaPrivateKey.class.getName())); + throw new InvalidKeyException(String.format("Invalid key, expected a key of type %s.", + ParsecRsaPrivateKey.class.getName())); } this.keyName = ((ParsecRsaPrivateKey) privateKey).getParsecName(); this.messageDigest = makeMessageDigest(); @@ -35,8 +45,7 @@ private MessageDigest makeMessageDigest() throws InvalidKeyException { return this.signatureInfo.getMessageDigestFactory().create(); } catch (NoSuchAlgorithmException e) { String message = - String.format( - "Error creating associated message digest, key: %s, signatureInfo: %s", + String.format("Error creating associated message digest, key: %s, signatureInfo: %s", keyName, this.signatureInfo); log.debug(message); throw new InvalidKeyException(message, e); @@ -65,10 +74,8 @@ protected void engineUpdate(byte[] b, int off, int len) throws SignatureExceptio protected byte[] engineSign() throws SignatureException { byte[] digest = this.messageDigest.digest(); try { - NativeResult.PsaSignHashResult r = - parsecClientAccessor - .get() - .psaSignHash(keyName, digest, signatureInfo.getParsecAlgorithm()); + NativeResult.PsaSignHashResult r = parsecClientAccessor.get().psaSignHash(keyName, digest, + signatureInfo.getParsecAlgorithm()); log.info(String.format("Signed with algorithm %s", signatureInfo.getAlgorithmName())); return r.getSignature(); @@ -83,14 +90,20 @@ protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException this.keyName = ((ParsecPublicKey) publicKey).getParsecName(); this.messageDigest = makeMessageDigest(); } else { - Provider.Service service = - Providers.getFullProviderList() - .getServices("Signature", signatureInfo.getAlgorithmName()) - .stream() - .filter(s -> !ParsecProvider.PROVIDER_NAME.equals(s.getProvider().getName())) - .findFirst() - .orElseThrow( - () -> new InvalidKeyException("couldn't find a provider to delegate to")); + // TODO: this uses internal APIs and is replaced for now. + // Make sure the replacing code below is correct! + // Provider.Service service = Providers.getFullProviderList() + // .getServices("Signature", signatureInfo.getAlgorithmName()).stream() + // .filter(s -> !ParsecProvider.PROVIDER_NAME.equals(s.getProvider().getName())).findFirst() + // .orElseThrow(() -> new InvalidKeyException("couldn't find a provider to delegate to")); + + Provider[] providers = Security.getProviders(); + Provider.Service service = Arrays.stream(providers).flatMap(p -> p.getServices().stream()) + .filter(s -> "Signature".equals(s.getType()) + && signatureInfo.getAlgorithmName().equals(s.getAlgorithm()) + && !ParsecProvider.PROVIDER_NAME.equals(s.getProvider().getName())) + .findFirst() + .orElseThrow(() -> new InvalidKeyException("couldn't find a provider to delegate to")); try { this.verifyerDelegate = Signature.getInstance(service.getAlgorithm(), service.getProvider()); @@ -110,9 +123,8 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { } byte[] digest = this.messageDigest.digest(); try { - parsecClientAccessor - .get() - .psaVerifyHash(keyName, digest, signatureInfo.getParsecAlgorithm(), sigBytes); + parsecClientAccessor.get().psaVerifyHash(keyName, digest, signatureInfo.getParsecAlgorithm(), + sigBytes); return true; } catch (ServiceException | ClientException e) { throw new SignatureException("error verifying value, signatureInfo: " + signatureInfo, e); diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsec.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsec.java index 7ae2c99..3fb86a1 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsec.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/SecureRandomParsec.java @@ -1,17 +1,20 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.RequiredArgsConstructor; - import java.security.SecureRandomSpi; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + @RequiredArgsConstructor +@Slf4j public final class SecureRandomParsec extends SecureRandomSpi { private final ParsecClientAccessor parsecClientFactory; @Override protected void engineSetSeed(byte[] seed) { // TODO: verify if this is the intended behaviour - // throw new UnsupportedOperationException("Parsec does not accept seeding the random number generator."); + throw new UnsupportedOperationException( + "Parsec does not accept seeding the random number generator."); } @Override @@ -22,7 +25,8 @@ protected void engineNextBytes(byte[] bytes) { @Override protected byte[] engineGenerateSeed(int numBytes) { - // TODO: Verify that we can simply use `psaGenerateRandom` here (The use case may be to generate seeds for other RNGs) + // TODO: Verify that we can simply use `psaGenerateRandom` here (The use case may be to generate + // seeds for other RNGs) byte[] seed = new byte[numBytes]; engineNextBytes(seed); return seed; diff --git a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/X509KeyManagerImpl.java b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/X509KeyManagerImpl.java index 825d3ab..24faa39 100644 --- a/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/X509KeyManagerImpl.java +++ b/parsec-jca-java/src/main/java/org/parallaxsecond/parsec/jce/provider/X509KeyManagerImpl.java @@ -1,105 +1,99 @@ package org.parallaxsecond.parsec.jce.provider; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.parallaxsecond.parsec.jce.provider.KeyStoreExtensions.WithAlias; -import org.parallaxsecond.parsec.protobuf.psa_key_attributes.PsaKeyAttributes; - -import javax.net.ssl.X509KeyManager; -import javax.security.auth.x500.X500Principal; import java.net.Socket; import java.security.KeyStore; import java.security.Principal; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.net.ssl.X509KeyManager; +import javax.security.auth.x500.X500Principal; + +import org.parallaxsecond.parsec.jce.provider.KeyStoreExtensions.WithAlias; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + @RequiredArgsConstructor @Slf4j public final class X509KeyManagerImpl implements X509KeyManager { private final ParsecClientAccessor parsecClientFactory; private final List builders; - private Stream> certificates(String[] keyTypes, Principal[] issuers) { - return Stream.of(keyTypes) - .flatMap(keyType -> certificates(keyType, issuers)); + private Stream> certificates(String[] keyTypes, + Principal[] issuers) { + return Stream.of(keyTypes).flatMap(keyType -> certificates(keyType, issuers)); } + private X509Certificate bySubjectX500Principal(X500Principal cn) { return builders.stream() - .flatMap(ksb -> KeyStoreExtensions.allCertificates(KeyStoreExtensions.fromBuilder(ksb))) - .map(WithAlias::getObject) - .filter(c->c.getSubjectX500Principal() != null) - .filter(c->c.getSubjectX500Principal().equals(cn)) - .findFirst() - .orElse(null); + .flatMap(ksb -> KeyStoreExtensions + .allCertificates(KeyStoreExtensions.fromBuilder(ksb))) + .map(WithAlias::getObject).filter(c -> c.getSubjectX500Principal() != null) + .filter(c -> c.getSubjectX500Principal().equals(cn)).findFirst().orElse(null); } - private Stream> certificates(String keyType, Principal[] issuers) { + private Stream> certificates(String keyType, Principal[] issuers) { Set issuerSet = Arrays.stream(issuers) - .filter(i->i instanceof X500Principal) - .map(i -> (X500Principal)i ) + .filter(i -> i instanceof X500Principal).map(i -> (X500Principal) i) .collect(Collectors.toCollection(LinkedHashSet::new)); - return builders.stream() - .flatMap(ksb -> certificates(KeyStoreExtensions.fromBuilder(ksb), keyType, issuerSet)); + return builders.stream().flatMap( + ksb -> certificates(KeyStoreExtensions.fromBuilder(ksb), keyType, issuerSet)); } - private Stream certificatesForAlias(String alias){ + private Stream certificatesForAlias(String alias) { return builders.stream() - .map(ksb -> KeyStoreExtensions.getCertificate(KeyStoreExtensions.fromBuilder(ksb), alias)) - .filter(Objects::nonNull) - .map(WithAlias::getObject) - .filter(X509Certificate.class::isInstance) - .map(X509Certificate.class::cast); + .map(ksb -> KeyStoreExtensions.getCertificate(KeyStoreExtensions.fromBuilder(ksb), + alias)) + .filter(Objects::nonNull).map(WithAlias::getObject) + .filter(X509Certificate.class::isInstance).map(X509Certificate.class::cast); } - private static Stream> certificates(KeyStore keyStore, String keyType, Set issuers) { + private static Stream> certificates(KeyStore keyStore, + String keyType, Set issuers) { return KeyStoreExtensions.allCertificates(keyStore) .filter(c -> c.getObject().getPublicKey() != null) .filter(c -> c.getObject().getPublicKey().getAlgorithm() != null) .filter(c -> c.getObject().getIssuerX500Principal() != null) - .filter(c -> keyType == null || c.getObject().getPublicKey().getAlgorithm().equals(keyType)) - .filter(c -> issuers.isEmpty() || issuers.contains(c.getObject().getIssuerX500Principal())); + .filter(c -> keyType == null + || c.getObject().getPublicKey().getAlgorithm().equals(keyType)) + .filter(c -> issuers.isEmpty() + || issuers.contains(c.getObject().getIssuerX500Principal())); } @Override public String[] getClientAliases(String keyType, Principal[] issuers) { - return certificates(keyType, issuers) - .map(WithAlias::getAlias) - .toArray(String[]::new); + return certificates(keyType, issuers).map(WithAlias::getAlias).toArray(String[]::new); } @Override public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { - return certificates(keyType, issuers) - .map(WithAlias::getAlias) - .findFirst() - .orElse(null); + return certificates(keyType, issuers).map(WithAlias::getAlias).findFirst().orElse(null); } @Override public String[] getServerAliases(String keyType, Principal[] issuers) { - return certificates(keyType, issuers) - .map(WithAlias::getAlias) - .toArray(String[]::new); + return certificates(keyType, issuers).map(WithAlias::getAlias).toArray(String[]::new); } @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { - return certificates(keyType, issuers) - .map(WithAlias::getAlias) - .findFirst() - .orElse(null); + return certificates(keyType, issuers).map(WithAlias::getAlias).findFirst().orElse(null); } @Override public X509Certificate[] getCertificateChain(String alias) { - X509Certificate current = certificatesForAlias(alias) - .findFirst() - .orElse(null); + X509Certificate current = certificatesForAlias(alias).findFirst().orElse(null); if (current == null) { return null; } @@ -120,27 +114,24 @@ public PrivateKey getPrivateKey(String alias) { return parsecClientFactory.get().listKeys().getKeys().stream() .filter(k -> k.getName().equals(alias)) - .filter(k-> ParsecKeyAttributesHelper.algorithm(k.getAttributes())!= null) - .findFirst() - - .map(keyInfo -> { + .filter(k -> ParsecKeyAttributesHelper.algorithm(k.getAttributes()) != null) + .findFirst().map(keyInfo -> { String algorithm = ParsecKeyAttributesHelper.algorithm(keyInfo.getAttributes()); String format = ParsecKeyAttributesHelper.format(keyInfo.getAttributes()); switch (algorithm) { case "RSA": - if (! (certificate.getPublicKey() instanceof RSAPublicKey)) { + if (!(certificate.getPublicKey() instanceof RSAPublicKey)) { return null; } - return new ParsecRsaPrivateKey( - keyInfo.getName(), - algorithm, - format, - ((RSAPublicKey)certificate.getPublicKey())); + RSAPublicKey rsaPublicKey = (RSAPublicKey) certificate.getPublicKey(); + return ParsecRsaPrivateKey.builder().parsecName(keyInfo.getName()) + .algorithm(algorithm).format(format) + .modulus(rsaPublicKey.getModulus()) + .publicExponent(rsaPublicKey.getPublicExponent()).build(); default: - throw new IllegalStateException("unsupported key algorithm " + algorithm); + throw new IllegalStateException( + "unsupported key algorithm " + algorithm); } - - }) - .orElse(null); + }).orElse(null); } } diff --git a/parsec-protobuf-java/pom.xml b/parsec-protobuf-java/pom.xml index 6210370..6ef1ec9 100644 --- a/parsec-protobuf-java/pom.xml +++ b/parsec-protobuf-java/pom.xml @@ -1,7 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -38,7 +37,7 @@ org.codehaus.gmavenplus gmavenplus-plugin - 1.13.0 + 4.1.1 execute @@ -63,7 +62,7 @@ org.codehaus.groovy groovy - 3.0.7 + 3.0.24 runtime diff --git a/parsec-testcontainers/build.sh b/parsec-testcontainers/build.sh index 0df1084..03f11c4 100755 --- a/parsec-testcontainers/build.sh +++ b/parsec-testcontainers/build.sh @@ -2,14 +2,19 @@ docker_cache=parsec_docker_cache CACHE_CONFIG="" -if (docker buildx inspect |grep "Driver: docker-container"); then +if (docker buildx inspect | grep "Driver: docker-container"); then CACHE_CONFIG=" --set *.cache-from=type=local,src=${docker_cache} --set *.cache-to=mode=max,type=local,dest=${docker_cache}_new" fi +echo "docker buildx command:" +echo "docker buildx bake ${CACHE_CONFIG} --progress plain --load" + # shellcheck disable=SC2086 docker buildx bake ${CACHE_CONFIG} \ --progress plain \ --load rm -rf ${docker_cache} || true -mv ${docker_cache}_new ${docker_cache} || true +if [ -d "${docker_cache}_new" ]; then + mv ${docker_cache}_new ${docker_cache} || true +fi diff --git a/parsec-testcontainers/docker-bake.hcl b/parsec-testcontainers/docker-bake.hcl index 3588ee4..1bc40fe 100644 --- a/parsec-testcontainers/docker-bake.hcl +++ b/parsec-testcontainers/docker-bake.hcl @@ -1,6 +1,5 @@ group "default" { - #targets = ["parsec", "parsec_0.7.0", "parsec_0.8.1"] - targets = ["parsec_0.8.1", "nginx-test"] + targets = ["parsec", "nginx-test"] } target "generic" { context = "." @@ -18,28 +17,7 @@ target "nginx-test" { target "parsec" { inherits = ["generic"] context = "./parsec" - args = { - PARSEC_BRANCH = "main" - } tags = [ "parallaxsecond/parsec:latest" ] } -target "parsec_0.8.1" { - inherits = ["parsec"] - args = { - PARSEC_BRANCH = "0.8.1" - } - tags = [ - "parallaxsecond/parsec:0.8.1" - ] -} -target "parsec_0.7.0" { - inherits = ["parsec"] - args = { - PARSEC_BRANCH = "0.7.0" - } - tags = [ - "parallaxsecond/parsec:0.7.0" - ] -} \ No newline at end of file diff --git a/parsec-testcontainers/parsec/Dockerfile b/parsec-testcontainers/parsec/Dockerfile index 2464806..6fa131f 100644 --- a/parsec-testcontainers/parsec/Dockerfile +++ b/parsec-testcontainers/parsec/Dockerfile @@ -1,72 +1,109 @@ -ARG REGISTRY -FROM amazonlinux:2 as builder - -RUN set -e -x; \ - yum install -y git gcc shadow-utils cmake3 make gcc-c++ pkgconfig clang-devel;\ - rm -rf /root/.cache && \ - yum clean all && \ - rm -rf /var/cache/yum && \ - rpm --rebuilddb && \ - ln -s /usr/bin/cmake3 /usr/bin/cmake - -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y - -# Workaround for incorrect aarch64 entry in cargo build -RUN ln -s /usr/bin/gcc /usr/bin/aarch64-linux-gnu-gcc - -ARG PARSEC_TOOL_VERSION=0.4.0 -RUN set -e -x; \ - source $HOME/.cargo/env; \ - git clone https://github.com/parallaxsecond/parsec-tool; \ - cd parsec-tool; \ - git checkout ${PARSEC_TOOL_VERSION}; \ - cargo install --path .; \ - cd ..; \ - rm -rf parsec-tool - -ARG PARSEC_BRANCH=0.8.1 +# Copyright 2023 Contributors to the Parsec project. +# SPDX-License-Identifier: Apache-2.0 +# Liberally adapted from: +# https://github.com/parallaxsecond/parsec/blob/5b2c4904d96808a5fb98a599f5304acef8b26dda/quickstart/quickstart.Dockerfile + +# --------------------------------------------- +# Docker Stage: Base builder used for both parsec service and tools +FROM rust:latest AS base_builder + +RUN apt update -y && \ + apt install -y llvm-dev libclang-dev clang cmake jq + +## Track various build environment things we may want to use throughout +WORKDIR /build-env +RUN echo "$(uname | awk '{print tolower($0)}')" > /build-env/os +RUN echo "$(arch)" > /build-env/arch +RUN echo "$(rustc --version)" > /build-env/rustc-version +RUN echo "$(cargo --version)" > /build-env/cargo-version + +# --------------------------------------------- +# Docker Stage: Temporary stage to help dependency caching +FROM base_builder AS parsec_service_scratch + +WORKDIR / +## Checkout the latest release +RUN git clone https://github.com/parallaxsecond/parsec; + +WORKDIR /parsec +# Latest release 1.4.1 doesn't compile: https://github.com/parallaxsecond/parsec/issues/776 +# TODO: add the following line before `cargo fetch` once the fix is released +# git checkout $(git tag --sort=committerdate | grep -v rc | tail -1); \ +RUN cargo fetch + +# --------------------------------------------- +# Docker Stage: Executes the build of the Parsec Service +FROM parsec_service_scratch AS parsec_service_builder + +## Run the actual build ARG PARSEC_FEATURES="cryptoauthlib-provider,mbed-crypto-provider,unix-peer-credentials-authenticator,direct-authenticator" -RUN set -e -x; \ - source $HOME/.cargo/env; \ - git clone https://github.com/parallaxsecond/parsec; \ - cd parsec; \ - git checkout ${PARSEC_BRANCH}; \ - cargo install --features "${PARSEC_FEATURES}" --path .; \ - cd ..; \ - rm -rf parsec - -ARG PARSEC_UID=4000 -ARG PARSEC_GID=4000 -RUN set -e -x; \ - groupadd -g ${PARSEC_GID} parsec; \ - useradd -u ${PARSEC_UID} -g ${PARSEC_GID} -d /home/parsec parsec; \ - mkdir -p /var/lib/parsec /home/parsec /run/parsec /etc/parsec /usr/libexec/parsec; \ - chmod 700 /var/lib/parsec /etc/parsec /usr/libexec/parsec; \ - chmod 755 /run/parsec; - -FROM amazonlinux:2 as run -ENV RUST_LOG=info -ARG PARSEC_UID=4000 -ARG PARSEC_GID=4000 - -RUN yum install -y socat && \ - rm -rf /root/.cache && \ - yum clean all && \ - rm -rf /var/cache/yum && \ - rpm --rebuilddb - -COPY --from=builder /etc/passwd /etc/passwd -COPY --from=builder /etc/group /etc/group -COPY --from=builder /root/.cargo/bin/parsec* /usr/bin/ +RUN git describe --exact-match --tags; \ + echo 'Building Parsec with features:' && echo "${PARSEC_FEATURES}" && \ + cargo build --release --features "${PARSEC_FEATURES}"; + +# Save the current parsec version and dependencies as defined by cargo and the current git commit hash +RUN echo "$(cargo metadata --format-version=1 --no-deps --offline | jq -r '.packages[0].version')" > /build-env/parsec-version +RUN echo "$(cargo tree)" > /build-env/parsec-dependencies +RUN echo "$(git rev-parse HEAD)" > /build-env/parsec-commit + +# --------------------------------------------- +# Docker Stage: Executes the build of the Parsec Tool +FROM base_builder AS parsec_tool_builder + +RUN git clone https://github.com/parallaxsecond/parsec-tool /parsec-tool +WORKDIR /parsec-tool +RUN git checkout $(git tag --sort=committerdate | tail -1) +RUN cargo build --release + +# Save the current parsec-tool version and dependencies as defined by cargo and the current git commit hash +RUN echo "$(cargo metadata --format-version=1 --no-deps --offline | jq -r '.packages[0].version')" > /build-env/parsec-tool-version +RUN echo "$(cargo tree)" > /build-env/parsec-tool-dependencies +RUN echo "$(git rev-parse HEAD)" > /build-env/parsec-tool-commit + +# --------------------------------------------- +# Docker Stage: Extracts build results from previous stages and adds in quickstart configs +FROM base_builder AS layout + +## Add the built binaries into the image +COPY --from=parsec_service_builder /parsec/target/release/parsec /parsec/bin/parsec +COPY --from=parsec_tool_builder /parsec-tool/target/release/parsec-tool /parsec/bin/parsec-tool + +## Create and configure a starting directory for quickstart operations +WORKDIR /parsec/quickstart +COPY config.toml /parsec/quickstart/config.toml +COPY --from=parsec_tool_builder /parsec-tool/tests/parsec-cli-tests.sh /parsec/quickstart/parsec-cli-tests.sh + +## Grab all the build-env values +COPY --from=parsec_service_builder /build-env/* /build-env/ +COPY --from=parsec_tool_builder /build-env/* /build-env/ + +## Generate the build details file +COPY construct-build-details.sh /build-env/ +RUN chmod +x /build-env/construct-build-details.sh && /build-env/construct-build-details.sh > /parsec/quickstart/build.txt + +# --------------------------------------------- +# Docker Stage: Constructs a valid Docker image with Parsec Quickstart +FROM ubuntu:latest AS runnable_image + +COPY --from=layout /parsec /parsec + +ENV PATH=$PATH:/parsec/bin +ENV PARSEC_SERVICE_ENDPOINT=unix:/run/parsec/parsec.sock + +RUN apt update && apt install -y openssl socat + + +RUN useradd -ms /bin/bash qs +RUN chown -R qs:qs /parsec/quickstart +RUN mkdir -p /run/parsec +RUN chown -R qs:qs /run/parsec +RUN chmod -R 777 /run/parsec +USER qs + +WORKDIR /parsec/quickstart + +RUN chmod a+rwx /parsec/quickstart/ + COPY config.toml /etc/parsec/ -COPY --from=builder --chown=${PARSEC_UID}:${PARSEC_GID} /usr/libexec/parsec /usr/libexec/parsec -COPY --from=builder --chown=${PARSEC_UID}:${PARSEC_GID} /var/lib/parsec /var/lib/parsec -COPY --from=builder --chown=${PARSEC_UID}:${PARSEC_GID} /home/parsec /home/parsec -COPY --from=builder --chown=${PARSEC_UID}:${PARSEC_GID} /run/parsec /run/parsec -COPY --from=builder --chown=${PARSEC_UID}:${PARSEC_GID} /etc/parsec /etc/parsec - -# FIXME volume mount problem -# Volumes are mounted as root first -#USER parsec:parsec -WORKDIR "/home/parsec" -CMD ["parsec", "-c", "/etc/parsec/config.toml"] \ No newline at end of file + +CMD ["parsec", "-c", "/etc/parsec/config.toml"] diff --git a/parsec-testcontainers/parsec/config.toml b/parsec-testcontainers/parsec/config.toml index b790810..dfc39d3 100644 --- a/parsec-testcontainers/parsec/config.toml +++ b/parsec-testcontainers/parsec/config.toml @@ -1,6 +1,7 @@ [core_settings] log_timestamp = false log_error_details = true +log_level = "debug" # Possible values: "debug", "info", "warn", "error", "trace" allow_root = true [listener] @@ -9,12 +10,13 @@ timeout = 200 # in milliseconds socket_path = "/run/parsec/parsec.sock" [authenticator] -auth_type = "UnixPeerCredentials" +# auth_type = "UnixPeerCredentials" +auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" -store_path = "/var/lib/parsec/mappings" +store_path = "/parsec/quickstart/mappings" [[provider]] provider_type = "MbedCrypto" diff --git a/parsec-testcontainers/parsec/construct-build-details.sh b/parsec-testcontainers/parsec/construct-build-details.sh new file mode 100644 index 0000000..e802c5a --- /dev/null +++ b/parsec-testcontainers/parsec/construct-build-details.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Copyright 2023 Contributors to the Parsec project. +# SPDX-License-Identifier: Apache-2.0 + +cat < + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.parallaxsecond @@ -11,10 +10,20 @@ parsec-testcontainers + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + org.testcontainers junit-jupiter - 1.16.2 + 1.20.5 diff --git a/parsec-testcontainers/src/main/java/org/parallaxsecond/testcontainers/ParsecContainer.java b/parsec-testcontainers/src/main/java/org/parallaxsecond/testcontainers/ParsecContainer.java index c30a952..c3c65f3 100644 --- a/parsec-testcontainers/src/main/java/org/parallaxsecond/testcontainers/ParsecContainer.java +++ b/parsec-testcontainers/src/main/java/org/parallaxsecond/testcontainers/ParsecContainer.java @@ -1,21 +1,25 @@ package org.parallaxsecond.testcontainers; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Assertions; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; import java.net.ServerSocket; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermissions; -import java.util.Locale; +import java.time.Duration; +import static java.util.Collections.singletonList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import static java.util.Collections.singletonList; +import org.junit.jupiter.api.Assertions; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.ShellStrategy; +import org.testcontainers.shaded.org.awaitility.Awaitility; +import org.testcontainers.utility.DockerImageName; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; @Slf4j public class ParsecContainer extends GenericContainer { @@ -30,20 +34,43 @@ public class ParsecContainer extends GenericContainer { private Process cmd; private ExecutorService executor; + public Path runDir; + @SneakyThrows public ParsecContainer(final DockerImageName dockerImageName) { super(dockerImageName); - Path runDir = Files.createTempDirectory("ps"); + this.setWaitStrategy(new ShellStrategy().withCommand("parsec-tool ping") + .withStartupTimeout(Duration.ofSeconds(10))); + runDir = Files.createTempDirectory("parsec_"); Files.setPosixFilePermissions(runDir, PosixFilePermissions.fromString("rwxrwxrwx")); this.parsecSock = runDir.resolve(PARSEC_SOCKET_FILE); this.parsecSockSocat = runDir.resolve("ps_socat.sock"); - this.withFileSystemBind( - runDir.toFile().getAbsoluteFile().getAbsolutePath(), PARSEC_RUN_DIR, BindMode.READ_WRITE); + this.setPortBindings(singletonList(localPort + ":" + PARSEC_TCP_PORT)); + this.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(ParsecContainer.class))); + } + + public boolean isRunning() { + if (getContainerId() == null) { + return false; + } + + try { + Boolean running = getCurrentContainerInfo().getState().getRunning(); + return Boolean.TRUE.equals(running); + } catch (Throwable e) { + return false; + } } + @SneakyThrows public static ParsecContainer withVersion(String version) { - return new ParsecContainer(DockerImageName.parse(IMAGE_NAME + ":" + version)); + ParsecContainer parsecContainer = + new ParsecContainer(DockerImageName.parse(IMAGE_NAME + ":" + version)); + parsecContainer.start(); + // Wait for container to be running + Awaitility.await().until(parsecContainer::isRunning); + return parsecContainer; } @SneakyThrows @@ -63,20 +90,12 @@ public URI getSocketUri() { @Override public void start() { + useSocat(); super.start(); - if (isOsx()) { - useSocat(); - } - } - private static boolean isOsx() { - String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); - return ((os.contains("mac")) || (os.contains("darwin"))); } - @Override public void close() { - super.close(); if (this.cmd != null) { this.cmd.destroyForcibly(); this.cmd = null; @@ -90,29 +109,20 @@ public void close() { @SneakyThrows public void useSocat() { executor = Executors.newSingleThreadExecutor(); - executor.submit( - () -> { - log.info("starting socat in docker container"); - ParsecContainer.this.execInContainer( - "socat", - "TCP4-LISTEN:" + PARSEC_TCP_PORT + ",reuseaddr,fork", - "UNIX-CONNECT:" + PARSEC_SOCKET); - log.info("started socat in docker container"); - return null; - }); - - log.info("starting socat on local machine"); - cmd = - Runtime.getRuntime() - .exec( - new String[] { - "socat", - "UNIX-LISTEN:" - + this.parsecSockSocat.toFile().getAbsolutePath() - + ",fork,reuseaddr", - "TCP4:localhost:" + this.localPort - }); - log.info("started socat on local machine"); + executor.submit(() -> { + ParsecContainer.this.execInContainer("socat", "-d", "-d", // Add debug output + "TCP4-LISTEN:" + PARSEC_TCP_PORT + ",reuseaddr,fork", "UNIX-CONNECT:" + PARSEC_SOCKET); + return null; + }); + + cmd = Runtime.getRuntime().exec(new String[] {"socat", "-d", "-d", // Add debug output + "UNIX-LISTEN:" + this.parsecSockSocat.toFile().getAbsolutePath() + ",fork,reuseaddr", + "TCP4:localhost:" + this.localPort}); + + // wait until this.parsecSockSocat is created + Awaitility.await().until(() -> { + return Files.exists(this.parsecSockSocat); + }); } @SneakyThrows @@ -121,6 +131,7 @@ public void parsecTool(String... args) { System.arraycopy(args, 0, args_, 1, args.length); args_[0] = "parsec-tool"; ExecResult r = execInContainer(args_); - Assertions.assertEquals(0, r.getExitCode(), getLogs() + "\n" + r.getStdout() + "\n" + r.getStderr()); + Assertions.assertEquals(0, r.getExitCode(), + getLogs() + "\n" + r.getStdout() + "\n" + r.getStderr()); } } diff --git a/pom.xml b/pom.xml index 0f1e669..1f5af09 100644 --- a/pom.xml +++ b/pom.xml @@ -1,8 +1,7 @@ -4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 pom org.parallaxsecond parsec-java @@ -11,9 +10,21 @@ UTF-8 1.8 1.8 - 1.18.22 + 1.3.15 + 1.18.36 + 5.12.1 + 2.0.16 + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.projectlombok @@ -25,13 +36,13 @@ org.junit.jupiter junit-jupiter - 5.8.1 + ${junit.jupiter.version} test org.mockito mockito-junit-jupiter - 4.0.0 + 5.15.2 test @@ -77,7 +88,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.2