From 36fdc6b56868d96d39e647fecfd236a3a4175a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miranda?= Date: Thu, 11 Jul 2024 09:38:37 +0100 Subject: [PATCH 01/44] Initial commit --- .gitignore | 24 +++++++ LICENSE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 227 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..524f096 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..757a7ee --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# eudi-srv-web-walletdriven-signer-external-sca-java +rQES R3 external SCA From 5844e5840de23ab694f69e0e6444a37048eaa427 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Fri, 12 Jul 2024 09:53:59 +0100 Subject: [PATCH 02/44] Initial Commit --- .gitignore | 34 ++ .mvn/wrapper/maven-wrapper.properties | 18 ++ mvnw | 250 +++++++++++++++ mvnw.cmd | 146 +++++++++ pom.xml | 98 ++++++ .../r3/sca/Controllers/OAuth2Controller.java | 5 + .../sca/Controllers/SignaturesController.java | 240 ++++++++++++++ .../ec/eudi/signer/r3/sca/DSS_Service.java | 303 ++++++++++++++++++ .../AttributeSignDocRequest.java | 25 ++ .../DocumentDigestsSignDocRequest.java | 80 +++++ .../DocumentsSignDocRequest.java | 76 +++++ .../SignaturesSignDocRequest.java | 156 +++++++++ .../r3/sca/DTO/SignaturesSignDocResponse.java | 70 ++++ .../r3/sca/DTO/SignaturesSignHashRequest.java | 132 ++++++++ .../sca/DTO/SignaturesSignHashResponse.java | 24 ++ .../DTO/ValidationInfoSignDocResponse.java | 51 +++ .../ec/eudi/signer/r3/sca/QtspClient.java | 97 ++++++ .../ec/eudi/signer/r3/sca/ScaApplication.java | 13 + .../DocumentsSignDocConstraintAnnotation.java | 22 ++ .../DocumentsSignDocRequestValidator.java | 39 +++ .../SignDocRequestConstraintAnnotation.java | 20 ++ .../SignaturesSignDocRequestValidator.java | 27 ++ src/main/resources/application.properties | 2 + .../signer/r3/sca/ScaApplicationTests.java | 13 + tests/exampleSigned.pdf | Bin 0 -> 30533 bytes 25 files changed, 1941 insertions(+) create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/AttributeSignDocRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignDocResponse.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashResponse.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/ValidationInfoSignDocResponse.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocConstraintAnnotation.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignDocRequestConstraintAnnotation.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java create mode 100644 tests/exampleSigned.pdf diff --git a/.gitignore b/.gitignore index 524f096..999e5f4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,37 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + + +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..aeccdfd --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.1 +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..ba9212a --- /dev/null +++ b/mvnw @@ -0,0 +1,250 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.1 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl="${value-}" ;; + distributionSha256Sum) distributionSha256Sum="${value-}" ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..406932d --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,146 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.1 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d5ce525 --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.0 + + + eu.europa.ec.eudi.signer.r3 + sca + 0.0.1-SNAPSHOT + sca + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.json + json + 20231013 + + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + + org.hibernate.validator + hibernate-validator + 6.2.0.Final + + + + + eu.europa.ec.joinup.sd-dss + dss-pades + 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-pades-pdfbox + 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-utils-apache-commons + 6.0 + + + eu.europa.ec.joinup.sd-dss + validation-policy + 6.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M2 + + true + + + + + + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java new file mode 100644 index 0000000..a026365 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java @@ -0,0 +1,5 @@ +package eu.europa.ec.eudi.signer.r3.sca.Controllers; + +public class OAuth2Controller { + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java new file mode 100644 index 0000000..ea1d598 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -0,0 +1,240 @@ +package eu.europa.ec.eudi.signer.r3.sca.Controllers; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import eu.europa.ec.eudi.signer.r3.sca.DSS_Service; +import eu.europa.ec.eudi.signer.r3.sca.QtspClient; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; +import eu.europa.ec.eudi.signer.r3.sca.DTO.ValidationInfoSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.SignaturesSignDocRequest; +import eu.europa.esig.dss.enumerations.MimeType; +import eu.europa.esig.dss.model.DSSDocument; +import jakarta.validation.Valid; + +@RestController +@RequestMapping(value = "/signatures") +public class SignaturesController { + + @Autowired + private QtspClient qtspClient; + + @Autowired + private DSS_Service dssClient; + + private X509Certificate signingCertificate; + + public SignaturesController() throws Exception { + byte[] cert_bytes = Base64.getDecoder().decode( + "MIIBuzCCASSgAwIBAgIGAY/zF0AhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2lzc3Vlcl90ZXN0MB4XDTI0MDYwNzE0MjUzOFoXDTI1MDYwNzE0MjUzOFowFzEVMBMGA1UEAwwMc3ViamVjdF90ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCseUUmD8+Okuh5OrLT2LyO6QCNOIidohV7HAjIbgdpSU1C27z+JDWT3cfVbojQ5EzvZM9CDPayHrlnNK8NFD9ggE3rbOn6ATT9iC4qTQvPN3Sdel5OTaVabMuMT2satwbtl8wB98583i4bhJUyHRy7PJnXrOCscyK14GjGnuVwjQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACWKec1JiRggmTRm0aQin3SJnsvuF8JS5GlOpea45IGV2gOHws/iFPg8BAaGzQ1d+sG+RHH07xKCll8Xw1QaqLhc+96vNOCvl2cjl7BdLH/fiYurP8Vf0W3lkp5VbRFV2nWwHcOIPBUa8lNK+uV6Z5nPG5Ads12BJD5K8jAHXo2E"); + + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + InputStream in = new ByteArrayInputStream(cert_bytes); + this.signingCertificate = (X509Certificate) certFactory.generateCertificate(in); + System.out.println(this.signingCertificate.toString()); + + } + + @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") + public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest) { + + System.out.println(signDocRequest); + String url = signDocRequest.getRequest_uri(); + if (signDocRequest.getCredentialID() == null) { + System.out.println("To be defined: CredentialID needs to be defined in this implementation."); + return new SignaturesSignDocResponse(); + } + + if (signDocRequest.getSAD() == null) { + System.out.println( + "To be defined: the current solution expects the credential token to be sent in the SAD."); + return new SignaturesSignDocResponse(); + } + + if (signDocRequest.getOperationMode().equals("A")) { + System.out.println("To be defined: the current solution doesn't support assynchronious responses."); + return new SignaturesSignDocResponse(); + } + + if (signDocRequest.getDocuments() != null) { + try { + return handleDocumentsSignDocRequest(signDocRequest, url); + } catch (Exception e) { + + } + } + + if (signDocRequest.getDocumentDigests() != null) { + try { + return handleDocumentDigestsSignDocRequest(signDocRequest, url); + } catch (Exception e) { + } + } + + return new SignaturesSignDocResponse(); + } + + // i need the signing certificate before hand + public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String url) + throws Exception { + + // if signature_format == C => signed_envelope_property = Attached + // if signature_format == P => signed_envelope_property = Certification + // if signature_format == X => signed_envelope_property = Enveloped + // if signature_format == J => signed_envelope_property = Attached + + List allResponses = new ArrayList<>(); + for (DocumentsSignDocRequest document : signDocRequest.getDocuments()) { + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + byte[] dataToBeSigned = null; + if (document.getSignature_format().equals("C")) { + System.out.println("Not Supported by current version"); + } else if (document.getSignature_format().equals("P")) { + System.out.print("PAdES\n"); + dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, + document.getConformance_level(), document.getSigned_envelope_property(), + this.signingCertificate, new ArrayList<>()); + System.out.println("Data To Be Signed Created"); + } else if (document.getSignature_format().equals("X")) { + System.out.print("XAdES: to be implemented"); + return new SignaturesSignDocResponse(); + // dssClient.xadesToBeSignedData(dssDocument, document.getConformance_level(), + // document.getSigned_envelope_property()); + } else if (document.getSignature_format().equals("J")) { + System.out.print("JAdES: to be implemented"); + return new SignaturesSignDocResponse(); + // dssClient.jadesToBeSignedData(dssDocument, document.getConformance_level(), + // document.getSigned_envelope_property()); + } + + if (dataToBeSigned == null) { + return new SignaturesSignDocResponse(); + } + + String dtbs = Base64.getEncoder().encodeToString(dataToBeSigned); + List doc = new ArrayList<>(); + doc.add(dtbs); + + System.out.println(signDocRequest.toString()); + + // As the current operation mode only supported is "S", the validity_period and + // response_uri do not need to be defined + SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest( + signDocRequest.getCredentialID(), + signDocRequest.getSAD(), + doc, + null, + document.getSignAlgo(), + null, + signDocRequest.getOperationMode(), + -1, + null, + signDocRequest.getClientData()); + + try { + System.out.println("HTTP Request to QTSP."); + SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest); + System.out.println("HTTP Response received."); + allResponses.add(signHashResponse); + System.out.println(signHashResponse.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + List allSignaturesObjects = new ArrayList<>(); + for (SignaturesSignHashResponse response : allResponses) { + + DocumentsSignDocRequest document = signDocRequest.getDocuments().get(0); + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + + if (response.getSignatures() != null) { + byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); + DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, + new ArrayList<>()); + + try { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); + docSigned.save("tests/exampleSigned.pdf"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + allSignaturesObjects.addAll(response.getSignatures()); + } + + ValidationInfoSignDocResponse validationInfo = null; + if (signDocRequest.getReturnValidationInfo()) { + // TODO: obtain the validation info.... + validationInfo = new ValidationInfoSignDocResponse(); + } + + SignaturesSignDocResponse signDocResponse = new SignaturesSignDocResponse( + null, + allSignaturesObjects, + null, + validationInfo); + + return signDocResponse; + + } + + public SignaturesSignDocResponse handleDocumentDigestsSignDocRequest(SignaturesSignDocRequest signDocRequest, + String url) + throws Exception { + + // for each document digests.... + List allResponses = new ArrayList<>(); + for (int i = 0; i < signDocRequest.getDocumentDigests().size(); i++) { + SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest( + signDocRequest.getCredentialID(), + signDocRequest.getSAD(), + signDocRequest.getDocumentDigests().get(i).getHashes(), + signDocRequest.getDocumentDigests().get(i).getHashAlgorithmOID(), + signDocRequest.getDocumentDigests().get(i).getSignAlgo(), + signDocRequest.getDocumentDigests().get(i).getSignAlgoParams(), + signDocRequest.getOperationMode(), + signDocRequest.getValidity_period(), + signDocRequest.getResponse_uri(), + signDocRequest.getClientData()); + + SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest); + allResponses.add(signHashResponse); + } + + List allSignaturesObjects = new ArrayList<>(); + for (int i = 0; i < allResponses.size(); i++) { + allSignaturesObjects.addAll(allResponses.get(i).getSignatures()); + } + + ValidationInfoSignDocResponse validationInfo = null; + if (signDocRequest.getReturnValidationInfo()) { + // TODO: obtain the validation info.... + validationInfo = new ValidationInfoSignDocResponse(); + } + + SignaturesSignDocResponse signDocResponse = new SignaturesSignDocResponse( + null, + allSignaturesObjects, + null, + validationInfo); + + return signDocResponse; + + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java new file mode 100644 index 0000000..a5b1453 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -0,0 +1,303 @@ +package eu.europa.ec.eudi.signer.r3.sca; + +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Date; +import java.util.List; + +import org.springframework.stereotype.Service; + +import eu.europa.esig.dss.cades.signature.CMSSignedDocument; +import eu.europa.esig.dss.enumerations.SignatureAlgorithm; +import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.DSSMessageDigest; +import eu.europa.esig.dss.model.InMemoryDocument; +import eu.europa.esig.dss.model.SignatureValue; +import eu.europa.esig.dss.model.ToBeSigned; +import eu.europa.esig.dss.model.x509.CertificateToken; +import eu.europa.esig.dss.pades.PAdESSignatureParameters; +import eu.europa.esig.dss.pades.signature.ExternalCMSService; +import eu.europa.esig.dss.pades.signature.PAdESService; +import eu.europa.esig.dss.validation.CertificateVerifier; +import eu.europa.esig.dss.validation.CommonCertificateVerifier; + +@Service +public class DSS_Service { + + public DSSDocument loadDssDocument(String document) { + byte[] dataDocument = Base64.getDecoder().decode(document); + return new InMemoryDocument(dataDocument); + } + + public void test() { + System.out.println("This is a test."); + } + + // importante parameters: conformance_level, signed_envelope_property + + public void xadesToBeSignedData(DSSDocument document, String conformance_level, String signed_envelope_property) { + + } + + public void jadesToBeSignedData(DSSDocument document, String conformance_level, String signed_envelope_property) { + + } + + // -------------------- + + public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + ExternalCMSPAdESService service = new ExternalCMSPAdESService(cv); + + PAdESSignatureParameters parameters = new PAdESSignatureParameters(); + parameters.bLevel().setSigningDate(new Date()); + parameters.setGenerateTBSWithoutCertificate(true); + parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + parameters.setReason("DSS testing"); + + DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); + + PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + signatureParameters.setReason("DSS testing"); + + cv = new CommonCertificateVerifier(); + ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); + ToBeSigned dataToSign = cmsForPAdESGenerationService.getDataToSign(messageDigest, signatureParameters); + return dataToSign.getBytes(); + } + + public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, + X509Certificate signingCertificate, + List certificateChain) { + + SignatureValue signatureValue = new SignatureValue(); + signatureValue.setAlgorithm(SignatureAlgorithm.RSA_SHA256); + signatureValue.setValue(signature); + + CertificateVerifier cv = new CommonCertificateVerifier(); + ExternalCMSPAdESService service = new ExternalCMSPAdESService(cv); + PAdESSignatureParameters parameters = new PAdESSignatureParameters(); + parameters.bLevel().setSigningDate(new Date()); + parameters.setGenerateTBSWithoutCertificate(true); + parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + parameters.setReason("DSS testing"); + DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); + + PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + signatureParameters.setReason("DSS testing"); + + // stateless + cv = new CommonCertificateVerifier(); + ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); + CMSSignedDocument cmsSignedDocument = cmsForPAdESGenerationService.signMessageDigest(messageDigest, + signatureParameters, signatureValue); + byte[] cmsSignedData = cmsSignedDocument.getBytes(); + + // Stateless + service = new ExternalCMSPAdESService(cv); + service.setCmsSignedData(cmsSignedData); + return service.signDocument(documentToSign, signatureParameters, null); + } + + private static class ExternalCMSPAdESService extends PAdESService { + private static final long serialVersionUID = -2003453716888412577L; + private byte[] cmsSignedData; + + public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) { + super(certificateVerifier); + } + + public DSSMessageDigest getMessageDigest(DSSDocument documentToSign, PAdESSignatureParameters parameters) { + return super.computeDocumentDigest(documentToSign, parameters); + } + + @Override + protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, + final PAdESSignatureParameters parameters, + final SignatureValue signatureValue) { + if (this.cmsSignedData == null) { + throw new NullPointerException("A CMS signed data must be provided"); + } + return this.cmsSignedData; + } + + public void setCmsSignedData(final byte[] cmsSignedData) { + this.cmsSignedData = cmsSignedData; + } + + } + + /* + * public byte[] padesToBeSignedData2(DSSDocument document, String + * conformance_level, String signed_envelope_property, + * X509Certificate signingCertificate, List certificateChain) { + * + * CertificateVerifier cv = new CommonCertificateVerifier(); + * ExternalCMSService padesCMSGeneratorService = new ExternalCMSService(cv); + * + * PAdESSignatureParameters parameters = new PAdESSignatureParameters(); + * parameters.bLevel().setSigningDate(new Date()); + * parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + * parameters.setReason("DSS testing"); + * // parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + * + * PAdESSignatureParameters signatureParameters = new + * PAdESSignatureParameters(); + * signatureParameters.setSigningCertificate(new + * CertificateToken(signingCertificate)); + * List certChainToken = new ArrayList<>(); + * for (X509Certificate cert : certificateChain) { + * certChainToken.add(new CertificateToken(cert)); + * } + * signatureParameters.setCertificateChain(certChainToken); + * signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + * signatureParameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + * + * ToBeSigned dataToSign = padesCMSGeneratorService.getDataToSign(messageDigest, + * signatureParameters); + * return dataToSign.getBytes(); + * } + * + * // update to return doc sign and not only the CMSSignedData + * public byte[] createDocumentWithSignature(DSSDocument document, byte[] + * signature, + * X509Certificate signingCertificate, + * List certificateChain) { + * + * CertificateVerifier cv = new CommonCertificateVerifier(); + * ExternalCMSService padesCMSGeneratorService = new ExternalCMSService(cv); + * + * SignatureValue signatureValue = new SignatureValue(); + * signatureValue.setAlgorithm(SignatureAlgorithm.RSA_SHA256); + * signatureValue.setValue(signature); + * + * PAdESSignatureParameters signatureParameters = new + * PAdESSignatureParameters(); + * signatureParameters.setSigningCertificate(new + * CertificateToken(signingCertificate)); + * List certChainToken = new ArrayList<>(); + * for (X509Certificate cert : certificateChain) { + * certChainToken.add(new CertificateToken(cert)); + * } + * signatureParameters.setCertificateChain(certChainToken); + * signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + * signatureParameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + * + * // Create a CMS signature using the provided message-digest, signature + * // parameters and the signature value + * CMSSignedDocument cmsSignature = + * padesCMSGeneratorService.signMessageDigest(messageDigest, + * signatureParameters, + * signatureValue); + * CMSSignedData signedData = cmsSignature.getCMSSignedData(); + * signedData.getSignerInfos().getSigners() + * .forEach(x -> System.out.println(x.getEncryptionAlgOID() + " & " + + * x.getDigestAlgOID())); // sha256WithRSAEncryption + * // & + * System.out.println(signedData.getSignedContentTypeOID()); // data + * for (AlgorithmIdentifier alg : signedData.getDigestAlgorithmIDs()) { // + * // sha-256 + * System.out.println(alg.toASN1Primitive().toString()); + * } + * return cmsSignature.getCMSSignedData().getEncoded(); + * } + */ + + // ------------------ + + /* + * public DSSDocument example1(DSSDocument documentToSign, String + * conformance_level, String signed_envelope_property, + * X509Certificate signingCertificate, List certificateChain) { + * PAdESWithExternalCMSService service = new PAdESWithExternalCMSService(); + * + * PAdESSignatureParameters parameters = new PAdESSignatureParameters(); + * parameters.bLevel().setSigningDate(new Date()); + * parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + * parameters.setReason("DSS testing"); + * + * DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, + * parameters); + * + * // -------------------------- + * + * PAdESSignatureParameters signatureParameters = new + * PAdESSignatureParameters(); + * signatureParameters.bLevel().setSigningDate(new Date()); + * signatureParameters.setSigningCertificate(getSigningCert()); + * signatureParameters.setCertificateChain(getCertificateChain()); + * signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + * signatureParameters.setReason("DSS testing"); + * + * PAdESSignerInfoGeneratorBuilder padesCMSSignedDataBuilder = new + * PAdESSignerInfoGeneratorBuilder(messageDigest); + * SignatureAlgorithm signatureAlgorithm = + * signatureParameters.getSignatureAlgorithm(); + * + * CustomContentSigner customContentSigner = new + * CustomContentSigner(signatureAlgorithm.getJCEId()); + * SignerInfoGenerator signerInfoGenerator = + * padesCMSSignedDataBuilder.build(signatureParameters, + * customContentSigner); + * + * CMSSignedDataBuilder cmsSignedDataBuilder = new CMSSignedDataBuilder() + * .setSigningCertificate(signatureParameters.getSigningCertificate()) + * .setCertificateChain(signatureParameters.getCertificateChain()) + * .setGenerateWithoutCertificates(signatureParameters. + * isGenerateTBSWithoutCertificate()) + * .setEncapsulate(false); + * cmsSignedDataBuilder.createCMSSignedData(signerInfoGenerator, new + * InMemoryDocument(messageDigest.getValue())); + * + * SignatureValue signatureValue = getToken().sign( + * new ToBeSigned(customContentSigner.getOutputStream().toByteArray()), + * signatureParameters.getDigestAlgorithm(), getPrivateKeyEntry()); + * + * // ---------------------------- + * } + * + * public DSSDocument createDocumentExample1(DSSDocument documentToSign) { + * + * CustomContentSigner customContentSigner = new + * CustomContentSigner(signatureAlgorithm.getJCEId(), + * signatureValue.getValue()); + * signerInfoGenerator = padesCMSSignedDataBuilder.build(signatureParameters, + * customContentSigner); + * + * CMSSignedData cmsSignedData = + * cmsSignedDataBuilder.createCMSSignedData(signerInfoGenerator, + * new InMemoryDocument(messageDigest.getValue())); + * byte[] encoded = DSSASN1Utils.getDEREncoded(cmsSignedData); + * + * CMSSignedData cmsSignedData = DSSUtils.toCMSSignedData(encoded); + * CMSSignedDocument cmsSignedDocument = new CMSSignedDocument(cmsSignedData); + * + * // Stateless + * PAdESWithExternalCMSService service = new PAdESWithExternalCMSService(); + * return service.signDocument(documentToSign, signatureParameters, + * cmsSignedDocument); + * + * } + */ +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/AttributeSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/AttributeSignDocRequest.java new file mode 100644 index 0000000..4431819 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/AttributeSignDocRequest.java @@ -0,0 +1,25 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest; + +import jakarta.validation.constraints.NotBlank; + +public class AttributeSignDocRequest { + @NotBlank + private String attribute_name; + private String attribute_value; + + public String getAttribute_name() { + return attribute_name; + } + + public void setAttribute_name(String attribute_name) { + this.attribute_name = attribute_name; + } + + public String getAttribute_value() { + return attribute_value; + } + + public void setAttribute_value(String attribute_value) { + this.attribute_value = attribute_value; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java new file mode 100644 index 0000000..43d7038 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java @@ -0,0 +1,80 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest; + +import java.util.List; +import jakarta.validation.constraints.NotBlank; + +public class DocumentDigestsSignDocRequest { + private List hashes; + private String hashAlgorithmOID; + private String signature_format; + private String conformance_level; + @NotBlank + private String signAlgo; + private String signAlgoParams; + private List signed_props; + private String signed_envelop_property; + + public List getHashes() { + return hashes; + } + + public void setHashes(List hashes) { + this.hashes = hashes; + } + + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } + + public String getSignature_format() { + return signature_format; + } + + public void setSignature_format(String signature_format) { + this.signature_format = signature_format; + } + + public String getConformance_level() { + return conformance_level; + } + + public void setConformance_level(String conformance_level) { + this.conformance_level = conformance_level; + } + + public String getSignAlgo() { + return signAlgo; + } + + public void setSignAlgo(String signAlgo) { + this.signAlgo = signAlgo; + } + + public String getSignAlgoParams() { + return signAlgoParams; + } + + public void setSignAlgoParams(String signAlgoParams) { + this.signAlgoParams = signAlgoParams; + } + + public List getSigned_props() { + return signed_props; + } + + public void setSigned_props(List signed_props) { + this.signed_props = signed_props; + } + + public String getSigned_envelop_property() { + return signed_envelop_property; + } + + public void setSigned_envelop_property(String signed_envelop_property) { + this.signed_envelop_property = signed_envelop_property; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java new file mode 100644 index 0000000..805f042 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java @@ -0,0 +1,76 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest; + +import java.util.List; + +import eu.europa.ec.eudi.signer.r3.sca.Validators.DocumentsSignDocConstraintAnnotation; +import jakarta.validation.constraints.NotBlank; + +@DocumentsSignDocConstraintAnnotation +public class DocumentsSignDocRequest { + @NotBlank + private String document; + @NotBlank + private String signature_format = null; + private String conformance_level = "AdES-B-B"; + @NotBlank + private String signAlgo; + private String signAlgoParams; + private List signed_props; + private String signed_envelope_property; + + public String getDocument() { + return document; + } + + public void setDocument(String document) { + this.document = document; + } + + public String getSignature_format() { + return signature_format; + } + + public void setSignature_format(String signature_format) { + this.signature_format = signature_format; + } + + public String getConformance_level() { + return conformance_level; + } + + public void setConformance_level(String conformance_level) { + this.conformance_level = conformance_level; + } + + public String getSignAlgo() { + return signAlgo; + } + + public void setSignAlgo(String signAlgo) { + this.signAlgo = signAlgo; + } + + public String getSignAlgoParams() { + return signAlgoParams; + } + + public void setSignAlgoParams(String signAlgoParams) { + this.signAlgoParams = signAlgoParams; + } + + public List getSigned_props() { + return signed_props; + } + + public void setSigned_props(List signed_props) { + this.signed_props = signed_props; + } + + public String getSigned_envelope_property() { + return signed_envelope_property; + } + + public void setSigned_envelope_property(String signed_envelope_property) { + this.signed_envelope_property = signed_envelope_property; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java new file mode 100644 index 0000000..7dffc67 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java @@ -0,0 +1,156 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest; + +import eu.europa.ec.eudi.signer.r3.sca.Validators.SignDocRequestConstraintAnnotation; + +import java.util.List; +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; + +@SignDocRequestConstraintAnnotation +public class SignaturesSignDocRequest { + private String credentialID; + private String signatureQualifier; + private String SAD; + private List documentDigests; + @Valid + private List documents; + private String operationMode = "S"; + private int validity_period = -1; + private String response_uri; + private String clientData; + private Boolean returnValidationInfo = false; + @NotBlank + private String request_uri; + + public SignaturesSignDocRequest() { + } + + @JsonProperty + public String getCredentialID() { + return credentialID; + } + + @JsonProperty + public void setCredentialID(String credentialID) { + this.credentialID = credentialID; + } + + @JsonProperty + public String getSignatureQualifier() { + return signatureQualifier; + } + + @JsonProperty + public void setSignatureQualifier(String signatureQualifier) { + this.signatureQualifier = signatureQualifier; + } + + @JsonProperty + public String getSAD() { + return SAD; + } + + @JsonProperty + public void setSAD(String SAD) { + this.SAD = SAD; + } + + @JsonProperty + public List getDocumentDigests() { + return documentDigests; + } + + @JsonProperty + public void setDocumentDigests(List documentDigests) { + this.documentDigests = documentDigests; + } + + @JsonProperty + public List getDocuments() { + return documents; + } + + @JsonProperty + public void setDocuments(List documents) { + this.documents = documents; + } + + @JsonProperty + public String getOperationMode() { + return operationMode; + } + + @JsonProperty + public void setOperationMode(String operationMode) { + this.operationMode = operationMode; + } + + @JsonProperty + public int getValidity_period() { + return validity_period; + } + + @JsonProperty + public void setValidity_period(int validity_period) { + this.validity_period = validity_period; + } + + @JsonProperty + public String getResponse_uri() { + return response_uri; + } + + @JsonProperty + public void setResponse_uri(String response_uri) { + this.response_uri = response_uri; + } + + @JsonProperty + public String getClientData() { + return clientData; + } + + @JsonProperty + public void setClientData(String clientData) { + this.clientData = clientData; + } + + @JsonProperty + public Boolean getReturnValidationInfo() { + return returnValidationInfo; + } + + @JsonProperty + public void setReturnValidationInfo(Boolean returnValidationInfo) { + this.returnValidationInfo = returnValidationInfo; + } + + @JsonProperty + public String getRequest_uri() { + return request_uri; + } + + @JsonProperty + public void setRequest_uri(String request_uri) { + this.request_uri = request_uri; + } + + @java.lang.Override + public java.lang.String toString() { + return "SignaturesSignDocRequest{" + + "credentialID=" + credentialID + + ", signatureQualifier=" + signatureQualifier + + ", SAD=" + SAD + + ", documentDigests=" + documentDigests + + ", documents=" + documents + + ", operationMode=" + operationMode + + ", validity_period=" + validity_period + + ", response_uri=" + response_uri + + ", clientData=" + clientData + + ", returnValidationInfo=" + returnValidationInfo + + ", request_uri=" + request_uri + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignDocResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignDocResponse.java new file mode 100644 index 0000000..6c32b68 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignDocResponse.java @@ -0,0 +1,70 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SignaturesSignDocResponse { + private List documentWithSignature; + private List signatureObject; + private String responseID; + private ValidationInfoSignDocResponse validationInfo; + + public SignaturesSignDocResponse() { + this.documentWithSignature = null; + this.signatureObject = null; + this.responseID = null; + this.validationInfo = null; + } + + public SignaturesSignDocResponse(List documentWithSignature, List signatureObject, + String responseID, ValidationInfoSignDocResponse validationInfo) { + this.documentWithSignature = documentWithSignature; + this.signatureObject = signatureObject; + this.responseID = responseID; + this.validationInfo = validationInfo; + } + + public List getDocumentWithSignature() { + return documentWithSignature; + } + + public void setDocumentWithSignature(List documentWithSignature) { + this.documentWithSignature = documentWithSignature; + } + + public List getSignatureObject() { + return signatureObject; + } + + public void setSignatureObject(List signatureObject) { + this.signatureObject = signatureObject; + } + + public String getResponseID() { + return responseID; + } + + public void setResponseID(String responseID) { + this.responseID = responseID; + } + + public ValidationInfoSignDocResponse getValidationInfo() { + return validationInfo; + } + + public void setValidationInfo(ValidationInfoSignDocResponse validationInfo) { + this.validationInfo = validationInfo; + } + + @java.lang.Override + public java.lang.String toString() { + return "SignaturesSignDocResponse{" + + "documentWithSignature=" + documentWithSignature + + ", signatureObject=" + signatureObject + + ", responseID='" + responseID + '\'' + + ", validationInfo=" + validationInfo.toString() + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashRequest.java new file mode 100644 index 0000000..0aea1db --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashRequest.java @@ -0,0 +1,132 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +import java.util.List; + +import jakarta.validation.constraints.NotBlank; + +public class SignaturesSignHashRequest { + @NotBlank + private String credentialID; + private String SAD; + @NotBlank + private List hashes; + private String hashAlgorithmOID; + @NotBlank + private String signAlgo; + private String signAlgoParams; + private String operationMode; + private int validity_period; + private String response_uri; + private String clientData; + + public SignaturesSignHashRequest(String credentialID, String SAD, List hashes, String hashAlgorithmOID, + String signAlgo, String signAlgoParams, String operationMode, int validity_period, String response_uri, + String clientData) { + this.credentialID = credentialID; + this.SAD = SAD; + this.hashes = hashes; + this.hashAlgorithmOID = hashAlgorithmOID; + this.signAlgo = signAlgo; + this.signAlgoParams = signAlgoParams; + this.operationMode = operationMode; + this.validity_period = validity_period; + this.response_uri = response_uri; + this.clientData = clientData; + } + + public String getCredentialID() { + return credentialID; + } + + public void setCredentialID(String credentialID) { + this.credentialID = credentialID; + } + + public String getSAD() { + return SAD; + } + + public void setSAD(String SAD) { + this.SAD = SAD; + } + + public List getHashes() { + return hashes; + } + + public void setHashes(List hashes) { + this.hashes = hashes; + } + + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } + + public String getSignAlgo() { + return signAlgo; + } + + public void setSignAlgo(String signAlgo) { + this.signAlgo = signAlgo; + } + + public String getSignAlgoParams() { + return signAlgoParams; + } + + public void setSignAlgoParams(String signAlgoParams) { + this.signAlgoParams = signAlgoParams; + } + + public String getOperationMode() { + return operationMode; + } + + public void setOperationMode(String operationMode) { + this.operationMode = operationMode; + } + + public int getValidity_period() { + return validity_period; + } + + public void setValidity_period(int validity_period) { + this.validity_period = validity_period; + } + + public String getResponse_uri() { + return response_uri; + } + + public void setResponse_uri(String response_uri) { + this.response_uri = response_uri; + } + + public String getClientData() { + return clientData; + } + + public void setClientData(String clientData) { + this.clientData = clientData; + } + + @java.lang.Override + public java.lang.String toString() { + return "SignaturesSignHashRequestDTO{" + + "credentialID='" + credentialID + '\'' + + ", SAD='" + SAD + '\'' + + ", hashes=" + hashes + + ", hashAlgorithmOID='" + hashAlgorithmOID + '\'' + + ", signAlgo='" + signAlgo + '\'' + + ", signAlgoParams='" + signAlgoParams + '\'' + + ", operationMode='" + operationMode + '\'' + + ", validity_period=" + validity_period + + ", response_uri='" + response_uri + '\'' + + ", client_Data='" + clientData + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashResponse.java new file mode 100644 index 0000000..2127cf5 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignaturesSignHashResponse.java @@ -0,0 +1,24 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +import java.util.List; + +public class SignaturesSignHashResponse { + private List signatures; + private String responseID; + + public List getSignatures() { + return this.signatures; + } + + public void setSignatures(List signatures) { + this.signatures = signatures; + } + + public String getResponseID() { + return this.responseID; + } + + public void setResponseID(String responseID) { + this.responseID = responseID; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/ValidationInfoSignDocResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/ValidationInfoSignDocResponse.java new file mode 100644 index 0000000..9a5f489 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/ValidationInfoSignDocResponse.java @@ -0,0 +1,51 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +import java.util.List; + +public class ValidationInfoSignDocResponse { + private List ocsp; + private List crl; + private List certificates; + + public ValidationInfoSignDocResponse() { + } + + public ValidationInfoSignDocResponse(List ocsp, List crl, List certificates) { + this.ocsp = ocsp; + this.crl = crl; + this.certificates = certificates; + } + + public List getOcsp() { + return ocsp; + } + + public void setOcsp(List ocsp) { + this.ocsp = ocsp; + } + + public List getCrl() { + return crl; + } + + public void setCrl(List crl) { + this.crl = crl; + } + + public List getCertificates() { + return certificates; + } + + public void setCertificates(List certificates) { + this.certificates = certificates; + } + + @java.lang.Override + public java.lang.String toString() { + return "ValidationInfoSignDocResponse{" + + "ocsp=" + ocsp + + ", crl=" + crl + + ", certificates=" + certificates + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java new file mode 100644 index 0000000..041d3fa --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java @@ -0,0 +1,97 @@ +package eu.europa.ec.eudi.signer.r3.sca; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; +import reactor.core.publisher.Mono; + +@Service +public class QtspClient { + + /* + * public void requestInfo(String url) { + * WebClient webClient = WebClient.builder() + * .baseUrl(url) + * .defaultCookie("cookieKey", "cookieValue") + * .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + * .build(); + * + * webClient.post() + * .uri("/csc/v2/info") + * .bodyValue(); + * // .header(""); + * } + * + * public void requestOAuth2Authorize(String url) { + * WebClient webClient = WebClient.builder() + * .baseUrl(url) + * .defaultCookie("cookieKey", "cookieValue") + * .build(); + * + * webClient.get() + * .uri("/csc/v2/oauth2/authorize") + * .bodyValue(); + * // .header(""); + * } + * + * public void requestCredentialsList(String url) { + * WebClient webClient = WebClient.builder() + * .baseUrl(url) + * .defaultCookie("cookieKey", "cookieValue") + * .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + * .build(); + * + * webClient.post() + * .uri("/csc/v2/credentials/list") + * .bodyValue(); + * // .header(""); + * } + * + * public void requestCredentialsInfo(String url) { + * WebClient webClient = WebClient.builder() + * .baseUrl(url) + * .defaultCookie("cookieKey", "cookieValue") + * .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + * .build(); + * + * webClient.post() + * .uri("/csc/v2/credentials/info") + * .bodyValue(); + * // .header(""); + * } + * + * + */ + + public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHashRequest signHashRequest) + throws Exception { + // TODO: missing headers! + + System.out.println(signHashRequest.toString()); + + WebClient webClient = WebClient.builder() + .baseUrl(url) + .defaultCookie("cookieKey", "cookieValue") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + + Mono signHashResponse = webClient.post() + .uri("/csc/v2/signatures/signHash") + .bodyValue(signHashRequest) + .header("Authorization", "") + .exchangeToMono(response -> { + if (response.statusCode().equals(HttpStatus.OK)) { + return response.bodyToMono(SignaturesSignHashResponse.class); + } else { + return Mono.error(new Exception("Exception")); + } + }); + + return signHashResponse.block(); + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java new file mode 100644 index 0000000..e0f17fb --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java @@ -0,0 +1,13 @@ +package eu.europa.ec.eudi.signer.r3.sca; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ScaApplication { + + public static void main(String[] args) { + SpringApplication.run(ScaApplication.class, args); + } + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocConstraintAnnotation.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocConstraintAnnotation.java new file mode 100644 index 0000000..644bd10 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocConstraintAnnotation.java @@ -0,0 +1,22 @@ +package eu.europa.ec.eudi.signer.r3.sca.Validators; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Constraint(validatedBy = DocumentsSignDocRequestValidator.class) +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocumentsSignDocConstraintAnnotation { + + String message() default "The documents in the /signDoc body from the HTTP Request is invalid."; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java new file mode 100644 index 0000000..dccee29 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java @@ -0,0 +1,39 @@ +package eu.europa.ec.eudi.signer.r3.sca.Validators; + +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class DocumentsSignDocRequestValidator + implements ConstraintValidator { + + @Override + public void initialize(DocumentsSignDocConstraintAnnotation constraintAnnotation) { + + } + + @Override + public boolean isValid(DocumentsSignDocRequest request, ConstraintValidatorContext context) { + if (!request.getSignature_format().equals("C") && + !request.getSignature_format().equals("X") && + !request.getSignature_format().equals("P") && + !request.getSignature_format().equals("J")) + return false; + + if (request.getDocument() == null) { + return false; + } + + if (request.getSignAlgo() == null) { + return false; + } + + if (request.getSignature_format() == null) { + return false; + } + + return true; + + } + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignDocRequestConstraintAnnotation.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignDocRequestConstraintAnnotation.java new file mode 100644 index 0000000..bc0a6d2 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignDocRequestConstraintAnnotation.java @@ -0,0 +1,20 @@ +package eu.europa.ec.eudi.signer.r3.sca.Validators; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Constraint(validatedBy = SignaturesSignDocRequestValidator.class) +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface SignDocRequestConstraintAnnotation { + String message() default "The /signDoc body from the HTTP Request is invalid."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java new file mode 100644 index 0000000..a442080 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java @@ -0,0 +1,27 @@ +package eu.europa.ec.eudi.signer.r3.sca.Validators; + +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.SignaturesSignDocRequest; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class SignaturesSignDocRequestValidator + implements ConstraintValidator { + + @Override + public void initialize(SignDocRequestConstraintAnnotation constraintAnnotation) { + + } + + @Override + public boolean isValid(SignaturesSignDocRequest request, ConstraintValidatorContext context) { + if (!request.getOperationMode().equals("A") && !request.getOperationMode().equals("S")) + return false; + + if (request.getRequest_uri() == null) + return false; + + return (request.getCredentialID().equals(null) || request.getSignatureQualifier().equals(null)) + && (request.getDocuments() == null || request.getDocumentDigests() == null); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2658350 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=sca +server.port = 8081 \ No newline at end of file diff --git a/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java b/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java new file mode 100644 index 0000000..7cd5638 --- /dev/null +++ b/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java @@ -0,0 +1,13 @@ +package eu.europa.ec.eudi.signer.r3.sca; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ScaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/tests/exampleSigned.pdf b/tests/exampleSigned.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7a5b8f6214920979a26726f5077348197b1d4ea5 GIT binary patch literal 30533 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 Date: Mon, 15 Jul 2024 22:51:05 +0100 Subject: [PATCH 03/44] Small updates --- src/main/resources/application.properties | 2 +- tests/exampleSigned.pdf | Bin 30533 -> 30533 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2658350..ac01647 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,2 @@ spring.application.name=sca -server.port = 8081 \ No newline at end of file +server.port = 8082 \ No newline at end of file diff --git a/tests/exampleSigned.pdf b/tests/exampleSigned.pdf index 7a5b8f6214920979a26726f5077348197b1d4ea5..f5f32ae2ed6758760f107dcc11da2d87a7dfff62 100644 GIT binary patch delta 403 zcmWmAy=@gh3F`m zeS5pRz1_V$KYkggL}H-V*T;XKehTj_W!o$;v;-8bR62ILZQ)Uy3?wIE^1Ul1w zs?UiA!5X@Wx&|6s#V{@2ZIo!a(fNsbG0#RGE7p$qXd5L!9O^7}99^#6D^H4r=w^+J zrkoQ>LTxcsK&&1fu(hLNlUADwWTV1706UGfnc5{72q=z|2KTtGulRLc{@~~9?bp12 bI7izenCI1id_s*HiPPsn2MS)k{r>(An(*vk&^XB#C>0ZWyMKJ8`_tT#*KU#Q48W^L-sNB;4V>%E>>mc4GEJHNQ+z*RwGMbBQ zhBaJ1zW;qD)v%zjL}!Ivq_i7n?WMh=&;!cG5|mJ;>WtzlYFC=Aj5VU5naG9JJj7+3 z26OEr+d2d}`*bd2^4exV(qfGZH$9trG8oK}IoTA;kV?gf&2;59IWF>0KQ&rrz{F(D zINJ1<Bni<~8~G5zxaNt<`JXHhyY+J>Jmlpa?%u9H_4XS; Y$tnplvZ276sB6@Uinbnd`TFbeANdGm6#xJL From 68dd6eefa726ffb8d8d933d0e823ac6ce04938c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:39:43 +0100 Subject: [PATCH 04/44] Update SignaturesController.java --- .../sca/Controllers/SignaturesController.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index ea1d598..aa3a1cb 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.nio.file.Files; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -26,6 +27,9 @@ import eu.europa.esig.dss.model.DSSDocument; import jakarta.validation.Valid; +import java.io.File; +import java.io.IOException; + @RestController @RequestMapping(value = "/signatures") public class SignaturesController { @@ -102,6 +106,9 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; if (document.getSignature_format().equals("C")) { + dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, + document.getConformance_level(), document.getSigned_envelope_property(), + this.signingCertificate, new ArrayList<>()); System.out.println("Not Supported by current version"); } else if (document.getSignature_format().equals("P")) { System.out.print("PAdES\n"); @@ -155,6 +162,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc e.printStackTrace(); } } + List DocumentWithSignature = new ArrayList<>(); List allSignaturesObjects = new ArrayList<>(); for (SignaturesSignHashResponse response : allResponses) { @@ -170,6 +178,12 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc try { docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); + + File file = new File("tests/exampleSigned.pdf"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } catch (Exception e) { e.printStackTrace(); } @@ -185,11 +199,11 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc } SignaturesSignDocResponse signDocResponse = new SignaturesSignDocResponse( - null, + DocumentWithSignature, allSignaturesObjects, null, validationInfo); - + return signDocResponse; } From 344f897abe2fc4e4d34621e010a5f04c318d14a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:55:03 +0100 Subject: [PATCH 05/44] Hardcoded CAdES --- .../sca/Controllers/SignaturesController.java | 2 +- .../ec/eudi/signer/r3/sca/DSS_Service.java | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index aa3a1cb..cd2d983 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -106,10 +106,10 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; if (document.getSignature_format().equals("C")) { + System.out.print("CAdES\n"); dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>()); - System.out.println("Not Supported by current version"); } else if (document.getSignature_format().equals("P")) { System.out.print("PAdES\n"); dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index a5b1453..85cb041 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -8,9 +8,14 @@ import org.springframework.stereotype.Service; +import eu.europa.esig.dss.AbstractSignatureParameters; +import eu.europa.esig.dss.cades.CAdESSignatureParameters; +import eu.europa.esig.dss.cades.signature.CAdESService; import eu.europa.esig.dss.cades.signature.CMSSignedDocument; +import eu.europa.esig.dss.enumerations.DigestAlgorithm; import eu.europa.esig.dss.enumerations.SignatureAlgorithm; import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.enumerations.SignaturePackaging; import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.model.DSSMessageDigest; import eu.europa.esig.dss.model.InMemoryDocument; @@ -20,6 +25,7 @@ import eu.europa.esig.dss.pades.PAdESSignatureParameters; import eu.europa.esig.dss.pades.signature.ExternalCMSService; import eu.europa.esig.dss.pades.signature.PAdESService; +import eu.europa.esig.dss.signature.DocumentSignatureService; import eu.europa.esig.dss.validation.CertificateVerifier; import eu.europa.esig.dss.validation.CommonCertificateVerifier; @@ -36,6 +42,43 @@ public void test() { } // importante parameters: conformance_level, signed_envelope_property + @SuppressWarnings("rawtypes") + public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + // DocumentSignatureService service = new CAdESService(cv); + + // CAdESSignatureParameters parameters = new CAdESSignatureParameters(); + // parameters.bLevel().setSigningDate(new Date()); + // parameters.setGenerateTBSWithoutCertificate(true); + // parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + // System.out.print("1CAdES\n"); + + // DigestDocument messageDigest = service.computeDocumentDigest(documentToSign, parameters); + + CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B); + signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); + signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + // signatureParameters.setReason("DSS testing"); + System.out.print("2CAdES\n"); + + cv = new CommonCertificateVerifier(); + CAdESService cmsForCAdESGenerationService = new CAdESService(cv); + ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3CAdES\n"); + return dataToSign.getBytes(); + + } public void xadesToBeSignedData(DSSDocument document, String conformance_level, String signed_envelope_property) { From ceedca91c63f7cc566b2450893bac8bb56c91267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:51:40 +0100 Subject: [PATCH 06/44] Cades working --- pom.xml | 10 ++ .../sca/Controllers/SignaturesController.java | 23 +++-- .../ec/eudi/signer/r3/sca/DSS_Service.java | 97 +++++++++++++------ 3 files changed, 89 insertions(+), 41 deletions(-) diff --git a/pom.xml b/pom.xml index d5ce525..ee31f2d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,16 @@ eu.europa.ec.joinup.sd-dss dss-pades 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-xades + 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-jades + 6.0 eu.europa.ec.joinup.sd-dss diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index cd2d983..602eb85 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -106,26 +106,32 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; if (document.getSignature_format().equals("C")) { + System.out.print("CAdES\n"); dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>()); + } else if (document.getSignature_format().equals("P")) { + System.out.print("PAdES\n"); dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>()); System.out.println("Data To Be Signed Created"); } else if (document.getSignature_format().equals("X")) { - System.out.print("XAdES: to be implemented"); - return new SignaturesSignDocResponse(); - // dssClient.xadesToBeSignedData(dssDocument, document.getConformance_level(), - // document.getSigned_envelope_property()); + + System.out.print("XAdES\n"); + dataToBeSigned = dssClient.xadesToBeSignedData(dssDocument, + document.getConformance_level(), document.getSigned_envelope_property(), + this.signingCertificate, new ArrayList<>()); + } else if (document.getSignature_format().equals("J")) { - System.out.print("JAdES: to be implemented"); - return new SignaturesSignDocResponse(); - // dssClient.jadesToBeSignedData(dssDocument, document.getConformance_level(), - // document.getSigned_envelope_property()); + System.out.print("JAdES\n"); + + dataToBeSigned = dssClient.jadesToBeSignedData(dssDocument, + document.getConformance_level(), document.getSigned_envelope_property(), + this.signingCertificate, new ArrayList<>()); } if (dataToBeSigned == null) { @@ -174,7 +180,6 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, new ArrayList<>()); - try { docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 85cb041..6983ac5 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -13,6 +13,8 @@ import eu.europa.esig.dss.cades.signature.CAdESService; import eu.europa.esig.dss.cades.signature.CMSSignedDocument; import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.enumerations.JWSSerializationType; +import eu.europa.esig.dss.enumerations.SigDMechanism; import eu.europa.esig.dss.enumerations.SignatureAlgorithm; import eu.europa.esig.dss.enumerations.SignatureLevel; import eu.europa.esig.dss.enumerations.SignaturePackaging; @@ -23,6 +25,10 @@ import eu.europa.esig.dss.model.ToBeSigned; import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.pades.PAdESSignatureParameters; +import eu.europa.esig.dss.xades.XAdESSignatureParameters; +import eu.europa.esig.dss.xades.signature.XAdESService; +import eu.europa.esig.dss.jades.JAdESSignatureParameters; +import eu.europa.esig.dss.jades.signature.JAdESService; import eu.europa.esig.dss.pades.signature.ExternalCMSService; import eu.europa.esig.dss.pades.signature.PAdESService; import eu.europa.esig.dss.signature.DocumentSignatureService; @@ -48,15 +54,6 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, List certificateChain) { CertificateVerifier cv = new CommonCertificateVerifier(); - // DocumentSignatureService service = new CAdESService(cv); - - // CAdESSignatureParameters parameters = new CAdESSignatureParameters(); - // parameters.bLevel().setSigningDate(new Date()); - // parameters.setGenerateTBSWithoutCertificate(true); - // parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - // System.out.print("1CAdES\n"); - - // DigestDocument messageDigest = service.computeDocumentDigest(documentToSign, parameters); CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(new Date()); @@ -69,7 +66,6 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B); signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); - // signatureParameters.setReason("DSS testing"); System.out.print("2CAdES\n"); cv = new CommonCertificateVerifier(); @@ -79,12 +75,62 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, return dataToSign.getBytes(); } + @SuppressWarnings("rawtypes") + public byte[] xadesToBeSignedData(DSSDocument documentToSign, String string, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + + XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); + signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); + signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + // signatureParameters.setReason("DSS testing"); + System.out.print("2XAdES\n"); - public void xadesToBeSignedData(DSSDocument document, String conformance_level, String signed_envelope_property) { + cv = new CommonCertificateVerifier(); + XAdESService cmsForXAdESGenerationService = new XAdESService(cv); + ToBeSigned dataToSign = cmsForXAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3XAdES\n"); + return dataToSign.getBytes(); } - public void jadesToBeSignedData(DSSDocument document, String conformance_level, String signed_envelope_property) { + public byte[] jadesToBeSignedData (DSSDocument documentToSign, String string, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + + JAdESSignatureParameters signatureParameters = new JAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + signatureParameters.setJwsSerializationType(JWSSerializationType.JSON_SERIALIZATION); + signatureParameters.setSigDMechanism(SigDMechanism.OBJECT_ID_BY_URI_HASH); + signatureParameters.setSignatureLevel(SignatureLevel.JAdES_BASELINE_B); + signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); + signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + // signatureParameters.setReason("DSS testing"); + System.out.print("2JAdES\n"); + + cv = new CommonCertificateVerifier(); + JAdESService cmsForXAdESGenerationService = new JAdESService(cv); + ToBeSigned dataToSign = cmsForXAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3JAdES\n"); + return dataToSign.getBytes(); } @@ -131,15 +177,9 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur signatureValue.setValue(signature); CertificateVerifier cv = new CommonCertificateVerifier(); - ExternalCMSPAdESService service = new ExternalCMSPAdESService(cv); - PAdESSignatureParameters parameters = new PAdESSignatureParameters(); - parameters.bLevel().setSigningDate(new Date()); - parameters.setGenerateTBSWithoutCertificate(true); - parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - parameters.setReason("DSS testing"); - DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); + CAdESService service = new CAdESService(cv); - PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); + CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(new Date()); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); @@ -147,20 +187,13 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur certChainToken.add(new CertificateToken(cert)); } signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - signatureParameters.setReason("DSS testing"); - - // stateless - cv = new CommonCertificateVerifier(); - ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); - CMSSignedDocument cmsSignedDocument = cmsForPAdESGenerationService.signMessageDigest(messageDigest, - signatureParameters, signatureValue); - byte[] cmsSignedData = cmsSignedDocument.getBytes(); + signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B); + signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); + signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); // Stateless - service = new ExternalCMSPAdESService(cv); - service.setCmsSignedData(cmsSignedData); - return service.signDocument(documentToSign, signatureParameters, null); + service = new CAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); } private static class ExternalCMSPAdESService extends PAdESService { From f7e16f37ad102674da0247c00d9f7182bcea1bcd Mon Sep 17 00:00:00 2001 From: Rolando2000 <61158161+Rolando2000@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:54:32 +0100 Subject: [PATCH 07/44] hard coded removed --- .../sca/Controllers/SignaturesController.java | 3 +- .../ec/eudi/signer/r3/sca/DSS_Service.java | 176 ++++++++++++++++-- tests/exampleSigned.pdf | Bin 30533 -> 30511 bytes 3 files changed, 161 insertions(+), 18 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 602eb85..8f17bf1 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -179,7 +179,8 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc if (response.getSignatures() != null) { byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, - new ArrayList<>()); + new ArrayList<>(), document.getSignAlgo(), document.getSignature_format(), document.getConformance_level(), + document.getSigned_envelope_property()); try { docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 6983ac5..c745398 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -38,6 +38,92 @@ @Service public class DSS_Service { + public static SignatureLevel checkConformance_level(String conformance_level, char type) { + String enumValue = mapToEnumValue(conformance_level, type); + if (enumValue == null) { + return null; + } + + try { + return SignatureLevel.valueByName(enumValue); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + + return null; + } + + private static String mapToEnumValue(String conformance_level, char type) { + String prefix; + switch (type) { + case 'p': + prefix = "PAdES_BASELINE_"; + break; + case 'c': + prefix = "CAdES_BASELINE_"; + break; + case 'j': + prefix = "JAdES_BASELINE_"; + break; + default: + return null; + } + + switch (conformance_level) { + case "Ades-B-B": + return prefix + "B"; + case "Ades-B-LT": + return prefix + "LT"; + case "Ades-B-LTA": + return prefix + "LTA"; + case "Ades-B-T": + return prefix + "T"; + default: + return null; + } + } + + private static SignatureAlgorithm checkSignAlg(String alg) { + switch (alg) { + case "1.2.840.113549.1.1.11": + return SignatureAlgorithm.RSA_SHA256; + case "1.2.840.113549.1.1.12": + return SignatureAlgorithm.RSA_SHA384; + case "1.2.840.113549.1.1.13": + return SignatureAlgorithm.RSA_SHA512; + default: + return null; + } + } + + private static DigestAlgorithm checkSignAlgDigest(String alg) { + switch (alg) { + case "1.2.840.113549.1.1.11": + return DigestAlgorithm.SHA256; + case "1.2.840.113549.1.1.12": + return DigestAlgorithm.SHA384; + case "1.2.840.113549.1.1.13": + return DigestAlgorithm.SHA512; + default: + return null; + } + } + + private static SignaturePackaging checkEnvProps(String env) { + switch (env) { + case "ENVELOPED": + return SignaturePackaging.ENVELOPED; + case "ENVELOPING": + return SignaturePackaging.ENVELOPING; + case "DETACHED": + return SignaturePackaging.DETACHED; + case "INTERNALLY_DETACHED": + return SignaturePackaging.INTERNALLY_DETACHED; + default: + return null; + } + } + public DSSDocument loadDssDocument(String document) { byte[] dataDocument = Base64.getDecoder().decode(document); return new InMemoryDocument(dataDocument); @@ -146,7 +232,10 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance PAdESSignatureParameters parameters = new PAdESSignatureParameters(); parameters.bLevel().setSigningDate(new Date()); parameters.setGenerateTBSWithoutCertificate(true); - parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + + SignatureLevel aux_conf_level = checkConformance_level(conformance_level, 'p'); + + parameters.setSignatureLevel(aux_conf_level); parameters.setReason("DSS testing"); DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); @@ -159,41 +248,94 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance certChainToken.add(new CertificateToken(cert)); } signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + signatureParameters.setSignatureLevel(aux_conf_level); signatureParameters.setReason("DSS testing"); cv = new CommonCertificateVerifier(); ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); ToBeSigned dataToSign = cmsForPAdESGenerationService.getDataToSign(messageDigest, signatureParameters); + System.out.println("pades funcao 1 \n\n\n"); return dataToSign.getBytes(); } public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, X509Certificate signingCertificate, - List certificateChain) { + List certificateChain, String signAlg, String sign_format, String conform_level, String envelope_props) { SignatureValue signatureValue = new SignatureValue(); - signatureValue.setAlgorithm(SignatureAlgorithm.RSA_SHA256); + SignatureAlgorithm aux_alg = checkSignAlg(signAlg); + signatureValue.setAlgorithm(aux_alg); signatureValue.setValue(signature); + System.out.println("\n\n" + aux_alg + "\n\n\n"); CertificateVerifier cv = new CommonCertificateVerifier(); - CAdESService service = new CAdESService(cv); - CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); + if (sign_format.equals("C")) { + System.out.print("CAdES\n"); + CAdESService service = new CAdESService(cv); + CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + + service = new CAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); + + } else if (sign_format.equals("P")) { + System.out.print("PAdES\n"); + PAdESService service = new PAdESService(cv); + PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'p'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + + service = new PAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); + + } else if (sign_format.equals("X")) { + System.out.print("XAdES\n"); + + } else if (sign_format.equals("J")) { + System.out.print("JAdES\n"); + } - signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B); - signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); - signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + + System.out.println("\n\n null \n\n"); // Stateless - service = new CAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + return null; } private static class ExternalCMSPAdESService extends PAdESService { diff --git a/tests/exampleSigned.pdf b/tests/exampleSigned.pdf index f5f32ae2ed6758760f107dcc11da2d87a7dfff62..6bcef7e43041aa8271510bb5ff8093b2e11645a5 100644 GIT binary patch delta 1768 zcmZ`(yN+B%5Y-Y{mWYgukO6IC3E2Yb(bZi=24*+2oI*xFszV~nL})}tJ~7__$Ra24 zfA9sC_yf+25kl6kMt5${^zFJ;=k@CK#jmd~e*5{+y{DI-eEo%x6lxM&G}Mp3KYCzE zl|s7L;$C;7MNEyPlMAVBeRCfq_@9+&DbYAyJpN#g8&C52B#$>Id5m)fmePszTm?UdVt&lJ(t{ZAG-t*(*2j5N_HJajM)tcCD1$S*liIdT>iejx5S5wAX)Kt~2|GR59 z?;iht{QhCmJXe<5t5sC}e|_h`9Jh4GWQ3WVeVsCYAQwo+0Tiv=3Rj zd`!;_B46t6hge6;d)-Ibim%28*=?x3ygU!9-DbAA zQj&+4Fcn@r!Rm}tg_);WRb-rmAdqu@ z6hCi;di)19N{V@JF;@u>hDsx})VjDAk$^ZnBIig*V~R-@;F%b;4$lHYZFP`_y%V^6 zDBE>XLGyJkTDEqdJb-cIU2?OyM751#YK43E;WqbJ*|!!(ZsF*oo4Cl_{V4Z z>8+-?!CWW1-ao*XXdl4)4PFaUc>5Md!?q{p-cE_2qAWsCVeBOJfNc*S@th~h-IASX&Hm5B=j6o?xMV!{n=b%wXn z`&`f_mB^lBwAoOq0&C6OD>(JTWS!dKx?MNg3QToMklqXX@uzbV+>$dlKzd7Fon;D< zIW-tw=W1t!mSZzShYmVO1McmuaHCl-Dk=rasPA%|An!>Y4C8XpwMmmwSuE_q)^X2dS-jm~> zeCPP;`Mo<*FD1Om%fFsKy?96Y`1y-(?wed9A&4%&fA{Wr0f~D#{_^6xOTW67W`0fj jguc9La&?U@IIZz@`^wwZ@uMF;zRN*(A3nT#c769B(mKma delta 1792 zcmb7FJ*!Zv;f@tw9@`&XvwLhhW_zM!q zFR-xj$K)^QI`=ga-V@|scyai6Jb7}s zeERJ2`-|`HojrVn8=nh_QDzshM85rc|3jaYD5RETrKFW<5K|_pRD{%*UagG;_gR^S z61DB`2X}thH`l(*rLV2x|A7!jOkdr0qp(W#}# zijODH;wu|g&8EbYk+_OtuL)OH#$MD_wRqi6w?95PKUO@8Gj^yLCF{$C`btfBwn$m6E*fmQ5*4yBTk11b zMZLC;qZFA^Z4gj3XN!@mPn$NXdriH!(hX=_Tc$KeWpXPPuEK*m`r2a$ z=yR`CMcO`1rKgzFGfG33K{WW`iiRj1d2jT(A|4R?A+LoNpdqJ6@#{vaPor?68j#|p z=IFzs1Y=Yq)KVH;O(ZZHC1UnAF&JH3rh(5{k3Lf?)6h{L6;L?b*=1WrJJLuc^+ChO zs+}7+&b(|cmLidS0ZiSv*3ydi)yAw#4b`v6DT_u%jzfB0W0feaHeKC)4NhZv$d@tQ zkt939XK%!#qy5_1xi0a&c@@;vlq34$7I>nmj{@=N#HoOt?6ZvRw+H&!jgq)VUMI~S zp-iL?-pe&=3sMOD22De_d*j};iXc-KAxt4MY{NB8g~#|nQ&F}XD#Bn+(>&%8YOq!W z;{~&^8m%lt-=b6t)aJVhv^U%+_I3-L!lsgSTqwXW-0%<=&Pc0r$uUdqh>t{KB4*1y z11f=PopX)gsppfmYlY}mT1_xCFhsCX>-H&qaQr`f{)b&5Iq>Td4wsD;ZE7+MHiEah zdd{v55`vpyguu~)a2S;xYaZaHw5Z9ZXhq)(^)cpm|1KW!-FkoAZUQO5x!R6)!v!oaY~E36hjKbs;903nbf{= zRGPl(s8b-0 Date: Tue, 30 Jul 2024 15:14:06 +0100 Subject: [PATCH 08/44] jades xades --- .../sca/Controllers/SignaturesController.java | 42 +++++-- .../ec/eudi/signer/r3/sca/DSS_Service.java | 105 +++++++++++++++--- tests/exampleSigned.json | 1 + tests/exampleSigned.pdf | Bin 30511 -> 996 bytes tests/exampleSigned.xml | 1 + 5 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 tests/exampleSigned.json create mode 100644 tests/exampleSigned.xml diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 8f17bf1..149716e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -110,7 +110,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc System.out.print("CAdES\n"); dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>()); + this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); } else if (document.getSignature_format().equals("P")) { @@ -124,14 +124,14 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc System.out.print("XAdES\n"); dataToBeSigned = dssClient.xadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>()); + this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); } else if (document.getSignature_format().equals("J")) { System.out.print("JAdES\n"); dataToBeSigned = dssClient.jadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>()); + this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); } if (dataToBeSigned == null) { @@ -182,13 +182,35 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc new ArrayList<>(), document.getSignAlgo(), document.getSignature_format(), document.getConformance_level(), document.getSigned_envelope_property()); try { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); - docSigned.save("tests/exampleSigned.pdf"); - - File file = new File("tests/exampleSigned.pdf"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); - - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + if (document.getSignature_format().equals("J")) { + System.out.println("\nJADES SIGN\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); + docSigned.save("tests/exampleSigned.json"); + + File file = new File("tests/exampleSigned.json"); + byte[] jsonBytes = Files.readAllBytes(file.toPath()); + + DocumentWithSignature.add(Base64.getEncoder().encodeToString(jsonBytes)); + } + else if (document.getSignature_format().equals("X")) { + System.out.println("\nXADES SIGN\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); + docSigned.save("tests/exampleSigned.xml"); + + File file = new File("tests/exampleSigned.xml"); + byte[] xmlBytes = Files.readAllBytes(file.toPath()); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); + } + else { + System.out.println("\nOTHERS SIGN\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); + docSigned.save("tests/exampleSigned.pdf"); + + File file = new File("tests/exampleSigned.pdf"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index c745398..b38ec0b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -65,6 +65,9 @@ private static String mapToEnumValue(String conformance_level, char type) { case 'j': prefix = "JAdES_BASELINE_"; break; + case 'x': + prefix = "XAdES_BASELINE_"; + break; default: return null; } @@ -135,9 +138,9 @@ public void test() { // importante parameters: conformance_level, signed_envelope_property @SuppressWarnings("rawtypes") - public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, + public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain) { + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -148,10 +151,17 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } - signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B); - signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); - signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); System.out.print("2CAdES\n"); cv = new CommonCertificateVerifier(); @@ -162,9 +172,9 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String string, } @SuppressWarnings("rawtypes") - public byte[] xadesToBeSignedData(DSSDocument documentToSign, String string, + public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain) { + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -176,6 +186,14 @@ public byte[] xadesToBeSignedData(DSSDocument documentToSign, String string, certChainToken.add(new CertificateToken(cert)); } signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); + System.out.println("\n\n SIGN LEVEL: " + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n DIGEST ALG: " + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n SIGN PACK: " + aux_sign_pack + "\n\n\n"); + signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); @@ -189,10 +207,9 @@ public byte[] xadesToBeSignedData(DSSDocument documentToSign, String string, return dataToSign.getBytes(); } - - public byte[] jadesToBeSignedData (DSSDocument documentToSign, String string, + public byte[] jadesToBeSignedData (DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain) { + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -204,11 +221,18 @@ public byte[] jadesToBeSignedData (DSSDocument documentToSign, String string, certChainToken.add(new CertificateToken(cert)); } signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setJwsSerializationType(JWSSerializationType.JSON_SERIALIZATION); - signatureParameters.setSigDMechanism(SigDMechanism.OBJECT_ID_BY_URI_HASH); - signatureParameters.setSignatureLevel(SignatureLevel.JAdES_BASELINE_B); - signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); - signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + signatureParameters.setJwsSerializationType(JWSSerializationType.COMPACT_SERIALIZATION); + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'j'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); // signatureParameters.setReason("DSS testing"); System.out.print("2JAdES\n"); @@ -326,9 +350,58 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur } else if (sign_format.equals("X")) { System.out.print("XAdES\n"); + XAdESService service = new XAdESService(cv); + XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + + service = new XAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); } else if (sign_format.equals("J")) { System.out.print("JAdES\n"); + JAdESService service = new JAdESService(cv); + JAdESSignatureParameters signatureParameters = new JAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'j'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.setJwsSerializationType(JWSSerializationType.COMPACT_SERIALIZATION); + + service = new JAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); } diff --git a/tests/exampleSigned.json b/tests/exampleSigned.json new file mode 100644 index 0000000..d005456 --- /dev/null +++ b/tests/exampleSigned.json @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6Ik1DUXdHcVFZTUJZeEZEQVNCZ05WQkFNTUMybHpjM1ZsY2w5MFpYTjBBZ1lCai9NWFFDRT0iLCJ4NXQjUzI1NiI6IlpTRl9CcUdIbm9yTlV5Qko2dXUzeDdLbVhhUHVfWkhqNFR3NmpUOXZzcGsiLCJ4NWMiOlsiTUlJQnV6Q0NBU1NnQXdJQkFnSUdBWS96RjBBaE1BMEdDU3FHU0liM0RRRUJDd1VBTUJZeEZEQVNCZ05WQkFNTUMybHpjM1ZsY2w5MFpYTjBNQjRYRFRJME1EWXdOekUwTWpVek9Gb1hEVEkxTURZd056RTBNalV6T0Zvd0Z6RVZNQk1HQTFVRUF3d01jM1ZpYW1WamRGOTBaWE4wTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDc2VVVW1EOCtPa3VoNU9yTFQyTHlPNlFDTk9JaWRvaFY3SEFqSWJnZHBTVTFDMjd6K0pEV1QzY2ZWYm9qUTVFenZaTTlDRFBheUhybG5OSzhORkQ5Z2dFM3JiT242QVRUOWlDNHFUUXZQTjNTZGVsNU9UYVZhYk11TVQyc2F0d2J0bDh3Qjk4NTgzaTRiaEpVeUhSeTdQSm5Yck9Dc2N5SzE0R2pHbnVWd2pRSURBUUFCb3hNd0VUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRHQkFDV0tlYzFKaVJnZ21UUm0wYVFpbjNTSm5zdnVGOEpTNUdsT3BlYTQ1SUdWMmdPSHdzL2lGUGc4QkFhR3pRMWQrc0crUkhIMDd4S0NsbDhYdzFRYXFMaGMrOTZ2Tk9DdmwyY2psN0JkTEgvZmlZdXJQOFZmMFczbGtwNVZiUkZWMm5Xd0hjT0lQQlVhOGxOSyt1VjZaNW5QRzVBZHMxMkJKRDVLOGpBSFhvMkUiXSwidHlwIjoiam9zZSIsInNpZ1QiOiIyMDI0LTA3LTMwVDE0OjEyOjEyWiIsImNyaXQiOlsic2lnVCJdfQ.JVBERi0xLjMKJcTl8uXrp_Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmlsdGVyIC9GbGF0ZURlY29kZSA-PgpzdHJlYW0KeAF1j00LgkAURff-irOsRTof5oxbK9smDbQW0aBUsvn_0BOCVq0ul_sO992FhgVnsKVOVeEK8sJjjU9La3LePTdmsnPU3CPZQbSLaGL3F0oEGlDriXBm5RZU6q1xuTISKHY_K9XOKLqJKqC_seheTBImslr-ksIwsLm202vsqcZ2fnI51lvCg1OQCc0HpYAtrgplbmRzdHJlYW0KZW5kb2JqCjUgMCBvYmoKMTQ1CmVuZG9iagoyIDAgb2JqCjw8IC9UeXBlIC9QYWdlIC9QYXJlbnQgMyAwIFIgL1Jlc291cmNlcyA2IDAgUiAvQ29udGVudHMgNCAwIFIgL01lZGlhQm94IFswIDAgNjEyIDc5Ml0KPj4KZW5kb2JqCjYgMCBvYmoKPDwgL1Byb2NTZXQgWyAvUERGIC9UZXh0IF0gL0NvbG9yU3BhY2UgPDwgL0NzMSA3IDAgUiA-PiAvRXh0R1N0YXRlIDw8IC9HczIKOSAwIFIgL0dzMSAxMCAwIFIgPj4gL0ZvbnQgPDwgL0YxLjAgOCAwIFIgPj4gPj4KZW5kb2JqCjkgMCBvYmoKPDwgL1R5cGUgL0V4dEdTdGF0ZSAvQUFQTDpBQSB0cnVlID4-CmVuZG9iagoxMCAwIG9iago8PCAvVHlwZSAvRXh0R1N0YXRlIC9BQVBMOkFBIGZhbHNlID4-CmVuZG9iagoxMSAwIG9iago8PCAvTGVuZ3RoIDEyIDAgUiAvTiAxIC9BbHRlcm5hdGUgL0RldmljZUdyYXkgL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngBhVJPSBRRHP7NNhKEiEGFeIh3CgmVKaysoNp2dVmVbVuV0qIYZ9-6o7Mz05vZNcWTBF2iPHUPomN07NChm5eiwKxL1yCpIAg8dej7zezqKIRveTvf-_39ft97RG2dpu87KUFUc0OVK6Wnbk5Ni4MfKUUd1E5YphX46WJxjLHruZK_u9fWZ9LYst7HtXb79j21lWVgIeottrcQ-iGRZgAfmZ8oZYCzwB2Wr9g-ATxYDqwa8COiAw-auTDT0Zx0pbItkVPmoigqr2I7Sa77-bnGvou1iYP-XI9m1o69s-qq0UzUtPdEobwPrkQZz19U9mw1FKcN45xIQxop8q7V3ytMxxGRKxBKBlI1ZLmfak6ddeB1GLtdupPj-PYQpT7JYKiJtemymR2FfQB2KsvsEPAF6PGyYg_ngXth_1tRw5PAJ2E_ZId51q0f9heuU-B7hD014M4UrsXx2oofXi0BQ_dUI2iMc03E09c5c6SI7zHUGZj3RjmmCzF3lqoTN4A7YR9ZqmYKsV37ruol7nsCd9PjO9GbOQtcoBxJcrEV2RTQPAlYFH2LsEkOPD7OHlXgd6iYwBy5idzNKPce1REbZ6NSgVZ6jVfGT-O58cX4ZWwYz4B-rHbXe3z_6eMVdde2Pjz5jXrcOa69nRtVYVZxZQvd_8cyhI_ZJzmmwdOhWVhr2HbkD5rMTLAMKMR_BT6X-pITVdzV7u24RRLMUD4sbCW6S1RuKdTqPYNKrBwr2AB2cJLELFocuFNrujl4d9giem35TVey64b--vZ6-9ryHm3KqCkoE82zRGaUsVuj5N142_1mkRGfODq-572KWsn-SUUQP4U5WiryFFX0VlDWxG9nDn4btn5cP6Xn9UH9PAk9rZ_Rr-ijEb4MdEnPwnNRH6NJ8LBpIeISoIqDM9ROVGONA-Ip8fK0W2SR_Q9AGf1mCmVuZHN0cmVhbQplbmRvYmoKMTIgMCBvYmoKNzA0CmVuZG9iago3IDAgb2JqClsgL0lDQ0Jhc2VkIDExIDAgUiBdCmVuZG9iagozIDAgb2JqCjw8IC9UeXBlIC9QYWdlcyAvTWVkaWFCb3ggWzAgMCA2MTIgNzkyXSAvQ291bnQgMSAvS2lkcyBbIDIgMCBSIF0gPj4KZW5kb2JqCjEzIDAgb2JqCjw8IC9UeXBlIC9DYXRhbG9nIC9QYWdlcyAzIDAgUiA-PgplbmRvYmoKMTQgMCBvYmoKPDwgL0xlbmd0aCAxNSAwIFIgL0xlbmd0aDEgMTIyNDQgL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngBvXoJfFRFtnedqtvd6U7vS7bO0p0mbBcMJKAEIumQNAaCGjZN1GACAQOI7MpOXCKxAUdRjDPub_Q9nVG56aATxiXRMTycT98wjuM6TxFR0DHf-GZ0cBxIf_-63YmBx_Pn-9583-0-dapObef865yq230vI8aYlbUwwcKLVjSuYjdSKiSvgboWXb8u8OWJP_0bY3QLY8Znl6y6ZsV6r7OfsZRSxmzF11y7cYntyp8vZcy3CoNsaF7c2HSydn4KY8ET6H9-MwTWy3gPY_kBlIc1r1i3oXyT2YFyJcqTrl25qDHd63kF5S0o565o3LDKuMOuonw_yoHrGlcsfnbziV-j_DzKBatWrl3H3qPDKB9DeeqqNYtXDT96URpjoWGYfx9khI-8rMzIngQPsNlJiS7-v044egqQco4RDLrMeI6aAZEpmQEyg5eZWVgq9ASQRpVlMmY4xAoG0sFWyChTWYYsx9_XU4msnu9fH9fzjPW_I2X_k0sqJulc5v23xu1lK5k1PiV-Z_wr1s2amTdeHr83_sU5x7gpvo19yN5gr7L97Cl2H_sA-V72IjvAfs4-Rv4gck-xvWwhej_KHmCt4D9jj7C72RrIJZeS3v88No08Q3aSnWTtVM5mgp993Y1R7j5b-F-WP41nnaPug3g-28K28PX8GFuPz08w4pPs4SEtESHsMbaWbWa91McW0vNsCexpY03sR7wm_rnhVZYmNrA0tlJ5WHrCGdc9rJZtZE3i_vh_wE8cphrm4Pvi_4Gxzry-r91KzD1wHWC7yMK2Abfx7FLoOm2g4kwODE_ChkWw5SZ82rEanfhswbx3Dm1prJSlAb1Tkt464EeGCYm28ePw2Q16vlfinfTYj9j1mKGUjWW5zBHPg99UxRfHN8YfiL8Ab5Cr_89Jr-hG6Ql2J7VDg4XsKnYZf4369NJKlC9jMymHbOxBjD0xMeNAmowqGbu4pI_La0A_JYliMragZeKKTxnI0R5KBxKH2K9Yl67P_WwPi7IW4LAO_n0Fq4Huk7HxJNp9ovuw1Py7NgvYPPgeLvjgVNjzwcDYkht-r8f-koTsP-knY79XvJzoI1FMXNiCB67XsZKJaGgDNusRf4uwsif16JHr14tVewS-NlB32WDtQX1tZfuprEhqEZ8c3wbs_41dzlbyThpJN6Nf28BEQ_iTkA54cgb7gKYOqRua_Z_4_RbEUC-7Z-hwiPd6tvgMyVmFs_E7q_ocRUMfacDhDfYs5tuMjzyJzr4OwL97gdMmnCblbAfW8QPERzNiuAmIv0EBrM9vsYud62rEuIexKivFEpFc5XM1g4fIzzkuQ19CmMJIgecP-u5A04TvDpTO4FMNgr1DbvjHXewd-MRT7BfYS66RUnhx4vrvrdFN7Do2Ovlh4fCcGVWlUyaXTLrg_IkTiovGjys8b-wYdfSokSOGFwwL5QcDebk52f6szIz0NJ_X43Y5HXabNdViTjEZDYrgxMZQhpZRURtZpmVWNGjWUGXIGdCsl3x5caHG3P5gyBUoLqwbm2ylGVSNeao1b01tBwtPqtOM6tlNLtFEgfPPQXS-2B-IaEoBvqGZjU3ayDm1wZDzLf9gfR2G1bIqaoNBv8YL8J2BKnxnNgaaNGcN5KjQJTM0VlMrqSt-dBKEbFKwDumcWi13oFgnR0uYMkTJA4ionrPUvISizg5rZkWlxrwdzHpUYz7Z7MtJTGOl2kgVijiR00djhRp5_6yRRyPfxTDpzClktyOTzoFBpGlZKNK0FIg2NXyH6ZcJRIOBaCA6p9ZV7A8GdaWrtUOzaztSLRWhisUWWMF0AeuwpEKSKgVYllUdZJ1KeoZbI5M7OEuxAT63VDciaZkW3tmATKgSuKHG811NV7xn19Aqhm6JRgzN9Bzpc2rGCs2UUCKwVAs3amxnoGNMT3RXl5MtbFCtTaGmxqtqNdEIpTqYKIg0z9Oyq2uugAhKgBqaA3K5K_VELl4g0hyIoizbNiANVaLrmfKm5sUN0k2oIVSJOnNF7Y5gj19zg0c0l6rZ0N226ZhfRCMZSwOyGI3uCGgPz64dWhuUbeAEGWPHBKKREGbDYJFl0-SKFQ4um-6NM5r0xQnvbAxoLQuXATN8G3cN-H8w6tSsfw1idbA-6CmjQwIsqalhmTRlGXoqYIHozsW6qbt00-CvgciySkmyI7yfzUfvK2ojzaEI8ExOCEDQXxSc3TcY1DJV2TEajUgVG5ugvUQG30xVVyNRQEz4VYI-FVp4ns7YPH0NMGO4sbIuKUo2QI2CddDCDZV1ddKoxAJopoIdhvNCgagc3lSgeVVn8BXU9YwdUz2nNlIpvRMteUXthX0Z_j7kq2sGxZSBNtHCPgmSrJkbqp6d8IJmiY9MGuYlAhioJVceTZPt9VFfz_C_jr7TQ9MbotHpocD0aEO0sSvesjAUcIaiHVZrdFWkIaBHPkH-y51-bfquOs3Z0EyTscjS36bPqdY8s6-UyzM90NwICb5loeAkf9CFoRNtsHOcuzoZZ_B4-L2Ms6jzC1hsxY7kD0yX20sXdgW_5pwkwxSazK9FHCzCFJEmPUF8zMXgfhkpoq4gsnRuEiB_EFPqDiP3vdlJKQYJBmUM7ewKs4UoaC2zaxPlAFvoj7FwoYq1a5A1PQM1vvmypmWgZrB7QwhrlVGN-XWf-K98Gvv5oD9HXSF3oERu5tAO3xlNWs882PjNJC0FiOnL7amoFX4umyDH_ULmLCqOhFItXdU7SkywS0adocDhkOZUNUNFbY-_tC7gdGGDpEFnSI4o3dR5OPQqyU2UeZ0alWqUJsOKYVMFjNj00yehcrBjIBJtSHrfUPvQVLZuah6Mo4QVCFxpJGBwhhC3_gQeLndImvqa9PaBU6FgugwqrI2O2Mw6zS4PO83-hZ7AOH9FbQDbEMJ2tp4JRALNctW1QEOlvh_U-WX9gLgrfqShUu5_tXA0NPEn_RteDtiSMZGEoXpe7UBuTu1W_6a6sV1s7pjqLmbGSUp0e10XxVu7WGXOAWZm4uoFqJ43JhCILK3EhCjMHwPB6CByl42Bb0rXrw3VyZNkRlM0IJ2_CWbpHBWLo3WF8Ne5tdgvGeJQC9f5B7OL6-omY5zL5TjogubROoywLDkCuC4qPI1GtWOqsVMNr6nFZttSCUevlCEMc3sQVT3SYmlI3aCm0Hjr0oykzldA57rRqL8yMQp8tQVD1EWjcsy5tSG4eTTqj8KOZLmL2NmCcFLQxWQXGB7popYa9AUL6fcHkVAwJJGvq8RUVwH3gV2qi9V_P8ILBvVGz6uh7QId4YZ_EMKNPwThhT8I4UWDmp6BcBN0XiQRXnxuhDU-_HswHgppOAFp-ByQLjkD0mu-H9LmQUWh1VKo16xDuuwfBOnyHwLptT8I0hWDmp4B6XXQeYWEdOW5If1_4bSrzkB49fcjvGZQbyi5Ftqu0RFe9w9CeP0PQfj6H4TwDYOanoHwBuh8g0R44_8_hDcNQZgx_OrBr3588H-iiXl_YeQKk1T4-puv68n4cUFX0FWAhPBb71RYtJxqMbC_s7DSgp7o1Y3fgBH8r2HFP0a57HS46NMUmuGlz7xkzi7JnpFdl63I5Insv2UrK7K3Zu_OFhOzIll8q2e3h3vGka3K1RU_HB5jd1WVGOnHRrJa7rc8aRHPKgcVLhSPElKE2Zo9K5tbnbOc3NYuyIKDOjw7K6fKmkouEU6tSeUOT56Hew2mtLvSw8TS7xo3LncPCdseNtk00xQ1CZPNv83g3mp2mPPM_CEzpdu2sbDDfKmZm0XAvJLnseVcVenqBfVZGc6L-4rqVzv73uxzl9Szwr6yPpXp0pLx42SLepBaD85A9fWrVVVn9avri13BojSf06C4vMbQiOIi-UN0uEoTi4uQCYa6qeWDvv7DtPzLo8fU45U3fdExpeLxA_1PzzEcevzB_r-sWdn_wf4XH8i5YO8t-1rvuPk9iS-X-BruAb4W4LsvPKfEQ7d5PvPwUjdZ3bPc_FY7cfs0OzebzHTcfNLMq8y15mZzl1lJNdOMlAMpf0sRw1JoQgpNM84x8gIDGQw-Q8TQa1AUxatwi4KzOzwK6FscMx3LHMImHK6VLu4ypgHA1D3MudWUCqwyTCt5egIkHQtnn45MAhEAwlSAsVr_IpHArK5fLfFQ0tNcThYsCAYuON_lHHEeqdRNX_yBHIse6N_U_2D_x2SjwIYbt1_81ttPGQ4tXP_L_sP9P-pf9sB9j1EaGfZeWYF_zIGB-BYYmNm08PCUPQYDU6AccxgLjdzBCzl3CKORG8zQM5Wv5JYheha5SgrlmrHCMixnYd_4cVDKB3cGdYspp9u58TSGfqDfed_phiTeSpWOt4c9FB43UaG9CqW4RromucQJF623tdqesfXaFAnnQqMwGzONo4zCmWF1VL1tJzt-YIbLLI4qnyCDWC9axddCMVcq8xSeuqfMvM_8G_OHZsVsZh7dgK2GVAuUZoaAgVuFz7CKe89yRIlzWR-MqIfqJF1utVqvAugkvgH8l8GCoUzSfQzO1k13kfXz_p7-Y_Xzn3vt4eduNBx65jdf9H91ej__45MPtK5hwLMp_r74DP_khGjSAeZBMO3z51Z97acMom-IzGmZafzdtM_SuB6DmzKjmdyXQ605dHkujbZeYeWTrTOty6ybrFGr4cd55M7Lz-PLbeS3TbHxkSFKCZHJReaRRqpz0gknXZZPF-XTO_kn8vmsVNqV-kUqt1qyLbMsYozlWssWi2i2b7B_YxeV2TQhuzL7luxHs_dnf5VtNAcyAzzFkRVUmNu7J89X6OO-9D1uhe8REuVis7VKGPYwT9Z2hzcM-L1eh7g6ZXsKvzq4PchTtgXDTAREjRAWURBcyYeJa4eGeH29u6SkUC3sY2Wn31L7kNT3udzpMsbhuTq8ktWvllGuSxLSREE15I-YmJbAvJDOo4kTzi8uSvcND-Ubfd60PMoln1ehj_ae-PaRLe2LWpe8cN83PT9_8fO1K25vji7rubf_44Lyw-tuWnb5puop13Zv__m_l_Rcta525ppLS1e83PbS59IPr4kfV0qUy1gOG8G-DReMspXYZtg-sylmI1mCo4P82yCdEPRZKr2b-lnq31LF2yYyyX1xVHBY1UbTbSb-x3xanr85f2e-MBjpZ_m0Pr81nx8fRukFxwv4K0QkA39GIFRlpWy6isQpoo2CXhXvCp6pjFI497g9-R5hzrt3n5ngsdzlaHfYKVXYre0uZ2Z6e3ba8Ht4Gssy57Zm-XTHd6Y6quyhVkN4lK-ZjzQskXjru8UrqvOVPoQiKwPQB4E1QhJoYwF0t05uF-rq1avrme7a8HC5BqtXFxhD-cMnThgWLJ44IZRvmgiY03wuL_bWdNQI1wgd8OIi_nXHozti2FzSC1--_5l_ffrZp5ptmfaK2dGKaf90qKlqc_SnT9wY6775hYafzn70qX7vE8qKANkpk29uukg-0eFsZfy4-FS5nLmwz34evvwCJ5HTn1c1TJmg8FeUYwo3iIkiIuYL5THENK8TSwWfbKRfGz838vnGr408YqKIab5psUl40kJp_Hdpf0njk9PIbc-3F9lj9pftR-1GoHQ4bMnKrTKYW3H02OQSVGblVVm9NMZ7lfcL7ymvItZZqNBCYaVGaVCEQyG7wd1us3Ffe4qTtWMReJrb5ml1pdoAdEbqUp4-BGjnQewV0qNxamHTkHuG3P1wZmHfgCfLoyrp3pKrOr5s4gRWXOR2mYLuYNEFFHQC2I8O7v6y_7eUerj3bTo9V6WazZ2nG-jDox-HSqms_ySV9p_o77-wf-s0-j0Vyj2lF_cBrXgmZGGd4U0WY5ZxtBGBlyVGI_6SJXOWebRZWG3GHONYo7CZc8xjzThvcsRYIYw0x2hShNJmMHoNBqMsGlLMKTtMRq_JZBQpZsEUB25WjGZmcGK7FA6DyeQU5lbsnibcBVhNy3gqWzw0wosmOQ8uqAcchWVlemDvMDjVHSnqKzsMGYmM9DWJDuHkLnZRsZlC5Ortpdfe7J_B3af6yaieUrij33D6BT729O_gJ7qdFIWdgvnDNigkoIFiYK3jxiV9HdPJQ6YYAxnVv_0-2cfYDd_ys8_C0xWFlBTKctK3TspKp2_TabR7spuPzpycyXG6P-Lp8Lzk-cijlGTRI1kdWS9lfZSlGEy03rTX9Jip1_SJybBWEHbAd8IL3L4qIDXdeJmRGy10vmW6hQ9PuT6FpynDleuVe5RnFcPJVDppJNdeszPjngyTy5NlEAYnOXx2Zm-1CT-Ud0gryCx8zGtbPMXkbfWFs3N8raUD9hTpt0XJ-yJ5HBUXl5W50ovhYfJESgYsTiXdsWQI6w4GBHxyT0wEbMglo9eVZL29dxx979JdR-548OBdP3784D17f6ZMfe3YsU-euPfU5_zkL989PZKffO690yMH_OpC7IMutiE894CDnnDSWMeFjosdWx27HQ84jDeY6G3TcRN_3_ZH299twhbAfWGGnRSj1zgMxzMTTsGdSOx7pd0WZmk1CxfM9pibuTvhMIlbPmkaPKWvqAgRM36cftDKHUh3DmkK9h3ojy3ehKW9_-jux3svmvPMw1LzzvZTX_HXNl7Td7pc30t6-9crP4HOdpbOfhe-12OmWhsNcxA8eKSz1slPOqkr_VA6bxOUmUZL5VE7yVpl5c9aaLRlsmUmDizjRGPEKNzumJuP8FKOl0ZYqN3yLxb-NysdcL3q4lHzL828wDwRiWmiKWISNkeOY6xDpFpt2TbcTJAjXfhgs83hak9NY37PLSkOYYfhmSlLeQYMz2wtG9iei4pgvG66NL8M9ksEsLbyWs2woImcLAGSek9ibd0-Jw_lj8iktOKAC5uGqbd3459f-Sh--KWWe6OUfeqxB_rXG1597Vj_0W_6_9z_zN2nv-W3t1EHbetEHCGUyVhzbN0zVztKvyZ_4lnZITIUoYbpvKl_vbEbkcZw3yfbywvc9Hx_DV5TyD15_ckKW3CwJlHPmN0IEW_DbvQT1m3oZ93KTNC1oEtZk7KcXaPgqaTyFOvlNazXtIH1KidZr8Es9y8WwOcwzaE2-pa_Jm5Rhin3KjFDjeFnxtLE3FjRGxD5l-K3EjEnG4cndMxwxFYMmdSQmFvnsI15GJtVN3P25Zeos9YvWtrUeNGaxuua9MeFPKFqfBKe153rskMo8PbFCDaKFbPzWSWrYpdgzho875uLF2HYarWLXQOqB81Vy9GeT2dOEGfbeSUjXsZLoYfKp_JSHIl_eo5fCOGFKHjz2Auoc4I47aC2WFZeuItu63SmlbByO7UxJ4jTzdSKsVS6JclvpdYYV1ueo-0Y9kPaEl5CHx5JS89-8_dINm9J8zs2520u3Cw2b8n87RsQXX8DkhWrkFy7Esny69L8V1-3fTlfft32NVnr1nt92dcsQ7JkKZLFzV7_jxY_vPjwYrG4uXV1VubatE0VmcGNIH5AzBEzMbPzBTGd1YA4C4uSWKq95EC8R0yK2ZOZTrO1pKY8VYxlJMaJ8UBP5V_xv-ABu8o_jr3I1S7-h84Xm2Arf79z_OQSyWOhEXIUZLxePfNurLgkmRl9XjITDCUz6ZnJjN2lZw7HXMjwFr5Bqleu8nWsBsQBbT1y9cil8lnMD1oOEihVo1TNOJ-Mn9dpLI9PAneDF_HxEmw-LskLwXMhP4-Pj-XmBbrA3GklB-gkfRwTqqU8QF8xoj_R_5a98NpBgn-R5H9M8hN0XMJAn4Ir4J-Ao3381_RxZypUL8-BgNj1SG-VVXQ37dEHvCvJ99Ad8GKV7gQ3gf8IXE54O90Bk7u7USS2CmmLrKDLY3sUtYvmxu6U7NJYu2Tnd24XKhysJObOKCk30zAq0JVykkvnSnjKKcD3Tc03PPxpVlbJT-4T6v33Kep97Rb1Lox35x6jugcj7QXd287Ve9qF-nA7PdS-r727XTwvqsQMaZyYEWvlqnSJik6nqyTvRYEgYEdkinu2CUAtUO4WxWwcKAyqASmiWHilEqIoyQuFFy0Lu1FELMN7AiDOv4y9YIT_HI11p8gp-Eex9JG6C3wUgy908SOxHRbUf9jZrcBU_nZnqED619sxHxYN7d-IQaVyGz_IeyWe_GXeo_OXknyX1P15fj2_QZrCb0iawlcnTOFrpCl6GuYNA4M2xCyp-uhXx9Iz9MyC2LBReuYKvV-5l1-pd5Spg89EmsZnsOEgzsx8LMsEcainxlw-vd-oTpurBN4Wkt72As_nAbncPMgDMUV9FeMFsIfkIpXBlZeopb_Sb_SFPELPYOsM0of0TGx4MNBFH8ZygyXlWfTv9Afda95P8veS_N0kf4fe1gd4m97S271Fb8K7tG4UCXeWb-rC3-nCpeWp9AbsOCBTeiNZ91u9DjMejmETOAD__o30b7WbfsoeBe0HifgR-ueYx4dloN20S59wZ5JHwaVbXxa7FdsEzY-1CLB5sVsNYHNiOySbHWuTrCbWJusuibVKNksuVBdNiu2QbHysWwrzE0J7OBWV3_5dUf8uG-HHquVr6Zh_pSN_xc-unrC5w5ddEv4ELi9L5-2zOUqgaXh_zf6G_av2t-zv2X94_5H9X-4379_XlHfiuKLeFjWp0Z1GdRcIXX5x-7iiktt3Y0p09-7ODZXs3snVna0p6k03KuqN0oZ4T2fLzIvl-J0tZRUJPqEkwUeep89rbckJlbRs4-r2bfqoYevWyIySrShsw0hy6EAbhm6DhTsguBW39zffYlFvAV_V2tLKu1up3CLminnMjp_Zs5FeIi6VaUw05ZXPF7PExcwh_CJb5DCrcOAmzAVuxS2_HXwE-ChmE0HUh8BzUR8AH8FKRRCUC_KDHCArK-VP8af5Pmblj_B_4j8Ff5A_xB8GPwD-HLPxTtQ_A66hPgZ-AH06QXiBgT8FegT0IOhGfhOz8218O9ItfKtMdX3X8018M2LFyV24MbRyG7dzBzhxzgWzUj_FcXJbsZO72H0gLttir3eyh0DdoA9BBuzcNlYG2g4SLI_6ETeZ6OuHTh6M6QPPhB4ekBNkAxlBxErRtpReoBepG_N1UIw6wZ-mfXhCaKVXwf8Xs9GvUN8L3oP6l8FfRZ9fgXpkX1AH6GnQCrqO8PojNdJCWgS-gK6mBr28JJael1c-jZawMtB2kKCNqN2M0dai13rwVei1BnwjRloLWiVHBC0BNYIWgMbQWOag4TQC6Ugaxew0mlSkGZQJiZs8SL3kgyQNr63ZyUBGpBz_3NnxZizJNPw4XKU_7vBf4Ms43-eb6HNP8DmKfdYin3m8zzjOJwp97Dzf8BH2kSMco1X7GNWRH7IPCzly8-yBPIfD6bKaLalWoynFKhSDFUhbmQh7skJMePKMIjsvz1Hm2O7A30CUJy4V3SIuFD_l2DJMWTafM93mVry2O_w0pnR06cjS4aXDSvNLA6W5pf7SjFJfqbvUUWouNZaKUlZaU0yau5pVz5umeQh87jStWK3uEoE5WpFarZlrrkw8u4RU4214eDdPU9q6OJi74oora7soUz7abPUfkHZr1Q2tu-vw3so0jdq0EJ7lgYXxXDHQhqfq82o7OE3D-xvaBXieKlvVqTlak3y-3ZJTpxXJzB05dXhkP3m25g9NU8--1krB2nU6-66uY-TwiDY60qiNiTRUfidWVYYCFI5o_ZHGLuKhMyoHGp412IAYfC2uZLGLb4l08U0Yhm879zCD_brEJZEuMQtNRY1sum4tDdadI7N2HYSkp2fX6pOvWw9FzqiBANdawCC7Sjx0NiTR1V6bqGBDq9ngSAPSAT5kkiFmJ8eUvdaqVFHrr1PxiooWgpMMdEiOKBl10Rb5WkwXbU2wbQm2PcFaEuzGBLspwW5OsFsSrDXBbk2wHQnWlmC3SZa0DHclU3QpL02wCxNsaoKVJVg4wcoTbFqCVSSY_v5OF48kStMT7CLJgBtsW9thlt5fM2datZaCF1BSaq7UskIoHELhfBSsoWns_wBPDLV4CmVuZHN0cmVhbQplbmRvYmoKMTUgMCBvYmoKNzQ3MgplbmRvYmoKMTYgMCBvYmoKPDwgL1R5cGUgL0ZvbnREZXNjcmlwdG9yIC9Bc2NlbnQgOTY3IC9DYXBIZWlnaHQgNzMyIC9EZXNjZW50IC0yMTEgL0ZsYWdzIDMyCi9Gb250QkJveCBbLTEwNjcgLTczNyAxNjQxIDExNjJdIC9Gb250TmFtZSAvTFlKUVdOK0x1Y2lkYUdyYW5kZSAvSXRhbGljQW5nbGUKMCAvU3RlbVYgMTAzIC9NYXhXaWR0aCAxNjQwIC9TdGVtSCA3NyAvWEhlaWdodCA1MzYgL0ZvbnRGaWxlMiAxNCAwIFIgPj4KZW5kb2JqCjE3IDAgb2JqClsgMzE2IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU3NQowIDc0OSAwIDUzNiAwIDAgMCAwIDAgMCAwIDAgMCA1NTMgMCAwIDUzOSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU1MiAwCjAgMCA1NTcgMCAwIDAgMCAwIDU4NCAyODkgOTM0IDYyMSAwIDYyOSBdCmVuZG9iago4IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UcnVlVHlwZSAvQmFzZUZvbnQgL0xZSlFXTitMdWNpZGFHcmFuZGUgL0ZvbnREZXNjcmlwdG9yCjE2IDAgUiAvV2lkdGhzIDE3IDAgUiAvRmlyc3RDaGFyIDMyIC9MYXN0Q2hhciAxMTIgL0VuY29kaW5nIC9NYWNSb21hbkVuY29kaW5nCj4-CmVuZG9iagoxIDAgb2JqCjw8IC9UaXRsZSAoVW50aXRsZWQpIC9BdXRob3IgKHVzZXIpIC9TdWJqZWN0ICgpIC9BQVBMOktleXdvcmRzIFsgKCkgXSAvS2V5d29yZHMKKCkgL0NyZWF0b3IgKFRleHRFZGl0KSAvUHJvZHVjZXIgKE1hYyBPUyBYIDEwLjUuOCBRdWFydHogUERGQ29udGV4dCkgL0NyZWF0aW9uRGF0ZQooRDoyMDEwMDQzMDIyMDc0NlowMCcwMCcpIC9Nb2REYXRlIChEOjIwMTAwNDMwMjIwNzQ2WjAwJzAwJykgPj4KZW5kb2JqCnhyZWYKMCAxOAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDk4MTUgMDAwMDAgbiAKMDAwMDAwMDI2MCAwMDAwMCBuIAowMDAwMDAxNDcyIDAwMDAwIG4gCjAwMDAwMDAwMjIgMDAwMDAgbiAKMDAwMDAwMDI0MSAwMDAwMCBuIAowMDAwMDAwMzY0IDAwMDAwIG4gCjAwMDAwMDE0MzYgMDAwMDAgbiAKMDAwMDAwOTYzOCAwMDAwMCBuIAowMDAwMDAwNTAyIDAwMDAwIG4gCjAwMDAwMDA1NTQgMDAwMDAgbiAKMDAwMDAwMDYwOCAwMDAwMCBuIAowMDAwMDAxNDE2IDAwMDAwIG4gCjAwMDAwMDE1NTUgMDAwMDAgbiAKMDAwMDAwMTYwNSAwMDAwMCBuIAowMDAwMDA5MTY4IDAwMDAwIG4gCjAwMDAwMDkxODkgMDAwMDAgbiAKMDAwMDAwOTQzMCAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDE4IC9Sb290IDEzIDAgUiAvSW5mbyAxIDAgUiAvSUQgWyA8NTVlNmI4NjdlZWEwODdiZTRiNjViMGNiODVlZGVlNzE-Cjw1NWU2Yjg2N2VlYTA4N2JlNGI2NWIwY2I4NWVkZWU3MT4gXSA-PgpzdGFydHhyZWYKMTAwNTcKJSVFT0Y.MDCmw6cP_tq6X_gCn5M7qjDonqi2fWh07S006GFaSlUsS3Lv8xLU7ScrraCR2ZbGHz1fYQT6-ATImOHYFvrbY_cy71hVewaTGLeGgiaNnyn6Pq7l-SsW1t0Q2dG_0eMMyApZHupyX3WADV0TBOn3B-TKNMHNXrPjVhcdb98qqqo \ No newline at end of file diff --git a/tests/exampleSigned.pdf b/tests/exampleSigned.pdf index 6bcef7e43041aa8271510bb5ff8093b2e11645a5..a7526e34bc01cb1211ee54503dbdbceba0f32e74 100644 GIT binary patch literal 996 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/INspoFpj2axmxHRAnfh4V5f5AEE+I=uyy1tK1xE+O+FIem1Z2KZYQksYbtXhLZr/sj/AcNkm0=Sl5z5nMytXlmUrSS/gFFj2zaWLYoM/Kdf04EpWxMfGSSMe4YUkCTJ/NdiAy9vJy2NyWHXinUj9LTpBJ4MOMUROcQrNGAaCATwIJq2tRfV4qFTLSS/Meb4u9cfQRf19oImlS9iDD5nhUX/w7JrYPAjYWf9Ht5bU4iNcoWbXeCNV4=MIIBuzCCASSgAwIBAgIGAY/zF0AhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2lzc3Vlcl90ZXN0MB4XDTI0MDYwNzE0MjUzOFoXDTI1MDYwNzE0MjUzOFowFzEVMBMGA1UEAwwMc3ViamVjdF90ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCseUUmD8+Okuh5OrLT2LyO6QCNOIidohV7HAjIbgdpSU1C27z+JDWT3cfVbojQ5EzvZM9CDPayHrlnNK8NFD9ggE3rbOn6ATT9iC4qTQvPN3Sdel5OTaVabMuMT2satwbtl8wB98583i4bhJUyHRy7PJnXrOCscyK14GjGnuVwjQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACWKec1JiRggmTRm0aQin3SJnsvuF8JS5GlOpea45IGV2gOHws/iFPg8BAaGzQ1d+sG+RHH07xKCll8Xw1QaqLhc+96vNOCvl2cjl7BdLH/fiYurP8Vf0W3lkp5VbRFV2nWwHcOIPBUa8lNK+uV6Z5nPG5Ads12BJD5K8jAHXo2E2024-07-30T14:13:12ZeNrY+jS3YgsAJTVmDiei3G2KoB1O923Qx0hYOA6R2bRxLZHvvGdlZEsAi2ChlMPdfMdFNXP2yCZu1/7ECnkmxA==MCQwGqQYMBYxFDASBgNVBAMMC2lzc3Vlcl90ZXN0AgYBj/MXQCE=application/octet-stream \ No newline at end of file From 7ae27acd7094d76f963cfad13ddd10c103205784 Mon Sep 17 00:00:00 2001 From: Rolando2000 <61158161+Rolando2000@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:35:49 +0100 Subject: [PATCH 09/44] containers ASiC-E/S (XAdES, CAdES) --- pom.xml | 15 + .../sca/Controllers/SignaturesController.java | 65 +++- .../ec/eudi/signer/r3/sca/DSS_Service.java | 365 +++++++++++++++--- .../DocumentsSignDocRequest.java | 9 + tests/exampleSigned.cse | Bin 0 -> 11665 bytes tests/exampleSigned.json | 2 +- tests/exampleSigned.pdf | Bin 996 -> 996 bytes tests/exampleSigned.scs | Bin 0 -> 11341 bytes tests/exampleSigned.xml | Bin 2942 -> 11343 bytes 9 files changed, 398 insertions(+), 58 deletions(-) create mode 100644 tests/exampleSigned.cse create mode 100644 tests/exampleSigned.scs diff --git a/pom.xml b/pom.xml index ee31f2d..eba5621 100644 --- a/pom.xml +++ b/pom.xml @@ -86,6 +86,21 @@ validation-policy 6.0 + + eu.europa.ec.joinup.sd-dss + dss-asic-common + 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-asic-cades + 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-asic-xades + 6.0 + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 149716e..fa1b7b0 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -108,9 +108,20 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc if (document.getSignature_format().equals("C")) { System.out.print("CAdES\n"); - dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + if(document.getContainer().equals("ASiC-E")) { + dataToBeSigned = dssClient.cadesToBeSignedData_asic_E(dssDocument, document.getConformance_level(), + document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + } + else if(document.getContainer().equals("ASiC-S")) { + dataToBeSigned = dssClient.cadesToBeSignedData_asic_S(dssDocument, document.getConformance_level(), + document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + + } + else { + dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, + document.getConformance_level(), document.getSigned_envelope_property(), + this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + } } else if (document.getSignature_format().equals("P")) { @@ -122,10 +133,20 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc } else if (document.getSignature_format().equals("X")) { System.out.print("XAdES\n"); - dataToBeSigned = dssClient.xadesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); - + if(document.getContainer().equals("ASiC-E")) { + dataToBeSigned = dssClient.xadesToBeSignedData_asic_E(dssDocument, document.getConformance_level(), + document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + } + else if(document.getContainer().equals("ASiC-S")) { + dataToBeSigned = dssClient.xadesToBeSignedData_asic_S(dssDocument, document.getConformance_level(), + document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + + } + else { + dataToBeSigned = dssClient.xadesToBeSignedData(dssDocument, + document.getConformance_level(), document.getSigned_envelope_property(), + this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + } } else if (document.getSignature_format().equals("J")) { System.out.print("JAdES\n"); @@ -180,9 +201,35 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, new ArrayList<>(), document.getSignAlgo(), document.getSignature_format(), document.getConformance_level(), - document.getSigned_envelope_property()); + document.getSigned_envelope_property(), document.getContainer()); try { - if (document.getSignature_format().equals("J")) { + if (document.getContainer().equals("ASiC-E")) { + if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { + System.out.println("\nASIC-E\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); + docSigned.save("tests/exampleSigned.cse"); + + File file = new File("tests/exampleSigned.cse"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } + + } + else if (document.getContainer().equals("ASiC-S")) { + if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { + System.out.println("\nASIC-S\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); + docSigned.save("tests/exampleSigned.scs"); + + File file = new File("tests/exampleSigned.scs"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } + + } + else if (document.getSignature_format().equals("J")) { System.out.println("\nJADES SIGN\n"); docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); docSigned.save("tests/exampleSigned.json"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index b38ec0b..9717c93 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -9,9 +9,14 @@ import org.springframework.stereotype.Service; import eu.europa.esig.dss.AbstractSignatureParameters; +import eu.europa.esig.dss.asic.cades.ASiCWithCAdESSignatureParameters; +import eu.europa.esig.dss.asic.cades.signature.ASiCWithCAdESService; +import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; +import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; import eu.europa.esig.dss.cades.CAdESSignatureParameters; import eu.europa.esig.dss.cades.signature.CAdESService; import eu.europa.esig.dss.cades.signature.CMSSignedDocument; +import eu.europa.esig.dss.enumerations.ASiCContainerType; import eu.europa.esig.dss.enumerations.DigestAlgorithm; import eu.europa.esig.dss.enumerations.JWSSerializationType; import eu.europa.esig.dss.enumerations.SigDMechanism; @@ -162,6 +167,7 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); + System.out.print("2CAdES\n"); cv = new CommonCertificateVerifier(); @@ -170,6 +176,78 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc System.out.print("3CAdES\n"); return dataToSign.getBytes(); + } + @SuppressWarnings("rawtypes") + public byte[] cadesToBeSignedData_asic_E(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + + ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + + System.out.print("2CAdES\n"); + + cv = new CommonCertificateVerifier(); + ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); + ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3CAdES\n"); + return dataToSign.getBytes(); + + } + @SuppressWarnings("rawtypes") + public byte[] cadesToBeSignedData_asic_S(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + + ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); + + System.out.print("2CAdES\n"); + + cv = new CommonCertificateVerifier(); + ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); + ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3CAdES\n"); + return dataToSign.getBytes(); + } @SuppressWarnings("rawtypes") public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance_level, @@ -206,6 +284,78 @@ public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance System.out.print("3XAdES\n"); return dataToSign.getBytes(); + } + @SuppressWarnings("rawtypes") + public byte[] xadesToBeSignedData_asic_E(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + + ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + + System.out.print("2XAdES\n"); + + cv = new CommonCertificateVerifier(); + ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); + ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3XAdES\n"); + return dataToSign.getBytes(); + + } + @SuppressWarnings("rawtypes") + public byte[] xadesToBeSignedData_asic_S(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { + + CertificateVerifier cv = new CommonCertificateVerifier(); + + ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); + + System.out.print("2XAdES\n"); + + cv = new CommonCertificateVerifier(); + ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); + ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3XAdES\n"); + return dataToSign.getBytes(); + } public byte[] jadesToBeSignedData (DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, @@ -284,7 +434,8 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, X509Certificate signingCertificate, - List certificateChain, String signAlg, String sign_format, String conform_level, String envelope_props) { + List certificateChain, String signAlg, String sign_format, String conform_level, String envelope_props, + String container) { SignatureValue signatureValue = new SignatureValue(); SignatureAlgorithm aux_alg = checkSignAlg(signAlg); @@ -294,32 +445,91 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur CertificateVerifier cv = new CommonCertificateVerifier(); - if (sign_format.equals("C")) { - System.out.print("CAdES\n"); - CAdESService service = new CAdESService(cv); - CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); + if (sign_format.equals("C")) { + if(container.equals("ASiC-E")) { + System.out.print("CAdES ASiC-E\n"); + ASiCWithCAdESService service = new ASiCWithCAdESService(cv); + ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + + service = new ASiCWithCAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); } - signatureParameters.setCertificateChain(certChainToken); + else if (container.equals("ASiC-S")) { + System.out.print("CAdES ASiC-S\n"); + ASiCWithCAdESService service = new ASiCWithCAdESService(cv); + ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); + + service = new ASiCWithCAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - - service = new CAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + } + else { + System.out.print("CAdES\n"); + CAdESService service = new CAdESService(cv); + CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + + service = new CAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); + } } else if (sign_format.equals("P")) { System.out.print("PAdES\n"); @@ -349,31 +559,90 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur return service.signDocument(documentToSign, signatureParameters,signatureValue); } else if (sign_format.equals("X")) { - System.out.print("XAdES\n"); - XAdESService service = new XAdESService(cv); - XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); + if(container.equals("ASiC-E")) { + System.out.print("XAdES ASiC-E\n"); + ASiCWithXAdESService service = new ASiCWithXAdESService(cv); + ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + + service = new ASiCWithXAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); } - signatureParameters.setCertificateChain(certChainToken); + else if (container.equals("ASiC-S")) { + System.out.print("XAdES ASiC-S\n"); + ASiCWithXAdESService service = new ASiCWithXAdESService(cv); + ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); + + service = new ASiCWithXAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - - service = new XAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + } + else { + System.out.print("XAdES\n"); + XAdESService service = new XAdESService(cv); + XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); + + signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certChainToken = new ArrayList<>(); + for (X509Certificate cert : certificateChain) { + certChainToken.add(new CertificateToken(cert)); + } + signatureParameters.setCertificateChain(certChainToken); + + SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); + System.out.println("\n\n" + aux_sign_level + "\n\n"); + DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); + System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); + System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + + service = new XAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); + } } else if (sign_format.equals("J")) { System.out.print("JAdES\n"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java index 805f042..cd9b945 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java @@ -17,6 +17,7 @@ public class DocumentsSignDocRequest { private String signAlgoParams; private List signed_props; private String signed_envelope_property; + private String container = "No"; public String getDocument() { return document; @@ -73,4 +74,12 @@ public String getSigned_envelope_property() { public void setSigned_envelope_property(String signed_envelope_property) { this.signed_envelope_property = signed_envelope_property; } + + public String getContainer() { + return container; + } + + public void setContainer(String container) { + this.container = container; + } } diff --git a/tests/exampleSigned.cse b/tests/exampleSigned.cse new file mode 100644 index 0000000000000000000000000000000000000000..d6bd8c7dc2020fdfe2f6519e1572c7a7d06fc979 GIT binary patch literal 11665 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 BOVYTl%6r_JM#%z`U4DD}IG5Kf#+p} zXV|Q9e6wP`hs5!#6BSyAGg>k$8ipDju#OAS0kLXd BL~{TD diff --git a/tests/exampleSigned.scs b/tests/exampleSigned.scs new file mode 100644 index 0000000000000000000000000000000000000000..a21f5b865a5053e18094d155b40f73f8bda027f5 GIT binary patch literal 11341 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 EUmK|6-v9sr literal 2942 zcmb_eS$DEX5dAA2^XM_`QF5Z+X;2Uq5LsN_8fXy1VgN<`^#d4<$z`w%ox}}dzt=Pkq1|mZvK+=P43il3nsL(n)qiobXFc#^ znD0~6C?mw#vzzsrn>^n=V^~oXj|KIZq&`OCIF8{gR-SRQz<+2qB)8WL+%{xc5;}}c zI39r$6nb)5o3(MK_OX^P^#z`DFFbvKxFB6tgzDe8$2>m1t@=f#? z84wtOR~fX?(P=vb9X~X&TlgBoj|1WK6-qlwQuL5!u$^rn$vnM>oFfmVC4;C2nznxc zwM|^W4gw!#`9Dmhyz1do0+%Qj4ELyi^zpI&q)FpWV$+zUWV=MzZAo~Hj-xqs zSd)|WN_)*#cD~$9*D025G2ke#5&nD9tBfyP-iF{dcy*2IHak?+{u!%!$efv2KIO|W zGE-g0d_mZfXSkTI5*eGg9!?K;i=*s`x-KJnWxn#p7*pH;oY=$oL@KAnXc@%jmYo_q zZcNLnY|d3jOBE479vFo*ek1d_XVGthF)icE$fozK<7sn`U=t|{bv76bn0R9@v7$3h zS2M6(Ej)IVGqyx>b67j7558(RuM~+K^<_!o z_Gg|4y6%HQ;(#xSV1=Cp9BgEOi@g3O>XJ<-w{@&O=<@nrBr=DjV^|RtV>zC3CU}2y+OELN4P3FtWC;@E%JN zy{|a{%>;lJ6)@nrK$~#B#%IVhqd~W&zV0`SnobRJdQzV>UQwmMO*U3%1h$gxyF1+^ zc(QYDjRy;Zay-jUwi+`$AJ#I<4`%Q*O2b&0bQNL1`u>ngZ8d%ZhQS6r{bp!~nL_C< z3K`Xa7PRHPY>*ku3p)?H1;=)oE)~%BW^A5D^HXOwj+d#*XU=It(Bf8%_g1mhqyf+X zmmK9{XnJ{sYj_%_`*E%xc4sQe)$ zoo(3R-02AOg4I5qbuABHP~q#2vFy2Ek@2D!IjS_{OxO*TNo#LCt(kZxGQiCUk{b`0 zNp}lI%Y+=(viP;pZpKS1R#SW5q^i9|PTqg1)$du`mkZ20l^ga$AK#Z~ec*3&6*~}* z)FWB0Q3Iwv%1cH4!GbBZ^m6l8Rj$S3pGfk3nA>1}pt!%Mgl7a1>XSH0xA9J!!VQ9c zCa7nET)$u+fv)H7QY=k(Q*4pfu$A%t_eHzw$^S1h&Jg7L^1fexNJ&?%txm1{42%sc z8U!eyL~@dFgvyeXcEmR;6+F#Jdw!g*M~8#xhU;Ml0+Qc^^33&QS5THS^2D$A1lAey z@oRJhz3(LdLPmw|is)jIvTTo19i;&bOBQ5aE5tW#^*faU{)*dT@>1i6hGM${Y}l0TlN2?+yC4ZA3mYn=rC_w4&ELWWi$W$y>x*kmmO9;Mfz~J3oFep zAB9-r Date: Tue, 30 Jul 2024 18:07:38 +0100 Subject: [PATCH 10/44] jades detached fix --- .../ec/eudi/signer/r3/sca/DSS_Service.java | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 9717c93..92e4ad0 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -32,6 +32,8 @@ import eu.europa.esig.dss.pades.PAdESSignatureParameters; import eu.europa.esig.dss.xades.XAdESSignatureParameters; import eu.europa.esig.dss.xades.signature.XAdESService; +import eu.europa.esig.dss.jades.HTTPHeader; +import eu.europa.esig.dss.jades.HTTPHeaderDigest; import eu.europa.esig.dss.jades.JAdESSignatureParameters; import eu.europa.esig.dss.jades.signature.JAdESService; import eu.europa.esig.dss.pades.signature.ExternalCMSService; @@ -386,11 +388,28 @@ public byte[] jadesToBeSignedData (DSSDocument documentToSign, String conformanc // signatureParameters.setReason("DSS testing"); System.out.print("2JAdES\n"); - cv = new CommonCertificateVerifier(); - JAdESService cmsForXAdESGenerationService = new JAdESService(cv); - ToBeSigned dataToSign = cmsForXAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3JAdES\n"); - return dataToSign.getBytes(); + if(signed_envelope_property.equals("DETACHED")) { + System.out.println("\n\n JADES detached \n\n"); + signatureParameters.setSigDMechanism(SigDMechanism.HTTP_HEADERS); + signatureParameters.setBase64UrlEncodedPayload(false); + List documentsToSign = new ArrayList<>(); + documentsToSign.add(new HTTPHeader("content-type", "application/json")); + documentsToSign.add(new HTTPHeaderDigest(documentToSign, DigestAlgorithm.SHA256)); + + cv = new CommonCertificateVerifier(); + JAdESService cmsForJAdESGenerationService = new JAdESService(cv); + ToBeSigned dataToSign = cmsForJAdESGenerationService.getDataToSign(documentsToSign, signatureParameters); + System.out.print("3JAdES\n"); + return dataToSign.getBytes(); + } + else { + cv = new CommonCertificateVerifier(); + JAdESService cmsForJAdESGenerationService = new JAdESService(cv); + ToBeSigned dataToSign = cmsForJAdESGenerationService.getDataToSign(documentToSign, signatureParameters); + System.out.print("3JAdES\n"); + return dataToSign.getBytes(); + } + } @@ -668,9 +687,22 @@ else if (container.equals("ASiC-S")) { signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); signatureParameters.setJwsSerializationType(JWSSerializationType.COMPACT_SERIALIZATION); + + if(envelope_props.equals("DETACHED")) { + System.out.println("\n\n JADES detached \n\n"); + signatureParameters.setSigDMechanism(SigDMechanism.HTTP_HEADERS); + signatureParameters.setBase64UrlEncodedPayload(false); + List documentsToSign = new ArrayList<>(); + documentsToSign.add(new HTTPHeader("content-type", "application/json")); + documentsToSign.add(new HTTPHeaderDigest(documentToSign, DigestAlgorithm.SHA256)); + service = new JAdESService(cv); + return service.signDocument(documentsToSign, signatureParameters,signatureValue); + } + else { + service = new JAdESService(cv); + return service.signDocument(documentToSign, signatureParameters,signatureValue); + } - service = new JAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); } From d9e5f0a94aff458945e0c7f6d704a94f183fb773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:31:06 +0100 Subject: [PATCH 11/44] teste --- pom.xml | 10 +++++++ .../sca/Controllers/SignaturesController.java | 3 +- .../ec/eudi/signer/r3/sca/DSS_Service.java | 30 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index eba5621..7056f82 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,16 @@ eu.europa.ec.joinup.sd-dss dss-pades 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-service + 6.0 + + + eu.europa.ec.joinup.sd-dss + dss-cades + 6.0 eu.europa.ec.joinup.sd-dss diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index fa1b7b0..d5bbb02 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -196,12 +196,13 @@ else if(document.getContainer().equals("ASiC-S")) { DocumentsSignDocRequest document = signDocRequest.getDocuments().get(0); DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - + if (response.getSignatures() != null) { byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, new ArrayList<>(), document.getSignAlgo(), document.getSignature_format(), document.getConformance_level(), document.getSigned_envelope_property(), document.getContainer()); + System.out.println(docSigned); try { if (document.getContainer().equals("ASiC-E")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 92e4ad0..58841db 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -15,6 +15,7 @@ import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; import eu.europa.esig.dss.cades.CAdESSignatureParameters; import eu.europa.esig.dss.cades.signature.CAdESService; +import eu.europa.esig.dss.cades.signature.CAdESTimestampParameters; import eu.europa.esig.dss.cades.signature.CMSSignedDocument; import eu.europa.esig.dss.enumerations.ASiCContainerType; import eu.europa.esig.dss.enumerations.DigestAlgorithm; @@ -25,11 +26,13 @@ import eu.europa.esig.dss.enumerations.SignaturePackaging; import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.model.DSSMessageDigest; +import eu.europa.esig.dss.model.DigestDocument; import eu.europa.esig.dss.model.InMemoryDocument; import eu.europa.esig.dss.model.SignatureValue; import eu.europa.esig.dss.model.ToBeSigned; import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.pades.PAdESSignatureParameters; +import eu.europa.esig.dss.pades.PAdESTimestampParameters; import eu.europa.esig.dss.xades.XAdESSignatureParameters; import eu.europa.esig.dss.xades.signature.XAdESService; import eu.europa.esig.dss.jades.HTTPHeader; @@ -38,6 +41,8 @@ import eu.europa.esig.dss.jades.signature.JAdESService; import eu.europa.esig.dss.pades.signature.ExternalCMSService; import eu.europa.esig.dss.pades.signature.PAdESService; +import eu.europa.esig.dss.service.http.commons.TimestampDataLoader; +import eu.europa.esig.dss.service.tsp.OnlineTSPSource; import eu.europa.esig.dss.signature.DocumentSignatureService; import eu.europa.esig.dss.validation.CertificateVerifier; import eu.europa.esig.dss.validation.CommonCertificateVerifier; @@ -158,6 +163,7 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } + signatureParameters.setCertificateChain(certChainToken); SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); @@ -166,14 +172,21 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_T); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.setGenerateTBSWithoutCertificate(true); + System.out.print("2CAdES\n"); cv = new CommonCertificateVerifier(); CAdESService cmsForCAdESGenerationService = new CAdESService(cv); + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; + OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); + onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type + cmsForCAdESGenerationService.setTspSource(onlineTSPSource); + ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3CAdES\n"); return dataToSign.getBytes(); @@ -542,12 +555,23 @@ else if (container.equals("ASiC-S")) { SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_T); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); + signatureParameters.setGenerateTBSWithoutCertificate(true); service = new CAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + System.out.println("teste"); + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; + OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); + onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type + service.setTspSource(onlineTSPSource); + + DSSDocument signed_document= service.signDocument(documentToSign, signatureParameters,signatureValue); + System.out.println("teste2"); + + + return signed_document; } } else if (sign_format.equals("P")) { From b316b30ef61816255262b60da65842c51d4e0f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:26:19 +0100 Subject: [PATCH 12/44] teste --- logback.xml | 10 ++++++++++ .../sca/Controllers/SignaturesController.java | 6 ++++-- .../ec/eudi/signer/r3/sca/DSS_Service.java | 20 +++++++++++++------ .../ec/eudi/signer/r3/sca/ScaApplication.java | 6 ++++++ src/main/resources/application.properties | 5 ++++- 5 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 logback.xml diff --git a/logback.xml b/logback.xml new file mode 100644 index 0000000..335c13d --- /dev/null +++ b/logback.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index d5bbb02..cebd5ec 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -7,6 +7,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; +import java.util.Date; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -102,6 +103,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc // if signature_format == J => signed_envelope_property = Attached List allResponses = new ArrayList<>(); + Date date = new Date(); for (DocumentsSignDocRequest document : signDocRequest.getDocuments()) { DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; @@ -120,7 +122,7 @@ else if(document.getContainer().equals("ASiC-S")) { else { dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + this.signingCertificate, new ArrayList<>(), document.getSignAlgo(), date); } } else if (document.getSignature_format().equals("P")) { @@ -201,7 +203,7 @@ else if(document.getContainer().equals("ASiC-S")) { byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, new ArrayList<>(), document.getSignAlgo(), document.getSignature_format(), document.getConformance_level(), - document.getSigned_envelope_property(), document.getContainer()); + document.getSigned_envelope_property(), document.getContainer(), date); System.out.println(docSigned); try { if (document.getContainer().equals("ASiC-E")) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 58841db..5b611f4 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -152,12 +152,13 @@ public void test() { @SuppressWarnings("rawtypes") public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + List certificateChain, String signAlg, Date date) { CertificateVerifier cv = new CommonCertificateVerifier(); CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.bLevel().setSigningDate(date); + System.out.println(new Date()); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { @@ -175,8 +176,11 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_T); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); + CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(DigestAlgorithm.SHA256); + signatureParameters.setSignatureTimestampParameters(timestampParameters); + signatureParameters.setArchiveTimestampParameters(timestampParameters); + signatureParameters.setContentTimestampParameters(timestampParameters); - signatureParameters.setGenerateTBSWithoutCertificate(true); System.out.print("2CAdES\n"); @@ -467,7 +471,7 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, X509Certificate signingCertificate, List certificateChain, String signAlg, String sign_format, String conform_level, String envelope_props, - String container) { + String container, Date date) { SignatureValue signatureValue = new SignatureValue(); SignatureAlgorithm aux_alg = checkSignAlg(signAlg); @@ -540,7 +544,7 @@ else if (container.equals("ASiC-S")) { CAdESService service = new CAdESService(cv); CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.bLevel().setSigningDate(date); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { @@ -558,7 +562,11 @@ else if (container.equals("ASiC-S")) { signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_T); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.setGenerateTBSWithoutCertificate(true); + CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(DigestAlgorithm.SHA256); + signatureParameters.setSignatureTimestampParameters(timestampParameters); + signatureParameters.setArchiveTimestampParameters(timestampParameters); + signatureParameters.setContentTimestampParameters(timestampParameters); + service = new CAdESService(cv); System.out.println("teste"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java index e0f17fb..4d289d2 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java @@ -1,12 +1,18 @@ package eu.europa.ec.eudi.signer.r3.sca; +import java.util.logging.Level; +import java.util.logging.Logger; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ScaApplication { + public static void main(String[] args) { + Logger libraryLogger = Logger.getLogger("eu.europa.esig"); + libraryLogger.setLevel(Level.FINE); SpringApplication.run(ScaApplication.class, args); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ac01647..92e1895 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,5 @@ spring.application.name=sca -server.port = 8082 \ No newline at end of file +server.port = 8082 +logging.level.org.springframework.web=DEBUG +logging.level.org.hibernate=ERROR +logging.config=logback.xml \ No newline at end of file From 2793c4f68463f828a92e88e0f6facb879d7364a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:49:05 +0100 Subject: [PATCH 13/44] cades and xades with baseline T --- .../sca/Controllers/SignaturesController.java | 2 +- .../ec/eudi/signer/r3/sca/DSS_Service.java | 43 ++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index cebd5ec..8f6c4c5 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -147,7 +147,7 @@ else if(document.getContainer().equals("ASiC-S")) { else { dataToBeSigned = dssClient.xadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); + this.signingCertificate, new ArrayList<>(), document.getSignAlgo(), date); } } else if (document.getSignature_format().equals("J")) { System.out.print("JAdES\n"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 5b611f4..1c80c1b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -34,6 +34,7 @@ import eu.europa.esig.dss.pades.PAdESSignatureParameters; import eu.europa.esig.dss.pades.PAdESTimestampParameters; import eu.europa.esig.dss.xades.XAdESSignatureParameters; +import eu.europa.esig.dss.xades.XAdESTimestampParameters; import eu.europa.esig.dss.xades.signature.XAdESService; import eu.europa.esig.dss.jades.HTTPHeader; import eu.europa.esig.dss.jades.HTTPHeaderDigest; @@ -173,10 +174,10 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_T); + signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(DigestAlgorithm.SHA256); + CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(aux_digest_alg); signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); @@ -271,12 +272,12 @@ public byte[] cadesToBeSignedData_asic_S(DSSDocument documentToSign, String conf @SuppressWarnings("rawtypes") public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + List certificateChain, String signAlg, Date date) { CertificateVerifier cv = new CommonCertificateVerifier(); XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.bLevel().setSigningDate(date); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { @@ -291,14 +292,25 @@ public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n SIGN PACK: " + aux_sign_pack + "\n\n\n"); - signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B); - signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA256); - signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED); + signatureParameters.setSignatureLevel(aux_sign_level); + signatureParameters.setDigestAlgorithm(aux_digest_alg); + signatureParameters.setSignaturePackaging(aux_sign_pack); + XAdESTimestampParameters timestampParameters= new XAdESTimestampParameters(aux_digest_alg); + signatureParameters.setSignatureTimestampParameters(timestampParameters); + signatureParameters.setArchiveTimestampParameters(timestampParameters); + signatureParameters.setContentTimestampParameters(timestampParameters); + + // signatureParameters.setReason("DSS testing"); System.out.print("2XAdES\n"); cv = new CommonCertificateVerifier(); XAdESService cmsForXAdESGenerationService = new XAdESService(cv); + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; + OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); + onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type + cmsForXAdESGenerationService.setTspSource(onlineTSPSource); + ToBeSigned dataToSign = cmsForXAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3XAdES\n"); return dataToSign.getBytes(); @@ -559,10 +571,10 @@ else if (container.equals("ASiC-S")) { SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - signatureParameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_T); + signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(DigestAlgorithm.SHA256); + CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(aux_digest_alg); signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); @@ -672,7 +684,7 @@ else if (container.equals("ASiC-S")) { XAdESService service = new XAdESService(cv); XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.bLevel().setSigningDate(date); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { @@ -690,8 +702,19 @@ else if (container.equals("ASiC-S")) { signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); + XAdESTimestampParameters timestampParameters= new XAdESTimestampParameters(aux_digest_alg); + signatureParameters.setSignatureTimestampParameters(timestampParameters); + signatureParameters.setArchiveTimestampParameters(timestampParameters); + signatureParameters.setContentTimestampParameters(timestampParameters); + service = new XAdESService(cv); + System.out.println("teste"); + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; + OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); + onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type + service.setTspSource(onlineTSPSource); + return service.signDocument(documentToSign, signatureParameters,signatureValue); } From 25aa692b4c6719b650143be25251ee17be4a06c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:45:27 +0100 Subject: [PATCH 14/44] testing LT signature in Cades --- .../ec/eudi/signer/r3/sca/DSS_Service.java | 102 +++++++++++++++++- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 1c80c1b..3ca769d 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -6,9 +6,12 @@ import java.util.Date; import java.util.List; +import org.slf4j.event.Level; import org.springframework.stereotype.Service; import eu.europa.esig.dss.AbstractSignatureParameters; +import eu.europa.esig.dss.alert.ExceptionOnStatusAlert; +import eu.europa.esig.dss.alert.LogOnStatusAlert; import eu.europa.esig.dss.asic.cades.ASiCWithCAdESSignatureParameters; import eu.europa.esig.dss.asic.cades.signature.ASiCWithCAdESService; import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; @@ -42,11 +45,20 @@ import eu.europa.esig.dss.jades.signature.JAdESService; import eu.europa.esig.dss.pades.signature.ExternalCMSService; import eu.europa.esig.dss.pades.signature.PAdESService; +import eu.europa.esig.dss.service.SecureRandomNonceSource; +import eu.europa.esig.dss.service.crl.OnlineCRLSource; +import eu.europa.esig.dss.service.http.commons.CommonsDataLoader; +import eu.europa.esig.dss.service.http.commons.OCSPDataLoader; import eu.europa.esig.dss.service.http.commons.TimestampDataLoader; +import eu.europa.esig.dss.service.ocsp.OnlineOCSPSource; import eu.europa.esig.dss.service.tsp.OnlineTSPSource; import eu.europa.esig.dss.signature.DocumentSignatureService; +import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; +import eu.europa.esig.dss.spi.x509.aia.DefaultAIASource; import eu.europa.esig.dss.validation.CertificateVerifier; import eu.europa.esig.dss.validation.CommonCertificateVerifier; +import eu.europa.esig.dss.validation.OCSPFirstRevocationDataLoadingStrategyFactory; +import eu.europa.esig.dss.validation.RevocationDataVerifier; @Service public class DSS_Service { @@ -161,10 +173,16 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc signatureParameters.bLevel().setSigningDate(date); System.out.println(new Date()); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + // certificateChain.add(signingCertificate); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } + + CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); + for (CertificateToken certificateToken : certChainToken) { + certificateSource.addCertificate(certificateToken); + } signatureParameters.setCertificateChain(certChainToken); SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); @@ -181,11 +199,47 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); + signatureParameters.setGenerateTBSWithoutCertificate(true); System.out.print("2CAdES\n"); - cv = new CommonCertificateVerifier(); + cv.setTrustedCertSources(certificateSource); + cv.setRevocationDataVerifier(RevocationDataVerifier.createDefaultRevocationDataVerifier()); + cv.setCheckRevocationForUntrustedChains(true); + // Capability to download resources from AIA + cv.setAIASource(new DefaultAIASource()); + + OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); + onlineCRLSource.setDataLoader(new CommonsDataLoader()); + cv.setCrlSource(onlineCRLSource); + + OnlineOCSPSource onlineOCSPSource = new OnlineOCSPSource(); + onlineOCSPSource.setDataLoader(new OCSPDataLoader()); + onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); + cv.setOcspSource(onlineOCSPSource); + + cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); + cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); + + cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); + + cv.setAlertOnRevokedCertificate(new ExceptionOnStatusAlert()); + + cv.setAlertOnInvalidTimestamp(new ExceptionOnStatusAlert()); + + cv.setAlertOnNoRevocationAfterBestSignatureTime(new LogOnStatusAlert(Level.ERROR)); + + cv.setAlertOnExpiredSignature(new ExceptionOnStatusAlert()); + + cv.setRevocationDataLoadingStrategyFactory(new OCSPFirstRevocationDataLoadingStrategyFactory()); + + RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); + cv.setRevocationDataVerifier(revocationDataVerifier); + + cv.setRevocationFallback(false); + + CAdESService cmsForCAdESGenerationService = new CAdESService(cv); String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); @@ -553,15 +607,20 @@ else if (container.equals("ASiC-S")) { } else { System.out.print("CAdES\n"); - CAdESService service = new CAdESService(cv); + CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(date); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); + // certificateChain.add(signingCertificate); for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } + CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); + for (CertificateToken certificateToken : certChainToken) { + certificateSource.addCertificate(certificateToken); + } signatureParameters.setCertificateChain(certChainToken); SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); @@ -578,9 +637,44 @@ else if (container.equals("ASiC-S")) { signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); - + signatureParameters.setGenerateTBSWithoutCertificate(true); + + cv.setTrustedCertSources(certificateSource); + cv.setRevocationDataVerifier(RevocationDataVerifier.createDefaultRevocationDataVerifier()); + cv.setCheckRevocationForUntrustedChains(false); + // Capability to download resources from AIA + cv.setAIASource(new DefaultAIASource()); + + OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); + onlineCRLSource.setDataLoader(new CommonsDataLoader()); + cv.setCrlSource(onlineCRLSource); + + OnlineOCSPSource onlineOCSPSource = new OnlineOCSPSource(); + onlineOCSPSource.setDataLoader(new OCSPDataLoader()); + onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); + cv.setOcspSource(onlineOCSPSource); + + cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); + cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); + + cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); + + cv.setAlertOnRevokedCertificate(new ExceptionOnStatusAlert()); + + cv.setAlertOnInvalidTimestamp(new ExceptionOnStatusAlert()); + + cv.setAlertOnNoRevocationAfterBestSignatureTime(new LogOnStatusAlert(Level.ERROR)); + + cv.setAlertOnExpiredSignature(new ExceptionOnStatusAlert()); + + cv.setRevocationDataLoadingStrategyFactory(new OCSPFirstRevocationDataLoadingStrategyFactory()); + + RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); + cv.setRevocationDataVerifier(revocationDataVerifier); + + cv.setRevocationFallback(false); - service = new CAdESService(cv); + CAdESService service = new CAdESService(cv); System.out.println("teste"); String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); From 0022cbd1118f7b2062f458a00eeff3df65ce3181 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Fri, 30 Aug 2024 09:34:21 +0100 Subject: [PATCH 15/44] initial changes to support credential authorization with oauth2 --- pom.xml | 13 ++ .../r3/sca/Controllers/OAuth2Controller.java | 170 ++++++++++++++++ .../sca/Controllers/SignaturesController.java | 5 +- .../r3/sca/DTO/AuthRequestTemporary.java | 23 +++ .../r3/sca/DTO/OAuth2AuthorizeRequest.java | 190 ++++++++++++++++++ .../ec/eudi/signer/r3/sca/QtspClient.java | 64 +++++- 6 files changed, 459 insertions(+), 6 deletions(-) create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthRequestTemporary.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/OAuth2AuthorizeRequest.java diff --git a/pom.xml b/pom.xml index d5ce525..f75e354 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,18 @@ 20231013 + + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + + + org.apache.httpcomponents + httpclient + 4.5.13 + + jakarta.validation jakarta.validation-api @@ -76,6 +88,7 @@ validation-policy 6.0 + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java index a026365..cd025e8 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java @@ -1,5 +1,175 @@ package eu.europa.ec.eudi.signer.r3.sca.Controllers; +import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthRequestTemporary; +import eu.europa.ec.eudi.signer.r3.sca.DTO.OAuth2AuthorizeRequest; +import eu.europa.ec.eudi.signer.r3.sca.QtspClient; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.http.Header; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Base64; + +@RestController +@RequestMapping(value = "/credential") public class OAuth2Controller { + private final QtspClient qtspClient; + + public OAuth2Controller(@Autowired QtspClient qtspClient){ + this.qtspClient = qtspClient; + } + + private String generateNonce(String root) throws Exception{ + SecureRandom prng = new SecureRandom(); + String randomNum = String.valueOf(prng.nextInt()); + System.out.println("Code_Verifier: "+ root); + MessageDigest sha = MessageDigest.getInstance("SHA-256"); + byte[] result = sha.digest(root.getBytes()); + String code_challenge = Base64.getUrlEncoder().encodeToString(result); + System.out.println("Code_Challenge: "+code_challenge); + return code_challenge; + } + + @GetMapping(value = "/authorize") + public void credential_authorization() throws Exception{ + System.out.println("Authorize: -----------------------------"); + String qtspUrl = "http://localhost:9000"; + + // calculate hash + + // given some credential + + // generate code_challenge, code_challenge_method, code_verifier + String code_challenge = generateNonce("root"); + + OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); + authorizeRequest.setClient_id("sca-client"); + authorizeRequest.setRedirect_uri("http://localhost:8082/credential/oauth/login/code"); + authorizeRequest.setScope("credential"); + authorizeRequest.setCode_challenge(code_challenge); + authorizeRequest.setCode_challenge_method("S256"); + authorizeRequest.setLang("pt-PT"); + authorizeRequest.setState("12345678"); + authorizeRequest.setCredentialID(URLEncoder.encode("cred1", StandardCharsets.UTF_8)); + authorizeRequest.setNumSignatures(Integer.toString(1)); + authorizeRequest.setHashes("some_document_hash"); + authorizeRequest.setHashAlgorithmOID("2.16.840.1.101.3.4.2.1"); + + this.qtspClient.requestOAuth2Authorize(qtspUrl, authorizeRequest); + System.out.println("-----------------------------"); + } + + @GetMapping("/temporary") + public void temporary(@RequestBody AuthRequestTemporary authRequest) throws Exception{ + System.out.println("Temporary: -----------------------------"); + + System.out.println("URL: " + authRequest.getUrl()); + System.out.println("Cookie: " + authRequest.getCookie()); + + String location_redirect = null; + String new_session_id = null; + + // Get localhost:9000 after auth + try(CloseableHttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling().build()) { + + HttpGet followRequest = new HttpGet(authRequest.getUrl()); + followRequest.setHeader("Cookie", authRequest.getCookie()); + HttpResponse followResponse = httpClient.execute(followRequest); + + if(followResponse.getStatusLine().getStatusCode() == 302) { + location_redirect = followResponse.getLastHeader("Location").getValue(); + System.out.println("Location: "+location_redirect); + new_session_id = followResponse.getLastHeader("Set-Cookie").getElements()[0].toString(); + System.out.println("Cookie: "+new_session_id); + } + } + + if ( location_redirect==null || new_session_id == null ) + return; + + System.out.println("-----------------------------"); + + // Get /oauth2/authorize after oid4vp + try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { + HttpGet followRequest = new HttpGet(location_redirect); + followRequest.setHeader("Cookie", new_session_id); + HttpResponse followResponse = httpClient2.execute(followRequest); + // System.out.println(followResponse.getStatusLine().getStatusCode()); + } + } + + + private static String getBasicAuthenticationHeader(String username, String password) { + String valueToEncode = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); + } + + @GetMapping(value="/oauth/login/code") + public void credential_authorization_code(HttpServletRequest request) throws Exception{ + System.out.println("Login Code: -----------------------------"); + + String code_verifier = "root"; + + if(request.getParameter("code") != null){ + String code = request.getParameter("code"); + System.out.println("Code: "+code); + + String url = "http://localhost:9000/oauth2/token?" + + "grant_type=authorization_code&" + + "code=" + code + "&" + + "client_id=sca-client&"+ + "redirect_uri=http%3A%2F%2Flocalhost%3A8082%2Fcredential%2Foauth%2Flogin%2Fcode&" + + "code_verifier="+code_verifier; + System.out.println("Url: "+url); + + String authorizationHeader = getBasicAuthenticationHeader("sca-client", "secret"); + System.out.println("Authorization Header: "+authorizationHeader); + String new_session_id = request.getHeader("Set-Cookie"); + + try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { + HttpPost followRequest = new HttpPost(url); + followRequest.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); + followRequest.setHeader("Cookie", new_session_id); + + System.out.println(followRequest.getHeaders(HttpHeaders.AUTHORIZATION)[0].getValue()); + HttpResponse followResponse = httpClient2.execute(followRequest); + + System.out.println(followResponse.getStatusLine().getStatusCode()); + + for(Header h: followResponse.getAllHeaders()){ + System.out.println(h.getName()+": "+h.getValue()); + } + + InputStream is = followResponse.getEntity().getContent(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + is.close(); + String responseString = sb.toString(); + + JSONObject json = new JSONObject(responseString); + System.out.println(json); + } + + } + + } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index aa3a1cb..69b91d3 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -28,7 +28,6 @@ import jakarta.validation.Valid; import java.io.File; -import java.io.IOException; @RestController @RequestMapping(value = "/signatures") @@ -105,12 +104,12 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc for (DocumentsSignDocRequest document : signDocRequest.getDocuments()) { DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; - if (document.getSignature_format().equals("C")) { + /*if (document.getSignature_format().equals("C")) { dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>()); System.out.println("Not Supported by current version"); - } else if (document.getSignature_format().equals("P")) { + } else*/ if (document.getSignature_format().equals("P")) { System.out.print("PAdES\n"); dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthRequestTemporary.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthRequestTemporary.java new file mode 100644 index 0000000..fdf138a --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthRequestTemporary.java @@ -0,0 +1,23 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +public class AuthRequestTemporary { + private String url; + private String cookie; + + // Getters e Setters + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getCookie() { + return cookie; + } + + public void setCookie(String cookie) { + this.cookie = cookie; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/OAuth2AuthorizeRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/OAuth2AuthorizeRequest.java new file mode 100644 index 0000000..769347a --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/OAuth2AuthorizeRequest.java @@ -0,0 +1,190 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +import jakarta.validation.constraints.NotBlank; + +public class OAuth2AuthorizeRequest { + @NotBlank + private String client_id; + private String redirect_uri; + private String scope; + private String authorization_details; + @NotBlank + private String code_challenge; + private String code_challenge_method = "plain"; + private String state; + private String request_uri; + + private String lang; + + private String credentialID; + private String signatureQualifier; + private String numSignatures; + private String hashes; + private String hashAlgorithmOID; + + private String description; + private String account_token; + private String clientData; + + public String getClient_id() { + return client_id; + } + + public void setClient_id(String client_id) { + this.client_id = client_id; + } + + public String getRedirect_uri() { + return redirect_uri; + } + + public void setRedirect_uri(String redirect_uri) { + this.redirect_uri = redirect_uri; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getAuthorization_details() { + return authorization_details; + } + + public void setAuthorization_details(String authorization_details) { + this.authorization_details = authorization_details; + } + + public String getCode_challenge() { + return code_challenge; + } + + public void setCode_challenge(String code_challenge) { + this.code_challenge = code_challenge; + } + + public String getCode_challenge_method() { + return code_challenge_method; + } + + public void setCode_challenge_method(String code_challenge_method) { + this.code_challenge_method = code_challenge_method; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getRequest_uri() { + return request_uri; + } + + public void setRequest_uri(String request_uri) { + this.request_uri = request_uri; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + public String getCredentialID() { + return credentialID; + } + + public void setCredentialID(String credentialID) { + this.credentialID = credentialID; + } + + public String getSignatureQualifier() { + return signatureQualifier; + } + + public void setSignatureQualifier(String signatureQualifier) { + this.signatureQualifier = signatureQualifier; + } + + public String getNumSignatures() { + return numSignatures; + } + + public void setNumSignatures(String numSignatures) { + this.numSignatures = numSignatures; + } + + public String getHashes() { + return hashes; + } + + public void setHashes(String hashes) { + this.hashes = hashes; + } + + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getAccount_token() { + return account_token; + } + + public void setAccount_token(String account_token) { + this.account_token = account_token; + } + + public String getClientData() { + return clientData; + } + + public void setClientData(String clientData) { + this.clientData = clientData; + } + + @Override + public String toString() { + return "OAuth2AuthorizeRequestDTO{" + + "client_id='" + client_id + '\'' + + ", redirect_uri='" + redirect_uri + '\'' + + ", scope='" + scope + '\'' + + ", authorization_details='" + authorization_details + '\'' + + ", code_challenge='" + code_challenge + '\'' + + ", code_challenge_method='" + code_challenge_method + '\'' + + ", state='" + state + '\'' + + ", request_uri='" + request_uri + '\'' + + ", lang='" + lang + '\'' + + ", credentialID='" + credentialID + '\'' + + ", signatureQualifier='" + signatureQualifier + '\'' + + ", numSignatures='" + numSignatures + '\'' + + ", hashes='" + hashes + '\'' + + ", hashAlgorithmOID='" + hashAlgorithmOID + '\'' + + ", description='" + description + '\'' + + ", account_token='" + account_token + '\'' + + ", clientData='" + clientData + '\'' + + '}'; + } + + + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java index 041d3fa..6182521 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java @@ -1,15 +1,22 @@ package eu.europa.ec.eudi.signer.r3.sca; +import eu.europa.ec.eudi.signer.r3.sca.DTO.OAuth2AuthorizeRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; +import java.util.Optional; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; - -import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; -import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; +import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; + @Service public class QtspClient { @@ -94,4 +101,55 @@ public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHash return signHashResponse.block(); } + + public void requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest) + throws Exception { + + try(CloseableHttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling().build()) { + UriComponentsBuilder uriBuilder = UriComponentsBuilder + .fromUriString(url) + .pathSegment("oauth2") + .pathSegment("authorize"); + + uriBuilder + .queryParam("response_type", "code") + .queryParam("client_id", authorizeRequest.getClient_id()) + .queryParamIfPresent("redirect_uri", Optional.ofNullable(authorizeRequest.getRedirect_uri())) + .queryParamIfPresent("scope", Optional.ofNullable(authorizeRequest.getScope())) + .queryParam("code_challenge", Optional.ofNullable(authorizeRequest.getCode_challenge())) + .queryParamIfPresent("code_challenge_method", Optional.ofNullable(authorizeRequest.getCode_challenge_method())) + .queryParamIfPresent("state", Optional.ofNullable(authorizeRequest.getState())) + .queryParamIfPresent("lang", Optional.ofNullable(authorizeRequest.getLang())) + .queryParamIfPresent("description", Optional.ofNullable(authorizeRequest.getDescription())) + .queryParamIfPresent("account_token", Optional.ofNullable(authorizeRequest.getAccount_token())) + .queryParamIfPresent("clientData", Optional.ofNullable(authorizeRequest.getClientData())) + .queryParamIfPresent("authorization_details", Optional.ofNullable(authorizeRequest.getAuthorization_details())) + .queryParamIfPresent("credentialID", Optional.ofNullable(authorizeRequest.getCredentialID())) + .queryParamIfPresent("signatureQualifier", Optional.ofNullable(authorizeRequest.getSignatureQualifier())) + .queryParamIfPresent("numSignatures", Optional.ofNullable(authorizeRequest.getNumSignatures())) + .queryParamIfPresent("hashes", Optional.ofNullable(authorizeRequest.getHashes())) + .queryParamIfPresent("hashAlgorithmOID", Optional.ofNullable(authorizeRequest.getHashAlgorithmOID())); + + String uri = uriBuilder.build().toString(); + System.out.println(uri); + HttpGet request = new HttpGet(uri); + HttpResponse response = httpClient.execute(request); + System.out.println(response.getStatusLine().getStatusCode()); + + if(response.getStatusLine().getStatusCode() == 302) { + String location = response.getLastHeader("Location").getValue(); + System.out.println("Location: " + location); + String cookie = response.getLastHeader("Set-Cookie").getValue(); + System.out.println("Cookie: " + cookie); + } + } + } + + + + + + + + } From 07e965a03e5763299dc15f3f0f7dedb79da798c5 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Mon, 2 Sep 2024 09:58:29 +0100 Subject: [PATCH 16/44] Updates to support initial tests with the wallet tester --- .../r3/sca/Controllers/OAuth2Controller.java | 22 ++++++++------- .../r3/sca/DTO/AuthResponseTemporary.java | 27 +++++++++++++++++++ .../ec/eudi/signer/r3/sca/QtspClient.java | 6 ++++- 3 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthResponseTemporary.java diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java index cd025e8..8d5e3b4 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java @@ -1,6 +1,7 @@ package eu.europa.ec.eudi.signer.r3.sca.Controllers; import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthRequestTemporary; +import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthResponseTemporary; import eu.europa.ec.eudi.signer.r3.sca.DTO.OAuth2AuthorizeRequest; import eu.europa.ec.eudi.signer.r3.sca.QtspClient; import jakarta.servlet.http.HttpServletRequest; @@ -45,8 +46,8 @@ private String generateNonce(String root) throws Exception{ return code_challenge; } - @GetMapping(value = "/authorize") - public void credential_authorization() throws Exception{ + @GetMapping(value = "/authorize", produces = "application/json") + public AuthResponseTemporary credential_authorization() throws Exception{ System.out.println("Authorize: -----------------------------"); String qtspUrl = "http://localhost:9000"; @@ -70,11 +71,12 @@ public void credential_authorization() throws Exception{ authorizeRequest.setHashes("some_document_hash"); authorizeRequest.setHashAlgorithmOID("2.16.840.1.101.3.4.2.1"); - this.qtspClient.requestOAuth2Authorize(qtspUrl, authorizeRequest); + AuthResponseTemporary responseTemporary = this.qtspClient.requestOAuth2Authorize(qtspUrl, authorizeRequest); System.out.println("-----------------------------"); + return responseTemporary; } - @GetMapping("/temporary") + /*@GetMapping("/temporary") public void temporary(@RequestBody AuthRequestTemporary authRequest) throws Exception{ System.out.println("Temporary: -----------------------------"); @@ -111,16 +113,15 @@ public void temporary(@RequestBody AuthRequestTemporary authRequest) throws Exce HttpResponse followResponse = httpClient2.execute(followRequest); // System.out.println(followResponse.getStatusLine().getStatusCode()); } - } - + }*/ private static String getBasicAuthenticationHeader(String username, String password) { String valueToEncode = username + ":" + password; return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); } - @GetMapping(value="/oauth/login/code") - public void credential_authorization_code(HttpServletRequest request) throws Exception{ + @GetMapping(value="/oauth/login/code", produces="application/json") + public String credential_authorization_code(HttpServletRequest request) throws Exception{ System.out.println("Login Code: -----------------------------"); String code_verifier = "root"; @@ -167,9 +168,10 @@ public void credential_authorization_code(HttpServletRequest request) throws Exc JSONObject json = new JSONObject(responseString); System.out.println(json); - } + return responseString; + } } - + return null; } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthResponseTemporary.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthResponseTemporary.java new file mode 100644 index 0000000..d96e5a9 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/AuthResponseTemporary.java @@ -0,0 +1,27 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +public class AuthResponseTemporary { + private String location_wallet; + private String session_cookie; + + public AuthResponseTemporary(String location, String cookie){ + this.location_wallet = location; + this.session_cookie = cookie; + } + + public String getLocation_wallet() { + return location_wallet; + } + + public void setLocation_wallet(String location_wallet) { + this.location_wallet = location_wallet; + } + + public String getSession_cookie() { + return session_cookie; + } + + public void setSession_cookie(String session_cookie) { + this.session_cookie = session_cookie; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java index 6182521..5ae0f04 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java @@ -1,5 +1,6 @@ package eu.europa.ec.eudi.signer.r3.sca; +import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthResponseTemporary; import eu.europa.ec.eudi.signer.r3.sca.DTO.OAuth2AuthorizeRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; @@ -102,7 +103,7 @@ public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHash return signHashResponse.block(); } - public void requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest) + public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest) throws Exception { try(CloseableHttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling().build()) { @@ -141,7 +142,10 @@ public void requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeR System.out.println("Location: " + location); String cookie = response.getLastHeader("Set-Cookie").getValue(); System.out.println("Cookie: " + cookie); + return new AuthResponseTemporary(location, cookie); } + + return null; } } From 388d21f3a9dd17fb5397c280446a80eee31f46f7 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Thu, 5 Sep 2024 10:25:36 +0100 Subject: [PATCH 17/44] updates to support the credential scope requests --- .../r3/sca/Controllers/OAuth2Controller.java | 39 +++++--- .../sca/Controllers/SignaturesController.java | 37 ++++--- .../ec/eudi/signer/r3/sca/DSS_Service.java | 2 + .../DTO/CredentialAuthorizationRequest.java | 95 ++++++++++++++++++ .../CredentialsInfo/CredentialsInfoAuth.java | 42 ++++++++ .../CredentialsInfo/CredentialsInfoCert.java | 89 +++++++++++++++++ .../CredentialsInfo/CredentialsInfoKey.java | 60 ++++++++++++ .../CredentialsInfoRequest.java | 71 ++++++++++++++ .../CredentialsInfoResponse.java | 98 +++++++++++++++++++ .../ec/eudi/signer/r3/sca/QtspClient.java | 40 +++++++- .../r3/sca/model/CredentialsService.java | 57 +++++++++++ .../signer/r3/sca/model/SignatureService.java | 38 +++++++ 12 files changed, 634 insertions(+), 34 deletions(-) create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoResponse.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java index 8d5e3b4..f96b0bd 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java @@ -2,8 +2,11 @@ import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthRequestTemporary; import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthResponseTemporary; +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialAuthorizationRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.OAuth2AuthorizeRequest; import eu.europa.ec.eudi.signer.r3.sca.QtspClient; +import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; +import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; import jakarta.servlet.http.HttpServletRequest; import org.apache.http.Header; import org.apache.http.HttpHeaders; @@ -23,16 +26,24 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; +import java.security.cert.X509Certificate; import java.util.Base64; +import java.util.List; @RestController @RequestMapping(value = "/credential") public class OAuth2Controller { private final QtspClient qtspClient; + private final CredentialsService credentialsService; + private final SignatureService signatureService; - public OAuth2Controller(@Autowired QtspClient qtspClient){ + public OAuth2Controller(@Autowired QtspClient qtspClient, + @Autowired CredentialsService credentialsService, + @Autowired SignatureService signatureService){ this.qtspClient = qtspClient; + this.credentialsService = credentialsService; + this.signatureService = signatureService; } private String generateNonce(String root) throws Exception{ @@ -47,13 +58,19 @@ private String generateNonce(String root) throws Exception{ } @GetMapping(value = "/authorize", produces = "application/json") - public AuthResponseTemporary credential_authorization() throws Exception{ + public AuthResponseTemporary credential_authorization( + @RequestBody CredentialAuthorizationRequest credentialAuthorization, + @RequestHeader (name="Authorization") String authorizationBearerHeader) throws Exception{ System.out.println("Authorize: -----------------------------"); - String qtspUrl = "http://localhost:9000"; + System.out.println(credentialAuthorization.toString()); - // calculate hash + System.out.println("authorization: "+authorizationBearerHeader); + List certificateList = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); - // given some credential + // calculate hash + List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateList.get(0), certificateList.subList(1, certificateList.size()), credentialAuthorization.getHashAlgorithmOID()); + String hash = String.join(";", hashes); + System.out.println("hash: "+hash); // generate code_challenge, code_challenge_method, code_verifier String code_challenge = generateNonce("root"); @@ -66,12 +83,12 @@ public AuthResponseTemporary credential_authorization() throws Exception{ authorizeRequest.setCode_challenge_method("S256"); authorizeRequest.setLang("pt-PT"); authorizeRequest.setState("12345678"); - authorizeRequest.setCredentialID(URLEncoder.encode("cred1", StandardCharsets.UTF_8)); - authorizeRequest.setNumSignatures(Integer.toString(1)); - authorizeRequest.setHashes("some_document_hash"); - authorizeRequest.setHashAlgorithmOID("2.16.840.1.101.3.4.2.1"); + authorizeRequest.setCredentialID(URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); + authorizeRequest.setNumSignatures(credentialAuthorization.getNumSignatures()); + authorizeRequest.setHashes(hash); + authorizeRequest.setHashAlgorithmOID(credentialAuthorization.getHashAlgorithmOID()); - AuthResponseTemporary responseTemporary = this.qtspClient.requestOAuth2Authorize(qtspUrl, authorizeRequest); + AuthResponseTemporary responseTemporary = this.qtspClient.requestOAuth2Authorize(credentialAuthorization.getAuthorizationServerUrl(), authorizeRequest, authorizationBearerHeader); System.out.println("-----------------------------"); return responseTemporary; } @@ -138,7 +155,7 @@ public String credential_authorization_code(HttpServletRequest request) throws E "code_verifier="+code_verifier; System.out.println("Url: "+url); - String authorizationHeader = getBasicAuthenticationHeader("sca-client", "secret"); + String authorizationHeader = getBasicAuthenticationHeader("sca-client", "somesecret1"); System.out.println("Authorization Header: "+authorizationHeader); String new_session_id = request.getHeader("Set-Cookie"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 69b91d3..1b33d53 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -10,10 +10,7 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import eu.europa.ec.eudi.signer.r3.sca.DSS_Service; import eu.europa.ec.eudi.signer.r3.sca.QtspClient; @@ -33,15 +30,14 @@ @RequestMapping(value = "/signatures") public class SignaturesController { - @Autowired - private QtspClient qtspClient; + private final QtspClient qtspClient; + private final DSS_Service dssClient; + private final X509Certificate signingCertificate; - @Autowired - private DSS_Service dssClient; + public SignaturesController(@Autowired DSS_Service dssClient, @Autowired QtspClient qtspClient) throws Exception { + this.dssClient = dssClient; + this.qtspClient = qtspClient; - private X509Certificate signingCertificate; - - public SignaturesController() throws Exception { byte[] cert_bytes = Base64.getDecoder().decode( "MIIBuzCCASSgAwIBAgIGAY/zF0AhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2lzc3Vlcl90ZXN0MB4XDTI0MDYwNzE0MjUzOFoXDTI1MDYwNzE0MjUzOFowFzEVMBMGA1UEAwwMc3ViamVjdF90ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCseUUmD8+Okuh5OrLT2LyO6QCNOIidohV7HAjIbgdpSU1C27z+JDWT3cfVbojQ5EzvZM9CDPayHrlnNK8NFD9ggE3rbOn6ATT9iC4qTQvPN3Sdel5OTaVabMuMT2satwbtl8wB98583i4bhJUyHRy7PJnXrOCscyK14GjGnuVwjQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACWKec1JiRggmTRm0aQin3SJnsvuF8JS5GlOpea45IGV2gOHws/iFPg8BAaGzQ1d+sG+RHH07xKCll8Xw1QaqLhc+96vNOCvl2cjl7BdLH/fiYurP8Vf0W3lkp5VbRFV2nWwHcOIPBUa8lNK+uV6Z5nPG5Ads12BJD5K8jAHXo2E"); @@ -53,16 +49,19 @@ public SignaturesController() throws Exception { } @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") - public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest) { + public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest, + @RequestHeader (name="Authorization") String authorizationBearerHeader) { System.out.println(signDocRequest); + System.out.println(authorizationBearerHeader); String url = signDocRequest.getRequest_uri(); + System.out.println(url); if (signDocRequest.getCredentialID() == null) { System.out.println("To be defined: CredentialID needs to be defined in this implementation."); return new SignaturesSignDocResponse(); } - if (signDocRequest.getSAD() == null) { + if (signDocRequest.getSAD() == null && authorizationBearerHeader == null) { System.out.println( "To be defined: the current solution expects the credential token to be sent in the SAD."); return new SignaturesSignDocResponse(); @@ -75,7 +74,7 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe if (signDocRequest.getDocuments() != null) { try { - return handleDocumentsSignDocRequest(signDocRequest, url); + return handleDocumentsSignDocRequest(signDocRequest, url, authorizationBearerHeader); } catch (Exception e) { } @@ -83,7 +82,7 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe if (signDocRequest.getDocumentDigests() != null) { try { - return handleDocumentDigestsSignDocRequest(signDocRequest, url); + return handleDocumentDigestsSignDocRequest(signDocRequest, url, authorizationBearerHeader); } catch (Exception e) { } } @@ -92,7 +91,7 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe } // i need the signing certificate before hand - public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String url) + public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String url, String authorizationBearerHeader) throws Exception { // if signature_format == C => signed_envelope_property = Attached @@ -153,7 +152,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc try { System.out.println("HTTP Request to QTSP."); - SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest); + SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest, authorizationBearerHeader); System.out.println("HTTP Response received."); allResponses.add(signHashResponse); System.out.println(signHashResponse.toString()); @@ -208,7 +207,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc } public SignaturesSignDocResponse handleDocumentDigestsSignDocRequest(SignaturesSignDocRequest signDocRequest, - String url) + String url, String authorizationBearerHeader) throws Exception { // for each document digests.... @@ -226,7 +225,7 @@ public SignaturesSignDocResponse handleDocumentDigestsSignDocRequest(SignaturesS signDocRequest.getResponse_uri(), signDocRequest.getClientData()); - SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest); + SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest, authorizationBearerHeader); allResponses.add(signHashResponse); } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index a5b1453..fd952ed 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -6,6 +6,7 @@ import java.util.Date; import java.util.List; +import eu.europa.esig.dss.enumerations.DigestAlgorithm; import org.springframework.stereotype.Service; import eu.europa.esig.dss.cades.signature.CMSSignedDocument; @@ -59,6 +60,7 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance parameters.setGenerateTBSWithoutCertificate(true); parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); parameters.setReason("DSS testing"); + parameters.setDigestAlgorithm(DigestAlgorithm.SHA256); DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java new file mode 100644 index 0000000..d09a69e --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java @@ -0,0 +1,95 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO; + +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentDigestsSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; +import java.util.List; + +public class CredentialAuthorizationRequest { + + private String credentialID; + private String numSignatures; + private List documentDigests; + private List documents; + private String hashAlgorithmOID; + private String authorizationServerUrl; + private String resourceServerUrl; + private String clientData; + + public String getCredentialID() { + return credentialID; + } + + public void setCredentialID(String credentialID) { + this.credentialID = credentialID; + } + + public String getNumSignatures() { + return numSignatures; + } + + public void setNumSignatures(String numSignatures) { + this.numSignatures = numSignatures; + } + + public List getDocumentDigests() { + return documentDigests; + } + + public void setDocumentDigests(List documentDigests) { + this.documentDigests = documentDigests; + } + + public List getDocuments() { + return documents; + } + + public void setDocuments(List documents) { + this.documents = documents; + } + + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } + + public String getClientData() { + return clientData; + } + + public void setClientData(String clientData) { + this.clientData = clientData; + } + + public String getAuthorizationServerUrl() { + return authorizationServerUrl; + } + + public void setAuthorizationServerUrl(String authorizationServerUrl) { + this.authorizationServerUrl = authorizationServerUrl; + } + + public String getResourceServerUrl() { + return resourceServerUrl; + } + + public void setResourceServerUrl(String resourceServerUrl) { + this.resourceServerUrl = resourceServerUrl; + } + + @Override + public String toString() { + return "CredentialAuthorizationRequest{" + + "credentialID='" + credentialID + '\'' + + ", numSignatures='" + numSignatures + '\'' + + ", documentDigests=" + documentDigests + + ", documents=" + documents + + ", hashAlgorithmOID='" + hashAlgorithmOID + '\'' + + ", authorizationServerUrl='" + authorizationServerUrl + '\'' + + ", resourceServerUrl='" + resourceServerUrl + '\'' + + ", clientData='" + clientData + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java new file mode 100644 index 0000000..d3e32e7 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java @@ -0,0 +1,42 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfo; + +import java.util.List; + +public class CredentialsInfoAuth { + private String mode; + private String expression; + private List objects; + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public List getObjects() { + return objects; + } + + public void setObjects(List objects) { + this.objects = objects; + } + + @Override + public String toString() { + return "CredentialsInfoAuth{" + + "mode='" + mode + '\'' + + ", expression='" + expression + '\'' + + ", objects=" + objects + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java new file mode 100644 index 0000000..395adb7 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java @@ -0,0 +1,89 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfo; + +import java.util.List; + +public class CredentialsInfoCert { + // valid | expired | revoked | suspended + private String status; + // one or more certificates from the certificate chain + private List certificates; + // the issuer distinguished name from the end entity certificate + private String issuerDN; + // the serial number of the end entity certificate + private String serialNumber; + // the subject distinguished name from the end entity certificate + private String subjectDN; + // the validity start date from the end entity certificate + private String validFrom; + // the validity end date from the end entity certificate + private String validTo; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public List getCertificates() { + return certificates; + } + + public void setCertificates(List certificates) { + this.certificates = certificates; + } + + public String getIssuerDN() { + return issuerDN; + } + + public void setIssuerDN(String issuerDN) { + this.issuerDN = issuerDN; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getSubjectDN() { + return subjectDN; + } + + public void setSubjectDN(String subjectDN) { + this.subjectDN = subjectDN; + } + + public String getValidFrom() { + return validFrom; + } + + public void setValidFrom(String validFrom) { + this.validFrom = validFrom; + } + + public String getValidTo() { + return validTo; + } + + public void setValidTo(String validTo) { + this.validTo = validTo; + } + + @Override + public String toString() { + return "CredentialsInfoCert{" + + "status='" + status + '\'' + + ", certificates=" + certificates + + ", issuerDN='" + issuerDN + '\'' + + ", serialNumber='" + serialNumber + '\'' + + ", subjectDN='" + subjectDN + '\'' + + ", validFrom='" + validFrom + '\'' + + ", validTo='" + validTo + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java new file mode 100644 index 0000000..e71d9fa --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java @@ -0,0 +1,60 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfo; + +import jakarta.validation.constraints.NotBlank; + +import java.util.List; + +public class CredentialsInfoKey { + + // enabled | disabled + @NotBlank + private String status; + @NotBlank + private List algo; + @NotBlank + private int len; + private String curve; + + public @NotBlank String getStatus() { + return status; + } + + public void setStatus(@NotBlank String status) { + this.status = status; + } + + public @NotBlank List getAlgo() { + return algo; + } + + public void setAlgo(@NotBlank List algo) { + this.algo = algo; + } + + @NotBlank + public int getLen() { + return len; + } + + public void setLen(@NotBlank int len) { + this.len = len; + } + + public String getCurve() { + return curve; + } + + public void setCurve(String curve) { + this.curve = curve; + } + + @Override + public String toString() { + return "CredentialsKeyInfoResponse{" + + "status='" + status + '\'' + + ", algo=" + algo + + ", len=" + len + + ", curve='" + curve + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoRequest.java new file mode 100644 index 0000000..fb1653e --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoRequest.java @@ -0,0 +1,71 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo; + +public class CredentialsInfoRequest { + private String credentialID; + // none | single | chain + private String certificates = "single"; + private Boolean certInfo = false; + private Boolean authInfo = false; + private String lang; + private String clientData; + + public String getCredentialID() { + return credentialID; + } + + public void setCredentialID(String credentialID) { + this.credentialID = credentialID; + } + + public String getCertificates() { + return certificates; + } + + public void setCertificates(String certificates) { + this.certificates = certificates; + } + + public Boolean getCertInfo() { + return certInfo; + } + + public void setCertInfo(Boolean certInfo) { + this.certInfo = certInfo; + } + + public Boolean getAuthInfo() { + return authInfo; + } + + public void setAuthInfo(Boolean authInfo) { + this.authInfo = authInfo; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + public String getClientData() { + return clientData; + } + + public void setClientData(String clientData) { + this.clientData = clientData; + } + + @Override + public String toString() { + return "CredentialsInfoRequestDTO{" + + "credentialID='" + credentialID + '\'' + + ", certificates='" + certificates + '\'' + + ", certInfo=" + certInfo + + ", authInfo=" + authInfo + + ", lang='" + lang + '\'' + + ", clientData='" + clientData + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoResponse.java new file mode 100644 index 0000000..3e4b204 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialsInfo/CredentialsInfoResponse.java @@ -0,0 +1,98 @@ +package eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo; + +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfo.CredentialsInfoAuth; +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfo.CredentialsInfoCert; +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfo.CredentialsInfoKey; +import jakarta.validation.constraints.NotBlank; + +public class CredentialsInfoResponse { + private String description; + private String signatureQualifier; + private CredentialsInfoKey key; + private CredentialsInfoCert cert; + private CredentialsInfoAuth auth; + // 1 | 2 + private String SCAL = "1"; + // >= 1 + @NotBlank + private int multisign; + private String lang; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getSignatureQualifier() { + return signatureQualifier; + } + + public void setSignatureQualifier(String signatureQualifier) { + this.signatureQualifier = signatureQualifier; + } + + public CredentialsInfoKey getKey() { + return key; + } + + public void setKey(CredentialsInfoKey key) { + this.key = key; + } + + public CredentialsInfoCert getCert() { + return cert; + } + + public void setCert(CredentialsInfoCert cert) { + this.cert = cert; + } + + public CredentialsInfoAuth getAuth() { + return auth; + } + + public void setAuth(CredentialsInfoAuth auth) { + this.auth = auth; + } + + public String getSCAL() { + return SCAL; + } + + public void setSCAL(String SCAL) { + this.SCAL = SCAL; + } + + public int getMultisign() { + return multisign; + } + + public void setMultisign(int multisign) { + this.multisign = multisign; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + @Override + public String toString() { + return "CredentialsInfoResponse{" + + "description='" + description + '\'' + + ", signatureQualifier='" + signatureQualifier + '\'' + + ", key='" + key.toString() + + ", cert='" + cert.toString()+ + ", auth='" + auth.toString() + + ", SCAL='" + SCAL + '\'' + + ", multisign=" + multisign + + ", lang='" + lang + '\'' + + '}'; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java index 5ae0f04..183432f 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/QtspClient.java @@ -1,10 +1,14 @@ package eu.europa.ec.eudi.signer.r3.sca; import eu.europa.ec.eudi.signer.r3.sca.DTO.AuthResponseTemporary; +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfoRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfoResponse; import eu.europa.ec.eudi.signer.r3.sca.DTO.OAuth2AuthorizeRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; import java.util.Optional; + +import jdk.jfr.ContentType; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; @@ -76,11 +80,13 @@ public class QtspClient { * */ - public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHashRequest signHashRequest) + public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHashRequest signHashRequest, String authorizationBearerHeader) throws Exception { // TODO: missing headers! - System.out.println(signHashRequest.toString()); + System.out.println("url: "+url); + System.out.println("body: "+signHashRequest.toString()); + System.out.println("header: "+authorizationBearerHeader); WebClient webClient = WebClient.builder() .baseUrl(url) @@ -91,7 +97,7 @@ public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHash Mono signHashResponse = webClient.post() .uri("/csc/v2/signatures/signHash") .bodyValue(signHashRequest) - .header("Authorization", "") + .header("Authorization", authorizationBearerHeader) .exchangeToMono(response -> { if (response.statusCode().equals(HttpStatus.OK)) { return response.bodyToMono(SignaturesSignHashResponse.class); @@ -103,7 +109,7 @@ public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHash return signHashResponse.block(); } - public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest) + public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest, String authorizationBearerHeader) throws Exception { try(CloseableHttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling().build()) { @@ -149,7 +155,33 @@ public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeR } } + public CredentialsInfoResponse requestCredentialInfo(String url, CredentialsInfoRequest credentialsInfoRequest, String authorizationBearerHeader){ + WebClient webClient = WebClient.builder() + .baseUrl(url) + .defaultCookie("cookieKey", "cookieValue") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + + System.out.println("url: "+url); + System.out.println("body: "+credentialsInfoRequest); + System.out.println("header: "+authorizationBearerHeader); + + Mono signHashResponse = webClient.post() + .uri("/csc/v2/credentials/info") + .bodyValue(credentialsInfoRequest) + .header("Authorization", authorizationBearerHeader) + + .exchangeToMono(response -> { + if (response.statusCode().equals(HttpStatus.OK)) { + return response.bodyToMono(CredentialsInfoResponse.class); + } else { + System.out.println(response.statusCode().value()); + return Mono.error(new Exception("Exception")); + } + }); + return signHashResponse.block(); + } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java new file mode 100644 index 0000000..917d214 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -0,0 +1,57 @@ +package eu.europa.ec.eudi.signer.r3.sca.model; + +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfoRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfoResponse; +import eu.europa.ec.eudi.signer.r3.sca.QtspClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +@Service +public class CredentialsService { + + private final QtspClient qtspClient; + + public CredentialsService(@Autowired QtspClient qtspClient){ + this.qtspClient = qtspClient; + } + + // get the certificate and certificate chain of the credentialID + public List getCertificateAndCertificateChain(String qtspUrl, String credentialId, String authorizationBearerHeader){ + CredentialsInfoRequest infoRequest = new CredentialsInfoRequest(); + infoRequest.setCredentialID(credentialId); + infoRequest.setCertificates("chain"); + infoRequest.setCertInfo(true); + + CredentialsInfoResponse infoResponse = this.qtspClient.requestCredentialInfo(qtspUrl, infoRequest, authorizationBearerHeader); + List certificates = infoResponse.getCert().getCertificates(); + + List x509Certificates = new ArrayList<>(); + for(String c: certificates){ + try{ + X509Certificate cert = base64DecodeCertificate(c); + x509Certificates.add(cert); + } + catch (Exception e){ + e.printStackTrace(); + } + } + return x509Certificates; + } + + public X509Certificate base64DecodeCertificate(String certificate) throws Exception{ + byte[] certificateBytes = Base64.getDecoder().decode(certificate); + ByteArrayInputStream inputStream = new ByteArrayInputStream(certificateBytes); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate)certFactory.generateCertificate(inputStream); + } + + + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java new file mode 100644 index 0000000..2df9a22 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -0,0 +1,38 @@ +package eu.europa.ec.eudi.signer.r3.sca.model; + +import eu.europa.ec.eudi.signer.r3.sca.DSS_Service; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; +import eu.europa.esig.dss.model.DSSDocument; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +@Service +public class SignatureService { + + private final DSS_Service dssClient; + + public SignatureService(@Autowired DSS_Service dssClient){ + this.dssClient = dssClient; + } + + public List calculateHashValue(List documents, X509Certificate signingCertificate, List certificateChain, String hashAlgorithmOID){ + List doc = new ArrayList<>(); + for (DocumentsSignDocRequest document : documents) { + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + byte[] dataToBeSigned = null; + if (document.getSignature_format().equals("P")) { + dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), signingCertificate, certificateChain); + } + + String dtbs = Base64.getEncoder().encodeToString(dataToBeSigned); + doc.add(dtbs); + } + return doc; + } + +} From 274617e92182f7728a56a4a4832133298913a33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:40:34 +0100 Subject: [PATCH 18/44] Baseline-LT and LTA working --- pom.xml | 5 + .../sca/Controllers/SignaturesController.java | 5 +- .../ec/eudi/signer/r3/sca/DSS_Service.java | 299 ++++++++++-------- 3 files changed, 173 insertions(+), 136 deletions(-) diff --git a/pom.xml b/pom.xml index 7056f82..18503f3 100644 --- a/pom.xml +++ b/pom.xml @@ -111,6 +111,11 @@ dss-asic-xades 6.0 + + eu.europa.ec.joinup.sd-dss + dss-crl-parser-x509crl + 6.0 + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 8f6c4c5..a21317f 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -44,12 +44,11 @@ public class SignaturesController { private X509Certificate signingCertificate; public SignaturesController() throws Exception { - byte[] cert_bytes = Base64.getDecoder().decode( - "MIIBuzCCASSgAwIBAgIGAY/zF0AhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2lzc3Vlcl90ZXN0MB4XDTI0MDYwNzE0MjUzOFoXDTI1MDYwNzE0MjUzOFowFzEVMBMGA1UEAwwMc3ViamVjdF90ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCseUUmD8+Okuh5OrLT2LyO6QCNOIidohV7HAjIbgdpSU1C27z+JDWT3cfVbojQ5EzvZM9CDPayHrlnNK8NFD9ggE3rbOn6ATT9iC4qTQvPN3Sdel5OTaVabMuMT2satwbtl8wB98583i4bhJUyHRy7PJnXrOCscyK14GjGnuVwjQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACWKec1JiRggmTRm0aQin3SJnsvuF8JS5GlOpea45IGV2gOHws/iFPg8BAaGzQ1d+sG+RHH07xKCll8Xw1QaqLhc+96vNOCvl2cjl7BdLH/fiYurP8Vf0W3lkp5VbRFV2nWwHcOIPBUa8lNK+uV6Z5nPG5Ads12BJD5K8jAHXo2E"); - + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); InputStream in = new ByteArrayInputStream(cert_bytes); this.signingCertificate = (X509Certificate) certFactory.generateCertificate(in); + System.out.println(this.signingCertificate.toString()); } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 3ca769d..9177f3b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -1,5 +1,10 @@ package eu.europa.ec.eudi.signer.r3.sca; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; @@ -50,11 +55,17 @@ import eu.europa.esig.dss.service.http.commons.CommonsDataLoader; import eu.europa.esig.dss.service.http.commons.OCSPDataLoader; import eu.europa.esig.dss.service.http.commons.TimestampDataLoader; +import eu.europa.esig.dss.service.http.proxy.ProxyConfig; import eu.europa.esig.dss.service.ocsp.OnlineOCSPSource; import eu.europa.esig.dss.service.tsp.OnlineTSPSource; import eu.europa.esig.dss.signature.DocumentSignatureService; +import eu.europa.esig.dss.spi.client.http.DataLoader; +import eu.europa.esig.dss.spi.client.http.Protocol; +import eu.europa.esig.dss.spi.tsl.TrustedListsCertificateSource; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; +import eu.europa.esig.dss.spi.x509.aia.AIASource; import eu.europa.esig.dss.spi.x509.aia.DefaultAIASource; +import eu.europa.esig.dss.spi.x509.aia.OnlineAIASource; import eu.europa.esig.dss.validation.CertificateVerifier; import eu.europa.esig.dss.validation.CommonCertificateVerifier; import eu.europa.esig.dss.validation.OCSPFirstRevocationDataLoadingStrategyFactory; @@ -163,16 +174,24 @@ public void test() { // importante parameters: conformance_level, signed_envelope_property @SuppressWarnings("rawtypes") - public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg, Date date) { + public byte[] cadesToBeSignedData(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg, Date date) throws CertificateException { + + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + InputStream tsa = new ByteArrayInputStream(tsa_bytes); + InputStream in = new ByteArrayInputStream(cert_bytes); + X509Certificate trustedCertificate = (X509Certificate) certFactory.generateCertificate(in); + X509Certificate TsaCertificate = (X509Certificate) certFactory.generateCertificate(tsa); CertificateVerifier cv = new CommonCertificateVerifier(); CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(date); System.out.println(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + + CertificateToken certToken= new CertificateToken(signingCertificate); + signatureParameters.setSigningCertificate(certToken); // certificateChain.add(signingCertificate); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { @@ -180,35 +199,33 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc } CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); - for (CertificateToken certificateToken : certChainToken) { - certificateSource.addCertificate(certificateToken); - } - signatureParameters.setCertificateChain(certChainToken); - + certificateSource.addCertificate(new CertificateToken(trustedCertificate)); + certificateSource.addCertificate(new CertificateToken(TsaCertificate)); + cv.setTrustedCertSources(certificateSource); + // for (CertificateToken certificateToken : certChainToken) { + // certificateSource.addCertificate(certificateToken); + // } + //signatureParameters.setCertificateChain(certToken); + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(aux_digest_alg); + CAdESTimestampParameters timestampParameters = new CAdESTimestampParameters(aux_digest_alg); signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); - signatureParameters.setGenerateTBSWithoutCertificate(true); - - + // signatureParameters.setGenerateTBSWithoutCertificate(true); + System.out.print("2CAdES\n"); - cv.setTrustedCertSources(certificateSource); - cv.setRevocationDataVerifier(RevocationDataVerifier.createDefaultRevocationDataVerifier()); - cv.setCheckRevocationForUntrustedChains(true); - // Capability to download resources from AIA - cv.setAIASource(new DefaultAIASource()); + cv.setCheckRevocationForUntrustedChains(false); OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); onlineCRLSource.setDataLoader(new CommonsDataLoader()); @@ -219,7 +236,11 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); cv.setOcspSource(onlineOCSPSource); + // Capability to download resources from AIA + cv.setAIASource(new DefaultAIASource()); + cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); + cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); @@ -237,10 +258,9 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); cv.setRevocationDataVerifier(revocationDataVerifier); - cv.setRevocationFallback(false); - + cv.setRevocationFallback(true); - CAdESService cmsForCAdESGenerationService = new CAdESService(cv); + CAdESService cmsForCAdESGenerationService = new CAdESService(cv); String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type @@ -251,10 +271,11 @@ public byte[] cadesToBeSignedData (DSSDocument documentToSign, String conformanc return dataToSign.getBytes(); } + @SuppressWarnings("rawtypes") public byte[] cadesToBeSignedData_asic_E(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -265,11 +286,11 @@ public byte[] cadesToBeSignedData_asic_E(DSSDocument documentToSign, String conf for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } - + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -277,20 +298,21 @@ public byte[] cadesToBeSignedData_asic_E(DSSDocument documentToSign, String conf signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - + System.out.print("2CAdES\n"); cv = new CommonCertificateVerifier(); - ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); + ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3CAdES\n"); return dataToSign.getBytes(); } + @SuppressWarnings("rawtypes") public byte[] cadesToBeSignedData_asic_S(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -301,11 +323,11 @@ public byte[] cadesToBeSignedData_asic_S(DSSDocument documentToSign, String conf for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } - + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -313,24 +335,25 @@ public byte[] cadesToBeSignedData_asic_S(DSSDocument documentToSign, String conf signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); - + System.out.print("2CAdES\n"); cv = new CommonCertificateVerifier(); - ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); + ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3CAdES\n"); return dataToSign.getBytes(); } + @SuppressWarnings("rawtypes") public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg, Date date) { + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg, Date date) { CertificateVerifier cv = new CommonCertificateVerifier(); - XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); + XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(date); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); @@ -342,38 +365,38 @@ public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); System.out.println("\n\n SIGN LEVEL: " + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n DIGEST ALG: " + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n DIGEST ALG: " + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n SIGN PACK: " + aux_sign_pack + "\n\n\n"); signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - XAdESTimestampParameters timestampParameters= new XAdESTimestampParameters(aux_digest_alg); + XAdESTimestampParameters timestampParameters = new XAdESTimestampParameters(aux_digest_alg); signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); - // signatureParameters.setReason("DSS testing"); System.out.print("2XAdES\n"); cv = new CommonCertificateVerifier(); - XAdESService cmsForXAdESGenerationService = new XAdESService(cv); + XAdESService cmsForXAdESGenerationService = new XAdESService(cv); String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type cmsForXAdESGenerationService.setTspSource(onlineTSPSource); - + ToBeSigned dataToSign = cmsForXAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3XAdES\n"); return dataToSign.getBytes(); } + @SuppressWarnings("rawtypes") public byte[] xadesToBeSignedData_asic_E(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -384,11 +407,11 @@ public byte[] xadesToBeSignedData_asic_E(DSSDocument documentToSign, String conf for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } - + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -396,20 +419,21 @@ public byte[] xadesToBeSignedData_asic_E(DSSDocument documentToSign, String conf signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - + System.out.print("2XAdES\n"); cv = new CommonCertificateVerifier(); - ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); + ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3XAdES\n"); return dataToSign.getBytes(); } + @SuppressWarnings("rawtypes") public byte[] xadesToBeSignedData_asic_S(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); @@ -420,11 +444,11 @@ public byte[] xadesToBeSignedData_asic_S(DSSDocument documentToSign, String conf for (X509Certificate cert : certificateChain) { certChainToken.add(new CertificateToken(cert)); } - + SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -432,23 +456,24 @@ public byte[] xadesToBeSignedData_asic_S(DSSDocument documentToSign, String conf signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); - + System.out.print("2XAdES\n"); cv = new CommonCertificateVerifier(); - ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); + ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3XAdES\n"); return dataToSign.getBytes(); } - public byte[] jadesToBeSignedData (DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + + public byte[] jadesToBeSignedData(DSSDocument documentToSign, String conformance_level, + String signed_envelope_property, X509Certificate signingCertificate, + List certificateChain, String signAlg) { CertificateVerifier cv = new CommonCertificateVerifier(); - JAdESSignatureParameters signatureParameters = new JAdESSignatureParameters(); + JAdESSignatureParameters signatureParameters = new JAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(new Date()); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); @@ -461,7 +486,7 @@ public byte[] jadesToBeSignedData (DSSDocument documentToSign, String conformanc SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'j'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -471,29 +496,27 @@ public byte[] jadesToBeSignedData (DSSDocument documentToSign, String conformanc // signatureParameters.setReason("DSS testing"); System.out.print("2JAdES\n"); - if(signed_envelope_property.equals("DETACHED")) { + if (signed_envelope_property.equals("DETACHED")) { System.out.println("\n\n JADES detached \n\n"); signatureParameters.setSigDMechanism(SigDMechanism.HTTP_HEADERS); signatureParameters.setBase64UrlEncodedPayload(false); List documentsToSign = new ArrayList<>(); documentsToSign.add(new HTTPHeader("content-type", "application/json")); documentsToSign.add(new HTTPHeaderDigest(documentToSign, DigestAlgorithm.SHA256)); - + cv = new CommonCertificateVerifier(); - JAdESService cmsForJAdESGenerationService = new JAdESService(cv); + JAdESService cmsForJAdESGenerationService = new JAdESService(cv); ToBeSigned dataToSign = cmsForJAdESGenerationService.getDataToSign(documentsToSign, signatureParameters); System.out.print("3JAdES\n"); return dataToSign.getBytes(); - } - else { + } else { cv = new CommonCertificateVerifier(); - JAdESService cmsForJAdESGenerationService = new JAdESService(cv); + JAdESService cmsForJAdESGenerationService = new JAdESService(cv); ToBeSigned dataToSign = cmsForJAdESGenerationService.getDataToSign(documentToSign, signatureParameters); System.out.print("3JAdES\n"); return dataToSign.getBytes(); } - } // -------------------- @@ -536,19 +559,20 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, X509Certificate signingCertificate, - List certificateChain, String signAlg, String sign_format, String conform_level, String envelope_props, - String container, Date date) { + List certificateChain, String signAlg, String sign_format, String conform_level, + String envelope_props, + String container, Date date) throws CertificateException { SignatureValue signatureValue = new SignatureValue(); SignatureAlgorithm aux_alg = checkSignAlg(signAlg); - signatureValue.setAlgorithm(aux_alg); + signatureValue.setAlgorithm(SignatureAlgorithm.ECDSA_SHA256); signatureValue.setValue(signature); System.out.println("\n\n" + aux_alg + "\n\n\n"); CertificateVerifier cv = new CommonCertificateVerifier(); if (sign_format.equals("C")) { - if(container.equals("ASiC-E")) { + if (container.equals("ASiC-E")) { System.out.print("CAdES ASiC-E\n"); ASiCWithCAdESService service = new ASiCWithCAdESService(cv); ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); @@ -564,7 +588,7 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -574,9 +598,8 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); service = new ASiCWithCAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); - } - else if (container.equals("ASiC-S")) { + return service.signDocument(documentToSign, signatureParameters, signatureValue); + } else if (container.equals("ASiC-S")) { System.out.print("CAdES ASiC-S\n"); ASiCWithCAdESService service = new ASiCWithCAdESService(cv); ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); @@ -592,7 +615,7 @@ else if (container.equals("ASiC-S")) { SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -602,77 +625,93 @@ else if (container.equals("ASiC-S")) { signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); service = new ASiCWithCAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + return service.signDocument(documentToSign, signatureParameters, signatureValue); - } - else { + } else { System.out.print("CAdES\n"); - + + + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + InputStream tsa = new ByteArrayInputStream(tsa_bytes); + InputStream in = new ByteArrayInputStream(cert_bytes); + X509Certificate trustedCertificate = (X509Certificate) certFactory.generateCertificate(in); + X509Certificate TsaCertificate = (X509Certificate) certFactory.generateCertificate(tsa); + CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(date); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + System.out.println(signingCertificate.getSerialNumber()); + CertificateToken certToken= new CertificateToken(signingCertificate); + System.out.println(certToken.getIssuer().getPrettyPrintRFC2253()); + signatureParameters.setSigningCertificate(certToken); + List certChainToken = new ArrayList<>(); - // certificateChain.add(signingCertificate); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } + certChainToken.add(certToken); + + // for (X509Certificate cert : certificateChain) { + + // certChainToken.add(new CertificateToken(cert)); + // } + // System.out.println(); CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); - for (CertificateToken certificateToken : certChainToken) { - certificateSource.addCertificate(certificateToken); - } - signatureParameters.setCertificateChain(certChainToken); + certificateSource.addCertificate(new CertificateToken(trustedCertificate)); + certificateSource.addCertificate(new CertificateToken(TsaCertificate)); + cv.setTrustedCertSources(certificateSource); + + // for (CertificateToken certificateToken : certChainToken) { + // certificateSource.addCertificate(certificateToken); + // } + //signatureParameters.setCertificateChain(certChainToken); SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - CAdESTimestampParameters timestampParameters= new CAdESTimestampParameters(aux_digest_alg); + CAdESTimestampParameters timestampParameters = new CAdESTimestampParameters(aux_digest_alg); signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); signatureParameters.setGenerateTBSWithoutCertificate(true); - cv.setTrustedCertSources(certificateSource); - cv.setRevocationDataVerifier(RevocationDataVerifier.createDefaultRevocationDataVerifier()); cv.setCheckRevocationForUntrustedChains(false); - // Capability to download resources from AIA - cv.setAIASource(new DefaultAIASource()); - + OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); onlineCRLSource.setDataLoader(new CommonsDataLoader()); cv.setCrlSource(onlineCRLSource); - + OnlineOCSPSource onlineOCSPSource = new OnlineOCSPSource(); onlineOCSPSource.setDataLoader(new OCSPDataLoader()); onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); - cv.setOcspSource(onlineOCSPSource); + cv.setOcspSource(null); + + // Capability to download resources from AIA + cv.setAIASource(null); cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); - + cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); - + cv.setAlertOnRevokedCertificate(new ExceptionOnStatusAlert()); - + cv.setAlertOnInvalidTimestamp(new ExceptionOnStatusAlert()); - + cv.setAlertOnNoRevocationAfterBestSignatureTime(new LogOnStatusAlert(Level.ERROR)); - + cv.setAlertOnExpiredSignature(new ExceptionOnStatusAlert()); - + cv.setRevocationDataLoadingStrategyFactory(new OCSPFirstRevocationDataLoadingStrategyFactory()); - + RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); cv.setRevocationDataVerifier(revocationDataVerifier); - - cv.setRevocationFallback(false); + + cv.setRevocationFallback(true); CAdESService service = new CAdESService(cv); System.out.println("teste"); @@ -681,10 +720,9 @@ else if (container.equals("ASiC-S")) { onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type service.setTspSource(onlineTSPSource); - DSSDocument signed_document= service.signDocument(documentToSign, signatureParameters,signatureValue); + DSSDocument signed_document = service.signDocument(documentToSign, signatureParameters, signatureValue); System.out.println("teste2"); - return signed_document; } @@ -704,23 +742,23 @@ else if (container.equals("ASiC-S")) { SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'p'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - + service = new PAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + return service.signDocument(documentToSign, signatureParameters, signatureValue); } else if (sign_format.equals("X")) { - if(container.equals("ASiC-E")) { + if (container.equals("ASiC-E")) { System.out.print("XAdES ASiC-E\n"); ASiCWithXAdESService service = new ASiCWithXAdESService(cv); ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); - + signatureParameters.bLevel().setSigningDate(new Date()); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); @@ -732,7 +770,7 @@ else if (container.equals("ASiC-S")) { SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -742,9 +780,8 @@ else if (container.equals("ASiC-S")) { signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); service = new ASiCWithXAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); - } - else if (container.equals("ASiC-S")) { + return service.signDocument(documentToSign, signatureParameters, signatureValue); + } else if (container.equals("ASiC-S")) { System.out.print("XAdES ASiC-S\n"); ASiCWithXAdESService service = new ASiCWithXAdESService(cv); ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); @@ -760,7 +797,7 @@ else if (container.equals("ASiC-S")) { SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -770,10 +807,9 @@ else if (container.equals("ASiC-S")) { signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); service = new ASiCWithXAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + return service.signDocument(documentToSign, signatureParameters, signatureValue); - } - else { + } else { System.out.print("XAdES\n"); XAdESService service = new XAdESService(cv); XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); @@ -789,18 +825,17 @@ else if (container.equals("ASiC-S")) { SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); signatureParameters.setSignatureLevel(aux_sign_level); signatureParameters.setDigestAlgorithm(aux_digest_alg); signatureParameters.setSignaturePackaging(aux_sign_pack); - XAdESTimestampParameters timestampParameters= new XAdESTimestampParameters(aux_digest_alg); + XAdESTimestampParameters timestampParameters = new XAdESTimestampParameters(aux_digest_alg); signatureParameters.setSignatureTimestampParameters(timestampParameters); signatureParameters.setArchiveTimestampParameters(timestampParameters); signatureParameters.setContentTimestampParameters(timestampParameters); - service = new XAdESService(cv); System.out.println("teste"); @@ -809,9 +844,9 @@ else if (container.equals("ASiC-S")) { onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type service.setTspSource(onlineTSPSource); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + return service.signDocument(documentToSign, signatureParameters, signatureValue); } - + } else if (sign_format.equals("J")) { System.out.print("JAdES\n"); JAdESService service = new JAdESService(cv); @@ -828,7 +863,7 @@ else if (container.equals("ASiC-S")) { SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'j'); System.out.println("\n\n" + aux_sign_level + "\n\n"); DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println( "\n\n" + aux_digest_alg + "\n\n\n"); + System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); @@ -837,7 +872,7 @@ else if (container.equals("ASiC-S")) { signatureParameters.setSignaturePackaging(aux_sign_pack); signatureParameters.setJwsSerializationType(JWSSerializationType.COMPACT_SERIALIZATION); - if(envelope_props.equals("DETACHED")) { + if (envelope_props.equals("DETACHED")) { System.out.println("\n\n JADES detached \n\n"); signatureParameters.setSigDMechanism(SigDMechanism.HTTP_HEADERS); signatureParameters.setBase64UrlEncodedPayload(false); @@ -845,13 +880,11 @@ else if (container.equals("ASiC-S")) { documentsToSign.add(new HTTPHeader("content-type", "application/json")); documentsToSign.add(new HTTPHeaderDigest(documentToSign, DigestAlgorithm.SHA256)); service = new JAdESService(cv); - return service.signDocument(documentsToSign, signatureParameters,signatureValue); - } - else { + return service.signDocument(documentsToSign, signatureParameters, signatureValue); + } else { service = new JAdESService(cv); - return service.signDocument(documentToSign, signatureParameters,signatureValue); + return service.signDocument(documentToSign, signatureParameters, signatureValue); } - } From e8e239ebfabc03a4485abe7470f2b4dd2bd90f04 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Tue, 10 Sep 2024 14:40:14 +0100 Subject: [PATCH 19/44] Work in progress --- .../r3/sca/Controllers/OAuth2Controller.java | 54 +--- .../sca/Controllers/SignaturesController.java | 232 ++---------------- .../ec/eudi/signer/r3/sca/DSS_Service.java | 24 +- .../DTO/CredentialAuthorizationRequest.java | 11 - .../DocumentDigestsSignDocRequest.java | 80 ------ .../DocumentsSignDocRequest.java | 19 -- .../SignaturesSignDocRequest.java | 33 +-- .../DocumentsSignDocRequestValidator.java | 4 - .../SignaturesSignDocRequestValidator.java | 3 +- .../r3/sca/model/CredentialsService.java | 49 +++- .../signer/r3/sca/model/SignatureService.java | 92 ++++++- 11 files changed, 181 insertions(+), 420 deletions(-) delete mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java index f96b0bd..5292ebc 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/OAuth2Controller.java @@ -11,7 +11,6 @@ import org.apache.http.Header; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -26,7 +25,6 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; -import java.security.cert.X509Certificate; import java.util.Base64; import java.util.List; @@ -61,15 +59,18 @@ private String generateNonce(String root) throws Exception{ public AuthResponseTemporary credential_authorization( @RequestBody CredentialAuthorizationRequest credentialAuthorization, @RequestHeader (name="Authorization") String authorizationBearerHeader) throws Exception{ - System.out.println("Authorize: -----------------------------"); - System.out.println(credentialAuthorization.toString()); System.out.println("authorization: "+authorizationBearerHeader); - List certificateList = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); + System.out.println(credentialAuthorization); + CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); // calculate hash - List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateList.get(0), certificateList.subList(1, certificateList.size()), credentialAuthorization.getHashAlgorithmOID()); - String hash = String.join(";", hashes); + List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID()); + for(String s: hashes){ + System.out.println("Oauth2: "+ s); + } + + String hash = String.join(",", hashes); System.out.println("hash: "+hash); // generate code_challenge, code_challenge_method, code_verifier @@ -93,45 +94,6 @@ public AuthResponseTemporary credential_authorization( return responseTemporary; } - /*@GetMapping("/temporary") - public void temporary(@RequestBody AuthRequestTemporary authRequest) throws Exception{ - System.out.println("Temporary: -----------------------------"); - - System.out.println("URL: " + authRequest.getUrl()); - System.out.println("Cookie: " + authRequest.getCookie()); - - String location_redirect = null; - String new_session_id = null; - - // Get localhost:9000 after auth - try(CloseableHttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling().build()) { - - HttpGet followRequest = new HttpGet(authRequest.getUrl()); - followRequest.setHeader("Cookie", authRequest.getCookie()); - HttpResponse followResponse = httpClient.execute(followRequest); - - if(followResponse.getStatusLine().getStatusCode() == 302) { - location_redirect = followResponse.getLastHeader("Location").getValue(); - System.out.println("Location: "+location_redirect); - new_session_id = followResponse.getLastHeader("Set-Cookie").getElements()[0].toString(); - System.out.println("Cookie: "+new_session_id); - } - } - - if ( location_redirect==null || new_session_id == null ) - return; - - System.out.println("-----------------------------"); - - // Get /oauth2/authorize after oid4vp - try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { - HttpGet followRequest = new HttpGet(location_redirect); - followRequest.setHeader("Cookie", new_session_id); - HttpResponse followResponse = httpClient2.execute(followRequest); - // System.out.println(followResponse.getStatusLine().getStatusCode()); - } - }*/ - private static String getBasicAuthenticationHeader(String username, String password) { String valueToEncode = username + ":" + password; return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 1b33d53..d5987b2 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -1,72 +1,41 @@ package eu.europa.ec.eudi.signer.r3.sca.Controllers; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.file.Files; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import eu.europa.ec.eudi.signer.r3.sca.DSS_Service; -import eu.europa.ec.eudi.signer.r3.sca.QtspClient; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignDocResponse; -import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; -import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; -import eu.europa.ec.eudi.signer.r3.sca.DTO.ValidationInfoSignDocResponse; -import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.SignaturesSignDocRequest; -import eu.europa.esig.dss.enumerations.MimeType; -import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; +import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; import jakarta.validation.Valid; - -import java.io.File; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping(value = "/signatures") public class SignaturesController { - private final QtspClient qtspClient; - private final DSS_Service dssClient; - private final X509Certificate signingCertificate; - - public SignaturesController(@Autowired DSS_Service dssClient, @Autowired QtspClient qtspClient) throws Exception { - this.dssClient = dssClient; - this.qtspClient = qtspClient; - - byte[] cert_bytes = Base64.getDecoder().decode( - "MIIBuzCCASSgAwIBAgIGAY/zF0AhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMMC2lzc3Vlcl90ZXN0MB4XDTI0MDYwNzE0MjUzOFoXDTI1MDYwNzE0MjUzOFowFzEVMBMGA1UEAwwMc3ViamVjdF90ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCseUUmD8+Okuh5OrLT2LyO6QCNOIidohV7HAjIbgdpSU1C27z+JDWT3cfVbojQ5EzvZM9CDPayHrlnNK8NFD9ggE3rbOn6ATT9iC4qTQvPN3Sdel5OTaVabMuMT2satwbtl8wB98583i4bhJUyHRy7PJnXrOCscyK14GjGnuVwjQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACWKec1JiRggmTRm0aQin3SJnsvuF8JS5GlOpea45IGV2gOHws/iFPg8BAaGzQ1d+sG+RHH07xKCll8Xw1QaqLhc+96vNOCvl2cjl7BdLH/fiYurP8Vf0W3lkp5VbRFV2nWwHcOIPBUa8lNK+uV6Z5nPG5Ads12BJD5K8jAHXo2E"); - - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - InputStream in = new ByteArrayInputStream(cert_bytes); - this.signingCertificate = (X509Certificate) certFactory.generateCertificate(in); - System.out.println(this.signingCertificate.toString()); + private final SignatureService signatureService; + private final CredentialsService credentialsService; + public SignaturesController(@Autowired CredentialsService credentialsService, @Autowired SignatureService signatureService) throws Exception { + this.credentialsService = credentialsService; + this.signatureService = signatureService; } @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest, @RequestHeader (name="Authorization") String authorizationBearerHeader) { - System.out.println(signDocRequest); - System.out.println(authorizationBearerHeader); - String url = signDocRequest.getRequest_uri(); - System.out.println(url); + System.out.println("authorization: "+authorizationBearerHeader); + if (signDocRequest.getCredentialID() == null) { System.out.println("To be defined: CredentialID needs to be defined in this implementation."); return new SignaturesSignDocResponse(); } + CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(signDocRequest.getRequest_uri(), signDocRequest.getCredentialID(), authorizationBearerHeader); - if (signDocRequest.getSAD() == null && authorizationBearerHeader == null) { - System.out.println( - "To be defined: the current solution expects the credential token to be sent in the SAD."); + if (authorizationBearerHeader == null) { + System.out.println("To be defined: the current solution expects the credential token to be sent in the SAD."); return new SignaturesSignDocResponse(); } - if (signDocRequest.getOperationMode().equals("A")) { System.out.println("To be defined: the current solution doesn't support assynchronious responses."); return new SignaturesSignDocResponse(); @@ -74,179 +43,14 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe if (signDocRequest.getDocuments() != null) { try { - return handleDocumentsSignDocRequest(signDocRequest, url, authorizationBearerHeader); + return this.signatureService.handleDocumentsSignDocRequest( + signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), + certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo() + ); } catch (Exception e) { } } - - if (signDocRequest.getDocumentDigests() != null) { - try { - return handleDocumentDigestsSignDocRequest(signDocRequest, url, authorizationBearerHeader); - } catch (Exception e) { - } - } - return new SignaturesSignDocResponse(); } - - // i need the signing certificate before hand - public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String url, String authorizationBearerHeader) - throws Exception { - - // if signature_format == C => signed_envelope_property = Attached - // if signature_format == P => signed_envelope_property = Certification - // if signature_format == X => signed_envelope_property = Enveloped - // if signature_format == J => signed_envelope_property = Attached - - List allResponses = new ArrayList<>(); - for (DocumentsSignDocRequest document : signDocRequest.getDocuments()) { - DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - byte[] dataToBeSigned = null; - /*if (document.getSignature_format().equals("C")) { - dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>()); - System.out.println("Not Supported by current version"); - } else*/ if (document.getSignature_format().equals("P")) { - System.out.print("PAdES\n"); - dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>()); - System.out.println("Data To Be Signed Created"); - } else if (document.getSignature_format().equals("X")) { - System.out.print("XAdES: to be implemented"); - return new SignaturesSignDocResponse(); - // dssClient.xadesToBeSignedData(dssDocument, document.getConformance_level(), - // document.getSigned_envelope_property()); - } else if (document.getSignature_format().equals("J")) { - System.out.print("JAdES: to be implemented"); - return new SignaturesSignDocResponse(); - // dssClient.jadesToBeSignedData(dssDocument, document.getConformance_level(), - // document.getSigned_envelope_property()); - } - - if (dataToBeSigned == null) { - return new SignaturesSignDocResponse(); - } - - String dtbs = Base64.getEncoder().encodeToString(dataToBeSigned); - List doc = new ArrayList<>(); - doc.add(dtbs); - - System.out.println(signDocRequest.toString()); - - // As the current operation mode only supported is "S", the validity_period and - // response_uri do not need to be defined - SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest( - signDocRequest.getCredentialID(), - signDocRequest.getSAD(), - doc, - null, - document.getSignAlgo(), - null, - signDocRequest.getOperationMode(), - -1, - null, - signDocRequest.getClientData()); - - try { - System.out.println("HTTP Request to QTSP."); - SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest, authorizationBearerHeader); - System.out.println("HTTP Response received."); - allResponses.add(signHashResponse); - System.out.println(signHashResponse.toString()); - } catch (Exception e) { - e.printStackTrace(); - } - } - List DocumentWithSignature = new ArrayList<>(); - - List allSignaturesObjects = new ArrayList<>(); - for (SignaturesSignHashResponse response : allResponses) { - - DocumentsSignDocRequest document = signDocRequest.getDocuments().get(0); - DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - - if (response.getSignatures() != null) { - byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); - DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, - new ArrayList<>()); - - try { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); - docSigned.save("tests/exampleSigned.pdf"); - - File file = new File("tests/exampleSigned.pdf"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); - - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - allSignaturesObjects.addAll(response.getSignatures()); - } - - ValidationInfoSignDocResponse validationInfo = null; - if (signDocRequest.getReturnValidationInfo()) { - // TODO: obtain the validation info.... - validationInfo = new ValidationInfoSignDocResponse(); - } - - SignaturesSignDocResponse signDocResponse = new SignaturesSignDocResponse( - DocumentWithSignature, - allSignaturesObjects, - null, - validationInfo); - - return signDocResponse; - - } - - public SignaturesSignDocResponse handleDocumentDigestsSignDocRequest(SignaturesSignDocRequest signDocRequest, - String url, String authorizationBearerHeader) - throws Exception { - - // for each document digests.... - List allResponses = new ArrayList<>(); - for (int i = 0; i < signDocRequest.getDocumentDigests().size(); i++) { - SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest( - signDocRequest.getCredentialID(), - signDocRequest.getSAD(), - signDocRequest.getDocumentDigests().get(i).getHashes(), - signDocRequest.getDocumentDigests().get(i).getHashAlgorithmOID(), - signDocRequest.getDocumentDigests().get(i).getSignAlgo(), - signDocRequest.getDocumentDigests().get(i).getSignAlgoParams(), - signDocRequest.getOperationMode(), - signDocRequest.getValidity_period(), - signDocRequest.getResponse_uri(), - signDocRequest.getClientData()); - - SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest, authorizationBearerHeader); - allResponses.add(signHashResponse); - } - - List allSignaturesObjects = new ArrayList<>(); - for (int i = 0; i < allResponses.size(); i++) { - allSignaturesObjects.addAll(allResponses.get(i).getSignatures()); - } - - ValidationInfoSignDocResponse validationInfo = null; - if (signDocRequest.getReturnValidationInfo()) { - // TODO: obtain the validation info.... - validationInfo = new ValidationInfoSignDocResponse(); - } - - SignaturesSignDocResponse signDocResponse = new SignaturesSignDocResponse( - null, - allSignaturesObjects, - null, - validationInfo); - - return signDocResponse; - - } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index fd952ed..dc43cb9 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -7,6 +7,7 @@ import java.util.List; import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.enumerations.EncryptionAlgorithm; import org.springframework.stereotype.Service; import eu.europa.esig.dss.cades.signature.CMSSignedDocument; @@ -50,19 +51,23 @@ public void jadesToBeSignedData(DSSDocument document, String conformance_level, public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance_level, String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain) { + List certificateChain, String hashAlgorithmOID) { + + System.out.println(documentToSign.getName()); CertificateVerifier cv = new CommonCertificateVerifier(); ExternalCMSPAdESService service = new ExternalCMSPAdESService(cv); PAdESSignatureParameters parameters = new PAdESSignatureParameters(); parameters.bLevel().setSigningDate(new Date()); - parameters.setGenerateTBSWithoutCertificate(true); parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); parameters.setReason("DSS testing"); + parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); parameters.setDigestAlgorithm(DigestAlgorithm.SHA256); DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); + System.out.println(messageDigest.getAlgorithm()); + System.out.println("Message Digest: "+Base64.getEncoder().encodeToString(messageDigest.getValue())); PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(new Date()); @@ -73,8 +78,11 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance } signatureParameters.setCertificateChain(certChainToken); signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); + signatureParameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); signatureParameters.setReason("DSS testing"); + + cv = new CommonCertificateVerifier(); ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); ToBeSigned dataToSign = cmsForPAdESGenerationService.getDataToSign(messageDigest, signatureParameters); @@ -82,8 +90,7 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance } public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, - X509Certificate signingCertificate, - List certificateChain) { + X509Certificate signingCertificate, List certificateChain, String signAlgo, String hashAlgorithmOID) { SignatureValue signatureValue = new SignatureValue(); signatureValue.setAlgorithm(SignatureAlgorithm.RSA_SHA256); @@ -98,6 +105,7 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur parameters.setReason("DSS testing"); DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); + PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); signatureParameters.bLevel().setSigningDate(new Date()); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); @@ -112,8 +120,7 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur // stateless cv = new CommonCertificateVerifier(); ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); - CMSSignedDocument cmsSignedDocument = cmsForPAdESGenerationService.signMessageDigest(messageDigest, - signatureParameters, signatureValue); + CMSSignedDocument cmsSignedDocument = cmsForPAdESGenerationService.signMessageDigest(messageDigest, signatureParameters, signatureValue); byte[] cmsSignedData = cmsSignedDocument.getBytes(); // Stateless @@ -123,7 +130,6 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur } private static class ExternalCMSPAdESService extends PAdESService { - private static final long serialVersionUID = -2003453716888412577L; private byte[] cmsSignedData; public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) { @@ -135,9 +141,7 @@ public DSSMessageDigest getMessageDigest(DSSDocument documentToSign, PAdESSignat } @Override - protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, - final PAdESSignatureParameters parameters, - final SignatureValue signatureValue) { + protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, final PAdESSignatureParameters parameters, final SignatureValue signatureValue) { if (this.cmsSignedData == null) { throw new NullPointerException("A CMS signed data must be provided"); } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java index d09a69e..0275e55 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/CredentialAuthorizationRequest.java @@ -1,6 +1,5 @@ package eu.europa.ec.eudi.signer.r3.sca.DTO; -import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentDigestsSignDocRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; import java.util.List; @@ -8,7 +7,6 @@ public class CredentialAuthorizationRequest { private String credentialID; private String numSignatures; - private List documentDigests; private List documents; private String hashAlgorithmOID; private String authorizationServerUrl; @@ -31,14 +29,6 @@ public void setNumSignatures(String numSignatures) { this.numSignatures = numSignatures; } - public List getDocumentDigests() { - return documentDigests; - } - - public void setDocumentDigests(List documentDigests) { - this.documentDigests = documentDigests; - } - public List getDocuments() { return documents; } @@ -84,7 +74,6 @@ public String toString() { return "CredentialAuthorizationRequest{" + "credentialID='" + credentialID + '\'' + ", numSignatures='" + numSignatures + '\'' + - ", documentDigests=" + documentDigests + ", documents=" + documents + ", hashAlgorithmOID='" + hashAlgorithmOID + '\'' + ", authorizationServerUrl='" + authorizationServerUrl + '\'' + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java deleted file mode 100644 index 43d7038..0000000 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentDigestsSignDocRequest.java +++ /dev/null @@ -1,80 +0,0 @@ -package eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest; - -import java.util.List; -import jakarta.validation.constraints.NotBlank; - -public class DocumentDigestsSignDocRequest { - private List hashes; - private String hashAlgorithmOID; - private String signature_format; - private String conformance_level; - @NotBlank - private String signAlgo; - private String signAlgoParams; - private List signed_props; - private String signed_envelop_property; - - public List getHashes() { - return hashes; - } - - public void setHashes(List hashes) { - this.hashes = hashes; - } - - public String getHashAlgorithmOID() { - return hashAlgorithmOID; - } - - public void setHashAlgorithmOID(String hashAlgorithmOID) { - this.hashAlgorithmOID = hashAlgorithmOID; - } - - public String getSignature_format() { - return signature_format; - } - - public void setSignature_format(String signature_format) { - this.signature_format = signature_format; - } - - public String getConformance_level() { - return conformance_level; - } - - public void setConformance_level(String conformance_level) { - this.conformance_level = conformance_level; - } - - public String getSignAlgo() { - return signAlgo; - } - - public void setSignAlgo(String signAlgo) { - this.signAlgo = signAlgo; - } - - public String getSignAlgoParams() { - return signAlgoParams; - } - - public void setSignAlgoParams(String signAlgoParams) { - this.signAlgoParams = signAlgoParams; - } - - public List getSigned_props() { - return signed_props; - } - - public void setSigned_props(List signed_props) { - this.signed_props = signed_props; - } - - public String getSigned_envelop_property() { - return signed_envelop_property; - } - - public void setSigned_envelop_property(String signed_envelop_property) { - this.signed_envelop_property = signed_envelop_property; - } -} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java index 805f042..a97d9a0 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/DocumentsSignDocRequest.java @@ -12,9 +12,6 @@ public class DocumentsSignDocRequest { @NotBlank private String signature_format = null; private String conformance_level = "AdES-B-B"; - @NotBlank - private String signAlgo; - private String signAlgoParams; private List signed_props; private String signed_envelope_property; @@ -42,22 +39,6 @@ public void setConformance_level(String conformance_level) { this.conformance_level = conformance_level; } - public String getSignAlgo() { - return signAlgo; - } - - public void setSignAlgo(String signAlgo) { - this.signAlgo = signAlgo; - } - - public String getSignAlgoParams() { - return signAlgoParams; - } - - public void setSignAlgoParams(String signAlgoParams) { - this.signAlgoParams = signAlgoParams; - } - public List getSigned_props() { return signed_props; } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java index 7dffc67..6de315b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DTO/SignDocRequest/SignaturesSignDocRequest.java @@ -12,8 +12,6 @@ public class SignaturesSignDocRequest { private String credentialID; private String signatureQualifier; - private String SAD; - private List documentDigests; @Valid private List documents; private String operationMode = "S"; @@ -23,6 +21,7 @@ public class SignaturesSignDocRequest { private Boolean returnValidationInfo = false; @NotBlank private String request_uri; + private String hashAlgorithmOID; public SignaturesSignDocRequest() { } @@ -47,26 +46,6 @@ public void setSignatureQualifier(String signatureQualifier) { this.signatureQualifier = signatureQualifier; } - @JsonProperty - public String getSAD() { - return SAD; - } - - @JsonProperty - public void setSAD(String SAD) { - this.SAD = SAD; - } - - @JsonProperty - public List getDocumentDigests() { - return documentDigests; - } - - @JsonProperty - public void setDocumentDigests(List documentDigests) { - this.documentDigests = documentDigests; - } - @JsonProperty public List getDocuments() { return documents; @@ -137,13 +116,19 @@ public void setRequest_uri(String request_uri) { this.request_uri = request_uri; } + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } + @java.lang.Override public java.lang.String toString() { return "SignaturesSignDocRequest{" + "credentialID=" + credentialID + ", signatureQualifier=" + signatureQualifier + - ", SAD=" + SAD + - ", documentDigests=" + documentDigests + ", documents=" + documents + ", operationMode=" + operationMode + ", validity_period=" + validity_period + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java index dccee29..ee4baf9 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/DocumentsSignDocRequestValidator.java @@ -24,10 +24,6 @@ public boolean isValid(DocumentsSignDocRequest request, ConstraintValidatorConte return false; } - if (request.getSignAlgo() == null) { - return false; - } - if (request.getSignature_format() == null) { return false; } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java index a442080..a0a74ce 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Validators/SignaturesSignDocRequestValidator.java @@ -20,8 +20,7 @@ public boolean isValid(SignaturesSignDocRequest request, ConstraintValidatorCont if (request.getRequest_uri() == null) return false; - return (request.getCredentialID().equals(null) || request.getSignatureQualifier().equals(null)) - && (request.getDocuments() == null || request.getDocumentDigests() == null); + return (request.getCredentialID() != null || request.getSignatureQualifier() != null) && (request.getDocuments() != null); } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index 917d214..353b1db 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -3,6 +3,8 @@ import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfoRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.CredentialsInfo.CredentialsInfoResponse; import eu.europa.ec.eudi.signer.r3.sca.QtspClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -13,17 +15,55 @@ import java.util.Base64; import java.util.List; + @Service public class CredentialsService { private final QtspClient qtspClient; + private static final Logger logger = LoggerFactory.getLogger(CredentialsService.class); public CredentialsService(@Autowired QtspClient qtspClient){ this.qtspClient = qtspClient; } + public static class CertificateResponse { + private X509Certificate certificate; + private List certificateChain; + private List signAlgo; + + public CertificateResponse(X509Certificate certificate, List certificateChain, List signAlgo) { + this.certificate = certificate; + this.certificateChain = certificateChain; + this.signAlgo = signAlgo; + } + + public X509Certificate getCertificate() { + return certificate; + } + + public void setCertificate(X509Certificate certificate) { + this.certificate = certificate; + } + + public List getCertificateChain() { + return certificateChain; + } + + public void setCertificateChain(List certificateChain) { + this.certificateChain = certificateChain; + } + + public List getSignAlgo() { + return signAlgo; + } + + public void setSignAlgo(List signAlgo) { + this.signAlgo = signAlgo; + } + } + // get the certificate and certificate chain of the credentialID - public List getCertificateAndCertificateChain(String qtspUrl, String credentialId, String authorizationBearerHeader){ + public CertificateResponse getCertificateAndCertificateChain(String qtspUrl, String credentialId, String authorizationBearerHeader){ CredentialsInfoRequest infoRequest = new CredentialsInfoRequest(); infoRequest.setCredentialID(credentialId); infoRequest.setCertificates("chain"); @@ -31,21 +71,24 @@ public List getCertificateAndCertificateChain(String qtspUrl, S CredentialsInfoResponse infoResponse = this.qtspClient.requestCredentialInfo(qtspUrl, infoRequest, authorizationBearerHeader); List certificates = infoResponse.getCert().getCertificates(); + List keyAlgo = infoResponse.getKey().getAlgo(); List x509Certificates = new ArrayList<>(); for(String c: certificates){ try{ X509Certificate cert = base64DecodeCertificate(c); + logger.info("{}: {}", cert.getSubjectX500Principal(), cert.getSerialNumber()); x509Certificates.add(cert); } catch (Exception e){ e.printStackTrace(); } } - return x509Certificates; + int size = x509Certificates.size(); + return new CertificateResponse(x509Certificates.get(0), x509Certificates.subList(1, size), keyAlgo); } - public X509Certificate base64DecodeCertificate(String certificate) throws Exception{ + private X509Certificate base64DecodeCertificate(String certificate) throws Exception{ byte[] certificateBytes = Base64.getDecoder().decode(certificate); ByteArrayInputStream inputStream = new ByteArrayInputStream(certificateBytes); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index 2df9a22..71ffe78 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -2,10 +2,21 @@ import eu.europa.ec.eudi.signer.r3.sca.DSS_Service; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.SignaturesSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.DTO.SignaturesSignHashResponse; +import eu.europa.ec.eudi.signer.r3.sca.DTO.ValidationInfoSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.QtspClient; +import eu.europa.esig.dss.enumerations.MimeType; import eu.europa.esig.dss.model.DSSDocument; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.File; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; @@ -14,25 +25,92 @@ @Service public class SignatureService { + private final QtspClient qtspClient; private final DSS_Service dssClient; - public SignatureService(@Autowired DSS_Service dssClient){ + public SignatureService(@Autowired QtspClient qtspClient, @Autowired DSS_Service dssClient){ + this.qtspClient = qtspClient; this.dssClient = dssClient; } public List calculateHashValue(List documents, X509Certificate signingCertificate, List certificateChain, String hashAlgorithmOID){ - List doc = new ArrayList<>(); + List hashes = new ArrayList<>(); for (DocumentsSignDocRequest document : documents) { + System.out.println(document.getDocument()); DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; - if (document.getSignature_format().equals("P")) { - dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, document.getConformance_level(), document.getSigned_envelope_property(), signingCertificate, certificateChain); + switch (document.getSignature_format()) { + case "C" -> System.out.print("CAdES: to be implemented"); + case "P" -> + dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, document.getConformance_level(), + document.getSigned_envelope_property(), signingCertificate, certificateChain, hashAlgorithmOID); + case "X" -> System.out.print("XAdES: to be implemented"); + case "J" -> System.out.print("JAdES: to be implemented"); } - String dtbs = Base64.getEncoder().encodeToString(dataToBeSigned); - doc.add(dtbs); + if (dataToBeSigned == null) + continue; + + String dataToBeSignedStringEncoded = Base64.getEncoder().encodeToString(dataToBeSigned); + String dataToBeSignedURLEncoded = URLEncoder.encode(dataToBeSignedStringEncoded, StandardCharsets.UTF_8); + hashes.add(dataToBeSignedURLEncoded); } - return doc; + return hashes; } + + // i need the signing certificate before hand + public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String authorizationBearerHeader, X509Certificate certificate, List certificateChain, List signAlgo) throws Exception { + List hashes = calculateHashValue(signDocRequest.getDocuments(), certificate, certificateChain, signDocRequest.getHashAlgorithmOID()); + for(String s: hashes){ + System.out.println("signDoc: "+ s); + } + + SignaturesSignHashResponse signHashResponse = null; + try { + // As the current operation mode only supported is "S", the validity_period and response_uri do not need to be defined + SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest(signDocRequest.getCredentialID(), + null, hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, signDocRequest.getOperationMode(), + -1, null, signDocRequest.getClientData()); + + signHashResponse = qtspClient.requestSignHash(signDocRequest.getRequest_uri(), signHashRequest, authorizationBearerHeader); + } catch (Exception e) { + e.printStackTrace(); + } + + assert signHashResponse != null; + if(signHashResponse.getSignatures().size() != signDocRequest.getDocuments().size()){ + return new SignaturesSignDocResponse(); + } + + List allSignaturesObjects = new ArrayList<>(signHashResponse.getSignatures()); + List DocumentWithSignature = new ArrayList<>(); + for(int i = 0; i < signDocRequest.getDocuments().size(); i++){ + DocumentsSignDocRequest document = signDocRequest.getDocuments().get(i); + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + + byte[] signature = Base64.getDecoder().decode(signHashResponse.getSignatures().get(i)); + DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, certificate, certificateChain, signAlgo.get(0), signDocRequest.getHashAlgorithmOID()); + + try { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); + docSigned.save("tests/exampleSigned.pdf"); + + File file = new File("tests/exampleSigned.pdf"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // TODO: obtain the validation info.... + ValidationInfoSignDocResponse validationInfo = null; + if (signDocRequest.getReturnValidationInfo()) { + validationInfo = new ValidationInfoSignDocResponse(); + } + + return new SignaturesSignDocResponse(DocumentWithSignature, allSignaturesObjects, null, validationInfo); + } } From b0686e08d497455a44f17c41e39b37eb2fe99b11 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Tue, 10 Sep 2024 16:11:39 +0100 Subject: [PATCH 20/44] small fixes --- .../signer/r3/sca/Controllers/SignaturesController.java | 4 +--- .../eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index d5987b2..994c13a 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -43,9 +43,7 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe if (signDocRequest.getDocuments() != null) { try { - return this.signatureService.handleDocumentsSignDocRequest( - signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), - certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo() + return this.signatureService.handleDocumentsSignDocRequest(signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo() ); } catch (Exception e) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index dc43cb9..0b785ef 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -55,11 +55,14 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance System.out.println(documentToSign.getName()); + Date d = new Date(1725976542769L); + System.out.println(d.getTime()); + CertificateVerifier cv = new CommonCertificateVerifier(); ExternalCMSPAdESService service = new ExternalCMSPAdESService(cv); PAdESSignatureParameters parameters = new PAdESSignatureParameters(); - parameters.bLevel().setSigningDate(new Date()); + parameters.bLevel().setSigningDate(d); parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); parameters.setReason("DSS testing"); parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); @@ -70,7 +73,7 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance System.out.println("Message Digest: "+Base64.getEncoder().encodeToString(messageDigest.getValue())); PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.bLevel().setSigningDate(d); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { @@ -81,8 +84,6 @@ public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance signatureParameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); signatureParameters.setReason("DSS testing"); - - cv = new CommonCertificateVerifier(); ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); ToBeSigned dataToSign = cmsForPAdESGenerationService.getDataToSign(messageDigest, signatureParameters); From 2340217587f2aef3353bb378600bccee624ded59 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Tue, 10 Sep 2024 21:14:56 +0100 Subject: [PATCH 21/44] Fix date in signing request for tests. --- src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 0b785ef..fb2115f 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -108,7 +108,7 @@ public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signatur PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); + signatureParameters.bLevel().setSigningDate(new Date(1725976542769L)); signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); List certChainToken = new ArrayList<>(); for (X509Certificate cert : certificateChain) { From 382fb525228a9db2bd4722daf874e1df6d3e352a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:28:34 +0100 Subject: [PATCH 22/44] Clean Code, all levels working --- .gitignore | 1 + .../sca/Controllers/SignaturesController.java | 153 ++- .../ec/eudi/signer/r3/sca/DSS_Service.java | 1109 ++++------------- .../r3/sca/Models/SignatureDocumentForm.java | 130 ++ src/main/resources/config.properties | 3 + 5 files changed, 482 insertions(+), 914 deletions(-) create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/Models/SignatureDocumentForm.java create mode 100644 src/main/resources/config.properties diff --git a/.gitignore b/.gitignore index 999e5f4..18d823c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ target/ *.iws *.iml *.ipr +*.pem ### NetBeans ### /nbproject/private/ diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index a21317f..1e3d53e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -9,8 +9,10 @@ import java.util.Base64; import java.util.Date; import java.util.List; +import java.util.Properties; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,11 +26,21 @@ import eu.europa.ec.eudi.signer.r3.sca.DTO.ValidationInfoSignDocResponse; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.DocumentsSignDocRequest; import eu.europa.ec.eudi.signer.r3.sca.DTO.SignDocRequest.SignaturesSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.Models.SignatureDocumentForm; +import eu.europa.esig.dss.enumerations.ASiCContainerType; +import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.enumerations.EncryptionAlgorithm; import eu.europa.esig.dss.enumerations.MimeType; +import eu.europa.esig.dss.enumerations.SignatureForm; +import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.enumerations.SignaturePackaging; import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.x509.CertificateToken; +import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import jakarta.validation.Valid; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; @RestController @@ -43,13 +55,49 @@ public class SignaturesController { private X509Certificate signingCertificate; + private CommonTrustedCertificateSource certificateSource; + + public SignaturesController() throws Exception { + this.certificateSource= new CommonTrustedCertificateSource(); + + Properties properties = new Properties(); + + InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); + if (configStream == null) { + throw new Exception("Arquivo config.properties não encontrado!"); + } + + properties.load(configStream); + + String certificatePath = properties.getProperty("SigningCertificate"); + + if (certificatePath == null || certificatePath.isEmpty()) { + throw new Exception("Caminho do certificado não encontrado no arquivo de configuração!"); + } + + FileInputStream certInputStream = new FileInputStream(certificatePath); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - InputStream in = new ByteArrayInputStream(cert_bytes); - this.signingCertificate = (X509Certificate) certFactory.generateCertificate(in); + this.signingCertificate = (X509Certificate) certFactory.generateCertificate(certInputStream); + + String arrayOfStrings = properties.getProperty("TrustedCertificates"); - System.out.println(this.signingCertificate.toString()); + String [] teste= arrayOfStrings.split(";"); + System.out.println(teste); + + for ( String path : teste){ + if (path == null || path.isEmpty()) { + throw new Exception("Caminho do certificado não encontrado no arquivo de configuração!"); + } + FileInputStream certInput= new FileInputStream(path); + X509Certificate certificate= (X509Certificate) certFactory.generateCertificate(certInput); + this.certificateSource.addCertificate(new CertificateToken(certificate)); + + } + + certInputStream.close(); } @@ -103,58 +151,35 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc List allResponses = new ArrayList<>(); Date date = new Date(); + SignatureDocumentForm SignatureDocumentForm = new SignatureDocumentForm(); for (DocumentsSignDocRequest document : signDocRequest.getDocuments()) { DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); byte[] dataToBeSigned = null; - if (document.getSignature_format().equals("C")) { - - System.out.print("CAdES\n"); - if(document.getContainer().equals("ASiC-E")) { - dataToBeSigned = dssClient.cadesToBeSignedData_asic_E(dssDocument, document.getConformance_level(), - document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); - } - else if(document.getContainer().equals("ASiC-S")) { - dataToBeSigned = dssClient.cadesToBeSignedData_asic_S(dssDocument, document.getConformance_level(), - document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); - - } - else { - dataToBeSigned = dssClient.cadesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo(), date); - } - - } else if (document.getSignature_format().equals("P")) { - - System.out.print("PAdES\n"); - dataToBeSigned = dssClient.padesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>()); - System.out.println("Data To Be Signed Created"); - } else if (document.getSignature_format().equals("X")) { - - System.out.print("XAdES\n"); - if(document.getContainer().equals("ASiC-E")) { - dataToBeSigned = dssClient.xadesToBeSignedData_asic_E(dssDocument, document.getConformance_level(), - document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); - } - else if(document.getContainer().equals("ASiC-S")) { - dataToBeSigned = dssClient.xadesToBeSignedData_asic_S(dssDocument, document.getConformance_level(), - document.getSigned_envelope_property(), this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); - - } - else { - dataToBeSigned = dssClient.xadesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo(), date); - } - } else if (document.getSignature_format().equals("J")) { - System.out.print("JAdES\n"); + + SignatureLevel aux_sign_level = DSS_Service.checkConformance_level(document.getConformance_level(), document.getSignature_format()); + DigestAlgorithm aux_digest_alg = DSS_Service.checkSignAlgDigest(document.getSignAlgo()); + SignaturePackaging aux_sign_pack = DSS_Service.checkEnvProps(document.getSigned_envelope_property()); + ASiCContainerType aux_asic_ContainerType = DSS_Service.checkASiCContainerType(document.getContainer()); + SignatureForm signatureForm= DSS_Service.checkSignForm(document.getSignature_format()); - dataToBeSigned = dssClient.jadesToBeSignedData(dssDocument, - document.getConformance_level(), document.getSigned_envelope_property(), - this.signingCertificate, new ArrayList<>(), document.getSignAlgo()); - } + System.out.println(document.getSignature_format()); + + SignatureDocumentForm.setDocumentToSign(dssDocument); + SignatureDocumentForm.setSignaturePackaging(aux_sign_pack); + SignatureDocumentForm.setContainerType(aux_asic_ContainerType); + SignatureDocumentForm.setSignatureLevel(aux_sign_level); + SignatureDocumentForm.setDigestAlgorithm(aux_digest_alg); + SignatureDocumentForm.setSignatureForm(signatureForm); + SignatureDocumentForm.setCertificate(this.signingCertificate); + SignatureDocumentForm.setDate(date); + SignatureDocumentForm.setTrustedCertificates(this.certificateSource); + SignatureDocumentForm.setSignatureForm(signatureForm); + SignatureDocumentForm.setCertChain(new ArrayList<>()); + SignatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.ECDSA); + + System.out.println("/n/n before DataToBeSigned /n/n"); + dataToBeSigned = dssClient.DataToBeSignedData(SignatureDocumentForm); + System.out.println("\n\n after DataToBeSigned \n\n"); if (dataToBeSigned == null) { return new SignaturesSignDocResponse(); @@ -197,13 +222,33 @@ else if(document.getContainer().equals("ASiC-S")) { DocumentsSignDocRequest document = signDocRequest.getDocuments().get(0); DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + + SignatureLevel aux_sign_level = DSS_Service.checkConformance_level(document.getConformance_level(), document.getSignature_format()); + DigestAlgorithm aux_digest_alg = DSS_Service.checkSignAlgDigest(document.getSignAlgo()); + SignaturePackaging aux_sign_pack = DSS_Service.checkEnvProps(document.getSigned_envelope_property()); + ASiCContainerType aux_asic_ContainerType = DSS_Service.checkASiCContainerType(document.getContainer()); + SignatureForm signatureForm= DSS_Service.checkSignForm(document.getSignature_format()); + + SignatureDocumentForm.setDocumentToSign(dssDocument); + SignatureDocumentForm.setSignaturePackaging(aux_sign_pack); + SignatureDocumentForm.setContainerType(aux_asic_ContainerType); + SignatureDocumentForm.setSignatureLevel(aux_sign_level); + SignatureDocumentForm.setDigestAlgorithm(aux_digest_alg); + SignatureDocumentForm.setSignatureForm(signatureForm); + SignatureDocumentForm.setCertificate(this.signingCertificate); + SignatureDocumentForm.setDate(date); + SignatureDocumentForm.setTrustedCertificates(this.certificateSource); + SignatureDocumentForm.setSignatureForm(signatureForm); + SignatureDocumentForm.setCertChain(new ArrayList<>()); + SignatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.ECDSA); + if (response.getSignatures() != null) { byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); - DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, signingCertificate, - new ArrayList<>(), document.getSignAlgo(), document.getSignature_format(), document.getConformance_level(), - document.getSigned_envelope_property(), document.getContainer(), date); + SignatureDocumentForm.setSignatureValue(signature); + DSSDocument docSigned = dssClient.signDocument(SignatureDocumentForm); System.out.println(docSigned); + try { if (document.getContainer().equals("ASiC-E")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 9177f3b..8c0a1cd 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -1,42 +1,38 @@ package eu.europa.ec.eudi.signer.r3.sca; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.security.cert.Certificate; import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; -import java.util.Date; import java.util.List; import org.slf4j.event.Level; import org.springframework.stereotype.Service; +import eu.europa.ec.eudi.signer.r3.sca.Models.SignatureDocumentForm; import eu.europa.esig.dss.AbstractSignatureParameters; import eu.europa.esig.dss.alert.ExceptionOnStatusAlert; import eu.europa.esig.dss.alert.LogOnStatusAlert; import eu.europa.esig.dss.asic.cades.ASiCWithCAdESSignatureParameters; +import eu.europa.esig.dss.asic.cades.ASiCWithCAdESTimestampParameters; import eu.europa.esig.dss.asic.cades.signature.ASiCWithCAdESService; import eu.europa.esig.dss.asic.xades.ASiCWithXAdESSignatureParameters; import eu.europa.esig.dss.asic.xades.signature.ASiCWithXAdESService; import eu.europa.esig.dss.cades.CAdESSignatureParameters; import eu.europa.esig.dss.cades.signature.CAdESService; import eu.europa.esig.dss.cades.signature.CAdESTimestampParameters; -import eu.europa.esig.dss.cades.signature.CMSSignedDocument; import eu.europa.esig.dss.enumerations.ASiCContainerType; import eu.europa.esig.dss.enumerations.DigestAlgorithm; import eu.europa.esig.dss.enumerations.JWSSerializationType; import eu.europa.esig.dss.enumerations.SigDMechanism; import eu.europa.esig.dss.enumerations.SignatureAlgorithm; +import eu.europa.esig.dss.enumerations.SignatureForm; import eu.europa.esig.dss.enumerations.SignatureLevel; import eu.europa.esig.dss.enumerations.SignaturePackaging; import eu.europa.esig.dss.model.DSSDocument; -import eu.europa.esig.dss.model.DSSMessageDigest; -import eu.europa.esig.dss.model.DigestDocument; import eu.europa.esig.dss.model.InMemoryDocument; import eu.europa.esig.dss.model.SignatureValue; +import eu.europa.esig.dss.model.TimestampParameters; import eu.europa.esig.dss.model.ToBeSigned; import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.pades.PAdESSignatureParameters; @@ -44,28 +40,20 @@ import eu.europa.esig.dss.xades.XAdESSignatureParameters; import eu.europa.esig.dss.xades.XAdESTimestampParameters; import eu.europa.esig.dss.xades.signature.XAdESService; -import eu.europa.esig.dss.jades.HTTPHeader; -import eu.europa.esig.dss.jades.HTTPHeaderDigest; import eu.europa.esig.dss.jades.JAdESSignatureParameters; +import eu.europa.esig.dss.jades.JAdESTimestampParameters; import eu.europa.esig.dss.jades.signature.JAdESService; -import eu.europa.esig.dss.pades.signature.ExternalCMSService; import eu.europa.esig.dss.pades.signature.PAdESService; import eu.europa.esig.dss.service.SecureRandomNonceSource; import eu.europa.esig.dss.service.crl.OnlineCRLSource; import eu.europa.esig.dss.service.http.commons.CommonsDataLoader; import eu.europa.esig.dss.service.http.commons.OCSPDataLoader; import eu.europa.esig.dss.service.http.commons.TimestampDataLoader; -import eu.europa.esig.dss.service.http.proxy.ProxyConfig; import eu.europa.esig.dss.service.ocsp.OnlineOCSPSource; import eu.europa.esig.dss.service.tsp.OnlineTSPSource; import eu.europa.esig.dss.signature.DocumentSignatureService; -import eu.europa.esig.dss.spi.client.http.DataLoader; -import eu.europa.esig.dss.spi.client.http.Protocol; -import eu.europa.esig.dss.spi.tsl.TrustedListsCertificateSource; +import eu.europa.esig.dss.signature.MultipleDocumentsSignatureService; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; -import eu.europa.esig.dss.spi.x509.aia.AIASource; -import eu.europa.esig.dss.spi.x509.aia.DefaultAIASource; -import eu.europa.esig.dss.spi.x509.aia.OnlineAIASource; import eu.europa.esig.dss.validation.CertificateVerifier; import eu.europa.esig.dss.validation.CommonCertificateVerifier; import eu.europa.esig.dss.validation.OCSPFirstRevocationDataLoadingStrategyFactory; @@ -74,8 +62,8 @@ @Service public class DSS_Service { - public static SignatureLevel checkConformance_level(String conformance_level, char type) { - String enumValue = mapToEnumValue(conformance_level, type); + public static SignatureLevel checkConformance_level(String conformance_level, String string) { + String enumValue = mapToEnumValue(conformance_level, string); if (enumValue == null) { return null; } @@ -89,19 +77,19 @@ public static SignatureLevel checkConformance_level(String conformance_level, ch return null; } - private static String mapToEnumValue(String conformance_level, char type) { + private static String mapToEnumValue(String conformance_level, String string) { String prefix; - switch (type) { - case 'p': + switch (string) { + case "P": prefix = "PAdES_BASELINE_"; break; - case 'c': + case "C": prefix = "CAdES_BASELINE_"; break; - case 'j': + case "J": prefix = "JAdES_BASELINE_"; break; - case 'x': + case "X": prefix = "XAdES_BASELINE_"; break; default: @@ -122,6 +110,21 @@ private static String mapToEnumValue(String conformance_level, char type) { } } + public static SignatureForm checkSignForm(String signForm) { + switch (signForm) { + case "P": + return SignatureForm.PAdES; + case "C": + return SignatureForm.CAdES; + case "J": + return SignatureForm.JAdES; + case "X": + return SignatureForm.XAdES; + default: + return null; + } + } + private static SignatureAlgorithm checkSignAlg(String alg) { switch (alg) { case "1.2.840.113549.1.1.11": @@ -135,7 +138,7 @@ private static SignatureAlgorithm checkSignAlg(String alg) { } } - private static DigestAlgorithm checkSignAlgDigest(String alg) { + public static DigestAlgorithm checkSignAlgDigest(String alg) { switch (alg) { case "1.2.840.113549.1.1.11": return DigestAlgorithm.SHA256; @@ -148,7 +151,20 @@ private static DigestAlgorithm checkSignAlgDigest(String alg) { } } - private static SignaturePackaging checkEnvProps(String env) { + public static ASiCContainerType checkASiCContainerType(String alg) { + switch (alg) { + case "No": + return null; + case "ASiC-E": + return ASiCContainerType.ASiC_E; + case "ASiC-S": + return ASiCContainerType.ASiC_S; + default: + return null; + } + } + + public static SignaturePackaging checkEnvProps(String env) { switch (env) { case "ENVELOPED": return SignaturePackaging.ENVELOPED; @@ -172,906 +188,279 @@ public void test() { System.out.println("This is a test."); } - // importante parameters: conformance_level, signed_envelope_property - @SuppressWarnings("rawtypes") - public byte[] cadesToBeSignedData(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg, Date date) throws CertificateException { - - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - InputStream tsa = new ByteArrayInputStream(tsa_bytes); - InputStream in = new ByteArrayInputStream(cert_bytes); - X509Certificate trustedCertificate = (X509Certificate) certFactory.generateCertificate(in); - X509Certificate TsaCertificate = (X509Certificate) certFactory.generateCertificate(tsa); - - CertificateVerifier cv = new CommonCertificateVerifier(); - - CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(date); - System.out.println(new Date()); + @SuppressWarnings({ "rawtypes", "unchecked" }) + public byte[] DataToBeSignedData(SignatureDocumentForm form) throws CertificateException { - CertificateToken certToken= new CertificateToken(signingCertificate); - signatureParameters.setSigningCertificate(certToken); - // certificateChain.add(signingCertificate); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } + DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), + form.getTrustedCertificates()); - CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); - certificateSource.addCertificate(new CertificateToken(trustedCertificate)); - certificateSource.addCertificate(new CertificateToken(TsaCertificate)); - cv.setTrustedCertSources(certificateSource); - // for (CertificateToken certificateToken : certChainToken) { - // certificateSource.addCertificate(certificateToken); - // } - //signatureParameters.setCertificateChain(certToken); - - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - CAdESTimestampParameters timestampParameters = new CAdESTimestampParameters(aux_digest_alg); - signatureParameters.setSignatureTimestampParameters(timestampParameters); - signatureParameters.setArchiveTimestampParameters(timestampParameters); - signatureParameters.setContentTimestampParameters(timestampParameters); - // signatureParameters.setGenerateTBSWithoutCertificate(true); - - System.out.print("2CAdES\n"); + System.out.println("/n/n Teste1 /n/n"); + AbstractSignatureParameters parameters = fillParameters(form); - cv.setCheckRevocationForUntrustedChains(false); + DSSDocument toSignDocument = form.getDocumentToSign(); + ToBeSigned toBeSigned = service.getDataToSign(toSignDocument, parameters); + return toBeSigned.getBytes(); - OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); - onlineCRLSource.setDataLoader(new CommonsDataLoader()); - cv.setCrlSource(onlineCRLSource); - - OnlineOCSPSource onlineOCSPSource = new OnlineOCSPSource(); - onlineOCSPSource.setDataLoader(new OCSPDataLoader()); - onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); - cv.setOcspSource(onlineOCSPSource); - - // Capability to download resources from AIA - cv.setAIASource(new DefaultAIASource()); - - cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); - - cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); - - cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); - - cv.setAlertOnRevokedCertificate(new ExceptionOnStatusAlert()); + } - cv.setAlertOnInvalidTimestamp(new ExceptionOnStatusAlert()); + @SuppressWarnings({ "rawtypes", "unchecked" }) + public DSSDocument signDocument(SignatureDocumentForm form) { - cv.setAlertOnNoRevocationAfterBestSignatureTime(new LogOnStatusAlert(Level.ERROR)); + DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), + form.getTrustedCertificates()); - cv.setAlertOnExpiredSignature(new ExceptionOnStatusAlert()); + AbstractSignatureParameters parameters = fillParameters(form); - cv.setRevocationDataLoadingStrategyFactory(new OCSPFirstRevocationDataLoadingStrategyFactory()); - - RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); - cv.setRevocationDataVerifier(revocationDataVerifier); - - cv.setRevocationFallback(true); + DSSDocument toSignDocument = form.getDocumentToSign(); + SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.getAlgorithm(form.getEncryptionAlgorithm(), + form.getDigestAlgorithm()); - CAdESService cmsForCAdESGenerationService = new CAdESService(cv); - String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; - OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); - onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type - cmsForCAdESGenerationService.setTspSource(onlineTSPSource); + SignatureValue signatureValue = new SignatureValue(); + signatureValue.setAlgorithm(sigAlgorithm); + signatureValue.setValue(form.getSignatureValue()); - ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3CAdES\n"); - return dataToSign.getBytes(); + DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue); + return signedDocument; } - @SuppressWarnings("rawtypes") - public byte[] cadesToBeSignedData_asic_E(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { - - CertificateVerifier cv = new CommonCertificateVerifier(); - - ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + @SuppressWarnings({ "rawtypes" }) + private AbstractSignatureParameters fillParameters(SignatureDocumentForm form) { - System.out.print("2CAdES\n"); + AbstractSignatureParameters parameters = getSignatureParameters(form.getContainerType(), + form.getSignatureForm()); + parameters.setSignaturePackaging(form.getSignaturePackaging()); - cv = new CommonCertificateVerifier(); - ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); - ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3CAdES\n"); - return dataToSign.getBytes(); + fillParameters(parameters, form); + return parameters; } - @SuppressWarnings("rawtypes") - public byte[] cadesToBeSignedData_asic_S(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void fillParameters(AbstractSignatureParameters parameters, SignatureDocumentForm form) { - CertificateVerifier cv = new CommonCertificateVerifier(); + parameters.setSignatureLevel(form.getSignatureLevel()); + parameters.setDigestAlgorithm(form.getDigestAlgorithm()); + // parameters.setEncryptionAlgorithm(form.getEncryptionAlgorithm()); retrieved + // from certificate + parameters.bLevel().setSigningDate(form.getDate()); + + CertificateToken signingCertificate = new CertificateToken(form.getCertificate()); + parameters.setSigningCertificate(signingCertificate); - ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); + List certificateChainBytes = form.getCertChain(); List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { + for (X509Certificate cert : certificateChainBytes) { certChainToken.add(new CertificateToken(cert)); } - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); - - System.out.print("2CAdES\n"); - - cv = new CommonCertificateVerifier(); - ASiCWithCAdESService cmsForCAdESGenerationService = new ASiCWithCAdESService(cv); - ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3CAdES\n"); - return dataToSign.getBytes(); + parameters.setCertificateChain(certChainToken); + fillTimestampParameters(parameters, form); } - @SuppressWarnings("rawtypes") - public byte[] xadesToBeSignedData(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg, Date date) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void fillTimestampParameters(AbstractSignatureParameters parameters, SignatureDocumentForm form) { + SignatureForm signatureForm = form.getSignatureForm(); - CertificateVerifier cv = new CommonCertificateVerifier(); + ASiCContainerType containerType = null; + containerType = form.getContainerType(); - XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(date); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); - System.out.println("\n\n SIGN LEVEL: " + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n DIGEST ALG: " + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n SIGN PACK: " + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - XAdESTimestampParameters timestampParameters = new XAdESTimestampParameters(aux_digest_alg); - signatureParameters.setSignatureTimestampParameters(timestampParameters); - signatureParameters.setArchiveTimestampParameters(timestampParameters); - signatureParameters.setContentTimestampParameters(timestampParameters); - - // signatureParameters.setReason("DSS testing"); - System.out.print("2XAdES\n"); - - cv = new CommonCertificateVerifier(); - XAdESService cmsForXAdESGenerationService = new XAdESService(cv); - String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; - OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); - onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type - cmsForXAdESGenerationService.setTspSource(onlineTSPSource); - - ToBeSigned dataToSign = cmsForXAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3XAdES\n"); - return dataToSign.getBytes(); + TimestampParameters timestampParameters = getTimestampParameters(containerType, signatureForm); + timestampParameters.setDigestAlgorithm(form.getDigestAlgorithm()); + parameters.setContentTimestampParameters(timestampParameters); + parameters.setSignatureTimestampParameters(timestampParameters); + parameters.setArchiveTimestampParameters(timestampParameters); } @SuppressWarnings("rawtypes") - public byte[] xadesToBeSignedData_asic_E(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + private DocumentSignatureService getSignatureService(ASiCContainerType containerType, SignatureForm signatureForm, + CommonTrustedCertificateSource TrustedCertificates) { - CertificateVerifier cv = new CommonCertificateVerifier(); + System.out.println("\n\n" + containerType + "\n\n"); - ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } + CertificateVerifier cv = new CommonCertificateVerifier(); - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + cv.setTrustedCertSources(TrustedCertificates); + cv.setCheckRevocationForUntrustedChains(false); - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); + OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); + onlineCRLSource.setDataLoader(new CommonsDataLoader()); + cv.setCrlSource(onlineCRLSource); - System.out.print("2XAdES\n"); + OnlineOCSPSource onlineOCSPSource = new OnlineOCSPSource(); + onlineOCSPSource.setDataLoader(new OCSPDataLoader()); + onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); + cv.setOcspSource(null); - cv = new CommonCertificateVerifier(); - ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); - ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3XAdES\n"); - return dataToSign.getBytes(); + // Capability to download resources from AIA + cv.setAIASource(null); - } + // cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); - @SuppressWarnings("rawtypes") - public byte[] xadesToBeSignedData_asic_S(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); - CertificateVerifier cv = new CommonCertificateVerifier(); + cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); - ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } + cv.setAlertOnRevokedCertificate(new ExceptionOnStatusAlert()); - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'x'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); + cv.setAlertOnInvalidTimestamp(new ExceptionOnStatusAlert()); - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); + cv.setAlertOnNoRevocationAfterBestSignatureTime(new LogOnStatusAlert(Level.ERROR)); - System.out.print("2XAdES\n"); + cv.setAlertOnExpiredSignature(new ExceptionOnStatusAlert()); - cv = new CommonCertificateVerifier(); - ASiCWithXAdESService cmsForCAdESGenerationService = new ASiCWithXAdESService(cv); - ToBeSigned dataToSign = cmsForCAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3XAdES\n"); - return dataToSign.getBytes(); + cv.setRevocationDataLoadingStrategyFactory(new OCSPFirstRevocationDataLoadingStrategyFactory()); - } + RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); + cv.setRevocationDataVerifier(revocationDataVerifier); - public byte[] jadesToBeSignedData(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain, String signAlg) { + cv.setRevocationFallback(true); - CertificateVerifier cv = new CommonCertificateVerifier(); + System.out.println("\n\n" + signatureForm + "\n\n"); - JAdESSignatureParameters signatureParameters = new JAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setJwsSerializationType(JWSSerializationType.COMPACT_SERIALIZATION); - - SignatureLevel aux_sign_level = checkConformance_level(conformance_level, 'j'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(signed_envelope_property); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - // signatureParameters.setReason("DSS testing"); - System.out.print("2JAdES\n"); - - if (signed_envelope_property.equals("DETACHED")) { - System.out.println("\n\n JADES detached \n\n"); - signatureParameters.setSigDMechanism(SigDMechanism.HTTP_HEADERS); - signatureParameters.setBase64UrlEncodedPayload(false); - List documentsToSign = new ArrayList<>(); - documentsToSign.add(new HTTPHeader("content-type", "application/json")); - documentsToSign.add(new HTTPHeaderDigest(documentToSign, DigestAlgorithm.SHA256)); - - cv = new CommonCertificateVerifier(); - JAdESService cmsForJAdESGenerationService = new JAdESService(cv); - ToBeSigned dataToSign = cmsForJAdESGenerationService.getDataToSign(documentsToSign, signatureParameters); - System.out.print("3JAdES\n"); - return dataToSign.getBytes(); + DocumentSignatureService service = null; + if (containerType != null) { + service = (DocumentSignatureService) getASiCSignatureService(signatureForm, cv); } else { - cv = new CommonCertificateVerifier(); - JAdESService cmsForJAdESGenerationService = new JAdESService(cv); - ToBeSigned dataToSign = cmsForJAdESGenerationService.getDataToSign(documentToSign, signatureParameters); - System.out.print("3JAdES\n"); - return dataToSign.getBytes(); + switch (signatureForm) { + case CAdES: + service = new CAdESService(cv); + break; + case PAdES: + service = new PAdESService(cv); + break; + case XAdES: + service = new XAdESService(cv); + break; + case JAdES: + service = new JAdESService(cv); + break; + default: + throw new IllegalArgumentException(String.format("Unknown signature form : %s", signatureForm)); + } } + System.out.println("\n\n" + service.toString() + "\n\n"); + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; + OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); + onlineTSPSource.setDataLoader(new TimestampDataLoader()); + service.setTspSource(onlineTSPSource); + return service; } - // -------------------- - - public byte[] padesToBeSignedData(DSSDocument documentToSign, String conformance_level, - String signed_envelope_property, X509Certificate signingCertificate, - List certificateChain) { - - CertificateVerifier cv = new CommonCertificateVerifier(); - ExternalCMSPAdESService service = new ExternalCMSPAdESService(cv); - - PAdESSignatureParameters parameters = new PAdESSignatureParameters(); - parameters.bLevel().setSigningDate(new Date()); - parameters.setGenerateTBSWithoutCertificate(true); - - SignatureLevel aux_conf_level = checkConformance_level(conformance_level, 'p'); - - parameters.setSignatureLevel(aux_conf_level); - parameters.setReason("DSS testing"); - - DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, parameters); - - PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - signatureParameters.setSignatureLevel(aux_conf_level); - signatureParameters.setReason("DSS testing"); - - cv = new CommonCertificateVerifier(); - ExternalCMSService cmsForPAdESGenerationService = new ExternalCMSService(cv); - ToBeSigned dataToSign = cmsForPAdESGenerationService.getDataToSign(messageDigest, signatureParameters); - System.out.println("pades funcao 1 \n\n\n"); - return dataToSign.getBytes(); - } - - public DSSDocument getSignedDocument(DSSDocument documentToSign, byte[] signature, - X509Certificate signingCertificate, - List certificateChain, String signAlg, String sign_format, String conform_level, - String envelope_props, - String container, Date date) throws CertificateException { - - SignatureValue signatureValue = new SignatureValue(); - SignatureAlgorithm aux_alg = checkSignAlg(signAlg); - signatureValue.setAlgorithm(SignatureAlgorithm.ECDSA_SHA256); - signatureValue.setValue(signature); - System.out.println("\n\n" + aux_alg + "\n\n\n"); - - CertificateVerifier cv = new CommonCertificateVerifier(); - - if (sign_format.equals("C")) { - if (container.equals("ASiC-E")) { - System.out.print("CAdES ASiC-E\n"); - ASiCWithCAdESService service = new ASiCWithCAdESService(cv); - ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - - service = new ASiCWithCAdESService(cv); - return service.signDocument(documentToSign, signatureParameters, signatureValue); - } else if (container.equals("ASiC-S")) { - System.out.print("CAdES ASiC-S\n"); - ASiCWithCAdESService service = new ASiCWithCAdESService(cv); - ASiCWithCAdESSignatureParameters signatureParameters = new ASiCWithCAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); - + @SuppressWarnings("rawtypes") + private MultipleDocumentsSignatureService getASiCSignatureService(SignatureForm signatureForm, + CertificateVerifier cv) { + MultipleDocumentsSignatureService service = null; + switch (signatureForm) { + case CAdES: service = new ASiCWithCAdESService(cv); - return service.signDocument(documentToSign, signatureParameters, signatureValue); - - } else { - System.out.print("CAdES\n"); - - - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - InputStream tsa = new ByteArrayInputStream(tsa_bytes); - InputStream in = new ByteArrayInputStream(cert_bytes); - X509Certificate trustedCertificate = (X509Certificate) certFactory.generateCertificate(in); - X509Certificate TsaCertificate = (X509Certificate) certFactory.generateCertificate(tsa); - - CAdESSignatureParameters signatureParameters = new CAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(date); - System.out.println(signingCertificate.getSerialNumber()); - CertificateToken certToken= new CertificateToken(signingCertificate); - System.out.println(certToken.getIssuer().getPrettyPrintRFC2253()); - signatureParameters.setSigningCertificate(certToken); - - List certChainToken = new ArrayList<>(); - certChainToken.add(certToken); - - // for (X509Certificate cert : certificateChain) { - - // certChainToken.add(new CertificateToken(cert)); - // } - // System.out.println(); - CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); - certificateSource.addCertificate(new CertificateToken(trustedCertificate)); - certificateSource.addCertificate(new CertificateToken(TsaCertificate)); - cv.setTrustedCertSources(certificateSource); - - // for (CertificateToken certificateToken : certChainToken) { - // certificateSource.addCertificate(certificateToken); - // } - //signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'c'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - CAdESTimestampParameters timestampParameters = new CAdESTimestampParameters(aux_digest_alg); - signatureParameters.setSignatureTimestampParameters(timestampParameters); - signatureParameters.setArchiveTimestampParameters(timestampParameters); - signatureParameters.setContentTimestampParameters(timestampParameters); - signatureParameters.setGenerateTBSWithoutCertificate(true); - - cv.setCheckRevocationForUntrustedChains(false); - - OnlineCRLSource onlineCRLSource = new OnlineCRLSource(); - onlineCRLSource.setDataLoader(new CommonsDataLoader()); - cv.setCrlSource(onlineCRLSource); - - OnlineOCSPSource onlineOCSPSource = new OnlineOCSPSource(); - onlineOCSPSource.setDataLoader(new OCSPDataLoader()); - onlineOCSPSource.setNonceSource(new SecureRandomNonceSource()); - cv.setOcspSource(null); - - // Capability to download resources from AIA - cv.setAIASource(null); - - cv.setDefaultDigestAlgorithm(DigestAlgorithm.SHA256); - cv.setAlertOnMissingRevocationData(new ExceptionOnStatusAlert()); - - cv.setAlertOnUncoveredPOE(new LogOnStatusAlert(Level.WARN)); - - cv.setAlertOnRevokedCertificate(new ExceptionOnStatusAlert()); - - cv.setAlertOnInvalidTimestamp(new ExceptionOnStatusAlert()); - - cv.setAlertOnNoRevocationAfterBestSignatureTime(new LogOnStatusAlert(Level.ERROR)); - - cv.setAlertOnExpiredSignature(new ExceptionOnStatusAlert()); - - cv.setRevocationDataLoadingStrategyFactory(new OCSPFirstRevocationDataLoadingStrategyFactory()); - - RevocationDataVerifier revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); - cv.setRevocationDataVerifier(revocationDataVerifier); - - cv.setRevocationFallback(true); - - CAdESService service = new CAdESService(cv); - System.out.println("teste"); - String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; - OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); - onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type - service.setTspSource(onlineTSPSource); - - DSSDocument signed_document = service.signDocument(documentToSign, signatureParameters, signatureValue); - System.out.println("teste2"); - - return signed_document; - } - - } else if (sign_format.equals("P")) { - System.out.print("PAdES\n"); - PAdESService service = new PAdESService(cv); - PAdESSignatureParameters signatureParameters = new PAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'p'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - - service = new PAdESService(cv); - return service.signDocument(documentToSign, signatureParameters, signatureValue); - - } else if (sign_format.equals("X")) { - if (container.equals("ASiC-E")) { - System.out.print("XAdES ASiC-E\n"); - ASiCWithXAdESService service = new ASiCWithXAdESService(cv); - ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_E); - + break; + case XAdES: service = new ASiCWithXAdESService(cv); - return service.signDocument(documentToSign, signatureParameters, signatureValue); - } else if (container.equals("ASiC-S")) { - System.out.print("XAdES ASiC-S\n"); - ASiCWithXAdESService service = new ASiCWithXAdESService(cv); - ASiCWithXAdESSignatureParameters signatureParameters = new ASiCWithXAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.aSiC().setContainerType(ASiCContainerType.ASiC_S); + break; + default: + throw new IllegalArgumentException( + String.format("Not supported signature form for an ASiC container : %s", signatureForm)); + } + return service; + } - service = new ASiCWithXAdESService(cv); - return service.signDocument(documentToSign, signatureParameters, signatureValue); - - } else { - System.out.print("XAdES\n"); - XAdESService service = new XAdESService(cv); - XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(date); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'x'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - XAdESTimestampParameters timestampParameters = new XAdESTimestampParameters(aux_digest_alg); - signatureParameters.setSignatureTimestampParameters(timestampParameters); - signatureParameters.setArchiveTimestampParameters(timestampParameters); - signatureParameters.setContentTimestampParameters(timestampParameters); - - service = new XAdESService(cv); - System.out.println("teste"); - String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; - OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); - onlineTSPSource.setDataLoader(new TimestampDataLoader()); // uses the specific content-type - service.setTspSource(onlineTSPSource); - - return service.signDocument(documentToSign, signatureParameters, signatureValue); + private TimestampParameters getTimestampParameters(ASiCContainerType containerType, SignatureForm signatureForm) { + TimestampParameters parameters = null; + if (containerType == null) { + switch (signatureForm) { + case CAdES: + parameters = new CAdESTimestampParameters(); + break; + case XAdES: + parameters = new XAdESTimestampParameters(); + break; + case PAdES: + parameters = new PAdESTimestampParameters(); + break; + case JAdES: + parameters = new JAdESTimestampParameters(); + break; + default: + throw new IllegalArgumentException( + String.format("Not supported signature form for a time-stamp : %s", signatureForm)); } - } else if (sign_format.equals("J")) { - System.out.print("JAdES\n"); - JAdESService service = new JAdESService(cv); - JAdESSignatureParameters signatureParameters = new JAdESSignatureParameters(); - - signatureParameters.bLevel().setSigningDate(new Date()); - signatureParameters.setSigningCertificate(new CertificateToken(signingCertificate)); - List certChainToken = new ArrayList<>(); - for (X509Certificate cert : certificateChain) { - certChainToken.add(new CertificateToken(cert)); - } - signatureParameters.setCertificateChain(certChainToken); - - SignatureLevel aux_sign_level = checkConformance_level(conform_level, 'j'); - System.out.println("\n\n" + aux_sign_level + "\n\n"); - DigestAlgorithm aux_digest_alg = checkSignAlgDigest(signAlg); - System.out.println("\n\n" + aux_digest_alg + "\n\n\n"); - SignaturePackaging aux_sign_pack = checkEnvProps(envelope_props); - System.out.println("\n\n" + aux_sign_pack + "\n\n\n"); - - signatureParameters.setSignatureLevel(aux_sign_level); - signatureParameters.setDigestAlgorithm(aux_digest_alg); - signatureParameters.setSignaturePackaging(aux_sign_pack); - signatureParameters.setJwsSerializationType(JWSSerializationType.COMPACT_SERIALIZATION); - - if (envelope_props.equals("DETACHED")) { - System.out.println("\n\n JADES detached \n\n"); - signatureParameters.setSigDMechanism(SigDMechanism.HTTP_HEADERS); - signatureParameters.setBase64UrlEncodedPayload(false); - List documentsToSign = new ArrayList<>(); - documentsToSign.add(new HTTPHeader("content-type", "application/json")); - documentsToSign.add(new HTTPHeaderDigest(documentToSign, DigestAlgorithm.SHA256)); - service = new JAdESService(cv); - return service.signDocument(documentsToSign, signatureParameters, signatureValue); - } else { - service = new JAdESService(cv); - return service.signDocument(documentToSign, signatureParameters, signatureValue); + } else { + switch (signatureForm) { + case CAdES: + ASiCWithCAdESTimestampParameters asicParameters = new ASiCWithCAdESTimestampParameters(); + asicParameters.aSiC().setContainerType(containerType); + parameters = asicParameters; + break; + case XAdES: + parameters = new XAdESTimestampParameters(); + break; + default: + throw new IllegalArgumentException( + String.format("Not supported signature form for an ASiC time-stamp : %s", signatureForm)); } - } - - System.out.println("\n\n null \n\n"); - - // Stateless - return null; + return parameters; } - private static class ExternalCMSPAdESService extends PAdESService { - private static final long serialVersionUID = -2003453716888412577L; - private byte[] cmsSignedData; - - public ExternalCMSPAdESService(CertificateVerifier certificateVerifier) { - super(certificateVerifier); - } - - public DSSMessageDigest getMessageDigest(DSSDocument documentToSign, PAdESSignatureParameters parameters) { - return super.computeDocumentDigest(documentToSign, parameters); - } - - @Override - protected byte[] generateCMSSignedData(final DSSDocument toSignDocument, - final PAdESSignatureParameters parameters, - final SignatureValue signatureValue) { - if (this.cmsSignedData == null) { - throw new NullPointerException("A CMS signed data must be provided"); + @SuppressWarnings({ "rawtypes" }) + private AbstractSignatureParameters getSignatureParameters(ASiCContainerType containerType, + SignatureForm signatureForm) { + AbstractSignatureParameters parameters = null; + if (containerType != null) { + parameters = getASiCSignatureParameters(containerType, signatureForm); + } else { + switch (signatureForm) { + case CAdES: + parameters = new CAdESSignatureParameters(); + break; + case PAdES: + PAdESSignatureParameters padesParams = new PAdESSignatureParameters(); + padesParams.setContentSize(9472 * 2); // double reserved space for signature + parameters = padesParams; + break; + case XAdES: + parameters = new XAdESSignatureParameters(); + break; + case JAdES: + JAdESSignatureParameters jadesParameters = new JAdESSignatureParameters(); + jadesParameters.setJwsSerializationType(JWSSerializationType.JSON_SERIALIZATION); // to allow T+ + // levels + + // parallel + // signing + jadesParameters.setSigDMechanism(SigDMechanism.OBJECT_ID_BY_URI_HASH); // to use by default + parameters = jadesParameters; + break; + default: + throw new IllegalArgumentException(String.format("Unknown signature form : %s", signatureForm)); } - return this.cmsSignedData; } + return parameters; + } - public void setCmsSignedData(final byte[] cmsSignedData) { - this.cmsSignedData = cmsSignedData; + @SuppressWarnings({ "rawtypes" }) + private AbstractSignatureParameters getASiCSignatureParameters(ASiCContainerType containerType, + SignatureForm signatureForm) { + AbstractSignatureParameters parameters = null; + switch (signatureForm) { + case CAdES: + ASiCWithCAdESSignatureParameters asicCadesParams = new ASiCWithCAdESSignatureParameters(); + asicCadesParams.aSiC().setContainerType(containerType); + parameters = asicCadesParams; + break; + case XAdES: + ASiCWithXAdESSignatureParameters asicXadesParams = new ASiCWithXAdESSignatureParameters(); + asicXadesParams.aSiC().setContainerType(containerType); + parameters = asicXadesParams; + break; + default: + throw new IllegalArgumentException( + String.format("Not supported signature form for an ASiC container : %s", signatureForm)); } - + return parameters; } - - /* - * public byte[] padesToBeSignedData2(DSSDocument document, String - * conformance_level, String signed_envelope_property, - * X509Certificate signingCertificate, List certificateChain) { - * - * CertificateVerifier cv = new CommonCertificateVerifier(); - * ExternalCMSService padesCMSGeneratorService = new ExternalCMSService(cv); - * - * PAdESSignatureParameters parameters = new PAdESSignatureParameters(); - * parameters.bLevel().setSigningDate(new Date()); - * parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - * parameters.setReason("DSS testing"); - * // parameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); - * - * PAdESSignatureParameters signatureParameters = new - * PAdESSignatureParameters(); - * signatureParameters.setSigningCertificate(new - * CertificateToken(signingCertificate)); - * List certChainToken = new ArrayList<>(); - * for (X509Certificate cert : certificateChain) { - * certChainToken.add(new CertificateToken(cert)); - * } - * signatureParameters.setCertificateChain(certChainToken); - * signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - * signatureParameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); - * - * ToBeSigned dataToSign = padesCMSGeneratorService.getDataToSign(messageDigest, - * signatureParameters); - * return dataToSign.getBytes(); - * } - * - * // update to return doc sign and not only the CMSSignedData - * public byte[] createDocumentWithSignature(DSSDocument document, byte[] - * signature, - * X509Certificate signingCertificate, - * List certificateChain) { - * - * CertificateVerifier cv = new CommonCertificateVerifier(); - * ExternalCMSService padesCMSGeneratorService = new ExternalCMSService(cv); - * - * SignatureValue signatureValue = new SignatureValue(); - * signatureValue.setAlgorithm(SignatureAlgorithm.RSA_SHA256); - * signatureValue.setValue(signature); - * - * PAdESSignatureParameters signatureParameters = new - * PAdESSignatureParameters(); - * signatureParameters.setSigningCertificate(new - * CertificateToken(signingCertificate)); - * List certChainToken = new ArrayList<>(); - * for (X509Certificate cert : certificateChain) { - * certChainToken.add(new CertificateToken(cert)); - * } - * signatureParameters.setCertificateChain(certChainToken); - * signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - * signatureParameters.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); - * - * // Create a CMS signature using the provided message-digest, signature - * // parameters and the signature value - * CMSSignedDocument cmsSignature = - * padesCMSGeneratorService.signMessageDigest(messageDigest, - * signatureParameters, - * signatureValue); - * CMSSignedData signedData = cmsSignature.getCMSSignedData(); - * signedData.getSignerInfos().getSigners() - * .forEach(x -> System.out.println(x.getEncryptionAlgOID() + " & " + - * x.getDigestAlgOID())); // sha256WithRSAEncryption - * // & - * System.out.println(signedData.getSignedContentTypeOID()); // data - * for (AlgorithmIdentifier alg : signedData.getDigestAlgorithmIDs()) { // - * // sha-256 - * System.out.println(alg.toASN1Primitive().toString()); - * } - * return cmsSignature.getCMSSignedData().getEncoded(); - * } - */ - - // ------------------ - - /* - * public DSSDocument example1(DSSDocument documentToSign, String - * conformance_level, String signed_envelope_property, - * X509Certificate signingCertificate, List certificateChain) { - * PAdESWithExternalCMSService service = new PAdESWithExternalCMSService(); - * - * PAdESSignatureParameters parameters = new PAdESSignatureParameters(); - * parameters.bLevel().setSigningDate(new Date()); - * parameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - * parameters.setReason("DSS testing"); - * - * DSSMessageDigest messageDigest = service.getMessageDigest(documentToSign, - * parameters); - * - * // -------------------------- - * - * PAdESSignatureParameters signatureParameters = new - * PAdESSignatureParameters(); - * signatureParameters.bLevel().setSigningDate(new Date()); - * signatureParameters.setSigningCertificate(getSigningCert()); - * signatureParameters.setCertificateChain(getCertificateChain()); - * signatureParameters.setSignatureLevel(SignatureLevel.PAdES_BASELINE_B); - * signatureParameters.setReason("DSS testing"); - * - * PAdESSignerInfoGeneratorBuilder padesCMSSignedDataBuilder = new - * PAdESSignerInfoGeneratorBuilder(messageDigest); - * SignatureAlgorithm signatureAlgorithm = - * signatureParameters.getSignatureAlgorithm(); - * - * CustomContentSigner customContentSigner = new - * CustomContentSigner(signatureAlgorithm.getJCEId()); - * SignerInfoGenerator signerInfoGenerator = - * padesCMSSignedDataBuilder.build(signatureParameters, - * customContentSigner); - * - * CMSSignedDataBuilder cmsSignedDataBuilder = new CMSSignedDataBuilder() - * .setSigningCertificate(signatureParameters.getSigningCertificate()) - * .setCertificateChain(signatureParameters.getCertificateChain()) - * .setGenerateWithoutCertificates(signatureParameters. - * isGenerateTBSWithoutCertificate()) - * .setEncapsulate(false); - * cmsSignedDataBuilder.createCMSSignedData(signerInfoGenerator, new - * InMemoryDocument(messageDigest.getValue())); - * - * SignatureValue signatureValue = getToken().sign( - * new ToBeSigned(customContentSigner.getOutputStream().toByteArray()), - * signatureParameters.getDigestAlgorithm(), getPrivateKeyEntry()); - * - * // ---------------------------- - * } - * - * public DSSDocument createDocumentExample1(DSSDocument documentToSign) { - * - * CustomContentSigner customContentSigner = new - * CustomContentSigner(signatureAlgorithm.getJCEId(), - * signatureValue.getValue()); - * signerInfoGenerator = padesCMSSignedDataBuilder.build(signatureParameters, - * customContentSigner); - * - * CMSSignedData cmsSignedData = - * cmsSignedDataBuilder.createCMSSignedData(signerInfoGenerator, - * new InMemoryDocument(messageDigest.getValue())); - * byte[] encoded = DSSASN1Utils.getDEREncoded(cmsSignedData); - * - * CMSSignedData cmsSignedData = DSSUtils.toCMSSignedData(encoded); - * CMSSignedDocument cmsSignedDocument = new CMSSignedDocument(cmsSignedData); - * - * // Stateless - * PAdESWithExternalCMSService service = new PAdESWithExternalCMSService(); - * return service.signDocument(documentToSign, signatureParameters, - * cmsSignedDocument); - * - * } - */ } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Models/SignatureDocumentForm.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Models/SignatureDocumentForm.java new file mode 100644 index 0000000..aba8656 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Models/SignatureDocumentForm.java @@ -0,0 +1,130 @@ +package eu.europa.ec.eudi.signer.r3.sca.Models; + +import java.security.cert.X509Certificate; + +import eu.europa.esig.dss.enumerations.ASiCContainerType; +import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.enumerations.EncryptionAlgorithm; +import eu.europa.esig.dss.enumerations.SignatureForm; +import eu.europa.esig.dss.enumerations.SignatureLevel; +import eu.europa.esig.dss.enumerations.SignaturePackaging; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; + +import java.util.Date; +import java.util.List; + +public class SignatureDocumentForm { + private DSSDocument documentToSign; + private SignaturePackaging signaturePackaging; + private ASiCContainerType containerType; + private SignatureLevel signatureLevel; + private DigestAlgorithm digestAlgorithm; + private X509Certificate certificate; + private Date date; + private CommonTrustedCertificateSource trustedCertificates; + private SignatureForm signatureForm; + private List certChain; + private EncryptionAlgorithm encryptionAlgorithm; + private byte[] signatureValue; + + public SignatureDocumentForm() { + } + + public DSSDocument getDocumentToSign() { + return documentToSign; + } + + public void setDocumentToSign(DSSDocument documentToSign) { + this.documentToSign = documentToSign; + } + + public SignaturePackaging getSignaturePackaging() { + return signaturePackaging; + } + + public void setSignaturePackaging(SignaturePackaging signaturePackaging) { + this.signaturePackaging = signaturePackaging; + } + + public ASiCContainerType getContainerType() { + return containerType; + } + + public void setContainerType(ASiCContainerType containerType) { + this.containerType = containerType; + } + + public SignatureLevel getSignatureLevel() { + return signatureLevel; + } + + public void setSignatureLevel(SignatureLevel signatureLevel) { + this.signatureLevel = signatureLevel; + } + + public DigestAlgorithm getDigestAlgorithm() { + return digestAlgorithm; + } + + public void setDigestAlgorithm(DigestAlgorithm digestAlgorithm) { + this.digestAlgorithm = digestAlgorithm; + } + + public X509Certificate getCertificate() { + return certificate; + } + + public void setCertificate(X509Certificate certificate) { + this.certificate = certificate; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public CommonTrustedCertificateSource getTrustedCertificates() { + return trustedCertificates; + } + + public void setTrustedCertificates(CommonTrustedCertificateSource trustedCertificates) { + this.trustedCertificates=trustedCertificates; + } + + public SignatureForm getSignatureForm() { + return signatureForm; + } + + public void setSignatureForm(SignatureForm signatureForm) { + this.signatureForm=signatureForm; + } + + public List getCertChain() { + return certChain; + } + + public void setCertChain(List certChain) { + this.certChain = certChain; + } + + public EncryptionAlgorithm getEncryptionAlgorithm() { + return encryptionAlgorithm; + } + + public void setEncryptionAlgorithm(EncryptionAlgorithm encryptionAlgorithm) { + this.encryptionAlgorithm = encryptionAlgorithm; + } + + public byte[] getSignatureValue() { + return signatureValue; + } + + public void setSignatureValue(byte[] signatureValue) { + this.signatureValue = signatureValue; + } + +} \ No newline at end of file diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 0000000..c74c70e --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,3 @@ +SigningCertificate=src/main/resources/cert_UT.pem +TrustedCertificates=src/main/resources/PIDIssuerCAUT01.pem;src/main/resources/TSA_CC.pem +TSPServer=http://ts.cartaodecidadao.pt/tsa/server \ No newline at end of file From c0e536eeb0422000dd7263845bb83e6680f9433b Mon Sep 17 00:00:00 2001 From: Rolando2000 <61158161+Rolando2000@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:26:59 +0100 Subject: [PATCH 23/44] logs --- logback.xml | 43 ++++++++++++++---- .../sca/Controllers/SignaturesController.java | 7 +++ tests/exampleSigned.pdf | Bin 996 -> 49455 bytes 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/logback.xml b/logback.xml index 335c13d..b848d0e 100644 --- a/logback.xml +++ b/logback.xml @@ -1,10 +1,37 @@ - - - - - - - - \ No newline at end of file + + + + + + + + logs/app.log + + logs/app-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 1e3d53e..5234730 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -43,9 +43,13 @@ import java.io.FileInputStream; import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + @RestController @RequestMapping(value = "/signatures") public class SignaturesController { + private static final Logger fileLogger = LoggerFactory.getLogger("FileLogger"); @Autowired private QtspClient qtspClient; @@ -103,6 +107,7 @@ public SignaturesController() throws Exception { @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest) { + fileLogger.info("Entry /signDoc"); System.out.println(signDocRequest); String url = signDocRequest.getRequest_uri(); @@ -177,6 +182,8 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc SignatureDocumentForm.setCertChain(new ArrayList<>()); SignatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.ECDSA); + fileLogger.debug("SignatureDocumentForm: " + SignatureDocumentForm.getSignaturePackaging()); + System.out.println("/n/n before DataToBeSigned /n/n"); dataToBeSigned = dssClient.DataToBeSignedData(SignatureDocumentForm); System.out.println("\n\n after DataToBeSigned \n\n"); diff --git a/tests/exampleSigned.pdf b/tests/exampleSigned.pdf index 94181e1e1f641be7971575022bed0cfd88778fc1..2b06aa0bc0777a53e4fa336c9de7708c225aa980 100644 GIT binary patch literal 49455 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 literal 996 zcmXqLVt&BJsnzDu_MMlJooPW6^F@Ou=Ce$UjE1}h+-#f)Z61uN%q&cdAP!6!ADn@Qr1_Loe5d$GM=1>-99`4NI;?mTj_>$D( z5(7DLULz9&GXrx&6C+b&izp!16p3pfZYXLX4AI9^T$+@Xnp^_W*f<~A3Px52=EhzI zgT_v##zuxUm9A?1=ldqTsI=O2`Np2UmkhlY9dj3nR?Bdl$Yam+^mV$u=bwt{#TaP1f^@*e5jjzRr31i_zq7hn|)%_j&V@xm9s~zDuKWPWSj{ zOKoR+JN*pf_j5J(^rTy+8p+D+wwZZ-&4V??N?RXf9GmyFpqGi6k%4isuz{cfKhWv2 z!i!+tHPu~!$Q-8m+ zd$s-1_=~wuC(R4Z6%4&qxO7Xl26MX$Th(SBtDMiqXCYtPHeZkZ`{y6auXD|LCiRYpI%~H`@p?z z*U#5y7AFMpK2hC}`{c6dyu1x(J!P`XuY4_@#i7|-yk6XD&Z1|Z6!*ohj;i>3eld^G phvXiUZs~Wg-YNY_Eo0pJ@k8du$B}M%r;dItp8YtZD}Tc5p8zCmVjchh From d7e85ab8c6a2b481a1d1860faf33480095c36e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Thu, 12 Sep 2024 10:45:41 +0100 Subject: [PATCH 24/44] Add logs --- logback.xml | 31 +++++-------- .../sca/Controllers/SignaturesController.java | 44 +++++++++++-------- .../ec/eudi/signer/r3/sca/DSS_Service.java | 23 +++++++--- 3 files changed, 52 insertions(+), 46 deletions(-) diff --git a/logback.xml b/logback.xml index b848d0e..5009ece 100644 --- a/logback.xml +++ b/logback.xml @@ -1,37 +1,28 @@ - - - - - + - logs/app.log + logs/app.log - logs/app-%d{yyyy-MM-dd}.log - 30 + logs/app-%d{yyyy-MM-dd}.log + 30 - %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n - + - %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n - - - + + + - - - - - - + \ No newline at end of file diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index 5234730..b123906 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -1,7 +1,9 @@ package eu.europa.ec.eudi.signer.r3.sca.Controllers; +import java.beans.PropertyDescriptor; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.lang.reflect.Method; import java.nio.file.Files; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -11,6 +13,7 @@ import java.util.List; import java.util.Properties; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.PostMapping; @@ -78,7 +81,7 @@ public SignaturesController() throws Exception { String certificatePath = properties.getProperty("SigningCertificate"); if (certificatePath == null || certificatePath.isEmpty()) { - throw new Exception("Caminho do certificado não encontrado no arquivo de configuração!"); + throw new Exception("Signature Certificate Path not found in configuration file."); } FileInputStream certInputStream = new FileInputStream(certificatePath); @@ -89,11 +92,10 @@ public SignaturesController() throws Exception { String arrayOfStrings = properties.getProperty("TrustedCertificates"); String [] teste= arrayOfStrings.split(";"); - System.out.println(teste); for ( String path : teste){ if (path == null || path.isEmpty()) { - throw new Exception("Caminho do certificado não encontrado no arquivo de configuração!"); + throw new Exception("Trusted Certificate Path not found in configuration file."); } FileInputStream certInput= new FileInputStream(path); X509Certificate certificate= (X509Certificate) certFactory.generateCertificate(certInput); @@ -107,9 +109,11 @@ public SignaturesController() throws Exception { @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest) { + fileLogger.info("Entry /signDoc"); + + fileLogger.info("Signature Document Request:" + signDocRequest); - System.out.println(signDocRequest); String url = signDocRequest.getRequest_uri(); if (signDocRequest.getCredentialID() == null) { System.out.println("To be defined: CredentialID needs to be defined in this implementation."); @@ -167,7 +171,10 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc ASiCContainerType aux_asic_ContainerType = DSS_Service.checkASiCContainerType(document.getContainer()); SignatureForm signatureForm= DSS_Service.checkSignForm(document.getSignature_format()); - System.out.println(document.getSignature_format()); + fileLogger.info("Payload Received:{"+ "Document Hash:"+ dssDocument.getDigest(aux_digest_alg) +",conformance_level:" +document.getConformance_level()+ ","+ + "Signature Format:"+ document.getSignature_format() + "," + "Signature Algorithm:"+ document.getSignAlgo() + "," + + "Signature Packaging:"+ document.getSigned_envelope_property() + "," + "Type of Container:"+ document.getContainer() + "}"); + SignatureDocumentForm.setDocumentToSign(dssDocument); SignatureDocumentForm.setSignaturePackaging(aux_sign_pack); @@ -182,11 +189,10 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc SignatureDocumentForm.setCertChain(new ArrayList<>()); SignatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.ECDSA); - fileLogger.debug("SignatureDocumentForm: " + SignatureDocumentForm.getSignaturePackaging()); + fileLogger.info("SignatureDocumentForm: " + SignatureDocumentForm.getSignaturePackaging()); - System.out.println("/n/n before DataToBeSigned /n/n"); dataToBeSigned = dssClient.DataToBeSignedData(SignatureDocumentForm); - System.out.println("\n\n after DataToBeSigned \n\n"); + fileLogger.info("DataToBeSigned successfully created"); if (dataToBeSigned == null) { return new SignaturesSignDocResponse(); @@ -196,8 +202,6 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc List doc = new ArrayList<>(); doc.add(dtbs); - System.out.println(signDocRequest.toString()); - // As the current operation mode only supported is "S", the validity_period and // response_uri do not need to be defined SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest( @@ -213,12 +217,14 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc signDocRequest.getClientData()); try { - System.out.println("HTTP Request to QTSP."); + + fileLogger.info("HTTP Request to QTSP."); SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest); - System.out.println("HTTP Response received."); allResponses.add(signHashResponse); - System.out.println(signHashResponse.toString()); + fileLogger.info("HTTP Response received."); + } catch (Exception e) { + fileLogger.error("Error " + e); e.printStackTrace(); } } @@ -254,12 +260,12 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); SignatureDocumentForm.setSignatureValue(signature); DSSDocument docSigned = dssClient.signDocument(SignatureDocumentForm); - System.out.println(docSigned); + fileLogger.info("Document successfully signed."); try { if (document.getContainer().equals("ASiC-E")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - System.out.println("\nASIC-E\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); docSigned.save("tests/exampleSigned.cse"); @@ -272,7 +278,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc } else if (document.getContainer().equals("ASiC-S")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - System.out.println("\nASIC-S\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); docSigned.save("tests/exampleSigned.scs"); @@ -284,7 +290,7 @@ else if (document.getContainer().equals("ASiC-S")) { } else if (document.getSignature_format().equals("J")) { - System.out.println("\nJADES SIGN\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); docSigned.save("tests/exampleSigned.json"); @@ -294,7 +300,7 @@ else if (document.getSignature_format().equals("J")) { DocumentWithSignature.add(Base64.getEncoder().encodeToString(jsonBytes)); } else if (document.getSignature_format().equals("X")) { - System.out.println("\nXADES SIGN\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); docSigned.save("tests/exampleSigned.xml"); @@ -303,7 +309,7 @@ else if (document.getSignature_format().equals("X")) { DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); } else { - System.out.println("\nOTHERS SIGN\n"); + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 8c0a1cd..78c2af1 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -6,6 +6,8 @@ import java.util.Base64; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.event.Level; import org.springframework.stereotype.Service; @@ -61,6 +63,7 @@ @Service public class DSS_Service { + private static final Logger fileLogger = LoggerFactory.getLogger("FileLogger"); public static SignatureLevel checkConformance_level(String conformance_level, String string) { String enumValue = mapToEnumValue(conformance_level, string); @@ -93,6 +96,7 @@ private static String mapToEnumValue(String conformance_level, String string) { prefix = "XAdES_BASELINE_"; break; default: + fileLogger.error("Conformance Level invalid."); return null; } @@ -106,6 +110,7 @@ private static String mapToEnumValue(String conformance_level, String string) { case "Ades-B-T": return prefix + "T"; default: + fileLogger.error("Conformance Level invalid."); return null; } } @@ -121,6 +126,7 @@ public static SignatureForm checkSignForm(String signForm) { case "X": return SignatureForm.XAdES; default: + fileLogger.error("Signature Format invalid."); return null; } } @@ -147,6 +153,7 @@ public static DigestAlgorithm checkSignAlgDigest(String alg) { case "1.2.840.113549.1.1.13": return DigestAlgorithm.SHA512; default: + fileLogger.error("Signature Digest Algorithm invalid."); return null; } } @@ -160,6 +167,7 @@ public static ASiCContainerType checkASiCContainerType(String alg) { case "ASiC-S": return ASiCContainerType.ASiC_S; default: + fileLogger.error("ASICC Container Type invalid."); return null; } } @@ -175,6 +183,7 @@ public static SignaturePackaging checkEnvProps(String env) { case "INTERNALLY_DETACHED": return SignaturePackaging.INTERNALLY_DETACHED; default: + fileLogger.error("Signature Packaging invalid."); return null; } } @@ -194,9 +203,11 @@ public byte[] DataToBeSignedData(SignatureDocumentForm form) throws CertificateE DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - System.out.println("/n/n Teste1 /n/n"); + fileLogger.info("DataToBeSignedData Service created."); AbstractSignatureParameters parameters = fillParameters(form); + fileLogger.info("DataToBeSignedData Parameters Filled."); + DSSDocument toSignDocument = form.getDocumentToSign(); ToBeSigned toBeSigned = service.getDataToSign(toSignDocument, parameters); return toBeSigned.getBytes(); @@ -209,7 +220,10 @@ public DSSDocument signDocument(SignatureDocumentForm form) { DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); + fileLogger.info("signDocument Service created."); + AbstractSignatureParameters parameters = fillParameters(form); + fileLogger.info("DataToBeSignedData Parameters Filled."); DSSDocument toSignDocument = form.getDocumentToSign(); SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.getAlgorithm(form.getEncryptionAlgorithm(), @@ -278,8 +292,6 @@ private void fillTimestampParameters(AbstractSignatureParameters parameters, Sig private DocumentSignatureService getSignatureService(ASiCContainerType containerType, SignatureForm signatureForm, CommonTrustedCertificateSource TrustedCertificates) { - System.out.println("\n\n" + containerType + "\n\n"); - CertificateVerifier cv = new CommonCertificateVerifier(); cv.setTrustedCertSources(TrustedCertificates); @@ -318,8 +330,6 @@ private DocumentSignatureService getSignatureService(ASiCContainerType container cv.setRevocationFallback(true); - System.out.println("\n\n" + signatureForm + "\n\n"); - DocumentSignatureService service = null; if (containerType != null) { service = (DocumentSignatureService) getASiCSignatureService(signatureForm, cv); @@ -341,8 +351,7 @@ private DocumentSignatureService getSignatureService(ASiCContainerType container throw new IllegalArgumentException(String.format("Unknown signature form : %s", signatureForm)); } } - - System.out.println("\n\n" + service.toString() + "\n\n"); + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); onlineTSPSource.setDataLoader(new TimestampDataLoader()); From a9be295c727015b56fd708cde14f3d70112cbb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20de=20S=C3=A1?= <61152929+tomasdesa@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:38:44 +0100 Subject: [PATCH 25/44] Update logs --- .../sca/Controllers/SignaturesController.java | 16 ++++++------- .../ec/eudi/signer/r3/sca/DSS_Service.java | 23 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java index b123906..fb8c2f7 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/Controllers/SignaturesController.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; import eu.europa.ec.eudi.signer.r3.sca.DSS_Service; import eu.europa.ec.eudi.signer.r3.sca.QtspClient; @@ -171,7 +172,8 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc ASiCContainerType aux_asic_ContainerType = DSS_Service.checkASiCContainerType(document.getContainer()); SignatureForm signatureForm= DSS_Service.checkSignForm(document.getSignature_format()); - fileLogger.info("Payload Received:{"+ "Document Hash:"+ dssDocument.getDigest(aux_digest_alg) +",conformance_level:" +document.getConformance_level()+ ","+ + + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Payload Received:{"+ "Document Hash:"+ dssDocument.getDigest(aux_digest_alg) +",conformance_level:" +document.getConformance_level()+ ","+ "Signature Format:"+ document.getSignature_format() + "," + "Signature Algorithm:"+ document.getSignAlgo() + "," + "Signature Packaging:"+ document.getSigned_envelope_property() + "," + "Type of Container:"+ document.getContainer() + "}"); @@ -189,10 +191,8 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc SignatureDocumentForm.setCertChain(new ArrayList<>()); SignatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.ECDSA); - fileLogger.info("SignatureDocumentForm: " + SignatureDocumentForm.getSignaturePackaging()); - dataToBeSigned = dssClient.DataToBeSignedData(SignatureDocumentForm); - fileLogger.info("DataToBeSigned successfully created"); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",DataToBeSigned successfully created"); if (dataToBeSigned == null) { return new SignaturesSignDocResponse(); @@ -218,10 +218,10 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc try { - fileLogger.info("HTTP Request to QTSP."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Request to QTSP."); SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(url, signHashRequest); allResponses.add(signHashResponse); - fileLogger.info("HTTP Response received."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Response received."); } catch (Exception e) { fileLogger.error("Error " + e); @@ -260,7 +260,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc byte[] signature = Base64.getDecoder().decode(response.getSignatures().get(0)); SignatureDocumentForm.setSignatureValue(signature); DSSDocument docSigned = dssClient.signDocument(SignatureDocumentForm); - fileLogger.info("Document successfully signed."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Document successfully signed."); try { if (document.getContainer().equals("ASiC-E")) { @@ -309,7 +309,7 @@ else if (document.getSignature_format().equals("X")) { DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); } else { - + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java index 78c2af1..95a554a 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/DSS_Service.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; import eu.europa.ec.eudi.signer.r3.sca.Models.SignatureDocumentForm; import eu.europa.esig.dss.AbstractSignatureParameters; @@ -96,7 +97,7 @@ private static String mapToEnumValue(String conformance_level, String string) { prefix = "XAdES_BASELINE_"; break; default: - fileLogger.error("Conformance Level invalid."); + fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Conformance Level invalid."); return null; } @@ -110,7 +111,7 @@ private static String mapToEnumValue(String conformance_level, String string) { case "Ades-B-T": return prefix + "T"; default: - fileLogger.error("Conformance Level invalid."); + fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Conformance Level invalid."); return null; } } @@ -126,7 +127,7 @@ public static SignatureForm checkSignForm(String signForm) { case "X": return SignatureForm.XAdES; default: - fileLogger.error("Signature Format invalid."); + fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Format invalid."); return null; } } @@ -153,7 +154,7 @@ public static DigestAlgorithm checkSignAlgDigest(String alg) { case "1.2.840.113549.1.1.13": return DigestAlgorithm.SHA512; default: - fileLogger.error("Signature Digest Algorithm invalid."); + fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Digest Algorithm invalid."); return null; } } @@ -167,7 +168,7 @@ public static ASiCContainerType checkASiCContainerType(String alg) { case "ASiC-S": return ASiCContainerType.ASiC_S; default: - fileLogger.error("ASICC Container Type invalid."); + fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"ASICC Container Type invalid."); return null; } } @@ -183,7 +184,7 @@ public static SignaturePackaging checkEnvProps(String env) { case "INTERNALLY_DETACHED": return SignaturePackaging.INTERNALLY_DETACHED; default: - fileLogger.error("Signature Packaging invalid."); + fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Packaging invalid."); return null; } } @@ -203,10 +204,10 @@ public byte[] DataToBeSignedData(SignatureDocumentForm form) throws CertificateE DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - fileLogger.info("DataToBeSignedData Service created."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"DataToBeSignedData Service created."); AbstractSignatureParameters parameters = fillParameters(form); - fileLogger.info("DataToBeSignedData Parameters Filled."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"DataToBeSignedData Parameters Filled."); DSSDocument toSignDocument = form.getDocumentToSign(); ToBeSigned toBeSigned = service.getDataToSign(toSignDocument, parameters); @@ -220,10 +221,10 @@ public DSSDocument signDocument(SignatureDocumentForm form) { DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - fileLogger.info("signDocument Service created."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"signDocument Service created."); AbstractSignatureParameters parameters = fillParameters(form); - fileLogger.info("DataToBeSignedData Parameters Filled."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"DataToBeSignedData Parameters Filled."); DSSDocument toSignDocument = form.getDocumentToSign(); SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.getAlgorithm(form.getEncryptionAlgorithm(), @@ -351,7 +352,7 @@ private DocumentSignatureService getSignatureService(ASiCContainerType container throw new IllegalArgumentException(String.format("Unknown signature form : %s", signatureForm)); } } - + String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); onlineTSPSource.setDataLoader(new TimestampDataLoader()); From 18c006df4c9d1d289841c32645bb1b51aa4ba184 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Mon, 16 Sep 2024 14:55:15 +0100 Subject: [PATCH 26/44] remove unnecessary files --- .gitignore | 2 ++ tests/exampleSigned.cse | Bin 11665 -> 0 bytes tests/exampleSigned.json | 1 - tests/exampleSigned.pdf | Bin 49455 -> 0 bytes tests/exampleSigned.scs | Bin 11341 -> 0 bytes tests/exampleSigned.xml | Bin 11343 -> 0 bytes 6 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 tests/exampleSigned.cse delete mode 100644 tests/exampleSigned.json delete mode 100644 tests/exampleSigned.pdf delete mode 100644 tests/exampleSigned.scs delete mode 100644 tests/exampleSigned.xml diff --git a/.gitignore b/.gitignore index 18d823c..8d9090e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +tests/* + # Compiled class file *.class diff --git a/tests/exampleSigned.cse b/tests/exampleSigned.cse deleted file mode 100644 index d6bd8c7dc2020fdfe2f6519e1572c7a7d06fc979..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11665 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 diff --git a/tests/exampleSigned.scs b/tests/exampleSigned.scs deleted file mode 100644 index a21f5b865a5053e18094d155b40f73f8bda027f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11341 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 EUmK|6-v9sr From 440e521869c8947895f9ec460e3965964d803358 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Mon, 16 Sep 2024 17:28:26 +0100 Subject: [PATCH 27/44] fix bug in signing process --- .../eudi/signer/r3/sca/model/DSSService.java | 6 +++ .../signer/r3/sca/model/SignatureService.java | 4 +- .../sca/web/controller/OAuth2Controller.java | 32 ++++++++++++++- .../web/controller/SignaturesController.java | 40 ++++++------------- src/main/resources/config.properties | 3 +- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index 4842c7c..1453a88 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -146,10 +146,16 @@ public static DigestAlgorithm checkSignAlgDigest(String alg) { switch (alg) { case "1.2.840.113549.1.1.11": return DigestAlgorithm.SHA256; + case "2.16.840.1.101.3.4.2.1": + return DigestAlgorithm.SHA256; case "1.2.840.113549.1.1.12": return DigestAlgorithm.SHA384; + case "2.16.840.1.101.3.4.2.2": + return DigestAlgorithm.SHA384; case "1.2.840.113549.1.1.13": return DigestAlgorithm.SHA512; + case "2.16.840.1.101.3.4.2.3": + return DigestAlgorithm.SHA512; default: fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Digest Algorithm invalid."); return null; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index 9284723..cd45c96 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -112,7 +112,6 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc DocumentsSignDocRequest document = signDocRequest.getDocuments().get(i); DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); DigestAlgorithm aux_digest_alg = DSSService.checkSignAlgDigest(signDocRequest.getHashAlgorithmOID()); SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); @@ -130,14 +129,13 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc signatureDocumentForm.setDate(date); signatureDocumentForm.setTrustedCertificates(certificateSource); signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertChain(new ArrayList<>()); + signatureDocumentForm.setCertChain(certificateChain); signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); byte[] signature = Base64.getDecoder().decode(signHashResponse.getSignatures().get(i)); signatureDocumentForm.setSignatureValue(signature); DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Document successfully signed."); - //DSSDocument docSigned = dssClient.getSignedDocument(dssDocument, signature, certificate, certificateChain, signAlgo.get(0), signDocRequest.getHashAlgorithmOID()); try { if (document.getContainer().equals("ASiC-E")) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java index 9ec2034..3be31de 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java @@ -6,6 +6,7 @@ import eu.europa.ec.eudi.signer.r3.sca.model.QtspClient; import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; +import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import jakarta.servlet.http.HttpServletRequest; import org.apache.http.Header; @@ -20,15 +21,19 @@ import javax.sql.CommonDataSource; import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Date; import java.util.List; +import java.util.Properties; @RestController @RequestMapping(value = "/credential") @@ -37,13 +42,31 @@ public class OAuth2Controller { private final QtspClient qtspClient; private final CredentialsService credentialsService; private final SignatureService signatureService; + private final CertificateToken TSACertificateToken; + public OAuth2Controller(@Autowired QtspClient qtspClient, @Autowired CredentialsService credentialsService, - @Autowired SignatureService signatureService){ + @Autowired SignatureService signatureService) throws Exception{ this.qtspClient = qtspClient; this.credentialsService = credentialsService; this.signatureService = signatureService; + + Properties properties = new Properties(); + InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); + if (configStream == null) { + throw new Exception("Arquivo config.properties não encontrado!"); + } + properties.load(configStream); + + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + String certificateStringPath = properties.getProperty("TrustedCertificates"); + if (certificateStringPath == null || certificateStringPath.isEmpty()) { + throw new Exception("Trusted Certificate Path not found in configuration file."); + } + FileInputStream certInput= new FileInputStream(certificateStringPath); + X509Certificate TSACertificate = (X509Certificate) certFactory.generateCertificate(certInput); + this.TSACertificateToken = new CertificateToken(TSACertificate); } private String generateNonce(String root) throws Exception{ @@ -63,11 +86,16 @@ public AuthResponseTemporary credential_authorization( @RequestHeader (name="Authorization") String authorizationBearerHeader) throws Exception{ System.out.println("authorization: "+authorizationBearerHeader); - System.out.println(credentialAuthorization); + System.out.println("credential authorization request: "+credentialAuthorization); CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); Date date = new Date(); CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); + certificateSource.addCertificate(this.TSACertificateToken); + for(X509Certificate cert: certificateResponse.getCertificateChain()){ + certificateSource.addCertificate(new CertificateToken(cert)); + } + // calculate hash List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID(), date, certificateSource); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 40f50dd..8e6f4cd 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -15,6 +15,8 @@ import java.security.cert.X509Certificate; import java.util.Date; import java.util.Properties; + +import org.bouncycastle.jcajce.provider.asymmetric.X509; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; @@ -33,47 +35,28 @@ public class SignaturesController { private final SignatureService signatureService; private final CredentialsService credentialsService; - - @Autowired - private QtspClient qtspClient; - @Autowired - private DSSService dssClient; - - private CommonTrustedCertificateSource certificateSource; + private final CertificateToken TSACertificateToken; public SignaturesController(@Autowired CredentialsService credentialsService, @Autowired SignatureService signatureService) throws Exception { - this.credentialsService = credentialsService; this.signatureService = signatureService; - this.certificateSource= new CommonTrustedCertificateSource(); Properties properties = new Properties(); - InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); if (configStream == null) { throw new Exception("Arquivo config.properties não encontrado!"); } - properties.load(configStream); - String certificatePath = properties.getProperty("SigningCertificate"); - if (certificatePath == null || certificatePath.isEmpty()) { - throw new Exception("Signature Certificate Path not found in configuration file."); - } - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - String arrayOfStrings = properties.getProperty("TrustedCertificates"); - String [] teste = arrayOfStrings.split(";"); - for ( String path : teste){ - if (path == null || path.isEmpty()) { - throw new Exception("Trusted Certificate Path not found in configuration file."); - } - FileInputStream certInput= new FileInputStream(path); - X509Certificate certificate= (X509Certificate) certFactory.generateCertificate(certInput); - this.certificateSource.addCertificate(new CertificateToken(certificate)); - + String certificateStringPath = properties.getProperty("TrustedCertificates"); + if (certificateStringPath == null || certificateStringPath.isEmpty()) { + throw new Exception("Trusted Certificate Path not found in configuration file."); } + FileInputStream certInput= new FileInputStream(certificateStringPath); + X509Certificate TSACertificate = (X509Certificate) certFactory.generateCertificate(certInput); + this.TSACertificateToken = new CertificateToken(TSACertificate); } @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") @@ -102,7 +85,10 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe if (signDocRequest.getDocuments() != null) { try { CommonTrustedCertificateSource commonTrustedCertificateSource = new CommonTrustedCertificateSource(); - + commonTrustedCertificateSource.addCertificate(this.TSACertificateToken); + for (X509Certificate cert: certificateResponse.getCertificateChain()){ + commonTrustedCertificateSource.addCertificate(new CertificateToken(cert)); + } Date date = new Date(signDocRequest.getSignature_date()); return this.signatureService.handleDocumentsSignDocRequest(signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo(), date); } catch (Exception e) { diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index c74c70e..f246594 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -1,3 +1,2 @@ -SigningCertificate=src/main/resources/cert_UT.pem -TrustedCertificates=src/main/resources/PIDIssuerCAUT01.pem;src/main/resources/TSA_CC.pem +TrustedCertificates=src/main/resources/TSA_CC.pem TSPServer=http://ts.cartaodecidadao.pt/tsa/server \ No newline at end of file From a62209ac8812d4d7af0c894cfa0326e5fa8c2678 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Fri, 20 Sep 2024 08:50:02 +0100 Subject: [PATCH 28/44] small updates to deploy in preprod --- .../signer/r3/sca/web/controller/OAuth2Controller.java | 7 +++---- src/main/resources/application.properties | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java index 3be31de..c3414b4 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java @@ -96,7 +96,6 @@ public AuthResponseTemporary credential_authorization( certificateSource.addCertificate(new CertificateToken(cert)); } - // calculate hash List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID(), date, certificateSource); for(String s: hashes){ @@ -111,7 +110,7 @@ public AuthResponseTemporary credential_authorization( OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); authorizeRequest.setClient_id("sca-client"); - authorizeRequest.setRedirect_uri("http://localhost:8082/credential/oauth/login/code"); + authorizeRequest.setRedirect_uri("https://walletcentric.signer.eudiw.dev/credential/oauth/login/code"); authorizeRequest.setScope("credential"); authorizeRequest.setCode_challenge(code_challenge); authorizeRequest.setCode_challenge_method("S256"); @@ -144,11 +143,11 @@ public String credential_authorization_code(HttpServletRequest request) throws E String code = request.getParameter("code"); System.out.println("Code: "+code); - String url = "http://localhost:9000/oauth2/token?" + + String url = "https://walletcentric.signer.eudiw.dev/oauth2/token?" + "grant_type=authorization_code&" + "code=" + code + "&" + "client_id=sca-client&"+ - "redirect_uri=http%3A%2F%2Flocalhost%3A8082%2Fcredential%2Foauth%2Flogin%2Fcode&" + + "redirect_uri=https%3A%2F%2Fwalletcentric.signer.eudiw.dev%2Fcredential%2Foauth%2Flogin%2Fcode&" + "code_verifier="+code_verifier; System.out.println("Url: "+url); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 92e1895..15a5a77 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=sca -server.port = 8082 +server.port=8086 logging.level.org.springframework.web=DEBUG logging.level.org.hibernate=ERROR logging.config=logback.xml \ No newline at end of file From 0d927b0f305328bb9ae597ab3f816da9fd0f566a Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Wed, 25 Sep 2024 15:21:44 +0100 Subject: [PATCH 29/44] WIP --- README.md | 5 + deploy_sca.sh | 1 + .../ec/eudi/signer/r3/sca/ScaApplication.java | 4 - .../r3/sca/model/CredentialsService.java | 37 ++++++- .../eudi/signer/r3/sca/model/DSSService.java | 23 ++--- .../eudi/signer/r3/sca/model/QtspClient.java | 2 +- .../signer/r3/sca/model/SignatureService.java | 53 ++++------ .../sca/web/controller/OAuth2Controller.java | 98 +++++++++++-------- .../web/controller/SignaturesController.java | 18 +--- .../sca/web/dto/OAuth2AuthorizeRequest.java | 10 ++ 10 files changed, 140 insertions(+), 111 deletions(-) create mode 100644 deploy_sca.sh diff --git a/README.md b/README.md index 757a7ee..630c61e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # eudi-srv-web-walletdriven-signer-external-sca-java rQES R3 external SCA + +## Deployment: + +1. Add the TSA_CC.pem file in the resources folder, and define the parameter TrustedCertificates in the config.properties of the same folder with the path of the TSA_CC.pem file. +2. Run the deployment script \ No newline at end of file diff --git a/deploy_sca.sh b/deploy_sca.sh new file mode 100644 index 0000000..88617f6 --- /dev/null +++ b/deploy_sca.sh @@ -0,0 +1 @@ +mvn clean install; nohup java -jar target/sca-0.0.1-SNAPSHOT.jar & \ No newline at end of file diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java index 4d289d2..6adc93e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java @@ -2,18 +2,14 @@ import java.util.logging.Level; import java.util.logging.Logger; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ScaApplication { - - public static void main(String[] args) { Logger libraryLogger = Logger.getLogger("eu.europa.esig"); libraryLogger.setLevel(Level.FINE); SpringApplication.run(ScaApplication.class, args); } - } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index 721338d..4e2c44b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -2,26 +2,48 @@ import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfoRequest; import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfoResponse; +import eu.europa.esig.dss.model.x509.CertificateToken; +import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; import java.util.List; +import java.util.Properties; @Service public class CredentialsService { - private final QtspClient qtspClient; + private final CertificateToken TSACertificateToken; private static final Logger logger = LoggerFactory.getLogger(CredentialsService.class); - public CredentialsService(@Autowired QtspClient qtspClient){ + public CredentialsService(@Autowired QtspClient qtspClient) throws Exception{ this.qtspClient = qtspClient; + + Properties properties = new Properties(); + InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); + if (configStream == null) { + throw new Exception("Arquivo config.properties não encontrado!"); + } + properties.load(configStream); + + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + String certificateStringPath = properties.getProperty("TrustedCertificates"); + if (certificateStringPath == null || certificateStringPath.isEmpty()) { + throw new Exception("Trusted Certificate Path not found in configuration file."); + } + FileInputStream certInput= new FileInputStream(certificateStringPath); + X509Certificate TSACertificate = (X509Certificate) certFactory.generateCertificate(certInput); + this.TSACertificateToken = new CertificateToken(TSACertificate); + certInput.close(); } public static class CertificateResponse { @@ -82,6 +104,8 @@ public CertificateResponse getCertificateAndCertificateChain(String qtspUrl, Str e.printStackTrace(); } } + int i = x509Certificates.size() - 1; + logger.info("Number of certificate in chain: {}", i); int size = x509Certificates.size(); return new CertificateResponse(x509Certificates.get(0), x509Certificates.subList(1, size), keyAlgo); } @@ -92,4 +116,13 @@ private X509Certificate base64DecodeCertificate(String certificate) throws Excep CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); return (X509Certificate)certFactory.generateCertificate(inputStream); } + + public CommonTrustedCertificateSource getCommonTrustedCertificateSource (List certificateChain){ + CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); + certificateSource.addCertificate(this.TSACertificateToken); + for(X509Certificate cert: certificateChain){ + certificateSource.addCertificate(new CertificateToken(cert)); + } + return certificateSource; + } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index 1453a88..eb25a1b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -217,32 +217,28 @@ public byte[] DataToBeSignedData(SignatureDocumentForm form) throws CertificateE @SuppressWarnings({ "rawtypes", "unchecked" }) public DSSDocument signDocument(SignatureDocumentForm form) { - DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), - form.getTrustedCertificates()); + DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"signDocument Service created."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +", signDocument Service created."); AbstractSignatureParameters parameters = fillParameters(form); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"DataToBeSignedData Parameters Filled."); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +", DataToBeSignedData Parameters Filled."); + + System.out.println("######: "+parameters.getCertificateChain().size()); DSSDocument toSignDocument = form.getDocumentToSign(); - SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.getAlgorithm(form.getEncryptionAlgorithm(), - form.getDigestAlgorithm()); SignatureValue signatureValue = new SignatureValue(); - signatureValue.setAlgorithm(sigAlgorithm); + signatureValue.setAlgorithm(SignatureAlgorithm.getAlgorithm(form.getEncryptionAlgorithm(), form.getDigestAlgorithm())); signatureValue.setValue(form.getSignatureValue()); - DSSDocument signedDocument = service.signDocument(toSignDocument, parameters, signatureValue); - return signedDocument; - + return service.signDocument(toSignDocument, parameters, signatureValue); } @SuppressWarnings({ "rawtypes" }) private AbstractSignatureParameters fillParameters(SignatureDocumentForm form) { - AbstractSignatureParameters parameters = getSignatureParameters(form.getContainerType(), - form.getSignatureForm()); + AbstractSignatureParameters parameters = getSignatureParameters(form.getContainerType(), form.getSignatureForm()); parameters.setSignaturePackaging(form.getSignaturePackaging()); fillParameters(parameters, form); @@ -255,8 +251,6 @@ private void fillParameters(AbstractSignatureParameters parameters, SignatureDoc parameters.setSignatureLevel(form.getSignatureLevel()); parameters.setDigestAlgorithm(form.getDigestAlgorithm()); - // parameters.setEncryptionAlgorithm(form.getEncryptionAlgorithm()); retrieved - // from certificate parameters.bLevel().setSigningDate(form.getDate()); CertificateToken signingCertificate = new CertificateToken(form.getCertificate()); @@ -267,7 +261,6 @@ private void fillParameters(AbstractSignatureParameters parameters, SignatureDoc for (X509Certificate cert : certificateChainBytes) { certChainToken.add(new CertificateToken(cert)); } - parameters.setCertificateChain(certChainToken); fillTimestampParameters(parameters, form); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java index bfa88ca..3fcea24 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java @@ -22,7 +22,7 @@ @Service public class QtspClient { - public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHashRequest signHashRequest, String authorizationBearerHeader) throws Exception { + public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHashRequest signHashRequest, String authorizationBearerHeader) { System.out.println("url: "+url); System.out.println("body: "+signHashRequest.toString()); System.out.println("header: "+authorizationBearerHeader); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index cd45c96..bd59a55 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -48,12 +48,10 @@ public List calculateHashValue(List documents, ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); SignatureForm signatureForm = DSSService.checkSignForm(document.getSignature_format()); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Payload Received:{"+ "Document Hash:"+ dssDocument.getDigest(aux_digest_alg) +",conformance_level:" +document.getConformance_level()+ ","+ - "Signature Format:"+ document.getSignature_format() + "," + "Hash Algorithm OID:"+ hashAlgorithmOID + "," + - "Signature Packaging:"+ document.getSigned_envelope_property() + "," + "Type of Container:"+ document.getContainer() + "}"); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Payload Received:{ Document Hash:"+ dssDocument.getDigest(aux_digest_alg) +", conformance_level:" +document.getConformance_level()+ ","+ + "Signature Format:"+ document.getSignature_format() + ", Hash Algorithm OID:"+ hashAlgorithmOID + ", Signature Packaging:"+ document.getSigned_envelope_property() + ", Type of Container:"+ document.getContainer() + "}"); SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); - signatureDocumentForm.setDocumentToSign(dssDocument); signatureDocumentForm.setSignaturePackaging(aux_sign_pack); signatureDocumentForm.setContainerType(aux_asic_ContainerType); @@ -64,7 +62,7 @@ public List calculateHashValue(List documents, signatureDocumentForm.setDate(date); signatureDocumentForm.setTrustedCertificates(certificateSource); signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertChain(new ArrayList<>()); + signatureDocumentForm.setCertChain(certificateChain); signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); byte[] dataToBeSigned = dssClient.DataToBeSignedData(signatureDocumentForm); @@ -79,37 +77,25 @@ public List calculateHashValue(List documents, return hashes; } - // i need the signing certificate before hand - public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String authorizationBearerHeader, X509Certificate certificate, List certificateChain, List signAlgo, Date date) throws Exception { - CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); - + public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String authorizationBearerHeader, X509Certificate certificate, List certificateChain, List signAlgo, Date date, CommonTrustedCertificateSource certificateSource) throws Exception { List hashes = calculateHashValue(signDocRequest.getDocuments(), certificate, certificateChain, signDocRequest.getHashAlgorithmOID(), date, certificateSource); - SignaturesSignHashResponse signHashResponse = null; - try { - // As the current operation mode only supported is "S", the validity_period and response_uri do not need to be defined - SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest(signDocRequest.getCredentialID(), - null, hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, signDocRequest.getOperationMode(), - -1, null, signDocRequest.getClientData()); - - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Request to QTSP."); - signHashResponse = qtspClient.requestSignHash(signDocRequest.getRequest_uri(), signHashRequest, authorizationBearerHeader); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Response received."); - - } catch (Exception e) { - e.printStackTrace(); - } + SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest(signDocRequest.getCredentialID(),null, + hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, signDocRequest.getOperationMode(), + -1, null, signDocRequest.getClientData()); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Request to QTSP."); + SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(signDocRequest.getRequest_uri(), signHashRequest, authorizationBearerHeader); + List allSignaturesObjects = signHashResponse.getSignatures(); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Response received."); - assert signHashResponse != null; - if(signHashResponse.getSignatures().size() != signDocRequest.getDocuments().size()){ - return new SignaturesSignDocResponse(); - } + if(signHashResponse.getSignatures().size() != signDocRequest.getDocuments().size()) return new SignaturesSignDocResponse(); - List allSignaturesObjects = signHashResponse.getSignatures(); List DocumentWithSignature = new ArrayList<>(); for(int i = 0; i < signDocRequest.getDocuments().size(); i++){ DocumentsSignDocRequest document = signDocRequest.getDocuments().get(i); + String signatureValue = signHashResponse.getSignatures().get(i); + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); @@ -131,9 +117,8 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc signatureDocumentForm.setSignatureForm(signatureForm); signatureDocumentForm.setCertChain(certificateChain); signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + signatureDocumentForm.setSignatureValue(Base64.getDecoder().decode(signatureValue)); - byte[] signature = Base64.getDecoder().decode(signHashResponse.getSignatures().get(i)); - signatureDocumentForm.setSignatureValue(signature); DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Document successfully signed."); @@ -187,10 +172,10 @@ else if (document.getSignature_format().equals("X")) { docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); - File file = new File("tests/exampleSigned.pdf"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); + // File file = new File("tests/exampleSigned.pdf"); + // byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); } } catch (Exception e) { fileLogger.error("invalid request: "+ e.getMessage()); @@ -198,8 +183,6 @@ else if (document.getSignature_format().equals("X")) { } } - allSignaturesObjects.addAll(signHashResponse.getSignatures()); - ValidationInfoSignDocResponse validationInfo = null; if (signDocRequest.getReturnValidationInfo()) { validationInfo = new ValidationInfoSignDocResponse(); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java index c3414b4..e29240e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java @@ -15,19 +15,18 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.json.JSONArray; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.sql.CommonDataSource; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Base64; @@ -42,31 +41,12 @@ public class OAuth2Controller { private final QtspClient qtspClient; private final CredentialsService credentialsService; private final SignatureService signatureService; - private final CertificateToken TSACertificateToken; - - public OAuth2Controller(@Autowired QtspClient qtspClient, - @Autowired CredentialsService credentialsService, + public OAuth2Controller(@Autowired QtspClient qtspClient, @Autowired CredentialsService credentialsService, @Autowired SignatureService signatureService) throws Exception{ this.qtspClient = qtspClient; this.credentialsService = credentialsService; this.signatureService = signatureService; - - Properties properties = new Properties(); - InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); - if (configStream == null) { - throw new Exception("Arquivo config.properties não encontrado!"); - } - properties.load(configStream); - - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - String certificateStringPath = properties.getProperty("TrustedCertificates"); - if (certificateStringPath == null || certificateStringPath.isEmpty()) { - throw new Exception("Trusted Certificate Path not found in configuration file."); - } - FileInputStream certInput= new FileInputStream(certificateStringPath); - X509Certificate TSACertificate = (X509Certificate) certFactory.generateCertificate(certInput); - this.TSACertificateToken = new CertificateToken(TSACertificate); } private String generateNonce(String root) throws Exception{ @@ -84,33 +64,30 @@ private String generateNonce(String root) throws Exception{ public AuthResponseTemporary credential_authorization( @RequestBody CredentialAuthorizationRequest credentialAuthorization, @RequestHeader (name="Authorization") String authorizationBearerHeader) throws Exception{ - System.out.println("authorization: "+authorizationBearerHeader); System.out.println("credential authorization request: "+credentialAuthorization); - CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); Date date = new Date(); - CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); - certificateSource.addCertificate(this.TSACertificateToken); - for(X509Certificate cert: certificateResponse.getCertificateChain()){ - certificateSource.addCertificate(new CertificateToken(cert)); - } + CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); - // calculate hash - List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID(), date, certificateSource); - for(String s: hashes){ - System.out.println("Oauth2: "+ s); + saveCertificateToPem(certificateResponse.getCertificate(), "cert_0.pem"); + int i = 1; + for (X509Certificate c: certificateResponse.getCertificateChain()){ + saveCertificateToPem(c, "cert_"+i+".pem"); + i++; } + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateResponse.getCertificateChain()); + + List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID(), date, certificateSource); String hash = String.join(",", hashes); - System.out.println("hash: "+hash); // generate code_challenge, code_challenge_method, code_verifier String code_challenge = generateNonce("root"); OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); authorizeRequest.setClient_id("sca-client"); - authorizeRequest.setRedirect_uri("https://walletcentric.signer.eudiw.dev/credential/oauth/login/code"); + authorizeRequest.setRedirect_uri("http://localhost:8086/credential/oauth/login/code"); authorizeRequest.setScope("credential"); authorizeRequest.setCode_challenge(code_challenge); authorizeRequest.setCode_challenge_method("S256"); @@ -121,10 +98,35 @@ public AuthResponseTemporary credential_authorization( authorizeRequest.setHashes(hash); authorizeRequest.setHashAlgorithmOID(credentialAuthorization.getHashAlgorithmOID()); + /* + JSONArray documentDigests = new JSONArray(); + for(String h: hashes){ + JSONObject documentDigest = new JSONObject(); + documentDigest.put("hash", h); + documentDigest.put("label", "This is some document hash"); + documentDigests.put(documentDigest); + } + + JSONObject authorization_details = new JSONObject(); + authorization_details.put("type", "credential"); + authorization_details.put("credentialID", URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); + authorization_details.put("documentDigests", documentDigests); + authorization_details.put("hashAlgorithmOID", credentialAuthorization.getHashAlgorithmOID()); + System.out.println(authorization_details); + + OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); + authorizeRequest.setResponse_type("code"); + authorizeRequest.setClient_id("sca-client"); + authorizeRequest.setRedirect_uri("http://localhost:8086/credential/oauth/login/code"); + authorizeRequest.setCode_challenge(code_challenge); + authorizeRequest.setCode_challenge_method("S256"); + authorizeRequest.setLang("pt-PT"); + authorizeRequest.setState("12345678"); + authorizeRequest.setAuthorization_details(URLEncoder.encode(authorization_details.toString(), StandardCharsets.UTF_8)); + System.out.println(authorizeRequest);*/ + AuthResponseTemporary responseTemporary = this.qtspClient.requestOAuth2Authorize(credentialAuthorization.getAuthorizationServerUrl(), authorizeRequest, authorizationBearerHeader); - System.out.println(date.getTime()); responseTemporary.setSignature_date(date.getTime()); - System.out.println("-----------------------------"); return responseTemporary; } @@ -143,11 +145,11 @@ public String credential_authorization_code(HttpServletRequest request) throws E String code = request.getParameter("code"); System.out.println("Code: "+code); - String url = "https://walletcentric.signer.eudiw.dev/oauth2/token?" + + String url = "http://localhost:8084/oauth2/token?" + "grant_type=authorization_code&" + "code=" + code + "&" + "client_id=sca-client&"+ - "redirect_uri=https%3A%2F%2Fwalletcentric.signer.eudiw.dev%2Fcredential%2Foauth%2Flogin%2Fcode&" + + "redirect_uri=http%3A%2F%2Flocalhost:8086%2Fcredential%2Foauth%2Flogin%2Fcode&" + "code_verifier="+code_verifier; System.out.println("Url: "+url); @@ -187,4 +189,18 @@ public String credential_authorization_code(HttpServletRequest request) throws E } return null; } + + public static void saveCertificateToPem(X509Certificate certificate, String filePath) throws IOException, CertificateEncodingException { + String pemCertificate = convertToPem(certificate); + try (Writer writer = new FileWriter(filePath)) { + writer.write(pemCertificate); + } + } + + public static String convertToPem(X509Certificate certificate) throws CertificateEncodingException { + byte[] encodedCert = certificate.getEncoded(); + String base64Cert = Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(encodedCert); + return "-----BEGIN CERTIFICATE-----\n" + base64Cert + "\n-----END CERTIFICATE-----\n"; + } + } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 8e6f4cd..147db9a 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -1,7 +1,5 @@ package eu.europa.ec.eudi.signer.r3.sca.web.controller; -import eu.europa.ec.eudi.signer.r3.sca.model.DSSService; -import eu.europa.ec.eudi.signer.r3.sca.model.QtspClient; import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.SignaturesSignDocRequest; import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignDocResponse; import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; @@ -15,8 +13,6 @@ import java.security.cert.X509Certificate; import java.util.Date; import java.util.Properties; - -import org.bouncycastle.jcajce.provider.asymmetric.X509; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; @@ -37,7 +33,6 @@ public class SignaturesController { private final CredentialsService credentialsService; private final CertificateToken TSACertificateToken; - public SignaturesController(@Autowired CredentialsService credentialsService, @Autowired SignatureService signatureService) throws Exception { this.credentialsService = credentialsService; this.signatureService = signatureService; @@ -57,13 +52,14 @@ public SignaturesController(@Autowired CredentialsService credentialsService, @A FileInputStream certInput= new FileInputStream(certificateStringPath); X509Certificate TSACertificate = (X509Certificate) certFactory.generateCertificate(certInput); this.TSACertificateToken = new CertificateToken(TSACertificate); + certInput.close(); } @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest, @RequestHeader (name="Authorization") String authorizationBearerHeader) { fileLogger.info("Entry /signDoc"); - fileLogger.info("Signature Document Request:" + signDocRequest); - fileLogger.info("Authorization Header: "+authorizationBearerHeader); + fileLogger.info("Signature Document Request:{}", signDocRequest); + fileLogger.info("Authorization Header: {}", authorizationBearerHeader); if (signDocRequest.getCredentialID() == null) { System.out.println("To be defined: CredentialID needs to be defined in this implementation."); @@ -84,13 +80,9 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe if (signDocRequest.getDocuments() != null) { try { - CommonTrustedCertificateSource commonTrustedCertificateSource = new CommonTrustedCertificateSource(); - commonTrustedCertificateSource.addCertificate(this.TSACertificateToken); - for (X509Certificate cert: certificateResponse.getCertificateChain()){ - commonTrustedCertificateSource.addCertificate(new CertificateToken(cert)); - } Date date = new Date(signDocRequest.getSignature_date()); - return this.signatureService.handleDocumentsSignDocRequest(signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo(), date); + CommonTrustedCertificateSource commonTrustedCertificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateResponse.getCertificateChain()); + return this.signatureService.handleDocumentsSignDocRequest(signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo(), date, commonTrustedCertificateSource); } catch (Exception e) { fileLogger.error(e.getMessage()); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java index fb900f0..1e2916e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java @@ -3,6 +3,8 @@ import jakarta.validation.constraints.NotBlank; public class OAuth2AuthorizeRequest { + @NotBlank + private String response_type = "code"; @NotBlank private String client_id; private String redirect_uri; @@ -26,6 +28,14 @@ public class OAuth2AuthorizeRequest { private String account_token; private String clientData; + public String getResponse_type() { + return response_type; + } + + public void setResponse_type(String response_type) { + this.response_type = response_type; + } + public String getClient_id() { return client_id; } From 4169906bdd06bf22598475cb6b8d6e69b2b9efc4 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Wed, 2 Oct 2024 10:03:23 +0100 Subject: [PATCH 30/44] Add html page and other small changes --- pom.xml | 6 ++ .../ec/eudi/signer/r3/sca/ScaApplication.java | 4 + .../r3/sca/config/OAuthClientConfig.java | 54 ++++++++++++ .../signer/r3/sca/model/SignatureService.java | 2 +- .../web/controller/CallbackController.java | 74 ++++++++++++++++ .../sca/web/controller/OAuth2Controller.java | 88 ++----------------- .../web/controller/SignaturesController.java | 5 -- .../SignaturesSignDocRequest.java | 50 +---------- .../SignaturesSignDocRequestValidator.java | 5 +- src/main/resources/application-dev.yml | 7 ++ src/main/resources/application.properties | 5 -- src/main/resources/application.yml | 20 +++++ .../templates/successful_authentication.html | 14 +++ 13 files changed, 191 insertions(+), 143 deletions(-) create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java create mode 100644 src/main/resources/application-dev.yml delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/templates/successful_authentication.html diff --git a/pom.xml b/pom.xml index 6de62d5..2d717f5 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,12 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-thymeleaf + 3.3.0 + + org.springframework.boot spring-boot-starter-webflux diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java index 6adc93e..7de22a0 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java @@ -2,10 +2,14 @@ import java.util.logging.Level; import java.util.logging.Logger; + +import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication +@EnableConfigurationProperties({ OAuthClientConfig.class }) public class ScaApplication { public static void main(String[] args) { Logger libraryLogger = Logger.getLogger("eu.europa.esig"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java new file mode 100644 index 0000000..9b9c927 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java @@ -0,0 +1,54 @@ +package eu.europa.ec.eudi.signer.r3.sca.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Set; + +@ConfigurationProperties(prefix = "oauth-client") +public class OAuthClientConfig { + private String clientId; + private String clientSecret; + private Set clientAuthenticationMethods; + private String redirectUri; + private String scope; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public Set getClientAuthenticationMethods() { + return clientAuthenticationMethods; + } + + public void setClientAuthenticationMethods(Set clientAuthenticationMethods) { + this.clientAuthenticationMethods = clientAuthenticationMethods; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index bd59a55..7c883f4 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -82,7 +82,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc List hashes = calculateHashValue(signDocRequest.getDocuments(), certificate, certificateChain, signDocRequest.getHashAlgorithmOID(), date, certificateSource); SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest(signDocRequest.getCredentialID(),null, - hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, signDocRequest.getOperationMode(), + hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, "S", -1, null, signDocRequest.getClientData()); fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Request to QTSP."); SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(signDocRequest.getRequest_uri(), signHashRequest, authorizationBearerHeader); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java new file mode 100644 index 0000000..47af767 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java @@ -0,0 +1,74 @@ +package eu.europa.ec.eudi.signer.r3.sca.web.controller; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Base64; + +import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class CallbackController { + + private final OAuthClientConfig oAuthClientConfig; + + public CallbackController(@Autowired OAuthClientConfig oAuthClientConfig) { + this.oAuthClientConfig = oAuthClientConfig; + } + + @GetMapping(value="/credential/oauth/login/code") + public String credential_authorization_code(@RequestParam("code") String code, Model model) throws Exception { + URIBuilder uriBuilder = new URIBuilder("http://localhost:8084/oauth2/token"); + uriBuilder.setParameter("grant_type", "authorization_code"); + uriBuilder.setParameter("code", code); + uriBuilder.setParameter("client_id", this.oAuthClientConfig.getClientId()); + uriBuilder.setParameter("redirect_uri", this.oAuthClientConfig.getRedirectUri()); + uriBuilder.setParameter("code_verifier", "root"); + + String url = uriBuilder.build().toString(); + System.out.println("URI: "+ url); + + String authorizationHeader = getBasicAuthenticationHeader(this.oAuthClientConfig.getClientId(), this.oAuthClientConfig.getClientSecret()); + try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { + HttpPost followRequest = new HttpPost(url); + followRequest.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); + + HttpResponse followResponse = httpClient2.execute(followRequest); + + InputStream is = followResponse.getEntity().getContent(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + is.close(); + String responseString = sb.toString(); + + JSONObject json = new JSONObject(responseString); + + model.addAttribute("body", json.toString()); + model.addAttribute("url", "http://127.0.0.1:5000/oauth/credential/login/code"); + return "successful_authentication"; + } + } + + private static String getBasicAuthenticationHeader(String username, String password) { + String valueToEncode = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); + } + + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java index e29240e..4160f24 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java @@ -1,38 +1,34 @@ package eu.europa.ec.eudi.signer.r3.sca.web.controller; +import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; import eu.europa.ec.eudi.signer.r3.sca.web.dto.AuthResponseTemporary; import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialAuthorizationRequest; import eu.europa.ec.eudi.signer.r3.sca.web.dto.OAuth2AuthorizeRequest; import eu.europa.ec.eudi.signer.r3.sca.model.QtspClient; import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; -import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import jakarta.servlet.http.HttpServletRequest; -import org.apache.http.Header; +import jakarta.servlet.http.HttpServletResponse; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; -import org.json.JSONArray; -import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; -import javax.sql.CommonDataSource; import java.io.*; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Date; import java.util.List; -import java.util.Properties; @RestController @RequestMapping(value = "/credential") @@ -41,12 +37,14 @@ public class OAuth2Controller { private final QtspClient qtspClient; private final CredentialsService credentialsService; private final SignatureService signatureService; + private final OAuthClientConfig oAuthClientConfig; public OAuth2Controller(@Autowired QtspClient qtspClient, @Autowired CredentialsService credentialsService, - @Autowired SignatureService signatureService) throws Exception{ + @Autowired SignatureService signatureService, @Autowired OAuthClientConfig oAuthClientConfig) throws Exception{ this.qtspClient = qtspClient; this.credentialsService = credentialsService; this.signatureService = signatureService; + this.oAuthClientConfig = oAuthClientConfig; } private String generateNonce(String root) throws Exception{ @@ -69,14 +67,6 @@ public AuthResponseTemporary credential_authorization( Date date = new Date(); CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); - - saveCertificateToPem(certificateResponse.getCertificate(), "cert_0.pem"); - int i = 1; - for (X509Certificate c: certificateResponse.getCertificateChain()){ - saveCertificateToPem(c, "cert_"+i+".pem"); - i++; - } - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateResponse.getCertificateChain()); List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID(), date, certificateSource); @@ -86,9 +76,9 @@ public AuthResponseTemporary credential_authorization( String code_challenge = generateNonce("root"); OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); - authorizeRequest.setClient_id("sca-client"); - authorizeRequest.setRedirect_uri("http://localhost:8086/credential/oauth/login/code"); - authorizeRequest.setScope("credential"); + authorizeRequest.setClient_id(this.oAuthClientConfig.getClientId()); + authorizeRequest.setRedirect_uri(this.oAuthClientConfig.getRedirectUri()); + authorizeRequest.setScope(this.oAuthClientConfig.getScope()); authorizeRequest.setCode_challenge(code_challenge); authorizeRequest.setCode_challenge_method("S256"); authorizeRequest.setLang("pt-PT"); @@ -130,66 +120,6 @@ public AuthResponseTemporary credential_authorization( return responseTemporary; } - private static String getBasicAuthenticationHeader(String username, String password) { - String valueToEncode = username + ":" + password; - return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); - } - - @GetMapping(value="/oauth/login/code", produces="application/json") - public String credential_authorization_code(HttpServletRequest request) throws Exception{ - System.out.println("Login Code: -----------------------------"); - - String code_verifier = "root"; - - if(request.getParameter("code") != null){ - String code = request.getParameter("code"); - System.out.println("Code: "+code); - - String url = "http://localhost:8084/oauth2/token?" + - "grant_type=authorization_code&" + - "code=" + code + "&" + - "client_id=sca-client&"+ - "redirect_uri=http%3A%2F%2Flocalhost:8086%2Fcredential%2Foauth%2Flogin%2Fcode&" + - "code_verifier="+code_verifier; - System.out.println("Url: "+url); - - String authorizationHeader = getBasicAuthenticationHeader("sca-client", "somesecret1"); - System.out.println("Authorization Header: "+authorizationHeader); - String new_session_id = request.getHeader("Set-Cookie"); - - try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { - HttpPost followRequest = new HttpPost(url); - followRequest.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); - followRequest.setHeader("Cookie", new_session_id); - - System.out.println(followRequest.getHeaders(HttpHeaders.AUTHORIZATION)[0].getValue()); - HttpResponse followResponse = httpClient2.execute(followRequest); - - System.out.println(followResponse.getStatusLine().getStatusCode()); - - for(Header h: followResponse.getAllHeaders()){ - System.out.println(h.getName()+": "+h.getValue()); - } - - InputStream is = followResponse.getEntity().getContent(); - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - is.close(); - String responseString = sb.toString(); - - JSONObject json = new JSONObject(responseString); - System.out.println(json); - - return responseString; - } - } - return null; - } - public static void saveCertificateToPem(X509Certificate certificate, String filePath) throws IOException, CertificateEncodingException { String pemCertificate = convertToPem(certificate); try (Writer writer = new FileWriter(filePath)) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 147db9a..5247fb5 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -73,11 +73,6 @@ public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRe return new SignaturesSignDocResponse(); } - if (signDocRequest.getOperationMode().equals("A")) { - System.out.println("To be defined: the current solution doesn't support assynchronious responses."); - return new SignaturesSignDocResponse(); - } - if (signDocRequest.getDocuments() != null) { try { Date date = new Date(signDocRequest.getSignature_date()); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java index 9cf135f..2354a35 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java @@ -9,18 +9,14 @@ @SignDocRequestConstraintAnnotation public class SignaturesSignDocRequest { private String credentialID; - private String signatureQualifier; @Valid private List documents; - private String operationMode = "S"; - private int validity_period = -1; - private String response_uri; - private String clientData; private Boolean returnValidationInfo = false; @NotBlank private String request_uri; private String hashAlgorithmOID; private long signature_date; + private String clientData; public SignaturesSignDocRequest() { } @@ -35,16 +31,6 @@ public void setCredentialID(String credentialID) { this.credentialID = credentialID; } - @JsonProperty - public String getSignatureQualifier() { - return signatureQualifier; - } - - @JsonProperty - public void setSignatureQualifier(String signatureQualifier) { - this.signatureQualifier = signatureQualifier; - } - @JsonProperty public List getDocuments() { return documents; @@ -55,36 +41,6 @@ public void setDocuments(List documents) { this.documents = documents; } - @JsonProperty - public String getOperationMode() { - return operationMode; - } - - @JsonProperty - public void setOperationMode(String operationMode) { - this.operationMode = operationMode; - } - - @JsonProperty - public int getValidity_period() { - return validity_period; - } - - @JsonProperty - public void setValidity_period(int validity_period) { - this.validity_period = validity_period; - } - - @JsonProperty - public String getResponse_uri() { - return response_uri; - } - - @JsonProperty - public void setResponse_uri(String response_uri) { - this.response_uri = response_uri; - } - @JsonProperty public String getClientData() { return clientData; @@ -135,11 +91,7 @@ public void setSignature_date(long signature_date) { public java.lang.String toString() { return "SignaturesSignDocRequest{" + "credentialID=" + credentialID + - ", signatureQualifier=" + signatureQualifier + ", documents=" + documents + - ", operationMode=" + operationMode + - ", validity_period=" + validity_period + - ", response_uri=" + response_uri + ", clientData=" + clientData + ", returnValidationInfo=" + returnValidationInfo + ", request_uri=" + request_uri + diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java index 4f50106..b0e7475 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java @@ -14,13 +14,10 @@ public void initialize(SignDocRequestConstraintAnnotation constraintAnnotation) @Override public boolean isValid(SignaturesSignDocRequest request, ConstraintValidatorContext context) { - if (!request.getOperationMode().equals("A") && !request.getOperationMode().equals("S")) - return false; - if (request.getRequest_uri() == null) return false; - return (request.getCredentialID() != null || request.getSignatureQualifier() != null) && (request.getDocuments() != null); + return (request.getCredentialID() != null) && (request.getDocuments() != null); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..f164e51 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,7 @@ +oauth-client: + client-id: "sca-client" + client-secret: "somesecret1" + client-authentication-methods: + - "client_secret_basic" + redirect-uri: "https://walletcentric.signer.eudiw.dev/credential/oauth/login/code" + scope: "credential" \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 15a5a77..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,5 +0,0 @@ -spring.application.name=sca -server.port=8086 -logging.level.org.springframework.web=DEBUG -logging.level.org.hibernate=ERROR -logging.config=logback.xml \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..05b97ee --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,20 @@ +spring: + application: + name: sca + +server: + port: 8086 + +logging: + config: logback.xml + level: + org.hibernate: ERROR + org.springframework.web: DEBUG + +oauth-client: + client-id: "sca-client" + client-secret: "somesecret1" + client-authentication-methods: + - "client_secret_basic" + redirect-uri: "http://localhost:8086/credential/oauth/login/code" + scope: "credential" \ No newline at end of file diff --git a/src/main/resources/templates/successful_authentication.html b/src/main/resources/templates/successful_authentication.html new file mode 100644 index 0000000..33691d3 --- /dev/null +++ b/src/main/resources/templates/successful_authentication.html @@ -0,0 +1,14 @@ + + + + + Successful Authentication + + +

Successful Authentication

+
+ + +
+ + \ No newline at end of file From d6800e0dd61fe555e3529d7702e29bfefb2d00b3 Mon Sep 17 00:00:00 2001 From: Mariana Rodrigues <62109899+MarianaFilipa@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:19:34 +0100 Subject: [PATCH 31/44] Delete src/main/resources/application-dev.yml --- src/main/resources/application-dev.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 src/main/resources/application-dev.yml diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml deleted file mode 100644 index f164e51..0000000 --- a/src/main/resources/application-dev.yml +++ /dev/null @@ -1,7 +0,0 @@ -oauth-client: - client-id: "sca-client" - client-secret: "somesecret1" - client-authentication-methods: - - "client_secret_basic" - redirect-uri: "https://walletcentric.signer.eudiw.dev/credential/oauth/login/code" - scope: "credential" \ No newline at end of file From 0c4fb3b4f39d42f9ba8484fd3385e05239bddcc2 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Mon, 21 Oct 2024 11:23:41 +0100 Subject: [PATCH 32/44] Add new endpoints and refactoring --- .gitignore | 3 + .../ec/eudi/signer/r3/sca/ScaApplication.java | 10 +- .../r3/sca/config/OAuthClientConfig.java | 10 +- .../sca/config/TrustedCertificateConfig.java | 25 +++ .../r3/sca/model/CredentialsService.java | 46 ++++-- .../signer/r3/sca/model/OAuth2Service.java | 103 ++++++++++++ .../{QtspClient.java => QTSPClient.java} | 103 +++++++----- .../signer/r3/sca/model/SignatureService.java | 127 ++++++++++++--- .../web/controller/CallbackController.java | 69 ++------ .../sca/web/controller/OAuth2Controller.java | 151 +++++------------- .../web/controller/SignaturesController.java | 142 ++++++++++------ .../r3/sca/web/dto/AuthRequestTemporary.java | 23 --- .../sca/web/dto/SignedDocumentRequestDTO.java | 71 ++++++++ .../calculateHash/CalculateHashRequest.java | 45 ++++++ .../calculateHash/CalculateHashResponse.java | 29 ++++ .../CredentialsInfoAuth.java | 2 +- .../CredentialsInfoCert.java | 2 +- .../CredentialsInfoKey.java | 2 +- .../CredentialsInfoRequest.java | 2 +- .../CredentialsInfoResponse.java | 5 +- .../CredentialAuthorizationRequest.java | 5 +- .../CredentialAuthorizationResponse.java} | 6 +- .../{ => oauth2}/OAuth2AuthorizeRequest.java | 2 +- .../AttributeSignDocRequest.java | 2 +- .../DocumentsSignDocRequest.java | 4 +- .../SignaturesSignDocRequest.java | 24 ++- .../SignaturesSignDocResponse.java | 2 +- .../ValidationInfoSignDocResponse.java | 2 +- .../SignaturesSignHashRequest.java | 2 +- .../SignaturesSignHashResponse.java | 2 +- .../DocumentsSignDocConstraintAnnotation.java | 22 --- .../DocumentsSignDocRequestValidator.java | 35 ---- .../SignDocRequestConstraintAnnotation.java | 20 --- .../SignaturesSignDocRequestValidator.java | 23 --- src/main/resources/application.yml | 15 +- src/main/resources/config.properties | 2 - 36 files changed, 669 insertions(+), 469 deletions(-) create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/{QtspClient.java => QTSPClient.java} (70%) delete mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthRequestTemporary.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{CredentialsInfo/CredentialsInfo => credentialsInfo}/CredentialsInfoAuth.java (91%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{CredentialsInfo/CredentialsInfo => credentialsInfo}/CredentialsInfoCert.java (96%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{CredentialsInfo/CredentialsInfo => credentialsInfo}/CredentialsInfoKey.java (93%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{CredentialsInfo => credentialsInfo}/CredentialsInfoRequest.java (96%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{CredentialsInfo => credentialsInfo}/CredentialsInfoResponse.java (85%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{ => oauth2}/CredentialAuthorizationRequest.java (94%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{AuthResponseTemporary.java => oauth2/CredentialAuthorizationResponse.java} (81%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{ => oauth2}/OAuth2AuthorizeRequest.java (98%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{SignDocRequest => signDoc}/AttributeSignDocRequest.java (89%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{SignDocRequest => signDoc}/DocumentsSignDocRequest.java (89%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{SignDocRequest => signDoc}/SignaturesSignDocRequest.java (78%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{ => signDoc}/SignaturesSignDocResponse.java (97%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{ => signDoc}/ValidationInfoSignDocResponse.java (95%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{ => signHash}/SignaturesSignHashRequest.java (98%) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{ => signHash}/SignaturesSignHashResponse.java (89%) delete mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocConstraintAnnotation.java delete mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocRequestValidator.java delete mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignDocRequestConstraintAnnotation.java delete mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java delete mode 100644 src/main/resources/config.properties diff --git a/.gitignore b/.gitignore index 8d9090e..7688673 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ tests/* +src/main/resources/application-auth.yml +src/main/resources/application-dev.yml +src/main/resources/TSA_CC.pem # Compiled class file *.class diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java index 7de22a0..57e2bf7 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java @@ -1,19 +1,17 @@ package eu.europa.ec.eudi.signer.r3.sca; -import java.util.logging.Level; -import java.util.logging.Logger; - import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; +import eu.europa.ec.eudi.signer.r3.sca.config.TrustedCertificateConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication -@EnableConfigurationProperties({ OAuthClientConfig.class }) +@EnableConfigurationProperties({ OAuthClientConfig.class, TrustedCertificateConfig.class }) public class ScaApplication { public static void main(String[] args) { - Logger libraryLogger = Logger.getLogger("eu.europa.esig"); - libraryLogger.setLevel(Level.FINE); + //Logger libraryLogger = Logger.getLogger("eu.europa.esig"); + //libraryLogger.setLevel(Level.FINE); SpringApplication.run(ScaApplication.class, args); } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java index 9b9c927..08f53ca 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java @@ -1,7 +1,6 @@ package eu.europa.ec.eudi.signer.r3.sca.config; import org.springframework.boot.context.properties.ConfigurationProperties; - import java.util.Set; @ConfigurationProperties(prefix = "oauth-client") @@ -11,6 +10,7 @@ public class OAuthClientConfig { private Set clientAuthenticationMethods; private String redirectUri; private String scope; + private String defaultAuthorizationServerUrl; public String getClientId() { return clientId; @@ -51,4 +51,12 @@ public String getScope() { public void setScope(String scope) { this.scope = scope; } + + public String getDefaultAuthorizationServerUrl() { + return defaultAuthorizationServerUrl; + } + + public void setDefaultAuthorizationServerUrl(String defaultAuthorizationServerUrl) { + this.defaultAuthorizationServerUrl = defaultAuthorizationServerUrl; + } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java new file mode 100644 index 0000000..af8bb17 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java @@ -0,0 +1,25 @@ +package eu.europa.ec.eudi.signer.r3.sca.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "trusted-certificate") +public class TrustedCertificateConfig { + private String filename; + private String timeStampAuthority; + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getTimeStampAuthority() { + return timeStampAuthority; + } + + public void setTimeStampAuthority(String timeStampAuthority) { + this.timeStampAuthority = timeStampAuthority; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index 4e2c44b..ec07c91 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -1,7 +1,8 @@ package eu.europa.ec.eudi.signer.r3.sca.model; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfoRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfoResponse; +import eu.europa.ec.eudi.signer.r3.sca.config.TrustedCertificateConfig; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo.CredentialsInfoRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo.CredentialsInfoResponse; import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import org.slf4j.Logger; @@ -11,32 +12,24 @@ import java.io.ByteArrayInputStream; import java.io.FileInputStream; -import java.io.InputStream; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; import java.util.List; -import java.util.Properties; @Service public class CredentialsService { - private final QtspClient qtspClient; + private final QTSPClient qtspClient; private final CertificateToken TSACertificateToken; private static final Logger logger = LoggerFactory.getLogger(CredentialsService.class); - public CredentialsService(@Autowired QtspClient qtspClient) throws Exception{ + public CredentialsService(@Autowired QTSPClient qtspClient, + @Autowired TrustedCertificateConfig trustedCertificateConfig) throws Exception{ this.qtspClient = qtspClient; - Properties properties = new Properties(); - InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); - if (configStream == null) { - throw new Exception("Arquivo config.properties não encontrado!"); - } - properties.load(configStream); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - String certificateStringPath = properties.getProperty("TrustedCertificates"); + String certificateStringPath = trustedCertificateConfig.getFilename(); if (certificateStringPath == null || certificateStringPath.isEmpty()) { throw new Exception("Trusted Certificate Path not found in configuration file."); } @@ -49,9 +42,11 @@ public CredentialsService(@Autowired QtspClient qtspClient) throws Exception{ public static class CertificateResponse { private X509Certificate certificate; private List certificateChain; + private CommonTrustedCertificateSource tsaCommonSource; private List signAlgo; - public CertificateResponse(X509Certificate certificate, List certificateChain, List signAlgo) { + public CertificateResponse(X509Certificate certificate, List certificateChain, + List signAlgo) { this.certificate = certificate; this.certificateChain = certificateChain; this.signAlgo = signAlgo; @@ -73,6 +68,14 @@ public void setCertificateChain(List certificateChain) { this.certificateChain = certificateChain; } + public CommonTrustedCertificateSource getTsaCommonSource() { + return tsaCommonSource; + } + + public void setTsaCommonSource(CommonTrustedCertificateSource tsaCommonSource) { + this.tsaCommonSource = tsaCommonSource; + } + public List getSignAlgo() { return signAlgo; } @@ -82,8 +85,17 @@ public void setSignAlgo(List signAlgo) { } } + public CertificateResponse getCertificateAndChainAndCommonSource(String qtspUrl, String credentialId, String authorizationBearerHeader){ + CertificateResponse response = getCertificateAndCertificateChain(qtspUrl, credentialId, authorizationBearerHeader); + logger.info("Retrieved the signing certificate and the certificate chain."); + CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(response.getCertificateChain()); + response.setTsaCommonSource(commonTrustedCertificateSource); + logger.info("Retrieved the certificate source."); + return response; + } + // get the certificate and certificate chain of the credentialID - public CertificateResponse getCertificateAndCertificateChain(String qtspUrl, String credentialId, String authorizationBearerHeader){ + private CertificateResponse getCertificateAndCertificateChain(String qtspUrl, String credentialId, String authorizationBearerHeader){ CredentialsInfoRequest infoRequest = new CredentialsInfoRequest(); infoRequest.setCredentialID(credentialId); infoRequest.setCertificates("chain"); @@ -110,7 +122,7 @@ public CertificateResponse getCertificateAndCertificateChain(String qtspUrl, Str return new CertificateResponse(x509Certificates.get(0), x509Certificates.subList(1, size), keyAlgo); } - private X509Certificate base64DecodeCertificate(String certificate) throws Exception{ + public X509Certificate base64DecodeCertificate(String certificate) throws Exception{ byte[] certificateBytes = Base64.getDecoder().decode(certificate); ByteArrayInputStream inputStream = new ByteArrayInputStream(certificateBytes); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java new file mode 100644 index 0000000..cb3777a --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java @@ -0,0 +1,103 @@ +package eu.europa.ec.eudi.signer.r3.sca.model; + +import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.OAuth2AuthorizeRequest; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Date; + +@Service +public class OAuth2Service { + private final QTSPClient qtspClient; + private final OAuthClientConfig oAuthClientConfig; + + public OAuth2Service(@Autowired QTSPClient qtspClient, + @Autowired OAuthClientConfig oAuthClientConfig) { + this.qtspClient = qtspClient; + this.oAuthClientConfig = oAuthClientConfig; + } + + private String generateNonce(String root) throws Exception{ + SecureRandom prng = new SecureRandom(); + String randomNum = String.valueOf(prng.nextInt()); + System.out.println("Code_Verifier: "+ root); + MessageDigest sha = MessageDigest.getInstance("SHA-256"); + byte[] result = sha.digest(root.getBytes()); + String code_challenge = Base64.getUrlEncoder().encodeToString(result); + System.out.println("Code_Challenge: "+code_challenge); + return code_challenge; + } + + private static String getBasicAuthenticationHeader(String username, String password) { + String valueToEncode = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); + } + + public CredentialAuthorizationResponse getOAuth2Authorize(CredentialAuthorizationRequest credentialAuthorization, + String hash, Date date, String authorizationBearerHeader) throws Exception{ + + // generate code_challenge, code_challenge_method, code_verifier + String code_challenge = generateNonce("root"); + + OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); + authorizeRequest.setClient_id(this.oAuthClientConfig.getClientId()); + authorizeRequest.setRedirect_uri(this.oAuthClientConfig.getRedirectUri()); + authorizeRequest.setScope(this.oAuthClientConfig.getScope()); + authorizeRequest.setCode_challenge(code_challenge); + authorizeRequest.setCode_challenge_method("S256"); + authorizeRequest.setLang("pt-PT"); + authorizeRequest.setState("12345678"); + authorizeRequest.setCredentialID(URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); + authorizeRequest.setNumSignatures(credentialAuthorization.getNumSignatures()); + authorizeRequest.setHashes(hash); + authorizeRequest.setHashAlgorithmOID(credentialAuthorization.getHashAlgorithmOID()); + + /* + JSONArray documentDigests = new JSONArray(); + for(String h: hashes){ + JSONObject documentDigest = new JSONObject(); + documentDigest.put("hash", h); + documentDigest.put("label", "This is some document hash"); + documentDigests.put(documentDigest); + } + + JSONObject authorization_details = new JSONObject(); + authorization_details.put("type", "credential"); + authorization_details.put("credentialID", URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); + authorization_details.put("documentDigests", documentDigests); + authorization_details.put("hashAlgorithmOID", credentialAuthorization.getHashAlgorithmOID()); + System.out.println(authorization_details); + + OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); + authorizeRequest.setResponse_type("code"); + authorizeRequest.setClient_id("sca-client"); + authorizeRequest.setRedirect_uri("http://localhost:8086/credential/oauth/login/code"); + authorizeRequest.setCode_challenge(code_challenge); + authorizeRequest.setCode_challenge_method("S256"); + authorizeRequest.setLang("pt-PT"); + authorizeRequest.setState("12345678"); + authorizeRequest.setAuthorization_details(URLEncoder.encode(authorization_details.toString(), StandardCharsets.UTF_8)); + System.out.println(authorizeRequest);*/ + + CredentialAuthorizationResponse responseTemporary = this.qtspClient.requestOAuth2Authorize(credentialAuthorization.getAuthorizationServerUrl(), authorizeRequest, authorizationBearerHeader); + responseTemporary.setSignature_date(date.getTime()); + return responseTemporary; + } + + public JSONObject getOAuth2Token(String code) throws Exception{ + String authorizationHeader = getBasicAuthenticationHeader(this.oAuthClientConfig.getClientId(), + this.oAuthClientConfig.getClientSecret()); + return this.qtspClient.requestOAuth2Token(this.oAuthClientConfig.getDefaultAuthorizationServerUrl(), + code, this.oAuthClientConfig.getClientId(), this.oAuthClientConfig.getRedirectUri(), authorizationHeader); + } + +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java similarity index 70% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java index 3fcea24..2f6f896 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QtspClient.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java @@ -1,16 +1,24 @@ package eu.europa.ec.eudi.signer.r3.sca.model; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.AuthResponseTemporary; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfoRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfoResponse; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.OAuth2AuthorizeRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignHashRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignHashResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo.CredentialsInfoRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo.CredentialsInfoResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.OAuth2AuthorizeRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash.SignaturesSignHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash.SignaturesSignHashResponse; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Optional; + import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.json.JSONObject; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -20,7 +28,30 @@ import reactor.core.publisher.Mono; @Service -public class QtspClient { +public class QTSPClient { + public CredentialsInfoResponse requestCredentialInfo(String url, CredentialsInfoRequest credentialsInfoRequest, String authorizationBearerHeader){ + WebClient webClient = WebClient.builder() + .baseUrl(url) + .defaultCookie("cookieKey", "cookieValue") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + + Mono signHashResponse = webClient.post() + .uri("/csc/v2/credentials/info") + .bodyValue(credentialsInfoRequest) + .header("Authorization", authorizationBearerHeader) + + .exchangeToMono(response -> { + if (response.statusCode().equals(HttpStatus.OK)) { + return response.bodyToMono(CredentialsInfoResponse.class); + } else { + System.out.println(response.statusCode().value()); + return Mono.error(new Exception("Exception")); + } + }); + + return signHashResponse.block(); + } public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHashRequest signHashRequest, String authorizationBearerHeader) { System.out.println("url: "+url); @@ -48,8 +79,7 @@ public SignaturesSignHashResponse requestSignHash(String url, SignaturesSignHash return signHashResponse.block(); } - public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest, String authorizationBearerHeader) throws Exception { - + public CredentialAuthorizationResponse requestOAuth2Authorize(String url, OAuth2AuthorizeRequest authorizeRequest, String authorizationBearerHeader) throws Exception { try(CloseableHttpClient httpClient = HttpClientBuilder.create().disableRedirectHandling().build()) { UriComponentsBuilder uriBuilder = UriComponentsBuilder .fromUriString(url) @@ -76,7 +106,6 @@ public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeR .queryParamIfPresent("hashAlgorithmOID", Optional.ofNullable(authorizeRequest.getHashAlgorithmOID())); String uri = uriBuilder.build().toString(); - System.out.println(uri); HttpGet request = new HttpGet(uri); HttpResponse response = httpClient.execute(request); System.out.println(response.getStatusLine().getStatusCode()); @@ -86,38 +115,40 @@ public AuthResponseTemporary requestOAuth2Authorize(String url, OAuth2AuthorizeR System.out.println("Location: " + location); String cookie = response.getLastHeader("Set-Cookie").getValue(); System.out.println("Cookie: " + cookie); - return new AuthResponseTemporary(location, cookie); + return new CredentialAuthorizationResponse(location, cookie); } return null; } } - public CredentialsInfoResponse requestCredentialInfo(String url, CredentialsInfoRequest credentialsInfoRequest, String authorizationBearerHeader){ - WebClient webClient = WebClient.builder() - .baseUrl(url) - .defaultCookie("cookieKey", "cookieValue") - .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .build(); - - System.out.println("url: "+url); - System.out.println("body: "+credentialsInfoRequest); - System.out.println("header: "+authorizationBearerHeader); - - Mono signHashResponse = webClient.post() - .uri("/csc/v2/credentials/info") - .bodyValue(credentialsInfoRequest) - .header("Authorization", authorizationBearerHeader) - - .exchangeToMono(response -> { - if (response.statusCode().equals(HttpStatus.OK)) { - return response.bodyToMono(CredentialsInfoResponse.class); - } else { - System.out.println(response.statusCode().value()); - return Mono.error(new Exception("Exception")); - } - }); + public JSONObject requestOAuth2Token(String urlBase, String code, String clientId, String redirectUri, String authorizationHeader) throws Exception{ + String uriEndpoint = urlBase+"/oauth2/token"; + URIBuilder uriBuilder = new URIBuilder(uriEndpoint); + uriBuilder.setParameter("grant_type", "authorization_code"); + uriBuilder.setParameter("code", code); + uriBuilder.setParameter("client_id", clientId); + uriBuilder.setParameter("redirect_uri", redirectUri); + uriBuilder.setParameter("code_verifier", "root"); + String url = uriBuilder.build().toString(); + + try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { + HttpPost followRequest = new HttpPost(url); + followRequest.setHeader(org.apache.http.HttpHeaders.AUTHORIZATION, authorizationHeader); + + HttpResponse followResponse = httpClient2.execute(followRequest); + + InputStream is = followResponse.getEntity().getContent(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + is.close(); + String responseString = sb.toString(); - return signHashResponse.block(); + return new JSONObject(responseString); + } } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index 7c883f4..ca2f719 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -1,11 +1,11 @@ package eu.europa.ec.eudi.signer.r3.sca.model; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.DocumentsSignDocRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.SignaturesSignDocRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignDocResponse; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignHashRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignHashResponse; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.ValidationInfoSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.SignaturesSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.SignaturesSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash.SignaturesSignHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash.SignaturesSignHashResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.ValidationInfoSignDocResponse; import eu.europa.esig.dss.enumerations.*; import eu.europa.esig.dss.model.DSSDocument; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; @@ -29,10 +29,10 @@ public class SignatureService { private static final Logger fileLogger = LoggerFactory.getLogger("FileLogger"); - private final QtspClient qtspClient; + private final QTSPClient qtspClient; private final DSSService dssClient; - public SignatureService(@Autowired QtspClient qtspClient, @Autowired DSSService dssClient){ + public SignatureService(@Autowired QTSPClient qtspClient, @Autowired DSSService dssClient){ this.qtspClient = qtspClient; this.dssClient = dssClient; } @@ -77,7 +77,6 @@ public List calculateHashValue(List documents, return hashes; } - // i need the signing certificate before hand public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String authorizationBearerHeader, X509Certificate certificate, List certificateChain, List signAlgo, Date date, CommonTrustedCertificateSource certificateSource) throws Exception { List hashes = calculateHashValue(signDocRequest.getDocuments(), certificate, certificateChain, signDocRequest.getHashAlgorithmOID(), date, certificateSource); @@ -125,45 +124,32 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc try { if (document.getContainer().equals("ASiC-E")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); docSigned.save("tests/exampleSigned.cse"); - File file = new File("tests/exampleSigned.cse"); byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); } - } else if (document.getContainer().equals("ASiC-S")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); docSigned.save("tests/exampleSigned.scs"); - File file = new File("tests/exampleSigned.scs"); byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); } - } else if (document.getSignature_format().equals("J")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); docSigned.save("tests/exampleSigned.json"); - File file = new File("tests/exampleSigned.json"); byte[] jsonBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(jsonBytes)); } else if (document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); docSigned.save("tests/exampleSigned.xml"); - File file = new File("tests/exampleSigned.xml"); byte[] xmlBytes = Files.readAllBytes(file.toPath()); DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); @@ -171,10 +157,6 @@ else if (document.getSignature_format().equals("X")) { else { docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); docSigned.save("tests/exampleSigned.pdf"); - - // File file = new File("tests/exampleSigned.pdf"); - // byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); } } catch (Exception e) { @@ -190,4 +172,97 @@ else if (document.getSignature_format().equals("X")) { return new SignaturesSignDocResponse(DocumentWithSignature, allSignaturesObjects, null, validationInfo); } + + + public SignaturesSignDocResponse buildSignedDocument( + List documents, String hashAlgorithmOID, boolean returnValidationInfo, + X509Certificate certificate, List certificateChain, Date date, + CommonTrustedCertificateSource certificateSource, List signatureObjects) throws Exception { + + if(signatureObjects.size() != documents.size()) return new SignaturesSignDocResponse(); + + List DocumentWithSignature = new ArrayList<>(); + for(int i = 0; i < documents.size(); i++){ + DocumentsSignDocRequest document = documents.get(i); + String signatureValue = signatureObjects.get(i); + + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + + SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); + DigestAlgorithm aux_digest_alg = DSSService.checkSignAlgDigest(hashAlgorithmOID); + SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); + ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); + SignatureForm signatureForm= DSSService.checkSignForm(document.getSignature_format()); + + SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); + signatureDocumentForm.setDocumentToSign(dssDocument); + signatureDocumentForm.setSignaturePackaging(aux_sign_pack); + signatureDocumentForm.setContainerType(aux_asic_ContainerType); + signatureDocumentForm.setSignatureLevel(aux_sign_level); + signatureDocumentForm.setDigestAlgorithm(aux_digest_alg); + signatureDocumentForm.setSignatureForm(signatureForm); + signatureDocumentForm.setCertificate(certificate); + signatureDocumentForm.setDate(date); + signatureDocumentForm.setTrustedCertificates(certificateSource); + signatureDocumentForm.setSignatureForm(signatureForm); + signatureDocumentForm.setCertChain(certificateChain); + signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + signatureDocumentForm.setSignatureValue(Base64.getDecoder().decode(signatureValue)); + + DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Document successfully signed."); + + try { + if (document.getContainer().equals("ASiC-E")) { + if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); + docSigned.save("tests/exampleSigned.cse"); + File file = new File("tests/exampleSigned.cse"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } + } + else if (document.getContainer().equals("ASiC-S")) { + if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); + docSigned.save("tests/exampleSigned.scs"); + File file = new File("tests/exampleSigned.scs"); + byte[] pdfBytes = Files.readAllBytes(file.toPath()); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); + } + } + else if (document.getSignature_format().equals("J")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); + docSigned.save("tests/exampleSigned.json"); + File file = new File("tests/exampleSigned.json"); + byte[] jsonBytes = Files.readAllBytes(file.toPath()); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(jsonBytes)); + } + else if (document.getSignature_format().equals("X")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); + docSigned.save("tests/exampleSigned.xml"); + File file = new File("tests/exampleSigned.xml"); + byte[] xmlBytes = Files.readAllBytes(file.toPath()); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); + } + else { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); + docSigned.save("tests/exampleSigned.pdf"); + DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); + } + } catch (Exception e) { + fileLogger.error("invalid request: "+ e.getMessage()); + throw e; + } + } + + ValidationInfoSignDocResponse validationInfo = null; + if (returnValidationInfo) { + validationInfo = new ValidationInfoSignDocResponse(); + } + + return new SignaturesSignDocResponse(DocumentWithSignature, signatureObjects, null, validationInfo); + } + + } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java index 47af767..fb4c468 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java @@ -1,74 +1,37 @@ package eu.europa.ec.eudi.signer.r3.sca.web.controller; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Base64; - -import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; +import eu.europa.ec.eudi.signer.r3.sca.model.OAuth2Service; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.server.ResponseStatusException; @Controller public class CallbackController { + private static final Logger logger = LoggerFactory.getLogger(CallbackController.class); - private final OAuthClientConfig oAuthClientConfig; + private final OAuth2Service oAuth2Service; - public CallbackController(@Autowired OAuthClientConfig oAuthClientConfig) { - this.oAuthClientConfig = oAuthClientConfig; + public CallbackController(@Autowired OAuth2Service oAuth2Service) { + this.oAuth2Service = oAuth2Service; } @GetMapping(value="/credential/oauth/login/code") - public String credential_authorization_code(@RequestParam("code") String code, Model model) throws Exception { - URIBuilder uriBuilder = new URIBuilder("http://localhost:8084/oauth2/token"); - uriBuilder.setParameter("grant_type", "authorization_code"); - uriBuilder.setParameter("code", code); - uriBuilder.setParameter("client_id", this.oAuthClientConfig.getClientId()); - uriBuilder.setParameter("redirect_uri", this.oAuthClientConfig.getRedirectUri()); - uriBuilder.setParameter("code_verifier", "root"); - - String url = uriBuilder.build().toString(); - System.out.println("URI: "+ url); - - String authorizationHeader = getBasicAuthenticationHeader(this.oAuthClientConfig.getClientId(), this.oAuthClientConfig.getClientSecret()); - try(CloseableHttpClient httpClient2 = HttpClientBuilder.create().build()) { - HttpPost followRequest = new HttpPost(url); - followRequest.setHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); - - HttpResponse followResponse = httpClient2.execute(followRequest); - - InputStream is = followResponse.getEntity().getContent(); - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - is.close(); - String responseString = sb.toString(); - - JSONObject json = new JSONObject(responseString); - + public String credential_authorization_code(@RequestParam("code") String code, Model model){ + try { + JSONObject json = this.oAuth2Service.getOAuth2Token(code); model.addAttribute("body", json.toString()); - model.addAttribute("url", "http://127.0.0.1:5000/oauth/credential/login/code"); + model.addAttribute("url", "http://127.0.0.1:5000/tester/oauth/credential/login/code"); return "successful_authentication"; + } catch (Exception e){ + logger.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response"); } } - - private static String getBasicAuthenticationHeader(String username, String password) { - String valueToEncode = username + ":" + password; - return "Basic " + Base64.getEncoder().encodeToString(valueToEncode.getBytes()); - } - - } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java index 4160f24..ed05b8e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java @@ -1,136 +1,63 @@ package eu.europa.ec.eudi.signer.r3.sca.web.controller; -import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.AuthResponseTemporary; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialAuthorizationRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.OAuth2AuthorizeRequest; -import eu.europa.ec.eudi.signer.r3.sca.model.QtspClient; +import eu.europa.ec.eudi.signer.r3.sca.model.OAuth2Service; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationRequest; import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; -import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.*; - -import java.io.*; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.Base64; import java.util.Date; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + @RestController @RequestMapping(value = "/credential") public class OAuth2Controller { + private final Logger logger = LoggerFactory.getLogger(OAuth2Controller.class); - private final QtspClient qtspClient; private final CredentialsService credentialsService; private final SignatureService signatureService; - private final OAuthClientConfig oAuthClientConfig; + private final OAuth2Service oAuth2Service; - public OAuth2Controller(@Autowired QtspClient qtspClient, @Autowired CredentialsService credentialsService, - @Autowired SignatureService signatureService, @Autowired OAuthClientConfig oAuthClientConfig) throws Exception{ - this.qtspClient = qtspClient; + public OAuth2Controller(@Autowired CredentialsService credentialsService, @Autowired SignatureService signatureService, + @Autowired OAuth2Service oAuth2Service){ this.credentialsService = credentialsService; this.signatureService = signatureService; - this.oAuthClientConfig = oAuthClientConfig; - } - - private String generateNonce(String root) throws Exception{ - SecureRandom prng = new SecureRandom(); - String randomNum = String.valueOf(prng.nextInt()); - System.out.println("Code_Verifier: "+ root); - MessageDigest sha = MessageDigest.getInstance("SHA-256"); - byte[] result = sha.digest(root.getBytes()); - String code_challenge = Base64.getUrlEncoder().encodeToString(result); - System.out.println("Code_Challenge: "+code_challenge); - return code_challenge; + this.oAuth2Service = oAuth2Service; } @GetMapping(value = "/authorize", produces = "application/json") - public AuthResponseTemporary credential_authorization( - @RequestBody CredentialAuthorizationRequest credentialAuthorization, - @RequestHeader (name="Authorization") String authorizationBearerHeader) throws Exception{ - System.out.println("authorization: "+authorizationBearerHeader); - System.out.println("credential authorization request: "+credentialAuthorization); + public CredentialAuthorizationResponse credentialAuthorization( + @RequestHeader (name="Authorization") String authorizationBearerHeader, + @RequestBody CredentialAuthorizationRequest credentialAuthorization){ + logger.info("Request received for credential authorization: {}", credentialAuthorization); Date date = new Date(); - CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(credentialAuthorization.getResourceServerUrl(), credentialAuthorization.getCredentialID(), authorizationBearerHeader); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateResponse.getCertificateChain()); - - List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), credentialAuthorization.getHashAlgorithmOID(), date, certificateSource); - String hash = String.join(",", hashes); - - // generate code_challenge, code_challenge_method, code_verifier - String code_challenge = generateNonce("root"); - - OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); - authorizeRequest.setClient_id(this.oAuthClientConfig.getClientId()); - authorizeRequest.setRedirect_uri(this.oAuthClientConfig.getRedirectUri()); - authorizeRequest.setScope(this.oAuthClientConfig.getScope()); - authorizeRequest.setCode_challenge(code_challenge); - authorizeRequest.setCode_challenge_method("S256"); - authorizeRequest.setLang("pt-PT"); - authorizeRequest.setState("12345678"); - authorizeRequest.setCredentialID(URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); - authorizeRequest.setNumSignatures(credentialAuthorization.getNumSignatures()); - authorizeRequest.setHashes(hash); - authorizeRequest.setHashAlgorithmOID(credentialAuthorization.getHashAlgorithmOID()); - - /* - JSONArray documentDigests = new JSONArray(); - for(String h: hashes){ - JSONObject documentDigest = new JSONObject(); - documentDigest.put("hash", h); - documentDigest.put("label", "This is some document hash"); - documentDigests.put(documentDigest); - } - - JSONObject authorization_details = new JSONObject(); - authorization_details.put("type", "credential"); - authorization_details.put("credentialID", URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); - authorization_details.put("documentDigests", documentDigests); - authorization_details.put("hashAlgorithmOID", credentialAuthorization.getHashAlgorithmOID()); - System.out.println(authorization_details); - - OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); - authorizeRequest.setResponse_type("code"); - authorizeRequest.setClient_id("sca-client"); - authorizeRequest.setRedirect_uri("http://localhost:8086/credential/oauth/login/code"); - authorizeRequest.setCode_challenge(code_challenge); - authorizeRequest.setCode_challenge_method("S256"); - authorizeRequest.setLang("pt-PT"); - authorizeRequest.setState("12345678"); - authorizeRequest.setAuthorization_details(URLEncoder.encode(authorization_details.toString(), StandardCharsets.UTF_8)); - System.out.println(authorizeRequest);*/ - - AuthResponseTemporary responseTemporary = this.qtspClient.requestOAuth2Authorize(credentialAuthorization.getAuthorizationServerUrl(), authorizeRequest, authorizationBearerHeader); - responseTemporary.setSignature_date(date.getTime()); - return responseTemporary; - } - - public static void saveCertificateToPem(X509Certificate certificate, String filePath) throws IOException, CertificateEncodingException { - String pemCertificate = convertToPem(certificate); - try (Writer writer = new FileWriter(filePath)) { - writer.write(pemCertificate); + logger.info("Requested received at {}", date.getTime()); + + try { + CredentialsService.CertificateResponse certificates = + this.credentialsService.getCertificateAndChainAndCommonSource( + credentialAuthorization.getResourceServerUrl(), + credentialAuthorization.getCredentialID(), + authorizationBearerHeader); + logger.info("Retrieved all the required certificates."); + + List hashes = this.signatureService.calculateHashValue(credentialAuthorization.getDocuments(), + certificates.getCertificate(), certificates.getCertificateChain(), + credentialAuthorization.getHashAlgorithmOID(), date, certificates.getTsaCommonSource()); + String hash = String.join(",", hashes); + logger.info("Calculated the value of the hashes to sign."); + + return this.oAuth2Service.getOAuth2Authorize(credentialAuthorization, hash, date, authorizationBearerHeader); + } catch (Exception e){ + logger.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response"); } } - - public static String convertToPem(X509Certificate certificate) throws CertificateEncodingException { - byte[] encodedCert = certificate.getEncoded(); - String base64Cert = Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(encodedCert); - return "-----BEGIN CERTIFICATE-----\n" + base64Cert + "\n-----END CERTIFICATE-----\n"; - } - } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 5247fb5..0a84c13 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -1,18 +1,22 @@ package eu.europa.ec.eudi.signer.r3.sca.web.controller; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.SignaturesSignDocRequest; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignaturesSignDocResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash.CalculateHashRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash.CalculateHashResponse; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.SignaturesSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.SignaturesSignDocResponse; import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; -import eu.europa.esig.dss.model.x509.CertificateToken; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignedDocumentRequestDTO; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; -import jakarta.validation.Valid; -import java.io.InputStream; -import java.io.FileInputStream; -import java.security.cert.CertificateFactory; + import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Date; -import java.util.Properties; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; @@ -20,70 +24,110 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.web.server.ResponseStatusException; @RestController @RequestMapping(value = "/signatures") public class SignaturesController { - private static final Logger fileLogger = LoggerFactory.getLogger("FileLogger"); + private final Logger logger = LogManager.getLogger(SignaturesController.class); private final SignatureService signatureService; private final CredentialsService credentialsService; - private final CertificateToken TSACertificateToken; - public SignaturesController(@Autowired CredentialsService credentialsService, @Autowired SignatureService signatureService) throws Exception { + public SignaturesController(@Autowired CredentialsService credentialsService, + @Autowired SignatureService signatureService){ this.credentialsService = credentialsService; this.signatureService = signatureService; + } + + @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") + public SignaturesSignDocResponse signDoc( + @RequestBody SignaturesSignDocRequest signDocRequest, + @RequestHeader (name="Authorization") String authorizationBearerHeader) { + logger.info("Request received for signing document: {}", signDocRequest); - Properties properties = new Properties(); - InputStream configStream = getClass().getClassLoader().getResourceAsStream("config.properties"); - if (configStream == null) { - throw new Exception("Arquivo config.properties não encontrado!"); + if (signDocRequest.getCredentialID() == null) { + logger.error("The credentialId should be specified in the Request Body."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "invalid_response: the credentialId should be specified."); } - properties.load(configStream); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - String certificateStringPath = properties.getProperty("TrustedCertificates"); - if (certificateStringPath == null || certificateStringPath.isEmpty()) { - throw new Exception("Trusted Certificate Path not found in configuration file."); + if (authorizationBearerHeader == null) { + logger.error("The current solution expects the credential token to be sent in the Authorization Header."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "invalid_response: the authorization header with credential authorization should be present."); } - FileInputStream certInput= new FileInputStream(certificateStringPath); - X509Certificate TSACertificate = (X509Certificate) certFactory.generateCertificate(certInput); - this.TSACertificateToken = new CertificateToken(TSACertificate); - certInput.close(); - } - @PostMapping(value = "/signDoc", consumes = "application/json", produces = "application/json") - public SignaturesSignDocResponse signDoc(@Valid @RequestBody SignaturesSignDocRequest signDocRequest, @RequestHeader (name="Authorization") String authorizationBearerHeader) { - fileLogger.info("Entry /signDoc"); - fileLogger.info("Signature Document Request:{}", signDocRequest); - fileLogger.info("Authorization Header: {}", authorizationBearerHeader); + if(signDocRequest.getDocuments() == null){ + logger.error("The documents to be signed should be sent in the Http Request Body."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "invalid_response: the documents to be signed should be sent in the request."); + } - if (signDocRequest.getCredentialID() == null) { - System.out.println("To be defined: CredentialID needs to be defined in this implementation."); - return new SignaturesSignDocResponse(); + if(signDocRequest.getSignature_date() == 0){ + logger.error("The date when the credential authorization was requested should be sent in the Http Request Body."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "invalid_response: the date when the credential authorization was requested should be sent in the request."); } - CredentialsService.CertificateResponse certificateResponse = this.credentialsService.getCertificateAndCertificateChain(signDocRequest.getRequest_uri(), signDocRequest.getCredentialID(), authorizationBearerHeader); + try { + Date date = new Date(signDocRequest.getSignature_date()); - if (authorizationBearerHeader == null) { - System.out.println("To be defined: the current solution expects the credential token to be sent in the SAD."); - return new SignaturesSignDocResponse(); + CredentialsService.CertificateResponse certificates = + this.credentialsService.getCertificateAndChainAndCommonSource( + signDocRequest.getRequest_uri(), + signDocRequest.getCredentialID(), + authorizationBearerHeader); + logger.info("Retrieved all the required certificates."); + + SignaturesSignDocResponse signaturesResponse = this.signatureService.handleDocumentsSignDocRequest( + signDocRequest, authorizationBearerHeader, certificates.getCertificate(), + certificates.getCertificateChain(), certificates.getSignAlgo(), date, certificates.getTsaCommonSource()); + logger.info("Obtained the documents signed."); + + return signaturesResponse; + } catch (Exception e) { + logger.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response"); } + } - if (signDocRequest.getDocuments() != null) { - try { - Date date = new Date(signDocRequest.getSignature_date()); - CommonTrustedCertificateSource commonTrustedCertificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateResponse.getCertificateChain()); - return this.signatureService.handleDocumentsSignDocRequest(signDocRequest, authorizationBearerHeader, certificateResponse.getCertificate(), certificateResponse.getCertificateChain(), certificateResponse.getSignAlgo(), date, commonTrustedCertificateSource); - } catch (Exception e) { - fileLogger.error(e.getMessage()); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response"); - } + @GetMapping(value="/calculate_hash", consumes = "application/json", produces = "application/json") + public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest requestDTO) throws Exception{ + List documents = requestDTO.getDocuments(); + X509Certificate signingCertificate = this.credentialsService.base64DecodeCertificate(requestDTO.getSigningCertificate()); + logger.info("Loaded signing certificate."); + List certificateChain = new ArrayList<>(); + for(String c: requestDTO.getCertificateChain()){ + certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); } - return new SignaturesSignDocResponse(); + logger.info("Loaded certificate chain."); + + String hashAlgorithmOID = requestDTO.getHashAlgorithmOID(); + Date date = new Date(); + System.out.println(date.getTime()); + + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); + logger.info("Loaded certificate source."); + List hashes = this.signatureService.calculateHashValue(documents, signingCertificate, certificateChain, hashAlgorithmOID, date, certificateSource); + logger.info("Created list of hashes."); + return new CalculateHashResponse(hashes, date.getTime()); } + @GetMapping(value="/obtain_signed_doc", consumes = "application/json", produces = "application/json") + public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocumentRequestDTO requestDTO) throws Exception{ + List documents = requestDTO.getDocuments(); + String hashAlgorithmOID = requestDTO.getHashAlgorithmOID(); + boolean returnValidationInfo = requestDTO.isReturnValidationInfo(); + X509Certificate signingCertificate = this.credentialsService.base64DecodeCertificate(requestDTO.getSigningCertificate()); + List certificateChain = new ArrayList<>(); + for(String c: requestDTO.getCertificateChain()){ + certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); + } + Date date = new Date(requestDTO.getDate()); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); + List signatures = requestDTO.getSignatures(); + return this.signatureService.buildSignedDocument(documents, hashAlgorithmOID, returnValidationInfo, signingCertificate, + certificateChain, date, certificateSource, signatures); + } } \ No newline at end of file diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthRequestTemporary.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthRequestTemporary.java deleted file mode 100644 index be586e9..0000000 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthRequestTemporary.java +++ /dev/null @@ -1,23 +0,0 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; - -public class AuthRequestTemporary { - private String url; - private String cookie; - - // Getters e Setters - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getCookie() { - return cookie; - } - - public void setCookie(String cookie) { - this.cookie = cookie; - } -} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java new file mode 100644 index 0000000..9a63eaf --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java @@ -0,0 +1,71 @@ +package eu.europa.ec.eudi.signer.r3.sca.web.dto; + +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; + +import java.util.List; + +public class SignedDocumentRequestDTO { + private List documents; + private String hashAlgorithmOID; + private boolean returnValidationInfo; + private String signingCertificate; + private List certificateChain; + private long date; + List signatures; + + public List getDocuments() { + return documents; + } + + public void setDocuments(List documents) { + this.documents = documents; + } + + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } + + public boolean isReturnValidationInfo() { + return returnValidationInfo; + } + + public void setReturnValidationInfo(boolean returnValidationInfo) { + this.returnValidationInfo = returnValidationInfo; + } + + public String getSigningCertificate() { + return signingCertificate; + } + + public void setSigningCertificate(String signingCertificate) { + this.signingCertificate = signingCertificate; + } + + public List getCertificateChain() { + return certificateChain; + } + + public void setCertificateChain(List certificateChain) { + this.certificateChain = certificateChain; + } + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + public List getSignatures() { + return signatures; + } + + public void setSignatures(List signatures) { + this.signatures = signatures; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java new file mode 100644 index 0000000..e24b231 --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java @@ -0,0 +1,45 @@ +package eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash; + +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; + +import java.util.List; + +public class CalculateHashRequest { + + private List documents; + private String signingCertificate; + private List certificateChain; + private String hashAlgorithmOID; + + public List getDocuments() { + return documents; + } + + public void setDocuments(List documents) { + this.documents = documents; + } + + public String getSigningCertificate() { + return signingCertificate; + } + + public void setSigningCertificate(String signingCertificate) { + this.signingCertificate = signingCertificate; + } + + public List getCertificateChain() { + return certificateChain; + } + + public void setCertificateChain(List certificateChain) { + this.certificateChain = certificateChain; + } + + public String getHashAlgorithmOID() { + return hashAlgorithmOID; + } + + public void setHashAlgorithmOID(String hashAlgorithmOID) { + this.hashAlgorithmOID = hashAlgorithmOID; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java new file mode 100644 index 0000000..eff647c --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java @@ -0,0 +1,29 @@ +package eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash; + +import java.util.List; + +public class CalculateHashResponse { + private List hashes; + private long signature_date; + + public CalculateHashResponse(List hashes, long signature_date) { + this.hashes = hashes; + this.signature_date = signature_date; + } + + public List getHashes() { + return hashes; + } + + public void setHashes(List hashes) { + this.hashes = hashes; + } + + public long getSignature_date() { + return signature_date; + } + + public void setSignature_date(long signature_date) { + this.signature_date = signature_date; + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java similarity index 91% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java index 3614adc..915507c 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoAuth.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfo; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java similarity index 96% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java index a7e388e..2243fb1 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoCert.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfo; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java similarity index 93% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java index d60acce..08a1673 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfo/CredentialsInfoKey.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfo; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfoRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java similarity index 96% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfoRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java index f3d67b2..5a386a5 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfoRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; public class CredentialsInfoRequest { private String credentialID; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfoResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java similarity index 85% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfoResponse.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java index 293caa6..2f0d22c 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialsInfo/CredentialsInfoResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java @@ -1,8 +1,5 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfo.CredentialsInfoAuth; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfo.CredentialsInfoCert; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.CredentialsInfo.CredentialsInfo.CredentialsInfoKey; import jakarta.validation.constraints.NotBlank; public class CredentialsInfoResponse { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialAuthorizationRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java similarity index 94% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialAuthorizationRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java index 421b218..9ee9fff 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/CredentialAuthorizationRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java @@ -1,10 +1,9 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.DocumentsSignDocRequest; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; import java.util.List; public class CredentialAuthorizationRequest { - private String credentialID; private String numSignatures; private List documents; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthResponseTemporary.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java similarity index 81% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthResponseTemporary.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java index 03527c5..62533cb 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/AuthResponseTemporary.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java @@ -1,11 +1,11 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2; -public class AuthResponseTemporary { +public class CredentialAuthorizationResponse { private String location_wallet; private String session_cookie; private long signature_date; - public AuthResponseTemporary(String location, String cookie){ + public CredentialAuthorizationResponse(String location, String cookie){ this.location_wallet = location; this.session_cookie = cookie; } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java similarity index 98% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java index 1e2916e..715c30b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/OAuth2AuthorizeRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/AttributeSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java similarity index 89% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/AttributeSignDocRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java index 20b8ddd..201bb48 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/AttributeSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/DocumentsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java similarity index 89% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/DocumentsSignDocRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java index dc320f4..c020299 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/DocumentsSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java @@ -1,10 +1,8 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; -import eu.europa.ec.eudi.signer.r3.sca.web.validator.DocumentsSignDocConstraintAnnotation; import jakarta.validation.constraints.NotBlank; import java.util.List; -@DocumentsSignDocConstraintAnnotation public class DocumentsSignDocRequest { @NotBlank private String document; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java similarity index 78% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java index 2354a35..00f527d 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignDocRequest/SignaturesSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java @@ -1,15 +1,11 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import com.fasterxml.jackson.annotation.JsonProperty; -import eu.europa.ec.eudi.signer.r3.sca.web.validator.SignDocRequestConstraintAnnotation; -import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import java.util.List; -@SignDocRequestConstraintAnnotation public class SignaturesSignDocRequest { private String credentialID; - @Valid private List documents; private Boolean returnValidationInfo = false; @NotBlank @@ -87,14 +83,16 @@ public void setSignature_date(long signature_date) { this.signature_date = signature_date; } - @java.lang.Override - public java.lang.String toString() { + @Override + public String toString() { return "SignaturesSignDocRequest{" + - "credentialID=" + credentialID + - ", documents=" + documents + - ", clientData=" + clientData + - ", returnValidationInfo=" + returnValidationInfo + - ", request_uri=" + request_uri + - '}'; + "credentialID='" + credentialID + '\'' + + ", documents=" + documents + + ", returnValidationInfo=" + returnValidationInfo + + ", request_uri='" + request_uri + '\'' + + ", hashAlgorithmOID='" + hashAlgorithmOID + '\'' + + ", signature_date=" + signature_date + + ", clientData='" + clientData + '\'' + + '}'; } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignDocResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java similarity index 97% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignDocResponse.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java index 438ab37..bda5ced 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignDocResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/ValidationInfoSignDocResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java similarity index 95% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/ValidationInfoSignDocResponse.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java index 65cbbf4..bb8469b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/ValidationInfoSignDocResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java similarity index 98% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignHashRequest.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java index 5e7ac1f..c519ac2 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignHashRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignHashResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java similarity index 89% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignHashResponse.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java index 0725ea8..7312b66 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignaturesSignHashResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java @@ -1,4 +1,4 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.dto; +package eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocConstraintAnnotation.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocConstraintAnnotation.java deleted file mode 100644 index 1bea8be..0000000 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocConstraintAnnotation.java +++ /dev/null @@ -1,22 +0,0 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.validator; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import jakarta.validation.Constraint; -import jakarta.validation.Payload; - -@Constraint(validatedBy = DocumentsSignDocRequestValidator.class) -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface DocumentsSignDocConstraintAnnotation { - - String message() default "The documents in the /signDoc body from the HTTP Request is invalid."; - - Class[] groups() default {}; - - Class[] payload() default {}; - -} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocRequestValidator.java deleted file mode 100644 index 2ac1e52..0000000 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/DocumentsSignDocRequestValidator.java +++ /dev/null @@ -1,35 +0,0 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.validator; - -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.DocumentsSignDocRequest; -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -public class DocumentsSignDocRequestValidator - implements ConstraintValidator { - - @Override - public void initialize(DocumentsSignDocConstraintAnnotation constraintAnnotation) { - - } - - @Override - public boolean isValid(DocumentsSignDocRequest request, ConstraintValidatorContext context) { - if (!request.getSignature_format().equals("C") && - !request.getSignature_format().equals("X") && - !request.getSignature_format().equals("P") && - !request.getSignature_format().equals("J")) - return false; - - if (request.getDocument() == null) { - return false; - } - - if (request.getSignature_format() == null) { - return false; - } - - return true; - - } - -} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignDocRequestConstraintAnnotation.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignDocRequestConstraintAnnotation.java deleted file mode 100644 index d10837b..0000000 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignDocRequestConstraintAnnotation.java +++ /dev/null @@ -1,20 +0,0 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.validator; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import jakarta.validation.Constraint; -import jakarta.validation.Payload; - -@Constraint(validatedBy = SignaturesSignDocRequestValidator.class) -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface SignDocRequestConstraintAnnotation { - String message() default "The /signDoc body from the HTTP Request is invalid."; - - Class[] groups() default {}; - - Class[] payload() default {}; -} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java deleted file mode 100644 index b0e7475..0000000 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/validator/SignaturesSignDocRequestValidator.java +++ /dev/null @@ -1,23 +0,0 @@ -package eu.europa.ec.eudi.signer.r3.sca.web.validator; - -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignDocRequest.SignaturesSignDocRequest; -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -public class SignaturesSignDocRequestValidator - implements ConstraintValidator { - - @Override - public void initialize(SignDocRequestConstraintAnnotation constraintAnnotation) { - - } - - @Override - public boolean isValid(SignaturesSignDocRequest request, ConstraintValidatorContext context) { - if (request.getRequest_uri() == null) - return false; - - return (request.getCredentialID() != null) && (request.getDocuments() != null); - } - -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 05b97ee..5ac033a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,9 @@ spring: application: name: sca + profiles: + include: + - "auth" server: port: 8086 @@ -9,12 +12,8 @@ logging: config: logback.xml level: org.hibernate: ERROR - org.springframework.web: DEBUG + org.springframework.web: INFO -oauth-client: - client-id: "sca-client" - client-secret: "somesecret1" - client-authentication-methods: - - "client_secret_basic" - redirect-uri: "http://localhost:8086/credential/oauth/login/code" - scope: "credential" \ No newline at end of file +trusted-certificate: + filename: "src/main/resources/TSA_CC.pem" + time-stamp-authority: "http://ts.cartaodecidadao.pt/tsa/server" \ No newline at end of file diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties deleted file mode 100644 index f246594..0000000 --- a/src/main/resources/config.properties +++ /dev/null @@ -1,2 +0,0 @@ -TrustedCertificates=src/main/resources/TSA_CC.pem -TSPServer=http://ts.cartaodecidadao.pt/tsa/server \ No newline at end of file From 5aee83cacee785dc79057cc23a17c8e2ea17c78f Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Mon, 21 Oct 2024 14:22:09 +0100 Subject: [PATCH 33/44] Small updates --- .../signer/r3/sca/config/OAuthClientConfig.java | 9 +++++++++ .../r3/sca/web/controller/CallbackController.java | 7 +++++-- .../r3/sca/web/controller/SignaturesController.java | 11 ++++++----- ...ntRequestDTO.java => SignedDocumentRequest.java} | 12 ++++++------ .../web/dto/calculateHash/CalculateHashRequest.java | 13 +++++++------ 5 files changed, 33 insertions(+), 19 deletions(-) rename src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/{SignedDocumentRequestDTO.java => SignedDocumentRequest.java} (83%) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java index 08f53ca..7e5f466 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java @@ -11,6 +11,7 @@ public class OAuthClientConfig { private String redirectUri; private String scope; private String defaultAuthorizationServerUrl; + private String appRedirectUri; public String getClientId() { return clientId; @@ -59,4 +60,12 @@ public String getDefaultAuthorizationServerUrl() { public void setDefaultAuthorizationServerUrl(String defaultAuthorizationServerUrl) { this.defaultAuthorizationServerUrl = defaultAuthorizationServerUrl; } + + public String getAppRedirectUri() { + return appRedirectUri; + } + + public void setAppRedirectUri(String appRedirectUri) { + this.appRedirectUri = appRedirectUri; + } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java index fb4c468..b6a80ab 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java @@ -1,5 +1,6 @@ package eu.europa.ec.eudi.signer.r3.sca.web.controller; +import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; import eu.europa.ec.eudi.signer.r3.sca.model.OAuth2Service; import org.json.JSONObject; import org.slf4j.Logger; @@ -17,9 +18,11 @@ public class CallbackController { private static final Logger logger = LoggerFactory.getLogger(CallbackController.class); private final OAuth2Service oAuth2Service; + private final OAuthClientConfig oAuthClientConfig; - public CallbackController(@Autowired OAuth2Service oAuth2Service) { + public CallbackController(@Autowired OAuth2Service oAuth2Service, @Autowired OAuthClientConfig oAuthClientConfig) { this.oAuth2Service = oAuth2Service; + this.oAuthClientConfig = oAuthClientConfig; } @GetMapping(value="/credential/oauth/login/code") @@ -27,7 +30,7 @@ public String credential_authorization_code(@RequestParam("code") String code, M try { JSONObject json = this.oAuth2Service.getOAuth2Token(code); model.addAttribute("body", json.toString()); - model.addAttribute("url", "http://127.0.0.1:5000/tester/oauth/credential/login/code"); + model.addAttribute("url", this.oAuthClientConfig.getAppRedirectUri()); return "successful_authentication"; } catch (Exception e){ logger.error(e.getMessage()); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 0a84c13..53be242 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -7,7 +7,7 @@ import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.SignaturesSignDocResponse; import eu.europa.ec.eudi.signer.r3.sca.model.CredentialsService; import eu.europa.ec.eudi.signer.r3.sca.model.SignatureService; -import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignedDocumentRequestDTO; +import eu.europa.ec.eudi.signer.r3.sca.web.dto.SignedDocumentRequest; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import java.security.cert.X509Certificate; @@ -95,9 +95,10 @@ public SignaturesSignDocResponse signDoc( @GetMapping(value="/calculate_hash", consumes = "application/json", produces = "application/json") public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest requestDTO) throws Exception{ List documents = requestDTO.getDocuments(); - X509Certificate signingCertificate = this.credentialsService.base64DecodeCertificate(requestDTO.getSigningCertificate()); + X509Certificate certificate = this.credentialsService.base64DecodeCertificate(requestDTO.getEndEntityCertificate()); logger.info("Loaded signing certificate."); List certificateChain = new ArrayList<>(); + System.out.println("Certificate Chain nb: "+requestDTO.getCertificateChain().size()); for(String c: requestDTO.getCertificateChain()){ certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); } @@ -109,17 +110,17 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); logger.info("Loaded certificate source."); - List hashes = this.signatureService.calculateHashValue(documents, signingCertificate, certificateChain, hashAlgorithmOID, date, certificateSource); + List hashes = this.signatureService.calculateHashValue(documents, certificate, certificateChain, hashAlgorithmOID, date, certificateSource); logger.info("Created list of hashes."); return new CalculateHashResponse(hashes, date.getTime()); } @GetMapping(value="/obtain_signed_doc", consumes = "application/json", produces = "application/json") - public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocumentRequestDTO requestDTO) throws Exception{ + public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocumentRequest requestDTO) throws Exception{ List documents = requestDTO.getDocuments(); String hashAlgorithmOID = requestDTO.getHashAlgorithmOID(); boolean returnValidationInfo = requestDTO.isReturnValidationInfo(); - X509Certificate signingCertificate = this.credentialsService.base64DecodeCertificate(requestDTO.getSigningCertificate()); + X509Certificate signingCertificate = this.credentialsService.base64DecodeCertificate(requestDTO.getEndEntityCertificate()); List certificateChain = new ArrayList<>(); for(String c: requestDTO.getCertificateChain()){ certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java similarity index 83% rename from src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java rename to src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java index 9a63eaf..af14696 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequestDTO.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java @@ -4,11 +4,11 @@ import java.util.List; -public class SignedDocumentRequestDTO { +public class SignedDocumentRequest { private List documents; private String hashAlgorithmOID; private boolean returnValidationInfo; - private String signingCertificate; + private String endEntityCertificate; private List certificateChain; private long date; List signatures; @@ -37,12 +37,12 @@ public void setReturnValidationInfo(boolean returnValidationInfo) { this.returnValidationInfo = returnValidationInfo; } - public String getSigningCertificate() { - return signingCertificate; + public String getEndEntityCertificate() { + return endEntityCertificate; } - public void setSigningCertificate(String signingCertificate) { - this.signingCertificate = signingCertificate; + public void setEndEntityCertificate(String endEntityCertificate) { + this.endEntityCertificate = endEntityCertificate; } public List getCertificateChain() { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java index e24b231..03b5ebc 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java @@ -2,13 +2,14 @@ import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; +import java.util.ArrayList; import java.util.List; public class CalculateHashRequest { private List documents; - private String signingCertificate; - private List certificateChain; + private String endEntityCertificate; + private List certificateChain = new ArrayList<>(); private String hashAlgorithmOID; public List getDocuments() { @@ -19,12 +20,12 @@ public void setDocuments(List documents) { this.documents = documents; } - public String getSigningCertificate() { - return signingCertificate; + public String getEndEntityCertificate() { + return endEntityCertificate; } - public void setSigningCertificate(String signingCertificate) { - this.signingCertificate = signingCertificate; + public void setEndEntityCertificate(String endEntityCertificate) { + this.endEntityCertificate = endEntityCertificate; } public List getCertificateChain() { From d2a2cc6b0a8d43dd4da3a0f6f2dd5bb21c1c50b2 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Tue, 22 Oct 2024 10:48:31 +0100 Subject: [PATCH 34/44] Fix bug: the current signed pdf files returned by the SCA where missing the certificate chain --- .../r3/sca/model/CredentialsService.java | 21 +- .../eudi/signer/r3/sca/model/DSSService.java | 237 +++++++----------- .../web/controller/SignaturesController.java | 4 +- .../calculateHash/CalculateHashRequest.java | 1 - 4 files changed, 104 insertions(+), 159 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index ec07c91..dd88dc7 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -41,9 +41,9 @@ public CredentialsService(@Autowired QTSPClient qtspClient, public static class CertificateResponse { private X509Certificate certificate; - private List certificateChain; + private final List certificateChain; private CommonTrustedCertificateSource tsaCommonSource; - private List signAlgo; + private final List signAlgo; public CertificateResponse(X509Certificate certificate, List certificateChain, List signAlgo) { @@ -64,10 +64,6 @@ public List getCertificateChain() { return certificateChain; } - public void setCertificateChain(List certificateChain) { - this.certificateChain = certificateChain; - } - public CommonTrustedCertificateSource getTsaCommonSource() { return tsaCommonSource; } @@ -80,15 +76,12 @@ public List getSignAlgo() { return signAlgo; } - public void setSignAlgo(List signAlgo) { - this.signAlgo = signAlgo; - } } public CertificateResponse getCertificateAndChainAndCommonSource(String qtspUrl, String credentialId, String authorizationBearerHeader){ CertificateResponse response = getCertificateAndCertificateChain(qtspUrl, credentialId, authorizationBearerHeader); logger.info("Retrieved the signing certificate and the certificate chain."); - CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(response.getCertificateChain()); + CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(); response.setTsaCommonSource(commonTrustedCertificateSource); logger.info("Retrieved the certificate source."); return response; @@ -113,7 +106,8 @@ private CertificateResponse getCertificateAndCertificateChain(String qtspUrl, St x509Certificates.add(cert); } catch (Exception e){ - e.printStackTrace(); + logger.error(e.getMessage()); + logger.error(e.getLocalizedMessage()); } } int i = x509Certificates.size() - 1; @@ -129,12 +123,9 @@ public X509Certificate base64DecodeCertificate(String certificate) throws Except return (X509Certificate)certFactory.generateCertificate(inputStream); } - public CommonTrustedCertificateSource getCommonTrustedCertificateSource (List certificateChain){ + public CommonTrustedCertificateSource getCommonTrustedCertificateSource (){ CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); certificateSource.addCertificate(this.TSACertificateToken); - for(X509Certificate cert: certificateChain){ - certificateSource.addCertificate(new CertificateToken(cert)); - } return certificateSource; } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index eb25a1b..b003678 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -1,5 +1,6 @@ package eu.europa.ec.eudi.signer.r3.sca.model; +import eu.europa.ec.eudi.signer.r3.sca.web.controller.SignaturesController; import eu.europa.esig.dss.AbstractSignatureParameters; import eu.europa.esig.dss.alert.ExceptionOnStatusAlert; import eu.europa.esig.dss.alert.LogOnStatusAlert; @@ -53,26 +54,26 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.slf4j.event.Level; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; @Service public class DSSService { - private static final Logger fileLogger = LoggerFactory.getLogger("FileLogger"); + private final static Logger logger = LogManager.getLogger(SignaturesController.class); public static SignatureLevel checkConformance_level(String conformance_level, String string) { String enumValue = mapToEnumValue(conformance_level, string); if (enumValue == null) { return null; } - try { return SignatureLevel.valueByName(enumValue); } catch (IllegalArgumentException e) { - e.printStackTrace(); + logger.error("Session_id:{}. Error message: {}.", RequestContextHolder.currentRequestAttributes().getSessionId(), e.getMessage()); } return null; @@ -94,102 +95,82 @@ private static String mapToEnumValue(String conformance_level, String string) { prefix = "XAdES_BASELINE_"; break; default: - fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Conformance Level invalid."); + logger.error("Session_id:{},Conformance Level invalid.", RequestContextHolder.currentRequestAttributes().getSessionId()); return null; } - switch (conformance_level) { - case "Ades-B-B": - return prefix + "B"; - case "Ades-B-LT": - return prefix + "LT"; - case "Ades-B-LTA": - return prefix + "LTA"; - case "Ades-B-T": - return prefix + "T"; - default: - fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Conformance Level invalid."); - return null; - } + return switch (conformance_level) { + case "Ades-B-B" -> prefix + "B"; + case "Ades-B-LT" -> prefix + "LT"; + case "Ades-B-LTA" -> prefix + "LTA"; + case "Ades-B-T" -> prefix + "T"; + default -> { + logger.error("Session_id:" + RequestContextHolder.currentRequestAttributes().getSessionId() + "," + "Conformance Level invalid."); + yield null; + } + }; } public static SignatureForm checkSignForm(String signForm) { - switch (signForm) { - case "P": - return SignatureForm.PAdES; - case "C": - return SignatureForm.CAdES; - case "J": - return SignatureForm.JAdES; - case "X": - return SignatureForm.XAdES; - default: - fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Format invalid."); - return null; - } + return switch (signForm) { + case "P" -> SignatureForm.PAdES; + case "C" -> SignatureForm.CAdES; + case "J" -> SignatureForm.JAdES; + case "X" -> SignatureForm.XAdES; + default -> { + logger.error("Session_id:{},Signature Format invalid.", RequestContextHolder.currentRequestAttributes().getSessionId()); + yield null; + } + }; } private static SignatureAlgorithm checkSignAlg(String alg) { - switch (alg) { - case "1.2.840.113549.1.1.11": - return SignatureAlgorithm.RSA_SHA256; - case "1.2.840.113549.1.1.12": - return SignatureAlgorithm.RSA_SHA384; - case "1.2.840.113549.1.1.13": - return SignatureAlgorithm.RSA_SHA512; - default: - return null; - } + return switch (alg) { + case "1.2.840.113549.1.1.11" -> SignatureAlgorithm.RSA_SHA256; + case "1.2.840.113549.1.1.12" -> SignatureAlgorithm.RSA_SHA384; + case "1.2.840.113549.1.1.13" -> SignatureAlgorithm.RSA_SHA512; + default -> null; + }; } public static DigestAlgorithm checkSignAlgDigest(String alg) { - switch (alg) { - case "1.2.840.113549.1.1.11": - return DigestAlgorithm.SHA256; - case "2.16.840.1.101.3.4.2.1": - return DigestAlgorithm.SHA256; - case "1.2.840.113549.1.1.12": - return DigestAlgorithm.SHA384; - case "2.16.840.1.101.3.4.2.2": - return DigestAlgorithm.SHA384; - case "1.2.840.113549.1.1.13": - return DigestAlgorithm.SHA512; - case "2.16.840.1.101.3.4.2.3": - return DigestAlgorithm.SHA512; - default: - fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Digest Algorithm invalid."); - return null; - } + return switch (alg) { + case "1.2.840.113549.1.1.11" -> DigestAlgorithm.SHA256; + case "2.16.840.1.101.3.4.2.1" -> DigestAlgorithm.SHA256; + case "1.2.840.113549.1.1.12" -> DigestAlgorithm.SHA384; + case "2.16.840.1.101.3.4.2.2" -> DigestAlgorithm.SHA384; + case "1.2.840.113549.1.1.13" -> DigestAlgorithm.SHA512; + case "2.16.840.1.101.3.4.2.3" -> DigestAlgorithm.SHA512; + default -> { + logger.error("Session_id:{},Signature Digest Algorithm invalid.", RequestContextHolder.currentRequestAttributes().getSessionId()); + yield null; + } + }; } public static ASiCContainerType checkASiCContainerType(String alg) { - switch (alg) { - case "No": - return null; - case "ASiC-E": - return ASiCContainerType.ASiC_E; - case "ASiC-S": - return ASiCContainerType.ASiC_S; - default: - fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"ASICC Container Type invalid."); - return null; - } + return switch (alg) { + case "No" -> null; + case "ASiC-E" -> ASiCContainerType.ASiC_E; + case "ASiC-S" -> ASiCContainerType.ASiC_S; + default -> { + logger.error("Session_id:{},ASICC Container Type invalid.", RequestContextHolder.currentRequestAttributes().getSessionId()); + yield null; + } + }; } public static SignaturePackaging checkEnvProps(String env) { - switch (env) { - case "ENVELOPED": - return SignaturePackaging.ENVELOPED; - case "ENVELOPING": - return SignaturePackaging.ENVELOPING; - case "DETACHED": - return SignaturePackaging.DETACHED; - case "INTERNALLY_DETACHED": - return SignaturePackaging.INTERNALLY_DETACHED; - default: - fileLogger.error("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"Signature Packaging invalid."); - return null; - } + return switch (env) { + case "ENVELOPED" -> SignaturePackaging.ENVELOPED; + case "ENVELOPING" -> SignaturePackaging.ENVELOPING; + case "DETACHED" -> SignaturePackaging.DETACHED; + case "INTERNALLY_DETACHED" -> SignaturePackaging.INTERNALLY_DETACHED; + default -> { + logger.error("Session_id:{},Signature Packaging invalid.", RequestContextHolder.currentRequestAttributes().getSessionId()); + yield null; + } + }; } public DSSDocument loadDssDocument(String document) { @@ -198,15 +179,15 @@ public DSSDocument loadDssDocument(String document) { } @SuppressWarnings({ "rawtypes", "unchecked" }) - public byte[] DataToBeSignedData(SignatureDocumentForm form) throws CertificateException { + public byte[] DataToBeSignedData(SignatureDocumentForm form) { DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"DataToBeSignedData Service created."); + logger.info("Session_id:{},DataToBeSignedData Service created.", RequestContextHolder.currentRequestAttributes().getSessionId()); AbstractSignatureParameters parameters = fillParameters(form); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +","+"DataToBeSignedData Parameters Filled."); + logger.info("Session_id:{},DataToBeSignedData Parameters Filled.", RequestContextHolder.currentRequestAttributes().getSessionId()); DSSDocument toSignDocument = form.getDocumentToSign(); ToBeSigned toBeSigned = service.getDataToSign(toSignDocument, parameters); @@ -219,10 +200,10 @@ public DSSDocument signDocument(SignatureDocumentForm form) { DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +", signDocument Service created."); + logger.info("Session_id:{}, signDocument Service created.", RequestContextHolder.currentRequestAttributes().getSessionId()); AbstractSignatureParameters parameters = fillParameters(form); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +", DataToBeSignedData Parameters Filled."); + logger.info("Session_id:{}, DataToBeSignedData Parameters Filled.", RequestContextHolder.currentRequestAttributes().getSessionId()); System.out.println("######: "+parameters.getCertificateChain().size()); @@ -269,9 +250,7 @@ private void fillParameters(AbstractSignatureParameters parameters, SignatureDoc @SuppressWarnings({ "rawtypes", "unchecked" }) private void fillTimestampParameters(AbstractSignatureParameters parameters, SignatureDocumentForm form) { SignatureForm signatureForm = form.getSignatureForm(); - - ASiCContainerType containerType = null; - containerType = form.getContainerType(); + ASiCContainerType containerType = form.getContainerType(); TimestampParameters timestampParameters = getTimestampParameters(containerType, signatureForm); timestampParameters.setDigestAlgorithm(form.getDigestAlgorithm()); @@ -322,26 +301,18 @@ private DocumentSignatureService getSignatureService(ASiCContainerType container cv.setRevocationFallback(true); - DocumentSignatureService service = null; + DocumentSignatureService service; if (containerType != null) { service = (DocumentSignatureService) getASiCSignatureService(signatureForm, cv); } else { - switch (signatureForm) { - case CAdES: - service = new CAdESService(cv); - break; - case PAdES: - service = new PAdESService(cv); - break; - case XAdES: - service = new XAdESService(cv); - break; - case JAdES: - service = new JAdESService(cv); - break; - default: - throw new IllegalArgumentException(String.format("Unknown signature form : %s", signatureForm)); - } + service = switch (signatureForm) { + case CAdES -> new CAdESService(cv); + case PAdES -> new PAdESService(cv); + case XAdES -> new XAdESService(cv); + case JAdES -> new JAdESService(cv); + default -> + throw new IllegalArgumentException(String.format("Unknown signature form : %s", signatureForm)); + }; } String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; @@ -353,41 +324,25 @@ private DocumentSignatureService getSignatureService(ASiCContainerType container @SuppressWarnings("rawtypes") private MultipleDocumentsSignatureService getASiCSignatureService(SignatureForm signatureForm, CertificateVerifier cv) { - MultipleDocumentsSignatureService service = null; - switch (signatureForm) { - case CAdES: - service = new ASiCWithCAdESService(cv); - break; - case XAdES: - service = new ASiCWithXAdESService(cv); - break; - default: - throw new IllegalArgumentException( - String.format("Not supported signature form for an ASiC container : %s", signatureForm)); - } - return service; + return switch (signatureForm) { + case CAdES -> new ASiCWithCAdESService(cv); + case XAdES -> new ASiCWithXAdESService(cv); + default -> throw new IllegalArgumentException( + String.format("Not supported signature form for an ASiC container : %s", signatureForm)); + }; } private TimestampParameters getTimestampParameters(ASiCContainerType containerType, SignatureForm signatureForm) { - TimestampParameters parameters = null; + TimestampParameters parameters; if (containerType == null) { - switch (signatureForm) { - case CAdES: - parameters = new CAdESTimestampParameters(); - break; - case XAdES: - parameters = new XAdESTimestampParameters(); - break; - case PAdES: - parameters = new PAdESTimestampParameters(); - break; - case JAdES: - parameters = new JAdESTimestampParameters(); - break; - default: - throw new IllegalArgumentException( - String.format("Not supported signature form for a time-stamp : %s", signatureForm)); - } + parameters = switch (signatureForm) { + case CAdES -> new CAdESTimestampParameters(); + case XAdES -> new XAdESTimestampParameters(); + case PAdES -> new PAdESTimestampParameters(); + case JAdES -> new JAdESTimestampParameters(); + default -> throw new IllegalArgumentException( + String.format("Not supported signature form for a time-stamp : %s", signatureForm)); + }; } else { switch (signatureForm) { @@ -409,7 +364,7 @@ private TimestampParameters getTimestampParameters(ASiCContainerType containerTy @SuppressWarnings({ "rawtypes" }) private AbstractSignatureParameters getSignatureParameters(ASiCContainerType containerType, SignatureForm signatureForm) { - AbstractSignatureParameters parameters = null; + AbstractSignatureParameters parameters; if (containerType != null) { parameters = getASiCSignatureParameters(containerType, signatureForm); } else { @@ -443,7 +398,7 @@ private AbstractSignatureParameters getSignatureParameters(ASiCContainerType con @SuppressWarnings({ "rawtypes" }) private AbstractSignatureParameters getASiCSignatureParameters(ASiCContainerType containerType, SignatureForm signatureForm) { - AbstractSignatureParameters parameters = null; + AbstractSignatureParameters parameters; switch (signatureForm) { case CAdES: ASiCWithCAdESSignatureParameters asicCadesParams = new ASiCWithCAdESSignatureParameters(); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 53be242..53e627b 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -108,7 +108,7 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req Date date = new Date(); System.out.println(date.getTime()); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); logger.info("Loaded certificate source."); List hashes = this.signatureService.calculateHashValue(documents, certificate, certificateChain, hashAlgorithmOID, date, certificateSource); logger.info("Created list of hashes."); @@ -126,7 +126,7 @@ public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocume certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); } Date date = new Date(requestDTO.getDate()); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); List signatures = requestDTO.getSignatures(); return this.signatureService.buildSignedDocument(documents, hashAlgorithmOID, returnValidationInfo, signingCertificate, certificateChain, date, certificateSource, signatures); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java index 03b5ebc..f2c2bf9 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java @@ -6,7 +6,6 @@ import java.util.List; public class CalculateHashRequest { - private List documents; private String endEntityCertificate; private List certificateChain = new ArrayList<>(); From 058a75a2519c840a63a2ec356739e964ac89c05d Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Sun, 27 Oct 2024 19:18:19 +0000 Subject: [PATCH 35/44] Change methods of the endpoints /calculate_hash and /obtain_signed_doc from GET to POST --- .../eudi/signer/r3/sca/model/DSSService.java | 1 - .../signer/r3/sca/model/OAuth2Service.java | 42 ++----------------- .../web/controller/SignaturesController.java | 4 +- 3 files changed, 6 insertions(+), 41 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index b003678..08f3fe9 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -49,7 +49,6 @@ import eu.europa.esig.dss.validation.CommonCertificateVerifier; import eu.europa.esig.dss.validation.OCSPFirstRevocationDataLoadingStrategyFactory; import eu.europa.esig.dss.validation.RevocationDataVerifier; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java index cb3777a..d7b81b9 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java @@ -4,16 +4,14 @@ import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationResponse; import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationRequest; import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.OAuth2AuthorizeRequest; -import org.json.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.security.SecureRandom; import java.util.Base64; import java.util.Date; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; @Service public class OAuth2Service { @@ -27,14 +25,9 @@ public OAuth2Service(@Autowired QTSPClient qtspClient, } private String generateNonce(String root) throws Exception{ - SecureRandom prng = new SecureRandom(); - String randomNum = String.valueOf(prng.nextInt()); - System.out.println("Code_Verifier: "+ root); MessageDigest sha = MessageDigest.getInstance("SHA-256"); byte[] result = sha.digest(root.getBytes()); - String code_challenge = Base64.getUrlEncoder().encodeToString(result); - System.out.println("Code_Challenge: "+code_challenge); - return code_challenge; + return Base64.getUrlEncoder().withoutPadding().encodeToString(result); } private static String getBasicAuthenticationHeader(String username, String password) { @@ -61,33 +54,6 @@ public CredentialAuthorizationResponse getOAuth2Authorize(CredentialAuthorizatio authorizeRequest.setHashes(hash); authorizeRequest.setHashAlgorithmOID(credentialAuthorization.getHashAlgorithmOID()); - /* - JSONArray documentDigests = new JSONArray(); - for(String h: hashes){ - JSONObject documentDigest = new JSONObject(); - documentDigest.put("hash", h); - documentDigest.put("label", "This is some document hash"); - documentDigests.put(documentDigest); - } - - JSONObject authorization_details = new JSONObject(); - authorization_details.put("type", "credential"); - authorization_details.put("credentialID", URLEncoder.encode(credentialAuthorization.getCredentialID(), StandardCharsets.UTF_8)); - authorization_details.put("documentDigests", documentDigests); - authorization_details.put("hashAlgorithmOID", credentialAuthorization.getHashAlgorithmOID()); - System.out.println(authorization_details); - - OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest(); - authorizeRequest.setResponse_type("code"); - authorizeRequest.setClient_id("sca-client"); - authorizeRequest.setRedirect_uri("http://localhost:8086/credential/oauth/login/code"); - authorizeRequest.setCode_challenge(code_challenge); - authorizeRequest.setCode_challenge_method("S256"); - authorizeRequest.setLang("pt-PT"); - authorizeRequest.setState("12345678"); - authorizeRequest.setAuthorization_details(URLEncoder.encode(authorization_details.toString(), StandardCharsets.UTF_8)); - System.out.println(authorizeRequest);*/ - CredentialAuthorizationResponse responseTemporary = this.qtspClient.requestOAuth2Authorize(credentialAuthorization.getAuthorizationServerUrl(), authorizeRequest, authorizationBearerHeader); responseTemporary.setSignature_date(date.getTime()); return responseTemporary; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 53e627b..8434e4e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -92,7 +92,7 @@ public SignaturesSignDocResponse signDoc( } } - @GetMapping(value="/calculate_hash", consumes = "application/json", produces = "application/json") + @PostMapping(value="/calculate_hash", consumes = "application/json", produces = "application/json") public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest requestDTO) throws Exception{ List documents = requestDTO.getDocuments(); X509Certificate certificate = this.credentialsService.base64DecodeCertificate(requestDTO.getEndEntityCertificate()); @@ -115,7 +115,7 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req return new CalculateHashResponse(hashes, date.getTime()); } - @GetMapping(value="/obtain_signed_doc", consumes = "application/json", produces = "application/json") + @PostMapping(value="/obtain_signed_doc", consumes = "application/json", produces = "application/json") public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocumentRequest requestDTO) throws Exception{ List documents = requestDTO.getDocuments(); String hashAlgorithmOID = requestDTO.getHashAlgorithmOID(); From 1f238058ab9e90e69f9dd76a77ccca080e2a8e6d Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Tue, 5 Nov 2024 10:42:40 +0000 Subject: [PATCH 36/44] Update code to support multiple key algorithms: modify function to identify multiple algorithms by OID. Add additional logs. Enable dynamic configuration of the encryption algorithm. --- .../eudi/signer/r3/sca/model/DSSService.java | 34 +++++------ .../signer/r3/sca/model/SignatureService.java | 36 ++++-------- .../web/controller/SignaturesController.java | 58 ++++++++++++++++++- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index 08f3fe9..7009116 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -123,28 +123,22 @@ public static SignatureForm checkSignForm(String signForm) { }; } - private static SignatureAlgorithm checkSignAlg(String alg) { - return switch (alg) { - case "1.2.840.113549.1.1.11" -> SignatureAlgorithm.RSA_SHA256; - case "1.2.840.113549.1.1.12" -> SignatureAlgorithm.RSA_SHA384; - case "1.2.840.113549.1.1.13" -> SignatureAlgorithm.RSA_SHA512; - default -> null; - }; - } - - public static DigestAlgorithm checkSignAlgDigest(String alg) { - return switch (alg) { - case "1.2.840.113549.1.1.11" -> DigestAlgorithm.SHA256; - case "2.16.840.1.101.3.4.2.1" -> DigestAlgorithm.SHA256; - case "1.2.840.113549.1.1.12" -> DigestAlgorithm.SHA384; - case "2.16.840.1.101.3.4.2.2" -> DigestAlgorithm.SHA384; - case "1.2.840.113549.1.1.13" -> DigestAlgorithm.SHA512; - case "2.16.840.1.101.3.4.2.3" -> DigestAlgorithm.SHA512; - default -> { + public static DigestAlgorithm checkDigestAlgorithm(String alg) throws Exception{ + try { + return DigestAlgorithm.forOID(alg); + } + catch (IllegalArgumentException e){ + logger.info("Session_id:{}, hashAlgorithmOID given doesn't match a DigestAlgorithm. Try to load SignatureAlgorithm.", RequestContextHolder.currentRequestAttributes().getSessionId()); + + try { + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forOID(alg); + return signatureAlgorithm.getDigestAlgorithm(); + } + catch (IllegalArgumentException e1){ logger.error("Session_id:{},Signature Digest Algorithm invalid.", RequestContextHolder.currentRequestAttributes().getSessionId()); - yield null; + throw new Exception("A DigestAlgorithm was not found from the hashAlgorithmOID."); } - }; + } } public static ASiCContainerType checkASiCContainerType(String alg) { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index ca2f719..efd1d24 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -43,12 +43,13 @@ public List calculateHashValue(List documents, DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); - DigestAlgorithm aux_digest_alg = DSSService.checkSignAlgDigest(hashAlgorithmOID); + DigestAlgorithm aux_digest_alg = DSSService.checkDigestAlgorithm(hashAlgorithmOID); + EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(signingCertificate.getPublicKey().getAlgorithm()); SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); SignatureForm signatureForm = DSSService.checkSignForm(document.getSignature_format()); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Payload Received:{ Document Hash:"+ dssDocument.getDigest(aux_digest_alg) +", conformance_level:" +document.getConformance_level()+ ","+ + fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Payload Received:{ Document Hash:"+ aux_digest_alg +", conformance_level:" +document.getConformance_level()+ ","+ "Signature Format:"+ document.getSignature_format() + ", Hash Algorithm OID:"+ hashAlgorithmOID + ", Signature Packaging:"+ document.getSigned_envelope_property() + ", Type of Container:"+ document.getContainer() + "}"); SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); @@ -63,7 +64,7 @@ public List calculateHashValue(List documents, signatureDocumentForm.setTrustedCertificates(certificateSource); signatureDocumentForm.setSignatureForm(signatureForm); signatureDocumentForm.setCertChain(certificateChain); - signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); byte[] dataToBeSigned = dssClient.DataToBeSignedData(signatureDocumentForm); fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",DataToBeSigned successfully created"); @@ -98,7 +99,8 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); - DigestAlgorithm aux_digest_alg = DSSService.checkSignAlgDigest(signDocRequest.getHashAlgorithmOID()); + DigestAlgorithm aux_digest_alg = DSSService.checkDigestAlgorithm(signDocRequest.getHashAlgorithmOID()); + EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); SignatureForm signatureForm= DSSService.checkSignForm(document.getSignature_format()); @@ -115,7 +117,7 @@ public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDoc signatureDocumentForm.setTrustedCertificates(certificateSource); signatureDocumentForm.setSignatureForm(signatureForm); signatureDocumentForm.setCertChain(certificateChain); - signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); signatureDocumentForm.setSignatureValue(Base64.getDecoder().decode(signatureValue)); DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); @@ -189,7 +191,8 @@ public SignaturesSignDocResponse buildSignedDocument( DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); - DigestAlgorithm aux_digest_alg = DSSService.checkSignAlgDigest(hashAlgorithmOID); + DigestAlgorithm aux_digest_alg = DSSService.checkDigestAlgorithm(hashAlgorithmOID); + EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); SignatureForm signatureForm= DSSService.checkSignForm(document.getSignature_format()); @@ -206,7 +209,7 @@ public SignaturesSignDocResponse buildSignedDocument( signatureDocumentForm.setTrustedCertificates(certificateSource); signatureDocumentForm.setSignatureForm(signatureForm); signatureDocumentForm.setCertChain(certificateChain); - signatureDocumentForm.setEncryptionAlgorithm(EncryptionAlgorithm.RSA); + signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); signatureDocumentForm.setSignatureValue(Base64.getDecoder().decode(signatureValue)); DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); @@ -216,40 +219,23 @@ public SignaturesSignDocResponse buildSignedDocument( if (document.getContainer().equals("ASiC-E")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); - docSigned.save("tests/exampleSigned.cse"); - File file = new File("tests/exampleSigned.cse"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); } } else if (document.getContainer().equals("ASiC-S")) { if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); - docSigned.save("tests/exampleSigned.scs"); - File file = new File("tests/exampleSigned.scs"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); } } else if (document.getSignature_format().equals("J")) { docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); - docSigned.save("tests/exampleSigned.json"); - File file = new File("tests/exampleSigned.json"); - byte[] jsonBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(jsonBytes)); } else if (document.getSignature_format().equals("X")) { docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); - docSigned.save("tests/exampleSigned.xml"); - File file = new File("tests/exampleSigned.xml"); - byte[] xmlBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); } else { docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); - docSigned.save("tests/exampleSigned.pdf"); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); } + DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); } catch (Exception e) { fileLogger.error("invalid request: "+ e.getMessage()); throw e; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 8434e4e..f5ce3ed 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -94,40 +94,94 @@ public SignaturesSignDocResponse signDoc( @PostMapping(value="/calculate_hash", consumes = "application/json", produces = "application/json") public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest requestDTO) throws Exception{ + List documents = requestDTO.getDocuments(); + if(requestDTO.getDocuments() == null){ + logger.error("The documents to be signed should be sent in the Http Request Body."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "invalid_response: the documents to be signed should be sent in the request."); + } + + + if(requestDTO.getEndEntityCertificate() == null){ + logger.error("The certificate is missing from the request."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the certificate parameter is missing."); + } X509Certificate certificate = this.credentialsService.base64DecodeCertificate(requestDTO.getEndEntityCertificate()); logger.info("Loaded signing certificate."); + + List certificateChain = new ArrayList<>(); - System.out.println("Certificate Chain nb: "+requestDTO.getCertificateChain().size()); for(String c: requestDTO.getCertificateChain()){ certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); } logger.info("Loaded certificate chain."); String hashAlgorithmOID = requestDTO.getHashAlgorithmOID(); + if(hashAlgorithmOID == null){ + logger.error("The digest/hash algorithm oid parameter is missing."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the hash algorithm oid is missing."); + } + Date date = new Date(); - System.out.println(date.getTime()); CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); logger.info("Loaded certificate source."); + List hashes = this.signatureService.calculateHashValue(documents, certificate, certificateChain, hashAlgorithmOID, date, certificateSource); logger.info("Created list of hashes."); + return new CalculateHashResponse(hashes, date.getTime()); } @PostMapping(value="/obtain_signed_doc", consumes = "application/json", produces = "application/json") public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocumentRequest requestDTO) throws Exception{ List documents = requestDTO.getDocuments(); + if(documents == null || documents.isEmpty()){ + logger.error("The documents to be signed should be sent in the Http Request Body."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "invalid_response: the documents to be signed should be sent in the request."); + } + String hashAlgorithmOID = requestDTO.getHashAlgorithmOID(); + if(hashAlgorithmOID == null){ + logger.error("The digest/hash algorithm oid parameter is missing."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the hash algorithm oid is missing."); + } + boolean returnValidationInfo = requestDTO.isReturnValidationInfo(); + + if(requestDTO.getEndEntityCertificate() == null){ + logger.error("The certificate is missing from the request."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the certificate parameter is missing."); + } X509Certificate signingCertificate = this.credentialsService.base64DecodeCertificate(requestDTO.getEndEntityCertificate()); + List certificateChain = new ArrayList<>(); for(String c: requestDTO.getCertificateChain()){ certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); } + logger.info("Loaded certificate chain."); + + if(requestDTO.getDate() == -1){ + logger.error("The date parameter is missing."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the date parameter is missing."); + } Date date = new Date(requestDTO.getDate()); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); + logger.info("Loaded the certificate source"); + List signatures = requestDTO.getSignatures(); + if(signatures == null || signatures.isEmpty()){ + logger.error("The signature parameter is missing."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the signature parameter is missing."); + } + if(signatures.size() != documents.size()){ + logger.error("The number of signatures received doesn't match the number of documents to signed received."); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: " + + "the number of signatures received doesn't match the number of documents to signed received."); + } return this.signatureService.buildSignedDocument(documents, hashAlgorithmOID, returnValidationInfo, signingCertificate, certificateChain, date, certificateSource, signatures); } From 97777348bad0832fd042b7a20ccf62c2dc955d77 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Fri, 8 Nov 2024 13:26:52 +0000 Subject: [PATCH 37/44] Initial stages of updating the document digest calculation. --- .../ec/eudi/signer/r3/sca/model/DSSService.java | 16 ++++++---------- .../signer/r3/sca/model/SignatureService.java | 1 - .../sca/web/controller/SignaturesController.java | 1 + 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index 7009116..01d7038 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -1,6 +1,5 @@ package eu.europa.ec.eudi.signer.r3.sca.model; -import eu.europa.ec.eudi.signer.r3.sca.web.controller.SignaturesController; import eu.europa.esig.dss.AbstractSignatureParameters; import eu.europa.esig.dss.alert.ExceptionOnStatusAlert; import eu.europa.esig.dss.alert.LogOnStatusAlert; @@ -20,14 +19,11 @@ import eu.europa.esig.dss.enumerations.SignatureForm; import eu.europa.esig.dss.enumerations.SignatureLevel; import eu.europa.esig.dss.enumerations.SignaturePackaging; -import eu.europa.esig.dss.model.DSSDocument; -import eu.europa.esig.dss.model.InMemoryDocument; -import eu.europa.esig.dss.model.SignatureValue; -import eu.europa.esig.dss.model.TimestampParameters; -import eu.europa.esig.dss.model.ToBeSigned; +import eu.europa.esig.dss.model.*; import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.pades.PAdESSignatureParameters; import eu.europa.esig.dss.pades.PAdESTimestampParameters; +import eu.europa.esig.dss.spi.DSSUtils; import eu.europa.esig.dss.xades.XAdESSignatureParameters; import eu.europa.esig.dss.xades.XAdESTimestampParameters; import eu.europa.esig.dss.xades.signature.XAdESService; @@ -49,6 +45,7 @@ import eu.europa.esig.dss.validation.CommonCertificateVerifier; import eu.europa.esig.dss.validation.OCSPFirstRevocationDataLoadingStrategyFactory; import eu.europa.esig.dss.validation.RevocationDataVerifier; + import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; @@ -62,7 +59,7 @@ @Service public class DSSService { - private final static Logger logger = LogManager.getLogger(SignaturesController.class); + private final static Logger logger = LogManager.getLogger(DSSService.class); public static SignatureLevel checkConformance_level(String conformance_level, String string) { String enumValue = mapToEnumValue(conformance_level, string); @@ -184,8 +181,7 @@ public byte[] DataToBeSignedData(SignatureDocumentForm form) { DSSDocument toSignDocument = form.getDocumentToSign(); ToBeSigned toBeSigned = service.getDataToSign(toSignDocument, parameters); - return toBeSigned.getBytes(); - + return DSSUtils.digest(parameters.getDigestAlgorithm(), toBeSigned.getBytes()); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -206,7 +202,7 @@ public DSSDocument signDocument(SignatureDocumentForm form) { signatureValue.setAlgorithm(SignatureAlgorithm.getAlgorithm(form.getEncryptionAlgorithm(), form.getDigestAlgorithm())); signatureValue.setValue(form.getSignatureValue()); - return service.signDocument(toSignDocument, parameters, signatureValue); + return service.signDocument(toSignDocument, parameters, signatureValue); } @SuppressWarnings({ "rawtypes" }) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index efd1d24..849f8cd 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -175,7 +175,6 @@ else if (document.getSignature_format().equals("X")) { return new SignaturesSignDocResponse(DocumentWithSignature, allSignaturesObjects, null, validationInfo); } - public SignaturesSignDocResponse buildSignedDocument( List documents, String hashAlgorithmOID, boolean returnValidationInfo, X509Certificate certificate, List certificateChain, Date date, diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index f5ce3ed..c941559 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -163,6 +163,7 @@ public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocume } logger.info("Loaded certificate chain."); + System.out.println(requestDTO.getDate()); if(requestDTO.getDate() == -1){ logger.error("The date parameter is missing."); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the date parameter is missing."); From 838db3cba6f593ed29adcddc1ad2715afcb76e62 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Sat, 9 Nov 2024 21:17:33 +0000 Subject: [PATCH 38/44] WIP --- .../r3/sca/model/CredentialsService.java | 7 +- .../eudi/signer/r3/sca/model/DSSService.java | 14 +- .../signer/r3/sca/model/SignatureService.java | 282 ++++++++---------- .../web/controller/SignaturesController.java | 6 +- 4 files changed, 132 insertions(+), 177 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index dd88dc7..86fa3b7 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -81,7 +81,7 @@ public List getSignAlgo() { public CertificateResponse getCertificateAndChainAndCommonSource(String qtspUrl, String credentialId, String authorizationBearerHeader){ CertificateResponse response = getCertificateAndCertificateChain(qtspUrl, credentialId, authorizationBearerHeader); logger.info("Retrieved the signing certificate and the certificate chain."); - CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(); + CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(response.getCertificateChain()); response.setTsaCommonSource(commonTrustedCertificateSource); logger.info("Retrieved the certificate source."); return response; @@ -123,9 +123,12 @@ public X509Certificate base64DecodeCertificate(String certificate) throws Except return (X509Certificate)certFactory.generateCertificate(inputStream); } - public CommonTrustedCertificateSource getCommonTrustedCertificateSource (){ + public CommonTrustedCertificateSource getCommonTrustedCertificateSource (List certificateChain){ CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); certificateSource.addCertificate(this.TSACertificateToken); + for(X509Certificate cert: certificateChain){ + certificateSource.addCertificate(new CertificateToken(cert)); + } return certificateSource; } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index 01d7038..469a0cf 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -169,14 +169,15 @@ public DSSDocument loadDssDocument(String document) { } @SuppressWarnings({ "rawtypes", "unchecked" }) - public byte[] DataToBeSignedData(SignatureDocumentForm form) { - - DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), - form.getTrustedCertificates()); + /** + * Function that returns the digest of the data to be signed from the document and parameters received + */ + public byte[] getDigestOfDataToBeSigned(SignatureDocumentForm form) { + DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); logger.info("Session_id:{},DataToBeSignedData Service created.", RequestContextHolder.currentRequestAttributes().getSessionId()); - AbstractSignatureParameters parameters = fillParameters(form); + AbstractSignatureParameters parameters = fillParameters(form); logger.info("Session_id:{},DataToBeSignedData Parameters Filled.", RequestContextHolder.currentRequestAttributes().getSessionId()); DSSDocument toSignDocument = form.getDocumentToSign(); @@ -188,14 +189,11 @@ public byte[] DataToBeSignedData(SignatureDocumentForm form) { public DSSDocument signDocument(SignatureDocumentForm form) { DocumentSignatureService service = getSignatureService(form.getContainerType(), form.getSignatureForm(), form.getTrustedCertificates()); - logger.info("Session_id:{}, signDocument Service created.", RequestContextHolder.currentRequestAttributes().getSessionId()); AbstractSignatureParameters parameters = fillParameters(form); logger.info("Session_id:{}, DataToBeSignedData Parameters Filled.", RequestContextHolder.currentRequestAttributes().getSessionId()); - System.out.println("######: "+parameters.getCertificateChain().size()); - DSSDocument toSignDocument = form.getDocumentToSign(); SignatureValue signatureValue = new SignatureValue(); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index 849f8cd..1b92b8c 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -8,6 +8,7 @@ import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.ValidationInfoSignDocResponse; import eu.europa.esig.dss.enumerations.*; import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,10 +16,8 @@ import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; -import java.io.File; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; @@ -32,139 +31,78 @@ public class SignatureService { private final QTSPClient qtspClient; private final DSSService dssClient; - public SignatureService(@Autowired QTSPClient qtspClient, @Autowired DSSService dssClient){ + public SignatureService(@Autowired QTSPClient qtspClient, @Autowired DSSService dssClient) { this.qtspClient = qtspClient; this.dssClient = dssClient; } - public List calculateHashValue(List documents, X509Certificate signingCertificate, List certificateChain, String hashAlgorithmOID, Date date, CommonTrustedCertificateSource certificateSource) throws Exception{ + public List calculateHashValue(List documents, X509Certificate certificate, + List certificateChain, String hashAlgorithmOID, Date date, + CommonTrustedCertificateSource certificateSource) throws Exception { + + DigestAlgorithm digestAlgorithm = DSSService.checkDigestAlgorithm(hashAlgorithmOID); + EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); + List hashes = new ArrayList<>(); for (DocumentsSignDocRequest document : documents) { - DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - - SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); - DigestAlgorithm aux_digest_alg = DSSService.checkDigestAlgorithm(hashAlgorithmOID); - EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(signingCertificate.getPublicKey().getAlgorithm()); - SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); - ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); - SignatureForm signatureForm = DSSService.checkSignForm(document.getSignature_format()); - - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Payload Received:{ Document Hash:"+ aux_digest_alg +", conformance_level:" +document.getConformance_level()+ ","+ - "Signature Format:"+ document.getSignature_format() + ", Hash Algorithm OID:"+ hashAlgorithmOID + ", Signature Packaging:"+ document.getSigned_envelope_property() + ", Type of Container:"+ document.getContainer() + "}"); - - SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); - signatureDocumentForm.setDocumentToSign(dssDocument); - signatureDocumentForm.setSignaturePackaging(aux_sign_pack); - signatureDocumentForm.setContainerType(aux_asic_ContainerType); - signatureDocumentForm.setSignatureLevel(aux_sign_level); - signatureDocumentForm.setDigestAlgorithm(aux_digest_alg); - signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertificate(signingCertificate); - signatureDocumentForm.setDate(date); - signatureDocumentForm.setTrustedCertificates(certificateSource); - signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertChain(certificateChain); - signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); - - byte[] dataToBeSigned = dssClient.DataToBeSignedData(signatureDocumentForm); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",DataToBeSigned successfully created"); + fileLogger.info("Session_id:{},Payload Received:{ Document Hash:{}, conformance_level:{},Signature Format:{}, Hash Algorithm OID:{}, Signature Packaging:{}, Type of Container:{}}", RequestContextHolder.currentRequestAttributes().getSessionId(), digestAlgorithm, document.getConformance_level(), document.getSignature_format(), hashAlgorithmOID, document.getSigned_envelope_property(), document.getContainer()); + + /*if(document.getConformance_level().equals("Ades-B-LTA") || document.getConformance_level().equals("Ades-B-LT")){ + System.out.println("here1"); + for (X509Certificate cert : certificateChain) { + certificateSource.addCertificate(new CertificateToken(cert)); + } + } + System.out.println(certificateSource.getCertificates().size());*/ + SignatureDocumentForm signatureDocumentForm = getSignatureForm(document, digestAlgorithm, encryptionAlgorithm, + certificate, date, certificateSource, certificateChain); + + byte[] dataToBeSigned = dssClient.getDigestOfDataToBeSigned(signatureDocumentForm); if (dataToBeSigned == null) continue; String dataToBeSignedStringEncoded = Base64.getEncoder().encodeToString(dataToBeSigned); String dataToBeSignedURLEncoded = URLEncoder.encode(dataToBeSignedStringEncoded, StandardCharsets.UTF_8); hashes.add(dataToBeSignedURLEncoded); } + fileLogger.info("Session_id:{},DataToBeSigned successfully created", RequestContextHolder.currentRequestAttributes().getSessionId()); return hashes; } - public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String authorizationBearerHeader, X509Certificate certificate, List certificateChain, List signAlgo, Date date, CommonTrustedCertificateSource certificateSource) throws Exception { + + public SignaturesSignDocResponse handleDocumentsSignDocRequest(SignaturesSignDocRequest signDocRequest, String authorizationBearerHeader, + X509Certificate certificate, List certificateChain, + List signAlgo, Date date, CommonTrustedCertificateSource certificateSource) throws Exception { List hashes = calculateHashValue(signDocRequest.getDocuments(), certificate, certificateChain, signDocRequest.getHashAlgorithmOID(), date, certificateSource); - SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest(signDocRequest.getCredentialID(),null, - hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, "S", - -1, null, signDocRequest.getClientData()); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Request to QTSP."); + SignaturesSignHashRequest signHashRequest = new SignaturesSignHashRequest(signDocRequest.getCredentialID(), null, + hashes, signDocRequest.getHashAlgorithmOID(), signAlgo.get(0), null, "S", + -1, null, signDocRequest.getClientData()); + fileLogger.info("Session_id:{},HTTP Request to QTSP.", RequestContextHolder.currentRequestAttributes().getSessionId()); SignaturesSignHashResponse signHashResponse = qtspClient.requestSignHash(signDocRequest.getRequest_uri(), signHashRequest, authorizationBearerHeader); List allSignaturesObjects = signHashResponse.getSignatures(); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",HTTP Response received."); + fileLogger.info("Session_id:{},HTTP Response received.", RequestContextHolder.currentRequestAttributes().getSessionId()); - if(signHashResponse.getSignatures().size() != signDocRequest.getDocuments().size()) return new SignaturesSignDocResponse(); + if (signHashResponse.getSignatures().size() != signDocRequest.getDocuments().size()) + return new SignaturesSignDocResponse(); + + DigestAlgorithm digestAlgorithm = DSSService.checkDigestAlgorithm(signDocRequest.getHashAlgorithmOID()); + EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); List DocumentWithSignature = new ArrayList<>(); - for(int i = 0; i < signDocRequest.getDocuments().size(); i++){ + for (int i = 0; i < signDocRequest.getDocuments().size(); i++) { DocumentsSignDocRequest document = signDocRequest.getDocuments().get(i); String signatureValue = signHashResponse.getSignatures().get(i); - DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - - SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); - DigestAlgorithm aux_digest_alg = DSSService.checkDigestAlgorithm(signDocRequest.getHashAlgorithmOID()); - EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); - SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); - ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); - SignatureForm signatureForm= DSSService.checkSignForm(document.getSignature_format()); - - SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); - signatureDocumentForm.setDocumentToSign(dssDocument); - signatureDocumentForm.setSignaturePackaging(aux_sign_pack); - signatureDocumentForm.setContainerType(aux_asic_ContainerType); - signatureDocumentForm.setSignatureLevel(aux_sign_level); - signatureDocumentForm.setDigestAlgorithm(aux_digest_alg); - signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertificate(certificate); - signatureDocumentForm.setDate(date); - signatureDocumentForm.setTrustedCertificates(certificateSource); - signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertChain(certificateChain); - signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); + SignatureDocumentForm signatureDocumentForm = getSignatureForm(document, digestAlgorithm, encryptionAlgorithm, + certificate, date, certificateSource, certificateChain); + signatureDocumentForm.setSignatureValue(Base64.getDecoder().decode(signatureValue)); DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Document successfully signed."); - - try { - if (document.getContainer().equals("ASiC-E")) { - if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); - docSigned.save("tests/exampleSigned.cse"); - File file = new File("tests/exampleSigned.cse"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); - } - } - else if (document.getContainer().equals("ASiC-S")) { - if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); - docSigned.save("tests/exampleSigned.scs"); - File file = new File("tests/exampleSigned.scs"); - byte[] pdfBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(pdfBytes)); - } - } - else if (document.getSignature_format().equals("J")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); - docSigned.save("tests/exampleSigned.json"); - File file = new File("tests/exampleSigned.json"); - byte[] jsonBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(jsonBytes)); - } - else if (document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); - docSigned.save("tests/exampleSigned.xml"); - File file = new File("tests/exampleSigned.xml"); - byte[] xmlBytes = Files.readAllBytes(file.toPath()); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(xmlBytes)); - } - else { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); - docSigned.save("tests/exampleSigned.pdf"); - DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); - } - } catch (Exception e) { - fileLogger.error("invalid request: "+ e.getMessage()); - throw e; - } + fileLogger.info("Session_id:{},Document successfully signed.", RequestContextHolder.currentRequestAttributes().getSessionId()); + String signedDocumentString = getSignedDocumentString(document, docSigned); + DocumentWithSignature.add(signedDocumentString); } ValidationInfoSignDocResponse validationInfo = null; @@ -176,78 +114,96 @@ else if (document.getSignature_format().equals("X")) { } public SignaturesSignDocResponse buildSignedDocument( - List documents, String hashAlgorithmOID, boolean returnValidationInfo, - X509Certificate certificate, List certificateChain, Date date, - CommonTrustedCertificateSource certificateSource, List signatureObjects) throws Exception { + List documents, String hashAlgorithmOID, boolean returnValidationInfo, + X509Certificate certificate, List certificateChain, Date date, + CommonTrustedCertificateSource certificateSource, List signatureObjects) throws Exception { + + if (signatureObjects.size() != documents.size()) return new SignaturesSignDocResponse(); - if(signatureObjects.size() != documents.size()) return new SignaturesSignDocResponse(); + DigestAlgorithm digestAlgorithm = DSSService.checkDigestAlgorithm(hashAlgorithmOID); + EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); List DocumentWithSignature = new ArrayList<>(); - for(int i = 0; i < documents.size(); i++){ + for (int i = 0; i < documents.size(); i++) { DocumentsSignDocRequest document = documents.get(i); String signatureValue = signatureObjects.get(i); - DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); - - SignatureLevel aux_sign_level = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); - DigestAlgorithm aux_digest_alg = DSSService.checkDigestAlgorithm(hashAlgorithmOID); - EncryptionAlgorithm encryptionAlgorithm = EncryptionAlgorithm.forName(certificate.getPublicKey().getAlgorithm()); - SignaturePackaging aux_sign_pack = DSSService.checkEnvProps(document.getSigned_envelope_property()); - ASiCContainerType aux_asic_ContainerType = DSSService.checkASiCContainerType(document.getContainer()); - SignatureForm signatureForm= DSSService.checkSignForm(document.getSignature_format()); - - SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); - signatureDocumentForm.setDocumentToSign(dssDocument); - signatureDocumentForm.setSignaturePackaging(aux_sign_pack); - signatureDocumentForm.setContainerType(aux_asic_ContainerType); - signatureDocumentForm.setSignatureLevel(aux_sign_level); - signatureDocumentForm.setDigestAlgorithm(aux_digest_alg); - signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertificate(certificate); - signatureDocumentForm.setDate(date); - signatureDocumentForm.setTrustedCertificates(certificateSource); - signatureDocumentForm.setSignatureForm(signatureForm); - signatureDocumentForm.setCertChain(certificateChain); - signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); + /*if(document.getConformance_level().equals("Ades-B-LTA") || document.getConformance_level().equals("Ades-B-LT")){ + System.out.println("here2"); + for (X509Certificate cert : certificateChain) { + certificateSource.addCertificate(new CertificateToken(cert)); + } + } + System.out.println(certificateSource.getCertificates().size());*/ + + SignatureDocumentForm signatureDocumentForm = getSignatureForm(document, digestAlgorithm, encryptionAlgorithm, + certificate, date, certificateSource, certificateChain); signatureDocumentForm.setSignatureValue(Base64.getDecoder().decode(signatureValue)); DSSDocument docSigned = dssClient.signDocument(signatureDocumentForm); - fileLogger.info("Session_id:"+ RequestContextHolder.currentRequestAttributes().getSessionId() +",Document successfully signed."); - - try { - if (document.getContainer().equals("ASiC-E")) { - if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); - } - } - else if (document.getContainer().equals("ASiC-S")) { - if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); - } - } - else if (document.getSignature_format().equals("J")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); - } - else if (document.getSignature_format().equals("X")) { - docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); - } - else { - docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); - } - DocumentWithSignature.add(Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes())); - } catch (Exception e) { - fileLogger.error("invalid request: "+ e.getMessage()); - throw e; - } + fileLogger.info("Session_id:{},Document successfully signed.", RequestContextHolder.currentRequestAttributes().getSessionId()); + String signedDocumentString = getSignedDocumentString(document, docSigned); + DocumentWithSignature.add(signedDocumentString); } ValidationInfoSignDocResponse validationInfo = null; - if (returnValidationInfo) { - validationInfo = new ValidationInfoSignDocResponse(); - } + if (returnValidationInfo) validationInfo = new ValidationInfoSignDocResponse(); return new SignaturesSignDocResponse(DocumentWithSignature, signatureObjects, null, validationInfo); } + private SignatureDocumentForm getSignatureForm(DocumentsSignDocRequest document, DigestAlgorithm digestAlgorithm, + EncryptionAlgorithm encryptionAlgorithm, X509Certificate certificate, + Date date, CommonTrustedCertificateSource certificateSource, + List certificateChain){ + + DSSDocument dssDocument = dssClient.loadDssDocument(document.getDocument()); + + SignatureLevel signatureLevel = DSSService.checkConformance_level(document.getConformance_level(), document.getSignature_format()); + SignaturePackaging signaturePackaging = DSSService.checkEnvProps(document.getSigned_envelope_property()); + ASiCContainerType asicContainerType = DSSService.checkASiCContainerType(document.getContainer()); + SignatureForm signatureForm = DSSService.checkSignForm(document.getSignature_format()); + + SignatureDocumentForm signatureDocumentForm = new SignatureDocumentForm(); + signatureDocumentForm.setDocumentToSign(dssDocument); + signatureDocumentForm.setSignaturePackaging(signaturePackaging); + signatureDocumentForm.setContainerType(asicContainerType); + signatureDocumentForm.setSignatureLevel(signatureLevel); + signatureDocumentForm.setDigestAlgorithm(digestAlgorithm); + signatureDocumentForm.setSignatureForm(signatureForm); + signatureDocumentForm.setCertificate(certificate); + signatureDocumentForm.setDate(date); + signatureDocumentForm.setTrustedCertificates(certificateSource); + signatureDocumentForm.setSignatureForm(signatureForm); + signatureDocumentForm.setCertChain(certificateChain); + signatureDocumentForm.setEncryptionAlgorithm(encryptionAlgorithm); + + return signatureDocumentForm; + } + + + private String getSignedDocumentString(DocumentsSignDocRequest document, DSSDocument docSigned) throws Exception{ + try { + if (document.getContainer().equals("ASiC-E")) { + if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-e+zip")); + } + } else if (document.getContainer().equals("ASiC-S")) { + if (document.getSignature_format().equals("C") || document.getSignature_format().equals("X")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/vnd.etsi.asic-s+zip")); + } + } else if (document.getSignature_format().equals("J")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/jose")); + } else if (document.getSignature_format().equals("X")) { + docSigned.setMimeType(MimeType.fromMimeTypeString("text/xml")); + } else { + docSigned.setMimeType(MimeType.fromMimeTypeString("application/pdf")); + } + } catch (Exception e) { + fileLogger.error("invalid request: {}", e.getMessage()); + throw e; + } + return Base64.getEncoder().encodeToString(docSigned.openStream().readAllBytes()); + } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index c941559..b563368 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -102,7 +102,6 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req "invalid_response: the documents to be signed should be sent in the request."); } - if(requestDTO.getEndEntityCertificate() == null){ logger.error("The certificate is missing from the request."); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the certificate parameter is missing."); @@ -110,7 +109,6 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req X509Certificate certificate = this.credentialsService.base64DecodeCertificate(requestDTO.getEndEntityCertificate()); logger.info("Loaded signing certificate."); - List certificateChain = new ArrayList<>(); for(String c: requestDTO.getCertificateChain()){ certificateChain.add(this.credentialsService.base64DecodeCertificate(c)); @@ -125,7 +123,7 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req Date date = new Date(); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); logger.info("Loaded certificate source."); List hashes = this.signatureService.calculateHashValue(documents, certificate, certificateChain, hashAlgorithmOID, date, certificateSource); @@ -170,7 +168,7 @@ public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocume } Date date = new Date(requestDTO.getDate()); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); logger.info("Loaded the certificate source"); List signatures = requestDTO.getSignatures(); From 90a984d7108dfde836360887c21e9ac57f826caf Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Tue, 12 Nov 2024 15:27:59 +0000 Subject: [PATCH 39/44] Update the document digest calculation --- .../ec/eudi/signer/r3/sca/model/CredentialsService.java | 7 ++----- .../ec/eudi/signer/r3/sca/model/SignatureService.java | 8 ++------ .../r3/sca/web/controller/SignaturesController.java | 4 ++-- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index 86fa3b7..dd88dc7 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -81,7 +81,7 @@ public List getSignAlgo() { public CertificateResponse getCertificateAndChainAndCommonSource(String qtspUrl, String credentialId, String authorizationBearerHeader){ CertificateResponse response = getCertificateAndCertificateChain(qtspUrl, credentialId, authorizationBearerHeader); logger.info("Retrieved the signing certificate and the certificate chain."); - CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(response.getCertificateChain()); + CommonTrustedCertificateSource commonTrustedCertificateSource = getCommonTrustedCertificateSource(); response.setTsaCommonSource(commonTrustedCertificateSource); logger.info("Retrieved the certificate source."); return response; @@ -123,12 +123,9 @@ public X509Certificate base64DecodeCertificate(String certificate) throws Except return (X509Certificate)certFactory.generateCertificate(inputStream); } - public CommonTrustedCertificateSource getCommonTrustedCertificateSource (List certificateChain){ + public CommonTrustedCertificateSource getCommonTrustedCertificateSource (){ CommonTrustedCertificateSource certificateSource = new CommonTrustedCertificateSource(); certificateSource.addCertificate(this.TSACertificateToken); - for(X509Certificate cert: certificateChain){ - certificateSource.addCertificate(new CertificateToken(cert)); - } return certificateSource; } } diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index 1b92b8c..f84b42a 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -47,13 +47,11 @@ public List calculateHashValue(List documents, for (DocumentsSignDocRequest document : documents) { fileLogger.info("Session_id:{},Payload Received:{ Document Hash:{}, conformance_level:{},Signature Format:{}, Hash Algorithm OID:{}, Signature Packaging:{}, Type of Container:{}}", RequestContextHolder.currentRequestAttributes().getSessionId(), digestAlgorithm, document.getConformance_level(), document.getSignature_format(), hashAlgorithmOID, document.getSigned_envelope_property(), document.getContainer()); - /*if(document.getConformance_level().equals("Ades-B-LTA") || document.getConformance_level().equals("Ades-B-LT")){ - System.out.println("here1"); + if(document.getConformance_level().equals("Ades-B-LTA") || document.getConformance_level().equals("Ades-B-LT")){ for (X509Certificate cert : certificateChain) { certificateSource.addCertificate(new CertificateToken(cert)); } } - System.out.println(certificateSource.getCertificates().size());*/ SignatureDocumentForm signatureDocumentForm = getSignatureForm(document, digestAlgorithm, encryptionAlgorithm, certificate, date, certificateSource, certificateChain); @@ -128,13 +126,11 @@ public SignaturesSignDocResponse buildSignedDocument( DocumentsSignDocRequest document = documents.get(i); String signatureValue = signatureObjects.get(i); - /*if(document.getConformance_level().equals("Ades-B-LTA") || document.getConformance_level().equals("Ades-B-LT")){ - System.out.println("here2"); + if(document.getConformance_level().equals("Ades-B-LTA") || document.getConformance_level().equals("Ades-B-LT")){ for (X509Certificate cert : certificateChain) { certificateSource.addCertificate(new CertificateToken(cert)); } } - System.out.println(certificateSource.getCertificates().size());*/ SignatureDocumentForm signatureDocumentForm = getSignatureForm(document, digestAlgorithm, encryptionAlgorithm, certificate, date, certificateSource, certificateChain); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index b563368..4345373 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -123,7 +123,7 @@ public CalculateHashResponse calculateHash(@RequestBody CalculateHashRequest req Date date = new Date(); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); logger.info("Loaded certificate source."); List hashes = this.signatureService.calculateHashValue(documents, certificate, certificateChain, hashAlgorithmOID, date, certificateSource); @@ -168,7 +168,7 @@ public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocume } Date date = new Date(requestDTO.getDate()); - CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(certificateChain); + CommonTrustedCertificateSource certificateSource = this.credentialsService.getCommonTrustedCertificateSource(); logger.info("Loaded the certificate source"); List signatures = requestDTO.getSignatures(); From f4c958cb5444d53fa0cdcbd2d2ba4bb496677e29 Mon Sep 17 00:00:00 2001 From: Mariana Rodrigues <62109899+MarianaFilipa@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:46:10 +0000 Subject: [PATCH 40/44] Update SignaturesController.java --- .../signer/r3/sca/web/controller/SignaturesController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 4345373..5cc36be 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -161,8 +161,7 @@ public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocume } logger.info("Loaded certificate chain."); - System.out.println(requestDTO.getDate()); - if(requestDTO.getDate() == -1){ + if(requestDTO.getDate() == 0){ logger.error("The date parameter is missing."); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_response: the date parameter is missing."); } @@ -184,4 +183,4 @@ public SignaturesSignDocResponse obtainSignedDocuments(@RequestBody SignedDocume return this.signatureService.buildSignedDocument(documents, hashAlgorithmOID, returnValidationInfo, signingCertificate, certificateChain, date, certificateSource, signatures); } -} \ No newline at end of file +} From 59c13ec7f02c8eb36e0650d641e165808b425800 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Wed, 13 Nov 2024 14:05:28 +0000 Subject: [PATCH 41/44] Inital update: add license header, update readme --- .mvn/wrapper/maven-wrapper.properties | 18 -- README.md | 150 ++++++++++- deploy_sca.sh | 2 +- licenses.md | 138 ++++++++++ logback.xml | 15 ++ mvnw | 250 ------------------ mvnw.cmd | 146 ---------- pom.xml | 34 ++- .../ec/eudi/signer/r3/sca/ScaApplication.java | 16 ++ .../r3/sca/config/OAuthClientConfig.java | 16 ++ .../signer/r3/sca/config/SwaggerConfig.java | 33 +++ .../sca/config/TrustedCertificateConfig.java | 16 ++ .../r3/sca/model/CredentialsService.java | 16 ++ .../eudi/signer/r3/sca/model/DSSService.java | 27 +- .../signer/r3/sca/model/OAuth2Service.java | 16 ++ .../eudi/signer/r3/sca/model/QTSPClient.java | 16 ++ .../r3/sca/model/SignatureDocumentForm.java | 16 ++ .../signer/r3/sca/model/SignatureService.java | 16 ++ .../web/controller/CallbackController.java | 16 ++ .../sca/web/controller/OAuth2Controller.java | 16 ++ .../web/controller/SignaturesController.java | 16 ++ .../r3/sca/web/dto/SignedDocumentRequest.java | 16 ++ .../calculateHash/CalculateHashRequest.java | 16 ++ .../calculateHash/CalculateHashResponse.java | 16 ++ .../credentialsInfo/CredentialsInfoAuth.java | 16 ++ .../credentialsInfo/CredentialsInfoCert.java | 16 ++ .../credentialsInfo/CredentialsInfoKey.java | 16 ++ .../CredentialsInfoRequest.java | 16 ++ .../CredentialsInfoResponse.java | 16 ++ .../CredentialAuthorizationRequest.java | 16 ++ .../CredentialAuthorizationResponse.java | 16 ++ .../dto/oauth2/OAuth2AuthorizeRequest.java | 16 ++ .../dto/signDoc/AttributeSignDocRequest.java | 16 ++ .../dto/signDoc/DocumentsSignDocRequest.java | 16 ++ .../dto/signDoc/SignaturesSignDocRequest.java | 16 ++ .../signDoc/SignaturesSignDocResponse.java | 16 ++ .../ValidationInfoSignDocResponse.java | 16 ++ .../signHash/SignaturesSignHashRequest.java | 16 ++ .../signHash/SignaturesSignHashResponse.java | 16 ++ src/main/resources/application.yml | 14 + .../templates/successful_authentication.html | 14 + .../signer/r3/sca/ScaApplicationTests.java | 16 ++ 42 files changed, 897 insertions(+), 424 deletions(-) delete mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 licenses.md delete mode 100644 mvnw delete mode 100644 mvnw.cmd create mode 100644 src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/SwaggerConfig.java diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index aeccdfd..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.1 -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip diff --git a/README.md b/README.md index 630c61e..93a8e6e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,149 @@ -# eudi-srv-web-walletdriven-signer-external-sca-java -rQES R3 external SCA +# EUDI Wallet-driven external SCA + +## Table of contents + +- [EUDI Wallet-driven external SCA](#eudi-wallet-driven-external-sca) + - [Table of contents](#table-of-contents) + - [Overview](#overview) + - [Disclaimer](#disclaimer) + - [Sequence Diagrams](#sequence-diagrams) + - [Credential Authorization](#credential-authorization) + - [Endpoints](#endpoints) + - [Deployment](#deployment) + - [Requirements](#requirements) + - [How to contribute](#how-to-contribute) + - [License](#license) + - [Third-party component licenses](#third-party-component-licenses) + - [License details](#license-details) + + +## Overview + + + +## Disclaimer + +The released software is an initial development release version: + +- The initial development release is an early endeavor reflecting the efforts of a short timeboxed + period, and by no means can be considered as the final product. +- The initial development release may be changed substantially over time, might introduce new + features but also may change or remove existing ones, potentially breaking compatibility with your + existing code. +- The initial development release is limited in functional scope. +- The initial development release may contain errors or design flaws and other problems that could + cause system or other failures and data loss. +- The initial development release has reduced security, privacy, availability, and reliability + standards relative to future releases. This could make the software slower, less reliable, or more + vulnerable to attacks than mature software. +- The initial development release is not yet comprehensively documented. +- Users of the software must perform sufficient engineering and additional testing in order to + properly evaluate their application and determine whether any of the open-sourced components is + suitable for use in that application. +- We strongly recommend not putting this version of the software into production use. +- Only the latest version of the software will be supported + +## Sequence Diagrams + +### Credential Authorization + +```mermaid +sequenceDiagram + title Document Signing + + actor U as UserAgent + participant EW as EUDI Wallet + participant SCA as Signature Creation Application + participant AS as Authorization Server (QTSP) + participant RS as Resource Server (QTSP) + participant OIDV as OID4VP Verifier + + U->>+EW: Chooses credential to use + U->>+EW: Request document signing + EW->>+RS: /csc/v2/credentials/info + RS->>-EW: credentials info + + EW->>+SCA: "calculate hash" (certificates, document to sign) + SCA->>-EW: hash value + + EW->>+AS: /oauth2/authorize?...&redirect_uri=wallet://login/oauth2/code&... + AS->>+OIDV: Authorization Request (Post dev.verifier-backend.eudiw.dev/ui/presentations?redirect_uri={oid4vp_redirect_uri}) + OIDV->>-AS: Authorization Request returns + AS->>+AS: Generate link to Wallet + AS->>-EW: Redirect to link in the Wallet + EW->>-U: Request Authorization + + U->>+EW: Authorize (Shares PID) + EW->>+AS: Redirect to oid4vp_redirect_uri + AS->>+OIDV: Request VP Token + OIDV->>-AS: Get and validate VP Token + AS->>-EW: Returns session token (successful authentication) & Redirects to /oauth2/authorize + EW->>+AS: GET /oauth2/authorize?...&redirect_uri=wallet://oauth2/callback&... [Cookie JSession] + AS->>-EW: Redirect to wallet://login/oauth2/code?code={code} + EW->>+EW: Get wallet://login/oauth2/code.... + EW->>+AS: /oauth2/token?code={code} + AS->>-EW: access token authorizing credentials use (SAD/R) + + EW->>+RS: /signatures/signHash + RS->>-EW: signature + + EW->>+SCA: "obtain signed document" (certificates & document & signature value) + SCA->>-EW: signed document +``` + +## Endpoints + +### Calculate Hash Endpoint + +### Obtain Signed Document Endpoint ## Deployment: -1. Add the TSA_CC.pem file in the resources folder, and define the parameter TrustedCertificates in the config.properties of the same folder with the path of the TSA_CC.pem file. -2. Run the deployment script \ No newline at end of file +### Requirements +* Java version 17 +* Apache Maven 3.6.3 + +### Signature Creation Application + +1. **Create the application-auth.yml file** + +2. **Add the Timestamp Authority Certificate** + + Add the TSA_CC.pem file in the resources folder, and define the parameter TrustedCertificates in the config.properties of the same folder with the path of the TSA_CC.pem file. + +3. **Update the Timestamp Authority Url** + as + +4. **Run the Resource Server** + + After configuring the previously mentioned settings and run the script + ``` + ./deploy_sca.sh + ``` + +## How to contribute + +We welcome contributions to this project. To ensure that the process is smooth for everyone +involved, follow the guidelines found in [CONTRIBUTING.md](CONTRIBUTING.md). + +## License + +### Third-party component licenses + +See [licenses.md](licenses.md) for details. + +### License details + +Copyright (c) 2023 European Commission + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/deploy_sca.sh b/deploy_sca.sh index 88617f6..2ed09f8 100644 --- a/deploy_sca.sh +++ b/deploy_sca.sh @@ -1 +1 @@ -mvn clean install; nohup java -jar target/sca-0.0.1-SNAPSHOT.jar & \ No newline at end of file +mvn clean install; java -jar target/sca-0.0.1-SNAPSHOT.jar \ No newline at end of file diff --git a/licenses.md b/licenses.md new file mode 100644 index 0000000..4a75712 --- /dev/null +++ b/licenses.md @@ -0,0 +1,138 @@ +# TrustProvider Signer + +## Dependency License Report + +_2024-11-13 12:11:20 CET_ + +## Apache License, Version 2.0 + +**1** **Group:** `org.springframework.boot` **Name:** `spring-boot-starter` **Version:** `3.3.0` + +> - **POM Project URL**: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**2** **Group:** `org.springframework.boot` **Name:** `spring-boot-starter-web` **Version:** `3.3.0` + +> - **POM Project URL**: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**3** **Group:** `org.springframework.boot` **Name:** `spring-boot-starter-test` **Version:** `3.3.0` + +> - **POM Project URL**: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**4** **Group:** `org.springframework.boot` **Name:** `spring-boot-maven-plugin` **Version:** `3.3.0` + +> - **POM Project URL**: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**5** **Group:** `org.apache.maven.plugins` **Name:** `maven-surefire-plugin` **Version:** `3.0.0-M2` + +> - **POM Project URL**: [https://maven.apache.org/surefire/maven-surefire-plugin/](https://maven.apache.org/surefire/maven-surefire-plugin/) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**6** **Group:** `org.apache.httpcomponents` **Name:** `httpclient` **Version:** `4.5.13` + +> - **POM Project URL**: [http://hc.apache.org/httpcomponents-client](http://hc.apache.org/httpcomponents-client) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**7** **Group:** `org.springframework.boot` **Name:** `spring-boot-starter-thymeleaf` **Version:** `3.3.0` + +> - **POM Project URL**: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**8** **Group:** `org.springframework.boot` **Name:** `spring-boot-starter-webflux` **Version:** `3.3.0` + +> - **POM Project URL**: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**9** **Group:** `jakarta.validation` **Name:** `jakarta.validation-api` **Version:** `3.0.2` + +> - **POM Project URL**: [https://beanvalidation.org](https://beanvalidation.org) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**10** **Group:** `org.hibernate.validator` **Name:** `hibernate-validator` **Version:** `8.0.1.Final` + +> - **POM Project URL**: [https://hibernate.org/validator/](https://hibernate.org/validator/) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +**11** **Group:** `org.springdoc` **Name:** `springdoc-openapi-starter-webmvc-ui` **Version:** `2.7.0-RC1` + +> - **POM Project URL**: [https://springdoc.org/](https://springdoc.org/) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +## LGPL 2.1 License + +**12** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-pades` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**13** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-pades-pdfbox` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [ https://www.gnu.org/licenses/lgpl-2.1.html]( https://www.gnu.org/licenses/lgpl-2.1.html) + +**14** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-utils-apache-commons` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**15** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `validation-policy` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**16** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-cades` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**17** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-service` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**18** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-xades` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**19** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-jades` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**20** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-asic-common` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**21** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-asic-cades` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**22** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-asic-xades` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +**23** **Group:** `eu.europa.ec.joinup.sd-dss` **Name:** `dss-crl-parser-x509crl` **Version:** `6.0` + +> - **POM Project URL**: [https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS](https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS) +> - **POM License**: LGPL 2.1 - [https://www.gnu.org/licenses/lgpl-2.1.html](https://www.gnu.org/licenses/lgpl-2.1.html) + +## Org.Json Public Domain License + +**24** **Group:** `org.json` **Name:** `json` **Version:** `20231013` + +> - **POM Project URL**: [https://github.com/douglascrockford/JSON-java](https://github.com/douglascrockford/JSON-java) +> - **POM License**: Public Domain - [https://github.com/stleary/JSON-java/blob/master/LICENSE](https://github.com/stleary/JSON-java/blob/master/LICENSE) + +## Base Project + +**25** **Group:** `com.eidtrust` **Name:** `signer` **Version:** `0.1.0` + +> - **POM Project URL**: [https://github.com/devisefutures/com.eidtrust.signer](https://github.com/devisefutures/com.eidtrust.signer) +> - **POM License**: Unlicense License - [https://github.com/devisefutures/com.eidtrust.signer/blob/main/LICENSE](https://github.com/devisefutures/com.eidtrust.signer/blob/main/LICENSE) \ No newline at end of file diff --git a/logback.xml b/logback.xml index 5009ece..8133e0a 100644 --- a/logback.xml +++ b/logback.xml @@ -1,4 +1,19 @@ + diff --git a/mvnw b/mvnw deleted file mode 100644 index ba9212a..0000000 --- a/mvnw +++ /dev/null @@ -1,250 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.1 -# -# Optional ENV vars -# ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output -# ---------------------------------------------------------------------------- - -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x - -# OS specific support. -native_path() { printf %s\\n "$1"; } -case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; -esac - -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - -die() { - printf %s\\n "$1" >&2 - exit 1 -} - -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl="${value-}" ;; - distributionSha256Sum) distributionSha256Sum="${value-}" ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" - -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" - -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" -} - -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" -fi - -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac - -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT -else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v - -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac - -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" -fi - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi -fi - -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" - -clean || : -exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd deleted file mode 100644 index 406932d..0000000 --- a/mvnw.cmd +++ /dev/null @@ -1,146 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.1 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index 2d717f5..c296979 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,19 @@ + 4.0.0 @@ -19,6 +34,7 @@ org.springframework.boot spring-boot-starter-web + 3.3.0 @@ -27,19 +43,23 @@ 3.3.0 + org.springframework.boot spring-boot-starter-webflux + 3.3.0 org.springframework.boot spring-boot-starter + 3.3.0 org.springframework.boot spring-boot-starter-test + 3.3.0 test @@ -49,11 +69,11 @@ 20231013
- + org.apache.httpcomponents httpclient @@ -69,7 +89,7 @@ org.hibernate.validator hibernate-validator - 6.2.0.Final + 8.0.1.Final @@ -133,6 +153,13 @@ dss-crl-parser-x509crl 6.0 + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + @@ -140,6 +167,7 @@ org.springframework.boot spring-boot-maven-plugin + 3.3.0 org.apache.maven.plugins diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java index 57e2bf7..baade29 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplication.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca; import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java index 7e5f466..3fe5cd7 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/OAuthClientConfig.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/SwaggerConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/SwaggerConfig.java new file mode 100644 index 0000000..04c857c --- /dev/null +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/SwaggerConfig.java @@ -0,0 +1,33 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package eu.europa.ec.eudi.signer.r3.sca.config; + +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + GroupedOpenApi publicApi(){ + return GroupedOpenApi.builder() + .group("public-apis") + .pathsToMatch("/**") + .build(); + } +} diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java index af8bb17..79e5e77 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/config/TrustedCertificateConfig.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java index dd88dc7..074e3bb 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/CredentialsService.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.model; import eu.europa.ec.eudi.signer.r3.sca.config.TrustedCertificateConfig; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java index 469a0cf..1f77d35 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/DSSService.java @@ -1,5 +1,22 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.model; +import eu.europa.ec.eudi.signer.r3.sca.config.TrustedCertificateConfig; import eu.europa.esig.dss.AbstractSignatureParameters; import eu.europa.esig.dss.alert.ExceptionOnStatusAlert; import eu.europa.esig.dss.alert.LogOnStatusAlert; @@ -54,12 +71,18 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.slf4j.event.Level; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; @Service public class DSSService { private final static Logger logger = LogManager.getLogger(DSSService.class); + private final TrustedCertificateConfig trustedCertificateConfig; + + public DSSService(@Autowired TrustedCertificateConfig trustedCertificateConfig){ + this.trustedCertificateConfig = trustedCertificateConfig; + } public static SignatureLevel checkConformance_level(String conformance_level, String string) { String enumValue = mapToEnumValue(conformance_level, string); @@ -169,7 +192,7 @@ public DSSDocument loadDssDocument(String document) { } @SuppressWarnings({ "rawtypes", "unchecked" }) - /** + /* * Function that returns the digest of the data to be signed from the document and parameters received */ public byte[] getDigestOfDataToBeSigned(SignatureDocumentForm form) { @@ -302,7 +325,7 @@ private DocumentSignatureService getSignatureService(ASiCContainerType container }; } - String tspServer = "http://ts.cartaodecidadao.pt/tsa/server"; + String tspServer = this.trustedCertificateConfig.getTimeStampAuthority(); OnlineTSPSource onlineTSPSource = new OnlineTSPSource(tspServer); onlineTSPSource.setDataLoader(new TimestampDataLoader()); service.setTspSource(onlineTSPSource); diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java index d7b81b9..7680e72 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/OAuth2Service.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.model; import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java index 2f6f896..601eae4 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/QTSPClient.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.model; import eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2.CredentialAuthorizationResponse; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureDocumentForm.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureDocumentForm.java index 5c3ad82..22f8524 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureDocumentForm.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureDocumentForm.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.model; import java.security.cert.X509Certificate; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java index f84b42a..02b6381 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/model/SignatureService.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.model; import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java index b6a80ab..728b213 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/CallbackController.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.controller; import eu.europa.ec.eudi.signer.r3.sca.config.OAuthClientConfig; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java index ed05b8e..4fdb338 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/OAuth2Controller.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.controller; import eu.europa.ec.eudi.signer.r3.sca.model.OAuth2Service; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java index 5cc36be..8613fc8 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/controller/SignaturesController.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.controller; import eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash.CalculateHashRequest; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java index af14696..251857a 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/SignedDocumentRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto; import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java index f2c2bf9..de0482c 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash; import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java index eff647c..744ea50 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/calculateHash/CalculateHashResponse.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.calculateHash; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java index 915507c..fe43859 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoAuth.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java index 2243fb1..b035dd0 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoCert.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java index 08a1673..fc65cef 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoKey.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java index 5a386a5..27154f1 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; public class CredentialsInfoRequest { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java index 2f0d22c..ca589d2 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/credentialsInfo/CredentialsInfoResponse.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.credentialsInfo; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java index 9ee9fff..345c056 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2; import eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc.DocumentsSignDocRequest; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java index 62533cb..96e1cbe 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/CredentialAuthorizationResponse.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2; public class CredentialAuthorizationResponse { diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java index 715c30b..2e2b4f3 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/oauth2/OAuth2AuthorizeRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.oauth2; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java index 201bb48..be847f3 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/AttributeSignDocRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java index c020299..cb7b7d6 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/DocumentsSignDocRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java index 00f527d..433a38e 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java index bda5ced..c77fb72 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/SignaturesSignDocResponse.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java index bb8469b..ab87ff4 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signDoc/ValidationInfoSignDocResponse.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signDoc; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java index c519ac2..1a9c255 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashRequest.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash; import java.util.List; diff --git a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java index 7312b66..c3355d3 100644 --- a/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java +++ b/src/main/java/eu/europa/ec/eudi/signer/r3/sca/web/dto/signHash/SignaturesSignHashResponse.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca.web.dto.signHash; import java.util.List; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5ac033a..35ae95a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,17 @@ +# Copyright 2024 European Commission +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + spring: application: name: sca diff --git a/src/main/resources/templates/successful_authentication.html b/src/main/resources/templates/successful_authentication.html index 33691d3..0047b62 100644 --- a/src/main/resources/templates/successful_authentication.html +++ b/src/main/resources/templates/successful_authentication.html @@ -1,3 +1,17 @@ + + diff --git a/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java b/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java index 7cd5638..501855b 100644 --- a/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java +++ b/src/test/java/eu/europa/ec/eudi/signer/r3/sca/ScaApplicationTests.java @@ -1,3 +1,19 @@ +/* + Copyright 2024 European Commission + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package eu.europa.ec.eudi.signer.r3.sca; import org.junit.jupiter.api.Test; From 3ff825df7dd933b551fe64a5319d6116cd9646ca Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Wed, 13 Nov 2024 22:04:47 +0000 Subject: [PATCH 42/44] Update Readme.md --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 93a8e6e..8376e2a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # EUDI Wallet-driven external SCA +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) + +:heavy_exclamation_mark: **Important!** Before you proceed, please read +the [EUDI Wallet Reference Implementation project description](https://github.com/eu-digital-identity-wallet/.github/blob/main/profile/reference-implementation.md) + ## Table of contents - [EUDI Wallet-driven external SCA](#eudi-wallet-driven-external-sca) @@ -19,7 +24,10 @@ ## Overview +This is a REST API server that implements the wallet-driven SCA for the remote Qualified Electronic Signature component of the EUDI Wallet. +The SCA provides endpoints that allow to calculate the hash value of a document and obtained the signed document given the signature value. +Currently, the server is running at "https://walletcentric.signer.eudiw.dev", but you can [deploy](#deployment) it in your environment. ## Disclaimer @@ -95,9 +103,40 @@ sequenceDiagram ### Calculate Hash Endpoint +* Method: POST +* URL: http://localhost:8086/signatures/calculate_hash + +This endpoint calculates the digest value of a given document. +The payload of this request is a JSON object with the following attributes: +* **documents**: a JSON array consisting of JSON objects, where each object contains a base64-encoded document content to be signed and additional request parameters. +* **endEntityCertificate**: the base64-encoded certificate of the user. +* **certificateChain**: a list of base64-encoded certificates representing the certificate chain to be used when calculating the digest value, excluding the end-entity certificate. +* **hashAlgorithmOID**: the OID of the hash algorithm used to generate the digest value. + +The endpoint should return a JSON object with the following attributes: +* **hashes**: a list of strings containing one or more BASE64 URL-encoded hash values to be signed. +* **signature_date**: the date of the signature request, as a long integer, which will be used later when obtaining the signed document. + ### Obtain Signed Document Endpoint -## Deployment: +* Method: POST +* URL: http://localhost:8086/signatures/obtain_signed_doc + +This endpoint retrieves the signed document, given the document to be signed and the signature value. +The payload of this request is a JSON object with the following attributes: +* **documents**: a JSON array consisting of JSON objects, where each object contains a base64-encoded document content to be signed and additional request parameters. +* **endEntityCertificate**: the base64-encoded certificate of the user. +* **certificateChain**: a list of base64-encoded certificates representing the certificate chain to be used when calculating the digest value, excluding the end-entity certificate. +* **hashAlgorithmOID**: the OID of the hash algorithm used to generate the digest value. +* **returnValidationInfo**: a boolean indicating whether the server should return validation information (OCSP, CRL, or certificates). +* **date**: the value of 'signature_date' received in the response of 'calculate_hash' endpoint. +* **signatures**: the base64-encoded signature value of the document's digest. + +The endpoint returns a JSON object with the following attributes: +* **documentWithSignature**: a base64-encoded signed document . +* **signatureObject**: the signature string received in the request. + +## Deployment ### Requirements * Java version 17 @@ -107,16 +146,32 @@ sequenceDiagram 1. **Create the application-auth.yml file** -2. **Add the Timestamp Authority Certificate** - - Add the TSA_CC.pem file in the resources folder, and define the parameter TrustedCertificates in the config.properties of the same folder with the path of the TSA_CC.pem file. - -3. **Update the Timestamp Authority Url** - as - -4. **Run the Resource Server** + ``` + oauth-client: + client-id: "{client-id}" + client-secret: "{client-secret}" + client-authentication-methods: + - "client_secret_basic" + redirect-uri: "{redirect-uri}" + scope: "credential" + default-authorization-server-url: "{url of the authorization server}" + app-redirect-uri: "{url of the wallet callback endpoint}" + ``` + +2. **Add the Timestamp Authority Information** + + For certain conformance levels, access to a Timestamp Authority is required. + The Timestamp Authority to be used can be specified in the **application.yml** file located in the folder **src/main/resources/application.yml**. + + ``` + trusted-certificate: + filename: # the path to the Timestamp Authority Certificate chosen + time-stamp-authority: # the url to the Timestamp Authority + ``` + +3. **Run the Resource Server** - After configuring the previously mentioned settings and run the script + After configuring the previously mentioned settings and run the script: ``` ./deploy_sca.sh ``` From fbf9c742bed52be7c09d9489f95315a43d342dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miranda?= Date: Mon, 18 Nov 2024 11:31:09 +0000 Subject: [PATCH 43/44] review Readme --- CODE_OF_CONDUCT.md | 138 ++++++++++++++++++++++++++++ CONTRIBUTING.md | 114 +++++++++++++++++++++++ NOTICE.txt | 221 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 31 ++++--- SECURITY.md | 62 +++++++++++++ changelog.md | 15 +++ 6 files changed, 567 insertions(+), 14 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 NOTICE.txt create mode 100644 SECURITY.md create mode 100644 changelog.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b8e7f88 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,138 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported at [cnect-eudiw-development@ec.europa.eu](mailto:cnect-eudiw-development@ec.europa.eu). +to the community leaders responsible for enforcement. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org + +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html + +[Mozilla CoC]: https://github.com/mozilla/diversity + +[FAQ]: https://www.contributor-covenant.org/faq + +[translations]: https://www.contributor-covenant.org/translations + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0833b66 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,114 @@ +# Contribution Guidelines + +We welcome contributions to this project. To ensure that the process is smooth for everyone +involved, please follow the guidelines below. + +If you encounter a bug in the project, check if the bug has already been reported. If the +bug has not been reported, you can open an issue to report the bug. + +Before making any changes, it's a good practice to create an issue to describe the changes +you plan to make and the reasoning behind them. + +You can +read [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github) +for more information. + +## GitHub Flow + +We use the [GitHub Flow](https://guides.github.com/introduction/flow/) workflow for making +contributions to this project. This means that: + +1. Fork the repository and create a new branch from `main` for your changes. + + ```bash + git checkout main + git pull + git checkout -b my-branch + ``` + +2. Make changes to the code, documentation, or any other relevant files. +3. Commit your changes and push them to your forked repository. + + ```bash + git add . + git commit -m "Add a new feature" + git push origin my-branch + ``` + +4. Create a pull request from your branch to the `main` branch of this repository. + +## Pull Request Checklist + +* Branch from the main branch and, if needed, rebase to the current main branch before submitting + your pull request. If it doesn't merge cleanly with main you may be asked to rebase your changes. + +* Commits should be as small as possible while ensuring that each commit is correct independently ( + i.e., each commit should compile and pass tests). + +* Test your changes as thoroughly as possible before you commit them. Preferably, automate your test + by unit/integration tests. If tested manually, provide information about the test scope in the PR + description (e.g. “Test passed: Upgrade version from 0.42 to 0.42.23.”). + +* Create _Work In Progress [WIP]_ pull requests only if you need clarification or an explicit review + before you can continue your work item. + +* If your patch is not getting reviewed or you need a specific person to review it, you can @-reply + a reviewer asking for a review in the pull request or a comment. + +* Post review: + * If a review requires you to change your commit(s), please test the changes again. + * Amend the affected commit(s) and force push onto your branch. + * Set respective comments in your GitHub review to resolved. + * Create a general PR comment to notify the reviewers that your amendments are ready for another + round of review. + +## Branch Name Rules + +Please name your branch using the following convention: + +```text +/ +``` + +- `type` should be one of the following: + - `feat` for a new feature, + - `fix` for a bug fix, + - `docs` for documentation changes, + - `style` for changes that do not affect the code, such as formatting or whitespace, + - `refactor` for code refactoring, + - `test` for adding or updating tests, or + - `chore` for any other miscellaneous tasks. +- `short-description` should be a short, descriptive name of the changes you are making. + +For example: + +```text +feat/add-new-button +fix/typo-in-readme +docs/update-contributing-guide +style/format-code +refactor/extract-method +test/add-unit-tests +chore/update-dependencies +``` + +## Issues and Planning + +* We use GitHub issues to track bugs and enhancement requests. + +* Please provide as much context as possible when you open an issue. The information you provide + must be comprehensive enough to reproduce that issue for the assignee. Therefore, contributors may + use but aren't restricted to the issue template provided by the project maintainers. + +* When creating an issue, try using one of our issue templates which already contain some guidelines + on which content is expected to process the issue most efficiently. If no template applies, you + can of course also create an issue from scratch. + +* Please apply one or more applicable [labels](/../../labels) to your issue so that all community + members are able to cluster the issues better. + +## Code of Conduct + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). +By participating in this project, you agree to abide by its terms. + diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..a5dc907 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,221 @@ +EU Digital Identity Wallet +Copyright (c) 2023 European Commission + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +(1) google/identity-credential (available at https://github.com/google/identity-credential) +Certain file packages were originally created under the Apache License, Version 2.0. These files have been copied into this project and modified. All modifications are Copyright 2023 European Commission, and are licensed under the Apache License, Version 2.0. + +Original License: + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md index 8376e2a..8eb3389 100644 --- a/README.md +++ b/README.md @@ -8,24 +8,27 @@ the [EUDI Wallet Reference Implementation project description](https://github.co ## Table of contents - [EUDI Wallet-driven external SCA](#eudi-wallet-driven-external-sca) - - [Table of contents](#table-of-contents) - - [Overview](#overview) - - [Disclaimer](#disclaimer) - - [Sequence Diagrams](#sequence-diagrams) - - [Credential Authorization](#credential-authorization) - - [Endpoints](#endpoints) - - [Deployment](#deployment) - - [Requirements](#requirements) - - [How to contribute](#how-to-contribute) - - [License](#license) - - [Third-party component licenses](#third-party-component-licenses) - - [License details](#license-details) + - [Table of contents](#table-of-contents) + - [Overview](#overview) + - [Disclaimer](#disclaimer) + - [Sequence Diagrams](#sequence-diagrams) + - [Credential Authorization](#credential-authorization) + - [Endpoints](#endpoints) + - [Calculate Hash Endpoint](#calculate-hash-endpoint) + - [Obtain Signed Document Endpoint](#obtain-signed-document-endpoint) + - [Deployment](#deployment) + - [Requirements](#requirements) + - [Signature Creation Application](#signature-creation-application) + - [How to contribute](#how-to-contribute) + - [License](#license) + - [Third-party component licenses](#third-party-component-licenses) + - [License details](#license-details) ## Overview This is a REST API server that implements the wallet-driven SCA for the remote Qualified Electronic Signature component of the EUDI Wallet. -The SCA provides endpoints that allow to calculate the hash value of a document and obtained the signed document given the signature value. +The SCA provides endpoints that allow to calculate the hash value of a document and obtain the signed document given the signature value. Currently, the server is running at "https://walletcentric.signer.eudiw.dev", but you can [deploy](#deployment) it in your environment. @@ -189,7 +192,7 @@ See [licenses.md](licenses.md) for details. ### License details -Copyright (c) 2023 European Commission +Copyright (c) 2024 European Commission Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d4ec5f6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,62 @@ +# EU Digital Identity Wallet Vulnerability Disclosure Policy (VDP) + +At the European Commission, we treat the security of our Communication and Information Systems as a +top priority, in line with Commission Decision EC 2017/46. However, vulnerabilities can never be +completely eliminated, despite all efforts. If exploited, such vulnerabilities can harm the +confidentiality, integrity or availability of the Commission's systems and of the information +processed therein. To identify and remediate vulnerabilities as soon as possible, we value the input +of external entities acting in good faith, and we encourage responsible vulnerability research and +disclosure. This document sets out our definition of good faith in the context of finding and +reporting vulnerabilities, as well as what you can expect from us in return. + +## Scope + +- Architecture and Reference Framework +- Source code in [eu-digital-identity-wallet](https://github.com/eu-digital-identity-wallet) public + repositories + +## If you have identified a vulnerability, please do the following: + +* E-mail your findings to EC-VULNERABILITY-DISCLOSURE@ec.europa.eu, specifying whether or not you + agree to your name or pseudonym being made publicly available as the discoverer of the problem. +* Encrypt your findings using + our [PGP key](https://ec.europa.eu/assets/digit/pgpkey/ec-vulnerability-disclosure-pgp.txt) + to prevent this critical information from falling into the wrong hands. +* Provide us sufficient information to reproduce the problem so that we can resolve it as quickly as + possible. Usually, the IP address or the URL of the affected system and a description of the + vulnerability will be sufficient, but complex vulnerabilities may require further explanation in + terms of technical information or potential proof-of-concept code. +* Provide your report in English, preferably, or in any other official language of the European + Union. +* Inform us if you agree to make your name/pseudonym publicly available as the discoverer of the + vulnerability. + +## Please do not do the following + +* Do not take advantage of the vulnerability or problem you have discovered, for example by + downloading more data than necessary to demonstrate the vulnerability, deleting, or modifying + other people’s data. +* Do not reveal any data downloaded during the discovery to any other parties. +* Do not reveal the problem to others until it has been resolved. +* Do not perform the following actions: + * Placing malware (virus, worm, Trojan horse, etc.) within the system. + * Reading, copying, modifying or deleting data from the system. + * Making changes to the system. + * Repeatedly accessing the system or sharing access with others. + * Using any access obtained to attempt to access other systems. + * Changing access rights for any other users. + * Using automated scanning tools. + * Using the so-called "brute force" of access to the system. + * Using denial-of-service or social engineering (phishing, vishing, spam etc.). +* Do not use attacks on physical security. + +## What we promise: + +* We will respond to your report within three business days with our evaluation of the report. +* We will handle your report with strict confidentiality. +* Where possible, we will inform you when the vulnerability has been remedied. +* We will process the personal data that you provide (such as your e-mail address and name) in + accordance with the applicable data protection legislation and will not pass on your personal + details to third parties without your permission. +* In the public information concerning the problem reported, we will publish your name as the + discoverer of the problem if you have agreed to this in your initial e-mail diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..22a1450 --- /dev/null +++ b/changelog.md @@ -0,0 +1,15 @@ +# Changelog + +## [0.x.0] + +_00 Jun 0000_ + +### Added: +- + +### Changed + +- + +### Fixed +- From 02844897b9c73427fee75fd5b15cc889ca28f1b6 Mon Sep 17 00:00:00 2001 From: MarianaFilipa Date: Mon, 18 Nov 2024 12:33:00 +0000 Subject: [PATCH 44/44] Small change to README.md and add specification doc. --- README.md | 6 + ...2fd36d1b4044a7c6eaea6cab21b631ce99aea3.png | Bin 0 -> 197089 bytes ...f4fbab132f2bdb492ff21b7a9caa380ab012a2.png | Bin 0 -> 56586 bytes ...c52c3070305a5f81354001257578dd8d60b6d7.png | Bin 0 -> 61663 bytes ...bcc96a4a20e0ee2b01830a6850d76f6f5ef76e.png | Bin 0 -> 68954 bytes docs/media/image3.png | Bin 0 -> 51195 bytes docs/media/image4.png | Bin 0 -> 14515 bytes docs/media/image7.png | Bin 0 -> 88111 bytes docs/rqes-walledriven.md | 1315 +++++++++++++++++ 9 files changed, 1321 insertions(+) create mode 100644 docs/media/b12fd36d1b4044a7c6eaea6cab21b631ce99aea3.png create mode 100644 docs/media/dff4fbab132f2bdb492ff21b7a9caa380ab012a2.png create mode 100644 docs/media/e9c52c3070305a5f81354001257578dd8d60b6d7.png create mode 100644 docs/media/eabcc96a4a20e0ee2b01830a6850d76f6f5ef76e.png create mode 100644 docs/media/image3.png create mode 100644 docs/media/image4.png create mode 100644 docs/media/image7.png create mode 100644 docs/rqes-walledriven.md diff --git a/README.md b/README.md index 8eb3389..7e8871f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ The SCA provides endpoints that allow to calculate the hash value of a document Currently, the server is running at "https://walletcentric.signer.eudiw.dev", but you can [deploy](#deployment) it in your environment. + +The Wallet Centric rQES Specification can be found [here](docs/rqes-walledriven.md). + +This server can be used with the servers from [eudi-srv-web-walletdriven-signer-qtsp-java](https://github.com/eu-digital-identity-wallet/eudi-srv-web-walletdriven-signer-qtsp-java) +to deploy a remote Qualified Electronic Signature (rQES) component, as described in the previous specification. + ## Disclaimer The released software is an initial development release version: diff --git a/docs/media/b12fd36d1b4044a7c6eaea6cab21b631ce99aea3.png b/docs/media/b12fd36d1b4044a7c6eaea6cab21b631ce99aea3.png new file mode 100644 index 0000000000000000000000000000000000000000..dd0c4b7236f6e27a0a3618d0ee016e9f087e6dc3 GIT binary patch literal 197089 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,tw6T4Z4=> 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�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 z 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 RlOby0@$ovcU*Zv(`Y-NR-b?@h literal 0 HcmV?d00001 diff --git a/docs/media/dff4fbab132f2bdb492ff21b7a9caa380ab012a2.png b/docs/media/dff4fbab132f2bdb492ff21b7a9caa380ab012a2.png new file mode 100644 index 0000000000000000000000000000000000000000..f513c39542d1555f8882a7250ef6e7cacef2db99 GIT binary patch literal 56586 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 literal 0 HcmV?d00001 diff --git a/docs/media/e9c52c3070305a5f81354001257578dd8d60b6d7.png b/docs/media/e9c52c3070305a5f81354001257578dd8d60b6d7.png new file mode 100644 index 0000000000000000000000000000000000000000..810c48eb62701976173cd175dd62de3bcca28a52 GIT binary patch literal 61663 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 literal 0 HcmV?d00001 diff --git a/docs/media/eabcc96a4a20e0ee2b01830a6850d76f6f5ef76e.png b/docs/media/eabcc96a4a20e0ee2b01830a6850d76f6f5ef76e.png new file mode 100644 index 0000000000000000000000000000000000000000..103f69dc3df50f1399189a74e635fcfd424610b4 GIT binary patch literal 68954 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 RuR&LLS?j81?s@A+{{bzJ7)$^F literal 0 HcmV?d00001 diff --git a/docs/media/image3.png b/docs/media/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..edcfd624ccf26810ba79ab6c1ed818587d9974d3 GIT binary patch literal 51195 zcma&N2Q*w?+c!+qkPtzX1VQv@L39!|Bn%O~hA7c{H(G=cJ!*6jz4uOZqeS%Hdl_vo z#>{+Y^1ture!k~@-nHIY%VM^D_Bngcp6j}PWq(mqRlG|?ON51mbyw+?+*>THThmxr zIMRgpKnv{=3iAuw<*lL&R@um-UEmF_9SXhs`1U0TpvGlH>FYFmZunnerPQaVrXmX$% z+1Ne)23AZH5#hi8)lFDdo_nH=({CgZ;D#vM|0sufeS0&36PfLYlMHvz6=}C)B03ku zT*jo#Q?%KHc&dhqzkR3VOBnEayK%d$eElG)>DAv0IaYBZ|Nen}f;;`*l>vLuzd!jq zrX#ziSa_We;qCPi^4WsH`k(hP4;1U4u_RNL4L)(Bpf?Fn`Fm?l5K8d@kpqFAlm&X~ z?^uPV&O(^p>My7b{>}rho7@1@k*4n&iy1lW?fpq-LlFWic>O-%uG8NKJ!Ru;;L$mg zJW!w(pdvL{l#-hH`+yrs`CL+ed(b|W`u(?w;vW9=KaZ^5>87nsRc34n{(HMIwTjJD z%=qk&@09(W?5$rb|7zGJH@KwhX$r?Lu{m^08du<@&9r`Tp`l3ToDlpcG}a^sIh4@5|lJfS}^CBn0w^-LYz=|Iqa69 zYeKN|{=uts3$G`>eztA)>(lw0&P47h0T07Tv!6;ct<|?QC810{ zF728ldVQ?mV?+nskovs_KGJPVU$R0Qd&Uvzc8Nb?3mp56C5i#u-;30>OB8*H zgtSbp-2^fuAM}pDhIKQFN6C7b?<~)T3=&xqkXeHXLv0@9{D3;WiQTM@UON&k5BrEH zr7D>XZ1&J(bwC0S!Sc{iK01~4;eO3j{{Dj@FHY_cloNFnqU*31c3kwH&R@L02&h0L ze~0=wR1X%}LodEVFx$mE;Hzg=Wmd^8oZ(54c5oc!y@;wu3|3Q}u1@^!qpjhI>k3is zmTdGA0(hXXjyb0a4ZNADs-79=pAVyFy%squ60w`gd@LEI<`)94Rc$VI^n%vXi=Fan zhhKf!?Mf;|$OrheQw}NXL*4~kzc9|E){AX+z=;yP+)cUtrdTe|;HMb@tC7$KbA`5Iv;hW);u-12+BrPHzZaah_gVn=kS(HWAvg>1oPR zak4({9VCsXsX`bUY+GAL@wAmR{<1Zg_t1C8VeWTxImy}AMBB-?hc)MW0XGMtl2R1> zx8TXx(#Mu$pY&gz?>CsGX0W^ZsqrQ*)26WAy7sE=$Rmx~za7+w99NEQ$KOHE_4Qz5 zzFw)+%$>Eiiy5KZmNK5kJ*N35s7|FBq<%DW?K-fHcv zbOk1^(%$o>CIS;ElCp_J+D`jNnf`}FV1n&OZHojOL(gw{Wt&{cm@JFR-T$~Z#;qY{ z&ESSN^-KhPw%w7C%G8p=<^!+z998Q59C(5;YJQ6#o7ww*+sf&*f4s;ZYL5tBy_d|=X#(HE=Rz^K6Gyt5yl zK`*|i_Gdce*C^z8i&!C|2K+)}%W8g?b)+NB8u&zLWuE~dA~^#`q;z~N5#`5Nu}?T9 zbiw9Odn6uCMS4pgY}IPNOPZf0|8!oY`zbE`g_)MtgAAK#@*vlFBzw2`)#>>2^EIi3 zHEX(ytDm=xZSkD_(`1gt?4CQ{`$!kv-^FbX>@5q>595#O=-5Y~mkRMHJl5_RSxA7*^n8q7kKDFZ81iC-OPX#;wKT=a1Pq3n(w_I^K&P=4{Yy zc*uFLA6wo&l6)Mz^6L6CvcvRc&pvx$sbZ)``wV_=J*lvIH2CotJUeNgz7L1sK~A$A zZ+U1IgU(a-Qn`?;_Qq?WN7byrM?)5w{B`nem%wD0?e1(EK(MX}>E#%WE^wy=qiWheBbIM*4nj>$Frl8= z{tfQ*VYwd1BLR=iEW59DZ%E~*iI3iWE$DJ*C^Pck%TaJM$-J1|DX4PJ4Cq z2r&;cw)0-_Z?^bP@P6<3e`$yTq9r2A)KDI0s`jD@LL-X`P{FCn6bp|je)LVsw?>N8 zq0HgFTdFxN$7!fHlOeSf1x({eD=Bn}_id^#>FysZO`0NSz-*knZj8OZ)CN zrb(hqZ{MUzWoS-IHqNR3@utT2WlwIuyd)2}h>cKVt7MABBL&Diz0r5zWHc!jC-hCl zT3Hn*k`kW1sZHh@LVMw*RY+PNdcNDu9$HzPqqM}e&&{?%yhNneiH8>4CQ}=#sBuVn zD+a^sNk+xQmgO(DAl}{0V1Jp&gk-(N60rR7Zl1TP8YVZD6-_h$N~LRsn1k zdf!QIsU}XKJjJ5x%Wlzj>>3H#DIQPK5tWYEW*L0a-e2*X{k?_RPZP3$Ya+F)BP@biKJhe21)X(1Yn`O5` z{=D^=4)XJlJ|{lCa9`O;`7^fHj%p@3kV@yJVE;=hQ8uA?nk3tc2uwi zMbP+Ym%~OIwNzeT*DB~2fs0F*gYW=d)u6-8+M#1~k>WT1GbItMYa>=#vaEF z^q^mQ?Pc1hPcuCcxV7NcNb4ZWHpe~TE#K&Z!$>13%CY&^z?P&Sv3%#ixVf(_!rg=S znn*1QoOVdg?B_27Nx?);1+M=}O}(PI^U2CQUl%gzD{yWZMLkY4OYv_fev)egTrspwLc8FU4#U8b8!t zc4E(q`R3{DhL`6vaQAmUnx+GDK^r$^oKlzfyj8Yz2uPv-d@$FZ&u@+WcSBiddg=VQ?0rZj#(s0TC( zrh-1}neto>g3)=>X+epGiodD3_L=Zb1A9wK zo=QxYvE89!&XdLOtN&Kon~*9QTej|ME&GJ>No5Yh^p{PW+-rA|-rx13$^x^|N0F5X z6pxNK1FHkKfZmw~3*rH97mGlx`Mrb7_NrLPZ$N<;DR0KypdeM0!P}b{oUS_9J7g$o zWz$B0()do5RW@rF`e;uacChBdSHVF*IwiN&xE1|;_WS49(_@PP{EbJRivzJd1)g*) zwXhw>VNiB{#J_Vuw@xTEK0`^i0&R{&o%fEp|IaL~K2YHgp&JQkOb!+O;!=36n#gV~W z_~765|EDoQOd9wPAo$ezKM=xyZ-^AmH~zJD7}0ZlUv*Cj7>MHQ|34>uOC9kT->{qo z<$`18$52IZ@-t|TAxQgklfSHF{pXW~nv3~4?&$+$+*%fh6$I$T0 zY@VjUm%9ie%M*MuVUR??61EmD=;;>zzMEeLJ$ z_kvVvKW(qKjiIs!h7bHj6Yn};NHUREef5e8GlVD2DRE@0mG7RUt9I#&_v=2M$6tkN zeUU_L{juB`0=z?A9i+TWK4<1acex_?k1NLH*ffX^ynZFBWIP@gU;x4s3ceYe*{WL6 z(uz70XzLg^+er+E zql`AQ0tHq(FEu6~gJTT0^xH{qNPm91M@Dv+l*S-a%#G9ca85|~jYhVk>|w@n_o?Kn zvB!=QU1+zbMnm>A?cwb5k1fzsOn&(38seY60$W{TUaj5o6|x_?RIX~NMlHMPlDEKK z-=1cyn>Nx?_VW5}*)C**I51<@E!S_B{?Y4e=g+jX9{=a0-pHoa|EP1REldu`k}I!33iNTJ&3e4am*u48JLB7f@w_eXNJ?2x8= zf5q&GMD^B?{wbOK+NuWX=4~T+k7#=B4`pTPx#D;sE`g&wdZZb(K)`?MFJ5#yQP9+i z{&&RR<BpH{K_u`e6~Qbi<;tscwG!p3e8!uBWPQo4n+u6^cJy-q;r6i5aV z>M!#OL^F7yNXHP}-3K~o9xN|Y_@4Yz&ll$TZr_nfRtzP}d`Zf+O@qjcE-K>a=#1!< z{jzrBs#ip{qFPzbO|`&{RO0s2GrE_*WL@e*IQvya;6e+bri^#+2uwdy4HVFK#tEYH8t4zeBD+|BA{u-DeY?WHkqD7@0xTK z4gk(`R^vK5-3|iy>wJeAXRYCN`f}-=lX|w2B|m>Uqy|NPKg2FD-)k~7C?vKL^cl{CdE&*rIN`HV7$$1}F z+38jQBetb@xt^Xuu(qaq-Nj)w>(=cr>|Qj<&$PK|rrw&>ttzSB`hhyoz5w>h72+1O zNe<~tmri|B*Des3^V@?W`miaSTa!adjViuuS&9D(_yu3gy6{0s#66Fq7-?s;<1w)r zx|@IIsmA-EcWGGt1hmyvYO^~}&Vb#mPCHPrGWUin8U7x7B>xXV)xj@24HEj2FF#oN zJC%?zt)kT9)izC)RvPk8LZ8o`wmp5Z$V&4;;i3!0AD2f;Wd=#HADS39AEn|%&#<6Q zt2XCTE%0g|3O8?4ebWrP=E;4>Yk6qw>OdE)spc;3-n9gFQhV;Dw-oAm6L)C*b_8LQ zH!sq6tSJyPqBuz&!Y(~iXlyeCs%x2PKihwQV9X{JF`0|0wIe^$Oi~&{qNz*n-Bns< zIG$Om))_6k0=D^Q`Ce&&LgDCdrg1sOUwSQh76P>Tnm~kD^1w4J>ZA09sF^#FJTTd3 z4q?AQaI;L$o_i430lZwyR{AHbqW@DJ0_|59Au@zR?n-nvQpotN*^<7@a1 z*((vPb|z~L)^N$pxF2hkv8Fqk4T8Bo>SHu(@-cEg@_UAVMy;IL_CdvewIO4L%mY#L z;S3p8fA_-G=h(j4g2Bw@IE`T7f|qRcdJ&PhhcHX;Iy&pnS3DOzh}f146PsQe2GymR zI^=FIT($|1m_UPT4LG-hy&2LkXGTx2p+T)j$=jjZLcL!1_61DogA3qJBcR$yiEcwyK<&9cBiv)+e(_4%4P0N zD!-9|&gg~S&oAp!|FMm|;gK>7$-JMa)4%UvdmiaL9QuTnrI((rsn}~Yeu}gK-hk5a$K_gQj z%na*kx61VuX?+=0_;0^rBu%svxF8~w={!;X0vX7bw3CuJ(CZW%>~*k^nXR>9&m95a zuim>F0M24%q7|n$5I^PLwg9*?$#7`r3l{)9!5SQ-_8Dp<7F>&gJX}dRWJGu%9t!a` z@Pj`z{vod^&gZo(e)VVknJ4@JoK+_%W_3|mtd7s4&p%r2m%XLu+x!x&J36J&5|yig z7ZPj2mt9!RN5giq-C1L+%=G)pbl;jnYuAkFRzB}mSjXDHTN?@Gs>9b#h;>4t_Mq3b zr45GNDL;QtvjqxO^T|i|i4&AeFNW&3%*VK*@9$q{8KqH0D_p1;3=J1AMj!QxaIX}$ zNiX|GfkxV%IVr-AF7{LOh`pDC{Juic1jgrntqbz0*sth>W;o!YPli zh#%>fq}SEo?nB{EFD-)!kLugj46t-Fhj~VOdQE^bc#WwPZIkO}+`65r3#^qHwZX$g zDTBkCul2fG*BHngtbF^+_Xjx|3MJX(4`Cnle_hwAnXyz=RUI2NXT;o^bpC#GFQ;g>b~jhdez*MC*jL?ZI3eElaQQY0EZydK7F?^c;E$Lc|bkXL7 zDi^!VrIe&$RWR&6bEHh&O7uI&)QS5G&BcGv=>?$6Fk}Q}H{^jgg4%POb%Enys7-f> z{kIv2B7*~01Hr}mJiRFR5mgWT{$&oc=5kH;bhH27QA*njeh5(3-wC#9KTR}qs3r{i z9#JU|a*hpzZGjJ#IE>?0+;Wu`>+0HE#if=5o5}H(%T}J9hAdWUezoUz*>l=XpURa5 zCCR=sdE7SSLfZX`^5kW0!;;v;q~R7-hq-XFG`cd~u()?YbW}7}Sd9;Fcr$GgK-qEM zz1aUCpj)|_y-^c)rpi8n$&`RMHtP-Ehi8QK_3!x_C;9tdTq_ULGt0}?P51=0%R!^l zw5za1Rp%77-+6~OnMhQq0T+^R$#+BX0+k11_aE#xFx^rvCK1*7$TF~{NH&iMkx&F5 zoUQdR7v&i@*X&U)WNCnomKx_?rN=_EQ>NYzUxv50nBf7aHH^rB6odRT$pgUzae^4jp-^$zRG-YkCI0~#~L>^(N;7^ZJ zyfPn-e8J)x!FgXaIev$S8D7On+W7nw4~i>X#G*XSO^~2rQA(-u7&K|#LZ z+q7~%CP%Ou=hiWI;%RYiq!ak0YaLTEUy5`ha`m)&yb^YfLWlwmTkQS7@K%wSdCO7{ zfVy>iB5WW{6=U1>U1aLvX3zHHQA^6?zP92R8{8jz_Mg(la`l4t)#5gg;ms`;o5;*A ziAsly%8?YiOpP@pBI_O0!&S9eJYO%fy$nm-Ib#4Wd~l3L-6M-+0oG(JqS|=Z^k>5% zn4)YpddVFWO;F{gzi1Do?e22N*KALBDLjj;zjC-8H9+Y*E1GLpssBL!@%G4<$-ofu zn=gzOkjlxmRORAY;M>8H!r+gP<4gK6X>8cizw){sAM)ar5uxE+tlqvS(?C?F_?7n~ z;ei|9^*}YMQADmYoSY1Mmki!E)0x|%_Kg&mRM%U<5dNZytVO@mOOmIz<;CQ7Ieg*s z>$p)ifSK)G@lK1V(YyUjWGoQ&o5A{7TZ4I9<-S&Pf|2Q>=>-*5Vd%8!CbWruEy7|8 zTf8*>;vTce+tMkf{^X3c5c#UZvudPMJ*ZZ1P#+b$p8rSO)jP@-so7b$ty7S;jRhA4 zlZn-`D$RWo28r`f8hCpr9ZOMcxB5K0BidTjcb7(|&vxwMWvw3K4cnKKdzQ*yH;EIM zENlC}mu_0ONloVuE2s_;yjd?d&J$ya-#Al}6MVqmKq4n~bTjs(#`lViPGUf+ESJIR z+>D)6@aHf4-LJT@eVSAsPN!~ufm*4ZhKRpIux`e6~H>_vv;nxlQ!NHIfiR z6_yoyKg|JvA)xnAvB10dpsSq%Q|H{@|FVp(+IdrUnRm+awS?Da%{sRX75!)+E&92e z>K9HW^jha}*lAA1I<2Y~p@+lBiQOk8qs~TE^s}(<!)OYV({9ClC8X_9UZD=nJN6mbSg2l+uW#FiZFXPKH#A zUyJmXD0}p9OSSxw%Ml zpG7|`>B-dR{!+)gxk%9u+&-+w`1njYvA?Vm!@~Z^5#HsMVn+Et@Zhgt&AHQqsazE@ zZ7WAo!x5DPlB@ZZZ$PX}gMyf<)ao2SQLqz+CH-`XzRWQ8$p1rX<@=x7>wjy=`Fe7j zHgR)}#%BigB|{Y`ly`$VYh_WsUjL%1560j;Ra~@c%Y}UcQy(Yt*TEl-Pwf0P(*!7? zIg1)3b2sTp#|QlUH>U*NL_pb|M~pvhB)m+$Yc}~bKe#PdM|f7p zKt5wg8mj-TJQBg<3XXPI{i^hjjyQG|OZlLwsfo`Y<@+X?PptC7s(hJhmQ!y$@_}TN zdAvB*o&S}7R_jSV+kUWia|Y4eZ%9SI4-~M^D{Ki(WGL-eV)1t_oqyM6G{g&;HdE^V z7BQRz3+t(6$bc{Tebrl<0Y}shlV*(N25E$ffsvj$USdn-JKYLG9srvH?R=~IXy~MN z8mc?BAttqSC=3*sDqF-9M=f-oV@sj`h?`z}czbKv$3kOZ#}KIL6Wt0HAsTi8$| z501N$Of9?(1H{-#Vy{HcbI4$< z0KcARF0G(fKa=mc{!1qutyFJ%rbPsfs7iQR5Y43_BZh5bwF=3Nku2BQW1gQKZ#Gcx zHzmx_XX=_l^0)_7hNN_<+>3bQE&&3_9WAp5mnYPp@<7awNJw)gF|zkF?BM;eL}f=& zt5y+20#EOUVTV~*#$I6;J%;9uv!z?oC8RFr*WZ+-LdYL~KfrENVoxiIka?*ak`ME_ z=SwGTgC4XDGno?KhZr~#^;G-F9__g9q z;bz>B$|@PBL+UuFHUDZbn0_eHip@>-&i9`t>szlzA64pkg?>k`15o!Jmj_5%FD6*j zN$)Io081a*-uAn{n+FFV#;mUR8tA$G6i?P(nm|ZPS0*2()wS<|`P2rQ$s+FOS#&2v zi~9@S=4L=C)t?TAULJ%JaliH#!Q*a@x`Otq)?U+w=bJ?g6}oTokur~-Xxx9q(C9C6 z6;kQHMs1r|$k!7L`L4B3`&S}-!1*u~4aUO&X9@gisevWV5?3rP_QeLTdxhA5dZ&?3 z@nQOtTHCniN;!rc8YDoKz0_zgIGt98}o)0`0iA!DeK;oI#o7EF8mck&RMA~7n z-ITaDfivHN32e?Ub~`_&Bmg;nyn3?BPXR>B|A%#BrQUpc`b5R`<{NIG;z6eOi?P{u zRo_Hq5Z7s-s?TEY=@ATIjK@6t%B~b%xoZ1-Bchl7Vt9gy6ykONa#eilgi-%S6{VEj zGovn)u%abH8^uRhCVqL3VEb20`56LyGGt_UpT?eIeq;Do;;PM?YuNr@;w#L`@xK__ z{}8hI*}vPcKkz>2Dn}LhtN*oZV78L$I6_+HgGqA_lgZUa%g(Y7= z4s{#Gx67R|HU{-;6UTH5neAUqKvlnnHCe6d}Cx8qmXGx&4IVY-t8-33Bi0uue3M^L30hsU#f z|MtcFCpPa3YVdKrP6GZL#y*aDI*q_n=Jzej#TeQ)jNJ^=1SdZ^^(UY=5DGXjMkbIx z|Lax&d?g?Ij}jeB=H*bycQ6yd|6fGPbo@Gp<*2|wY)OT`vkU*v>@brD*7`pU@lz4K z`Lr>}3H46~=FuY_v4&%SkwKDv`r|~i(RMY8amSmH^sn0ll+v3I5F4H@e(~Fn_PZ4K zdm71vB-v5>(XD<1j{sdyWcwnY_sg!A$a!J@8$gQ%Zrix%YxcvzuKLlp)iVF#r*FpO zwNu!~3j-SO3LhAhvUOL{3cr~I+JGEL6m0Y7e@E3p94<^RLv>lh`@86xua7&*2H#%? z{#-3Wi;x^9hcqMT`?$T8zX5nz_iqNGRsWwQ$|Vd^c17>CLH5DYa-ZX40hE1e(lu8; z+J2UTxg(K-`8)4E+3TTcmV4GE-4lBj5M0E*pqJ&G>$kh>* z-BOl2e)~a1y^w{@ss{(eGo}$o{T6xZj^=8}Hsh+OBwR#naB1aMtOLx2N+O=7VkEZXScAi_u@&p^vMOBWn775`l)MsJ)mPepK(?}ZE*K3 zZCT$Dq%w=h*vPrH|6fp3p1@v|iP}30L{i#c$RPtzQ{c{fcyG~Q*CdMU`jXd^PM4}# zKDi?Unt>ZiWVZ7#kL=^KXD#qwPPIR(9B@dAr&J{URV2mI;7k=Zxc1s-C5I5oa_Kb> zgzrHn5sx%gXHIog+c%v3P6lbZh9yoOk@F_##SajLB#lp#|0yyS+xtd$H(J%X; zHa|dWd(PMJ^93ML(roTKC?<^RaApJ*v3~06tk(;jTkcGln|f8umYAi3E3Bpr|O{jiPUCTEYCNAG0z| zxZ^f~T$ZD|C*5&JnS@d8GflK1`fqJQYVX3Y{Kh{o`I(}TOXk!+7I0$4Z{tx$Gf@GA zi$lrDCj~165aQ4I8-8M)1KML#@=u=3Mc!Lu&nAO|Bp;QLjo-Te4k=n4Ssag%xAvy| z8BjNkge}`ce*^>*w7)-T81HQS;hoP{H~MDz73(evmexfHFixt+nW>Bn(zU3u3Wnv2 z9aE;VQ#+K=q}0l81FT-YopZs1c8L%hE^pAYeCA!uWh!(h+j@JfX1TDiTb&&`fnRW zum3LcezWA|pB6e7m0N8g&6<=@wDJyb&f+mo(BX?q2aM=@5WNxB$G>yUPb^%5ece7K zI`d3%-nZeb?(IenJr(TKSu(gufN@`wdwT%!3TnL&z-4pP#SPZmN7@?`?K6BMzFR<% zSzh9H7xl*u2z=&u$_A3uY^twMl5Ob(9TuKK?&?g>6>m{}TZ~%V>3xHwwbct;beSwU z=PSLRKqvmYT^XMzw$sZ*btqGti$SOQCxaBi%9=Fot_QMSlE;YD5-xl;_?uypToKs2 zZu*ioBJg=i{ZfKQhj4L=>?i$54a47V+4mkgifr9HxtVCsCegMIFY#j8<^#1z{aIYN zzu*HXyyvH(`p~XLQwf=ii|p-zEG?;;m6Xv;~K!u75Pu9`IlYu|EZyrDiS307*Uu@*f_XrdR1S(AJRXn z`Q7~W#&+p&C(6qnc1;ykTqTRL&MB61Ili%l39DE+57*-_OEZ*BZR##9WITbfElM$- zoG=1u^2upwL&h~}WA%5w z_fn1WyCSdBe#+bn@3%z^ZtRu&NibXP2;3R)9;n8z?nz%LFufKtM-{z)nG~C-QHS53 zS=RD2Lb}%K`kXFDCL3z38}$5>R+ezwI;QG6CmGBT3g=fqm0`|~KfR%I?I?s#Z*)&H zQKodb9}KI3`O60jg*hdScrc$CoPl!Na%dt;y-^y9WA@W#Q>(gdZ5p8I`*xWaFlTO_ zUBwUk6dM#iebP~;JV|Sx82Y;N4+2BgT7s_S)ntq_C-OQ^?3Z6LF#IhPR#I=oXTGFR zZN1OZ#jO)3R_Ezu#;zdDlFqOnju1t2(%qdnCj#^S0KY-c|M2Q9D(6I-rvYk^EQaO}~{^{|Hoq`y71m zkq%_BJ5CtdWmQC*@m$kk1lC%=OYiA?p4U8d16VM%ZHS8wq^HY+hM%Jo>*( z4u51eVva}!#iUPjn##zjXjo#?wo&PZM`@O`&>Ev7$6=Xo=dDrV2U_p96^#ZRmA=mH zJMA%WO2gRZq%w(=xzrg^4rSoGeI*#5z`C-Q&d%Q6uN(Lo|NCD%-E-@H$oYDQKIUkN zpo0Tj$q;spklz*doJW21=Le-0+L#1!gfR^VjiYFf{D}+L+am%2V?yL>ATqFHRrQDw z>q0U*tNT8ebIAca&b1F<6mF>n?6tn1?7IRORKPfq;{>c6rttfJG&lUaM*Ii}|3%od zAUgSeO?v4$;{@qO6wjRGW|nK~V}HFH7FVyqDNwxhr|7m==AVCUZ=q6?ximd<7aia> zNi!V;a_jo%RVLkb{!Zz8Upi)nK1E_7>5YB2mo9$KgMTc0z`)JfWSLg_hazsbcS|MOV!68mu|rDeoz}*#m*B!@+6n@) zLtOS{;_E`G7K0Vq8UO2Sa;)^AARF+(E@xFUq4pI=0<$mJ$3BkArpia zef4{PJv_bBXT8BoSN>8nZap49JmVXcWOVMUXoB9Gv@-#a;7{|Akg_tKp-gE8a@Mb^ zC6Y_i?S2S%iPL`O{*jR>jX$7*S{jT80#0LW0uW$R-Hjn4=o@DkOW~Hk&dOK)kF3z_ zO+~JX`lk##;^A&W|6v_qsT>$v%_#vgZj!ffdgkoYDXhs}f4jMV$yYa;Fk`dSZbu=4 zK2mQf`eJ(mLi4&B$!~BT!{`%^b#$cgHzyGIPy$VEMTo!e1%6aiVSwqjr%&_xIG+ z1PJ4}qX3_^w~@o-G}Ja%Vo82y$#NJ*&yd1%+>^$$5T&O+4Hg<+kQQw>E9O*S(El!` z0DWxS+U}hiV}&R=lL3WyEuVggGXCl|;9^0UHvMEC{UMHlVe}-)w1q&GuI#Wr5$P^V zWj{k!$gB;GcJnQFz-d)Yr z+Ej^f_9Cw9?dBh#j+&w8I^KJ;%M=+pZEO-J9){k^+E>%2zK5t7T8%TC=ubWq$rOO& zPhl(6C;HR$;AYS9K8ekr%4PYzGfjVZ_A$Q@YvZ;)OgQABcj*|rH}aw>H}P_Rrusd+ zlB}~fHydTd!$-1EPWJ%_|HbacKG$QlO0Ww?)wKVOu zOnP~-zZvQ0I9lIrR3B>Du5ywT?bOCr5E|C|{AWmve-+EsKh|$O>D$~?UlUCk>THV? z3@bIY3aJ2|Ca#^j9%a;mUe)|o&Z||r@IVsXB~1g5)*f?i(N0>F>65k6)jcQ?zc`h8 z3RpN|@Mo+KA6WYnh>Ck)mD-gZ#*LRx)X{P$|6>*OG)mH;Q%=+BU`aM2HW2kujbtg7 zhQYBBHFuM`FGdCQu!uS7<$3m;#9v2cx-oH7ku}5bQ&N#xeP>8PG|0(k^3$$6XRrP1kw8bz?h!@Qzw9QE z*p|(i=IVPhF#0O44@$7XtgA)C= zyNzBGJBGlIdZAMSHAbf0K^wn0yx zfL?4CK@~l|oS+)s8YpBEU8G0w`|+z$LQMn1dVzyIf>xX+biMTQOfb;PRfCo4h$F3C zz+KtkeIA4D?jnT0QSp)JxxKwYZq&nfT<}3tn+(w>#7Bzn9`b;}4E8Ck`i%QdmAd#8 z4`5h7hNQJh(|hkF)QgD>>@gg{*fjWj7*`A&XGT`%{W#7W_)00@HmDn$tkwkOedy4>rZn4h{dGqfJAMMquU z02wQ!Q|PYhvU-c>y_r*4uE?&*@TWEH*Yxxfxr+E*n*RT2Fb8{I&&9d7r0e!M>)!R; zj>_N4GN8%}V!;EvC%e|e^b{C}JwTr!>!%TJc@{W4yNR8EFLE`Gx;;n#;UxD_sd?9w ziec4t{BY7dalWQVZ}7)99qvMlz0d1)Zxe1Ehm7$$P7|9q)L0ON`w?h+HJAm9l;wcE zAX1lo3dK1jZ^VtlDv!u=fdnoq^0t~UPs`?Y&}KZnG>EIt_xKO^*zL*AVYP-t4#VS` zThm98vita>P5s9|u75n9Jv}|bwzeLtS>Jf9UmA_kSXlmQEIDrgQQ$^=JTk>yPhN#^ zQ=Az-&d$?&)R?4RwB0UZhBv3R=LKddF#Z`+Q~Grx({MaeUIA6N$I+d!JyiZ8fzEiR zCU*4s!*a5&rl}dxIp*P`)Y02l?_L+Z*JI%nchB-q@Y0v)V90)@ z$C&+NDs?lHkfgqU0-qV#ecL^0#i$ZIK_Pl3CKWx-tjP2%j2s3rUwqkkz*I&YlG&_b zd~^Bnsn2{xqWa6#w!0!Fy&5xNloc`hF0psL4*Qka-Yd7kI)$3(JeqjU=!rPjuyBY<}>IZAL}>K_Py1-_L`@uxYYyzeR3&`3Q3+EG<&Cl zg4nL@`OOI53ms~ZV#W%fwfsu70S?V%zZLrYdw)G$!Pz#7`p zi3-Oi=PO2)v3e)}_Wrvfg@vP?HOzSLPW$XOPPC;r1ZF~gC(j=JHK4NH2qx|h$bIul ze!#@X{Rt?J!-6`1+h9n-fPZ7Iu=oeh?B)a~{h?M3o+oP!M(7MlpJP(=F=?`hQ%;6V z%0@g$YAo*xd>qWM1JHcxTdywY`k?hzwvDVr9YbCM>m(SC^68Yv_ebo{wIMKnI4@cr zM6c_cg~9bz9?O1I^62O*Y4{inz57e@av>LEc6z3m0~GZ zRZD8o_WQ3bO7}yB_*?oM>OKG^+kYbtH?0oKQ>7V!;t+Q3YH3$r+iD@IgML_ETc72- zEAF`+XuQ(CR|x((bb;3+rg#ep_e7Fkz@DuLL9h{*hvrw82>p|T%GU*T{!+%YYuARE z5h9uC01Jb5@_8_l+20#LU33Z)-2KifxVf8IBMi|ifHNZFm$z0>aHr;WOG`&^n^E`; zuJ?J12+YTbxpC?}O2RMh5M;zZ!bwNE;ov?TFjO|fhgLw z3q87iH<~2nHz2SqBE_E+je=j#AS|31jG-{ScwL=DBy}(MXbmlEJjh*Lb#NSAhCpkVvs$*% z-8RO10~F-JHqOoP-hR@@$>u7kH(nSVWK-4~KzsCZ?j}ZOS-qs#O2Mq9-R;l9BZ|Eo z;5Z3dB=n5E@34O7uYlO!z>SD){D>>4s~?8Nvc5cXoN1grFPiG zDVt>$wE+%@Lj=CwUiXVdX)o}gc+%0M=!ta-;{R~SiDaNAoH{hL+^i*2Pxd* zH@O||QP!I<^?aE-B6~}F0@U1$W8W21XH30?yyQ%7y*J}Qk_5ZjtVT$7t~_11#~g5E zy?}MF5bjmKcRNaAVdISe1bo-ds}@-st$-H{9>|PBy9eLV0@!H!ddVYiXhe}*KqHjS zcemz%#0?$z4`h*e+KpP^mcmKW(k+E#mxGET>P@!=?n9gm7W6`QDFF#zBRXQs5a+#I zedc{rM-`IE(~IMvf-2)G&G|-d9(?V=0Bg;}k0kl((3W0RpP2E~u^H<{qNMtX`NJk7 z$x@^29sSJ_lTi(~h!en!y}}82MO%6oJ_l71-H~WF@bgMBr-G4p>lWC)*|ad_TXV=v z2vT1zy40W-M(8B(5?nYr;)C>*TGqFdicy!xD>RaeTlrw9&CubfepKr)WkKBCmlm^| z)EVSZ&{Pxt^2-zlrRnnVytG4*bJCM=uOmhL?MHNnNGfb-tDifTZ6dO{hPihB7zJB} zOdWmBl7e6)GN-V-t;Qn%t~1x zo@b4d^ELaoDY>AaKf$KwCYFE;3uiy2+4-w$tm)G^ zSOy#0np;CpIkdmnWg3BJF^_YuTjk{hl8GxeD2S6ZP#5>5Q`d2f@DS zGb9P|MYqT#@5Wd7IAS2 zkI^z;Qfv-23N+JfL2`Aic6lO}#^>MlxpB@^Z4GowlIo+3;n$SR;3$$WSEtekxTL0z z_K*mZ8rixhdvGNe!{~pJy-myhGzMa@@9>-|b59}r0CBd-1gQA^&rN6r)hcC1{$S~Z z=YN2+kfmlOd10P^SluU>F9U=pO`jZU7v32`JrwPf2Zh@in~g@ta+OTvcImoXK{IVT z+pD$!R;NGlw6m#(vkF<}7ZX^^RVqtQD#@FTysGOX`uy6O8P+HE{4{6?HADd%$@Dc^ z`|7)!N0uBDq;g?b^WF|sG6J#UbtMlRsyl31d?x952>_paBnVk4odRI?R6 z6kF$7@YgC|YSG&}a92{s_8#fgbjmI=J;;lv&c2hCgMh$$-e9-hR~fu{5zQNMX_ql} zKQ+3D^K6Z!^7qL-y>5hkyNA)hK2@jrx_KpOeVbyKJsP&d0e+>ojPqtcWd|Bn|ST>fZC<;&*w7`dl5Dx zGyC=-4qlnd(r3KdxE3*Pa$CiTwohI^q_$s5vk_nu0Zxy`jemVS-_n1)k){t_evD%~ z8I-9%<@2&jZ%CD(a>ThmAC61#AOZ~-_7bymavrKKjIBCDcud{5w-wD4nn#;Ynmd9Y} z_v3ki0ixk%f&uY}MpXLVOBf3>k0BvG?lh(W6+>KXw znH-7devc(6pG&A#y|etFCrcLhb_m@UeG|>pPhJoMe0Tk?CVO=V&3n`HN9(xsN=g^Y ztFvIYPR3x5$y9C}O}Zb?2ldfac?>{$)pw6s3^#C!b$T*_mspE_fu81Q^RI&WYf7?0 z4``ifm15qFcolh%tcHt}I~+?i*y7EeBB`j7-OC?8W5FeMig)2R+uX?az42{0ACYZh zT)U6jZQGfOLEmh&ueUv4$?W!6lJ!ChOD4RzQ^tYy%rVSfp2YBgw4^U$$4FV?_F$ zlf&NBp$xN>|Hh4v29#DX8%6uZYS2-tNACuB<%bM(tJQ@b^59x=#Z z35Syk;t$3@%Xv#p0)8(Mz!uaq{7j|m&E}U;y`@(to`ZhdkG+r=qRvlxLa|yup9Qwz zUZa9WyZvJmJ#5gv`v`Kqkz}e+(^n^f?;-4m=*I<1l4_y5?)z)`ysM7G;0^j?G93uT zZ8AA|@63u@cc*cJGmq3sZ$x2;oM_`ImmI;CoQvfyu*q0zISz+Fs1|OVaTu02;5BAa z6aJ%g{#b+HLkvxEbDdliGGYP!RAQTr!Iu5}qR)p=NXuHybS;g2HtQ|#=leRO)2L>b zEc5!HFAP?$Jc8>>k$jo2V>lScI5@P3;%3bTcZh|wq9UQc8G&3M7{fuohb@mtYy@9% z8%5a{0VeCO+!!;+wDj382`==!Ibi%yA;VtqX>$8LZZ@FvU{d21o0NV#lk-gkv=a{F z^L|^og|HW;geWd8U;aMLr94`P=7PPV8jP7gcizklTJ`HkB~62V^$uUu`3L?|ImOod zPFM$%8m6F1a2(NGG>gzd-99^ePnuGNeh$U)b5|`WPQtYjF{r-BGJ@kJrt@G;^4=CI z_UQu#)_3QS&DJ#r>|| zGR+5UqebXUFDE`KtD`9wQ5|UJq++e;;!*Xi(jp)I*07c%_uf%aeCfNd2~b23kswh)B?$Pw_ndpyy6djHhJRSAX}YR*Rafo(ywCG_LzNOA z9*${X2lKSQr4;8`FZQIbBMuu8O_OOKmXHoBQS>y9_Y3+>OMKkd6*8DGPtO*CmR$Nx-ouB+P8(M?jta|KWrTaEE}I@+=po%}<}s_~ zzcackQZCbxPtX)|Wi7Dzs3DkMXLjs+bD1UL!Y&f;Md;Y4lO$ur3;=NQ<{)>kzUx&{ z(U=!z)YT%C-uzj>ZX+Vm^Vw6)$GSK!b#8>rl~~m1(p!k&1U8Z0jm^xKL?!O)?N*X zd;C8yp-(i4+}^;4YI+n)mCuPUf9`$-``q5$w_nkq3?3f8Krp)gbr(1st2H8K~fcwVp zvgsy&NGYw$=gj<4^JxTMu64t8%~#>4?H6tdcRd38PDo!lN!Cvur_G}|4F~}eL4@l5 z+7Thh?R&N;MKf_0ZDj2^`QEDYTh{Ikl)tbNiBTTdPXO(rj*mxy%FZ?okON=iLI% zSz(D4FC)z12P9AF@|737+eE9BdHU68%(o*QDG(#;T>optUyd6TXTaBU9jGCxja`Z? z!?KmXY6HXBMhFEbT}bwaadHm)?OWK8wcoJ$RkGHselPcamYi7?>u^VAqu!HkYRxze zFU`69vB)9zk|RgSS{VFCr5kCVyTtP~etio>wx(p(OtwjzaQXG9@V9#Ho->U~XHf8m zrkqE)+V3f`2?`KENv-=LwKhfz6$S1OsdA8ID71e zANHfHrenYK>?KXh3h17oFzqkKslJ?a$ov39}pHTzuiip68Mi%S-zge>fSi07U(!mN)(uDEXvoXOK_!Z542>v_6W@JDugw zEq`3mbV{hm{gRsSp~4TZn_m;dgTM+g)!yVom~enKzkf^ zeS|*r4i**p*P9+O9>fmeIBP=d^+b135T&4-dLyB542Jm)O}i3M!xwO_;K+=fE{Tk$ zey`ytH@(xas(1VLc3RH`?9?3xuB#lCqlFFHy#hV;yYUt?2BC|nqC?~NpN=g27FoZL z#bs*;hiRSyvUKNqGvRsYNh*R6zWIzQM#ZHn7lGMXaheX6{16k9o$8;}If@|k1xlud#tCJ`ZqF2Nbdg4u!0Pk(M zS=v$K?UF;YyZF-2zRMp27Me)F7E(rU3yV>#-s+r!K9Lm^1N9eAsvQI1SQP*AZELzH zNQv(rR<8l+`qa?AeO$dZ&FxBZ`e+*lgUrV)eBkY0KKfAc2M2`PTo^!pO<{)oy2-#- z9Gsw|M6r&!jVT4u!ZAkMoBsiopnTsZfMz<80IId}Ar`G2I!CNUV z7iH7P3;Q{4bmWO<Kkhaq>IQ(eH4M9nL3QoST94D zV#1G3i}Z#xu!9r4p#OUY_1&aLnK!;#A&9sk&oYi7)28kPFr@@deK?!p(ps@iDMlwX z9sWJ!>9#BKn3ngLH!N*DpMoW`n@smJ5A%a@+SmH%*1qn*?1QaLPZi3VIvr4i>pf5l z&SVaFqF_{iHd?`Y$^7zsV&*gO90eoyD6( z$u$$<`1Nwz)9d)IbqWXu;XxhEsgG@Q;%}4q|3NaiF~qM+*YGvIG1?PslS2o{X8^ZT(1CvYS-7 z!QWv^tn7J_Hgo)@pN4)LX`!5tuO{S8mEIH7T6m#whmxXC7=BBC*@290(gnz|} z#Z?5oDTf_<0)3i8i4i9&r7rX<3e305@Q7t-r}FZ9gzO}@j~T+Wqt!KcHj1?Gd?_MX zvYo&QE$rUvbEfrxXZKT}Fw4)?XXdf2Z)orh;E9Rn2B*2U9zlG%&G}~cv6eFZ0)O8_gUGXi)iVcfgQOOnbd5GQr)ViB1g=T#F7ECi( z^qxKV+^rUUObG!~S;TVvUY+67%~QkWmyFA;Rf8}u`$QsU-XAA|lG06OSLDT#taihC zd<$@|c1n0J3_=DGWv@#~$th`4?#+Nus9^dvfGO=@rcU1>bG&GviB!93Y{$Dm>T`Ns z6=BR>rpqsc*xvfXHF&FCZArw{in8{BF_U@EWw>`xaa(VrzIrXX?D|$sBhH@B@*8lO zKN!ss3G`D}PO^S_y~C#%vGIsYg!ZPNAC1wGWhKLVvu#XBPm}rByy{yQd)ahhWSdx{ zTbf;)>IQXTvfXd&m89@gu&6R^Cfmkf!Ym~XVc6&#PH3^cXZuB)^8S9&?ln`4R|!Is zp~r8_%xwA&8|TegA3f)V(=to^0cXL@CUW_iNQFH&Aa*`kxG1*p0H&w0P!}f>{vfe; z)3k($*ykA^=^HgK%jXJWK4nq};pSx*x8qpbFzDqC_@tuefKIWX~I5qr<+3&=K&fo+c5wv>4;M%QPOERx{^ z{Q9u0B(sjhF5H-v1xz!@uC)77)V(#4UidsoLC5Wj>o#Ma# z_9d{evnJoTJ|ePoG99?{)5BAr6?f$Z4qv+Gx$S1n(Y(P-8JW4f`smB~_mh*>*PAPW zPUc+|GG0x_`$B9;WNC|O%|BwUn)*kdH8nT5W%6{HR8=prwIH_LdgaqkS&(Y|$Uo}y z)45+U11BV$J4GWx-w=!S+Il0Qt^cjy;ys_&&y%lwV z!BPG%a!e zO0I<2!C;H>Fnup<${h^WnP}Nv89dyBk#rh~goYM5Tp-mI==d58LIq72V!oxA zf^4m_En$mgDZO>k+`ZX&i?5}XRjw~!a^7GrD82X?aN{CdoTaorvrBm0k7}P)m@nn0$T)&8bcqXa%l^8PoY12QAOrgwQTg2@0num{~#Fp zzGJ~1*v=OJ-QE?z>|V&4rIvl6_#Rcdl1(79vUNuly7ve$&xrOiJV|H{B^dRMy}X91 zZo;rL5WUzR5Pk$-$m@E)y!FGKlWD;9DZM4DdH_4)ynO|hs7ZFW{owG{_7{odF^syb znh)Uy!WM1DZ}^=0b8YS4D1ZMb0Z7n~9d&9Vx#+xV$y!O?2Q75wr{6i8EO|6RS-Cy0 zLyO=(Y_JmUWa^a0-n=()xUm$nUNbzrUNZ#`J=I5JiFstXx00d-iyeSRxzoE(QCfMq zY?pD2Zs+R@E$i(~4o#HWBIqqE(;5}R=B=D%oZ8ji z;GR!)d>C{QRdkOhcj&G~YRso5_x>aX9*5mcQoYCPSq}tlC3V6brj6HBuZHe`~;Xq__9|FH~=*PjMsgPl8;SB)#*Qfq?;dOVcDqq+$tL z6Xtt#o&nR(|6PBuT$C|F5qEvR(s(yi^BC>{@h}(9g{s0k_d=bl;smMp2dt2SV~QfG z+G7i>1S_XxBk}YX5(?yGWG4HO;$h8d%r3<@7^&mHyf-tYbg3;!IA|zBDt=C`e&0T#m~|?Rp!4l*AoyFEvU0rDbVC z*bQ60ov@iDusfS_XjeY`Snb#YnIq|+56U4_TE&Uzi${uKYDpkGC5U^NbbD}Vj*B;N z4UJ(^x4)k+7xXDtdxAL6AWBq^xJ?LQjj*S=+q4; zN0EwSZT2;^EIqzjHY<;i7WD4dUtwS8350yXrcoIFme7FX68YkCT(SAW1G9pm%SC)+ z9?2QYs`Q{HY=oDwnZ>P4Cow|dOjIp8(dIm#lOOcn#TgMeQJw{|A1ja@TpIQo)ZXsY zThph-CZt}W?mM}QOqP5H)3ZU_i#b02MT1zfXv(xhOQ~K#)M-inoluMzDyrN&e~zDpxI#V z{q3~$-N#PZX0b;534Lq8=+=Aix%R?WxCVol4|1t%s+kTGOxDBOzU{U>6)NJ>ItNarzm%1sCV9 zc=SV?J!Q$}a|U~Z9U9L4y-@O&UR#i|2w2HKol6m9lxn_QB#@nO-g-_de&%gw*Jn{^ z8P4tpSklbiGYr40zD~C-*3l%2m_}U~fR$yJ?Wbi#ouaLx!gd%j8v5CQb@IE+`Bl=H zYLSE)|9+9A8?Fmul!=GW&aUk)oO^OPbY#lZKns*+-HuDM*!}|6E_qnQ%o_tIweMdh zE~{ss&#+^0qffgyRq#kH!0%-w`VyMD@DqGM=DUd1Nuz8z#NCz>G!^HG=ZQHV5t8DePoPvQ@Tic#F-={3Q%k?xli6 z;z(d?`gV7egm|b4*itb1MDmZ96g4Sk)-!lPVE}?AAi-Wv=xuwOIXQZo|3y6KRw@iL zwtR1HpLg-m?Ld&lXtmtbAkycz;$obLc;4YN0P}5n8AKI#z}08>BTwx|(4akySk|x= zNE|ebM;Q-Cysa(&))16b`H}sXG$FQ(&*hHAeb?4XeoT9KDnOdO_^jbQC{(VCy*+Vp z1`3{YCGkGF=d#FnU5QO?H>7$%NM>!rFL1g+3BT}yH=3Ztg&DCc84*( zr2FnHZK2v{i}r;Dh0OXU^+^QG@}6~Ttmv}AOy1$;4!vm=Lvg?!!7N`Nd4!5JO{%s9Utpr517&cO)^)FgqOv|8KwG>|W!*`upBHa%gb`O9a zXuPb$3z3nqPlsRxn2kUm`d10kM~HCl1jms> zj7TIzGGzry+q7KXO`6l~CxSTWzg4*X_zH;ss2Kzapi zDRCO?O(gs^g(kFNDe}K$%6ZGE4vS2IFMBuLpak9viQ!~fC7(&^g z{W`pL>mk*e<2=+AA@MR$6JG5DGaui1nj;%9rbjIVG~gm(0r(=HD!WqG)66$F8}-3? zIez7zuO5@(C6I+eFQL#>V5^9R2To;^qzOUw{Rd~igAa}or~T}rove!#3s5Od_0hcvh2%w$V0!5uT~-z zPsR7&Pr+joO2|pJ(LNJ4mlvLpRs>SLIUR6kk=C#JL?LH~=UVL#N32Z=7dPh4C6PQ| zBn}YOp1NBeSc7CDT#d-gVcZhE52tf{*4<4cChX2Ff|-77?F!`_a69(P-}GS|wTDBL zn6>dS+`Pe()mBA7nV3vcKR7Xeogir+r}PD>>MGpavwo53gh5a$ZIxrGOPki7C0Lsg zMANshEp^XJ7a2QMHlLG4ZJNF*5QJSTVYR(Otez^g`V;ZLDSXcL_l-GX)Y5 zrO1BerlL6|+%P}5F04^DrCO9-f4g#+?A*5(r5d683ExYb?XqNN$i^!CjGoitTAd_K z`^#AK=dsz$H+Xs=2`2rhq)~Bc38V*Yd~3{|)?3Y0beQU7!P)(Pq&&qc$CE>A^1Ao1Ag`2v)30jz1o87zLrP%%IP@A!6$=RiL?ZQ|t zVH!;bm?7P{lCcTwP{gS%EA2z1$24?zduEeDbBD&(x*B51fKtjTSPItIa8VzfsLGmc zPM}i%zQka*LcT>2-xhnbp~SoZcsIN#(~>z7g;U*a_ld3|Wr=%Es<+A$2_AlYAV*qZ zRlHS$7Wb?dsI#)Ie| z3JAP=cKut*Q(YNNhq7DrHk&G2Jv+U=D68#CH{l%_d>LTYwY^#p_(ILvV0>VsJZ6o? z?C8=K=h17SI3nrl3?n!jXEs}HDGei9+h#Ow8qGXZ?azFwv5rqPLHH;O8?vBzKr8zo zDH`RgA+TC-rhd}(YuBJ*bdcL#fxXDhwW={SE&P;BX^BP3xFDQj>+;lm__xSCsBx7?^Ftf`(W*7dG_dR?ulAtaO+pA>S+Y(Qu4DQo$^xWH^RiM6Fc33U=OI@}4L;pf@Ny;> zw2)v)b9!-x6$Q>k))qg5vST*&bCR z2wKY09Epjr$9~3HSU)cK{={unjbVO_BWMz#ku(3i-pd=fs~x4m**jU(5v zb^Jrq3o~o?BFo~}pKyo>y5d1)90(5SR^r{n!Y z8=Ix}c`Fg9G9)J<7=Uure6$}_ovX&C7|&7bC$O_ybhac(JtsBZnlo=huU*Xl0d*Sz zwOjB>zuv**E+To8eJSyg+H6V5VS;F(>~DYg4NJ{<#2GlJ9*!Nj>&q|Ac3)*ZpPD(u z^GmO%2aZKyto?-CD`t2vo%PkZ-+}Emx%tFa!(g>6YYcOH=F&5nh|{YVMzj6!QfD+Q zw7>Lrw^-3VTXnBm@iQ#`(f6_5rCW8CHcJ%?E0eW%CfTpgHlnR05!6- zDr)vc*~{cpnI>%9xEDH`pH z__5VZzr5nNUHdW_(?92fDfbl`mdCft9ehp8j8IjWLp_vP>}Xm+bcRg24p9n-%yD$d=&qYX(8-AsAF!(Tq75r6xP*4WfZv8qdO!{Q`p2ObCaGci==io25==e@<)k1!2pHmD~wj>1*H3%3(+1hAUu_ z7^*btgy5#IqO4<3APH5}u4$%Uw~;@6m`Zz5?z;Vqb--Epl#M)t{F6u@Y4wj{Sv|>} zal*IBB9P1kYyh8f-lXK`V|Iqm*^d=N|e*y*G z4Z@FMcdzXMu?_jnSdhKneGqq1-mdUYS0WBfRB7<%t)DCspBT<~`jZ{5xF1 z1_gc1T2?@pVg>{9*McI6vSYeCIzfRFmK)orm-O=3e3MBVJ!j~Ppnh%LPaTz! zwa|PEW3+^Z>8dg&Zumo!R+eAZh;R@_9nuK+YOoLO4$yzK1K!Mqr{Xeg6>Md5KzRNG z=LzQTh)>CVET9G*i0okT-#OSn9{|>vxF)3$2%!(!iYEH9vmc6Ha;1;xJ-B4cZ0Re^ zmh~pIM&tCHIL!oNRVxW#E}04>;|5Yq&$y!r01(B*)6@PN)@p(DrHnu7J@unyF6qSB zuik1%$95!n{CvqTItT?CC^mZIiV-P6#R zk2=o9xD;!#TzXXF%gMEhZKqd_?#xLsz@X(*wQbdGIWx&E|g<$#jagZ(bCne+bS=5BbtL-TmK)ejfT~ zG_F{&UR=IPdyBIKyQOeOM#>2SE(wwCNbdpNXh$ZB3APFs@$xkpV*#d}cH?u}2fF~t zPF9O?gFn24XRuqQ78#C{H6Mdu`ZgjZ=DtNQNDQ5IuEbJsoAB2wExl!tAA59>xl~}uz-1<7Y#{UnipBJc{w1atNy*{h;G1eGg8TNm+fWl1(S0IP^Qaf2HX>~YMDCwHTk*ba!%H*EDdA*+A9TKL zW#QWq8G0PN@o0B?@|IOfo23yO(PN|$zle9mcurdn}lc%*FnzZBYVSYLb2f#`F^{~E8 z1)qf|PhV*p@_Wp0NM@(&5XAVZpQ#}g=f_8PCyQb$$zi17BKz)VZzf_-Q;f zpEFGub~*k~XYOKUjh{ABWFuUe^%4P6G^tyPt1!>ugOyMD%2 zob~zS+qFySdK0J4JL1mYHCP?Y47_O%`zkzjFp`8F9$@n=XAUIA3fQwoS=1c98U>{G%)J8rSy)~?C<>|2;o_>?8v=fwyGsl3 z$19DdmwF&w_SoS4&4UjPf5a@jwG+W}9i(i@`>~(y^EF`fxQ;o1M71%fd4>bh#DIN5lW{_r(T7`Ecu$Ei!X~%?L{9pzPbZ-0r>EpaA3%x5B+;TORy^5WQjgqbU0-HZu?jW(*{!- zA;La=z40sK`Sqt&N`uNFA!r)vju`}z2}RCVxiZqYpw^inj~|Zv)z!^;;q#$K`lks0 z{t5DcHz$5^;N^?T`EvXh;Nr z9#F1Phd~lh{Pf-GH2!1Gu8%&}%Ruq{kJtyC(}x3RdOpoJ2VSJ^Gk}_-IDt&`b44Nu zPslKy<-~x|IKhf(_2OvOB@buW@9RPwFbk_dSGxX0dG-XcZHfh~*A>L;n zgqSmoj3URoD25gB%%9OVXun2Q6Zio7FLE$=&%VBN^}IEFrMDg8(#XAnUV@4GaUPqd z{)#KS0vL_o+6yAgpaJrL4yz0HPRMhuet+KYBf@ zh}mLb5T@A2RAIRf6J*-sS-?2{FM6-SYZGK(242`1?RTqgm`+W`7A{)U7Ey7wQ+%!r z5uo76_;%QTKTB&IEjOpKjTd46=B_KHVf9i_p_|V@T=3o-#Y6PmM4;xTZMRpYV@_YU zE<`lUY)2h8hcxt7MiL$#x~}TF?|8NC9=L&qo^^RKPj2uFF?p27QkaRq`Zw0G5(YKE zq;vtGNA_I#KuHyQespEY^R^HU!&Ow76^Ba+*9=7oq<4kg2YMHs$o-kCeFc3LnvF~n z>Fv9`S5poiZ=8=Z)qk58UDPkStv5tj+Cq z()M!FPdo9mty6}vbqD-|tzqWh$oCx|ruZ+Q%0!rA(T!5{z!#vQ)0jrUn_2RH7zo3d>GD*{$#ST`hZ5#x+m$okj%y|nLWy(6450HXEW~U z&SvHA@!PE;>X?&#l1&wUGV5!W)D-|latmxtD0n!w^6>|{73GU$=04r8qCZ}__(iVN z@qWG0x`({c*PH{ad0eUc#vo47=9D?LJ^1GQlf*?A?Y-gkGi(~K%=SjdtBC^o-MTEq z?bh>8<3JfhLZR2X*yTpN6=gVZToDy~%@!8hcuoV~?M?V@+{~JBN&6jUv?g4vIj9Iv z5$PK7!;(DuE{y=A4z5HpRB`Yb-RzY&;-JgqWGj-MGJtCiU^FSX%y1xBeGW(OSnNrT zRXO8`Y4dr?!{EBCtvrPfdjHW)ui&Q2$`sGsP-o;w1cL@zEPB=H+t^Gkk6G*{SqHcy zK2k@n(AaMt+P4MYV4&z)OB~JIkK6YL6?DBv&ly!NDAS~T# zTEdhI4XhvQXZV!bHvM9B-Jq9i?b)NScCkh!*yk={ zpzsorSFluDuF&5X9IOAK`E4~OI5eo_)1txD2Q;2K2&(Uj1&JlRs<8jNScL-=RK21< zYE_Ai`tX-ig7;Mffc#J88LaeCUhN^!+UVn@ia&KFs4a|?cCTD>V{m=?>hN56`zpg7 z63W)-gTU-rqj<<*)yJ^3b#2YnsoiS!Q7W`jy|TcoHD*sfBXUrEFe&$@2bSuUODwz);xA5-@7vS}9W%-ZX1V6kS=(~w9H+37rIp3yK` z;L4X8zwEGdQqsj;k9e@prLwj4cSoy*FM;A{ijV2hsq-_GQJ>s(V29){!VL>b-*aAe zx14!(-(yQOHUjy;eScx71j2~IqNh>+pia$&?I$Y++dIbdy?W}VcEeJcgnw`g_&f>T z4vRdP{lSoCYazz=^;Og8g&whwhS-Kpwnp{2$;_3t+YJ0+f%B(t8)K=1{F^H{Wks^B#8g-cHfQ%ieMR zIA}{gX&X;}J)nx@0W4fjHlO@`Ua7Y5iv`O_PIwnww0jg;XT(q(OJegR&uwulza!)z z)SZNv;!j4BI#4yFA@x^Ave*ozZ}Y-`7i~3E!f`m_IJS@<+_;OdL#;hMwvmWU=)vvG z7m1K%7IJ&_pkp}|giHefwC%@53drW?{D{MY>#n!&-E#%~QIlVzJ%`+bY1}K*m4l2* zVpHP0#`fxmCg@?7e)nR!ejKydKt0VtE+{pl^u}SP1(LAzyHJb^h(9v=7rQi|ogw%R z^_?CMP)6ky9MrDETAd`HaSZU$*slR~WpR_4fL%(x4F~Q+ZnUT>n)t)=Nd{)VZ-|zY zq>tp4iBZd_Ze446h6d5m(%Jv&QU9lURQI?4>QVpIqyDQ${a25IfCK;4qyDQ$0rS~^ z^{D@U>QV9s)SDQ`gXE?$EAA>gH5mr4WA$T4ndXD$M$V%8(5)cvHph@0t1l&MsgAMV ze6)Qh)+iQP2UEGyMGm}#(V=uZ;Rw=--4#GGWkFhNwBLd7#MQ#wLV=7h)`uukChK?` zi5R_3N}WEu!FA1u{Tz|Dog0P-iAe(K4OE=heTU}u=M10`p_+`}%b&!<*}~enQ-LP% zsF!6yV=6`j-=6<;gG;@;&rczig+y>4Ij9}GuI@Vgaa~c(txdme%tnqN4;l8-V|lfJ zC@97J8%TXs8j#^nqGLe0)zcnxs1iVv0{DEcv|i$}BkFz0NUB9yM&$Kajl?<3C(yc~ z_fca0Gz2W{J!N1Ie`u~Sd;M(m>D$BTAWbDvmovN9lx4V^za0P};-FRnbmbUPqo8{y z6GP5qEnSmcC>QUS!&CmRb}YO#%J2V!9g7;WWARw~*rS-J>J*S?f833&6B>vTQkRj^ zPXneT&q^FOD9rm=-P=wmVBDdBnvk}SWviCG4B(XDrnz~@Lqp$xHW;+4OyrdV1OQM? z%oGXyU)QT(PizdaZJS~5hK$S*#Hy+Q8At2+#TWFIEwbYKZ14nc2-7*@L_06w#V&R> zP|=#KuzsjIIfnqnAvf@9wY)cK(*l#w%VkyCs>HU{msi=_l2MmFR&4Pq_8GY~@}|RJ z0PIWDaWt^E!nEpU{q{-rKYEo|cI#J?oEhcbrF4p2j@_P;R;{B1nUThC!}JhfWK3Lh+2&zwn7C!_sQ_l^_vH`1)kF$Rgtv-{U}bmrFc z>b^t&eUYGPo66n0uuEccy~NK()n;k$T=92Y%LG}ok#U_Z+zKRJapT6Gd<56JfiI|- z41jv-&t58HEzqWdE2-yPpiAc-!MFjR3~_xwqt=m%S#AVqy}iTBU1AV|rREl@66LTU zMGK%mw;G+;e{T~{{6U+O74JVtSC1?&I4++sHML)7L3XruAPZa5k5*%tJ0}iag**S> z=~m-O`lnwJi}wXfozEc>x-VS1<)*iytvfoy@L?{brdbmGOr}0r@lgi4GiT0s3kSN) z4D1*P+XTAHny!4N)rW`g3Ee-LaaMx_b>jWOVbP9u3Sz-l9KQruvu6EfFv|NRc?R*W zWrhi#q*FYVroG!k$Yr@68f8{OF0u zc|pe++l_m&XIA^h-b_^1J@kP}UsMVmvqcnhNg1Yu>%H3B3@+Tbyn&BZzfc$&eN*lc zXi&6#`KM_?jzjJlOBW5qOCKO-)l>SHCYy9rn~1FmZ#=Rr>PZVr1cd3C>F)jQ(KFQ}h2d z3{Zg$^X^Ub;AnW0?#p=dlGoW&(zH$SQ0hoO+<`-COLhoMT#`P zn?7qxa`ei2^$~9Kt)8|qoJ(qEggWzvdWoGMT6w^p#2zbVfVaEqXC`dN9|heKzH~de zp%g=p<}r&w>4Zg(oVXitu@p59cCBcDsKdMKF60lxkj$M9sBU^)Z~9Z-1Yf$f?_#=o zkIyC(OuOx0++Ewp?yi0g&3c=+GWLgk!(Qss8{HhK!J@KfhTg2kN#EhNHP`!CT5W(G z9U6Ih8}^d8?4DC8Cq;CaZLwRh(i3)s!|ig+MhRC`?H3jf!n@W^bEe;==@2))=dXr3*O`XuA)3YR$KM z-Yr6td@9EF56%waxJrm8EIV$W1E8`^$L~#0>5>~nR-Rzy&)ikivr?^&Qjm41M88gg zA-M@GmcEY6%p7x67?$niB@^N2n_uws%RYGj3@@*XB+Co=79KA-X4p$|Uf_?cR=zk? z3(0Ee(v`oEm?uM)I@ zjtQW*kF7lC3v4ij4wB^XLTMs%Jtm)P6GtbENkbpLyE!JZby!Sbgz4Qc>+;JIb({uGLN z!lS%=!lMz*f|qq~c*k#eedzgDxKJUQ7b|oK+7=M+&fBP0Er9lld+F$zYN$~G>s87z zVq^?v%X;V$EVe)#(fUVU^PCWR!tg8JIP>^YCzakzs*AJ26PD9ONKeezDYlliSEYwR zP$ripyJ`ccdux=_QzV6+B{Ov`1SUHD1%FRQ;A-OI#g++b(w>H?FAirScj;pKu6wDZ zKhH07x#q)trWdt+HMw<1KqPT$v6&e!Rl5M^V09!g+7ZI|&{*XU*ej_=bLni$Z?M&CVCSwf^o;_$bAv&#U0H8@e9-|$de~N4Btl?@!MoM{5 z(z5|45~#as8&0(~jK1h*muT^S{ zN?G{s993+qTA)$i>Vx*t#h%knk(YQBi00lbUV%p?yri=}QiqH8lTFztNRVRuLeh-5 z68qLPaRYkay8CJrG`3I;IUe3DE6w8v|Is)p!k1tzwi3HOh`k2Ov8N@Jxiv->tKxEf zIO(R{djvbjud8TfBafM2Xq$+=N={hpaQ>JN>N6&O zXCUWJg)ka!gy1k$i?-ic7ST7?UY<#g#XjdwQO8)W>Q&8<-KTa}I#g{Ogt2m*h428G})D?e- zUtiC?3-sy$=fu7q{#FH-fDY-{oQU%$=*HJRk}XEu>MMK|+v)rH7_(60*Gb}bQ@4a_ z4&brMd{^n^Rw2ZtH};NNZ8*;DT1)%8b@#Z~58@DcR%td_Z51W()qm=@lsx0b8iT_In}@PmQjVNa>;>Ktm)xc@xv_&>pp6x+U1?Qjj1oe%vK+$#uQXTaxyT1+N@UQMKF4jJ?;%Qez5RBoWhE4i$&*qP5aKRFHu z6$*Q3&&<*U#M_=+jLOVD?;Go0G|nRxfNj4kukYFkE9YnNmSA7A)UvuNU>)+F`sDIB+H=VkF6%cE@`o{YdwDBT;7k zVVY(s_xBd;O-3mra6fAZU}cFuZ+^&M!CwBWy@bGb%k@dr8eljAQa}w?$9$2zpN_*8 zXkpLQ2k>Wdqq}m#pmrT%SD4EAP&YYo8;cj`b42mHlp(OF#A>>kfV%KQan7}V_Y^Lb zuim#}^#Y{Yllxz-@Xa9?-d;a7-%|YT)%||Z30em#U%tV0epn{VyyqLHxu!bBiY^CC z4Q4{ciz{C^`&(N%aSjxdg+JjH^zxYes56jzaevaq+8ws86A-J_>C;{XOCj=EX0*a{ zK+H%7Y~cEzJ{VZ(=kTze<2&$j|GwekjJe&;80Tzr%RX5eb-VVZj}N3Mg#59S~@`y zhSi=T5@OUYD{$4do1~_E&2$If0-k{^=2#GJrO)dJ;FWKPBEI!Kcgl^S| zFK%;)Q>~!yaOk_{#sR-h1F-}dWUcv!h~06{k^2VJZsGs+oFm<}W0vAHC1mw_%Qryf zgzsd&aiVWUZxu55>;u436BE==q5TvW=v*+$cjyiLTEPf^T5q9>_ER$=k*|Nz7@moc4VJ+bmXSL_o7EaZc1a zZtcyZ!bw-&zZJW-)2F@i{IvT{?llC{@1x$Zu1W-sQkc&Y?D=1v~ zIP!Id#Q+umHBZDrY^))hD0=iZU|`2;WiUfK0QyS)t0xXRkN;qj``Zh-fk;M|{N=E< zTw_!SWV(VH?M%PAfIgyX2t#}t*?fM+Wdy`e!Py3`E41gvl>%#{o}ebk*meUNz@F1Z z^MB)EK|26FmV>X^aS|Qg@+Q2 z^fESH2mANuaG>AE{n+H9_0FfxeNK(D(R2&far~9)%cE2RN+AAGTbx&Sm2^OOBBkaGgp^qvE#MUD$Uhw-=XGgca;NzQyjt72Y(Yu$ zI`z{RvhwFcdl$~-KaUK#YEAsj8YPmGzRMr8M%9Gu79*-k{V5U1N!$X0jfucG{|)-4 z{nZerVB{08dZ)GPFQS|5k?|o4ALQz!Gd!(1&Auy&4uVM;-1{|vX^>u z)fv{lJ`V~i>eND{&?HQS-%&rxmU-j+^4>6|Gq`xW#2={Q0VYMxTfxGtrlNqup)v)Q3RACAiY|UCLqlyT@aBPdJho=3xbFUh?EG@drjyOX+r27 zLa0I@G$~04A>rFO$8+BEo_oJ>%a3o2d&l={jy3nn+H1`<_sU++Uh|n8Uonm;yx}G4 zwe*r9O9|*c!m@*nGv*Ri+Q!c>#B_|G;7O2;?*>VZsN7|77S)kh_^Cr*XHe$*)cb(7 zU2#tvOZGQiKa@$oBCRer9)O7594leU&KYd@vg>i>Z%4R%0$p0yIWuSV}n5{rR|~_`Xara7ubiIpzf$1pVi|O-rvwD{VLz8PE}I5 zb4^}E_wSbKls7MoezFfO-FY!7xlf91G#Ff#TW??K)pF{$vzMg-F9{C-n zJ9{^Kya`AR@#mp4t8yS$bGUdlgXOChVEI~QzW--T<2H1HeoMj-c^KmvyfsVL8# zsc-=K+lJ2u0MlK57A_iXeR+u9%vHDR_EAL4HHlWU5`3btcTL~$LK$oksv3Yj4$F+` z1oKlUrQOrN;w4=Q#J7lApGm#n?SBAx9bQoW=+iR@4k#_;RSodymwKZG-&s7faSwrI zRCIdp6J@fc^4`liiGU{?GO3vp{J0SiqHLok5p$Jcs{iaL+P?4qI*L{y;cB02R=n>J zgX!ze4^qy~Ow=C{mBIcz4G26Es_n}PB()~i>!wKQL}e``7WEdHPDJhyMW0v!Bwvk? zpVz{%@iLDv+0{HoKWbYG@8KS8z62P)!e-LwC-G~#*_4y+bkQc=mV*wGh2`MErRWri z<*sS75-})W*_hz)!Ey>`Rqa!D$%=1m^Wre;iclag_falvOSAS1;JEXL)EivQ!v#27 z4SdbVs>367Qtvl1^GKS4b_mK_I^0xx7Tz4WpFhMO`}my=#=x9#DmU8dbo1t1`i81)`LaR*I>mhI zGa*W2(u4L*GiEU$^+^2CvYLC+l+jtVGHNXQY}B&tmM!HTy^JdKNfQgIv&h2X&OCFL9tgv?;;@`4ML84^#}x zz*RiY?x>g2-;Nga8#Fcd)|#&SI@XPl&HBp*6$b7vE-3;zqYYXrBMb#sz8oJhX2g#< z>H!;pAqL4;3vYP=*(W}pv9B-)F7DSl`v^N}>O@jb1!4=tZ0tP3!w6W;bZ``Q)tG7Qk~zR`0T+Or=-B+K`j{ zD)KVIKFjqy{v2YJO1vGl*6w6(J(s%D*>LAQZV#)Dw6b( z|9TEGaS}D}t;Xt`X`_Iopm*@bmq8sbcE}>Vz6QTyHTA!3CjAkq$$OnY?Wo;cH-OYB z3vpk;iICOq`trWK{wFU=Vd$bHuq^!(u^aAO&9`xP)!O+NWOogK>|`RI*FrVC#$tJw z2l6n5mTERf4yCA{=1Q`c&adBQp>dX8QFi0hLqCF%o&N{euB|4q^Zyah?*4xrXm<#% zAe)&Z4+Nq>I|(4Oxpk~vyOyr3X}$zm;aRq-7y^TFDION3wR|;`6zTcg z#%wHqR09AG$aCPNNhNOLYuyq`@fEz?`MBpPb%Qx9KP3eoWuES08kE|)cC%4oijLZP zRV>)DbR&|V#3g9ZZF#F;vSzoe^=VeY^Q_AGqDTvtqXuI!4a9%v?pxWLoWQl-cIWfs z-79#QVXPh>6CUYsuB88!c|+?Po*cDy;;~O3dR`47)g&Dfzq~X}>oeY>e+Is63*`j% z31o+4mur90@jwcAjIgsE=u3Cd@q7LDG}G<6&(|u|2wd4-$_4`8Lh9@vr#$E`9Ih!N zonD(+(>U`HpOB0q*6)%=?Ne|_QFqFQomn+$n;Nx3-|UZhVmbNYetlMJT)|t~>(|n{ zuU{{5T&l)}cQr-bkth0C;-VCsC9=uZHva9UJS-KmP?cs^fQTPcJ|9U=@>noVg+9+7AzGF$Tn!Mb8cP%;aC+|yB)tZ~HMd$4- zFo_$cMKz;q2PFLzPE_jl%bQ*O%|!+ipBP|9s=@-*CnoOPbdX#+5YJHo0||MBjT^I^ z6OZveH+n`v$5%9Ztc%4oplm^Y>6&N#G0XOC zD#D#A}fLLpX!Lw17;Bz*8wG1cA=}(B$=fmSq9JbbnK3kaI*CZA6Tt?x;hw59YDy6t1aG zw>UPSL3tj*l_6y*k;I(kqWpZbPpquqrfRXBZNp_xk<8WM$K0S3$G=H~yp#8O4h+t& zvYw#-@J&7)Sf{X=Vl34e+IbE&xh{}=h&$K~EkN4)PBVls^?_|O?JM?kq<++sj#zaIruBE*i^~s}%S9K(026}_;Iav@yKC`MSHmI^6yuzW)~566ZG8%;t%LXSe7tj9m*hjSY?nmvE{ z{tEhwxis}{l-udrR1~7r zjRh#qk!__PAi>&kMErQ3XI@58Q3%ci|=On_(`3WGcj~q@j0EfflWPD z{fQQOPgK=jiaG50nfK&L#oUocUCkDl=?LZ-QO_D|(rVw<)J|Ae=w#NXjL1uOsW_R- z9DxO$^{vjdf!NHSX_U!08ndhYMNYVdy{Vre;kT_4%PJ#BN$&>iPupvt@S``^xozv< zFJ^cqsVpGSFh}135D3J1^qcfkgTXS?pik^+8||fl=%U^U6%dH>+3c0X5H-u}`}Tw? z2viyT?h;V@=_}5{eSZjvRTC4FUICzuThDF*I?%09H}+qmvM_f}kroa~Toi;jkAHoK(kEK zXEY_Q`zi~aRSX=A+Z;&jk!bcBHaBAiuCYy@d1Z(icx=B9^mY4YC7=TF->78nGd9+C zL09MUnRRJ&kA`kpN7N62KoLfvPUmzfrIkCf+8hwFQ}gOOSkVgWc@C#%J7~p*d;398 z*I&4?r!S7;indQJwvj4*#12zVBq$ot=1Ka5_U|t@m_Q))H;gI@7Y4_T(ma6nDV-9F zRR?Cxm_S$c0KZV$DjyI&^(Lp#PENTL=oRs;8~cycAcJJb&b@*wh~9@qY#`9N*MQP? z43}%>O1HAi(Js0)Xq?m1#pu9Gp!Lc$*_4=hoKlm~?02bvDfJxW{AvfRG^^>F{-HXF zWrRL3Yt0dE?0;I<%B1nRz}VFVJSK3UmOpdkS>%|9T&e}}rp2niJKU)~GSNeM@9Q9t`%_H0!dN^jFslH{HI<&8oC8!$F%WpCcz&%6hQineEa| z#D$wIlc_*q|hXQr17LtLyEVMn+wk`LmqAPIiJ{7$4so>0&P^ZuDIzFbeDJDvSUmH2* zyWE>~H{*S4pj>~oK0QjmqD**Gqhd0YFp-hGXajo7bPVdr`{c86cCt>T$+`*RSAqMf8Q-aC z=*sBg3YSo=&yh{+aXyGY3kCNaJdVuj5Z`RS0PCe~i6+1&_+~8!(MN~{8rN>y1M~%D z)A_ExrZLbGlhFvDCv8X=Hh|LHJ+e_DGeiXoUc%W}hZbwvn0Sg6^Dt zj}(%L$AlF&%9+|VoZbZ~Xb3LKv8oHeYPzbKzI&q#nonn$ziIbaDXrK$KtysqCzkk6!#+w~)G8 z(gPTAvHGDZ+=JVL>Hw!k;3S>nBY%9LpKdQ5uY()8B1j*c2?-fdk&)L=7Kb%eX4Ku5h^}o=va;e{@gbj)b3lg%!Tb0y>}kE;&`eZiMcoR#(%>WA|CTCU>It=t}T| zwcEX_61gjiTrrf8Z!6eJ4fu-8rYkCey;}L_?FW`rm*f0rh{2!j^0#6`piH%Mf?Mde zpD3}p{S~?JYts+O4>qGkLOJonqZ{v3?xTG_TD5`)se6xaG|Y#{Zrs7kmNzU#y@*QS zkZs$fb};4Mf4rw6Z;?2AB`9zp@FLbzVR+*xuvpP;w`1O1vrF(=jGCYa(EV7?WHF|TwCr+rq3>a)I(7M(dxriVHE@mntAjr~^2pm){ew*| z72Mm^15`F?eghZP48@MG!$qq+d!K^~WtV2OAhgK^UG zt#{C(Wjfa~`nUKY1-5f@aJMmQ{2r6+lSDs*MRZ(3v2x{HK?Ney$&T(M4sprlu5dKd!mAC|4pg^!sni>D z#nFsCU$7!pX{hC3-26BkIp=kb^;7ZLMarn_r3RIJZVH!C#>-4aFQXxqsl{m=wfLR0 zyl<3vPsZ1(tFHH9uG|5!Y|% zt2^-p_?PLe%vNu61-EACUa9b0JmITF=~Jvb%uAaSjq$Sf-f{CcGTy6W8(daQSyqsI zQvS{+mRI2GBHNU=>Q6CxxTB_&eR-Ik$?n7wCOPRn*N=ob4tcKZ(V}#G%HYas^Yw?3 zB+^w!be=Mm*o#W>Z1$3h&aEyk;e%+J4wx^M+*|ei67Y72TnX{wgaa-zCoLd1PMx zbWqZ~FD{(MX?+9caerXHQGK>v#r)n-Z1+MjL|WlkOIwWKaY&)L`{ULozsWtM3-(yN zxD_(PDGI$eva0&w_uV-4_)C}d9WSNzCZzvsK?N6gxwk6Oe$$hC*i+1kl(mcp@ljTL z$s-WcPb0nqr;e6PG=;#$ba#gfEzQJSaW!<|^>3OV2pg#7o90k;%yi;fZj_N};XZTOag|pfH(h@kzTZNrEbH^mttbUlUN~Ez zapHyUg%GZcu}?J`#M6g{L2T`Zp~iUuH3^pOszNo8B75HU%jYN+PTOKEAm-}AT4YOT zOX@PIWmVxBiW)aHQ<#aANzxFqoX+qQuev&kDNk{T5dXgK_}>t0P0+f7m$$FrDvDN`n2SH$-f&EZxGE4iv z`rQT^xz$|GvusWX3NH;C8?81}jMpnUDI^9wTeGo*z4dRq@Bm-J#lex$4-}!~__0p7 z_nTUN&^h?-jW58)5YKg{l}vARH}#InYlw5aC$7gnq9+>?r=`tXIJ zjEN|{_{d=*u^;(SNfnAyr`8gTW4u2TEjxYj{X@g-0l^vt1Wc`AyUczpNsinA?3LYX zs)x48>V%zX-XYtPu(8ToEu11`9z2yScD}#UOV3okbLil;qIZ0Ly{95FkD!(uBt|Ed zURC=#_R#x>Cq3>5G}UaH3uEH#CzwGelmyKYO8%1WbL}VXeeOh%X^e>M|HiWZ8_W7{ zEbIR(Eb9*t%Mrk>_^+q^V>vg63~nR`H|KS3#au!D2B0^pe_|*^RQ!`oPsrfN!#n^6 z*-)!wHoVhg!J*ySWm#bdpkqG)AQS{ryY-h)`xLI7vjTxo+MGe4ce3B4-(?0pFsJck zyv(Ck*vP_)_JP#=7${ZuYH%}3Qy6J}5$5;$A!?w~x_L zhEBL}ArhbNFUzq$Ah!|0=Vy>xP?e}M3IbVk0$y%cZ-OfAwXsOeFaCVKYJ96C)@>gNf$OSK>wk9lSFc~>=H?n3e!B^b#X4Qn80f*@X1iGL$r8!YByn+i zk~TU7R5=F@b-G_x;p$Q|2D$bziIyEaYR{Mu&NI_za@tQVuA=>cz=M-qz@2K3{)w*w z*BNMQ^!)K^;_bySU;PnD;5Og*&28U}TDd}~E=4sLUOFq=T>&b-J3f8pd6Hj_>EN2! zJ?fZGLE>L(2YNo4&JSBbjxyZmY4Kk^`R{$!9Q0K~)6v~Ir2}!ldNy>(1!q}6NKVWc zP27r=Gy`1iX%Fjg@na|4n2+yvo#5W|amI52qMS1a08-FVVDIvW;Cc!$nOx?J!X5+4 z;V1twZPnK12DaYlna8Fj)Z7B#Cu$Ps&AB2DjrH(B_O3`QS`}qy4BrFvpPz6R9`5{b zY;auI+Z)RmeK_+!+nV~2y&;n`jeV)x(UT@iznJ{F`lf0Mz`l@N^-d z{QCW!(63qkC78r&Yqfc}K8!iU2KUUW4B=`^~B+F5V}5fZc-&nPHsDooESIn{Sd9-IDE@I}CzLVSpm24O zhh9EDj`JM}mxWW~Spn5m^K=J5{N+BbC{9HVKw*90N!71)<98v+S*WODSX(`rY#QKl z7O3#FhnZNl3VeQ8M29bqBVS)ec>`LI2hV_L`fi!TAn%9j-`!hHZtLG0k~7zT?T~UT z;7cQ8RG`rJsdE9(WXfB;hoQ;QH&yzMngKotI#+XKdMEr$9+lXFpCv#M*%ag{K&|HU zywMwaqxD3;g{KSS=}{9h?_rGNZy$0PrAK!wSN*S~A{8^(em6d+p2)*W&$D1*Iy;m^ lfz0G0W`Iu^AP}qi4xD?Fp47&4m0>xMmb%`p!W-7l{sYJ6L;?T+ literal 0 HcmV?d00001 diff --git a/docs/media/image4.png b/docs/media/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..44f669b3b310b03abd5e57cfa478a1f99cdde88e GIT binary patch literal 14515 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 R>-%>=MOihOD(Mg3{ufdhG@Jkc literal 0 HcmV?d00001 diff --git a/docs/media/image7.png b/docs/media/image7.png new file mode 100644 index 0000000000000000000000000000000000000000..32172d7859178cc50b1fba3c4e977a02d843b0e7 GIT binary patch literal 88111 zcmX`S1yoeu_dbjYQqn`0fOLb>A>G|2UD7>t*U+7Uv`P-$-9szg-7$3Y4xjJu{j=6A z?uB*FoW1YP=h-JxSy2iNg$M->4h~I5T3i(l?rk9)+#6e@_prYZ;g%l5e!X!~l@f)k z7$ez--N0LlD2Twpf#XmgjS*n?$d1xFE^u(@J+GfP0}j8;;NV`0WW+_(f!~gPAQ%Cp z$omYPJ)Asxf?K?yDF1$*6p_1mu-g3=3l+uv4mc=cW|jcqdeCBiqM$g*x$wE5X`l{) zdy~)B;oj8JHVrvB%Ib(w%YZ`(70pwo1E5FE=0A0SXm+`>oIJB~ZrBZ!H+6&O3(Hd$9ho*CBM z^;65&@^y{0|GwoPiZ=$0=|zPJ(~4FIGa@J<)gUD3EsiZ0?N=muEqv0Y4`UO&wZ)_8 z1m*7O@?2kA4_wX9uHD6cVU{fGd2V#uW9{MEiR{)ZK(t;3@8b=Al`p^F$ferZl52-a zq5p__i0%t2)S3(RjqZCxLwr~3{U|@uM=ZjfNHW;2n0iTbD)@N#`SS*?Bup&m>uCY& z(z5JaN07eQV9&u({6oE7cYXae6S|)1cVcYMi53Ji%*qj(=DaZdf?VFbsi5NE0z>I+lq-lJi_)&Yg|i zMXT88Z;rm%)_e#X!o3GdXRy|no=&bDB-Cq{vLcv7p#F$gfi}vtTln$0TkEkRJvYQ; z=mmVx8r{V|<#beh$?p+_;Qyh(=8$7Iw2UPqnrlsmw~;?*E%R%ni+%>3=JQ z{kKwdqykg#bfta&2|NY5oZMuQ8r+J16>6Br2S+9q{<8VpaH-@Q}(DGLUC*qThX8?e{i&zfJ z8TK39x0-dD^#sq>byu=pOa7`XHI2Nd>VyQ$E8TQ*Hq-|u%?HiP{e@myf~&xd|9J?J z!2cG2PM$geFG}D$O}cMOb=Q`?`MP^Fc5mySohJD=M_=4J)fyV!lv()PN(rnyum9ZC z`%iyn|LL#9Y-mQl^>*|Pg`{2M^@hdmR=XnGKNA(N(zXvy`_w$yDkufpmu1XU8Fr%^bL>TxJx5TH%#B z`;oDut)#2}1RfHj&SuX)3Gn5;^8>civh0(E5WkmN%Nl6eJh91V-<=hkb7orwU;efx zI|$@8;UA@10)MyUYS)hch*R?#<|faSUxSvq%y&talZ<9GizrfZfhn|E1I+c?;+;Hs zpXWtwU<>itz1?x|p~`G)O%l#~H?)wpf1r2teX&i`rt+1_KF{hHbH`ju7tdV1q(hGX zx9^bu0*dSmIfH~I_zg4VlH>8(0#3n}(RcP8W+llqu4hLNUZGDv8+%daAZv$C_kAbf zuE^pSY#!Q>C9vR5m1)I)uMGP?vthV1*I`KcSV4}95Ii=--r#;~=f-e!XR+jIjyjNS zn$f_4P~HM>Jdo$p`e;{1!u+SXaWC8HW-c{gKB7_+*+6fUNBz~1{*nH-+@|y>*TyLt zq2q;f_b639iHi+ln48~?$J_U4=mWn_L}HtybUK8|dvJ%;ydA7paI!V{#FI&utGJql=VcdkM zQRfYn-Fx^6!`BRjS-W6R(2s|Bv)K48d{Hjw<-Ob|2*@`<)jSz-A9>9`5@I5+ImYS3 zU7zM-6(zFb*ck9^c*n66@K+`wJ=Ij)B9L(Buj3Cb35#~ZaX&C(g$cPo6K@a&Mp^GJ zDt6VO;~OR}ehXMI$(N9t_+7^ex?nzo{-ffb{ls}*^5}&WOn!@>)V>;bBwAw{rLp

3Ng#q`04hggoBB#RE_B)ciB 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 Gi2pD7k|;s| literal 0 HcmV?d00001 diff --git a/docs/rqes-walledriven.md b/docs/rqes-walledriven.md new file mode 100644 index 0000000..33e932a --- /dev/null +++ b/docs/rqes-walledriven.md @@ -0,0 +1,1315 @@ +# Wallet Centric rQES Specification + +> This document focuses mainly on the interaction between the EUDIW (and its external component, Signature Creation Application) and the QTSP that manages the Server Signing Application. + +## Table of contents + +- [Wallet Centric rQES Specification](#wallet-centric-rqes-specification) + - [Table of contents](#table-of-contents) + - [1. Wallet Centric rQES Architecture](#1-wallet-centric-rqes-architecture) + - [2. Wallet Components and Corresponding Functionality](#2-wallet-components-and-corresponding-functionality) + - [3. Wallet centric Signature flow](#3-wallet-centric-signature-flow) + - [3.1 Service (QTSP) Authentication](#31-service-qtsp-authentication) + - [3.1.1 OpenId4VP Interactions](#311-openid4vp-interactions) + - [3.1.1. Example of the Authorization Request (Authorization Server to OID4VP Verifier)](#311-example-of-the-authorization-request-authorization-server-to-oid4vp-verifier) + - [3.1.1. Authorization Request returns:](#311-authorization-request-returns) + - [3.1.1. Additional Interactions](#311-additional-interactions) + - [3.1.2 oauth2/authorize](#312-oauth2authorize) + - [3.1.2.Response (output of the /oauth2/authorize)](#312response-output-of-the-oauth2authorize) + - [3.1.3 oauth2/token](#313-oauth2token) + - [3.2 Credential Listing](#32-credential-listing) + - [3.2.1 /credentials/list](#321-credentialslist) + - [3.2.2 /credentials/info](#322-credentialsinfo) + - [3.3 Document Signing](#33-document-signing) + - [3.3.1 /oauth2/authorize](#331-oauth2authorize) + - [3.3.2 /signatures/calculate\_hash](#332-signaturescalculate_hash) + - [3.3.2.1 Request:](#3321-request) + - [3.3.2.2 Response:](#3322-response) + - [3.3.2.3 Documents JSON Array:](#3323-documents-json-array) + - [3.3.3 /oauth2/token](#333-oauth2token) + - [](#) + - [3.3.4 /signatures/obtain\_signed\_doc](#334-signaturesobtain_signed_doc) + - [3.3.4.1 Request:](#3341-request) + - [3.3.4.2 Response:](#3342-response) + - [3.3.5 /signatures/signHash](#335-signaturessignhash) +- [Annex 1](#annex-1) + + + +## 1. Wallet Centric rQES Architecture + +The following figure presents the current conceptual architecture and +the components that interact in this environment. + +

+ +

Figure 1 - Wallet centric rQES conceptual +architecture

+
+ +## 2. Wallet Components and Corresponding Functionality + ++ **Signature Activation Component (SAC)** + + The Signature Activation Component (SAC) receives and responds to +(extended[1]) “OID4VP” protocol requests from the Signature Activation Module (SAM). The SAC will +prompt the user to share their PID data (enabling the SAM to +authenticate the user) and will also request the user’s consent for the +SAM to access their private key to sign a hash. The (extended) “OID4VP” +request will include details about what the SAM is requesting to sign, +and this information will be displayed to the user before they consent +to allow the SAM to access their private key for signing. + ++ **Signature Creation Component (SCC)** + + The Signature Creation Component (SCC) acts as the ‘controller’ for the entire process. It receives the request to sign a document, along with +the document itself, from the Relying Party (RP) Application, calls the +Presentation component to display the document to the user, and allows +the user to decide whether to sign the document and select the +appropriate certificate. The SCC also interacts with the external +Signature Creation Application (SCA) and the QTSP (SSA), listing the +available QTSPs for the user to choose from. + +- **Presentation Component (PC)** + + The Presentation Component (PC) displays the document and associated +information received from the RP Application. After showing the +document, the wallet allows the user to decide whether to sign it or +not.” + +## 3. Wallet centric Signature flow + +The following wallet centric signature flow is analyzed in this +document: + +1. Service (QTSP) Authentication + +2. Credential Listing + +3. Document Signing + +### 3.1 Service (QTSP) Authentication + +**Before initiating the credential listing or signing operation, the +Wallet must authenticate itself to the QTSP. In this signature flow, it +is assumed that the wallet has +been previously registered with the QTSP.** + +To initiate user authentication with the QTSP (Service Authentication +process), the SIC (Signature Activation Component) will send a request +to the QTSP Authorization Server. The authentication is carried out +using the OAuth2 protocol, complemented by the OpenId4VP protocol: + +1. The user selects the QTSP they want to use to sign the document. We + assume that at least one QTSP (the one offering free signatures) + will be configured in the Wallet. + +2. The EUDIW sends an HTTP request to the `/oauth2/authorize` endpoint of + the Authorization Server (QTSP) to initiate the user authentication + process. + +3. The QTSP makes a request to the OpenId4VP Verifier to start the + Authorization Request. + +4. The QTSP generates a deep link to the Wallet . + +5. The EUDIW requests the user to share the necessary PID information. + +6. After sharing, the wallet will be redirected to the redirect\_url + (provided in step 4) in the QTSP. + +7. The QTSP interacts with the Verifier to obtain the VP Token. + +8. After validating the VP Token, the QTSP marks the current Session ID + Cookie as authenticated, and redirects the client back to the + `/oauth2/authorize` endpoint. If all parameters are valid, a code is + generated, and the client app is redirected to the "redirect\_url" + specified in the request. + +9. With that code value, an access token can be requested at the + `/oauth2/token` endpoint. This access token will grant access to the + QTSP Resource Server. + + + +#### 3.1.1 OpenId4VP Interactions + +##### 3.1.1. Example of the Authorization Request (Authorization Server to OID4VP Verifier) + + + +##### 3.1.1. Authorization Request returns: + + + +##### 3.1.1. Additional Interactions + +With the values from the request and from the response, we will generate +two links: + +**1. +eudi-openid4vp://dev.verifier-backend.eudiw.dev?client\_id={client\_id}&request\_uri={request\_uri}** + +This deep link is generated using the **client\_id** and the +**request\_uri** from the Authorization Request’s returned value. It +should then be sent to the Wallet to redirect the user to a page where +they can select which data to share. + +**2. https://dev.verifier-backend.eudiw.dev/ui/presentations/{presentation\_id}?nonce={nonce}** + +Using this link, the QTSP can obtain the VP Token from the Verifier once +the user has authorized the sharing of the requested data. + +#### 3.1.2 oauth2/authorize + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePresenceTypeDescription
response_typeREQUIREDStringThe value SHALL be “code”.
client_idREQUIREDStringThe client identifier as defined in oauth2 +[RFC 6749].
redirect_uri

REQUIRED

+

Conditional

String

The URL where the user will be +redirected after the authorization process has completed.

+

If omitted, the remote service will use the default redirect URI +pre-registered by the signature application.

scopeOPTIONALStringFor Service Authentication flow SHALL be +“service”: it SHALL be used to obtain an authorization code suitable for +service authorization.
code_challengeREQUIREDStringCryptographic nonce binding the +transaction to a certain user agent, used to detect code replay and CSRF +attacks.
code_challenge_methodOPTIONALStringCode verifier transformation method. Used +to obtain the “code_challenge” from the “code_verifier”. Defaults to +“plain”, but the recommended value is “S256”.
stateOPTIONALStringA value used by the client to maintain +state between the request and callback, as defined in oauth2 [RFC +6749].
request_uri

REQUIRED

+

Conditional

StringURI pointing to a pushed authorization +request previously uploaded by the client. (Note: this mechanism +is not currently supported)
langOPTIONALStringRequest a preferred language according to +RFC 5646. If specified, the authorization server SHOULD render the +authorization web page in this language, if supported.
descriptionOPTIONALStringA free form description of the +authorization transaction in the lang language.
account_tokenOPTIONALStringAn account_token as defined in [CSC-API] +section 8.4.1. (Note: this parameter is not currently +supported)
clientDataOPTIONALStringArbitrary data from the signature +application.
+ +#### 3.1.2.Response (output of the /oauth2/authorize) + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePresenceTypeDescription
codeREQUIREDStringThe authorization code generated by the authorization server. It +SHALL be bound to the client identifier and the redirection URI.
state

REQUIRED

+

Conditional

StringContains the arbitrary data from the signature application that was +specified in the state attribute of the input request.
error

REQUIRED

+

Conditional

StringSee [CSC-API] section 8.4.2.
error_descriptionOPTIONALStringSee [CSC-API] section 8.4.2.
error_uriOPTIONALStringSee [CSC-API] section 8.4.2.
+ +### 3.1.3 oauth2/token + +Requirement: In the Authentication header, there should be the value of +the Basic Auth obtained from the oauth2’s client\_id and client\_secret + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePresenceTypeDescription
grant_typeREQUIREDString

The grant type, which depends on the type of OAuth 2.0 flow:

+
    +
  • “authorization_code” SHALL be used in case of Authorization Code +Grant

  • +
codeREQUIREDStringThe authorization code returned by the authorization server. It +SHALL be bound to the client identifier and the redirection URI.
client_idREQUIREDStringA client_id that was send before by Signature Interaction Component +(SIC) during request (see Section 3.4.3.3 of CSC).
code_verifierREQUIREDStringThe String value (nonce) that was used to generate the +code_challenge from the /oauth2/authorize. See PKCE resources.
redirect_uriREQUIREDStringAn URL that was send before by Signature Interaction Component (SIC) +and where the user was redirected after the authorization process +completed. It is used to validate that it matches the original value +previously passed to the authorization server.
+ +## 3.2 Credential Listing + +**For this flow, it is assumed that the Wallet has already executed the +service authentication (see section [3.1](#31-sevice-qtsp-authentication)) on the QTSP that will be +used.** + +After the user has agreed to sign the document and selected which QTSP +will be used, the Wallet shall display the available credentials from +the chosen QTSP. To achieve this, the Wallet shall request the list of +credentials (including certificates and additional information) from the +QTSP via the `/credentials/list` endpoint. + +Additionally, the EUDI Wallet can show detailed information about each +credential by setting credentialInfo to true in the /credentials/list +request, or by making a request to the `/credentials/info` endpoint if +available. + +**\[Note: The HTTP request has the authentication token in the +header\]** + + + +### 3.2.1 /credentials/list + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
userIDREQUIRED ConditionalString

This parameter SHALL NOT be present as the service authorization +will be user-specific. The userID is already implicit in the +service access token passed in the Authorization header.

+

It SHALL NOT be allowed to use this parameter to obtain the list of +credentials associated to a different user. The remote service SHALL +return an error in such case.

credentialInfoOPTIONALBooleanRequest to return the main information included in the public key +certificate and the public key certificate itself or the certificate +chain associated to the credentials. The default value is “false”.
certificatesOPTIONAL ConditionalString | none | single | chain

Specifies which certificates from the certificate chain SHALL be +returned in certs/certificates:

+
    +
  • “none”: No certificate SHALL be returned.

  • +
  • “single”: Only the end entity certificate SHALL be +returned.

  • +
  • “chain”: The full certificate chain SHALL be returned.

  • +
+

The default value is “single”.

+

This parameter MAY be specified only if the parameter credentialInfo +is “true”.

certInfoOPTIONAL ConditionalBoolean

Request to return various parameters containing information from +the end entity certificate(s). This is useful in case the signature +application wants to retrieve some details of the certificate(s) without +having to decode it first. The default value is“false”.

+

This parameter MAY be specified only if the parameter credentialInfo +is “true”.

authInfoOPTIONAL ConditionalBooleanRequest to return various parameters containing information on the +authorization mechanisms supported by the corresponding credential (auth +group). The default value is “false”. This parameter MAY be specified +only if the parameter credentialInfo is “true”.
onlyValidOPTIONAL ConditionalBoolean

Request to return only credentials usable to create a valid +signature. The default value is “false”, so if the parameter is omitted +then the method will return all credentials available to the owner.

+

The remote server may not support this parameter.

langOPTIONALStringRequest a preferred language of the response to the remote +service.
clientDataOPTIONALStringArbitrary data from the signature application.
+ +### 3.2.2 /credentials/info + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
credentialIDREQUIREDStringThe unique identifier associated to the credential.
certificatesOPTIONALString | none | single | chainThe certificates as defined in the previous table.
certInfoOPTIONALBooleanThe certInfo as defined in the previous table.
authInfoOPTIONALBooleanThe authInfo as defined in the previous table
langOPTIONALStringRequest a preferred language of the response to the remote +service.
clientDataOPTIONALStringArbitrary data from the signature application.
+ +## 3.3 Document Signing + +**For this flow, it is assumed that the Wallet has already completed the +credential listing (see section [3.2](#32-credential-listing)).** + +The flow described in this section begins after the document to be +signed has been displayed to the user, the user has approved the signing +of the document, and if the user has selected an existing certificate +for the signature. + +The signature process is divided into two main operations: credential +authorization and document signing. + +In the credential authorization flow, the Wallet sends the document to +be signed, along with the certificate and the certificate chain of the +credential chosen, and other relevant information required by the +`/signatures/calculate_hash` endpoint in the SCA. The SCA then computes +the hash of the document to be signed. Following this, the Wallet +requests authorization from the QTSP to use the private signing key, +initiating an OAuth2 flow with OpenID for Verifiable Presentations. + +Once the Wallet receives the token granting access to the chosen +credential, the document signing process can proceed. The Wallet should +then use the QTSP API to obtain the signature for the hash value. +Finally, a request should be sent to the SCA with the signature value, +the document to be signed, the certificate, and any other required data. + +In the future, the extended “OID4VP” request will +include details about what the SAM is requesting to sign and which +credential will be used, with this information displayed to the user +before they consent to allowing the SAM to access their private key for +signing. + +**\[Note: All requests include the received token in the HTTP header\]** + + + +### 3.3.1 /oauth2/authorize + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePresenceTypeDescription
response_typeREQUIREDStringThe value SHALL be “code”.
client_idREQUIREDStringThe client identifier as defined in oauth2 +[RFC 6749].
redirect_uri

REQUIRED

+

Conditional

String

The URL where the user will be +redirected after the authorization process has completed.

+

If omitted, the remote service will use the default redirect URI +pre-registered by the signature application.

scopeOPTIONALStringFor Document Signing flow SHALL be “credential”: it SHALL be used to +obtain an authorization code suitable for credentials +authorization.
authorization_detailsOPTIONALJSON object as String

The details on the access request for credentials. Details +presented on the CSC v2.0.0.2.

+

If this parameter is used, all values relevant for credential +authorization SHALL be passed in this object. The scope “credential” as +well as any request parameter relevant for credential authorization +SHALL NOT be used in this case.

code_challengeREQUIREDStringCryptographic nonce binding the +transaction to a certain user agent, used to detect code replay and CSRF +attacks.
code_challenge_methodOPTIONALStringCode verifier transformation method. Used +to obtain the “code_challenge” from the “code_verifier”. Defaults to +“plain”, but the recommended value is “S256”.
stateOPTIONALStringA value used by the client to maintain +state between the request and callback, as defined in oauth2 [RFC +6749].
request_uri

REQUIRED

+

Conditional

StringURI pointing to a pushed authorization request previously uploaded +by the client. (Note: this mechanism is not currently +supported)
langOPTIONALStringRequest a preferred language according to +RFC 5646. If specified, the authorization server SHOULD render the +authorization web page in this language, if supported.
credentialID

REQUIRED

+

Conditional

StringThe identifier associated to the credential to authorize. It SHALL +be used only if the scope of the OAuth 2.0 authorization request is +“credential”.
signatureQualifierREQUIRED ConditionalStringThis parameter contains the symbolic identifier determining the kind +of signature to be created. It SHALL be used only if the scope is +“credential” and if there is no parameter “credentialID” present. +(Note: this mechanism is not currently supported)
numSignaturesREQUIRED ConditionalNumberThe number of signatures to authorize.
hashesREQUIRED ConditionalStringOne or more base64url-encoded hash values to be signed. It SHALL be +used if the scope is “credential” and if the SCAL parameter returned by +credentials/info method for the chosen credential is “2”.
hashAlgorithmOIDREQUIRED ConditionalStringString containing the OID of the hash algorithm used to generate the +hashes.
descriptionOPTIONALStringA free form description of the +authorization transaction in the lang language.
account_tokenOPTIONALStringAn account_token as defined in [CSC-API] +section 8.4.1. (Note: this parameter is not currently +supported)
clientDataOPTIONALStringArbitrary data from the signature +application.
+ +


+ +- Authorization details (authorization\_details) type “credential” as + defined in the CSC API v2.0.0.2: + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePresenceTypeDescription
typeREQUIREDStringAuthorization details type identifier. It SHALL be set to +“credential”.
credentialIDREQUIREDStringThe identifier associated to the credential to authorize.
signatureQualifierREQUIREDStringThis parameter contains the symbolic identifier determining the kind +of signature to be created. (Note: this mechanism is not +currently supported)
documentDigestsREQUIREDJSON Array

An array composed of entries for every document to be signed. +Every entry is composed of the following elements:

+
    +
  • “hash”: Base64-encoded octet-representation of the hash of the +document

  • +
  • “label”: A human-readable description of the respective +document

  • +
hashAlgorithmOIDREQUIREDStringString containing the OID of the hash algorithm used to generate the +hashes listed in documentDigests.
+ +### 3.3.2 /signatures/calculate\_hash + +#### 3.3.2.1 Request: + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
documentsREQUIREDJSON ArrayAn array containing JSON objects, each of them containing a +base64-encoded document content to be signed and further request +parameter.
endEntityCertificateREQUIREDString
certificateChainOptionalList of StringThe certificate chain to be used when calculating the hash, +excluding the end-entity certificate. If the list of certificates is not +provided, it will be considered empty.
hashAlgorithmOIDREQUIREDStringString containing the OID of the hash algorithm used to generate the +hashes.
+ +#### 3.3.2.2 Response: + + ++++++ + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
hashesREQUIREDList of StringOne or more Base64 URL-encoded hash values to be signed.
signature_dateREQUIREDlongThe date of the signature request, as a long, which should be used +when obtaining a signed document.
+ +#### 3.3.2.3 Documents JSON Array: + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
documentREQUIREDStringBase64-encoded document content to be signed.
signature_formatREQUIREDString

The required signature format:

+
    +
  • “C” SHALL be used to request the creation of a CAdES +signature;

  • +
+
    +
  • “X” SHALL be used to request the creation of a XAdES +signature.

  • +
+
    +
  • “P” SHALL be used to request the creation of a PAdES +signature.

  • +
+
    +
  • “J” SHALL be used to request the creation of a JAdES +signature.

  • +
conformance_levelREQUIREDStringThe required signature conformance level.
signed_propsOPTIONALArray of attributeList of signed attributes. The attributes that may be included +depend on the signature format and the signature creation policy.
signed_envelope_propertyREQUIREDStringThe required property concerning the signed envelope.
containerREQUIREDString
+ +### 3.3.3 /oauth2/token + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePresenceTypeDescription
grant_typeREQUIREDString

The grant type, which depends on the type of OAuth 2.0 flow:

+
    +
  • “authorization_code”: SHALL be used in case of Authorization Code +Grant

  • +
codeREQUIREDStringThe authorization code returned by the authorization server. It +SHALL be bound to the client identifier and the redirection URI.
client_idREQUIREDStringA client_id that was send before by Signature Interaction Component +(SIC) during request (see Section 3.4.3.3 of CSC).
code_verifierREQUIREDStringThe String value (nonce) that was used to generate the +code_challenge from the /oauth2/authorize. See PKCE resources.
redirect_uriREQUIREDStringAn URL that was send before by Signature Interaction Component (SIC) +and where the user was redirected after the authorization process +completed. It is used to validate that it matches the original value +previously passed to the authorization server.
authorization_details

REQUIRED

+

Conditional

JSON object as StringAn authorization detail that was send before by Signature +Interaction Component (SIC) during the /oauth2/authorize. It contains +the authorization details as approved during the authorization +process.
+ +### + +### 3.3.4 /signatures/obtain\_signed\_doc + +#### 3.3.4.1 Request: + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
documentsREQUIREDJSON ArrayAn array containing JSON objects, each of them containing a +base64-encoded document content to be signed and further request +parameter.
returnValidationInfoOPTIONALBooleanThis parameter SHALL be set to “true” to request the service to +return the “validationInfo” as defined below. The default value is +“false”, i.e. no“validationInfo” info is provided. (Note: this +mechanism is not currently supported)
hashAlgorithmOIDREQUIREDStringThe OID of the algorithm used to calculate the hash value(s).
endEntityCertificateREQUIREDStringThe certificate to be used. It SHALL match the endEntityCertificate +from the calculate_hash request.
certificateChainREQUIREDList of StringThe certificate chain to be used when calculating the hash, +excluding the end-entity certificate. If the list of certificates is not +provided, it will be considered empty.
dateREQUIREDlongThe value of signature_date received in the response to the +calculate_hash request.
signaturesREQUIREDList of StringThe signature value of the hash received from the calculate_hash +request.
+ +#### 3.3.4.2 Response: + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceValueDescription
documentWithSignature

REQUIRED

+

Conditional

Array of String“One or more Base64-encoded signatures enveloped within +thedocuments.” (CSC)
signatureObject

REQUIRED

+

Conditional

Array of String“One or more Base64-encoded signatures detached from the documents.” +(CSC)
validationInfoREQUIRED ConditionalJSON Object“The validationInfo is a JSON Object containing validation data that +SHALL be included in the signing response if requested using the input +parameter “returnValidationInfo”.” (CSC)
+ +### 3.3.5 /signatures/signHash + + ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterPresenceTypeDescription
credentialIDREQUIREDStringThe unique identifier associated to the credential. It should match +the credentialID defined in the /oauth2/authorize request with scope +“credential”
SADREQUIRED ConditionalStringThe Signature Activation Data returned by the Credential +Authorization methods. Not needed if the signing application has passed +an access token in the “Authorization” HTTP header with scope +“credential”, which is also good for the credential identified by +credentialID. (Note: this mechanism is not currently +supported)
hashesREQUIREDArrayOne or more hash values to be signed. This parameter SHALL contain +the Base64-encoded raw message digest(s).
hashAlgorithmOIDREQUIRED ConditionalStringThe OID of the algorithm used to calculate the hash value(s). This +parameter SHALL be omitted or ignored if the hash algorithm is +implicitly specified by the signAlgo algorithm. Only hashing +algorithms as strong or stronger than SHA256 SHALL be used.
signAlgoREQUIREDStringThe OID of the algorithm to use for signing. It SHALL be one of the +values allowed by the credential as returned in keyAlgo by the +credentials/info method.
signAlgoParamsREQUIREDStringThe Base64-encoded DER-encoded ASN.1 signature parameters, if +required by the signature algorithm. (Note: this mechanism is +not currently supported)
operationModeOPTIONALStringThe synchronous operation mode SHALL be supported. (Note: +this mechanism is not currently supported)
validity_periodOPTIONAL ConditionalIntegerMaximum period of time, expressed in milliseconds, until which the +server SHALL keep the request outcome(s) available for the client +application retrieval. If the parameter operationMode is not “A” this +parameter SHOULD not be specified. (Note: this mechanism is not +currently supported)
response_uriOPTIONAL ConditionalStringValue of one location where the server will notify the signature +creation operation completion, as an URI value. If the parameter +operationMode is not “A” this parameter SHOULD not be specified. +(Note: this mechanism is not currently supported)
clientDataOPTIONALStringArbitrary data from the signature +application.
+ +# Annex 1 + +The following image illustrates an architecture defined by the CSC for a +similar implementation: + + + +[1] The extended “OID4VP” request/messages will only be implemented +after the extension is added to the OID4VP draft.