diff --git a/.dockerignore b/.dockerignore index efffb918c9ccf917a14a836581e96540fc25cde1..13fb47336838dfbe939286133bd4394420129cd6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1 @@ -# exclude everything -* - -# include binary distribution archives -!build/distributions/ +# exclude nothing diff --git a/.gitignore b/.gitignore index 62d8458021b782ac52ac9deba0f0660cf2e04294..7c909f48e7cac1c703b044139e7bb9cfe9a676c7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,7 @@ .secret #logging -crymlin.log -medina-codyze.log +*.log # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/Dockerfile b/Dockerfile index d9ead257c01564dbba0590a8fc2162757cf577c5..5cfe4fc4f6d3bef5c2570ca5e2a79d09e3f7c0af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,44 @@ -FROM openjdk:11-jre-slim +# Builder +FROM eclipse-temurin:11.0.20.1_1-jdk AS builder +# required for Codyze MEDINA functionalities -> git, gpg RUN apt-get update && apt-get -y --no-install-recommends install \ - unzip \ - wget + git \ + gpg \ + && rm -rf /var/lib/apt/lists/* -# add distribution -ADD build/distributions/codyze-*.tar /usr/local/lib/ -# add links for ease of use -RUN ln -s /usr/local/lib/codyze-*/bin/codyze /usr/local/bin/ \ - && ln -s /usr/local/lib/codyze-* /codyze +WORKDIR /codyze-medina -RUN wget "https://github.com/Fraunhofer-AISEC/codyze/archive/refs/heads/main.zip" \ - && unzip main.zip \ - && mv codyze-main/src/dist/mark/ /codyze/mark/ \ - && rm -rf main.zip codyze-main +# files for build filtered by .dockerignore +ADD . . +RUN ./gradlew --parallel \ + generateAll \ + && ./gradlew --parallel \ + build \ + installDist -# working location with sources to be analyzed -WORKDIR /source + +# Executable image +FROM eclipse-temurin:11.0.20.1_1-jre + +LABEL org.opencontainers.image.authors="Fraunhofer AISEC <codyze@aisec.fraunhofer.de>" +LABEL org.opencontainers.image.vendor="Fraunhofer AISEC" +LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL org.opencontainers.image.title="Codyze for MEDINA" +LABEL org.opencontainers.image.description="Compliance checks for source code using Codyze. Adjusted for MEDINA framework." + +COPY --from=builder /codyze-medina/build/install/ / +RUN ln -st /usr/local/bin/ /codyze-medina/bin/codyze-medina + +# required for Codyze MEDINA functionalities -> git, gpg +RUN apt-get update && apt-get -y --no-install-recommends install \ + git \ + gpg \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /project +# Add workdir to safe directories +RUN git config --global --add safe.directory /project # default entrypoint and parameters -ENTRYPOINT ["codyze"] -CMD ["-c"] +ENTRYPOINT ["codyze-medina"] \ No newline at end of file diff --git a/README.md b/README.md index be5a7b429fa802f621fda28238f55de2fc8b2677..dfddf9315fff54b3b98b381293162566b02db5b6 100644 --- a/README.md +++ b/README.md @@ -48,14 +48,22 @@ This makes Codyze for MEDINA available for immediate use in `./build/install/cod Codyze for MEDINA provides a CLI. The main options are: -| Option | Description | -|-----------------------|-------------------------------------------------------------| -| `--endpoint <URL>` | URL of Orchestrator endpoint | -| `--oauth-endpoint <URL>` | URL of OAuth endpoint | -| `--username <STRING> \| -u <STRING>` | username for OAuth | -| `--password <STRING> \| -p <STRING>` | password for OAuth | -| `--ci <STRING>` | CI environment being used (i.e., [GITLAB, GITHUB, JENKINS]) | -| `--config <FILE>` | configuration file for Codyze | +| Option | Default | Description | +|-------------------------------------|:--------------------------:|-----------------------------------------------------------------------------------------------------------------------------------------------| +| `--id <STRING>` | - | UUID of the analyzed cloud service | +| `--rules <FILE>` | codyze-medina-metrics.yaml | File specifying environmental MEDINA rules | +| `--medina-output <PATH>` | codyze-medina.sarif | File where MEDINA rule evaluations will be stored. In case `combined-output` is set to true, this is the location of the combined result file | +| `--combined-output <BOOLEAN>` | true | Whether the respective SARIF files created by Codyze and the MEDINA evaluation should be merged | +| `--key-location <PATH>` | public-keys/ | Location of public key files necessary to evaluate signatures | +| `--endpoint <URL>` | - | URL of Orchestrator endpoint | +| `--oauth-endpoint <URL>` | - | URL of OAuth endpoint | +| `--username <STRING> / -u <STRING>` | - | username for OAuth | +| `--password <STRING> / -p <STRING>` | - | password for OAuth | +| `--required <BOOLEAN>` | true | Whether analysis fails on Orchestrator connection issues. | +| `--mark-builtin <LIST<FILE>>` | [] | Builtin MARK files used in the analysis | +| `--mark-project <LIST<FILE>>` | [] | External MARK files used in the analysis | +| `--ci <STRING>` | NONE | CI environment being used (i.e., [GITLAB, GITHUB, JENKINS]). If set to `NONE`, The program will try to determine this at runtime. | +| `--config <FILE>` | codyze-medina.yaml | Configuration file for Codyze | In addition, Codyze for MEDINA passes CLI options along to Codyze v2. Options for Codyze v2 can be found in the documentation at [codyze.io](shttps://www.codyze.io/Getting%20Started/configuration/). @@ -71,24 +79,87 @@ The structure of a configuration file is: ```yaml orchestrator: + required: <BOOLEAN> endpoint: <URL> auth: oauth-endpoint: <URL> username: <STRING> password: <STRING> - + +mark: + builtin: + - <PATH> + - ... + project: + - <PATH> + - ... + +id: <STRING> +rules: <FILE> + +# ... additional optional parameters ... # ... additional parameters from original Codyze ... ``` +The MARK file paths are interpreted based on the location of the codyze distribution: +- The `builtin` segment represents included MARK rule groups found in `codyze-medina/mark`. Expected inputs are the names of the respective subdirectories. +- The `project` segment represents additional MARK rules provided by any other source. Note that they also need to include a valid mapping file to be considered during the analysis. + See the test resources for an example configuration file. It uses a locally running Orchestrator. +The configuration file should be located within the target project to comply with the following assumptions: + - All **relative paths** used in the configuration of Codyze for MEDINA are evaluated relative to the location of the configuration file. + - The specified MEDINA Metrics from the `rules` parameter are evaluated at the location of the configuration file. + ## Mapping Mapping from `Finding`s to `AssessmentResult`s is currently handled by the mapping file -`src/main/java/resoures/mappings.txt`. Its entries follow the -scheme `[findingId0:findingId1:...]->(metricId;isDefault;operator;targetValue;valueType)` -where `;` separates different parameters and `:` separates elements of a list. See the mapping file in this project for -an example. +`mappings.txt`. +Its entries follow this scheme: +``` +metrics: + - name: <Metric Name> + rules: + - <Required MARK Rule> + - <Required MARK Rule> + - ... + configuration: + default: <Whether Config is Default> + operator: <Comparator as String> + type: [STRING, NUMBER, BOOLEAN] + target: + - <Target Value> + - ... + - ... +``` + +All findings NOT mapped in such a file are ignored during the analysis. +> More information about the mapping file can be found in `docs/mapping.md` + +## Exit Codes + +Codyze for MEDINA currently returns one of the following exit codes depending on the situation: + +| Exit Code | Meaning | +|-----------|---------------------------------------------------------| +| **126** | Orchestrator Connection Failed | +| **3** | Unrecognized Cloud Service Id | +| **127** | General Execution Error (consult logs for more details) | +| **1** | Violations found during analysis | +| **0** | No violations or errors | + +Exit codes higher up in this list take priority over lower ones, e.g. a return code of 126 does not exclude violations within the analysis results. + +## Environment Variables + +The following environment variables can be used to additionally configure the execution: + +| Name | Effect | +|------------------|---------------------------------------------------------------------------------------------------------------------| +| CODYZE_PWD | Sets the orchestrator password. Overwrites configuration file entries but is overwritten by command line arguments. | +| CODYZE_LOG_LEVEL | Manually overrides the log level | + +Additionally, Codyze for MEDINA parses variables automatically set by the CI system to gain necessary information about the environment. -All findings NOT mapped in this file are ignored when sending results to the orchestrator (they will still be present in -the result file). \ No newline at end of file +> Codyze for MEDINA requires the target project to be within a Git repository. +> Additionally, GnuPG must be available in order to evaluate any signatures. diff --git a/build.gradle.kts b/build.gradle.kts index 578492285eddc10fba4ee668f2bc25a91a8bfbd3..84f7c97c6551947ef40fb3fc459fec23adee459c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,22 +1,22 @@ import org.openapitools.generator.gradle.plugin.tasks.GenerateTask plugins { - java - kotlin("jvm") version "1.7.20" + kotlin("jvm") version "1.9.20" application - id("org.openapi.generator") version "6.2.0" + // generator for OpenAPI specs + id("org.openapi.generator") version "7.1.0" // code quality jacoco - id("com.diffplug.spotless") version "6.11.0" + id("com.diffplug.spotless") version "6.22.0" // documentation - id("org.jetbrains.dokka") version "1.7.20" + id("org.jetbrains.dokka") version "1.9.10" } group = "de.fraunhofer.aisec.codyze.medina" -version = "1.0.0" +version = "1.6.0" repositories { // JitPack for packages build directly from GitHub repos @@ -50,53 +50,55 @@ repositories { } dependencies { - // Java only - testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.1") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.1") + // testing + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1") + testImplementation("org.mockito:mockito-junit-jupiter:5.7.0") // depend on executing the build.gradle of the generated openapi-projects api(project("generator_orchestrator")) api(project("generator_evidence")) - // codyze - api("com.github.Fraunhofer-AISEC:codyze:2.2.0") + // Codyze + implementation("com.github.Fraunhofer-AISEC:codyze:2.3.0") implementation( "com.github.Fraunhofer-AISEC.codyze-mark-eclipse-plugin:de.fraunhofer.aisec.mark:2.0.0:repackaged" ) // CLI using picocli - implementation("info.picocli:picocli:4.6.3") - annotationProcessor("info.picocli:picocli-codegen:4.6.3") + implementation("info.picocli:picocli:4.7.5") + annotationProcessor("info.picocli:picocli-codegen:4.7.5") // OAuth2 - api("org.dmfs:oauth2-essentials:0.18") - implementation("org.dmfs:httpurlconnection-executor:0.20") + implementation("org.dmfs:oauth2-essentials:0.22.1") + implementation("org.dmfs:httpurlconnection-executor:1.22.1") - // Logging - implementation("io.github.microutils:kotlin-logging-jvm:3.0.2") - implementation("org.slf4j:slf4j-simple:2.0.3") + // logging + implementation("io.github.oshai:kotlin-logging-jvm:5.1.0") + implementation("org.slf4j:slf4j-api:2.0.9") // required by `io.github.oshai:kotlin-logging-jvm` + implementation("org.apache.logging.log4j:log4j-core:2.21.1") + runtimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1") - // additional dependencies - implementation("com.fasterxml.jackson.core:jackson-databind:2.13.4.2") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.4") + // YAML configuration files + implementation("com.fasterxml.jackson.core:jackson-databind:2.16.0") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.0") - // kotlin - implementation(kotlin("stdlib-jdk8")) + // SARIF Kotlin bindings + implementation("io.github.detekt.sarif4k:sarif4k:0.5.0") } -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - } +kotlin { + jvmToolchain(11) } application { - mainClass.set("de.fraunhofer.aisec.codyze.medina.CodyzeMedinaKt") + mainClass.set("de.fraunhofer.aisec.codyze.medina.main.CodyzeMedinaKt") } distributions { main { contents { + // copy over important files from("mark/") { into("mark/") } @@ -109,50 +111,62 @@ distributions { // license header used with spotless val license = """ - /* + // SPDX-License-Identifier: Apache-2.0 + + /* * Copyright (c) ${"$"}YEAR, Fraunhofer AISEC. All rights reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * 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. - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ * | | / _ \ / _` | | | |_ / _ \ * | |___| (_) | (_| | |_| |/ / __/ * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. */ """.trimIndent() spotless { - ratchetFrom("origin/main") - java { - importOrder() - removeUnusedImports() - googleJavaFormat() - licenseHeader(license).yearSeparator("-") - } kotlin { ktfmt().kotlinlangStyle() licenseHeader(license).yearSeparator("-") } } +// Manually exclude all spotless-Tasks of the "generator_"-subprojects from the gradle task graph. +// This is a workaround to prevent the exceptions thrown from the outdated google-java-format specified there +gradle.taskGraph.whenReady { + if(hasTask(":spotlessJava") || hasTask(":spotlessJavaDiagnose")) { + allTasks.forEach { + for (project in subprojects) { + if(it.path.matches(":generator_.+:spotless.*".toRegex())) { + it.enabled = false + } + } + } + } +} + // generates an openapi-project from the `orchestrator.yaml` specification in the resources // directory tasks.register<GenerateTask>("generateOrchestrator") { inputSpec.set("$rootDir/src/main/resources/orchestrator.yaml") - outputDir.set("$buildDir/generator_orchestrator") + outputDir.set("${layout.buildDirectory.get().asFile}/generator_orchestrator") generatorName.set("java") apiPackage.set("org.openapitools.client.orchestrator.api") modelPackage.set("org.openapitools.client.orchestrator.model") @@ -162,7 +176,7 @@ tasks.register<GenerateTask>("generateOrchestrator") { // generates an openapi-project from the `evidence.yaml` specification in the resources directory tasks.register<GenerateTask>("generateEvidence") { inputSpec.set("$rootDir/src/main/resources/evidence.yaml") - outputDir.set("$buildDir/generator_evidence") + outputDir.set("${layout.buildDirectory.get().asFile}/generator_evidence") generatorName.set("java") apiPackage.set("org.openapitools.client.evidence.api") modelPackage.set("org.openapitools.client.evidence.model") @@ -175,7 +189,9 @@ tasks.register("generateAll") { dependsOn(tasks.named("generateEvidence")) } -tasks.named<Test>("test") { useJUnitPlatform() } +tasks.named<Test>("test") { + useJUnitPlatform() +} tasks.test { finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run @@ -191,3 +207,19 @@ tasks.jacocoTestReport { tasks.dokkaHtml.configure { outputDirectory.set(rootDir.resolve("docs")) } + +tasks { + jar { + from("LICENSE") { + into("META-INF/") + } + // add name and version from gradle configuration to jar manifest + manifest { + attributes( + "Implementation-Title" to rootProject.name, + "Implementation-Version" to rootProject.version, + "Bundle-License" to "https://opensource.org/licenses/Apache-2.0" + ) + } + } +} diff --git a/docs/mapping.md b/docs/mapping.md new file mode 100644 index 0000000000000000000000000000000000000000..9d550b529787ac05358dfa323548556210096f7c --- /dev/null +++ b/docs/mapping.md @@ -0,0 +1,67 @@ +# Mapping Documentation + +## Content of a single mapping + +To adapt the provided mapping, the following syntax must be considered: + +The mapping consists of a set of **metrics**, which define the results as they will be sent to the Orchestrator. + +Each metric is specified by its **name**, **rules** and **configuration**. + +### Rules + +It is possible to define one or multiple rules for each metric. These rules represent the MARK rules evaluated by Codyze. +A metric will only be evaluated if _all_ of its rules have been evaluated prior. Only if _all_ of its rules passed the metric will be evaluated as compliant. + +### Configuration + +The configuration of a metric defines its evaluation in the Orchestrator. + +The **default** value indicates whether this configuration represents a default configuration or has been modified. + +The **operator** specifies the operator which is used to compare different results of this metric with the target value. + +The **type** specifies the data type the results of this metric have. Possible values are *BOOLEAN*, *NUMBER* and *STRING*. + +The **target** value specifies one or multiple values that define - together with the operator - which results are considered good. + +## Having multiple mappings + +There are many reasons why one would want to have multiple mapping files. +One of them being that there may be rules with the same name in different modules which should not be verified together. + +Therefore, the specified Mark directory is being scanned top-to-bottom until a mapping is found. +This mapping is subsequently applied to all hierarchical equivalent or lower rules within the same directory. +Other mapping files found below an existing mapping will be ignored. + +When creating multiple mappings consider that every evaluated mapping will cause a separate analysis run. + +### Example + +Consider the following structure of the Mark rule source directory: + +``` +mark/ +├─ botan/ +│ ├─ botan-rules-1/ +│ │ ├─ mapping.yaml [0] +│ │ ├─ botan-rule-1.mark +│ ├─ botan-rules-2/ +│ │ ├─ botan-rule-2.mark +│ ├─ botan-rule-0.mark +├─ bouncycastle/ +│ ├─ mapping.yaml [1] +│ ├─ bc-rules-1/ +│ │ ├─ bc-rule-1.mark +│ ├─ bc-rules-2/ +│ │ ├─ bc-rule-2.mark +│ │ ├─ mapping.yaml [2] +│ ├─ bc-rule-0.mark + +``` + +In this scenario, `botan-rule-1.mark` will be included in the run evaluating `[0]`. +All other rules in the `botan/` directory will be ignored as there is no mapping at or above their level. + +Within the `bouncycastle/` directory all rules will be analyzed in the context of `[1]`. +The mapping found at `[2]` will not be applied as it is within a directory that already belongs to `[1]`. \ No newline at end of file diff --git a/docs/medina-rules.md b/docs/medina-rules.md new file mode 100644 index 0000000000000000000000000000000000000000..68358b035ce95ffaa9f7fbf4208f1490ae6d8e84 --- /dev/null +++ b/docs/medina-rules.md @@ -0,0 +1,32 @@ +# MEDINA Rule Documentation + +## Content of a rule specification + +The file containing the MEDINA rules is specified by the program argument `--rules` and defaults to `medina.yaml`. +The YAML content is expected to be structured in the following way: + +``` +metrics: + - name: <METRIC1> + target: + - <TARGET1> + - ... + - name: <METRIC2> + target: + - <TARGET1> + - ... + - ... +``` + +### Available rules + +The following table lists all currently available rules and the expected format for their respective target values: + +| Name | Target Value | Explanation | +|:--------------|:--------------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------| +| CodeSignoff | "<SIGNER_NAME>" | Checks for a Signoff by the specified signer | +| SignedCommits | "<SIGNING_KEY_ID>" | Checks for a valid GPG signature from the specified key | +| SignedSignoff | <div>Map: {</br>name: "<SIGNER_NAME>", </br>email: "<SIGNER_EMAIL>", </br>pub-key-id: "<SIGNING_KEY_ID>"</br>}</div> | Checks whether the commit contains a valid signature and signoff from the specified subject | +| ApprovedCommitAuthor | "<AUTHOR_NAME>" | Checks whether the commit was authored from a specified subject | + +The `SIGNING_KEY_ID` is expected to be the 16-character-long ID of the key used for signing. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee733431762e7ccf8ab9b7409ed44960c..3fa8f862f753336d4fabfd607678a7a2317e8a06 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c20655813e44515156e7253a2a239138..1aa94a4269074199e6ed2c37e8db3e0826030965 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 53a6b238d414d91c30c5644c82393d27416fbbe6..6689b85beecde676054c39c2408085f41e6be6dc 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/local.Dockerfile b/local.Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..5e5fced13ec204160ff99e89d9811c342c785f14 --- /dev/null +++ b/local.Dockerfile @@ -0,0 +1,26 @@ +FROM eclipse-temurin:11.0.20.1_1-jre + +LABEL org.opencontainers.image.authors="Fraunhofer AISEC <codyze@aisec.fraunhofer.de>" +LABEL org.opencontainers.image.vendor="Fraunhofer AISEC" +LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL org.opencontainers.image.title="Codyze for MEDINA" +LABEL org.opencontainers.image.description="Compliance checks for source code using Codyze. Adjusted for MEDINA framework." + +# required for Codyze MEDINA functionalities -> git, gpg +RUN apt-get update && apt-get -y --no-install-recommends install \ + git \ + gpg \ + && rm -rf /var/lib/apt/lists/* + +# add distribution +ADD build/distributions/codyze-*.tar /usr/local/lib/ +# add links for ease of use +RUN ln -s /usr/local/lib/codyze-*/bin/codyze-medina /usr/local/bin/ \ + && ln -s /usr/local/lib/codyze-* /codyze-medina + +WORKDIR /project +# Add workdir to safe directories +RUN git config --global --add safe.directory /project + +# default entrypoint +ENTRYPOINT ["codyze-medina"] \ No newline at end of file diff --git a/mark/bc-jca/Cipher.mark b/mark/bc-jca/Cipher.mark new file mode 100644 index 0000000000000000000000000000000000000000..fc6d829a25bfc7ca3f1e7dbc889c92cfc90891f6 --- /dev/null +++ b/mark/bc-jca/Cipher.mark @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 java.jca + +entity Cipher { + + var transform; + var provider; + + var opmode; + var certificate; + var random; + var key; + var params; + var paramspec; + + var input; + var output; + + var wrappedkey; + var wrappedkeyalgorithm; + var wrappedkeytype; + + op instantiate { + javax.crypto.Cipher.getInstance( + transform : java.lang.String + ); + javax.crypto.Cipher.getInstance( + transform : java.lang.String, + provider : java.lang.String | java.security.Provider + ); + } + + op init { + javax.crypto.Cipher.init( + opmode : int, + certificate : java.security.cert.Certificate + ); + javax.crypto.Cipher.init( + opmode : int, + certificate : java.security.cert.Certificate, + random : java.security.SecureRandom + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + params : java.security.AlgorithmParameters + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + params : java.security.AlgorithmParameters, + random : java.security.SecureRandom + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + random : java.security.SecureRandom + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + paramspec : java.security.spec.AlgorithmParameterSpec + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + paramspec : javax.crypto.spec.IvParameterSpec + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + paramspec : java.security.spec.AlgorithmParameterSpec, + random : java.security.SecureRandom + ); + javax.crypto.Cipher.init( + opmode : int, + key : java.security.Key, + paramspec : javax.crypto.spec.IvParameterSpec, + random : java.security.SecureRandom + ); + } + + op aad { + javax.crypto.Cipher.updateAAD(src : byte[] | java.nio.ByteBuffer); + javax.crypto.Cipher.updateAAD(src: byte[], ...); + } + + op update { + output = javax.crypto.Cipher.update(input : byte[]); + output = javax.crypto.Cipher.update(input : byte[], _, _); + javax.crypto.Cipher.update(input : byte[], _, _, output : byte[]); + javax.crypto.Cipher.update(input : byte[], _, _, output : byte[], _); + javax.crypto.Cipher.update(input : java.nio.ByteBuffer, output : java.nio.ByteBuffer); + } + + op finalize { + output = javax.crypto.Cipher.doFinal(); + output = javax.crypto.Cipher.doFinal(input : byte[]); + javax.crypto.Cipher.doFinal(output : byte[], _); + output = javax.crypto.Cipher.doFinal(input : byte[], _, _); + javax.crypto.Cipher.doFinal(input : byte[], _, _, output : byte[]); + javax.crypto.Cipher.doFinal(input : byte[], _, _, output : byte[], _); + javax.crypto.Cipher.doFinal(input : java.nio.ByteBuffer, output: java.nio.ByteBuffer); + } + + op keywrap { + wrappedkey = javax.crypto.Cipher.wrap(key : java.security.Key); + key = javax.crypto.Cipher.unwrap( + wrappedkey : byte[], + wrappedkeyalgorithm : java.lang.String, + wrappedkeytype : int + ); + } +} diff --git a/src/test/resources/Mark/bouncycastle/KeyAgreement.mark b/mark/bc-jca/KeyAgreement.mark similarity index 61% rename from src/test/resources/Mark/bouncycastle/KeyAgreement.mark rename to mark/bc-jca/KeyAgreement.mark index f2ed50a8fbf01ddb227cfdf23cc28464d538874f..c99e99cfaef7cc9e6c175e6f0df23252abb02467 100644 --- a/src/test/resources/Mark/bouncycastle/KeyAgreement.mark +++ b/mark/bc-jca/KeyAgreement.mark @@ -1,3 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 java.jca entity KeyAgreement { diff --git a/mark/bc-jca/LICENSE b/mark/bc-jca/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/mark/bc-jca/LICENSE @@ -0,0 +1,202 @@ + + 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/src/test/resources/Mark/bouncycastle/Mac.mark b/mark/bc-jca/Mac.mark similarity index 52% rename from src/test/resources/Mark/bouncycastle/Mac.mark rename to mark/bc-jca/Mac.mark index f0f90460b28d18376dbdc0e84b4b2a2b511a07c1..15c7933dbb8e596ceb10530d2478c339f7ab9e89 100644 --- a/src/test/resources/Mark/bouncycastle/Mac.mark +++ b/mark/bc-jca/Mac.mark @@ -1,20 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 java.jca /* * Represents javax.crypto.Mac */ entity Mac { - + var algorithm; var provider; - + var key; var params; - + var input; var output; - op instantiate { javax.crypto.Mac.getInstance( algorithm : java.lang.String @@ -24,7 +49,7 @@ entity Mac { provider : java.lang.String | java.security.Provider ); } - + op init { javax.crypto.Mac.init(key : java.security.Key); javax.crypto.Mac.init( @@ -32,20 +57,19 @@ entity Mac { params : java.security.spec.AlgorithmParameterSpec ); } - + op update { javax.crypto.Mac.update(input : byte | byte[] | java.nio.ByteBuffer); javax.crypto.Mac.update(input : byte[], ...); } - + op finalize { output = javax.crypto.Mac.doFinal(); output = javax.crypto.Mac.doFinal(input : byte[]); javax.crypto.Mac.doFinal(output : byte[], _); } - + op reset { javax.crypto.Mac.reset(); } - -} \ No newline at end of file +} diff --git a/mark/bc-jca/MessageDigest.mark b/mark/bc-jca/MessageDigest.mark new file mode 100644 index 0000000000000000000000000000000000000000..0e3b5d12cd4fad11165bde824d9592c30d1bb476 --- /dev/null +++ b/mark/bc-jca/MessageDigest.mark @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 java.jca + +/* + * Represents java.security.MessageDigest + */ +entity MessageDigest { + + var algorithm; + var provider; + var input; + var digest; + + op instantiate { + java.security.MessageDigest.getInstance(algorithm : java.lang.String); + java.security.MessageDigest.getInstance( + algorithm : java.lang.String, + provider : java.lang.String | java.security.Provider + ); + } + + op update { + java.security.MessageDigest.update(input : byte | byte[] | java.nio.ByteBuffer); + java.security.MessageDigest.update( + input : byte[], + ... + ); + } + + op digest { + digest = java.security.MessageDigest.digest(); + digest = java.security.MessageDigest.digest(input : byte[]); + java.security.MessageDigest.digest(digest : byte[], ...); + } + + op reset { + java.security.MessageDigest.reset(); + } +} diff --git a/mark/bc-jca/README.md b/mark/bc-jca/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1854e6c0daa21da08ec2fb2dfba69be85d874214 --- /dev/null +++ b/mark/bc-jca/README.md @@ -0,0 +1,8 @@ +# MARK for Bouncy Castle's JCA/JCE Provider + +## License +These files are part of [Codyze](https://www.codyze.io/). +They are distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). + +They have been included from [Codyze on GitHub](https://github.com/Fraunhofer-AISEC/codyze/) into Codyze for MEDINA. +Adjustments within the [MEDINA project](https://medina-project.eu/) are marked appropriately. \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/SecureRandom.mark b/mark/bc-jca/SecureRandom.mark similarity index 64% rename from src/test/resources/Mark/bouncycastle/SecureRandom.mark rename to mark/bc-jca/SecureRandom.mark index fe531bf7501c140d46fbee81e275026bc4d02b5a..c85ea98f8548a2bf634215f4111990944053541e 100644 --- a/src/test/resources/Mark/bouncycastle/SecureRandom.mark +++ b/mark/bc-jca/SecureRandom.mark @@ -1,14 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 java.jca entity SecureRandom { - + var algorithm; var provider; var params; var seed; var numBytes; var randomBytes; - + op instantiate { java.security.SecureRandom.getInstance(algorithm : java.lang.String); java.security.SecureRandom.getInstance( @@ -32,20 +58,20 @@ entity SecureRandom { seed : byte[] ); } - + op seed { java.security.SecureRandom.setSeed(seed : byte[] | long); } - + op reseed { java.security.SecureRandom.reseed(); java.security.SecureRandom.reseed(params : java.security.SecureRandomParameters); } - + op generateSeed { seed = java.security.SecureRandom.generateSeed(numBytes : int); } - + op generate { java.security.SecureRandom.next(numBytes : int); java.security.SecureRandom.nextBytes(randomBytes : bytes[]); @@ -54,5 +80,4 @@ entity SecureRandom { params : java.security.SecureRandomParameters ); } - -} \ No newline at end of file +} diff --git a/mark/bc-jca/Signature.mark b/mark/bc-jca/Signature.mark new file mode 100644 index 0000000000000000000000000000000000000000..61a6b8d42e38c3ba9a634fe7c3708fdbed1decb2 --- /dev/null +++ b/mark/bc-jca/Signature.mark @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 java.jca + +entity Signature { + + var algorithm; + var provider; + + var privateKey; + var random; + + var certificate; + var publicKey; + + var b; + var data; + var off; + var len; + + var outbuf; + var offset; + var len; + + var signature; + var offset; + var length; + + op instantiate { + java.security.Signature.getInstance( + algorithm : java.lang.String + ); + java.security.Signature.getInstance( + algorithm : java.lang.String, + provider : java.lang.String | java.security.Provider + ); + } + + op initsign { + java.security.Signature.initSign( + privateKey : java.security.PrivateKey + ); + java.security.Signature.initSign( + privateKey : java.security.PrivateKey, + random : java.security.SecureRandom + ); + } + + op initverify { + java.security.Signature.initVerify( + certificate : java.security.cert.Certificate + ); + java.security.Signature.initVerify( + publicKey : java.security.PublicKey + ); + } + + op update { + java.security.Signature.update( + b : byte + ); + java.security.Signature.update( + data : byte[] | java.nio.ByteBuffer + ); + java.security.Signature.update( + data : byte[], + off : int, + len : int + ); + } + + op sign { + signature = java.security.Signature.sign(); + java.security.Signature.sign( + outbuf : byte[], + offseet : int, + len : int + ); + } + + op verify { + java.security.Signature.verify( + signature : byte[] + ); + java.security.Signature.verify( + signature : byte[], + offset : int, + length : int + ); + } +} diff --git a/mark/bc-jca/findingDescription.json b/mark/bc-jca/findingDescription.json new file mode 100644 index 0000000000000000000000000000000000000000..52dc0b7c80db3bb1b500ac1dd01614e803f08482 --- /dev/null +++ b/mark/bc-jca/findingDescription.json @@ -0,0 +1,92 @@ +{ + "Ciphers": { + "fullDescription": { + "text": "Using insecure ciphers. Ensure the use of recommended ciphers by BSI TR-02102-1, which includes AES and RSA/ECIES/DLIES." + }, + "shortDescription": { + "text": "Use of insecure ciphers." + }, + "fixes": [ + { + "description": { + "text": "Use AES with cipher modes CCM, GCM, CTR, CBC or RSA and encryption schemes ECIES and DLIES." + } + } + ] + }, + "HashFunctions": { + "fullDescription": { + "text": "Using insecure hash functions. Ensure the use of recommended hash functions by BSI TR-02102-1, which includes SHA-2 and SHA-2 of at least 256 bit digest size." + }, + "shortDescription": { + "text": "Use of insecure hash functions." + }, + "fixes": [ + { + "description": { + "text": "Use hash functions of the SHA-2 and SHA-3 family with at least 256 bit digest sizes." + } + } + ] + }, + "MessageAuthenticationCodes": { + "fullDescription": { + "text": "Using insecure message authentication codes. Ensure the use of recommended message authentication codes by BSI TR-02102-1, which includes HMAC, CMAC and GMAC using recommended ciphers and hash functions." + }, + "shortDescription": { + "text": "Use insecure message authentication codes." + }, + "fixes": [ + { + "description": { + "text": "Use message authentication codes based on HMAC, CMAC and GMAC and using recommended ciphers and hash functions." + } + } + ] + }, + "Signatures": { + "fullDescription": { + "text": "Using insecure signatures. Ensure the use of recommended signatures by BSI TR-02102-1, which includes DSA, ECDSA and RSA with recommended hash functions." + }, + "shortDescription": { + "text": "Use of insecure signatures." + }, + "fixes": [ + { + "description": { + "text": "Use signatures based on DSA, ECDSA and RSA with recommended hash functions." + } + } + ] + }, + "SecureRandom": { + "fullDescription": { + "text": "Using insecure PRNG. Ensure the use of recommended PRNG by BSI TR-02102-1, which includes DRBG and blocking native PRNG." + }, + "shortDescription": { + "text": "Use of insecure PRNG." + }, + "fixes": [ + { + "description": { + "text": "Use PRNG based on DRBG or native blocking PRNG." + } + } + ] + }, + "KeyAgreement": { + "fullDescription": { + "text": "Using insecure key agreement. Ensure the use of recommended key agreement by BSI TR-02102-1, which includes DH, ECDH/ECCDH and ECKA-EG with recommended hash functions." + }, + "shortDescription": { + "text": "Use of insecure key agreement." + }, + "fixes": [ + { + "description": { + "text": "Use key agreement based on DH, ECDH/ECCDH and ECKA-EG with recommended hash functions." + } + } + ] + } +} \ No newline at end of file diff --git a/mark/bc-jca/mapping.yaml b/mark/bc-jca/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f72cabcca99fd7f2fb26c8f5f54ff567dcf23eac --- /dev/null +++ b/mark/bc-jca/mapping.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# +# _____ _ +# / ____| | | +# | | ___ __| |_ _ _______ +# | | / _ \ / _` | | | |_ / _ \ +# | |___| (_) | (_| | |_| |/ / __/ +# \_____\___/ \__,_|\__, /___\___| +# __/ | +# |___/ +# +# This file is part of the MEDINA Framework. +metrics: + - name: "SecureCryptographicPrimitives" + rules: + - "Ciphers" + - "HashFunctions" + - "MessageAuthenticationCodes" + - "Signatures" + - "SecureRandom" + - "KeyAgreement" + configuration: + default: false + operator: "==" + type: BOOLEAN + target: + - true diff --git a/mark/bc-jca/rules.mark b/mark/bc-jca/rules.mark new file mode 100644 index 0000000000000000000000000000000000000000..50cac13828c07c9bbae3ffa9861cfc7d484f348a --- /dev/null +++ b/mark/bc-jca/rules.mark @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2020-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA framework. + */ +package de.fraunhofer.aisec.codyze.mark.bcjca + +/* + * Included providers + * - SUN version 17 + * - SunRsaSign version 17 + * - SunEC version 17 + * - SunJCE version 17 + * - BC version 1.76 + */ + +/** + * Check used ciphers transforms. + * + * Note: Recommendations are based on BSI TR-02102-1, version 2023-01 + * - Symmetric crypto + * - ciphers: AES-128, AES-192, AES-256 + * - cipher modes: CCM, GCM, CTR, CBC + * - padding (only for cipher mode CBC): ISO7816-4Padding, PKCS5Padding, PKCS7Padding + * - Asymmetric crypto + * - system/scheme: ECIES, DLIES, RSA + */ +rule Ciphers { + using + Cipher as c + ensure + c.transform in [ + /* SunJCE version 17 */ + /* AES with cipher modes CCM, GCM, CTR, CBC */ + "AES/CTR/NOPADDING", + "AES/CBC/PKCS5PADDING", + "AES/GCM/NoPadding", + "Cipher.AES_128/GCM/NoPadding", + "OID.2.16.840.1.101.3.4.1.6", "2.16.840.1.101.3.4.1.6", + "AES_192/GCM/NoPadding", + "OID.2.16.840.1.101.3.4.1.26", "2.16.840.1.101.3.4.1.26", + "AES_256/GCM/NoPadding", + "OID.2.16.840.1.101.3.4.1.46", "2.16.840.1.101.3.4.1.46", + /* AES Password based Encryption */ + "PBEWithHmacSHA256AndAES_128", + "PBEWithHmacSHA384AndAES_128", + "PBEWithHmacSHA512AndAES_128", + "PBEWithHmacSHA256AndAES_256", + "PBEWithHmacSHA384AndAES_256", + "PBEWithHmacSHA512AndAES_256", + /* RSA */ + "RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING", + "RSA/ECB/OAEPWITHSHA-512/256ANDMGF1PADDING", + "RSA/ECB/OAEPWITHSHA-384ANDMGF1PADDING", + "RSA/ECB/OAEPWITHSHA-512ANDMGF1PADDING", + /* BC version 1.76 */ + /* AES with cipher modes CCM, GCM, CTR, CBC */ + "CCM", + "2.16.840.1.101.3.4.1.7", "OID.2.16.840.1.101.3.4.1.7", "2.16.840.1.101.3.4.1.27", "OID.2.16.840.1.101.3.4.1.27", "2.16.840.1.101.3.4.1.47", "OID.2.16.840.1.101.3.4.1.47", "1.2.410.200046.1.1.37", "OID.1.2.410.200046.1.1.37", "1.2.410.200046.1.1.38", "OID.1.2.410.200046.1.1.38", "1.2.410.200046.1.1.39", "OID.1.2.410.200046.1.1.39", + "GCM", + "2.16.840.1.101.3.4.1.6", "OID.2.16.840.1.101.3.4.1.6", "2.16.840.1.101.3.4.1.26", "OID.2.16.840.1.101.3.4.1.26", "2.16.840.1.101.3.4.1.46", "OID.2.16.840.1.101.3.4.1.46", + /* AES Password based Encryption */ + "PBEWITHSHA256AND128BITAES-CBC-BC", + "1.3.6.1.4.1.22554.1.2.1.2.1.2", "OID.1.3.6.1.4.1.22554.1.2.1.2.1.2", "PBEWITHSHA-256AND128BITAES-CBC-BC", "PBEWITHSHA256AND128BITAES-BC", "PBEWITHSHA-256AND128BITAES-BC", + "PBEWITHSHA256AND192BITAES-CBC-BC", + "1.3.6.1.4.1.22554.1.2.1.2.1.22", "OID.1.3.6.1.4.1.22554.1.2.1.2.1.22", "PBEWITHSHA-256AND192BITAES-CBC-BC", "PBEWITHSHA256AND192BITAES-BC", "PBEWITHSHA-256AND192BITAES-BC", + "PBEWITHSHA256AND256BITAES-CBC-BC", + "1.3.6.1.4.1.22554.1.2.1.2.1.42", "OID.1.3.6.1.4.1.22554.1.2.1.2.1.42", "PBEWITHSHA-256AND256BITAES-CBC-BC", "PBEWITHSHA256AND256BITAES-BC", "PBEWITHSHA-256AND256BITAES-BC", + /* ECIES */ + "ECIESwithSHA256andAES-CBC", + "ECIESwithSHA384andAES-CBC", + "ECIESwithSHA512andAES-CBC" + /* DLIES */ + /* none */ + ] + fail +} + +/** + * Check used hash functions (aka message digest). + * + * Note: Recommendations are based on BSI TR-02102-1, version 2023-01 + * - Hash functions: SHA-256, SHA-512/256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512 + */ +rule HashFunctions { + using + MessageDigest as md + ensure + md.algorithm in [ + /* SUN version 17 */ + "SHA-256", + "OID.2.16.840.1.101.3.4.2.1", "2.16.840.1.101.3.4.2.1", "SHA256", + "SHA-512/256", + "OID.2.16.840.1.101.3.4.2.6", "2.16.840.1.101.3.4.2.6", "SHA512/256", + "SHA-384", + "OID.2.16.840.1.101.3.4.2.2", "2.16.840.1.101.3.4.2.2", "SHA384", + "SHA-512", + "OID.2.16.840.1.101.3.4.2.3", "2.16.840.1.101.3.4.2.3", "SHA512", + "SHA3-256", + "OID.2.16.840.1.101.3.4.2.8", "2.16.840.1.101.3.4.2.8", + "SHA3-384", + "OID.2.16.840.1.101.3.4.2.9", "2.16.840.1.101.3.4.2.9", + "SHA3-512", + "OID.2.16.840.1.101.3.4.2.10", "2.16.840.1.101.3.4.2.10", + /* BC version 1.76 */ + "SHA-256", + "SHA256", "2.16.840.1.101.3.4.2.1", + "SHA-512/256", + "SHA512/256", "SHA512256", "SHA-512(256)", "SHA512(256)", "2.16.840.1.101.3.4.2.6", + "SHA-384", + "SHA384", "2.16.840.1.101.3.4.2.2", + "SHA-512", + "SHA512", "2.16.840.1.101.3.4.2.3", + "SHA3-256", + "2.16.840.1.101.3.4.2.8", "OID.2.16.840.1.101.3.4.2.8", + "SHA3-384", + "2.16.840.1.101.3.4.2.9", "OID.2.16.840.1.101.3.4.2.9", + "SHA3-512", + "2.16.840.1.101.3.4.2.10", "OID.2.16.840.1.101.3.4.2.10" + ] + fail +} + +/** + * Check used message authentication codes. + * + * Note: Recommendations are based on BSI TR-02102-1, version 2023-01 + * - CMAC -> using recommended block ciphers + * - HMAC -> using recommended hash functions + * - GMAC -> using recommended block ciphers + */ +rule MessageAuthenticationCodes { + using + Mac as m + ensure + m.algorithm in [ + /* SunJCE version 17*/ + /* HMAC */ + "HmacPBESHA256", "HmacPBESHA512/256", "HmacPBESHA384", "HmacPBESHA512", + "PBEWithHmacSHA256", "PBEWithHmacSHA384", "PBEWithHmacSHA512", + "HmacSHA256", + "OID.1.2.840.113549.2.9", "1.2.840.113549.2.9", + "HmacSHA512/256", + "OID.1.2.840.113549.2.13", "1.2.840.113549.2.13", + "HmacSHA384", + "OID.1.2.840.113549.2.10", "1.2.840.113549.2.10", + "HmacSHA512", + "OID.1.2.840.113549.2.11", "1.2.840.113549.2.11", + "HmacSHA3-256", + "OID.2.16.840.1.101.3.4.2.14", "2.16.840.1.101.3.4.2.14", + "HmacSHA3-384", + "OID.2.16.840.1.101.3.4.2.15", "2.16.840.1.101.3.4.2.15", + "HmacSHA3-512", + "OID.2.16.840.1.101.3.4.2.16", "2.16.840.1.101.3.4.2.16", + /* BC version 1.76 */ + /* CMAC */ + "AESCMAC", + /* HMAC */ + "PBEWITHHMACSHA256", "PBEWITHHMACSHA384", "PBEWITHHMACSHA512", + "HMACSHA256", + "HMAC-SHA256", "HMAC/SHA256", "1.2.840.113549.2.9", "2.16.840.1.101.3.4.2.1", + "HMACSHA512/256", " + HMAC-SHA512/256", "HMAC/SHA512/256", + "HMACSHA384", + "HMAC-SHA384", "HMAC/SHA384", "1.2.840.113549.2.10", + "HMACSHA512", + "HMAC-SHA512", "HMAC/SHA512", "1.2.840.113549.2.11", + "HMACSHA3-256", + "HMAC-SHA3-256", "HMAC/SHA3-256", "2.16.840.1.101.3.4.2.14", + "HMACSHA3-384", + "HMAC-SHA3-384", "HMAC/SHA3-384", "2.16.840.1.101.3.4.2.15", + "HMACSHA3-512", + "HMAC-SHA3-512", "HMAC/SHA3-512", "2.16.840.1.101.3.4.2.16", + /* GMAC */ + "AES-GMAC", + "AESGMAC" + ] + fail +} + +rule Signatures { + using + Signature as s + ensure + s.algorithm in [ + /* SUN version 17 */ + /* DSA */ + "SHA256withDSA", + "OID.2.16.840.1.101.3.4.3.2", "2.16.840.1.101.3.4.3.2", + "SHA384withDSA", + "OID.2.16.840.1.101.3.4.3.3", "2.16.840.1.101.3.4.3.3", + "SHA512withDSA", + "OID.2.16.840.1.101.3.4.3.4", "2.16.840.1.101.3.4.3.4", + "SHA3-256withDSA", + "OID.2.16.840.1.101.3.4.3.6", "2.16.840.1.101.3.4.3.6", + "SHA3-384withDSA", + "OID.2.16.840.1.101.3.4.3.7", "2.16.840.1.101.3.4.3.7", + "SHA3-512withDSA", + "OID.2.16.840.1.101.3.4.3.8", "2.16.840.1.101.3.4.3.8", + "SHA256withDSAinP1363Format", + "SHA384withDSAinP1363Format", + "SHA512withDSAinP1363Format", + "SHA3-256withDSAinP1363Format", + "SHA3-384withDSAinP1363Format", + "SHA3-512withDSAinP1363Format", + /* SunRsaSign version 17 */ + /* RSA */ + /* none */ + /* SunEC version 17 */ + /* ECDSA */ + "SHA256withECDSA", + "OID.1.2.840.10045.4.3.2", "1.2.840.10045.4.3.2", + "SHA384withECDSA", + "OID.1.2.840.10045.4.3.3", "1.2.840.10045.4.3.3", + "SHA512withECDSA", + "OID.1.2.840.10045.4.3.4", "1.2.840.10045.4.3.4", + "SHA3-256withECDSA", + "OID.2.16.840.1.101.3.4.3.10", "2.16.840.1.101.3.4.3.10", + "SHA3-384withECDSA", + "OID.2.16.840.1.101.3.4.3.11", "2.16.840.1.101.3.4.3.11", + "SHA3-512withECDSA", + "OID.2.16.840.1.101.3.4.3.12", "2.16.840.1.101.3.4.3.12", + "SHA256withECDSAinP1363Format", + "SHA384withECDSAinP1363Format", + "SHA512withECDSAinP1363Format", + "SHA3-256withECDSAinP1363Format", + "SHA3-384withECDSAinP1363Format", + "SHA3-512withECDSAinP1363Format", + /* BC version 1.76*/ + /* RSA - EMSA-PSS */ + "SHA256WITHRSAANDMGF1", + "SHA256withRSA/PSS", "SHA256WithRSA/PSS", "SHA256WITHRSA/PSS", "SHA256withRSASSA-PSS", "SHA256WithRSASSA-PSS", "SHA256WITHRSASSA-PSS", "SHA256withRSAandMGF1", "SHA256WithRSAAndMGF1", + "SHA512(256)WITHRSAANDMGF1", + "SHA512(256)withRSA/PSS", "SHA512(256)WithRSA/PSS", "SHA512(256)WITHRSA/PSS", "SHA512(256)withRSASSA-PSS", "SHA512(256)WithRSASSA-PSS", "SHA512(256)WITHRSASSA-PSS", "SHA512(256)withRSAandMGF1", "SHA512(256)WithRSAAndMGF1", + "SHA384WITHRSAANDMGF1", + "SHA384withRSA/PSS", "SHA384WithRSA/PSS", "SHA384WITHRSA/PSS", "SHA384withRSASSA-PSS", "SHA384WithRSASSA-PSS", "SHA384WITHRSASSA-PSS", "SHA384withRSAandMGF1", "SHA384WithRSAAndMGF1", + "SHA512WITHRSAANDMGF1", + "SHA512withRSA/PSS", "SHA512WithRSA/PSS", "SHA512WITHRSA/PSS", "SHA512withRSASSA-PSS", "SHA512WithRSASSA-PSS", "SHA512WITHRSASSA-PSS", "SHA512withRSAandMGF1", "SHA512WithRSAAndMGF1", + "SHA3-256WITHRSAANDMGF1", + "SHA3-256withRSA/PSS", "SHA3-256WithRSA/PSS", "SHA3-256WITHRSA/PSS", "SHA3-256withRSASSA-PSS", "SHA3-256WithRSASSA-PSS", "SHA3-256WITHRSASSA-PSS", "SHA3-256withRSAandMGF1", "SHA3-256WithRSAAndMGF1", + "SHA3-384WITHRSAANDMGF1", + "SHA3-384withRSA/PSS", "SHA3-384WithRSA/PSS", "SHA3-384WITHRSA/PSS", "SHA3-384withRSASSA-PSS", "SHA3-384WithRSASSA-PSS", "SHA3-384WITHRSASSA-PSS", "SHA3-384withRSAandMGF1", "SHA3-384WithRSAAndMGF1", + "SHA3-512WITHRSAANDMGF1", + "SHA3-512withRSA/PSS", "SHA3-512WithRSA/PSS", "SHA3-512WITHRSA/PSS", "SHA3-512withRSASSA-PSS", "SHA3-512WithRSASSA-PSS", "SHA3-512WITHRSASSA-PSS", "SHA3-512withRSAandMGF1", "SHA3-512WithRSAAndMGF1", + /* RSA - Digital Signature Scheme (DS) 2 und 3 */ + "SHA256WITHRSA/ISO9796-2", + "SHA256withRSA/ISO9796-2", "SHA256WithRSA/ISO9796-2", + "SHA512(256)WITHRSA/ISO9796-2", + "SHA512(256)withRSA/ISO9796-2", "SHA512(256)WithRSA/ISO9796-2", + "SHA384WITHRSA/ISO9796-2", + "SHA384withRSA/ISO9796-2", "SHA384WithRSA/ISO9796-2", + "SHA512WITHRSA/ISO9796-2", + "SHA512withRSA/ISO9796-2", "SHA512WithRSA/ISO9796-2", + /* DSA */ + "SHA256WITHDSA", + "SHA256withDSA", "SHA256WithDSA", "SHA256/DSA", "2.16.840.1.101.3.4.3.2", "OID.2.16.840.1.101.3.4.3.2", + "SHA384WITHDSA", + "SHA384withDSA", "SHA384WithDSA", "SHA384/DSA", "2.16.840.1.101.3.4.3.3", "OID.2.16.840.1.101.3.4.3.3", + "SHA512WITHDSA", + "SHA512withDSA", "SHA512WithDSA", "SHA512/DSA", "2.16.840.1.101.3.4.3.4", "OID.2.16.840.1.101.3.4.3.4", + "SHA3-256WITHDSA", + "SHA3-256withDSA", "SHA3-256WithDSA", "SHA3-256/DSA", "2.16.840.1.101.3.4.3.6", "OID.2.16.840.1.101.3.4.3.6", + "SHA3-384WITHDSA", + "SHA3-384withDSA", "SHA3-384WithDSA", "SHA3-384/DSA", "2.16.840.1.101.3.4.3.7", "OID.2.16.840.1.101.3.4.3.7", + "SHA3-512WITHDSA", + "SHA3-512withDSA", "SHA3-512WithDSA", "SHA3-512/DSA", "2.16.840.1.101.3.4.3.8", "OID.2.16.840.1.101.3.4.3.8", + /* DSA - deterministic */ + "SHA256WITHDDSA", + "SHA384WITHDDSA", + "SHA512WITHDDSA", + "SHA3-256WITHDDSA", + "SHA3-384WITHDDSA", + "SHA3-512WITHDDSA", + "SHA256WITHDETDSA", + "SHA384WITHDETDSA", + "SHA512WITHDETDSA", + /* ECDSA */ + "SHA256WITHECDSA", + "SHA256withECDSA", "SHA256WithECDSA", "SHA256/ECDSA", "1.2.840.10045.4.3.2", "OID.1.2.840.10045.4.3.2", + "SHA384WITHECDSA", + "SHA384withECDSA", "SHA384WithECDSA", "SHA384/ECDSA", "1.2.840.10045.4.3.3", "OID.1.2.840.10045.4.3.3", + "SHA512WITHECDSA", + "SHA512withECDSA", "SHA512WithECDSA", "SHA512/ECDSA", "1.2.840.10045.4.3.4", "OID.1.2.840.10045.4.3.4", + "SHA3-256WITHECDSA", + "SHA3-256withECDSA", "SHA3-256WithECDSA", "SHA3-256/ECDSA", "2.16.840.1.101.3.4.3.10", "OID.2.16.840.1.101.3.4.3.10", + "SHA3-384WITHECDSA", + "SHA3-384withECDSA", "SHA3-384WithECDSA", "SHA3-384/ECDSA", "2.16.840.1.101.3.4.3.11", "OID.2.16.840.1.101.3.4.3.11", + "SHA3-512WITHECDSA", + "SHA3-512withECDSA", "SHA3-512WithECDSA", "SHA3-512/ECDSA", "2.16.840.1.101.3.4.3.12", "OID.2.16.840.1.101.3.4.3.12", + "SHA256WITHCVC-ECDSA", + "SHA256withCVC-ECDSA", "SHA256WithCVC-ECDSA", "SHA256/CVC-ECDSA", "0.4.0.127.0.7.2.2.2.2.3", "OID.0.4.0.127.0.7.2.2.2.2.3", + "SHA384WITHCVC-ECDSA", + "SHA384withCVC-ECDSA", "SHA384WithCVC-ECDSA", "SHA384/CVC-ECDSA", "0.4.0.127.0.7.2.2.2.2.4", "OID.0.4.0.127.0.7.2.2.2.2.4", + "SHA512WITHCVC-ECDSA", + "SHA512withCVC-ECDSA", "SHA512WithCVC-ECDSA", "SHA512/CVC-ECDSA", "0.4.0.127.0.7.2.2.2.2.5", "OID.0.4.0.127.0.7.2.2.2.2.5", + "SHA256WITHPLAIN-ECDSA", + "SHA256withPLAIN-ECDSA", "SHA256WithPLAIN-ECDSA", "SHA256/PLAIN-ECDSA", "0.4.0.127.0.7.1.1.4.1.3", "OID.0.4.0.127.0.7.1.1.4.1.3", + "SHA384WITHPLAIN-ECDSA", + "SHA384withPLAIN-ECDSA", "SHA384WithPLAIN-ECDSA", "SHA384/PLAIN-ECDSA", "0.4.0.127.0.7.1.1.4.1.4", "OID.0.4.0.127.0.7.1.1.4.1.4", + "SHA512WITHPLAIN-ECDSA", + "SHA512withPLAIN-ECDSA", "SHA512WithPLAIN-ECDSA", "SHA512/PLAIN-ECDSA", "0.4.0.127.0.7.1.1.4.1.5", "OID.0.4.0.127.0.7.1.1.4.1.5", + "SHA3-256WITHPLAIN-ECDSA", + "SHA3-256withPLAIN-ECDSA", "SHA3-256WithPLAIN-ECDSA", "SHA3-256/PLAIN-ECDSA", "0.4.0.127.0.7.1.1.4.1.9", "OID.0.4.0.127.0.7.1.1.4.1.9", + "SHA3-384WITHPLAIN-ECDSA", + "SHA3-384withPLAIN-ECDSA", "SHA3-384WithPLAIN-ECDSA", "SHA3-384/PLAIN-ECDSA", "0.4.0.127.0.7.1.1.4.1.10", "OID.0.4.0.127.0.7.1.1.4.1.10", + "SHA3-512WITHPLAIN-ECDSA", + "SHA3-512withPLAIN-ECDSA", "SHA3-512WithPLAIN-ECDSA", "SHA3-512/PLAIN-ECDSA", "0.4.0.127.0.7.1.1.4.1.11", "OID.0.4.0.127.0.7.1.1.4.1.11", + /* ECDSA - deterministic */ + "SHA256WITHECDDSA", + "SHA256WITHDETECDSA", + "SHA384WITHECDDSA", + "SHA384WITHDETECDSA", + "SHA512WITHECDDSA", + "SHA512WITHDETECDSA", + "SHA3-256WITHECDDSA", + "SHA3-384WITHECDDSA", + "SHA3-512WITHECDDSA" + ] + fail +} + +/** + * Check used message authentication codes. + * + * Note: Recommendations are based on BSI TR-02102-1, version 2023-01 + */ +rule SecureRandom { + using + SecureRandom as sr + ensure + sr.algorithm in [ + /* SUN version 17 */ + "NativePRNGBlocking", + "DRBG", + /* BC version 1.76 */ + "DEFAULT", + "NONCEANDIV" + ] + fail +} + +/** + * Check used key agreement protocols. + * + * Note: Recommendations are based on BSI TR-02102-1, version 2023-01 + */ +rule KeyAgreement { + using + KeyAgreement as ka + ensure + ka.algorithm in [ + /* BC version 1.76 */ + /* DH */ + "DHWITHSHA256KDF", + "DHWITHSHA384KDF", + "DHWITHSHA512KDF", + /* ECDH / ECCDH */ + "ECDHWITHSHA256KDF", + "1.3.132.1.11.1", "OID.1.3.132.1.11.1", + "ECDHWITHSHA384KDF", + "1.3.132.1.11.2", "OID.1.3.132.1.11.2", + "ECDHWITHSHA512KDF", + "1.3.132.1.11.3", "OID.1.3.132.1.11.3", + "ECCDHWITHSHA256KDF", + "1.3.132.1.14.1", "OID.1.3.132.1.14.1", + "ECCDHWITHSHA384KDF", + "1.3.132.1.14.2", "OID.1.3.132.1.14.2", + "ECCDHWITHSHA512KDF", + "1.3.132.1.14.3", "OID.1.3.132.1.14.3", + /* ECKA-EG */ + "ECKAEGWITHSHA256KDF", + "0.4.0.127.0.7.1.1.5.1.1.3", "OID.0.4.0.127.0.7.1.1.5.1.1.3", + "ECKAEGWITHSHA384KDF", + "0.4.0.127.0.7.1.1.5.1.1.4", "OID.0.4.0.127.0.7.1.1.5.1.1.4", + "ECKAEGWITHSHA512KDF", + "0.4.0.127.0.7.1.1.5.1.1.5", "OID.0.4.0.127.0.7.1.1.5.1.1.5" + ] + fail +} diff --git a/mark/bc-jsse/LICENSE b/mark/bc-jsse/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/mark/bc-jsse/LICENSE @@ -0,0 +1,202 @@ + + 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/mark/bc-jsse/README.md b/mark/bc-jsse/README.md index df628309017079fb171d8de0f9c2863ed511b02e..059f36f3da886ef4a68a5682a379ee9d088ff105 100644 --- a/mark/bc-jsse/README.md +++ b/mark/bc-jsse/README.md @@ -1,3 +1,5 @@ # MARK for Bouncy Castle's JSSE Provider -Example MARK specification files for a TLS server implemented using JSSE with Bouncy Castle as provide. The rule checks for an appropriate version of TLS. +## License +These files are part of the [MEDINA project](https://medina-project.eu/). +They are distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). \ No newline at end of file diff --git a/mark/bc-jsse/SSLContext.mark b/mark/bc-jsse/SSLContext.mark index d078c67a8af2e4d522a052f682f7c3634d3edba0..528825c72eec62d04e4f20cb38b71d01c95a7fdf 100644 --- a/mark/bc-jsse/SSLContext.mark +++ b/mark/bc-jsse/SSLContext.mark @@ -1,3 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ package de.fraunhofer.aisec.codyze.bcjsse entity SSLContext { diff --git a/mark/bc-jsse/SSLServerSocket.mark b/mark/bc-jsse/SSLServerSocket.mark index 6be07b7fab715e22196eda84e68470003bea4f82..a8a38837f7fd437bddfc26f84355517797859af6 100644 --- a/mark/bc-jsse/SSLServerSocket.mark +++ b/mark/bc-jsse/SSLServerSocket.mark @@ -1,3 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ package de.fraunhofer.aisec.codyze.bcjsse entity SSLServerSocket { diff --git a/mark/bc-jsse/SSLServerSocketFactory.mark b/mark/bc-jsse/SSLServerSocketFactory.mark index 7521debd77e5f1defe6986b7df11855ccaaa356d..91edaa6bb4f1e94dfd0945c7a3915923838374df 100644 --- a/mark/bc-jsse/SSLServerSocketFactory.mark +++ b/mark/bc-jsse/SSLServerSocketFactory.mark @@ -1,3 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ package de.fraunhofer.aisec.codyze.bcjsse entity SSLServerSocketFactory { diff --git a/mark/bc-jsse/findingDescription.json b/mark/bc-jsse/findingDescription.json new file mode 100644 index 0000000000000000000000000000000000000000..7e9790b9aa1dfb8a9198e591994b995e3b7cb3f9 --- /dev/null +++ b/mark/bc-jsse/findingDescription.json @@ -0,0 +1,32 @@ +{ + "TlsVersion": { + "fullDescription": { + "text": "TLS version isn't sufficiently restricted and may allow the use of deprecated version. Ensure the use of TLS versions 1.2 or 1.3." + }, + "shortDescription": { + "text": "Insufficient restriction of TLS to version 1.2 or 1.3." + }, + "fixes": [ + { + "description": { + "text": "Use TLS version 1.2 or 1.3." + } + } + ] + }, + "TlsCipherSuites": { + "fullDescription": { + "text": "TLS cipher suites aren't sufficiently restricted and may allow the use of less secure or deprecated cipher suites. Ensure the use of: \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_ECDSA_WITH_AES_128_CCM\", \"TLS_ECDHE_ECDSA_WITH_AES_256_CCM\", \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256\", \"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384\", \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256\", \"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_DHE_RSA_WITH_AES_128_CCM\", \"TLS_DHE_RSA_WITH_AES_256_CCM\", \"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_DH_DSS_WITH_AES_128_CBC_SHA256\", \"TLS_DH_DSS_WITH_AES_256_CBC_SHA256\", \"TLS_DH_DSS_WITH_AES_128_GCM_SHA256\", \"TLS_DH_DSS_WITH_AES_256_GCM_SHA384\", \"TLS_DH_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_DH_RSA_WITH_AES_256_CBC_SHA256\", \"TLS_DH_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_DH_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256\", \"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256\", \"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384\", \"TLS_DHE_PSK_WITH_AES_128_GCM_SHA256\", \"TLS_DHE_PSK_WITH_AES_256_GCM_SHA38\", \"TLS_DHE_PSK_WITH_AES_128_CCM\", \"TLS_DHE_PSK_WITH_AES_256_CCM\", \"TLS_RSA_PSK_WITH_AES_128_CBC_SHA256\", \"TLS_RSA_PSK_WITH_AES_256_CBC_SHA384\", \"TLS_RSA_PSK_WITH_AES_128_GCM_SHA256\", or \"TLS_RSA_PSK_WITH_AES_256_GCM_SHA384\"." + }, + "shortDescription": { + "text": "Insufficient restriction of TLS cipher suites." + }, + "fixes": [ + { + "description": { + "text": "Use any selection of the following TLS cipher suites: \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_ECDSA_WITH_AES_128_CCM\", \"TLS_ECDHE_ECDSA_WITH_AES_256_CCM\", \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256\", \"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384\", \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256\", \"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_DHE_RSA_WITH_AES_128_CCM\", \"TLS_DHE_RSA_WITH_AES_256_CCM\", \"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_DH_DSS_WITH_AES_128_CBC_SHA256\", \"TLS_DH_DSS_WITH_AES_256_CBC_SHA256\", \"TLS_DH_DSS_WITH_AES_128_GCM_SHA256\", \"TLS_DH_DSS_WITH_AES_256_GCM_SHA384\", \"TLS_DH_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_DH_RSA_WITH_AES_256_CBC_SHA256\", \"TLS_DH_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_DH_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256\", \"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256\", \"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384\", \"TLS_DHE_PSK_WITH_AES_128_GCM_SHA256\", \"TLS_DHE_PSK_WITH_AES_256_GCM_SHA38\", \"TLS_DHE_PSK_WITH_AES_128_CCM\", \"TLS_DHE_PSK_WITH_AES_256_CCM\", \"TLS_RSA_PSK_WITH_AES_128_CBC_SHA256\", \"TLS_RSA_PSK_WITH_AES_256_CBC_SHA384\", \"TLS_RSA_PSK_WITH_AES_128_GCM_SHA256\", or \"TLS_RSA_PSK_WITH_AES_256_GCM_SHA384\"." + } + } + ] + } +} \ No newline at end of file diff --git a/mark/bc-jsse/mapping.yaml b/mark/bc-jsse/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d160855e80fad14abc9b28e4ab2c05bd44ec9979 --- /dev/null +++ b/mark/bc-jsse/mapping.yaml @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# +# _____ _ +# / ____| | | +# | | ___ __| |_ _ _______ +# | | / _ \ / _` | | | |_ / _ \ +# | |___| (_) | (_| | |_| |/ / __/ +# \_____\___/ \__,_|\__, /___\___| +# __/ | +# |___/ +# +# This file is part of the MEDINA Framework. +metrics: + - name: "TLSVersion" + rules: + - "TlsVersion" + configuration: + default: true + operator: ">" + type: STRING + target: + - "1.2" + - name: "TlsCipherSuites" + rules: + - "TlsCipherSuites" + configuration: + default: true + operator: "<=" + type: STRING + target: + - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" + - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" + - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" + - "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" + - "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" + - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" + - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" + - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" + - "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" + - "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" + - "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" + - "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" + - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" + - "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" + - "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" + - "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" + - "TLS_DHE_RSA_WITH_AES_128_CCM" + - "TLS_DHE_RSA_WITH_AES_256_CCM" + - "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" + - "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" + - "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" + - "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" + - "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" + - "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" + - "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" + - "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" + - "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" + - "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" + - "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" + - "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" + - "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" + - "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" + - "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" + - "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" + - "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256" + - "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384" + - "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256" + - "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384" + - "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256" + - "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" + - "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384" + - "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" + - "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" + - "TLS_DHE_PSK_WITH_AES_128_CCM" + - "TLS_DHE_PSK_WITH_AES_256_CCM" + - "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" + - "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" + - "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" + - "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" + - name: "TlsDHGroups" + rules: + - "TlsDHGroups" + configuration: + default: true + operator: "<=" + type: STRING + target: + - "secp256r1" + - "secp384r1" + - "secp521r1" + - "brainpoolP256r1" + - "brainpoolP384r1" + - "brainpoolP512r1" + - "ffdhe2048" + - "ffdhe3072" + - "ffdhe4096" + - "brainpoolP256r1tls13" + - "brainpoolP384r1tls13" + - "brainpoolP512r1tls13" + - name: "TlsSignatureAlgorithms" + rules: + - "TlsSignatureAlgorithm" + configuration: + default: true + operator: "<=" + type: STRING + target: + - "RSA+SHA256" + - "RSA+SHA384" + - "RSA+SHA512" + - "DSA+SHA256" + - "DSA+SHA384" + - "DSA+SHA512" + - "ECDSA+SHA256" + - "ECDSA+SHA384" + - "ECDSA+SHA512" + - "rsa_pss_rsae_sha256" + - "rsa_pss_rsae_sha384" + - "rsa_pss_rsae_sha512" + - "rsa_pss_pss_sha256" + - "rsa_pss_pss_sha384" + - "rsa_pss_pss_sha512" + - "ecdsa_secp256r1_sha256" + - "ecdsa_secp384r1_sha384" + - "ecdsa_brainpoolP256r1tls13_sha256" + - "ecdsa_brainpoolP384r1tls13_sha384" + - "ecdsa_brainpoolP512r1tls13_sha512" + - "rsa_pkcs1_sha256" + - "rsa_pkcs1_sha384" + - "rsa_pkcs1_sha512" diff --git a/mark/bc-jsse/rules.mark b/mark/bc-jsse/rules.mark index 0bf5ca5ddad5b749e05afa3608eaa02e0a27fdc6..0a293e693be51c5171ed0e63c61cf77ab3e31a0c 100644 --- a/mark/bc-jsse/rules.mark +++ b/mark/bc-jsse/rules.mark @@ -1,9 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ package de.fraunhofer.aisec.codyze.mark.bcjsse -rule ID_1 { +rule TlsVersion { using SSLServerSocket as socket ensure _subset(socket.protocols, ["TLSv1.2", "TLSv1.3"]) fail } + +rule TlsCipherSuites { + using + SSLServerSocket as socket + ensure + _subset(socket.ciphersuites, + ["TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_128_CCM", + "TLS_DHE_RSA_WITH_AES_256_CCM", + "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", + "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", + "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256", + "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", + "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", + "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", + "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", + "TLS_DHE_PSK_WITH_AES_128_CCM", + "TLS_DHE_PSK_WITH_AES_256_CCM", + "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", + "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", + "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", + "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384"]) + fail +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 2f5d7f5892f45796012ea0406641d1fc96c57db1..5ea33066feec66cbda39e8e2dfd4689a9017da4f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "codyze" +rootProject.name = "codyze-medina" include("generator_orchestrator") project(":generator_orchestrator").projectDir = file("build/generator_orchestrator") diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeMedina.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeMedina.kt deleted file mode 100644 index d67383b201aba094334105d6bebbe42a3501c28a..0000000000000000000000000000000000000000 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeMedina.kt +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - * - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ - * | | / _ \ / _` | | | |_ / _ \ - * | |___| (_) | (_| | |_| |/ / __/ - * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ - * - * This file is part of the MEDINA Framework. - */ -package de.fraunhofer.aisec.codyze.medina - -import de.fraunhofer.aisec.codyze.analysis.AnalysisServer -import de.fraunhofer.aisec.codyze.analysis.Finding -import de.fraunhofer.aisec.codyze.medina.util.* -import java.util.concurrent.Callable -import java.util.concurrent.TimeUnit -import kotlin.streams.toList -import kotlin.system.exitProcess -import mu.KotlinLogging -import org.openapitools.client.orchestrator.model.Metric -import picocli.CommandLine -import picocli.CommandLine.Command -import picocli.CommandLine.Mixin - -@Command(name = "codyze", mixinStandardHelpOptions = true) -class CodyzeMedina(config: Configuration, args: Array<String>) : Callable<Int> { - private val logger = KotlinLogging.logger {} - @Mixin val configuration: Configuration = config - private val cmdlineArgs = args - - /** - * Callable function containing the core program logic - * @author Florian Wendland - * @author Robert Haimerl - * @return 1 when any of the Findings is problematic, 0 else - */ - override fun call(): Int { - configuration.validate() - - val connection = - Connection( - configuration.orchestrator.orchestratorEndpoint.toString(), - configuration.orchestrator.auth.oauthEndpoint.toString(), - configuration.orchestrator.auth.username, - configuration.orchestrator.auth.password - ) - - val config = - de.fraunhofer.aisec.codyze.config.Configuration.initConfig( - configuration.configFile.path.toFile(), - *cmdlineArgs, - "--default-passes", - "--passes+", - "IdentifierPass", - "--passes+", - "EdgeCachePass", - ) - - // do not allow LSP/TUI - if (config.executionMode.isLsp || config.executionMode.isTui) { - logger.warn { "Forbidden execution mode, changing to CLI" } - config.executionMode.isCli = true - config.executionMode.isLsp = false - config.executionMode.isTui = false - } - - val server = AnalysisServer(config) - server.start() - val ctx = server.analyze(config.source)[config.timeout, TimeUnit.MINUTES] - val findings = ctx.findings - - environment = configuration.ci - if (environment == Configuration.Environment.NONE) verifyEnvironment() - addEnvironmentVariables() - fetchGitHash() - printFindings(findings, config) - - parseMapping("mappings.txt") - - // convert the Set<Metric> to a List<String> consisting of the metric names - val metricNameList = - connection - .fetchMetrics() - .stream() - .map { m: Metric -> m.name } - .filter { n: String? -> n != null } - .map { n: String? -> n!! } - .toList() - val assessmentTool = createAssessmentTool(metricNameList) - val evidence = createEvidence(config.output) - val results = - findings - .stream() - .map { f -> findingToAR(f, evidence.id ?: "") } - .toList() - .filterNotNull() - .toTypedArray() - - // FIXME not implemented in orchestrator yet - // connection.registerAssessmentTool(assessmentTool) - connection.storeEvidence(evidence) - connection.sendAssessmentResults(results) - - // Return code based on the existence of violations - return if (findings.stream().anyMatch { obj: Finding -> obj.isProblem }) 1 else 0 - } -} - -private val logger = KotlinLogging.logger {} - -/** - * Entry point of the program - * @args cli arguments provided - * @author Florian Wendland - * @author Robert Haimerl - */ -fun main(args: Array<String>) { - // initialize configuration - val config = Configuration.initialize(args) - // parse remaining CLI options and overwrite defaults and options from config file - val returnValue = - CommandLine(CodyzeMedina(config, args)).setUnmatchedArgumentsAllowed(true).execute(*args) - logger.info { "Exit with return value: $returnValue" } - exitProcess(returnValue) -} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/Connection.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/Connection.kt deleted file mode 100644 index ec560a233121847ea849d95ba8ac9cad3a7e11fe..0000000000000000000000000000000000000000 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/Connection.kt +++ /dev/null @@ -1,329 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - * - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ - * | | / _ \ / _` | | | |_ / _ \ - * | |___| (_) | (_| | |_| |/ / __/ - * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ - * - * This file is part of the MEDINA Framework. - */ -package de.fraunhofer.aisec.codyze.medina - -import de.fraunhofer.aisec.codyze.medina.auth.* -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.Socket -import java.net.URL -import mu.KotlinLogging -import org.dmfs.oauth2.client.OAuth2AccessToken -import org.openapitools.client.evidence.api.EvidenceStoreApi -import org.openapitools.client.evidence.model.Evidence -import org.openapitools.client.orchestrator.api.OrchestratorApi -import org.openapitools.client.orchestrator.model.AssessmentResult -import org.openapitools.client.orchestrator.model.AssessmentTool -import org.openapitools.client.orchestrator.model.Metric - -/** - * the Connection Object handles the OAuth2 Token as well as the communication with the Orchestrator - * @author Robert Haimerl - * @param orchestratorURL the URL of the Orchestrator instance - * @param tokenURL the URL of the OAuth2 instance - * @param username the OAuth2 username - * @param password the OAuth2 password - */ -class Connection( - private val orchestratorURL: String, - private val tokenURL: String, - username: String, - password: String -) { - private val logger = KotlinLogging.logger {} - private val authClient = getOAuth2Client(tokenURL, username, password) - private val orchestratorClient = - org.openapitools.client.orchestrator.ApiClient().setBasePath(orchestratorURL) - private val orchestratorApi = OrchestratorApi(orchestratorClient) - private val evidenceClient = - org.openapitools.client.evidence.ApiClient().setBasePath(orchestratorURL) - private val evidenceApi = EvidenceStoreApi(evidenceClient) - // Fetches a token instantly after creating the Connection object - private var token: OAuth2AccessToken? = getToken() - - /** - * Test the connection to the OAuth2-Servers - * @author Robert Haimerl - * @return whether a connection could be established - */ - fun testOAuthConnection(): Boolean { - logger.info { "Testing the connection to the OAuth-Server" } - // fist do a simple ping - val tokenHost = URL(tokenURL).host - if (!isReachable(InetAddress.getByName(tokenHost), 80, 2000)) { - logger.error { "Failed to ping OAuth-Server" } - return false - } - // then try to request a token with the given credentials - if (getAccessToken(authClient) == null) { - logger.error { "Failed to retrieve a token with the given credentials" } - return false - } - return true - } - - /** - * Test the connection to the Orchestrator-Server - * @author Robert Haimerl - * @return whether a connection could be established - */ - fun testOrchestratorConnection(): Boolean { - logger.info { "Testing the connection to the Orchestrator" } - // first ping the server - val orchestratorHost = URL(orchestratorURL).host - if (!isReachable(InetAddress.getByName(orchestratorHost), 80, 2000)) { - logger.error { "Failed to ping orchestrator" } - return false - } - // then try to make a simple get request - applyToken() - return isTokenValid() - } - - /** - * retrieves access token with the client created by the given credentials - * @author Robert Haimerl - * @return the token or null if there was an error - */ - private fun getToken(): OAuth2AccessToken? { - return getAccessToken(authClient) - } - - /** - * applies the OAuth2-Token to both the Orchestrator Client and the Evidence Client - * @author Robert Haimerl - * @return whether there was a token that could be applied - */ - private fun applyToken(): Boolean { - val tk = token ?: return false - orchestratorClient.addDefaultHeader( - "Authorization", - "${tk.tokenType()} ${tk.accessToken()}" - ) - evidenceClient.addDefaultHeader("Authorization", "${tk.tokenType()} ${tk.accessToken()}") - logger.info { "Access token successfully applied to the connection" } - return true - } - - /** - * Sends the AssessmentResults to the orchestrator - * @author Robert Haimerl - * @param ar the AssessmentResults to send - * @param retryOnError whether it should retry the process on a connection error - * @return whether sending was successful - */ - fun sendAssessmentResults(ar: Array<AssessmentResult>?, retryOnError: Boolean = true): Boolean { - if (ar == null || ar.isEmpty()) { - logger.warn { "No AssessmentResults to send" } - return true - } - if (token == null) { - logger.error { "No auth token: aborting" } - return false - } - if (!isTokenValid()) { - logger.warn { "Token invalid, requesting a new one" } - token = getToken() - applyToken() - } - var nextElem = 0 - try { - logger.info { - "trying to send AssessmentResults to ${orchestratorApi.apiClient.basePath}" - } - for (result in ar) { - orchestratorApi.orchestratorStoreAssessmentResult(result) - nextElem++ - logger.debug { "Sent assessment result with id " + result.id } - } - } catch (ae: org.openapitools.client.orchestrator.ApiException) { - logger.error { "Could not send to ${orchestratorApi.apiClient.basePath}" } - return if (retryOnError && ae.code == 401) { - logger.info { "Retrying transmission process..." } - // retries the transmission process starting from the failed element - sendAssessmentResults(ar.copyOfRange(nextElem, ar.size), false) - } else false - } - logger.info { "All assessment results sent to ${orchestratorApi.apiClient.basePath}" } - return true - } - - // FIXME: methods surrounding assessment tools seem to be unimplemented - /** - * Registers an AssessmentTool. This previously checks whether the tool was already registered - * @author Robert Haimerl - * @param tool the AssessmentTool to register - * @return whether the tool could be registered successfully - */ - fun registerAssessmentTool(tool: AssessmentTool): Boolean { - if (!confirmToken()) return false - logger.info { "Trying to set the assessment tool to \"${tool.name}\" (${tool.id})" } - // first check whether the tool is already registered - return try { - val registeredTool = orchestratorApi.orchestratorGetAssessmentTool(tool.id) - if (registeredTool == null) { - // if no such tool is known (null returned): try to register - try { - orchestratorApi.orchestratorRegisterAssessmentTool(tool) - logger.info { "Successfully registered new tool" } - true - } catch (e: org.openapitools.client.orchestrator.ApiException) { - logger.error { "Failed to register assessment tool" } - false - } - } else if (!registeredTool.equals(tool)) { - // if the id is known, but it is not identical: try to update - try { - orchestratorApi.orchestratorUpdateAssessmentTool(tool.id, tool) - logger.info { "Successfully updated the tool" } - true - } catch (e: org.openapitools.client.orchestrator.ApiException) { - logger.error { "Failed to update assessment tool" } - false - } - } else { - // if the tool is known and identical: no action required - logger.info { "Tool was already registered" } - true - } - } catch (e: org.openapitools.client.orchestrator.ApiException) { - logger.error { "Failed to fetch assessment tool with id ${tool.id}" } - false - } - } - - /** - * Stores the Evidence in the Orchestrator - * @author Robert Haimerl - * @param evidence the Evidence to store - * @return whether the Evidence could be successfully stored - */ - fun storeEvidence(evidence: Evidence): Boolean { - if (!confirmToken()) return false - return try { - val res = evidenceApi.evidenceStoreStoreEvidence(evidence) - logger.info { "Successfully stored evidence with id ${evidence.id}" } - logger.info { - "Response has status '${res.status}' with status message '${res.statusMessage}'" - } - true - } catch (e: org.openapitools.client.evidence.ApiException) { - logger.error { "Failed to store evidence with id ${evidence.id}" } - false - } - } - - /** - * Fetches all available metrics from the catalog - * @author Robert Haimerl - * @return a Set of the metrics - */ - fun fetchMetrics(): Set<Metric> { - if (!confirmToken()) return setOf() - val metricSet = mutableSetOf<Metric>() - var nextPage: String? = null - try { - do { - val response = orchestratorApi.orchestratorListMetrics(null, nextPage, null, null) - if (response.metrics != null) metricSet.addAll(response.metrics!!) - nextPage = response.nextPageToken - } while (nextPage != null) - } catch (e: org.openapitools.client.orchestrator.ApiException) { - logger.error { "Failed to fetch the metrics from the catalog" } - } catch (e: ClassCastException) { - logger.error { "Response could not be parsed properly" } - } - return metricSet - } - - /** - * Checks if the given address/port combination is reachable. Used since some hosts do not - * respond to regular isReachable on port 7 - * @author Robert Haimerl - * @param addr the INetAddress we try to ping - * @param port the according port - * @param timeout when every attempt should time out (a maximum of two connection attempts are - * made) - * @return whether the address could be reached - */ - @Suppress("SameParameterValue") - private fun isReachable(addr: InetAddress, port: Int = 0, timeout: Int = 2000): Boolean { - // first try default isReachable (which tries ICMP or port 7) - if (!addr.isReachable(timeout)) { - // otherwise, try manual connection to specified port - if (port < 0 || port > 65535) return false - val sockaddr = InetSocketAddress(addr, port) - val sock = Socket() - try { - sock.connect(sockaddr, timeout) - } catch (exc: Exception) { - return false - } - } - return true - } - - /** - * Checks if the current OAuth-token is still valid (uses a simple GET call) - * @author Robert Haimerl - * @returns false on a APIException - */ - private fun isTokenValid(): Boolean { - return try { - applyToken() - orchestratorApi.orchestratorListAssessmentResults(1, null, null, null) - true - } catch (e: org.openapitools.client.orchestrator.ApiException) { - e.printStackTrace() - false - } - } - - /** - * Checks the OAuth-token and requests a new one if the current token is invalid - * @author Robert Haimerl - * @return false if we could not get a valid token - */ - private fun confirmToken(): Boolean { - if (token == null) { - logger.error { "No auth token: aborting" } - return false - } - if (!isTokenValid()) { - logger.warn { "Token invalid, requesting a new one" } - token = getToken() - applyToken() - if (!isTokenValid()) { - logger.error { "Re-requested token is also invalid: aborting" } - return false - } - } - return true - } -} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/Assembler.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/Assembler.kt new file mode 100644 index 0000000000000000000000000000000000000000..2767a9671a3424c348ba9938be499e5599f6dd8c --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/Assembler.kt @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.assembling + +import de.fraunhofer.aisec.codyze.analysis.Finding +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import de.fraunhofer.aisec.codyze.medina.evaluation.Rule +import de.fraunhofer.aisec.codyze.medina.main.CodyzeMedina +import de.fraunhofer.aisec.codyze.medina.mapping.Mapping +import de.fraunhofer.aisec.codyze.medina.mapping.RESOURCE_TYPE +import de.fraunhofer.aisec.codyze.medina.mapping.Type +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.IOException +import java.nio.file.Path +import java.util.* +import kotlin.io.path.readText +import org.openapitools.client.evidence.model.Evidence +import org.openapitools.client.orchestrator.model.AssessmentResult +import org.openapitools.client.orchestrator.model.MetricConfiguration + +class Assembler(private val environment: Environment, private val serviceId: String) { + private val logger = KotlinLogging.logger {} + private val toolId = "codyze-medina-${CodyzeMedina.CODYZE_VERSION}" + + /** + * Creates an Evidence based on a Codyze or MEDINA result + * + * @param resultFilePath the path to the results file used as evidence + * @return the resulting Evidence + * @author Robert Haimerl + */ + fun createEvidence(resultFilePath: Path): Evidence { + logger.debug { "Creating new Evidence based on $resultFilePath" } + val ev = Evidence() + + ev.id = UUID.randomUUID().toString() + ev.timestamp = java.time.OffsetDateTime.now() + ev.cloudServiceId = serviceId + ev.toolId = toolId + ev.raw = + try { + resultFilePath.readText() + } catch (e: IOException) { + logger.warn { + "Evidence with id ${ev.id} has no raw content since the results file at $resultFilePath could not be read" + } + "" + } + ev.resource = + object { + @Suppress("unused") val id = environment.getGitHash() + } + return ev + } + + /** + * Creates an AssessmentResult from the EvaluationResult creating from evaluating a MEDINA + * metric + * + * @param rule The rule covered by this result + * @param evaluationResult The result of the evaluation + * @param evidenceId the id of the Evidence belonging to the AssessmentResult + * @return the resulting AssessmentResult or null if not included in mapping.txt + * @author Robert Haimerl + */ + fun convertEvaluationToAssessmentResult( + rule: Rule, + evaluationResult: EvaluationResult, + evidenceId: String, + hash: String + ): AssessmentResult { + logger.debug { + "Creating AssessmentResult with evidence $evidenceId from result $evaluationResult" + } + + val metricConfiguration = MetricConfiguration() + metricConfiguration.operator = rule.operator + metricConfiguration.targetValue = rule.targetValue + metricConfiguration.isDefault = true + metricConfiguration.updatedAt = java.time.OffsetDateTime.now() + metricConfiguration.metricId = rule.name + metricConfiguration.cloudServiceId = serviceId + + val assessmentResult = AssessmentResult() + assessmentResult.metricConfiguration = metricConfiguration + assessmentResult.id = UUID.randomUUID().toString() + assessmentResult.timestamp = java.time.OffsetDateTime.now() + assessmentResult.evidenceId = evidenceId + assessmentResult.resourceId = hash + assessmentResult.addResourceTypesItem(RESOURCE_TYPE) + assessmentResult.metricId = rule.name + assessmentResult.compliant = evaluationResult.isValidated() + assessmentResult.nonComplianceComments = evaluationResult.getDetailedMessage() + assessmentResult.cloudServiceId = serviceId + assessmentResult.toolId = toolId + + return assessmentResult + } + + /** + * Creates AssessmentResults from a Finding + * + * @param findings the Findings produced by Codyze + * @param evidenceId the id of the Evidence belonging to the AssessmentResult + * @return the resulting AssessmentResult or null if not included in mapping.txt + * @author Robert Haimerl + */ + fun convertFindingsToAssessmentResults( + mapping: Mapping, + findings: Set<Finding>, + evidenceId: String, + hash: String + ): Array<AssessmentResult> { + logger.debug { "Creating AssessmentResults with evidence $evidenceId from set of Findings" } + + val results = mutableListOf<AssessmentResult>() + val findingIdentifiers = findings.map { it.identifier }.toList() + for (metric in mapping.metrics) { + logger.debug { "Creating an AssessmentResult for the metric ${metric.name}" } + // considers metric only if all specified rules exist as Findings + if (metric.rules.all { findingIdentifiers.contains(it) }) { + val metricConfiguration = MetricConfiguration() + metricConfiguration.operator = metric.configuration.operator + metricConfiguration.isDefault = metric.configuration.default + // map the target values to their defined type - falls back to String if double + // cannot be parsed + val target = + when (metric.configuration.type) { + Type.NUMBER -> + try { + metric.configuration.target.map { it.toString().toDouble() } + } catch (nfe: NumberFormatException) { + logger.error { + "Error while trying to parse defined target of ${metric.name} (${metric.configuration.target}) as a number. Falling back to String target." + } + metric.configuration.target.map { it.toString() } + } + Type.BOOLEAN -> metric.configuration.target.map { it.toString() == "true" } + else -> metric.configuration.target.map { it.toString() } + } + // give the target value or a list of values if specified + metricConfiguration.targetValue = if (target.size == 1) target[0] else target + metricConfiguration.updatedAt = java.time.OffsetDateTime.now() + metricConfiguration.metricId = metric.name + metricConfiguration.cloudServiceId = serviceId + + val assessmentResult = AssessmentResult() + assessmentResult.metricConfiguration = metricConfiguration + assessmentResult.id = UUID.randomUUID().toString() + assessmentResult.cloudServiceId = serviceId + assessmentResult.timestamp = java.time.OffsetDateTime.now() + assessmentResult.evidenceId = evidenceId + assessmentResult.resourceId = hash + assessmentResult.addResourceTypesItem(RESOURCE_TYPE) + assessmentResult.metricId = metric.name + // only see the metric as compliant if all of its rules are + val relevantFindings = findings.filter { metric.rules.contains(it.identifier) } + assessmentResult.compliant = relevantFindings.all { !it.isProblem } + // merges all log messages of findings that failed + assessmentResult.nonComplianceComments = + relevantFindings + .filter { it.isProblem } + .map { "${it.identifier}: ${it.logMsg}\n" } + .fold(StringBuilder()) { text, line -> text.append(line) } + .toString() + assessmentResult.toolId = toolId + + results.add(assessmentResult) + } + } + return results.toTypedArray() + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/Environment.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/Environment.kt new file mode 100644 index 0000000000000000000000000000000000000000..3694b252aac33495007f89b27547e4f4a7bfa21f --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/Environment.kt @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.assembling + +import de.fraunhofer.aisec.codyze.medina.main.Configuration.CIEnvironment +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.nio.file.Path +import kotlin.io.path.absolute + +class Environment(private var ciEnvironment: CIEnvironment, private val configPath: Path) { + // This lateinit variable is initialized at the first call of getGitHash() + private lateinit var gitHash: String + private val logger = KotlinLogging.logger {} + private var environmentVariables = mutableMapOf<String, String>() + + init { + if (ciEnvironment == CIEnvironment.NONE) { + ciEnvironment = getEnvironment() + } + addEnvironmentVariables() + } + + /** + * Tries multiple known environment variables to find out which CI/CD environment is being used + * Currently possible values for environment are ["None", "GitHub", "GitLab", "Jenkins"]. This + * method sets the "environment"-variable and fills the "environmentVariables"-Map specific to + * this environment + * + * @return the matched CIEnvironment + * @author Robert Haimerl + */ + private fun getEnvironment(): CIEnvironment { + // all known CI/CD environments set "CI" to "true" + if (System.getenv("CI") != "true") { + logger.warn { "No CI/CD environment recognized" } + return CIEnvironment.NONE + } + + val githubVars = arrayOf("GITHUB_ACTION", "GITHUB_JOB", "GITHUB_SHA") + val gitlabVars = arrayOf("CI_PIPELINE_ID", "CI_JOB_ID", "CI_COMMIT_SHA") + val jenkinsVars = arrayOf("JOB_NAME", "BUILD_ID", "JENKINS_URL") + + if (githubVars.all { variable -> System.getenv(variable) != null }) { + logger.info { "Determined CI/CD environment to be GitHub" } + return CIEnvironment.GITHUB + } else if (gitlabVars.all { variable -> System.getenv(variable) != null }) { + logger.info { "Determined CI/CD environment to be GitLab" } + return CIEnvironment.GITLAB + } else if (jenkinsVars.all { variable -> System.getenv(variable) != null }) { + logger.info { "Determined CI/CD environment to be Jenkins" } + return CIEnvironment.JENKINS + } + + logger.warn { "CI/CD environment could not be determined" } + return CIEnvironment.NONE + } + + /** + * Adds all relevant environment variables for the detected CI environment to the Map + * + * @author Robert Haimerl + */ + private fun addEnvironmentVariables() { + // jenkins: "SCM-specific variables such as GIT_COMMIT are not automatically defined as + // environment variables; rather you can use the return value of the checkout step" + when (ciEnvironment) { + CIEnvironment.GITHUB -> { + environmentVariables["commit_hash"] = "GITHUB_SHA" + } + CIEnvironment.GITLAB -> { + environmentVariables["commit_hash"] = "CI_COMMIT_SHA" + } + CIEnvironment.JENKINS -> { + environmentVariables["commit_hash"] = "GIT_COMMIT" + } + else -> return + } + } + + /** + * This function tries to initialize the git hash exactly once, after that it will just refer to + * the result of the first attempt. This removes overhead by trying to figure out the commit + * hash every time anew. + * + * The function tries environment variables set by GitHub actions, then GitLab CI/CD before + * ultimately falling back to console commands. + * + * @return The git hash or "invalid" + * @author Robert Haimerl + */ + fun getGitHash(): String { + if (!::gitHash.isInitialized) { + var hash: String? + // Default to "GITHUB_SHA" to prevent NullPointerExceptions from being thrown + hash = System.getenv(environmentVariables["commit_hash"] ?: "GITHUB_SHA") + if (hash == null) { + logger.warn { "Git commit hash could not be parsed from the CI Environment" } + hash = + try { + // Use the location of the configuration file in order to resolve the git + // information + val process = + Runtime.getRuntime() + .exec("git rev-parse HEAD", null, configPath.absolute().toFile()) + val reader = BufferedReader(InputStreamReader(process.inputStream)) + reader.readLine() + } catch (e: IOException) { + logger.error(e) { + "IOException while trying to manually resolve the git commit hash: ${e.localizedMessage}" + } + null + } + } + // If we couldn't figure hash out, log error and return "invalid" + gitHash = + if (hash == null) { + logger.error { "Failed to determine an associated Git commit hash" } + "invalid" + } else { + hash + } + } + return gitHash + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/auth/Auth.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/auth/Auth.kt deleted file mode 100644 index 76e762a21c55677857476a30862aae3952a7aa4f..0000000000000000000000000000000000000000 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/auth/Auth.kt +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - * - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ - * | | / _ \ / _` | | | |_ / _ \ - * | |___| (_) | (_| | |_| |/ / __/ - * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ - * - * This file is part of the MEDINA Framework. - */ -package de.fraunhofer.aisec.codyze.medina.auth - -import java.net.URI -import mu.KotlinLogging -import org.dmfs.httpessentials.client.HttpRequestExecutor -import org.dmfs.httpessentials.httpurlconnection.HttpUrlConnectionExecutor -import org.dmfs.oauth2.client.* -import org.dmfs.oauth2.client.grants.ClientCredentialsGrant -import org.dmfs.oauth2.client.grants.TokenRefreshGrant -import org.dmfs.oauth2.client.scope.BasicScope -import org.dmfs.rfc3986.uris.EmptyUri -import org.dmfs.rfc5545.Duration - -private val logger = KotlinLogging.logger {} - -/** - * Creates an OAuth2Client using the URL, username and password. The default Token ttl is set to 10 - * hours - * @author Florian Wendland - * @author Robert Haimerl - * @param tokenURL the URL of the token-granting server - * @param username the username used for authentication - * @param password the password used for authentication - * @return an OAuth2Client used to request tokens - */ -fun getOAuth2Client(tokenURL: String, username: String, password: String): OAuth2Client { - // create OAuth2 provider - val pv: OAuth2AuthorizationProvider = - BasicOAuth2AuthorizationProvider( - null, - URI.create(tokenURL), - Duration(1, 0, 36000) // token expires after 10 hours - ) - // Create OAuth2 client credentials - val cred: OAuth2ClientCredentials = BasicOAuth2ClientCredentials(username, password) - // Create OAuth2 client - return BasicOAuth2Client(pv, cred, EmptyUri()) -} - -/** - * Requests a new access-token - * @author Robert Haimerl - * @param client an OAuth2Client containing the corresponding URL and credentials - * @return an OAuth2AccessToken or <code>null</code> if no token could be retrieved - */ -fun getAccessToken(client: OAuth2Client): OAuth2AccessToken? { - // Create HttpRequestExecutor - val ex: HttpRequestExecutor = HttpUrlConnectionExecutor() - // Client Credentials Grant - return try { - ClientCredentialsGrant(client, BasicScope()).accessToken(ex) - } catch (e: Exception) { - logger.error { "Could not retrieve access token with the given credentials: $e" } - null - } -} - -// Currently, not implemented in the orchestrator -/** - * Refreshes an expiring access-token - * @author Robert Haimerl - * @param client an OAuth2Client containing the corresponding URL and credentials - * @param oldToken the token which should be replaced - * @return a new OAuth2AccessToken or null if no token could be retrieved - */ -fun refreshToken(client: OAuth2Client, oldToken: OAuth2AccessToken): OAuth2AccessToken? { - // Create HttpRequestExecutor - val ex: HttpRequestExecutor = HttpUrlConnectionExecutor() - // Request new access token, providing the previous one - return try { - TokenRefreshGrant(client, oldToken).accessToken(ex) - } catch (e: Exception) { - logger.error { "Could not refresh the access token: $e" } - null - } -} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/ApiManager.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/ApiManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..17d663a1dddc4441d150e5f80d735e0dc0b0ec4e --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/ApiManager.kt @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.connection + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.net.InetAddress +import java.net.URL +import java.util.UUID +import org.openapitools.client.evidence.api.EvidenceStoreApi +import org.openapitools.client.evidence.model.Evidence +import org.openapitools.client.orchestrator.ApiException +import org.openapitools.client.orchestrator.api.OrchestratorApi +import org.openapitools.client.orchestrator.model.AssessmentResult +import org.openapitools.client.orchestrator.model.CloudService + +/** + * The ApiManager handles calls towards the orchestrator and evidence api + * + * @param connection the connection containing this manager + * @param orchestratorURL the URL of the Orchestrator instance + * @author Robert Haimerl + */ +class ApiManager(private val connection: Connection, private val orchestratorURL: String) { + private val logger = KotlinLogging.logger {} + private val orchestratorApi = + OrchestratorApi( + org.openapitools.client.orchestrator.ApiClient().setBasePath(orchestratorURL) + ) + private val evidenceApi = + EvidenceStoreApi(org.openapitools.client.evidence.ApiClient().setBasePath(orchestratorURL)) + + init { + orchestratorApi.customBaseUrl = orchestratorURL + evidenceApi.customBaseUrl = orchestratorURL + } + + /** + * Test the connection to the Orchestrator-Server + * + * @return whether a connection could be established + * @author Robert Haimerl + */ + fun testOrchestratorConnection(): Boolean { + logger.info { "Testing the connection to the Orchestrator" } + // first ping the server + val orchestratorHost = URL(orchestratorURL).host + if (!connection.isReachable(InetAddress.getByName(orchestratorHost), 80, 2000)) { + logger.error { "Failed to ping orchestrator" } + return false + } + // then try to make a apply the Token + return connection + .getTokenManager() + .applyToken(orchestratorApi.apiClient, evidenceApi.apiClient) + } + + /** + * Sends the AssessmentResults to the orchestrator + * + * @param ar the AssessmentResults to send + * @param retryOnError whether it should retry the process on a connection error + * @return whether sending was successful + * @author Robert Haimerl + */ + fun sendAssessmentResults(ar: Array<AssessmentResult>?, retryOnError: Boolean = true): Boolean { + if (ar.isNullOrEmpty()) { + logger.warn { "No AssessmentResults to send" } + return true + } + var nextElem = 0 + try { + logger.info { + "trying to send AssessmentResults to ${orchestratorApi.apiClient.basePath}" + } + for (result in ar) { + orchestratorApi.orchestratorStoreAssessmentResult(result) + nextElem++ + logger.debug { "Sent assessment result with id " + result.id } + } + } catch (ae: org.openapitools.client.orchestrator.ApiException) { + logger.error(ae) { + "Could not send to ${orchestratorApi.apiClient.basePath}: ${ae.localizedMessage}" + } + return if (retryOnError && ae.code == 401) { + logger.info { "Retrying transmission process..." } + // retries the transmission process starting from the failed element + sendAssessmentResults(ar.copyOfRange(nextElem, ar.size), false) + } else false + } + logger.info { "All assessment results sent to ${orchestratorApi.apiClient.basePath}" } + return true + } + /** + * Stores the Evidence in the Orchestrator + * + * @param evidence the Evidence to store + * @return whether the Evidence could be successfully stored + * @author Robert Haimerl + */ + fun storeEvidence(evidence: Evidence): Boolean { + return try { + evidenceApi.evidenceStoreStoreEvidence(evidence) + logger.info { "Successfully stored evidence with id ${evidence.id}" } + true + } catch (e: org.openapitools.client.evidence.ApiException) { + logger.error(e) { + "Failed to store evidence with id ${evidence.id}: ${e.localizedMessage}" + } + false + } + } + + /** + * Tries to get a CloudService defined by its id + * + * @param id The unique UUID of the cloud service + * @return The CloudService defined by the provided id + * @author Robert Haimerl + */ + fun getCloudServiceById(id: UUID): CloudService? { + return try { + orchestratorApi.orchestratorGetCloudService(id.toString()) + } catch (ae: ApiException) { + logger.error(ae) { "Failed to resolve Cloud Service Id ($id): ${ae.localizedMessage}" } + null + } + } + + fun getOrchestratorApi(): OrchestratorApi { + return orchestratorApi + } + + fun getEvidenceApi(): EvidenceStoreApi { + return evidenceApi + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/Connection.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/Connection.kt new file mode 100644 index 0000000000000000000000000000000000000000..fecb41eb39ceae5f2ddbfefacf7b9efb79047e03 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/Connection.kt @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.connection + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket + +class Connection(orchestratorURL: String, tokenURL: String, username: String, password: String) { + private val logger = KotlinLogging.logger {} + private val apiManager: ApiManager = ApiManager(this, orchestratorURL) + private val oAuthManager: OAuthManager = OAuthManager(this, tokenURL, username, password) + private val tokenManager: TokenManager = TokenManager(this) + + /** + * Checks if the given address/port combination is reachable. Used since some hosts do not + * respond to regular isReachable on port 7 + * + * @param address the INetAddress we try to ping + * @param port the according port + * @param timeout when every attempt should time out (a maximum of two connection attempts are + * made) + * @return whether the address could be reached + * @author Robert Haimerl + */ + @Suppress("SameParameterValue") + fun isReachable(address: InetAddress, port: Int = 0, timeout: Int = 2000): Boolean { + logger.debug { "Testing whether $address:$port is reachable" } + // first try default isReachable (which tries ICMP or port 7) + if (!address.isReachable(timeout)) { + logger.debug { "Could not reach $address:$port, manually retrying" } + // otherwise, try manual connection to specified port + if (port < 0 || port > 65535) return false + val sockAddress = InetSocketAddress(address, port) + val sock = Socket() + try { + sock.connect(sockAddress, timeout) + } catch (exc: Exception) { + logger.warn { "Address $address:$port is unreachable" } + return false + } + } + logger.info { "Address $address:$port can be reached" } + return true + } + + fun getApiManager(): ApiManager { + return this.apiManager + } + + fun getTokenManager(): TokenManager { + return this.tokenManager + } + + fun getOAuthManager(): OAuthManager { + return this.oAuthManager + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/OAuthManager.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/OAuthManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..f8953fec181cf7da805c90097a2d00159e3ed489 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/OAuthManager.kt @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.connection + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.net.InetAddress +import java.net.URI +import java.net.URL +import org.dmfs.oauth2.client.* +import org.dmfs.rfc3986.uris.EmptyUri +import org.dmfs.rfc5545.Duration + +/** + * The OAuthManager handles the authentication process. Closely intertwined with the TokenManager + * + * @param connection the connection containing this manager + * @param tokenURL the URL of the OAuth2 instance + * @param username the OAuth2 username + * @param password the OAuth2 password + * @author Robert Haimerl + */ +class OAuthManager( + private val connection: Connection, + private val tokenURL: String, + username: String, + password: String +) { + private val logger = KotlinLogging.logger {} + // Creates the authClient instantly after creating the TokenManager + private val authClient = createOAuth2Client(tokenURL, username, password) + + /** + * Test the connection to the OAuth2-Servers + * + * @return whether a connection could be established + * @author Robert Haimerl + */ + fun testOAuthConnection(): Boolean { + logger.info { "Testing the connection to the OAuth-Server" } + // fist do a simple ping + val tokenHost = URL(tokenURL).host + if (!connection.isReachable(InetAddress.getByName(tokenHost), 80, 2000)) { + logger.error { "Failed to ping OAuth-Server" } + return false + } + // then try to request a token with the given credentials + if (connection.getTokenManager().requestToken(authClient) == null) { + logger.error { "Failed to retrieve a token with the given credentials" } + return false + } + return true + } + + /** + * Creates an OAuth2Client using the URL, username and password. The default Token ttl is set to + * 10 hours + * + * @param tokenURL the URL of the token-granting server + * @param username the username used for authentication + * @param password the password used for authentication + * @return an OAuth2Client used to request tokens + * @author Florian Wendland + * @author Robert Haimerl + */ + private fun createOAuth2Client( + tokenURL: String, + username: String, + password: String + ): OAuth2Client { + // create OAuth2 provider + val pv: OAuth2AuthorizationProvider = + BasicOAuth2AuthorizationProvider( + null, + URI.create(tokenURL), + Duration(1, 0, 36000) // token expires after 10 hours + ) + // Create OAuth2 client credentials + val cred: OAuth2ClientCredentials = BasicOAuth2ClientCredentials(username, password) + // Create OAuth2 client + val client = BasicOAuth2Client(pv, cred, EmptyUri()) + logger.info { "Successfully created the OAuth2-Client" } + return client + } + + fun getAuthClient(): OAuth2Client { + return this.authClient + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/TokenManager.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/TokenManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..f2b0546893fca32c5ecea4fa4d29e707d62ee368 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/connection/TokenManager.kt @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.connection + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.dmfs.httpessentials.client.HttpRequestExecutor +import org.dmfs.httpessentials.httpurlconnection.HttpUrlConnectionExecutor +import org.dmfs.oauth2.client.OAuth2AccessToken +import org.dmfs.oauth2.client.OAuth2Client +import org.dmfs.oauth2.client.grants.ClientCredentialsGrant +import org.dmfs.oauth2.client.scope.BasicScope + +/** + * the OAuthManager handles the token necessary for the authentication process + * + * @param connection the connection containing this manager + * @author Robert Haimerl + */ +class TokenManager(private val connection: Connection) { + private val logger = KotlinLogging.logger {} + // Fetches a token instantly after creating the TokenManager + private var token: OAuth2AccessToken? = + requestToken(connection.getOAuthManager().getAuthClient()) + + init { + if ( + !applyToken( + connection.getApiManager().getOrchestratorApi().apiClient, + connection.getApiManager().getEvidenceApi().apiClient, + token + ) + ) { + logger.error { + "Failed to apply the OAuth2 access token while initializing the TokenManager" + } + } + } + + /** + * Requests a new access-token + * + * @param client an OAuth2Client containing the corresponding URL and credentials + * @return an OAuth2AccessToken or <code>null</code> if no token could be retrieved + * @author Robert Haimerl + */ + fun requestToken(client: OAuth2Client): OAuth2AccessToken? { + // Create HttpRequestExecutor + val ex: HttpRequestExecutor = HttpUrlConnectionExecutor() + // Client Credentials Grant + val token = + try { + ClientCredentialsGrant(client, BasicScope()).accessToken(ex) + } catch (e: Exception) { + logger.error(e) { + "Could not retrieve access token with the given credentials: ${e.localizedMessage}" + } + null + } + logger.info { "Successfully requested an access token for the OAuth2-Connection" } + return token + } + + /** + * applies the OAuth2-Token to both the Orchestrator Client and the Evidence Client + * + * @return whether there was a token that could be applied + * @author Robert Haimerl + */ + fun applyToken( + orchestratorClient: org.openapitools.client.orchestrator.ApiClient, + evidenceClient: org.openapitools.client.evidence.ApiClient, + token: OAuth2AccessToken? = this.token + ): Boolean { + val tk = + token + ?: run { + logger.warn { "Tried to apply the OAuth2 access token before it was available" } + return false + } + orchestratorClient.addDefaultHeader( + "Authorization", + "${tk.tokenType()} ${tk.accessToken()}" + ) + evidenceClient.addDefaultHeader("Authorization", "${tk.tokenType()} ${tk.accessToken()}") + logger.info { "Access token successfully applied to the connection" } + return true + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/EvaluationResult.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/EvaluationResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..e327a2c5ee8a36f77b034bba72fa811864dbbd0a --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/EvaluationResult.kt @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation + +/** + * The result of the evaluation, its detailedMessage is being used as input for the evidence `raw` + */ +class EvaluationResult( + private val validated: Boolean, + private val message: String, + private val detailedMessage: String +) { + fun isValidated(): Boolean { + return this.validated + } + + fun getMessage(): String { + return this.message + } + + fun getDetailedMessage(): String { + return this.detailedMessage + } + + override fun toString(): String { + return "${if (!validated) "IN" else ""}VALID | [$message] | [$detailedMessage]" + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/Evaluator.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/Evaluator.kt new file mode 100644 index 0000000000000000000000000000000000000000..c214c05c35cc043274c74589d4164dec46f9005d --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/Evaluator.kt @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.base.* +import de.fraunhofer.aisec.codyze.medina.mapping.MedinaMapping +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File +import java.lang.IllegalArgumentException +import java.nio.file.Path +import kotlin.reflect.KClass +import kotlin.reflect.full.primaryConstructor + +class Evaluator(private val environment: Environment, private val base: Path) { + /** A mapping from the rule to its respective MetricEvaluator class */ + private val rulesToEvaluators: Map<Rule, KClass<out MetricEvaluator>> = + mapOf( + Rule.CodeSignoff to SignOffEvaluator::class, + Rule.SignedCommits to GPGSignatureEvaluator::class, + Rule.SignedSignoff to SignedSignOffEvaluator::class, + Rule.ApprovedCommitAuthor to ApprovedCommitAuthorEvaluator::class, + ) + + private val logger = KotlinLogging.logger {} + + /** + * Iterates over all metrics defined in the Medina Mapping and tries to evaluate them. When + * found metrics cannot be evaluated they are ignored and a matching log message is created + * + * @param medinaMapping the Medina Mapping as parsed from the respective file + * @return A map mapping each evaluated rule to its evaluation result + * @author Robert Haimerl + */ + fun evaluateAll(medinaMapping: MedinaMapping): Map<Rule, EvaluationResult> { + logger.info { "Starting Evaluation of MEDINA Metrics" } + val results = mutableMapOf<Rule, EvaluationResult>() + for (metric in medinaMapping.metrics) { + try { + val rule = Rule.valueOf(metric.name) + logger.debug { "Starting Evaluation of Metric ${metric.name}" } + // Return custom error message if git hash could not be resolved + if (environment.getGitHash() == "invalid") { + results[rule] = + EvaluationResult( + false, + "Git commit hash could not be resolved", + "Git commit hash could not be resolved" + ) + } else { + // call the evaluation function for the corresponding rule and pass the metric + // configuration + results[rule] = + rulesToEvaluators[rule]!! + .primaryConstructor!! + .call(environment, base) + .evaluate(metric.target) + } + } catch (ie: IllegalArgumentException) { + logger.warn { "No Rule for ${metric.name} could be found" } + } catch (npe: NullPointerException) { + logger.error { "No evaluation function for ${metric.name} was defined" } + } + } + return results + } + + /** + * Tries to import all files at the specified location into GPG as public keys + * + * @param keyLocation A PubKey-File or Directory containing public keys + * @author Robert Haimerl + */ + fun parseKeys(keyLocation: File) { + logger.info { "Trying to import public keys from ${keyLocation.path}" } + val files = + if (keyLocation.isDirectory) { + collectFiles(keyLocation) + } else { + setOf(keyLocation) + } + var success = 0 + for (key in files) { + val process = + Runtime.getRuntime() + .exec(arrayOf("gpg", "--import", key.canonicalPath), null, base.toFile()) + process.waitFor() + if (process.exitValue() != 0) { + logger.warn { "Failed to import public key file ${key.name}" } + } else { + success += 1 + } + } + logger.info { "Successfully registered $success keys" } + } + + /** + * Recursively collects all regular files within a directory into a set + * + * @param directory The starting directory + * @return A set of all regular files contained in the directory and all subdirectories + * @author Robert Haimerl + */ + private fun collectFiles(directory: File): Set<File> { + val files = mutableSetOf<File>() + + for (f in directory.walk()) { + if (f != directory && f.isDirectory) { + files += collectFiles(f) + } else if (f.isFile) { + files += f + } + } + return files + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/Rule.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/Rule.kt new file mode 100644 index 0000000000000000000000000000000000000000..1cd693234f5a98cf2836f823f71cbf91940b40d9 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/Rule.kt @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation + +/** + * The rules that can be evaluated. Each rule contains information about its associated operator and + * target type + * + * @param operator The operator used for evaluating the result of the rule + * @param targetValue The desired evaluation result + * @param id The stable and unique id of this rule, used for SARIF reports + * @param description A description of the rule, used for SARIF reports + */ +enum class Rule( + val operator: String, + val targetValue: Any, + val id: String, + val description: String +) { + CodeSignoff( + "==", + true, + "MD001", + "Whether the analyzed commit was signed off by a pre-specified subject" + ), + SignedCommits( + "==", + true, + "MD002", + "Whether the analyzed commit contains a valid signature from a pre-specified subject" + ), + SignedSignoff( + "==", + true, + "MD003", + "Whether the analyzed commit contains both a signoff and a valid signature from a pre-specified subject" + ), + ApprovedCommitAuthor( + "==", + true, + "MD004", + "Whether the analyzed commit comes from a permitted author" + ), +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/ApprovedCommitAuthorEvaluator.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/ApprovedCommitAuthorEvaluator.kt new file mode 100644 index 0000000000000000000000000000000000000000..797adc84106fbf10237e02f7bb5c4e6c0d81e63a --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/ApprovedCommitAuthorEvaluator.kt @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation.base + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import java.nio.file.Path + +class ApprovedCommitAuthorEvaluator( + override val environment: Environment, + override val base: Path +) : MetricEvaluator() { + + override val logger: KLogger = KotlinLogging.logger {} + + /** + * Validates commit author against list of approved authors. + * + * @return true if author is approved; otherwise, false + * @author Florian Wendland + */ + override fun evaluate(target: Array<Any>): EvaluationResult { + val gitHash = environment.getGitHash() + val commitAuthor = getCommitAuthorName(gitHash) + + if (target.contains<Any?>(commitAuthor)) { + return EvaluationResult( + true, + "Commit from approved author: ${commitAuthor}", + "Commit from approved author: ${commitAuthor}, expected one of: ${target.contentToString()}" + ) + } + return EvaluationResult( + false, + "Commit from unapproved author: ${commitAuthor}", + "Commit from unapproved author: ${commitAuthor}, expected one of: ${target.contentToString()}" + ) + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/GPGSignatureEvaluator.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/GPGSignatureEvaluator.kt new file mode 100644 index 0000000000000000000000000000000000000000..83d5b4d4761d86d4b25580fe0fe468f3f87c4414 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/GPGSignatureEvaluator.kt @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation.base + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import java.nio.file.Path + +class GPGSignatureEvaluator(override val environment: Environment, override val base: Path) : + MetricEvaluator() { + + override val logger: KLogger = KotlinLogging.logger {} + + override fun evaluate(target: Array<Any>): EvaluationResult { + val goodSignatures = + getGoodSignatures(getCommitSignatureInformation(environment.getGitHash())) + if (goodSignatures.isEmpty()) { + return EvaluationResult( + false, + "The commit contains no good signatures", + "was: [], expected one of: ${target.contentToString()}" + ) + } + for (signature in goodSignatures) { + if (target.contains(signature.first)) { + return EvaluationResult( + true, + "Commit signed by ${signature.second} <${signature.third}> with the key id ${signature.first}", + "was: ${goodSignatures.map { it.first }}, expected one of: ${target.contentToString()}" + ) + } + } + return EvaluationResult( + false, + "The commit contains good signatures, but none match the specified key ids", + "was: ${goodSignatures.map { it.first }}, expected one of: ${target.contentToString()}" + ) + } + + /** + * Tries to find good signatures in a gnupg status output as returned by + * *getCommitSignatureInformation* + * + * @param gpgStatus The gnupg status output like it is produced when calling git + * verify-signature + * @return A List of Triple<KeyId, Name, Email> that describes valid signatures of the commit + * @author Robert Haimerl + */ + internal fun getGoodSignatures(gpgStatus: List<String>): List<Triple<String, String, String>> { + return gpgStatus + .filter { line -> line.startsWith("[GNUPG:] GOODSIG") } + .map { line -> splitGoodSignatureLine(line) } + .toList() + } + + /** + * Splits a GOODSIG line of the gnupg status output, undefined return value for other argument + * Strings When an Exception occurs while trying to parse the keyId, it will be stored as -1 + * + * @param line The line of the output that shall be split + * @return The Triple<KeyId, Name, Email> + * @author Robert Haimerl + */ + private fun splitGoodSignatureLine(line: String): Triple<String, String, String> { + val splitLine = line.split(" ") + if (splitLine.size < 4) { + logger.error { "Signature line is too short to be parsed properly" } + return Triple("", "", "") + } + val keyId = splitLine[2] + val name = splitLine.subList(3, splitLine.size - 1).joinToString(" ") + val email = splitLine.last().substring(1, splitLine.last().length - 1) + return Triple(keyId, name, email) + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/MetricEvaluator.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/MetricEvaluator.kt new file mode 100644 index 0000000000000000000000000000000000000000..c681cd7179aa080188540a05bcd02f25a1853adb --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/MetricEvaluator.kt @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation.base + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import io.github.oshai.kotlinlogging.KLogger +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.file.Path + +abstract class MetricEvaluator { + + abstract val environment: Environment + abstract val base: Path + abstract val logger: KLogger + + /** + * Abstract evaluation function to be overwritten by each specific Evaluator + * + * @param target The target values for this evaluation + * @return An EvaluationResult containing a message and whether the metric could be validated + */ + abstract fun evaluate(target: Array<Any>): EvaluationResult + + /** + * Fetches the name of the commit author from the git console commands + * + * @return The full author name or null if it could not be found + * @author Robert Haimerl + */ + internal fun getCommitAuthorName(commitHash: String): String { + val process = + Runtime.getRuntime().exec("git show -s --format=%an $commitHash", null, base.toFile()) + val reader = BufferedReader(InputStreamReader(process.inputStream)) + return reader.readLine() + } + + /** + * Fetches the name of the commit author from the git console commands + * + * @return The full author name or null if it could not be found + * @author Robert Haimerl + */ + internal fun getCommitAuthorEmail(commitHash: String): String { + val process = + Runtime.getRuntime().exec("git show -s --format=%ae $commitHash", null, base.toFile()) + val reader = BufferedReader(InputStreamReader(process.inputStream)) + return reader.readLine() + } + + /** + * Searches the commit in question for sign-offs. A sign-off is identified by verifying whether + * the last line starts with the phrase "Signed-off-by: " + * + * @return A List of the found sign-off lines, may be empty + * @author Robert Haimerl + */ + internal fun getSignOff(commitHash: String): List<String> { + // try to read the commit message as console command + val process = + Runtime.getRuntime() + .exec("git show --pretty=format:\"%B\" --no-patch $commitHash", null, base.toFile()) + val commitMessageLines = + BufferedReader(InputStreamReader(process.inputStream)).readLines().toMutableList() + val signOffList: MutableList<String> = mutableListOf() + for (line in commitMessageLines) { + if (line.startsWith("Signed-off-by: ")) { + signOffList += line + } + } + return signOffList + } + + /** + * Calls git verify-commit raw and returns the response as a list of the separate lines of the + * response + * + * @param commitHash The commit of which we want to check the signature + * @return The separate lines of a machine-readable gpg status output, may be empty + * @author Robert Haimerl + */ + internal fun getCommitSignatureInformation(commitHash: String): List<String> { + val process = + Runtime.getRuntime().exec("git verify-commit --raw $commitHash", null, base.toFile()) + return BufferedReader(InputStreamReader(process.errorStream)).readLines().toList() + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/SignOffEvaluator.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/SignOffEvaluator.kt new file mode 100644 index 0000000000000000000000000000000000000000..2c8e0f0a02dfe580231f49727510a7103e4d6755 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/SignOffEvaluator.kt @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation.base + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import java.nio.file.Path + +class SignOffEvaluator(override val environment: Environment, override val base: Path) : + MetricEvaluator() { + + override val logger: KLogger = KotlinLogging.logger {} + + /** + * Tries to get all sign-offs of the current commit and compares it to the name and email + * associated with the commit author + * + * @return True only if the commit contains a sign-off which matches the expected author + * @author Robert Haimerl + */ + override fun evaluate(target: Array<Any>): EvaluationResult { + val gitHash = environment.getGitHash() + val signOffList = getSignOff(gitHash).map { parseSignOff(it) } + for (signOff in signOffList) { + if ( + signOff.first == getCommitAuthorName(gitHash) && + signOff.second == getCommitAuthorEmail(gitHash) && + target.contains(signOff.first) + ) + return EvaluationResult( + true, + "Message contains a sign-off by ${signOff.first}", + "was: ${signOffList.map { it.first }}, expected one of: ${target.contentToString()}" + ) + } + return EvaluationResult( + false, + "Message is not signed off by any of the specified signers", + "was: ${signOffList.map { it.first }}, expected one of: ${target.contentToString()}" + ) + } + + /** + * Parses a sign-off line into a name and email + * + * @return The Pair <Name, Email> + * @author Robert Haimerl + */ + internal fun parseSignOff(signOff: String): Pair<String, String> { + val signOffParts = signOff.split(" ") + val signOffName = + signOffParts.subList(1, signOffParts.size - 1).joinToString(separator = " ") + val signOffEmail = signOffParts.last().substring(1, signOffParts.last().length - 1) + return Pair(signOffName, signOffEmail) + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/SignedSignOffEvaluator.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/SignedSignOffEvaluator.kt new file mode 100644 index 0000000000000000000000000000000000000000..651d9f9b7d66da16d77eed7508b6fce8eeb9da1e --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/evaluation/base/SignedSignOffEvaluator.kt @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.evaluation.base + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import java.nio.file.Path + +class SignedSignOffEvaluator(override val environment: Environment, override val base: Path) : + MetricEvaluator() { + + override val logger: KLogger = KotlinLogging.logger {} + + private val signOffEvaluator: SignOffEvaluator = SignOffEvaluator(environment, base) + private val gpgSignatureEvaluator: GPGSignatureEvaluator = + GPGSignatureEvaluator(environment, base) + + /** + * This evaluator expects the target to be a map with the following keys: + * - name + * - email + * - pub-key-id + */ + @Suppress("UNCHECKED_CAST") + override fun evaluate(target: Array<Any>): EvaluationResult { + // NOTE: we can not just check whether both the Signature and the SignOff evaluate correctly + // since both can be from different sources which is not enough for this evaluation. + // To avoid code duplication we still re-use the functionality provided by those two + // Evaluators + val gitCommitHash = environment.getGitHash() + val goodSignatures = + gpgSignatureEvaluator.getGoodSignatures( + gpgSignatureEvaluator.getCommitSignatureInformation(gitCommitHash) + ) + val signOffs = signOffEvaluator.getSignOff(gitCommitHash) + + // Try to cast the target values into maps + val targetMaps: Array<LinkedHashMap<String, String>> + try { + targetMaps = target.map { t -> t as LinkedHashMap<String, String> }.toTypedArray() + } catch (e: ClassCastException) { + logger.error { + "The target for \"SignedSignoff\" is not correctly formatted. Expected an array of maps:\n\ttarget:\n\t\t- name: ...\n\t\t email: ...\n\t\t pub-key-id: ..." + } + return EvaluationResult( + false, + "Metric target was not correctly formatted", + "was: ${target.javaClass.simpleName}, Expected an array of maps" + ) + } + + // Compare the results with the specified targets for this metric by iterating over all + // valid gpg signatures + for (signature in goodSignatures) { + // First check whether a sign-off matches the signature + for (signOff in signOffs) { + val signOffTuple = signOffEvaluator.parseSignOff(signOff) + if ( + signOffTuple.first == signature.second && signOffTuple.second == signature.third + ) { + // Then check whether this signer is specified as one of the targets in the + // configuration + for (map in targetMaps) { + if ( + map["name"] == signOffTuple.first && + map["email"] == signOffTuple.second && + map["pub-key-id"] == signature.first + ) { + // Finally, check whether this matches the git commit author + if ( + signOffEvaluator.getCommitAuthorName(gitCommitHash) == + map["name"] && + signOffEvaluator.getCommitAuthorEmail(gitCommitHash) == + map["email"] + ) { + return EvaluationResult( + true, + "The commit was signed off and contains a good signature from ${map["name"]} <${map["email"]}> with the key id ${map["pub-key-id"]}", + "was: ${goodSignatures.map { it.second }}, expected one of: ${targetMaps.map { it["name"] }}" + ) + } + return EvaluationResult( + false, + "The commit was signed off and contains a good signature, but the signer does not match the commit author", + "was: ${goodSignatures.map { it.second }}, expected one of: ${targetMaps.map { it["name"] }}" + ) + } + } + return EvaluationResult( + false, + "The commit was signed off and contains a good signature, but the signer is not specified as a valid target", + "was: ${goodSignatures.map { it.second }}, expected one of: ${targetMaps.map { it["name"] }}" + ) + } + return EvaluationResult( + false, + "The commit contains a good signature, but matching sign-off could be found", + "was: ${goodSignatures.map { it.second }}, expected one of: ${targetMaps.map { it["name"] }}" + ) + } + return EvaluationResult( + false, + "The commit contains no sign-off", + "was: ${signOffs}, expected one of: ${targetMaps.map { it["name"] }}" + ) + } + return EvaluationResult( + false, + "The commit contains no good GPG signature", + "was: ${goodSignatures.map { it.second }}, expected one of: ${targetMaps.map { it["name"] }}" + ) + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/CodyzeMedina.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/CodyzeMedina.kt new file mode 100644 index 0000000000000000000000000000000000000000..9d4e363176c22259c2d052b62264eb8585424acd --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/CodyzeMedina.kt @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.main + +import de.fraunhofer.aisec.codyze.analysis.AnalysisServer +import de.fraunhofer.aisec.codyze.analysis.Finding +import de.fraunhofer.aisec.codyze.medina.assembling.Assembler +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.connection.Connection +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import de.fraunhofer.aisec.codyze.medina.evaluation.Evaluator +import de.fraunhofer.aisec.codyze.medina.evaluation.Rule +import de.fraunhofer.aisec.codyze.medina.mapping.Mapping +import de.fraunhofer.aisec.codyze.medina.mapping.parseMappingTree +import de.fraunhofer.aisec.codyze.medina.mapping.parseMedinaMapping +import de.fraunhofer.aisec.codyze.medina.util.* +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.util.* +import java.util.concurrent.Callable +import java.util.concurrent.TimeUnit +import kotlin.io.path.absolute +import kotlin.system.exitProcess +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.LoggerContext +import org.openapitools.client.orchestrator.ApiException +import picocli.CommandLine +import picocli.CommandLine.Command +import picocli.CommandLine.Mixin + +@Command( + name = "codyze-medina", + version = ["Codyze for MEDINA ${CodyzeMedina.CODYZE_VERSION}"], + mixinStandardHelpOptions = true +) +class CodyzeMedina(config: Configuration, args: Array<String>) : Callable<Int> { + private val logger = KotlinLogging.logger {} + @Mixin val configuration: Configuration = config + private val cmdlineArgs = args + + /** + * Callable function containing the core program logic + * + * @return A code specified by the function handleReturn() + * @author Florian Wendland + * @author Robert Haimerl + */ + override fun call(): Int { + // Check if all necessary options are set + configuration.validate() + val basePath = configuration.configFile.path.absolute().parent + + // Prepare the connection to the Orchestrator + val connection = + Connection( + configuration.orchestrator.orchestratorEndpoint.toString(), + configuration.orchestrator.auth.oauthEndpoint.toString(), + configuration.orchestrator.auth.username, + configuration.orchestrator.auth.password + ) + + // Prepare the configuration for Codyze + val config = + de.fraunhofer.aisec.codyze.config.Configuration.initConfig( + configuration.configFile.path.toFile(), + *cmdlineArgs, + "--default-passes", + "--passes+", + "de.fraunhofer.aisec.cpg.passes.IdentifierPass", + "--passes+", + "de.fraunhofer.aisec.cpg.passes.EdgeCachePass" + ) + + // do not allow LSP/TUI + if (config.executionMode.isLsp || config.executionMode.isTui) { + logger.warn { "Forbidden execution mode, changing to CLI" } + config.executionMode.isCli = true + config.executionMode.isLsp = false + config.executionMode.isTui = false + } + + // overwrite the mark file path with our resolved locations + var cc = config.getCodyzeConfiguration() + cc.mark = + convertMarkConfig( + configuration.mark.builtinMark, + configuration.mark.projectMark, + basePath + ) + .map { string -> File(string) } + .toTypedArray() + + // overwrite `no-good-findings` in Codyze configuration to always return all results + cc.noGoodFindings = false + + config.setCodyzeConfiguration(cc) + + // Create necessary helper objects + val environment = Environment(configuration.ci, basePath) + val assembler = Assembler(environment, configuration.id) + val evaluator = Evaluator(environment, basePath) + var analysisFailure = false + var numberOfNegativeFindings = 0 + var connectionError = false + val gitCommitHash = environment.getGitHash() + + // Check whether the CloudServiceId is recognized + val validUUID = + try { + val uuid = UUID.fromString(configuration.id) + val cloudService = connection.getApiManager().getCloudServiceById(uuid) + logger.info { + "Found ${cloudService?.name ?: "nothing"} when looking up the cloud service id" + } + cloudService != null + } catch (e: IllegalArgumentException) { + connectionError = true + logger.error { + "Failed parsing orchestrator response when checking the service id. Make sure the API is up to date" + } + false + } catch (e: ApiException) { + // error code 404 -> cloud service not found, otherwise connection failed + if (e.code != 404) { + logger.warn { + "Connection to the orchestrator failed while trying to look up the cloud service id" + } + connectionError = true + } else { + logger.warn { "Cloud service id could not be found" } + } + false + } + + // Evaluate the rules defined in the Medina.yaml + val medinaMapping = + parseMedinaMapping(relativeLocation(configuration.rules.toFile(), basePath)) + var evaluationResults: Map<Rule, EvaluationResult> = mapOf() + if (medinaMapping != null) { + evaluator.parseKeys(relativeLocation(configuration.keyLocation.toFile(), basePath)) + evaluationResults = evaluator.evaluateAll(medinaMapping) + if (evaluationResults.filter { result -> !result.value.isValidated() }.isNotEmpty()) + analysisFailure = true + } + + val mappingsToFindings = mutableMapOf<Mapping, Set<Finding>>() + val codyzeMarkConfig = config.getCodyzeConfiguration().mark + val sources = config.source.map { relativeLocation(it, basePath) }.toTypedArray() + + // Parse the Mapping for each mark path contained in the configuration + for (i in codyzeMarkConfig.indices) { + val markDirectory = relativeLocation(codyzeMarkConfig[i], basePath) + val mappingToDirectory = parseMappingTree(markDirectory) + // Iterate over each mapping and start an analysis with the included rules + logger.info { + "Starting the runs for ${mappingToDirectory.size} mappings within $markDirectory" + } + for (entry in mappingToDirectory.entries) { + // Modify Codyze config to only include relevant Mark rules + cc = config.getCodyzeConfiguration() + cc.mark = arrayOf(entry.value) + config.setCodyzeConfiguration(cc) + + // Start the server and generate Findings + val server = AnalysisServer(config) + server.start() + val ctx = server.analyze(sources)[config.timeout, TimeUnit.MINUTES] + val findings = ctx.findings + + // Check whether any negative findings were produced + numberOfNegativeFindings += findings.filter { f -> f.isProblem }.size + // Add the new findings to the aggregate + mappingsToFindings[entry.key] = findings + } + } + analysisFailure = numberOfNegativeFindings != 0 || analysisFailure + + val resultLocation = relativeLocation(configuration.resultFile.toFile(), basePath).toPath() + // Print result files before sending the results to the Orchestrator + printFindings(mappingsToFindings.values.flatten().toSet(), config) + + val fallbackReport = false + var moveError = false + val resolvedOutputLocation = relativeLocation(File(config.output), basePath).canonicalPath + if (configuration.combinedOutput) { + try { + amendExistingReport(Path.of(config.output), resultLocation, evaluationResults) + } catch (e: Exception) { + logger.error { + "Failed to amend the Codyze report at ${config.output}: ${e.localizedMessage}. Falling back to separate report" + } + printSarifReport(evaluationResults, resultLocation) + } + } + if (!configuration.combinedOutput || fallbackReport) { + // We explicitely need to move the original Codyze SARIF Output if the location was not + // an absolute path + try { + Files.move( + Path.of(config.output), + Path.of(resolvedOutputLocation), + StandardCopyOption.REPLACE_EXISTING + ) + } catch (e: IOException) { + moveError = true + logger.error { + "Failed to move the Codyze output file from ${config.output} to the target directory $resolvedOutputLocation" + } + } + + printSarifReport(evaluationResults, resultLocation) + } + val eventualCodyzeOutputLocation = if (moveError) config.output else resolvedOutputLocation + + connectionError = + connectionError || + !sendResults( + mappingsToFindings, + evaluationResults, + assembler, + environment.getGitHash(), + connection, + resultLocation, + Path.of(eventualCodyzeOutputLocation), + configuration.combinedOutput + ) + createLogReport( + evaluationResults, + numberOfNegativeFindings, + eventualCodyzeOutputLocation, + resultLocation, + configuration.combinedOutput + ) + + // determine the return value + return handleReturn( + connectionError && configuration.orchestrator.requiredReachable, + !validUUID, + gitCommitHash == "invalid", + analysisFailure + ) + } + + companion object { + const val CODYZE_VERSION = "1.6.0" + } +} + +private val logger = KotlinLogging.logger {} +/** + * Entry point of the program + * + * @author Florian Wendland + * @author Robert Haimerl + * @args cli arguments provided + */ +fun main(args: Array<String>) { + // dynamically set the log level according to CODYZE_LOG_LEVEL + setLogLevel() + // initialize configuration + val config = Configuration.initialize(args) + // parse remaining CLI options and overwrite defaults and options from config file + val returnValue = + CommandLine(CodyzeMedina(config, args)).setUnmatchedArgumentsAllowed(true).execute(*args) + logger.info { "Exit with return value: $returnValue" } + exitProcess(returnValue) +} + +/** + * Returns the appropriate code and prints an error message to the error stream if needed: + * - 126 when connection to Orchestrator failed + * - else 3 when cloud service id is not recognizable + * - else 127 for general application errors + * - else 1 when only Analysis produced negative findings + * - else 0 + */ +fun handleReturn( + connectionError: Boolean, + idError: Boolean, + generalError: Boolean, + analysisFailure: Boolean +): Int { + return if (connectionError) { + System.err.println("Connection to the orchestrator failed") + 126 + } else if (idError) { + System.err.println("Cloud service id was not recognized") + 3 + } else if (generalError) { + System.err.println( + "Errors while executing codyze-medina. Consult the logs for more details" + ) + 127 + } else if (analysisFailure) { + System.err.println("Analysis completed with violations") + 1 + } else { + 0 + } +} + +/** + * Sets the logLevel of Log4J according to the environment variable "CODYZE_LOG_LEVEL" if given This + * is done via an environment variable instead of a configuration parameter to be able to set this + * before evaluating the config file + */ +fun setLogLevel() { + // instantly return when the variable is not set, log a warning when the value could not be + // translated to a level + val envValue = System.getenv("CODYZE_LOG_LEVEL") ?: return + val logLevel = + Level.getLevel(envValue) + ?: run { + logger.warn { + "The value of \"CODYZE_LOG_LEVEL\" (\"$envValue\") could not be translated to a log level" + } + return + } + // update the configuration of the root logger + val ctx = LogManager.getContext(false) as LoggerContext + val cfg = ctx.configuration + val loggerConfig = cfg.getLoggerConfig(LogManager.ROOT_LOGGER_NAME) + loggerConfig.level = logLevel + ctx.updateLoggers() +} + +/** + * Creates the Evidences and AssessmentResults and sends them to the Orchestrator. This method + * assumes that the respective SARIF files already exist. + * + * @param findings All Findings resulting from the analysis, associated with the mapping that caused + * them + * @param evaluationResults All EvaluationResults from evaluating MEDINA metrics + * @param assembler The assembler used to create Evidences and AssessmentResults + * @param gitHash The git hash of the analyzed project + * @param connection The (already established) connection to the Orchestrator + * @param medinaResultFile The SARIF file carrying MEDINA results + * @param codyzeResultFile The SARIF file carrying Codyze results + * @param combinedResults Whether the SARIF files were combined + * @return whether the upload was successful + */ +fun sendResults( + findings: Map<Mapping, Set<Finding>>, + evaluationResults: Map<Rule, EvaluationResult>, + assembler: Assembler, + gitHash: String, + connection: Connection, + medinaResultFile: Path, + codyzeResultFile: Path, + combinedResults: Boolean +): Boolean { + var error = false + // start by sending MEDINA metrics + val mEvidence = assembler.createEvidence(medinaResultFile) + error = error || !connection.getApiManager().storeEvidence(mEvidence) + for (evResult in evaluationResults) { + val assessmentResult = + assembler.convertEvaluationToAssessmentResult( + evResult.key, + evResult.value, + mEvidence.id ?: "", + gitHash + ) + error = + error || !connection.getApiManager().sendAssessmentResults(arrayOf(assessmentResult)) + } + // then send Codyze Findings + val cEvidence = if (combinedResults) mEvidence else assembler.createEvidence(codyzeResultFile) + if (!combinedResults) error = error || !connection.getApiManager().storeEvidence(cEvidence) + for (entry in findings) { + if (entry.value.isEmpty()) { + continue + } + val results = + assembler.convertFindingsToAssessmentResults( + entry.key, + entry.value, + cEvidence.id ?: "", + gitHash + ) + error = error || !connection.getApiManager().sendAssessmentResults(results) + } + return !error +} + +/** + * Resolves a filepath relative to the location of the given base path. If the path is absolute, it + * does not change + * + * @param filePath The relative or absolute path of the file + * @param base The base path + * @return The same path evaluated relative to the configuration file + * @author Robert Haimerl + */ +fun relativeLocation(filePath: File, base: Path): File { + return base.resolve(filePath.toPath()).toFile() +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/Configuration.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/Configuration.kt similarity index 69% rename from src/main/kotlin/de/fraunhofer/aisec/codyze/medina/Configuration.kt rename to src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/Configuration.kt index 7de6b5a93c5c27b51f66f8cfe84c1da319cc028e..1c2fe528102af4c5ddcb0d422e8372c31cf60d2f 100644 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/Configuration.kt +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/Configuration.kt @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,16 +26,16 @@ * * This file is part of the MEDINA Framework. */ -package de.fraunhofer.aisec.codyze.medina +package de.fraunhofer.aisec.codyze.medina.main import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.dataformat.yaml.YAMLMapper +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.IOException import java.net.URL import java.nio.file.Path -import mu.KotlinLogging import picocli.CommandLine import picocli.CommandLine.Mixin import picocli.CommandLine.Option @@ -43,6 +43,7 @@ import picocli.CommandLine.Spec /** * Configuration class containing all needed variables passed in the configuration file and cli + * * @author Florian Wendland * @author Robert Haimerl */ @@ -55,28 +56,77 @@ class Configuration { @Mixin val orchestrator = Orchestrator() + @Mixin val mark = Mark() + /** The currently supported CI Environments */ - enum class Environment { + enum class CIEnvironment { NONE, GITLAB, GITHUB, JENKINS } + @JsonProperty("ci") @Option( names = ["--ci"], - required = false, paramLabel = "ENUM", description = ["CI environment currently being used"] ) - var ci: Environment = Environment.NONE + var ci: CIEnvironment = CIEnvironment.NONE + + @JsonProperty("id") + @Option( + names = ["--id"], + paramLabel = "STRING", + description = ["Id of the analyzed cloud service"] + ) + lateinit var id: String + + @JsonProperty("rules") + @Option( + names = ["--rules"], + paramLabel = "FILE", + description = ["Mapping file for Medina rules.\nDefault: 'codyze-medina-metrics.yaml'"] + ) + var rules: Path = Path.of("codyze-medina-metrics.yaml") + + @JsonProperty("medina-output") + @Option( + names = ["--medina-output"], + paramLabel = "PATH", + description = + [ + "File where MEDINA rule evaluation will be stored\nDefault: 'codyze-medina.sarif'\nIn case `combined-output` is set to true, this is the location of the combined result file" + ] + ) + var resultFile: Path = Path.of("codyze-medina.sarif") + + @JsonProperty("combined-output") + @Option( + names = ["--combined-output"], + paramLabel = "BOOLEAN", + description = + [ + "Whether the respective SARIF files created by Codyze and the MEDINA evaluation should be merged" + ] + ) + var combinedOutput: Boolean = true + + @JsonProperty("key-location") + @Option( + names = ["--key-location"], + paramLabel = "PATH", + description = ["Location of the public key files needed to evaluate GPG Signatures"] + ) + var keyLocation: Path = Path.of("public-keys") class ConfigFile { + @JsonProperty("config") @Option( names = ["--config"], paramLabel = "FILE", - defaultValue = "codyze.yaml", - description = ["Configuration file for Codyze.\nDefault: 'codyze.yaml'"] + defaultValue = "codyze-medina.yaml", + description = ["Configuration file for Codyze.\nDefault: 'codyze-medina.yaml'"] ) lateinit var path: Path } @@ -85,6 +135,14 @@ class Configuration { @Mixin val auth = Auth() + @JsonProperty("required") + @Option( + names = ["--required"], + paramLabel = "BOOLEAN", + description = ["Whether the Program fails when the Orchestrator is not reachable"] + ) + var requiredReachable: Boolean = true + @JsonProperty("endpoint") @Option( names = ["--endpoint"], @@ -131,8 +189,29 @@ class Configuration { fun isOrchestratorEndpointInitialized(): Boolean = ::orchestratorEndpoint.isInitialized } + class Mark { + @JsonProperty("builtin") + @Option( + names = ["--mark-builtin"], + paramLabel = "LIST<FILE>", + description = ["The builtin MARK files used to analyze the project."] + ) + var builtinMark: List<String> = listOf() + + @JsonProperty("project") + @Option( + names = ["--mark-project"], + paramLabel = "LIST<FILE>", + description = ["The external MARK files used to analyze the project."] + ) + var projectMark: List<String> = listOf() + } + + private fun isCloudServiceIdInitialized(): Boolean = ::id.isInitialized + /** * Checks whether all necessary fields were initialized + * * @author Florian Wendland * @author Robert Haimerl */ @@ -161,6 +240,12 @@ class Configuration { "Missing orchestrator endpoint URL ('--endpoint')" ) } + if (!isCloudServiceIdInitialized()) { + throw CommandLine.ParameterException( + spec.commandLine(), + "Missing cloud service id ('--id')" + ) + } logger.info { "Successfully validated the configuration" } } @@ -169,9 +254,10 @@ class Configuration { /** * Initializes the Configuration object by parsing the configuration file - * @author Florian Wendland + * * @param args the command-line arguments * @return a fully initialized Configuration object + * @author Florian Wendland */ fun initialize(args: Array<String>): Configuration { logger.info { "Initializing the configuration" } @@ -185,15 +271,16 @@ class Configuration { /** * Parses a configuration file - * @author Florian Wendland + * * @param configFile the path pointing to the file * @return a Configuration object + * @author Florian Wendland */ private fun parse(configFile: Path): Configuration { - logger.info { "Trying to parse the configuration file" } + logger.info { "Trying to parse the configuration file at $configFile" } val file = configFile.toFile() if (!file.exists() || !file.isFile || !file.canRead()) { - logger.warn { "Could not read from the configuration file" } + logger.warn { "Could not read from the configuration file at $configFile" } return Configuration() } @@ -206,7 +293,7 @@ class Configuration { } catch (e: IOException) { // also catches more specific Jackson exceptions logger.error(e) { - "Failed creating a valid configuration from the configuration file: $e" + "Failed creating a valid configuration from the configuration file: ${e.localizedMessage}" } } return Configuration() diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/MarkResolver.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/MarkResolver.kt new file mode 100644 index 0000000000000000000000000000000000000000..8909a1f89cf3b75439463fbe06e42e372c5a9174 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/main/MarkResolver.kt @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.main + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File +import java.nio.file.Path + +private val logger = KotlinLogging.logger {} + +/** + * Tries to resolve the location of the builtin MARK files. This assumes the following structure of + * the codyze-medina installation: + * - bin/ + * - codyze-medina + * - lib/ + * - ... + * - mark/ + * - ... + * + * @return The Path of the parent directory containing all builtin MARK rules + */ +private fun resolveMarkLocation(): Path { + // The following resolves the location of the codyze-medina-{version}.jar within lib/ and from + // there navigates to mark/ + val path = + Path.of(CodyzeMedina.Companion::class.java.protectionDomain.codeSource.location.toURI()) + .resolve("../../mark/") + .normalize() + logger.debug { "Determined the location of the MARK files as $path" } + return path +} + +/** + * Converts the MARK location arguments of the configuration to a single list that can be passed to + * Codyze + * + * @param builtin The argument list of builtin MARK files + * @param project The argument list of external MARK files + * @param base The base path used for relative MARK file locations + * @return A combined list of resolved paths to the needed MARK files + */ +fun convertMarkConfig(builtin: List<String>, project: List<String>, base: Path): List<String> { + val markList = mutableListOf<String>() + val markLocation = resolveMarkLocation().toString() + for (name in builtin) { + val mark = "$markLocation/$name" + markList.add(mark) + } + for (path in project) { + val resolvedPath = relativeLocation(File(path), base) + markList.add(resolvedPath.canonicalPath) + } + markList.forEach { logger.debug { "Added MARK file location ${println(it)}" } } + return markList +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/Mapping.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/Mapping.kt new file mode 100644 index 0000000000000000000000000000000000000000..b2072227e383f114d531a27956f333b46c90d04c --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/Mapping.kt @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.mapping + +/** Maps Mark rules of Findings to the respective AssessmentResults */ +@Suppress("unused") +class Mapping { + lateinit var metrics: Array<Metric> +} + +@Suppress("unused") +class Metric { + lateinit var name: String // the name of the metric + lateinit var rules: Array<String> // the name of the rules which need to be fulfilled + lateinit var configuration: Configuration // the configuration for this metric +} + +@Suppress("unused") +class Configuration { + var default: Boolean = true // whether this is a default metric + lateinit var operator: String // which operator compares this metric + lateinit var type: Type // of which type the target values are + lateinit var target: Array<Any> // what values are desired for this metric +} + +@Suppress("unused") +enum class Type { + STRING, + BOOLEAN, + NUMBER +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/MappingTree.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/MappingTree.kt new file mode 100644 index 0000000000000000000000000000000000000000..fb7a2222d9145afdc9b9c7c77131ce7817d44c14 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/MappingTree.kt @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.mapping + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File + +private val logger = KotlinLogging.logger {} +const val RESOURCE_TYPE = "Application" +const val MAP_FILE_NAME = "mapping.yaml" + +/** + * This function recursively steps through each branch of the hierarchy tree of the mark rule + * directory until it finds a mapping which is applied for all rules specified within this + * directory. Multiple mapping files can be specified in different directories within the same + * hierarchy layer. Rules in directories with no mappings will be ignored. All mapping files in + * lower hierarchy layers will be ignored. + * + * @param markOrigin the Path specified as the origin of the Mark rules + * @return a Map assigning each specified mapping to its directory + * @author Robert Haimerl + */ +fun parseMappingTree(markOrigin: File): Map<Mapping, File> { + // stop if we arrived at single file + if (!markOrigin.isDirectory) return mapOf() + + // if this directory contains a valid mapping, stop and add it to the map + val mapFile = File(markOrigin, MAP_FILE_NAME) + if (File(markOrigin, MAP_FILE_NAME).exists()) { + val mapping = parseMapping(mapFile) + if (mapping != null) { + return mapOf(mapping to markOrigin) + } else { + logger.warn { "Ignoring invalid mapping file at $mapFile" } + } + } + + // if there is no valid mapping, recursively search subdirectories for mappings + val mappingToDirectory = mutableMapOf<Mapping, File>() + for (branch: File in markOrigin.listFiles()!!) { + mappingToDirectory += parseMappingTree(branch) + } + return mappingToDirectory +} + +/** + * Parses the given file which contains instructions on how to map Findings to AssessmentResults. + * + * @param mapFile the File which includes the mapping information + * @return the Mapping object derived from the specification or null if parsing failed + * @author Robert Haimerl + */ +fun parseMapping(mapFile: File): Mapping? { + logger.info { "Parsing mappings from ${mapFile.name}" } + + // parse the yaml file into a Mapping object + val yamlMapper = ObjectMapper(YAMLFactory()) + return try { + yamlMapper.readValue(mapFile, Mapping::class.java) + } catch (e: Exception) { + logger.error(e) { + "Failed to parse the mapping file located at $mapFile: ${e.localizedMessage}" + } + null + } +} + +/** + * Parses the given file which contains instructions on how to construct AssessmentResults from + * Medina checks + * + * @param mapFile the File which includes the mapping information + * @return the MedinaMapping object derived from the specification + * @author Robert Haimerl + */ +fun parseMedinaMapping(mapFile: File): MedinaMapping? { + logger.info { "Parsing Medina mappings from ${mapFile.name}" } + + // parse the yaml file into a Mapping object, throw a RuntimeException on Error + val yamlMapper = ObjectMapper(YAMLFactory()) + return try { + yamlMapper.readValue(mapFile, MedinaMapping::class.java) + } catch (e: Exception) { + logger.warn { "Medina Mapping file was not found or cannot be parsed." } + null + } +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/MedinaMapping.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/MedinaMapping.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c624cd1721198f4c89a04cf15be5e9b26858825 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/mapping/MedinaMapping.kt @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.mapping + +/** Similar to the Mapping, but only specifies the name and the target values */ +@Suppress("unused") +class MedinaMapping { + lateinit var metrics: Array<MedinaMetric> +} + +@Suppress("unused") +class MedinaMetric { + lateinit var name: String // the name of the metric + lateinit var target: Array<Any> // the targets for this metrics +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Assemblers.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Assemblers.kt deleted file mode 100644 index 531450789b2a01e96a500c6b2d8cfb713f95a937..0000000000000000000000000000000000000000 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Assemblers.kt +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - * - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ - * | | / _ \ / _` | | | |_ / _ \ - * | |___| (_) | (_| | |_| |/ / __/ - * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ - * - * This file is part of the MEDINA Framework. - */ -package de.fraunhofer.aisec.codyze.medina.util - -import java.io.File -import java.io.IOException -import java.util.* -import mu.KotlinLogging -import org.openapitools.client.evidence.model.Evidence -import org.openapitools.client.orchestrator.model.AssessmentTool - -private val logger = KotlinLogging.logger {} -private val codyzeVersion = "1.0.0" - -/** - * Creates a Codyze AssessmentTool - * @author Robert Haimerl - * @param metrics a list of all available metrics for this tool - * @return the resulting AssessmentTool - */ -fun createAssessmentTool(metrics: List<String>): AssessmentTool { - logger.debug { "Creating the AssessmentTool" } - - val at = AssessmentTool() - at.id = "codyze-$codyzeVersion" - at.name = "MEDINA Codyze v$codyzeVersion" - // Description taken from the introduction of the GitHub README - at.description = - "Codyze is a static code analyzer that focuses on verifying security compliance in source code. It operates on code property graphs and is thus able to handle non-compiling or even incomplete code fragments." - // TODO: hardcoded for now, needs to be able to fetch metrics from available rules - at.availableMetrics = metrics - - return at -} - -/** - * Creates an Evidence - * @author Robert Haimerl - * @param resultFilePath the path to the results file used as evidence - * @return the resulting Evidence - */ -fun createEvidence(resultFilePath: String): Evidence { - logger.debug { "Creating new Evidence based on $resultFilePath" } - val ev = Evidence() - - ev.id = UUID.randomUUID().toString() - ev.timestamp = java.time.OffsetDateTime.now() - ev.serviceId = null - ev.toolId = "codyze-$codyzeVersion" - ev.raw = - try { - File(resultFilePath).bufferedReader().use { it.readText() } - } catch (e: IOException) { - "" - } - ev.resource = - object { - @Suppress("unused") val id = hash - } - return ev -} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Environment.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Environment.kt deleted file mode 100644 index 990cd3874d4eca191aaf7b4f58f0b3a716b873ec..0000000000000000000000000000000000000000 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Environment.kt +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - * - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ - * | | / _ \ / _` | | | |_ / _ \ - * | |___| (_) | (_| | |_| |/ / __/ - * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ - * - * This file is part of the MEDINA Framework. - */ -package de.fraunhofer.aisec.codyze.medina.util - -import de.fraunhofer.aisec.codyze.medina.Configuration.Environment -import java.io.BufferedReader -import java.io.InputStreamReader -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} -var environment = Environment.NONE -var environmentVariables = mutableMapOf<String, String>() -var hash: String? = null - -/** - * Tries multiple known environment variables to find out which CI/CD environment is being used - * Currently possible values for environment are ["None", "GitHub", "GitLab", "Jenkins"]. This - * method sets the "environment"-variable and fills the "environmentVariables"-Map specific to this - * environment - * @author Robert Haimerl - */ -fun verifyEnvironment() { - // all known CI/CD environments set "CI" to "true" - if (System.getenv("CI") != "true") { - logger.warn { "No CI/CD environment recognized" } - return - } - - val githubVars = arrayOf("GITHUB_ACTION", "GITHUB_JOB", "GITHUB_SHA") - val gitlabVars = arrayOf("CI_PIPELINE_ID", "CI_JOB_ID", "CI_COMMIT_SHA") - val jenkinsVars = arrayOf("JOB_NAME", "BUILD_ID", "JENKINS_URL") - - if (githubVars.all { variable -> System.getenv(variable) != null }) - environment = Environment.GITHUB - else if (gitlabVars.all { variable -> System.getenv(variable) != null }) - environment = Environment.GITLAB - else if (jenkinsVars.all { variable -> System.getenv(variable) != null }) - environment = Environment.JENKINS - - if (environment == Environment.NONE) logger.warn { "CI/CD environment could not be determined" } - else logger.info { "Determined CI/CD environment to be $environment" } -} - -/** - * Adds all relevant environment variables for the detected CI environment to the Map - * @author Robert Haimerl - */ -fun addEnvironmentVariables() { - // jenkins: "SCM-specific variables such as GIT_COMMIT are not automatically defined as - // environment variables; rather you can use the return value of the checkout step" - when (environment) { - Environment.GITHUB -> environmentVariables["commit_hash"] = "GITHUB_SHA" - Environment.GITLAB -> environmentVariables["commit_hash"] = "CI_COMMIT_SHA" - else -> return - } -} - -/** - * Tries environment variables set by GitHub actions, then GitLab CI/CD before ultimately falling - * back to console commands. This method modifies the "hash" member. - * @author Robert Haimerl - */ -@kotlin.jvm.Throws(RuntimeException::class) -fun fetchGitHash() { - // Default to "GITHUB_SHA" to prevent NullPointerExceptions from being thrown - hash = System.getenv(environmentVariables["commit_hash"] ?: "GITHUB_SHA") - if (hash == null) { - val process = Runtime.getRuntime().exec("git rev-parse HEAD") - val reader = BufferedReader(InputStreamReader(process.inputStream)) - hash = reader.readLine() - } - // If we couldn't figure hash out, abort - if (hash == null) - throw RuntimeException("Unable to fetch the Git commit hash for the analyzed project") -} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/MedinaSarif.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/MedinaSarif.kt new file mode 100644 index 0000000000000000000000000000000000000000..9997cea0e76a448faaf5674d5d5e835d0d27dd54 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/MedinaSarif.kt @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.util + +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import de.fraunhofer.aisec.codyze.medina.evaluation.Rule +import de.fraunhofer.aisec.codyze.medina.main.CodyzeMedina +import io.github.detekt.sarif4k.* +import java.nio.file.Path +import java.util.stream.Collectors +import kotlin.io.path.deleteIfExists +import kotlin.io.path.readText +import kotlin.io.path.writeText + +// The rules added to the ToolComponent, derived from evaluation.Rule +// NOTE: the order of rules is important as it is later referenced in the field "ruleIndex" +val rules: List<ReportingDescriptor> = + Rule.entries + .stream() + .map { + ReportingDescriptor( + name = it.name, + id = it.id, + fullDescription = MultiformatMessageString(text = it.description) + ) + } + .collect(Collectors.toList()) + +// The tool with Codyze-Medina as its driver and all custom rules +val codyzeMedinaComponent: ToolComponent = + ToolComponent( + name = "CodyzeMedina", + fullName = "CodyzeMedina ${CodyzeMedina.CODYZE_VERSION}", + organization = "Fraunhofer AISEC", + version = CodyzeMedina.CODYZE_VERSION, + rules = rules, + ) + +/** + * Prints a separate SARIF report from the MEDINA evaluation result. + * + * @param evaluationResults The MEDINA evaluation results + * @param filepath The location of the resulting SARIF file + * @author Robert Haimerl + */ +fun printSarifReport(evaluationResults: Map<Rule, EvaluationResult>, filepath: Path) { + val report = createSarifReport(evaluationResults) + filepath.writeText(SarifSerializer.toJson(report)) +} + +/** + * Takes the last run of an existing SARIF report (e.g. from the Codyze library) and adds it to the + * MEDINA report as an extension. + * + * @param oldLocation The location of the previous SARIF report. MUST be valid SARIF + * @param newLocation The location of the new SARIF report after amending. + * @param evaluationResults The MEDINA evaluation results + * @author Robert Haimerl + */ +fun amendExistingReport( + oldLocation: Path, + newLocation: Path, + evaluationResults: Map<Rule, EvaluationResult> +) { + val originalReport = oldLocation.readText() + oldLocation.deleteIfExists() + val originalSarif = SarifSerializer.fromJson(originalReport) + val lastRun = originalSarif.runs.last() + + // Add Codyze-Medina as the new driver and move the original driver to extensions + val newTool = + lastRun.tool.copy( + driver = codyzeMedinaComponent, + extensions = (lastRun.tool.extensions?.plus(listOf(lastRun.tool.driver))) + ) + val newResults = lastRun.results?.plus(collectResults(evaluationResults)) + + // Create the new runs by using the changed last run + val newRun = lastRun.copy(tool = newTool, results = newResults) + val newRuns: MutableList<Run> = + originalSarif.runs.subList(0, originalSarif.runs.size - 1).toMutableList() + newRuns.add(newRun) + + // Create the new SARIF by using the new runs + val newSarif = originalSarif.copy(runs = newRuns) + val newReport = SarifSerializer.toJson(newSarif) + newLocation.writeText(newReport) +} + +/** + * This function creates a valid SARIF report from the MEDINA rule evaluations It is a separate + * report with CodyzeMedina as a separate tool driver and not an extension + * + * @param evaluationResults The results of evaluation the MEDINA rules + * @author Robert Haimerl + */ +private fun createSarifReport(evaluationResults: Map<Rule, EvaluationResult>): SarifSchema210 { + val results = collectResults(evaluationResults) + return SarifSchema210( + schema = + "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + version = Version.The210, + runs = + listOf( + Run( + tool = Tool(driver = codyzeMedinaComponent), + results = results, + ) + ) + ) +} + +/** + * Converts the evaluation results to SARIF results + * + * @param evaluationResults The MEDINA evaluation results + * @return the corresponding SARIF results + * @author Robert Haimerl + */ +private fun collectResults(evaluationResults: Map<Rule, EvaluationResult>): List<Result> { + return evaluationResults.entries + .stream() + .map { (rule, evResult) -> + Result( + ruleID = rule.id, + kind = if (evResult.isValidated()) ResultKind.Pass else ResultKind.Fail, + message = Message(text = rule.description), + ) + } + .collect(Collectors.toList()) +} diff --git a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Util.kt b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Util.kt index d9368df79120f7357dc5ab54a5eaef99cce15c7e..f433609e28f7c65ed2dcd9cdfce5f26fc84ab064 100644 --- a/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Util.kt +++ b/src/main/kotlin/de/fraunhofer/aisec/codyze/medina/util/Util.kt @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 /* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,23 +30,22 @@ package de.fraunhofer.aisec.codyze.medina.util import de.fraunhofer.aisec.codyze.analysis.Finding import de.fraunhofer.aisec.codyze.config.Configuration +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import de.fraunhofer.aisec.codyze.medina.evaluation.Rule import de.fraunhofer.aisec.codyze.printer.LegacyPrinter import de.fraunhofer.aisec.codyze.printer.Printer import de.fraunhofer.aisec.codyze.printer.SarifPrinter -import java.io.InputStreamReader -import java.util.* -import mu.KotlinLogging -import org.openapitools.client.orchestrator.model.AssessmentResult -import org.openapitools.client.orchestrator.model.MetricConfiguration +import io.github.oshai.kotlinlogging.KotlinLogging +import java.nio.file.Path private val logger = KotlinLogging.logger {} -private val mapping = mutableMapOf<String, Pair<String, MetricConfiguration>>() /** * Prints the Findings the way specified in the configuration - * @author Robert Haimerl + * * @param findings the set of Findings - * @param config the config containing printing details (path and SARIF) + * @param config the configuration of codyze-medina + * @author Robert Haimerl */ fun printFindings(findings: Set<Finding>, config: Configuration) { val printer: Printer = @@ -59,80 +58,45 @@ fun printFindings(findings: Set<Finding>, config: Configuration) { } /** - * Creates an AssessmentResult from a Finding - * @author Robert Haimerl - * @param finding the Finding to be transformed - * @param evidenceId the id of the Evidence belonging to the AssessmentResult - * @return the resulting AssessmentResult or null if not included in mapping.txt - */ -fun findingToAR(finding: Finding, evidenceId: String): AssessmentResult? { - logger.debug { "Creating AssessmentResult for finding with id ${finding.identifier}" } - val ar = AssessmentResult() - - // TODO: currently ignores EVERY finding not specified in mappings.txt - if (mapping[finding.identifier] == null) { - logger.debug { "Finding with id ${finding.identifier} not specified; Ignoring" } - return null - } - - val mc = mapping[finding.identifier]!!.second - - ar.metricConfiguration = mc - ar.id = UUID.randomUUID().toString() - ar.timestamp = java.time.OffsetDateTime.now() - ar.evidenceId = evidenceId - ar.resourceId = hash - ar.metricId = mapping[finding.identifier]!!.first - ar.compliant = !finding.isProblem - ar.nonComplianceComments = finding.logMsg - - return ar -} - -/** - * Parses the given file which contains instructions on how to map Findings to AssessmentResults. - * The Resulting mappings are being added to "mapping" + * Creates a concluding analysis report, intended at to be at the very end + * + * @param medinaEvaluation The results from evaluating the Medina Metrics + * @param markViolationNumber The number of violations of MARK rules as returned by Codyze + * @param codyzeOutput The location of the output from the Codyze library + * @param output The location of the output as specified in the configuration + * @param combinedOutput Whether both output files were combined * @author Robert Haimerl - * @param mapFilePath the path of the file which includes the mapping information */ -fun parseMapping(mapFilePath: String) { - logger.info { "Parsing mappings from $mapFilePath" } +fun createLogReport( + medinaEvaluation: Map<Rule, EvaluationResult>, + markViolationNumber: Int, + codyzeOutput: String, + output: Path, + combinedOutput: Boolean +) { + logger.debug { "Creating the final report" } + // Filter for violated Rules + val negativeEvaluation = medinaEvaluation.filter { eval -> !eval.value.isValidated() } - val mapfile = InputStreamReader(ClassLoader.getSystemResourceAsStream(mapFilePath) ?: return) - // read from mappings.txt - val rawMappings = mapfile.useLines { it.toList() } - // parse each mapping and add it to a map - for (raw in rawMappings) { - val ar = raw.split("->")[1].drop(1).dropLast(1).split(";") - val kind = ar[4] - val targetValue: Any = - // in case we have a list of target values - if (ar[3].contains(":")) - when (kind) { - "N" -> ar[3].split(":").map { v: String -> v.toDouble() } - "B" -> ar[3].split(":").map { v: String -> v == "true" } - else -> ar[3].split(":") - } - // single values - else - when (kind) { - "N" -> ar[3].toDouble() - "B" -> ar[3] == "true" - else -> ar[3] - } + // Create the header of the summary + val summaryHeader = "\n-----------\n" + "Summary of Analysis\n" + "-----------\n\n" + // Create the summary of the MARK evaluation + val markSummary = + "Found $markViolationNumber violations of MARK rules.\n" + + "The Analysis has been written to ${if (combinedOutput) output else codyzeOutput}\n\n" - // create a new MetricConfiguration according to the file - val mc = MetricConfiguration() - mc.isDefault = ar[1] == "true" - mc.operator = ar[2] - mc.targetValue = targetValue - - // use the metric name and configuration as values of the map - val mapValue = Pair(ar[0], mc) + // Create the summary of the MEDINA evaluation + var medinaSummary = + "Found ${if (negativeEvaluation.isNotEmpty()) negativeEvaluation.size else "no"} violations of MEDINA rules" + + if (negativeEvaluation.isNotEmpty()) ":\n" else "\n" + for (eval in negativeEvaluation) { + medinaSummary += "\t- Rule ${eval.key}\n" + "\t\t \"${eval.value.getMessage()}\"\n" + } - // the findings we want to map to this assessmentResult - val findings = raw.split("->")[0].drop(1).dropLast(1).split(";") - for (mapKey in findings) if (mapKey != "") mapping[mapKey] = mapValue + // Print the whole message into the log + if (negativeEvaluation.isEmpty() && markViolationNumber == 0) { + logger.info { summaryHeader + markSummary + medinaSummary } + } else { + logger.error { summaryHeader + markSummary + medinaSummary } } - logger.debug { "Mappings: $mapping" } } diff --git a/src/main/resources/evidence.yaml b/src/main/resources/evidence.yaml index 9edfed182167feacd3a880509c8d4f83ef477bf5..4497d59ca1049acfdbb6a2461ce5a680f5a8c401 100644 --- a/src/main/resources/evidence.yaml +++ b/src/main/resources/evidence.yaml @@ -41,8 +41,17 @@ paths: description: Returns all stored evidences. Part of the public API, also exposed as REST. operationId: EvidenceStore_ListEvidences parameters: + - name: filter.cloudServiceId + in: query + schema: + type: string + - name: filter.toolId + in: query + schema: + type: string - name: pageSize in: query + description: 'page_size: 0 = default (50 is default value), > 0 = set value (i.e. page_size = 5 -> SQL-Limit = 5)' schema: type: integer format: int32 @@ -71,6 +80,33 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /v1/evidence_store/evidences/{evidenceId}: + get: + tags: + - EvidenceStore + description: |- + Returns a particular stored evidence. Part of the public API, also exposed + as REST. + operationId: EvidenceStore_GetEvidence + parameters: + - name: evidenceId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Evidence' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: schemas: Evidence: @@ -83,7 +119,7 @@ components: type: string description: time of evidence creation format: date-time - serviceId: + cloudServiceId: type: string description: Reference to a service this evidence was gathered from toolId: @@ -91,7 +127,7 @@ components: description: Reference to the tool which provided the evidence raw: type: string - description: Contains the evidence in its original form without following a defined schema, e.g. the raw JSON + description: Optional. Contains the evidence in its original form without following a defined schema, e.g. the raw JSON resource: $ref: '#/components/schemas/GoogleProtobufValue' description: An evidence resource @@ -132,10 +168,7 @@ components: description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' StoreEvidenceResponse: type: object - properties: - status: - type: boolean - statusMessage: - type: string + properties: {} + description: StoreEvidenceResponse belongs to StoreEvidence, which uses a custom unary RPC and therefore requires a response message according to the style convention. Since no return values are required, this is empty. tags: - name: EvidenceStore diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000000000000000000000000000000000000..bda1b36172385daa71df036b972b76318128dc7d --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,9 @@ +# Set root logger level to WARN and its only appender to A1. +log4j.rootLogger=WARN, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 1d1b7e18b0a7718f6d0ab905e1d1699790574f0e..23bbbda8e782ca4be279a2cd3a073c9fa238065e 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -5,7 +5,7 @@ <patternLayout pattern="%d{ABSOLUTE} %5p %c{1}:%L - %m%n" /> </console> - <file name="fileout" fileName="medina-codyze.log"> + <file name="fileout" fileName="codyze-medina.log"> <patternLayout pattern="%d{ABSOLUTE} %5p %c{1}:%L - %m%n"/> </file> </appenders> diff --git a/src/main/resources/orchestrator.yaml b/src/main/resources/orchestrator.yaml index bc269e13bfce2682882ba0ef3e588403bf93dc56..e3a12006df802697fc7fbb1fa2d950fdcd445740 100644 --- a/src/main/resources/orchestrator.yaml +++ b/src/main/resources/orchestrator.yaml @@ -14,6 +14,37 @@ paths: description: List all assessment results. Part of the public API, also exposed as REST. operationId: Orchestrator_ListAssessmentResults parameters: + - name: filter.cloudServiceId + in: query + description: Optional. List only assessment results of a specific cloud service. + schema: + type: string + - name: filter.compliant + in: query + description: Optional. List only compliant assessment results. + schema: + type: boolean + - name: filter.metricIds + in: query + description: Optional. List only assessment results of a specific metric id. + schema: + type: array + items: + type: string + - name: filter.metricId + in: query + schema: + type: string + - name: filter.toolId + in: query + description: Optional. List only assessment result from a specific assessment tool. + schema: + type: string + - name: latestByResourceId + in: query + description: Optional. Latest results grouped by resource_id and metric_id. + schema: + type: boolean - name: pageSize in: query schema: @@ -68,6 +99,31 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /v1/orchestrator/assessment_results/{id}: + get: + tags: + - Orchestrator + description: Get an assessment result by ID + operationId: Orchestrator_GetAssessmentResult + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AssessmentResult' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /v1/orchestrator/assessment_tools: get: tags: @@ -77,9 +133,30 @@ paths: passed metric id operationId: Orchestrator_ListAssessmentTools parameters: - - name: metricId + - name: filter.cloudServiceId + in: query + description: Optional. List only assessment results of a specific cloud service. + schema: + type: string + - name: filter.compliant + in: query + description: Optional. List only compliant assessment results. + schema: + type: boolean + - name: filter.metricIds + in: query + description: Optional. List only assessment results of a specific metric id. + schema: + type: array + items: + type: string + - name: filter.metricId in: query - description: filter tools by metric id + schema: + type: string + - name: filter.toolId + in: query + description: Optional. List only assessment result from a specific assessment tool. schema: type: string - name: pageSize @@ -136,18 +213,24 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/assessment_tools/{toolId}: - get: + /v1/orchestrator/assessment_tools/{tool.id}: + put: tags: - Orchestrator - description: Returns assessment tool given by the passed tool id - operationId: Orchestrator_GetAssessmentTool + description: Updates the assessment tool given by the passed id + operationId: Orchestrator_UpdateAssessmentTool parameters: - - name: toolId + - name: tool.id in: path required: true schema: type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssessmentTool' + required: true responses: "200": description: OK @@ -161,23 +244,18 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - put: + /v1/orchestrator/assessment_tools/{toolId}: + get: tags: - Orchestrator - description: Updates the assessment tool given by the passed id - operationId: Orchestrator_UpdateAssessmentTool + description: Returns assessment tool given by the passed tool id + operationId: Orchestrator_GetAssessmentTool parameters: - name: toolId in: path required: true schema: type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/AssessmentTool' - required: true responses: "200": description: OK @@ -214,12 +292,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/certificates: + /v1/orchestrator/catalogs: get: tags: - Orchestrator - description: Lists all target certificates - operationId: Orchestrator_ListCertificates + description: |- + Lists all security controls catalogs. Each catalog includes a list of its + categories but no additional sub-resources. + operationId: Orchestrator_ListCatalogs parameters: - name: pageSize in: query @@ -244,7 +324,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ListCertificatesResponse' + $ref: '#/components/schemas/ListCatalogsResponse' default: description: Default error response content: @@ -254,13 +334,13 @@ paths: post: tags: - Orchestrator - description: Creates a new certificate - operationId: Orchestrator_CreateCertificate + description: Creates a new security controls catalog + operationId: Orchestrator_CreateCatalog requestBody: content: application/json: schema: - $ref: '#/components/schemas/Certificate' + $ref: '#/components/schemas/Catalog' required: true responses: "200": @@ -268,62 +348,65 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Certificate' + $ref: '#/components/schemas/Catalog' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/certificates/{certificateId}: - get: + /v1/orchestrator/catalogs/{catalog.id}: + put: tags: - Orchestrator - description: Retrieves a certificate - operationId: Orchestrator_GetCertificate + description: Updates an existing certificate + operationId: Orchestrator_UpdateCatalog parameters: - - name: certificateId + - name: catalog.id in: path required: true schema: type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Catalog' + required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/Certificate' + $ref: '#/components/schemas/Catalog' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - put: + /v1/orchestrator/catalogs/{catalogId}: + get: tags: - Orchestrator - description: Updates an existing certificate - operationId: Orchestrator_UpdateCertificate + description: |- + Retrieves a specific catalog by it's ID. The catalog includes a list of all + of it categories as well as the first level of controls in each category. + operationId: Orchestrator_GetCatalog parameters: - - name: certificateId + - name: catalogId in: path required: true schema: type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Certificate' - required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/Certificate' + $ref: '#/components/schemas/Catalog' default: description: Default error response content: @@ -333,10 +416,10 @@ paths: delete: tags: - Orchestrator - description: Removes a certificate - operationId: Orchestrator_RemoveCertificate + description: Removes a catalog + operationId: Orchestrator_RemoveCatalog parameters: - - name: certificateId + - name: catalogId in: path required: true schema: @@ -351,12 +434,84 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/cloud_services: + /v1/orchestrator/catalogs/{catalogId}/categories/{categoryName}/controls/{controlId}: get: tags: - Orchestrator - description: Lists all target cloud services - operationId: Orchestrator_ListCloudServices + description: |- + Retrieves a control specified by the catalog ID, the control's category + name and the control ID. If present, it also includes a list of + sub-controls if present or a list of metrics if no sub-controls but metrics + are present. + operationId: Orchestrator_GetControl + parameters: + - name: catalogId + in: path + required: true + schema: + type: string + - name: categoryName + in: path + required: true + schema: + type: string + - name: controlId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Control' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/catalogs/{catalogId}/category/{categoryName}: + get: + tags: + - Orchestrator + description: |- + Retrieves a category of a catalog specified by the catalog ID and the + category name. It includes the first level of controls within each + category. + operationId: Orchestrator_GetCategory + parameters: + - name: catalogId + in: path + required: true + schema: + type: string + - name: categoryName + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Category' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/certificates: + get: + tags: + - Orchestrator + description: Lists all target certificates + operationId: Orchestrator_ListCertificates parameters: - name: pageSize in: query @@ -381,7 +536,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ListCloudServicesResponse' + $ref: '#/components/schemas/ListCertificatesResponse' default: description: Default error response content: @@ -391,13 +546,13 @@ paths: post: tags: - Orchestrator - description: Registers a new target cloud service - operationId: Orchestrator_RegisterCloudService + description: Creates a new certificate + operationId: Orchestrator_CreateCertificate requestBody: content: application/json: schema: - $ref: '#/components/schemas/CloudService' + $ref: '#/components/schemas/Certificate' required: true responses: "200": @@ -405,62 +560,63 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CloudService' + $ref: '#/components/schemas/Certificate' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/cloud_services/{serviceId}: - get: + /v1/orchestrator/certificates/{certificate.id}: + put: tags: - Orchestrator - description: Retrieves a target cloud service - operationId: Orchestrator_GetCloudService + description: Updates an existing certificate + operationId: Orchestrator_UpdateCertificate parameters: - - name: serviceId + - name: certificate.id in: path required: true schema: type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Certificate' + required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/CloudService' + $ref: '#/components/schemas/Certificate' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - put: + /v1/orchestrator/certificates/{certificateId}: + get: tags: - Orchestrator - description: Registers a new target cloud service - operationId: Orchestrator_UpdateCloudService + description: Retrieves a certificate + operationId: Orchestrator_GetCertificate parameters: - - name: serviceId + - name: certificateId in: path required: true schema: type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/CloudService' - required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/CloudService' + $ref: '#/components/schemas/Certificate' default: description: Default error response content: @@ -470,10 +626,10 @@ paths: delete: tags: - Orchestrator - description: Removes a target cloud service - operationId: Orchestrator_RemoveCloudService + description: Removes a certificate + operationId: Orchestrator_RemoveCertificate parameters: - - name: serviceId + - name: certificateId in: path required: true schema: @@ -488,43 +644,174 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/cloud_services/{serviceId}/metric_configurations: + /v1/orchestrator/cloud_services: get: tags: - Orchestrator - description: |- - Lists all a metric configurations (target value and operator) for a - specific service ID - operationId: Orchestrator_ListMetricConfigurations + description: Lists all target cloud services + operationId: Orchestrator_ListCloudServices parameters: - - name: serviceId - in: path - required: true + - name: pageSize + in: query + schema: + type: integer + format: int32 + - name: pageToken + in: query + schema: + type: string + - name: orderBy + in: query schema: type: string + - name: asc + in: query + schema: + type: boolean responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/ListMetricConfigurationResponse' + $ref: '#/components/schemas/ListCloudServicesResponse' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/cloud_services/{serviceId}/metric_configurations/{metricId}: - get: + post: tags: - Orchestrator - description: |- - Retrieves a metric configuration (target value and operator) for a specific - service and metric ID + description: Registers a new target cloud service + operationId: Orchestrator_RegisterCloudService + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CloudService' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CloudService' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/statistics: + get: + tags: + - Orchestrator + description: Retrieves target cloud service statistics + operationId: Orchestrator_GetCloudServiceStatistics + parameters: + - name: cloudServiceId + in: query + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetCloudServiceStatisticsResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{cloudServiceId}: + get: + tags: + - Orchestrator + description: Retrieves a target cloud service + operationId: Orchestrator_GetCloudService + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CloudService' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + delete: + tags: + - Orchestrator + description: Removes a target cloud service + operationId: Orchestrator_RemoveCloudService + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{cloudServiceId}/metric_configurations: + get: + tags: + - Orchestrator + description: |- + Lists all a metric configurations (target value and operator) for a + specific service ID + operationId: Orchestrator_ListMetricConfigurations + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListMetricConfigurationResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{cloudServiceId}/metric_configurations/{metricId}: + get: + tags: + - Orchestrator + description: |- + Retrieves a metric configuration (target value and operator) for a specific + service and metric ID. operationId: Orchestrator_GetMetricConfiguration parameters: - - name: serviceId + - name: cloudServiceId in: path required: true schema: @@ -555,7 +842,7 @@ paths: service and metric ID operationId: Orchestrator_UpdateMetricConfiguration parameters: - - name: serviceId + - name: cloudServiceId in: path required: true schema: @@ -577,7 +864,345 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MetricConfiguration' + $ref: '#/components/schemas/MetricConfiguration' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{cloudServiceId}/toes/{catalogId}: + get: + tags: + - Orchestrator + description: Retrieves a Target of Evaluation + operationId: Orchestrator_GetTargetOfEvaluation + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + - name: catalogId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TargetOfEvaluation' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + delete: + tags: + - Orchestrator + description: Removes a Target of Evaluation + operationId: Orchestrator_RemoveTargetOfEvaluation + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + - name: catalogId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{cloudServiceId}/toes/{catalogId}/controls_in_scope: + get: + tags: + - Orchestrator + description: Lists all controls in scope of a target of evaluation. + operationId: Orchestrator_ListControlsInScope + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + - name: catalogId + in: path + required: true + schema: + type: string + - name: pageSize + in: query + schema: + type: integer + format: int32 + - name: pageToken + in: query + schema: + type: string + - name: orderBy + in: query + schema: + type: string + - name: asc + in: query + schema: + type: boolean + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListControlsInScopeResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + ? /v1/orchestrator/cloud_services/{cloudServiceId}/toes/{catalogId}/controls_in_scope/categories/{controlCategoryName}/controls/{controlId} + : delete: + tags: + - Orchestrator + description: Adds the selected control as "in scope" for the target of evaluation. + operationId: Orchestrator_RemoveControlFromScope + parameters: + - name: cloudServiceId + in: path + required: true + schema: + type: string + - name: catalogId + in: path + required: true + schema: + type: string + - name: controlCategoryName + in: path + required: true + schema: + type: string + - name: controlId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{cloud_service.id}: + put: + tags: + - Orchestrator + description: Registers a new target cloud service + operationId: Orchestrator_UpdateCloudService + parameters: + - name: cloud_service.id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CloudService' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CloudService' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + ? /v1/orchestrator/cloud_services/{scope.target_of_evaluation_cloud_service_id}/toes/{scope.target_of_evaluation_catalog_id}/controls_in_scope + : post: + tags: + - Orchestrator + description: Adds the selected control as "in scope" for the target of evaluation. + operationId: Orchestrator_AddControlToScope + parameters: + - name: scope.target_of_evaluation_cloud_service_id + in: path + required: true + schema: + type: string + - name: scope.target_of_evaluation_catalog_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ControlInScope' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ControlInScope' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + ? /v1/orchestrator/cloud_services/{scope.target_of_evaluation_cloud_service_id}/toes/{scope.target_of_evaluation_catalog_id}/controls_in_scope/categories/{scope.control_category_name}/controls/{scope.control_id} + : put: + tags: + - Orchestrator + description: Updates a particular control in scope, e.g., its monitoring status. + operationId: Orchestrator_UpdateControlInScope + parameters: + - name: scope.target_of_evaluation_cloud_service_id + in: path + required: true + schema: + type: string + - name: scope.target_of_evaluation_catalog_id + in: path + required: true + schema: + type: string + - name: scope.control_category_name + in: path + required: true + schema: + type: string + - name: scope.control_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ControlInScope' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ControlInScope' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/cloud_services/{target_of_evaluation.cloud_service_id}/toes/{target_of_evaluation.catalog_id}: + put: + tags: + - Orchestrator + description: Updates an existing Target of Evaluation + operationId: Orchestrator_UpdateTargetOfEvaluation + parameters: + - name: target_of_evaluation.cloud_service_id + in: path + required: true + schema: + type: string + - name: target_of_evaluation.catalog_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TargetOfEvaluation' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TargetOfEvaluation' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/controls: + get: + tags: + - Orchestrator + description: |- + If no additional parameters are specified, this lists all controls. If a + catalog ID and a category name is specified, then only controls containing + in this category are returned. + operationId: Orchestrator_ListControls + parameters: + - name: catalogId + in: query + description: return either all controls or only the controls of the specified category + schema: + type: string + - name: categoryName + in: query + schema: + type: string + - name: pageSize + in: query + schema: + type: integer + format: int32 + - name: pageToken + in: query + schema: + type: string + - name: orderBy + in: query + schema: + type: string + - name: asc + in: query + schema: + type: boolean + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListControlsResponse' default: description: Default error response content: @@ -645,38 +1270,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/metrics/{metricId}: - get: + /v1/orchestrator/metrics/{implementation.metric_id}/implementation: + put: tags: - Orchestrator - description: Returns the metric with the passed metric id - operationId: Orchestrator_GetMetric + description: Updates an existing metric implementation + operationId: Orchestrator_UpdateMetricImplementation parameters: - - name: metricId + - name: implementation.metric_id in: path required: true schema: type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MetricImplementation' + required: true responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/Metric' + $ref: '#/components/schemas/MetricImplementation' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' + /v1/orchestrator/metrics/{metric.id}: put: tags: - Orchestrator description: Updates an existing metric operationId: Orchestrator_UpdateMetric parameters: - - name: metricId + - name: metric.id in: path required: true schema: @@ -700,52 +1332,43 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/metrics/{metricId}/implementation: + /v1/orchestrator/metrics/{metricId}: get: tags: - Orchestrator - description: Returns the metric implementation of the passed metric id - operationId: Orchestrator_GetMetricImplementation + description: Returns the metric with the passed metric id + operationId: Orchestrator_GetMetric parameters: - name: metricId in: path required: true schema: type: string - - name: lang - in: query - schema: - type: string responses: "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/MetricImplementation' + $ref: '#/components/schemas/Metric' default: description: Default error response content: application/json: schema: $ref: '#/components/schemas/Status' - put: + /v1/orchestrator/metrics/{metricId}/implementation: + get: tags: - Orchestrator - description: Updates an existing metric implementation - operationId: Orchestrator_UpdateMetricImplementation + description: Returns the metric implementation of the passed metric id + operationId: Orchestrator_GetMetricImplementation parameters: - name: metricId in: path required: true schema: type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/MetricImplementation' - required: true responses: "200": description: OK @@ -759,12 +1382,79 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' - /v1/orchestrator/requirements: + /v1/orchestrator/public/certificates: + get: + tags: + - Orchestrator + description: Lists all target certificates without state history + operationId: Orchestrator_ListPublicCertificates + parameters: + - name: pageSize + in: query + schema: + type: integer + format: int32 + - name: pageToken + in: query + schema: + type: string + - name: orderBy + in: query + schema: + type: string + - name: asc + in: query + schema: + type: boolean + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListPublicCertificatesResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/runtime_info: get: tags: - Orchestrator - operationId: Orchestrator_ListRequirements + description: Get Runtime Information + operationId: Orchestrator_GetRuntimeInfo + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Runtime' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/orchestrator/toes: + get: + tags: + - Orchestrator + description: Lists all Targets of Evaluation + operationId: Orchestrator_ListTargetsOfEvaluation parameters: + - name: cloudServiceId + in: query + description: We cannot create additional bindings when the parameter is optional so we check for != "" in the method to see if it is set when the service is specified, return all Targets of Evaluation that evaluate the given service for any catalog + schema: + type: string + - name: catalogId + in: query + description: when the catalog is specified, return all Targets of Evaluation that evaluate the given catalog for any service + schema: + type: string - name: pageSize in: query schema: @@ -788,7 +1478,31 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ListRequirementsResponse' + $ref: '#/components/schemas/ListTargetsOfEvaluationResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - Orchestrator + description: Creates a new Target of Evaluation + operationId: Orchestrator_CreateTargetOfEvaluation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TargetOfEvaluation' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TargetOfEvaluation' default: description: Default error response content: @@ -837,6 +1551,12 @@ components: nonComplianceComments: type: string description: Some comments on the reason for non-compliance + cloudServiceId: + type: string + description: The cloud service which this assessment result belongs to + toolId: + type: string + description: Reference to the tool which provided the assessment result description: A result resource, representing the result after assessing the cloud resource with id resource_id. AssessmentTool: type: object @@ -853,6 +1573,52 @@ components: type: string description: a list of metrics that this tool can assess, referred by their ids description: Represents an external tool or service that offers assessments according to certain metrics. + Catalog: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + categories: + type: array + items: + $ref: '#/components/schemas/Category' + allInScope: + type: boolean + description: Certain security catalogs do not allow to select the scope of the controls, but all controls are automatically "in scope", however they can be set to a DELEGATED status. + assuranceLevels: + type: array + items: + type: string + description: A list of the assurance levels, e.g., basic, substantial and high for the EUCS catalog. + shortName: + type: string + description: Catalogs short name, e.g. EUCS + metadata: + $ref: '#/components/schemas/Catalog_Metadata' + Catalog_Metadata: + type: object + properties: + color: + type: string + description: a color for the cloud service used by the UI + Category: + type: object + properties: + name: + type: string + catalogId: + type: string + description: Reference to the catalog this category belongs to. + description: + type: string + controls: + type: array + items: + $ref: '#/components/schemas/Control' Certificate: type: object properties: @@ -860,7 +1626,7 @@ components: type: string name: type: string - serviceId: + cloudServiceId: type: string issueDate: type: string @@ -889,15 +1655,120 @@ components: type: string description: type: string - requirements: - $ref: '#/components/schemas/CloudService_Requirements' - CloudService_Requirements: + catalogsInScope: + type: array + items: + $ref: '#/components/schemas/Catalog' + configuredMetrics: + type: array + items: + $ref: '#/components/schemas/Metric' + createdAt: + type: string + description: creation time of the cloud_service + format: date-time + updatedAt: + type: string + description: last update time of the cloud_service + format: date-time + metadata: + $ref: '#/components/schemas/CloudService_Metadata' + CloudService_Metadata: + type: object + properties: + labels: + type: object + additionalProperties: + type: string + description: a map of key/value pairs, e.g., env:prod + icon: + type: string + description: an icon for the cloud service used by the UI + Control: type: object properties: - requirementIds: + id: + type: string + description: A short name of the control, e.g. OPS-01, as used in OSCAL; it is not a unique ID! + categoryName: + type: string + categoryCatalogId: + type: string + name: + type: string + description: Human-readable name of the control + description: + type: string + description: Description of the control + controls: type: array items: - type: string + $ref: '#/components/schemas/Control' + description: List of sub - controls - this is in accordance with the OSCAL model. + metrics: + type: array + items: + $ref: '#/components/schemas/Metric' + description: metrics contains either a list of reference to metrics - in this case only the id field of the metric is populated - or a list of populated metric meta-data, most likely returned by the database. + parentControlId: + type: string + description: Reference to the parent category this control belongs to. + parentControlCategoryName: + type: string + parentControlCategoryCatalogId: + type: string + assuranceLevel: + type: string + description: An assurance level is not offered by every catalog, therefore it is optional. + description: Control represents a certain Control that needs to be fulfilled. It could be a Control in a certification catalog. It follows the OSCAL model. A requirement in the EUCS terminology, e.g., is represented as the lowest sub-control. + ControlInScope: + type: object + properties: + targetOfEvaluationCloudServiceId: + type: string + targetOfEvaluationCatalogId: + type: string + controlId: + type: string + controlCategoryName: + type: string + controlCategoryCatalogId: + type: string + monitoringStatus: + enum: + - MONITORING_STATUS_UNSPECIFIED + - MONITORING_STATUS_AUTOMATICALLY_MONITORED + - MONITORING_STATUS_MANUALLY_MONITORED + - MONITORING_STATUS_DELEGATED + type: string + format: enum + description: ControlInScope defines a control which is "in scope" of a target of evaluation. Additional meta-data can be defined when a control is in scope, e.g., its monitoring status (continuously monitored, delegated, etc.) + Dependency: + type: object + properties: + path: + type: string + version: + type: string + GetCloudServiceStatisticsResponse: + type: object + properties: + numberOfDiscoveredResources: + type: integer + description: number of discovered resources per cloud service + format: int64 + numberOfAssessmentResults: + type: integer + description: number of assessment results per cloud service + format: int64 + numberOfEvidences: + type: integer + description: number of evidences per cloud service + format: int64 + numberOfSelectedCatalogs: + type: integer + description: number of selected catalogs per cloud service + format: int64 GoogleProtobufAny: type: object properties: @@ -926,6 +1797,15 @@ components: $ref: '#/components/schemas/AssessmentTool' nextPageToken: type: string + ListCatalogsResponse: + type: object + properties: + catalogs: + type: array + items: + $ref: '#/components/schemas/Catalog' + nextPageToken: + type: string ListCertificatesResponse: type: object properties: @@ -944,6 +1824,24 @@ components: $ref: '#/components/schemas/CloudService' nextPageToken: type: string + ListControlsInScopeResponse: + type: object + properties: + controlsInScope: + type: array + items: + $ref: '#/components/schemas/ControlInScope' + nextPageToken: + type: string + ListControlsResponse: + type: object + properties: + controls: + type: array + items: + $ref: '#/components/schemas/Control' + nextPageToken: + type: string ListMetricConfigurationResponse: type: object properties: @@ -961,13 +1859,22 @@ components: $ref: '#/components/schemas/Metric' nextPageToken: type: string - ListRequirementsResponse: + ListPublicCertificatesResponse: + type: object + properties: + certificates: + type: array + items: + $ref: '#/components/schemas/Certificate' + nextPageToken: + type: string + ListTargetsOfEvaluationResponse: type: object properties: - requirements: + targetOfEvaluation: type: array items: - $ref: '#/components/schemas/Requirement' + $ref: '#/components/schemas/TargetOfEvaluation' nextPageToken: type: string Metric: @@ -1000,6 +1907,8 @@ components: type: integer description: The interval in seconds the evidences must be collected for the respective metric. For now, we are not able to use google.protobuf.Duration because it is converted to a custom object in OpenAPI (https://github.com/google/gnostic/issues/351) format: int64 + implementation: + $ref: '#/components/schemas/MetricImplementation' description: A metric resource MetricConfiguration: type: object @@ -1016,6 +1925,12 @@ components: type: string description: The last time of update format: date-time + metricId: + type: string + description: The metric this configuration belongs to + cloudServiceId: + type: string + description: The service this configuration belongs to description: Defines the operator and a target value for an individual metric MetricImplementation: type: object @@ -1026,7 +1941,7 @@ components: lang: enum: - LANGUAGE_UNSPECIFIED - - REGO + - LANGUAGE_REGO type: string description: The language this metric is implemented in format: enum @@ -1068,24 +1983,30 @@ components: minMax: $ref: '#/components/schemas/MinMax' description: A range resource representing the range of values - Requirement: + Runtime: type: object properties: - id: + releaseVersion: type: string - name: + description: release_version is the latest Clouditor release version for this commit + vcs: type: string - description: + description: vcs is the used version control system + commitHash: type: string - metrics: + description: commit_hash is the current Clouditor commit hash + commitTime: + type: string + description: commit_time is the time of the Clouditor commit + format: date-time + golangVersion: + type: string + description: golang_version is the used golang version + dependencies: type: array items: - $ref: '#/components/schemas/Metric' - description: metrics contains either a list of reference to metrics - in this case only the id field of the metric is populated - or a list of populated metric meta-data, most likely returned by the database. - category: - type: string - description: category can be used to categorize requirements, e.g., according to a specific security catalog or something that groups requirements together, e.g., a security control. - description: Requirement represents a certain requirement that needs to be fulfilled. It could be a control in a certification catalog. + $ref: '#/components/schemas/Dependency' + description: dependency is a list of used runtime dependencies State: type: object properties: @@ -1119,11 +2040,24 @@ components: description: A list of messages that carry the error details. There is a common set of message types for APIs to use. description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' StoreAssessmentResultResponse: + type: object + properties: {} + description: StoreAssessmentResultReponse belongs to StoreAssessmentResult, which uses a custom unary RPC and therefore requires a response message according to the style convention. Since no return values are required, this is empty. + TargetOfEvaluation: type: object properties: - status: - type: boolean - statusMessage: + cloudServiceId: + type: string + catalogId: type: string + assuranceLevel: + type: string + description: an assurance level is not offered by every catalog, therefore it is optional + controlsInScope: + type: array + items: + $ref: '#/components/schemas/Control' + description: 'the controls that are in scope of this ToE. Note: For some security catalogs, e.g., the EUCS, a specific set of controls (in the "worst case": all) are automatically in scope. In this case, this list needs auto-filled at an appropriate time, e.g,. in CreateTargetOfEvaluation. Note: Because of limitations of our ORM framework, this field only contains a list of controls that are in scope of the target, but not the actual meta-data associated it with it (which is of message type ControlInScope). In order to retrieve the meta-data of the controls, the RPC ListControlsInScope (or the associated REST path) must be called.' + description: A Target of Evaluation binds a cloud service to a catalog, so the service is evaluated regarding this catalog's controls tags: - name: Orchestrator diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/AuthTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/AuthTest.kt index c41601b4ef8713fd84167adeaf2c077f42728d9e..c2c06d78505cf0e6dbbb30b3eae330c060578e27 100644 --- a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/AuthTest.kt +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/AuthTest.kt @@ -28,6 +28,7 @@ */ package de.fraunhofer.aisec.codyze.medina +import de.fraunhofer.aisec.codyze.medina.connection.Connection import java.time.OffsetDateTime import java.util.* import org.junit.jupiter.api.Assertions @@ -44,6 +45,7 @@ class AuthTest { /** * Tests the OAuth connection to a local instance of the Orchestrator + * * @author Florian Wendland * @author Robert Haimerl */ @@ -52,8 +54,8 @@ class AuthTest { fun testLocalAuthConnection() { val connection = Connection(orchestratorURL, tokenURL, testUsername, testPassword) - Assertions.assertTrue(connection.testOAuthConnection()) - Assertions.assertTrue(connection.testOrchestratorConnection()) + Assertions.assertTrue(connection.getOAuthManager().testOAuthConnection()) + Assertions.assertTrue(connection.getApiManager().testOrchestratorConnection()) // create a MetricConfiguration (used for both AssessmentResults below) val mConfig = MetricConfiguration() @@ -84,11 +86,12 @@ class AuthTest { compAR.nonComplianceComments = "does not comply" val resultsToSend = arrayOf(nCompAR, compAR) - Assertions.assertTrue(connection.sendAssessmentResults(resultsToSend)) + Assertions.assertTrue(connection.getApiManager().sendAssessmentResults(resultsToSend)) } /** * Tests the OAuth connection to a remote instance of the Orchestrator + * * @author Florian Wendland */ @Disabled @@ -101,6 +104,6 @@ class AuthTest { val connection = Connection("", remoteTokenURI, username, password) - Assertions.assertTrue(connection.testOAuthConnection()) + Assertions.assertTrue(connection.getOAuthManager().testOAuthConnection()) } } diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CliTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CliTest.kt index b2e856614491a98660724717d8f9da5795545e6b..fae7b2fbac9be016bc6eaff8f33bca352be87f24 100644 --- a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CliTest.kt +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CliTest.kt @@ -28,6 +28,7 @@ */ package de.fraunhofer.aisec.codyze.medina +import de.fraunhofer.aisec.codyze.medina.main.Configuration import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import picocli.CommandLine @@ -36,12 +37,15 @@ class CliTest { /** * Tests the fail response on a missing OAuth-Endpoint + * * @author Florian Wendland */ @Test fun missingOAuth() { val args = arrayOf( + "--id", + "cli-test", "--username", "user", "--password", @@ -57,12 +61,15 @@ class CliTest { /** * Tests the fail response on a missing OAuth-Username + * * @author Florian Wendland */ @Test fun missingUsername() { val args = arrayOf( + "--id", + "cli-test", "--oauth-endpoint", "https://localhost:8080/", "--password", @@ -78,12 +85,15 @@ class CliTest { /** * Tests the fail response on a missing Orchestrator-Endpoint + * * @author Florian Wendland */ @Test fun missingOrchestrator() { val args = arrayOf( + "--id", + "cli-test", "--oauth-endpoint", "https://localhost:8080/", "--username", @@ -97,14 +107,41 @@ class CliTest { Assertions.assertThrows(CommandLine.ParameterException::class.java) { config.validate() } } + /** + * Tests the fail response on a missing cloudServiceId + * + * @author Robert Haimerl + */ + @Test + fun missingId() { + val args = + arrayOf( + "--oauth-endpoint", + "https://localhost:8080/", + "--username", + "user", + "--password", + "pw", + "--endpoint", + "http://localhost:8080/" + ) + + val config = Configuration() + CommandLine(config).parseArgs(*args) + Assertions.assertThrows(CommandLine.ParameterException::class.java) { config.validate() } + } + /** * Tests the response for a valid configuration + * * @author Florian Wendland */ @Test fun valid() { val args = arrayOf( + "--id", + "cli-test", "--oauth-endpoint", "https://localhost:8080/", "--username", diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeCliPassThroughTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeCliPassThroughTest.kt index af5d0ea6e5dd50643811a8cbbb3b6dd9ce839092..009a68e19a45b1e498f2330e269c14dab54759f1 100644 --- a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeCliPassThroughTest.kt +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/CodyzeCliPassThroughTest.kt @@ -28,6 +28,7 @@ */ package de.fraunhofer.aisec.codyze.medina +import de.fraunhofer.aisec.codyze.medina.main.Configuration import java.io.File import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -36,12 +37,15 @@ import picocli.CommandLine class CodyzeCliPassThroughTest { /** * Tests whether cli args are correctly passed on to codyze + * * @author Florian Wendland */ @Test fun cliArgs() { val args = arrayOf( + "--id", + "cli-pass-through-test", "--username", "user", "--password", @@ -62,6 +66,7 @@ class CodyzeCliPassThroughTest { /** * Tests whether config file args are correctly passed on to codyze + * * @author Florian Wendland */ @Test @@ -71,8 +76,9 @@ class CodyzeCliPassThroughTest { CodyzeCliPassThroughTest::class .java .classLoader - .getResource("codyze.yaml")!! - .toURI() + .getResource("codyze.yaml") + ?.toURI() + ?: Assertions.fail("Could not load codyze.yaml") ) .toPath() .toString() diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConfigurationTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConfigurationTest.kt index 87797aada70d4e8865b303ad0104f1c8398019f5..02f460768aacb8129fbc79e1dfadd6edc87958c7 100644 --- a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConfigurationTest.kt +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConfigurationTest.kt @@ -28,6 +28,7 @@ */ package de.fraunhofer.aisec.codyze.medina +import de.fraunhofer.aisec.codyze.medina.main.Configuration import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import picocli.CommandLine @@ -36,6 +37,7 @@ class ConfigurationTest { /** * Tests parsing of the cli arguments + * * @author Florian Wendland */ @Test diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConverterTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConverterTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c50fbcd59525f71079694eea48a5b33876d20572 --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ConverterTest.kt @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina + +import de.fraunhofer.aisec.codyze.analysis.Finding +import de.fraunhofer.aisec.codyze.medina.assembling.Assembler +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.main.Configuration +import de.fraunhofer.aisec.codyze.medina.mapping.parseMapping +import de.fraunhofer.aisec.codyze.sarif.schema.Result +import de.fraunhofer.aisec.mark.markDsl.Action +import java.io.File +import java.net.URI +import java.nio.file.Path +import kotlin.io.path.Path +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class ConverterTest { + /** + * Tests the conversion from Findings to compliant AssessmentResults + * + * @author Robert Haimerl + */ + @Test + fun testCompliant() { + // create some Finding object + val logMsg = "Variable cm not initialized" + val artifactUri = URI.create("file:///tmp/test.cpp") + val id = "WrongUseOfBotan_CipherMode" + val kind = Result.Kind.PASS + val f1 = Finding(id, Action.INFO, logMsg, artifactUri, listOf(), kind) + + val assembler = + Assembler(Environment(Configuration.CIEnvironment.NONE, Path.of("")), "test-id") + val assessmentResults = + assembler.convertFindingsToAssessmentResults( + parseMapping(File("src/test/resources/Mark/exampleMapping.yaml"))!!, + setOf(f1), + "", + "123" + ) + + assertNotNull(assessmentResults) + assertEquals(1, assessmentResults.size) + val result = assessmentResults.first() + assertTrue(result.compliant ?: false) + assertEquals(arrayListOf(1.23, 3.14), result.metricConfiguration!!.targetValue) + assertEquals("", result.nonComplianceComments) + assertEquals("TestMetric1", result.metricId) + } + + /** + * Tests the conversion from Findings to non-compliant AssessmentResults + * + * @author Robert Haimerl + */ + @Test + fun testNonCompliant() { + // create some Finding object + val logMsg = "You should not do that" + val artifactUri = URI.create("file:///tmp/test.cpp") + val id = "VariableNotInitialized" + val kind = Result.Kind.FAIL + val f2 = Finding(id, Action.FAIL, logMsg, artifactUri, listOf(), kind) + + val assembler = + Assembler(Environment(Configuration.CIEnvironment.NONE, Path("")), "test-id") + val assessmentResults = + assembler.convertFindingsToAssessmentResults( + parseMapping(File("src/test/resources/Mark/exampleMapping.yaml"))!!, + setOf(f2), + "", + "123" + ) + + assertNotNull(assessmentResults) + assertEquals(1, assessmentResults.size) + val result = assessmentResults.first() + assertTrue(!(result.compliant ?: true)) + assertEquals(true, result.metricConfiguration!!.targetValue) + assertEquals("${f2.identifier}: ${f2.logMsg}\n", result.nonComplianceComments) + assertEquals("TestMetric2", result.metricId) + } +} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/DemoTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/DemoTest.kt index cd62e0bfbc5f4ccf9b725813bcdb85123b293896..58dcdc2ce993f902aad7442895d95efa91648363 100644 --- a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/DemoTest.kt +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/DemoTest.kt @@ -36,6 +36,7 @@ class DemoTest { /** * Implements a demonstration printing the findings of the TlsServer.java class + * * @author Florian Wendland */ @Test diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/IntegrationTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/IntegrationTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..53080b34866d0b1a9cb4b8916e41889c3fd8c614 --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/IntegrationTest.kt @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022-2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina + +import de.fraunhofer.aisec.codyze.analysis.AnalysisServer +import de.fraunhofer.aisec.codyze.medina.assembling.Assembler +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.connection.Connection +import de.fraunhofer.aisec.codyze.medina.main.Configuration +import java.io.File +import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.io.path.Path +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.openapitools.client.orchestrator.model.AssessmentResult +import org.openapitools.client.orchestrator.model.MetricConfiguration +import picocli.CommandLine + +class IntegrationTest { + + private val args = arrayOf("--config", "src/test/resources/codyze-test.yaml") + + @Test + @Disabled + fun testCodyzeIntegration() { + // 1 ----------- Test Configuration + // parse configuration + val config = Configuration.initialize(args) + CommandLine(config).setUnmatchedArgumentsAllowed(true).parseArgs(*args) + // try to convert to codyze config + val codyzeConfig = + de.fraunhofer.aisec.codyze.config.Configuration.initConfig( + config.configFile.path.toFile(), + *args, + "--default-passes", + "--passes+", + "de.fraunhofer.aisec.cpg.passes.IdentifierPass", + "--passes+", + "de.fraunhofer.aisec.cpg.passes.EdgeCachePass" + ) + // assert that the specified configuration was taken over to the codyzeConfig + Assertions.assertTrue( + Path(codyzeConfig.output).endsWith(Path("src/test/resources/codyze.sarif")) + ) + Assertions.assertEquals(1, codyzeConfig.source.size) + Assertions.assertTrue( + codyzeConfig.source[0].endsWith(File("src/test/resources/exampleFiles/2_1_2_1_02.cpp")) + ) + Assertions.assertTrue(codyzeConfig.sarifOutput) + + // 2 ----------- Test Server Creation + val server = AnalysisServer(codyzeConfig) + server.start() + // assert that the server could be initialized + Assertions.assertNotNull(server) + + // 3 ----------- Test Analysis + val findings = + server.analyze(codyzeConfig.source)[codyzeConfig.timeout, TimeUnit.MINUTES].findings + // assert if findings are as expected + Assertions.assertEquals(3, findings.size) + Assertions.assertTrue(findings.stream().anyMatch { it.identifier == "Cipher_Mode_Order" }) + Assertions.assertTrue(findings.stream().anyMatch { it.identifier == "RNGOrder" }) + Assertions.assertTrue( + findings.stream().anyMatch { it.identifier == "WrongUseOfBotan_CipherMode" } + ) + } + + /** + * To successfully run this test, the CODYZE_PWD environment variable needs to be set to the + * OAuth2 password for the Orchestrator + */ + @Test + @Disabled + fun testOrchestratorIntegration() { + // create codyzeConfig + val config = Configuration.initialize(args) + CommandLine(config).setUnmatchedArgumentsAllowed(true).parseArgs(*args) + val codyzeConfig = + de.fraunhofer.aisec.codyze.config.Configuration.initConfig( + config.configFile.path.toFile(), + *args, + "--default-passes", + "--passes+", + "de.fraunhofer.aisec.cpg.passes.IdentifierPass", + "--passes+", + "de.fraunhofer.aisec.cpg.passes.EdgeCachePass" + ) + + // 1 ----------- Test Connection Creation + val connection = + Connection( + config.orchestrator.orchestratorEndpoint.toString(), + config.orchestrator.auth.oauthEndpoint.toString(), + config.orchestrator.auth.username, + config.orchestrator.auth.password + ) + // assert that Object could be created and connection is possible + Assertions.assertNotNull(connection) + Assertions.assertTrue(connection.getOAuthManager().testOAuthConnection()) + + // 2 ----------- Test Evidence Storage + val assembler = + Assembler(Environment(Configuration.CIEnvironment.NONE, Path("")), "integration-test") + val evidence = assembler.createEvidence(Path(codyzeConfig.output)) + evidence.resource = + object { + @Suppress("unused") val id = "123" + } + // assert that evidence could be stored without problems + Assertions.assertTrue(connection.getApiManager().storeEvidence(evidence)) + + // 3 ----------- Test Result Storage + // create an AssessmentResult + val ar = AssessmentResult() + val mc = MetricConfiguration() + + mc.isDefault = true + mc.operator = "=" + mc.targetValue = 5 + ar.metricConfiguration = mc + ar.id = UUID.randomUUID().toString() + ar.timestamp = java.time.OffsetDateTime.now() + ar.evidenceId = evidence.id + ar.resourceId = "123" + ar.metricId = "test-metric" + ar.compliant = false + ar.nonComplianceComments = "" + + // assert that this result could be stored without problems + Assertions.assertTrue(connection.getApiManager().sendAssessmentResults(arrayOf(ar))) + } +} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/LoggingTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/LoggingTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a5641c3f8b9cba859ad2b0bb442ae2491cfb50e7 --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/LoggingTest.kt @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.io.File +import org.apache.log4j.AppenderSkeleton +import org.apache.log4j.LogManager +import org.apache.log4j.spi.LoggingEvent +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +class LoggingTest { + private val logger = KotlinLogging.logger("TestLogger") + @TempDir private var logFile = File("test-log.log") + + @Test + fun testFileCreation() { + logger.info { "Testing file creation" } + Assertions.assertTrue(logFile.exists()) + } + + @Test + fun testLogContents() { + val logImpl = LogManager.getLogger("TestLogger") + val appender = TestAppender() + logImpl.addAppender(appender) + + logImpl.debug("debug") + logImpl.info("info") + logImpl.warn("warn") + logImpl.error("error") + + logImpl.removeAppender(appender) + val logLines = appender.getLog().map { event -> event.renderedMessage } + + // test whether log content is as specified + Assertions.assertEquals("warn", logLines[0]) + Assertions.assertEquals("error", logLines[1]) + } + + // create new Appender just for testing the logging + internal class TestAppender : AppenderSkeleton() { + private val log: MutableList<LoggingEvent> = ArrayList<LoggingEvent>() + + override fun requiresLayout(): Boolean { + return false + } + + override fun append(loggingEvent: LoggingEvent) { + log.add(loggingEvent) + } + + override fun close() {} + + fun getLog(): List<LoggingEvent> { + return ArrayList<LoggingEvent>(log) + } + } +} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/MappingTreeTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/MappingTreeTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..d947213ce45e6070435e9eb7bbb8fb0e8327d24d --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/MappingTreeTest.kt @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina + +import de.fraunhofer.aisec.codyze.medina.mapping.parseMappingTree +import java.io.File +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class MappingTreeTest { + /** + * Tests whether the tree structure of the directory gets resolved correctly + * + * @author Robert Haimerl + */ + @Test + fun testTreeParsing() { + val mappingMap = parseMappingTree(File("src/test/resources/mappingTreeTestStructure")) + Assertions.assertEquals(3, mappingMap.size) + val directoryArray = + setOf( + File("src/test/resources/mappingTreeTestStructure/botan"), + File("src/test/resources/mappingTreeTestStructure/bouncycastle/bc1"), + File("src/test/resources/mappingTreeTestStructure/bouncycastle/bc2") + ) + Assertions.assertEquals(directoryArray, mappingMap.values.toSet()) + } +} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/MedinaMetrikTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/MedinaMetrikTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..beabf0749327c26c4da899c0706d5e242761a95f --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/MedinaMetrikTest.kt @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina + +import de.fraunhofer.aisec.codyze.medina.assembling.Environment +import de.fraunhofer.aisec.codyze.medina.evaluation.base.ApprovedCommitAuthorEvaluator +import de.fraunhofer.aisec.codyze.medina.main.Configuration +import kotlin.io.path.Path +import org.junit.jupiter.api.Test + +class MedinaMetrikTest { + + @Test + fun testCommitAuthor() { + val env = Environment(Configuration.CIEnvironment.NONE, Path("./")) + val eval = ApprovedCommitAuthorEvaluator(env, java.nio.file.Path.of(".")) + + eval.evaluate(arrayOf()) + } +} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ParserTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ParserTest.kt deleted file mode 100644 index 7925c56150c1a7e65d1808d453c6bd28ef3c95db..0000000000000000000000000000000000000000 --- a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/ParserTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/* - * Copyright (c) 2022, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * 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. - * - * _____ _ - * / ____| | | - * | | ___ __| |_ _ _______ - * | | / _ \ / _` | | | |_ / _ \ - * | |___| (_) | (_| | |_| |/ / __/ - * \_____\___/ \__,_|\__, /___\___| - * __/ | - * |___/ - * - * This file is part of the MEDINA Framework. - */ -package de.fraunhofer.aisec.codyze.medina - -import de.fraunhofer.aisec.codyze.analysis.Finding -import de.fraunhofer.aisec.codyze.medina.util.findingToAR -import de.fraunhofer.aisec.codyze.sarif.schema.Result -import de.fraunhofer.aisec.cpg.sarif.Region -import de.fraunhofer.aisec.mark.markDsl.Action -import java.net.URI -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -class ParserTest { - /** - * Tests the conversion from Findings to compliant AssessmentResults - * @author Robert Haimerl - */ - @Disabled - @Test - fun testCompliant() { - // create some Finding object - val logMsg = "Variable cm not initialized" - val artifactUri = URI.create("file:///tmp/test.cpp") - val id = "WrongUseOfBotan_CipherMode" - val regions = listOf(Region(0, 2, 10, 12)) - val kind = Result.Kind.PASS - val f1 = Finding(id, Action.INFO, logMsg, artifactUri, regions, kind) - - val assessmentResult = findingToAR(f1, "") - - assertNotNull(assessmentResult) - assertTrue(assessmentResult?.compliant ?: false) - assertEquals(f1.identifier, assessmentResult?.metricId, "Wrong metricID") - assertEquals( - f1.logMsg, - assessmentResult?.nonComplianceComments, - "The comment for non-compliance differs from the logMsg" - ) - } - - /** - * Tests the conversion from Findings to non-compliant AssessmentResults - * @author Robert Haimerl - */ - @Disabled - @Test - fun testNonCompliant() { - // create some Finding object - val logMsg = "You should not do that" - val artifactUri = URI.create("file:///tmp/test.cpp") - val id = "VariableNotInitialized" - val regions = listOf(Region(0, 2, 10, 12)) - val kind = Result.Kind.FAIL - val f2 = Finding(id, Action.FAIL, logMsg, artifactUri, regions, kind) - - val assessmentResult = findingToAR(f2, "") - - assertNotNull(assessmentResult) - assertFalse(assessmentResult?.compliant ?: false) - assertEquals(f2.identifier, assessmentResult?.metricId, "Wrong metricID") - assertEquals( - f2.logMsg, - assessmentResult?.nonComplianceComments, - "The comment for non-compliance differs from the logMsg" - ) - } -} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/SarifTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/SarifTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..dffd97e1bbdc06ec6b5470068d228c8c2ea44536 --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/SarifTest.kt @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina + +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import de.fraunhofer.aisec.codyze.medina.evaluation.Rule +import de.fraunhofer.aisec.codyze.medina.util.amendExistingReport +import de.fraunhofer.aisec.codyze.medina.util.codyzeMedinaComponent +import io.github.detekt.sarif4k.* +import kotlin.io.path.exists +import kotlin.io.path.readText +import kotlin.io.path.writeText +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class SarifTest { + + private val sampleReport = + SarifSchema210( + schema = + "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + version = Version.The210, + runs = + listOf( + Run( + tool = + Tool( + driver = + ToolComponent( + name = "test-driver", + ), + extensions = listOf() + ), + results = + listOf( + Result(message = Message(text = "this is result 1")), + Result(message = Message(text = "this is result 2")), + Result(message = Message(text = "this is result 3")) + ) + ) + ) + ) + + @Test + fun testAmend() { + val evaluationResults: Map<Rule, EvaluationResult> = + mapOf( + Rule.CodeSignoff to + EvaluationResult(true, "valid sign-off", "valid sign-off by xyz"), + Rule.ApprovedCommitAuthor to + EvaluationResult(false, "wrong commit author", "commit authored by xyz") + ) + + val reportPath = kotlin.io.path.createTempFile() + val targetPath = kotlin.io.path.createTempFile() + + reportPath.writeText(SarifSerializer.toJson(sampleReport)) + amendExistingReport(reportPath, targetPath, evaluationResults) + val newReport = SarifSerializer.fromJson(targetPath.readText()) + + // Whether the old report was deleted + Assertions.assertFalse(reportPath.exists(), "The old report was not deleted after amending") + // Whether all results are present + Assertions.assertArrayEquals( + listOf( + Result(message = Message(text = "this is result 1")), + Result(message = Message(text = "this is result 2")), + Result(message = Message(text = "this is result 3")), + Result( + message = Message(text = Rule.CodeSignoff.description), + ruleID = Rule.CodeSignoff.id, + kind = ResultKind.Pass + ), + Result( + message = Message(text = Rule.ApprovedCommitAuthor.description), + ruleID = Rule.ApprovedCommitAuthor.id, + kind = ResultKind.Fail + ) + ) + .toTypedArray(), + newReport.runs[0].results!!.toTypedArray(), + "The results of the new report are not as expected" + ) + // Whether the extension was entered correctly + Assertions.assertEquals( + "test-driver", + newReport.runs[0].tool.extensions!![0].name, + "The old driver was not added as an extension" + ) + Assertions.assertEquals( + codyzeMedinaComponent, + newReport.runs[0].tool.driver, + "The new driver was not set correctly" + ) + } +} diff --git a/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/AssemblerTest.kt b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/AssemblerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2fbc2d355ebf35ca2c4c499329bf0573ff1473c6 --- /dev/null +++ b/src/test/kotlin/de/fraunhofer/aisec/codyze/medina/assembling/AssemblerTest.kt @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright (c) 2023, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + * + * _____ _ + * / ____| | | + * | | ___ __| |_ _ _______ + * | | / _ \ / _` | | | |_ / _ \ + * | |___| (_) | (_| | |_| |/ / __/ + * \_____\___/ \__,_|\__, /___\___| + * __/ | + * |___/ + * + * This file is part of the MEDINA Framework. + */ +package de.fraunhofer.aisec.codyze.medina.assembling + +import de.fraunhofer.aisec.codyze.analysis.Finding +import de.fraunhofer.aisec.codyze.medina.evaluation.EvaluationResult +import de.fraunhofer.aisec.codyze.medina.evaluation.Rule +import de.fraunhofer.aisec.codyze.medina.mapping.Configuration +import de.fraunhofer.aisec.codyze.medina.mapping.Mapping +import de.fraunhofer.aisec.codyze.medina.mapping.Metric +import de.fraunhofer.aisec.codyze.medina.mapping.Type +import java.time.OffsetDateTime +import java.util.* +import kotlin.io.path.Path +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.junit.jupiter.MockitoExtension + +@ExtendWith(MockitoExtension::class) +class AssemblerTest { + + private val nullId = UUID(0, 0) + private val tool = "codyze-medina" + private val gitHash = "ffffff" + + @Mock private lateinit var environment: Environment + + private lateinit var assembler: Assembler + + @BeforeEach + fun inject() { + assembler = Assembler(environment, nullId.toString()) + } + + @Test + fun testCreateEvidenceFromResultFile() { + `when`(environment.getGitHash()).thenReturn(gitHash) + + val currentTime = OffsetDateTime.now() + val evidence = assembler.createEvidence(Path("invalid_path")) + + // Assert that the timestamp is exact +- 5 seconds + assertTrue(evidence.timestamp?.isAfter(currentTime.minusSeconds(5)) ?: false) + assertTrue(evidence.timestamp?.isBefore(currentTime.plusSeconds(5)) ?: false) + // Assert that the cloud service id was correctly set + assertEquals(nullId.toString(), evidence.cloudServiceId) + // Assert that the tool id contains "codyze-medina" + assertTrue(evidence.toolId?.contains(tool, ignoreCase = true) ?: false) + + verify(environment).getGitHash() + + assertTrue(true) + } + + @Test + fun testConvertEvaluationToAssessmentResult() { + val rule = Rule.CodeSignoff + val detailedMessage = "detailed-message" + val validated = false + val evaluationResult = EvaluationResult(validated, "message", detailedMessage) + val hash = gitHash + val evidenceId = nullId.toString() + + val currentTime = OffsetDateTime.now() + val assessmentResult = + assembler.convertEvaluationToAssessmentResult(rule, evaluationResult, evidenceId, hash) + + assertEquals(rule.operator, assessmentResult.metricConfiguration?.operator) + assertEquals(rule.targetValue, assessmentResult.metricConfiguration?.targetValue) + assertTrue(assessmentResult.metricConfiguration?.isDefault ?: false) + assertTrue( + assessmentResult.metricConfiguration?.updatedAt?.isAfter(currentTime.minusSeconds(5)) + ?: false + ) + assertTrue( + assessmentResult.metricConfiguration?.updatedAt?.isBefore(currentTime.plusSeconds(5)) + ?: false + ) + assertEquals(rule.name, assessmentResult.metricConfiguration?.metricId) + assertEquals(nullId.toString(), assessmentResult.metricConfiguration?.cloudServiceId) + + assertTrue(assessmentResult.timestamp?.isAfter(currentTime.minusSeconds(5)) ?: false) + assertTrue(assessmentResult.timestamp?.isBefore(currentTime.plusSeconds(5)) ?: false) + assertEquals(evidenceId, assessmentResult.evidenceId) + assertEquals(hash, assessmentResult.resourceId) + assertEquals(rule.name, assessmentResult.metricId) + assertEquals(validated, assessmentResult.compliant) + assertEquals(nullId.toString(), assessmentResult.cloudServiceId) + } + + @Test + fun testCreateFindingsToAssessmentResult() { + val message = "message" + val rule = "test-rule" + val name = "test-metric" + val targetValue = true + val isDefault = false + val type = Type.BOOLEAN + val operator = "==" + + // initialize a simple mapping + val configuration = Configuration() + configuration.default = isDefault + configuration.type = type + configuration.target = arrayOf(targetValue) + configuration.operator = operator + val metric = Metric() + metric.name = name + metric.configuration = configuration + metric.rules = arrayOf(rule) + val mapping = Mapping() + mapping.metrics = arrayOf(metric) + + // create a finding with a rule id covered by the mapping + val finding = Finding(rule, null, message, null, 0, 0, 0, 0) + val currentTime = OffsetDateTime.now() + + val assessmentResult = + assembler + .convertFindingsToAssessmentResults( + mapping, + setOf(finding), + nullId.toString(), + gitHash + )[0] + + assertEquals(metric.configuration.operator, assessmentResult.metricConfiguration?.operator) + assertEquals(targetValue, assessmentResult.metricConfiguration?.targetValue) + assertEquals(isDefault, assessmentResult.metricConfiguration?.isDefault) + assertTrue( + assessmentResult.metricConfiguration?.updatedAt?.isAfter(currentTime.minusSeconds(5)) + ?: false + ) + assertTrue( + assessmentResult.metricConfiguration?.updatedAt?.isBefore(currentTime.plusSeconds(5)) + ?: false + ) + assertEquals(name, assessmentResult.metricConfiguration?.metricId) + assertEquals(nullId.toString(), assessmentResult.metricConfiguration?.cloudServiceId) + + assertTrue(assessmentResult.timestamp?.isAfter(currentTime.minusSeconds(5)) ?: false) + assertTrue(assessmentResult.timestamp?.isBefore(currentTime.plusSeconds(5)) ?: false) + assertEquals(nullId.toString(), assessmentResult.evidenceId) + assertEquals(gitHash, assessmentResult.resourceId) + assertEquals(name, assessmentResult.metricId) + assertEquals(false, assessmentResult.compliant) + assertEquals(nullId.toString(), assessmentResult.cloudServiceId) + } +} diff --git a/src/test/resources/Mark/botan/mapping.yaml b/src/test/resources/Mark/botan/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..be0dd4b225c79d78c50954e79ad25b5f5e963f6f --- /dev/null +++ b/src/test/resources/Mark/botan/mapping.yaml @@ -0,0 +1,29 @@ +metrics: + - name: "TestMetric1" + rules: + - "WrongUseOfBotan_CipherMode" + configuration: + default: false + operator: "==" + type: NUMBER + target: + - "1.23" + - "3.14" + - name: "TestMetric2" + rules: + - "RNGOrder" + configuration: + default: false + operator: "==" + type: BOOLEAN + target: + - "true" + - name: "TestMetric3" + rules: + - "Cipher_Mode_Order" + configuration: + default: false + operator: ">=" + type: NUMBER + target: + - "2" \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/AlgorithmParameterGenerator.mark b/src/test/resources/Mark/bouncycastle/AlgorithmParameterGenerator.mark deleted file mode 100644 index 6edb8a7d7dea2ca282c93702ba0f9461eab97146..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/AlgorithmParameterGenerator.mark +++ /dev/null @@ -1,43 +0,0 @@ -package java.jca - -entity AlgorithmParameterGenerator { - - var algorithm; - var provider; - var size; - var random; - var genParamSpec; - var params; - - - op instantiate { - java.security.AlgorithmParameterGenerator.getInstance( - algorithm : java.lang.String - ); - java.security.AlgorithmParameterGenerator.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op initialize { - java.security.AlgorithmParameterGenerator.init( - size : int - ); - java.security.AlgorithmParameterGenerator.init( - size : int, - random : java.security.SecureRandom - ); - java.security.AlgorithmParameterGenerator.init( - genParamSpec : java.security.spec.AlgorithmParameterSpec - ); - java.security.AlgorithmParameterGenerator.init( - genParamSpec : java.security.spec.AlgorithmParameterSpec, - random : java.security.SecureRandom - ); - } - - op generate { - params = java.security.AlgorithmParameterGenerator.generateParameters(); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/AlgorithmParameters.mark b/src/test/resources/Mark/bouncycastle/AlgorithmParameters.mark deleted file mode 100644 index 268c9124dbe47e8f66397c5d947eea7a4f5d4673..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/AlgorithmParameters.mark +++ /dev/null @@ -1,34 +0,0 @@ -package java.jca - -entity AlgorithmParameters { - - var algorithm; - var provider; - var params; - var format; - var paramSpec; - - - op instantiate { - java.security.AlgorithmParameters.getInstance( - algorithm : java.lang.String - ); - java.security.AlgorithmParameters.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op initialize { - java.security.AlgorithmParameters.init( - params : byte[] - ); - java.security.AlgorithmParameters.init( - params : byte[], - format : java.lang.String - ); - java.security.AlgorithmParameters.init( - paramSpec : java.security.spec.AlgorithmParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/CertPathBuilder.mark b/src/test/resources/Mark/bouncycastle/CertPathBuilder.mark deleted file mode 100644 index ec8c2645cd78f93ce5fc3f3b760b500bd6c20266..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/CertPathBuilder.mark +++ /dev/null @@ -1,24 +0,0 @@ -package jav.jca - -entity CertPathBuilder { - - var algorithm; - var provider; - var params; - - op instantiate { - java.security.cert.CertPathBuilder.getInstance( - algorithm : java.lang.String - ); - java.security.cert.CertPathBuilder.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op build { - java.security.cert.CertPathBuilder.build( - params : java.security.cert.CertPathParameters - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/CertPathValidator.mark b/src/test/resources/Mark/bouncycastle/CertPathValidator.mark deleted file mode 100644 index 76dd8afbc24d843f530db748aefd3684a830aa96..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/CertPathValidator.mark +++ /dev/null @@ -1,28 +0,0 @@ -package java.jca - -entity CertPathValidator { - - var algorithm; - var provider; - - var certPath; - var params; - - - op instantiate { - java.security.cert.CertPathValidator.getInstance( - algorithm : java.lang.String - ); - java.security.cert.CertPathValidator.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op validate { - java.security.cert.CertPathValidator.validate( - certPath : java.security.cert.CertPath, - params : java.security.cert.CertPathParameters - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/CertStore.mark b/src/test/resources/Mark/bouncycastle/CertStore.mark deleted file mode 100644 index 2e6af2ae5fc889f014d02b1bda671d617051c66d..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/CertStore.mark +++ /dev/null @@ -1,34 +0,0 @@ -package java.jca - -entity CertStore { - - var type; - var params; - var provider; - - var selector; - var certificates; - var crls; - - - op instantiate { - java.security.cert.CertStore.getInstance( - type : java.lang.String, - params : java.security.cert.CertStoreParameters - ); - java.security.cert.CertStore.getInstance( - type : java.lang.String, - params : java.security.cert.CertStoreParameters, - provider : java.lang.String | java.security.Provider - ); - } - - op get { - certificates = java.security.cert.CertStore.getCertificates( - selector : java.security.cert.CertSelector - ); - crls = java.security.cert.CertStore.getCRLs( - selector : java.security.cert.CRLSelector - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/CertificateFactory.mark b/src/test/resources/Mark/bouncycastle/CertificateFactory.mark deleted file mode 100644 index dafaed2dd9e2bcfcd8b800104e5f0c39ef822fee..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/CertificateFactory.mark +++ /dev/null @@ -1,56 +0,0 @@ -package java.jca - -entity CertificateFactory { - - var type; - var provider; - - var inStream; - var certificate; - - var encoding; - var certificates; - var certpath; - - - op instantiate { - java.security.cert.CertificateFactory.getInstance( - type : java.lang.String - ); - java.security.cert.CertificateFactory.getInstance( - type : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op generateCertificate { - certificate = java.security.cert.CertificateFactory.generateCertificate( - inStream : java.io.InputStream - ); - certificates = java.security.cert.CertificateFactory.generateCertificates( // TODO how to denote that this returns Collection<? extends Certificate> - inStream : java.io.InputStream - ); - } - - op generateCertPath { - certpath = java.security.cert.CertificateFactory.generateCertPath( - inStream : java.io.InputStream - ); - certificates = java.security.cert.CertificateFactory.generateCertPath( - inStream : java.io.InputStream, - encoding : java.lang.String - ); - certificates = java.security.cert.CertificateFactory.generateCertPath( - certificates : List/* <? extends Certificate> */ // TODO how to handle generic list? what is actually seen by cpg - ); - } - - op generateCRL { - certificate = java.security.cert.CertificateFactory.generateCRL( - inStream : java.io.InputStream - ); - certificates = java.security.cert.CertificateFactory.generateCRLs( // TODO how to denote that this returns Collection<? extends CRL> - inStream : java.io.InputStream - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ChaCha20ParameterSpec.mark b/src/test/resources/Mark/bouncycastle/ChaCha20ParameterSpec.mark deleted file mode 100644 index a57fc83710fe71621d4043532dc902dfdf9dc17d..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ChaCha20ParameterSpec.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity ChaCha20ParameterSpec { - - var nonce; - var counter; - - op instantiate { - javax.crypto.spec.ChaCha20ParameterSpec( - nonce : byte[], - counter : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DHGenParameterSpec.mark b/src/test/resources/Mark/bouncycastle/DHGenParameterSpec.mark deleted file mode 100644 index b90a800b6764dd13e23d3dd464b323716aaaef8f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DHGenParameterSpec.mark +++ /dev/null @@ -1,15 +0,0 @@ -package java.jca - -entity DHGenParameterSpec { - - var primeSize; - var exponentSize; - - - op instantiate { - javax.crypto.spec.DHGenParameterSpec( - primeSize : int, - exponentSize : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DHParameterSpec.mark b/src/test/resources/Mark/bouncycastle/DHParameterSpec.mark deleted file mode 100644 index 9d15c16fb3ed173287e3926a661b4a7246a2ca14..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DHParameterSpec.mark +++ /dev/null @@ -1,22 +0,0 @@ -package java.jca - -entity DHParameterSpec { - - var p; - var g; - var l; - - - op instantiate { - javax.crypto.spec.DHParameterSpec( - p : java.math.BigInteger, - g : java.math.BigInteger - ); - javax.crypto.spec.DHParameterSpec( - p : java.math.BigInteger, - g : java.math.BigInteger, - l : int - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DHPrivateKeySpec.mark b/src/test/resources/Mark/bouncycastle/DHPrivateKeySpec.mark deleted file mode 100644 index d505965202184a4a54e96918873c58538f1f1945..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DHPrivateKeySpec.mark +++ /dev/null @@ -1,16 +0,0 @@ -package java.jca - -entity DHPrivateKeySpec { - - var x; - var p; - var g; - - op instantiate { - javax.crypto.spec.DHPrivateKeySpec( - x : java.math.BigInteger, - p : java.math.BigInteger, - g : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DHPublicKeySpec.mark b/src/test/resources/Mark/bouncycastle/DHPublicKeySpec.mark deleted file mode 100644 index 4a563dd84cc6cea5c9b097c274e5b553a8c92f64..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DHPublicKeySpec.mark +++ /dev/null @@ -1,16 +0,0 @@ -package java.jca - -entity DHPublicKeySpec { - - var y; - var p; - var g; - - op instantiate { - javax.crypto.spec.DHPublicKeySpec( - y : java.math.BigInteger, - p : java.math.BigInteger, - g : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DSAGenParameterSpec.mark b/src/test/resources/Mark/bouncycastle/DSAGenParameterSpec.mark deleted file mode 100644 index b4c3130a10ab3fe6794dcb61007dbb1b5ba3da9c..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DSAGenParameterSpec.mark +++ /dev/null @@ -1,22 +0,0 @@ -package java.jca - -entity DSAGenParameterSpec { - - var primePLen; - var subprimeQLen; - var seedLen; - - - op instantiate { - java.security.spec.DSAGenParameterSpec( - primePLen : int, - subprimeQLen : int - ); - java.security.spec.DSAGenParameterSpec( - primePLen : int, - subprimeQLen : int, - seedLen : int - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DSAParameterSpec.mark b/src/test/resources/Mark/bouncycastle/DSAParameterSpec.mark deleted file mode 100644 index 612f3bdc68261e907e263293a018a0edcf25a850..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DSAParameterSpec.mark +++ /dev/null @@ -1,17 +0,0 @@ -package java.jca - -entity DSAParameterSpec { - - var p; - var q; - var g; - - - op intialize { - java.security.spec.DSAParameterSpec( - p : java.math.BigInteger, - q : java.math.BigInteger, - g : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DSAPrivateKeySpec.mark b/src/test/resources/Mark/bouncycastle/DSAPrivateKeySpec.mark deleted file mode 100644 index cd1a37f21db113aaf025bae313d520f6f31f32b0..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DSAPrivateKeySpec.mark +++ /dev/null @@ -1,18 +0,0 @@ -package java.jca - -entity DSAPrivateKeySpec { - - var x; - var p; - var q; - var g; - - op instantiate { - java.security.spec.DSAPrivateKeySpec( - x : java.math.BigInteger, - p : java.math.BigInteger, - q : java.math.BigInteger, - g : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/DSAPublicKeySpec.mark b/src/test/resources/Mark/bouncycastle/DSAPublicKeySpec.mark deleted file mode 100644 index cfe67350abc06adab2488ccb18603f9e4b3c89af..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/DSAPublicKeySpec.mark +++ /dev/null @@ -1,18 +0,0 @@ -package java.jca - -entity DSAPublicKeySpec { - - var y; - var p; - var q; - var g; - - op instantiate { - java.security.spec.DSAPublicKeySpec( - y : java.math.BigInteger, - p : java.math.BigInteger, - q : java.math.BigInteger, - g : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECFieldF2m.mark b/src/test/resources/Mark/bouncycastle/ECFieldF2m.mark deleted file mode 100644 index e069f482659f7ee1bc55c541f7393cd4cb12aa47..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECFieldF2m.mark +++ /dev/null @@ -1,22 +0,0 @@ -package java.jca - -entity ECFieldF2m { - - var m; - var ks; - var rp; - - op instantiate { - java.security.spec.ECFieldF2m( - m : int - ); - java.security.spec.ECFieldF2m( - m : int, - ks : int[] - ); - java.security.spec.ECFieldF2m( - m : int, - rp : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECFieldFp.mark b/src/test/resources/Mark/bouncycastle/ECFieldFp.mark deleted file mode 100644 index 0199f1673a2b93f7d7af5c66852f49b60cf9f633..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECFieldFp.mark +++ /dev/null @@ -1,13 +0,0 @@ -package java.jca - -entity ECFieldFp { - - var p; - - - op instantiate { - java.security.spec.ECFieldFp( - p : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECGenParameterSpec.mark b/src/test/resources/Mark/bouncycastle/ECGenParameterSpec.mark deleted file mode 100644 index c0e451e505881b448847533abd648e2283bea194..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECGenParameterSpec.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity ECGenParameterSpec { - - var stdName; - - - op instantiate { - java.security.spec.ECGenParameterSpec( - stdName : java.lang.String - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECKey.mark b/src/test/resources/Mark/bouncycastle/ECKey.mark deleted file mode 100644 index e80cae5e3ce1883a061e72e145b8fff31138f5f2..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECKey.mark +++ /dev/null @@ -1,5 +0,0 @@ -package java.jca - -entity ECKey { - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECParameterSpec.mark b/src/test/resources/Mark/bouncycastle/ECParameterSpec.mark deleted file mode 100644 index 3eb4049b29e58835c23a89c277bbce65ada93c87..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECParameterSpec.mark +++ /dev/null @@ -1,19 +0,0 @@ -package java.jca - -entity ECParameterSpec { - - var curve; - var g; - var n; - var h; - - - op instantiate { - java.security.spec.ECParameterSpec( - curve : java.security.spec.EllipticCurve, - g : java.security.spec.ECPoint, - n : java.math.BigInteger, - h : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECPoint.mark b/src/test/resources/Mark/bouncycastle/ECPoint.mark deleted file mode 100644 index fb9a58a29843a3b0504a462d88e34e0f6505efc1..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECPoint.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity ECPoint { - - var x; - var y; - - op instantiate { - java.security.spec.ECPoint( - x : java.math.BigInteger, - y : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECPrivateKeySpec.mark b/src/test/resources/Mark/bouncycastle/ECPrivateKeySpec.mark deleted file mode 100644 index 1f91cca021f880b470953b25708847dab5b78e4f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECPrivateKeySpec.mark +++ /dev/null @@ -1,16 +0,0 @@ -package java.jca - -entity ECPrivateKeySpec { - - var s; - var params; - - - op instantiate { - java.security.spec.ECPrivateKeySpec( - s : java.math.BigInteger, - params : java.security.spec.ECParameterSpec - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/ECPublicKeySpec.mark b/src/test/resources/Mark/bouncycastle/ECPublicKeySpec.mark deleted file mode 100644 index 17bf1a5960226f5f33f03c909fb8fc2a46028651..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/ECPublicKeySpec.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity ECPublicKeySpec { - - var w; - var params; - - op instantiate { - java.security.spec.ECPublicKeySpec( - w : java.security.spec.ECPoint, - params : java.security.spec.ECParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/EncodedKeySpec.mark b/src/test/resources/Mark/bouncycastle/EncodedKeySpec.mark deleted file mode 100644 index eb5252760d7f0141dba94f7f87eceb42b5ec0270..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/EncodedKeySpec.mark +++ /dev/null @@ -1,18 +0,0 @@ -package java.jca - -entity EncodedKeySpec { - - var encodedKey; - var algorithm; - - - op instantiate { - java.security.spec.EncodedKeySpec( - encodedKey : byte[] - ); - java.security.spec.EncodedKeySpec( - encodedKey : byte[], - algorithm : java.lang.String - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/GCMParameterSpec.mark b/src/test/resources/Mark/bouncycastle/GCMParameterSpec.mark deleted file mode 100644 index ed3c7f27e9a6b259f48389cb40a7074a23c0861f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/GCMParameterSpec.mark +++ /dev/null @@ -1,22 +0,0 @@ -package java.jca - -entity GCMParameterSpec { - - var tLen; - var src; - var offset; - var len; - - op instantiate { - javax.crypto.spec.GCMParameterSpec( - tLen : int, - src : byte[] - ); - javax.crypto.spec.GCMParameterSpec( - tLen : int, - src : byte[], - offset : int, - len : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/HMACParameterSpec.mark b/src/test/resources/Mark/bouncycastle/HMACParameterSpec.mark deleted file mode 100644 index a243d3321ab1d4addaa6efef3e7b15e212bf0be3..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/HMACParameterSpec.mark +++ /dev/null @@ -1,12 +0,0 @@ -package java.jca - -entity HMACParameterSpec { - - var outputLength; - - op instantiate { - javax.xml.crypto.dsig.spec.HMACParameterSpec( - outputLength : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/IvParameterSpec.mark b/src/test/resources/Mark/bouncycastle/IvParameterSpec.mark deleted file mode 100644 index 2ccc1a81bc59893241a87ab934f6f53b2965befa..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/IvParameterSpec.mark +++ /dev/null @@ -1,19 +0,0 @@ -package java.jca - -entity IvParameterSpec { - - var iv; - var offset; - var len; - - op instantiate { - javax.crypto.spec.IvParameterSpec( - iv : byte[] - ); - javax.crypto.spec.IvParameterSpec( - iv : byte[], - offset : int, - len : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/KeyFactory.mark b/src/test/resources/Mark/bouncycastle/KeyFactory.mark deleted file mode 100644 index 7e7bb8915aecd8827af60bca6c98422b4adfd741..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/KeyFactory.mark +++ /dev/null @@ -1,31 +0,0 @@ -package java.jca - -entity KeyFactory { - - var algorithm; - var provider; - - var keyspec; - var prikey; - var pubkey; - - var inkey; - var outkey; - - op instantiate { - java.security.KeyFactory.getInstance(algorithm : java.lang.String); - java.security.KeyFactory.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op generate { - prikey = java.security.KeyFactory.generatePrivate(keyspec : java.security.spec.KeySpec); - pubkey = java.security.KeyFactory.generatePublic(keyspec : java.security.spec.KeySpec); - } - - op translate { - outkey = java.security.KeyFactory.translateKey(inkey : java.security.Key); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/KeyGenerator.mark b/src/test/resources/Mark/bouncycastle/KeyGenerator.mark deleted file mode 100644 index 718e1a4c557e7dfe515a03ff0921980c972f9686..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/KeyGenerator.mark +++ /dev/null @@ -1,40 +0,0 @@ -package java.jca - -entity KeyGenerator { - - var algorithm; - var provider; - - var keysize; - var random; - var params; - - var key; - - - op instantiate { - javax.crypto.KeyGenerator.getInstance(algorithm : java.lang.String); - javax.crypto.KeyGenerator.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op init { - javax.crypto.KeyGenerator.init(keysize : int); - javax.crypto.KeyGenerator.init( - keysize : int, - random : java.security.SecureRandom - ); - javax.crypto.KeyGenerator.init(random : java.security.SecureRandom); - javax.crypto.KeyGenerator.init(params : java.security.spec.AlgorithmParameterSpecs); - javax.crypto.KeyGenerator.init( - params : java.security.spec.AlgorithmParameterSpec, - random : java.security.SecureRandom - ); - } - - op generate { - key = javax.crypto.KeyGenerator.generateKey(); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/KeyPair.mark b/src/test/resources/Mark/bouncycastle/KeyPair.mark deleted file mode 100644 index 5067b130cc3c1705e859d9e2358fe2215e432719..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/KeyPair.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity KeyPair { - - var publicKey; - var privateKey; - - op instantiate { - java.security.KeyPair( - publicKey : java.security.PublicKey, - privateKey : java.security.PrivateKey - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/KeyPairGenerator.mark b/src/test/resources/Mark/bouncycastle/KeyPairGenerator.mark deleted file mode 100644 index b9f919f620413286e56ab6f8c173766104ef934f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/KeyPairGenerator.mark +++ /dev/null @@ -1,46 +0,0 @@ -package java.jca - -entity KeyPairGenerator { - - var algorithm; - var provider; - - var keysize; - var random; - var params; - - var keypair; - - - op instantiate { - java.security.KeyPairGenerator.getInstance( - algorithm : java.lang.String - ); - java.security.KeyPairGenerator.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op initialize { - java.security.KeyPairGenerator.initialize( - keysize : int - ); - java.security.KeyPairGenerator.initialize( - keysize : int, - random : java.security.SecureRandom - ); - java.security.KeyPairGenerator.initialize( - params : java.security.spec.AlgorithmParameterSpec - ); - java.security.KeyPairGenerator.initialize( - params : java.security.spec.AlgorithmParameterSpec, - random : java.security.SecureRandom - ); - } - - op generate { - keypair = java.security.KeyPairGenerator.generateKeyPair(); - keypair = java.security.KeyPairGenerator.genKeyPair(); - } -} diff --git a/src/test/resources/Mark/bouncycastle/KeyStore.PasswordProtection.mark b/src/test/resources/Mark/bouncycastle/KeyStore.PasswordProtection.mark deleted file mode 100644 index 64a91baeec5d6a8508e789a76a5a69ea267f993d..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/KeyStore.PasswordProtection.mark +++ /dev/null @@ -1,19 +0,0 @@ -package java.jca - -entity KeyStore.PasswordProtection { - - var password; - var protectionAlgorithm; - var protectionParameters; - - op instantiate { - java.security.KeyStore.PasswordProtection( - password : char[] - ); - java.security.KeyStore.PasswordProtection( - password : char[], - protectionAlgorithm : java.lang.String, - protectionParameters : java.security.spec.AlgorithmParameterSpec - ); - } -} diff --git a/src/test/resources/Mark/bouncycastle/KeyStore.mark b/src/test/resources/Mark/bouncycastle/KeyStore.mark deleted file mode 100644 index e92b2a1b66c3b326671c1c75c8d58b97eeb04225..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/KeyStore.mark +++ /dev/null @@ -1,60 +0,0 @@ -package java.jca - -entity KeyStore { - - var file; - var password; - var param; - var type; - var provider; - - var alias; - var cert; - var entry; - var protParam; - var rawKey; - var certChain; - var key; - - - op instantiate { - java.security.KeyStore.getInstance( - file : java.io.File, - password : char[] - ); - java.security.KeyStore.getInstance( - file : java.io.File, - param : java.security.KeyStore.LoadStoreParameter - ); - java.security.KeyStore.getInstance( - type : java.lang.String - ); - java.security.KeyStore.getInstance( - type : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op store { - java.security.KeyStore.setCertificateEntry( - alias : java.lang.String, - cert : java.security.cert.Certificate - ); - java.security.KeyStore.setEntry( - alias : java.lang.String, - entry : java.security.KeyStore.Entry, - protParam : java.security.KeyStore.ProtectionParameter - ); - java.security.KeyStore.setKeyEntry( - alias : java.lang.String, - rawKey : byte[], - certChain : java.security.cert.Certificate[] - ); - java.security.KeyStore.setKeyEntry( - alias : java.lang.String, - key : java.security.Key, - password : char[], - certChain : java.security.cert.Certificate[] - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/MGF1ParameterSpec.mark b/src/test/resources/Mark/bouncycastle/MGF1ParameterSpec.mark deleted file mode 100644 index 208f2ae1cc46ada9a7f5f7bfcb080b05528054e9..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/MGF1ParameterSpec.mark +++ /dev/null @@ -1,12 +0,0 @@ -package java.jca - -entity MGF1ParameterSpec { - - var mdName; - - op instantiate { - java.security.spec.MGF1ParameterSpec( - mdName : java.lang.String - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/MessageDigest.mark b/src/test/resources/Mark/bouncycastle/MessageDigest.mark deleted file mode 100644 index 1ce9bd729ada6ce8a7559222d485ed36a5d261d2..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/MessageDigest.mark +++ /dev/null @@ -1,39 +0,0 @@ -package java.jca - -/* - * Represents java.security.MessageDigest - */ -entity MessageDigest { - - var algorithm; - var provider; - var input; - var digest; - - op instantiate { - java.security.MessageDigest.getInstance(algorithm : java.lang.String); - java.security.MessageDigest.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op update { - java.security.MessageDigest.update(input : byte | byte[] | java.nio.ByteBuffer); - java.security.MessageDigest.update( - input : byte[], - ... - ); - } - - op digest { - digest = java.security.MessageDigest.digest(); - digest = java.security.MessageDigest.digest(input : byte[]); - java.security.MessageDigest.digest(digest : byte[], ...); - } - - op reset { - java.security.MessageDigest.reset(); - } - -} diff --git a/src/test/resources/Mark/bouncycastle/OAEPParameterSpec.mark b/src/test/resources/Mark/bouncycastle/OAEPParameterSpec.mark deleted file mode 100644 index 4d9060a8c03c8cbca5eb5aba38e7b3eb02c75276..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/OAEPParameterSpec.mark +++ /dev/null @@ -1,19 +0,0 @@ -package java.jca - -entity OAEPParameterSpec { - - var mdName; - var mgfName; - var mgfSpec; - var pSrc; - - op instantiate { - javax.crypto.spec.OAEPParameterSpec( - mdName : java.lang.String, - mgfName : java.lang.String, - mgfSpec : java.security.spec.AlgorithmParameterSpec, - pSrc : javax.crypto.spec.PSource - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PBEKey.mark b/src/test/resources/Mark/bouncycastle/PBEKey.mark deleted file mode 100644 index b19111e4bc2696b809230227e946d0a78ed15b8a..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PBEKey.mark +++ /dev/null @@ -1,5 +0,0 @@ -package java.jca - -entity PBEKey { - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PBEKeySpec.mark b/src/test/resources/Mark/bouncycastle/PBEKeySpec.mark deleted file mode 100644 index aa14dcbea0d012fe1e51f40a5c7f7b048bf002d2..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PBEKeySpec.mark +++ /dev/null @@ -1,26 +0,0 @@ -package java.jca - -entity PBEKeySpec { - - var password; - var salt; - var iterationCount; - var keyLength; - - op instantiate { - javax.crypto.spec.PBEKeySpec( - password : byte[] - ); - javax.crypto.spec.PBEKeySpec( - password : byte[], - salt : byte[], - iterationCount : int - ); - javax.crypto.spec.PBEKeySpec( - password : byte[], - salt : byte[], - iterationCount : int, - keyLength : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PBEParameterSpec.mark b/src/test/resources/Mark/bouncycastle/PBEParameterSpec.mark deleted file mode 100644 index debd301db85b1c16bc781e391805dacf96808a6f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PBEParameterSpec.mark +++ /dev/null @@ -1,20 +0,0 @@ -package java.jca - -entity PBEParameterSpec { - - var salt; - var iterationCount; - var paramSpec; - - op instantiate { - javax.crypto.spec.PBEParameterSpec( - salt : byte[], - iterationCount : int - ); - javax.crypto.spec.PBEParameterSpec( - salt : byte[], - iterationCount : int, - paramSpec : java.security.AlgorithmParameter - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PKCS8EncodedKeySpec.mark b/src/test/resources/Mark/bouncycastle/PKCS8EncodedKeySpec.mark deleted file mode 100644 index bb19813800d66610ba63fcacde0e41964e23fe8f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PKCS8EncodedKeySpec.mark +++ /dev/null @@ -1,18 +0,0 @@ -package java.jca - -entity PKCS8EncodedKeySpec { - - var encodedKey; - var algorithm; - - op instantiate { - java.security.spec.PKCS8EncodedKeySpec( - encodedKey : byte[] - ); - java.security.spec.PKCS8EncodedKeySpec( - encodedKey : byte[], - algorithm : java.lang.String - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PSSParameterSpec.mark b/src/test/resources/Mark/bouncycastle/PSSParameterSpec.mark deleted file mode 100644 index 5b7256eeeb1f7f2b00ffac4601600fbf1dae7536..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PSSParameterSpec.mark +++ /dev/null @@ -1,23 +0,0 @@ -package java.jca - -entity PSSParameterSpec { - - var saltLen; - var mdName; - var mgfName; - var mgfSpec; - var trailerField; - - op instantiate { - java.security.spec.PSSParameterSpec( - saltLen : int - ); - java.security.spec.PSSParameterSpec( - mdName : java.lang.String, - mgfName : java.lang.String, - mgfSpec : java.security.spec.AlgorithmParameterSpec, - saltLen : int, - trailerField : int - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PrivateKey.mark b/src/test/resources/Mark/bouncycastle/PrivateKey.mark deleted file mode 100644 index 48d7aa1f163d0968c7be49c05b47ca3b2d053a09..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PrivateKey.mark +++ /dev/null @@ -1,5 +0,0 @@ -package java.jca - -entity PrivateKey { - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/PublicKey.mark b/src/test/resources/Mark/bouncycastle/PublicKey.mark deleted file mode 100644 index 44b8affffc1d5327879ce2e5d6d6957c2bd69150..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/PublicKey.mark +++ /dev/null @@ -1,5 +0,0 @@ -package java.jca - -entity PublicKey { - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RSAKeyGenParameterSpec.mark b/src/test/resources/Mark/bouncycastle/RSAKeyGenParameterSpec.mark deleted file mode 100644 index b8b938ebc3ace4f7595aec7d86b5c44eedd4fd81..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RSAKeyGenParameterSpec.mark +++ /dev/null @@ -1,20 +0,0 @@ -package java.jca - -entity RSAKeyGenParameterSpec { - - var keySize; - var publicExponent; - var keyParams; - - op instantiate { - java.security.spec.RSAKeyGenParameterSpec( - keysize : int, - publicExponent : java.math.BigInteger - ); - java.security.spec.RSAKeyGenParameterSpec( - keysize : int, - publicExponent : java.math.BigInteger, - keyParams : java.security.spec.AlgorithmParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RSAMultiPrimePrivateCrtKeySpec.mark b/src/test/resources/Mark/bouncycastle/RSAMultiPrimePrivateCrtKeySpec.mark deleted file mode 100644 index 81c6d518051e5348a92ce8601d1e7fc583ea0d1c..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RSAMultiPrimePrivateCrtKeySpec.mark +++ /dev/null @@ -1,42 +0,0 @@ -package java.jca - -entity RSAMultiPrimePrivateCrtKeySpec { - - var modulus; - var publicExponent; - var privateExponent; - var primeP; - var primeQ; - var primeExponentP; - var primeExponentQ; - var crtCoefficient; - var otherPrimeInfo; - var keyParams; - - - op instantiate { - java.security.spec.RSAMultiPrimePrivateCrtKeySpec( - modulus : java.math.BigInteger, - publicExponent : java.math.BigInteger, - privateExponent : java.math.BigInteger, - primeP : java.math.BigInteger, - primeQ : java.math.BigInteger, - primeExponentP : java.math.BigInteger, - primeExponentQ : java.math.BigInteger, - crtCoefficient : java.math.BigInteger, - otherPrimeInfo : java.security.spec.RSAOtherPrimeInfo[] - ); - java.security.spec.RSAMultiPrimePrivateCrtKeySpec( - modulus : java.math.BigInteger, - publicExponent : java.math.BigInteger, - privateExponent : java.math.BigInteger, - primeP : java.math.BigInteger, - primeQ : java.math.BigInteger, - primeExponentP : java.math.BigInteger, - primeExponentQ : java.math.BigInteger, - crtCoefficient : java.math.BigInteger, - otherPrimeInfo : java.security.spec.RSAOtherPrimeInfo[], - keyParams : java.security.spec.AlgorithmParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RSAPrivateCrtKeySpec.mark b/src/test/resources/Mark/bouncycastle/RSAPrivateCrtKeySpec.mark deleted file mode 100644 index 8c506c5ff6bb0f5f71a135717c1d5ffd7cee7c25..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RSAPrivateCrtKeySpec.mark +++ /dev/null @@ -1,38 +0,0 @@ -package java.jca - -entity RSAPrivateCrtKeySpec { - - var modulus; - var publicExponent; - var privateExponent; - var primeP; - var primeQ; - var primeExponentP; - var primeExponentQ; - var crtCoefficient; - var keyParams; - - op instantiate { - java.security.spec.RSAPrivateCrtKeySpec( - modulus : java.math.BigInteger, - publicExponent : java.math.BigInteger, - privateExponent : java.math.BigInteger, - primeP : java.math.BigInteger, - primeQ : java.math.BigInteger, - primeExponentP : java.math.BigInteger, - primeExponentQ : java.math.BigInteger, - crtCoefficient : java.math.BigInteger - ); - java.security.spec.RSAPrivateCrtKeySpec( - modulus : java.math.BigInteger, - publicExponent : java.math.BigInteger, - privateExponent : java.math.BigInteger, - primeP : java.math.BigInteger, - primeQ : java.math.BigInteger, - primeExponentP : java.math.BigInteger, - primeExponentQ : java.math.BigInteger, - crtCoefficient : java.math.BigInteger, - keyParams : java.security.spec.AlgorithmParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RSAPrivateKeySpec.mark b/src/test/resources/Mark/bouncycastle/RSAPrivateKeySpec.mark deleted file mode 100644 index 69a8732e9a8b1e285173f138a5838e8e5de2b6c6..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RSAPrivateKeySpec.mark +++ /dev/null @@ -1,21 +0,0 @@ -package java.jca - -entity RSAPrivateKeySpec { - - var modulus; - var privateExponent; - var params; - - - op instantiate { - java.security.spec.RSAPrivateKeySpec( - modulus : java.math.BigInteger, - privateExponent : java.math.BigInteger - ); - java.security.spec.RSAPrivateKeySpec( - modulus : java.math.BigInteger, - privateExponent : java.math.BigInteger, - params : java.security.spec.AlgorithmParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RSAPublicKeySpec.mark b/src/test/resources/Mark/bouncycastle/RSAPublicKeySpec.mark deleted file mode 100644 index 32f8764c560ad8a85287f5bc7af015674d157d1a..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RSAPublicKeySpec.mark +++ /dev/null @@ -1,21 +0,0 @@ -package java.jca - -entity RSAPublicKeySpec { - - var modulus; - var publicExponent; - var params; - - - op instantiate { - java.security.spec.RSAPublicKeySpec( - modulus : java.math.BigInteger, - publicExponent : java.math.BigInteger - ); - java.security.spec.RSAPublicKeySpec( - modulus : java.math.BigInteger, - publicExponent : java.math.BigInteger, - params : java.security.spec.AlgorithmParameterSpec - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RulesBase_Cipher.mark b/src/test/resources/Mark/bouncycastle/RulesBase_Cipher.mark deleted file mode 100644 index 84327ff3fc80e7ea3da98c7854763bdc3abd62c7..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesBase_Cipher.mark +++ /dev/null @@ -1,50 +0,0 @@ -package base.jca - -/** - * - */ -rule Crypt { - using - Cipher as c - when - _split(c.transform, "/", 1) in ["CBC", "CTR"] - && (c.opmode == "Cipher.ENCRYPT_MODE" - || c.opmode == "Cipher.DECRYPT_MODE" - || c.opmode == "1" /* ENCRYPT_MODE */ - || c.opmode == "2" /* DECRYPT_MODE */ - ) - ensure - order c.instantiate(), - c.init(), - c.update()*, - c.finalize() - onfail - InvalidOrderOfCipherOperations -} - -/** - * - */ -rule AEAD_Crypt { - using - Cipher as c - when - _split(c.transform, "/", 1) in ["CCM", "GCM"] - && (c.opmode == "Cipher.ENCRYPT_MODE" - || c.opmode == "Cipher.DECRYPT_MODE" - || c.opmode == "1" /* ENCRYPT_MODE */ - || c.opmode == "2" /* DECRYPT_MODE */ - ) - ensure - order c.instantiate(), - c.init(), - c.aad()*, /* optional because only called if actually supplying AAD */ - c.update()*, - c.finalize() - onfail - InvalidOrderforAEAD -} - - -//// TODO order rule for key wrap -//// TODO order rule for key unwrapping \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_Cipher.mark b/src/test/resources/Mark/bouncycastle/RulesTR_Cipher.mark deleted file mode 100644 index d371cb8fcf69d49a0610369064b9615fed1a036f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_Cipher.mark +++ /dev/null @@ -1,371 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1. Blockchiffren - * - block ciphers - */ -rule ID_2_01 { - using - Cipher as c - ensure - _split(c.transform, "/", 0) in ["AES"] /* BSI TR-02102-1, ID 2.01 */ - || _split(c.transform, "/", 0) in ["RSA"] - onfail - Invalid_TR21021_Cipher -} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.1. Betriebsarten - * - block cipher modes - */ -rule ID_2_1_01 { - using - Cipher as c - when - _split(c.transform, "/", 0) in ["AES"] - ensure - /* */ - _split(c.transform, "/", 1) in ["CCM", "GCM", "CBC", "CTR"] - onfail - InvalidCipherModeforAESBlockCipher -} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - CCM non-repeated IV during key period - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the dynamic behaviour of the program to check this rule. - */ -//rule ID_2_1_2_1_01 { -// using -// Cipher as c -// when -// _split(c.transform, "/", 0) in ["AES"] -// && _split(c.transform, "/", 1) in ["CCM"] -// ensure -// false -// onfail -// InsufficientCCMIVRenewal -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - CCM minimum length of authentication tag - * - * Note: - * Bouncy Castle uses GCMParameterSpec for AEAD cipher initialization. - */ -rule ID_2_1_2_1_02 { - using - Cipher as c, - GCMParameterSpec as gcmspec - when - _split(c.transform, "/", 0) in ["AES"] - && _split(c.transform, "/", 1) in ["CCM"] - ensure - _is(c.paramspec,gcmspec) - && gcmspec.tLen >= 64 - onfail - InsufficientCCMTagLength -} - - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - GCM non-repeated IV during key period - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the dynamic behaviour of the program to check this rule. - */ -//rule ID_2_1_2_2_01 { -// using -// Cipher as c, -// GCMParameterSpec as gcm -// when -// _split(c.transform, "/", 0) in ["AES"] -// && _split(c.transform, "/", 1) in ["GCM"] -// ensure -// false -// onfail -// InvalidGCMIV -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - GCM nonce length for authentication tag - */ -rule ID_2_1_2_2_02 { - using - Cipher as c, - GCMParameterSpec as gcm - when - _split(c.transform, "/", 0) in ["AES"] - && _split(c.transform, "/", 1) in ["GCM"] - && _is(c.paramspec, gcm) - ensure - (_has_value(gcm.src) && _length(gcm.src) == 12) /* in bytes */ - || (_has_value(gcm.len) && gcm.len == 12) - onfail - InvalidGCMAuthenticationNonceLength -} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - GCM minimum length of authentication tag - */ -rule ID_2_1_2_2_03 { - using - Cipher as c, - GCMParameterSpec as gcm - when - _split(c.transform, "/", 0) in ["AES"] - && _split(c.transform, "/", 1) in ["GCM"] - && _is(c.paramspec, gcm) - ensure - gcm.tLen >= 96 /* apparently, there are fixed sizes 96, 104, 112, 120 and 128 */ - onfail - InsufficientGCMTagLength -} - - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - CBC unpredictable IV - */ -rule ID_2_1_2_3_01 { - using - Cipher as c, - IvParameterSpec as ivspec, - SecureRandom as sr - when - _split(c.transform, "/", 0) in ["AES"] - && _split(c.transform, "/", 1) in ["CBC"] - ensure - // IMPROV not just random IV - _is(c.paramspec, ivspec) - && _is(ivspec.iv, sr.randomBytes) - onfail - InvalidCBCIV -} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.2. Betriebsbedingungen - * - CTR non-repeated counter during key period - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the dynamic behaviour of the program to check this rule. - */ -//rule ID_2_1_2_4_01 { -// using -// Cipher as c -// when -// _split(c.transform, "/", 0) in ["AES"] -// && _split(c.transform, "/", 1) in ["CTR"] -// ensure -// false -// onfail -// InvalidCTRCounter -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.1.3. Paddingverfahren - * - CBC padding - */ -rule ID_2_1_3_01 { - using - Cipher as c - when - _split(c.transform, "/", 0) in ["AES"] - && _split(c.transform, "/", 1) in ["CBC"] - ensure - _split(c.transform, "/", 2) in [ - "ISO7816-4Padding", // 1. ISO-Padding, siehe [57], padding method 2 und [73], Appendix A - "PKCS5Padding", "PKCS7Padding" // 2. Padding gemäß [87], Abschnitt 6.3 - ] - onfail - InvalidCBCPadding -} - - -/** - * BSI TR-02102-1 (Version 2019-01), 2.2. Stromchiffren - * - Integrity protection (e.g MAC) - * - * Note: - * - included in rule ID_2_2_02 - */ -//rule ID_2_2_01 { -// using -// Cipher as c -// ensure -// true -// onfail -// InsufficientStreamCipherIntegrityProtection -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 2.2. Stromchiffren - * - AES/CTR with MAC - */ -rule ID_2_2_02 { - using - Cipher as c, - Mac as m - when - _split(c.transform, "/", 0) in ["AES"] - && _split(c.transform, "/", 1) in ["CTR"] - ensure - // IMPROV insufficient application of MAC; MAC over complete stream required - _is(c.output, m.input) - onfail - InsufficientAESCTRIntegrityProtection -} - - -/** - * BSI TR-02102-1 (Version 2019-01), 3.3. ECIES-Verschlüsselungsverfahren - * - ECIES order of decryption operations - * - * Note: - * - implementation not provided by Bouncy Castle. Implementation by user likely to be error prone and should not be encouraged. - */ -//rule ID_3_3_01 { -// using -// Cipher as c -// when -// false -// ensure -// false -// onfail -// InvalidECIESOperationOrder -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 3.3. ECIES-Verschlüsselungsverfahren - * - ECIES curve parameters - * - * Note: - * - implementation not provided by Bouncy Castle. Implementation by user likely to be error prone and should not be encouraged. - */ -//rule ID_3_3_02 { -// using -// Cipher as c -// when -// false -// ensure -// false -// onfail -// InvalidECIESCurveParameter -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 3.3. ECIES-Verschlüsselungsverfahren - * - ECIES key derivation - * - * Note: - * - implementation not provided by Bouncy Castle. Implementation by user likely to be error prone and should not be encouraged. - */ -//rule ID_3_3_03 { -// using -// Cipher as c -// when -// false -// ensure -// false -// onfail -// InvalidECIESSymmetricKeyDerivation -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 3.3. ECIES-Verschlüsselungsverfahren - * - ECIES order of EC base point - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the supplied order of an EC base point - */ -//rule ID_3_3_04 { -// using -// Cipher as c -// when -// false -// ensure -// false -// onfail -// InsufficientECIESBasePointOrder -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 3.4. DLIES-Verschlüsselungsverfahren - * - * Note: - * - implementation not provided by Bouncy Castle. Implementation by user likely to be error prone and should not be encouraged. - * - applies to requirements ID 3.4.01, ID 3.4.02 and ID 3.4.03 - */ - - -/** - * BSI TR-02102-1 (Version 2019-01), 3.5. RSA - * - RSA EME-OAEP formatting scheme - */ -rule ID_3_5_01 { - using - Cipher as c - when - _split(c.transform, "/", 0) == "RSA" - //&& ( - // c.opmode == "javax.crypto.Cipher.ENCRYPT_MODE" - // || c.opmode == "javax.crypto.Cipher.DECRYPT_MODE" - // || c.opmode == "1" - // || c.opmode == "2" - // ) - ensure - _split(c.transform, "/", 2) in [ - // "OAEPWITHSHA1ANDMGF1PADDING", "OAEPWITHSHA-1ANDMGF1PADDING" // recommended by referenced RFC 8017, but not a recommended hash function by BSI - // "OAEPWITHSHA224ANDMGF1PADDING", "OAEPWITHSHA-224ANDMGF1PADDING", // recommended by referenced RFC 8017, but not a recommended hash function by BSI - "OAEPWITHSHA256ANDMGF1PADDING", "OAEPWITHSHA-256ANDMGF1PADDING", - "OAEPWITHSHA384ANDMGF1PADDING", "OAEPWITHSHA-384ANDMGF1PADDING", - "OAEPWITHSHA512ANDMGF1PADDING", "OAEPWITHSHA-512ANDMGF1PADDING"//, - // "OAEPWITHSHA3-256ANDMGF1PADDING", // not listed by referenced RFC 8017, but recommended hash function by BSI - // "OAEPWITHSHA3-384ANDMGF1PADDING", // not listed by referenced RFC 8017, but recommended hash function by BSI - // "OAEPWITHSHA3-512ANDMGF1PADDING" // not listed by referenced RFC 8017, but recommended hash function by BSI - ] - onfail - InvalidRSAPadding -} - -/** - * BSI TR-02102-1 (Version 2019-01), 3.5. RSA - * - RSA minimum length of modulus - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the supplied RSA public key. - */ -//rule ID_3_5_02 { -// using -// Cipher as c, -// RSAPublicKeySpec as pubKey -// when -// _split(c.transform, "/", 0) == "RSA" -// && c.opmode == "javax.crypto.Cipher.ENCRYPT_MODE" -// ensure -// false -// onfail -// InsufficientRSAKeylength // FIXME valid until 2022 -//} - -/* - * Check RSA key length on key generation. - * - * Ensures that generated keys have sufficient length in compliance with BSI TR-02102-1 (Version 2019-01). - * - */ -rule ID_3_5_02_RSAKeyGenParameterSpec { - using - RSAKeyGenParameterSpec as rsaKeyGenSpec - ensure - rsaKeyGenSpec.keysize >= 2000 - onfail - InsufficientRSAKeylength // FIXME valid until 2022 -} - diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_InstanceAuthentication.txt b/src/test/resources/Mark/bouncycastle/RulesTR_InstanceAuthentication.txt deleted file mode 100644 index 2fc11a00212bea1e425f1079b4e03768412ffa55..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_InstanceAuthentication.txt +++ /dev/null @@ -1,8 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 6. Instanzauthentisierung - * - * Note: - * - requirements ID 6.1.01 and ID 6.1.02 already covered by rules for Cipher and MAC - */ diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_KeyAgreement.txt b/src/test/resources/Mark/bouncycastle/RulesTR_KeyAgreement.txt deleted file mode 100644 index a5ea628a784a4d87081d5b85cf799b310cf3a0f4..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_KeyAgreement.txt +++ /dev/null @@ -1,10 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 7. Schlüsseleinigungsverfahren, Schlüsseltransportverfahren und Key-Update - * - * Note: - * - requirements ID 7.1.1.01, ID 7.1.1.02 and ID 7.2.1.01 already covered by rules for Cipher and MAC - * - implementation for requirement ID 7.1.2.01 not provided by Bouncy Castle. Implementation by user likely to be error prone and should not be encouraged. - * - unable to check requirements ID 7.2.2.1.01 and ID 7.2.2.2.01. We cannot sufficiently reason about supplied key length or parameters - */ diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_MAC.mark b/src/test/resources/Mark/bouncycastle/RulesTR_MAC.mark deleted file mode 100644 index c30e1ce91b84d8fe19efdecc741effa41117f09d..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_MAC.mark +++ /dev/null @@ -1,187 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - MAC algorithms - */ -rule ID_5_3_01 { - using - Mac as m - ensure - m.algorithm in [ - "AESCMAC", // CMACs - "HMACSHA256", "HMACSHA512/256", "HMACSHA384", "HMACSHA512", "HMACSHA3-256", "HMACSHA3-384", "HMACSHA3-512", // HMACs - "AES-GMAC" // GMACs - ] - onfail - InvalidMACAlgorithm -} - - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - CMAC minimum key length - */ -rule ID_5_3_02_CMAC_Keygen { - using - Mac as m, - KeyGenerator as kg - when - m.algorithm in ["AESCMAC"] - && _is(m.key, kg.key) - ensure - // find a keygenerator of sufficient size - _is(m.key, kg.key) - && kg.keysize >= 128 - onfail - InsufficientCMACKeyLength -} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - CMAC minimum key length - */ -rule ID_5_3_02_CMAC_HMAC_SecretKeyFactory { - using - Mac as m, - SecretKeySpec as sks, - SecretKeyFactory as kf - when - m.algorithm in ["AESCMAC"] - && _is(m.key, kf.outkey) - ensure - // find a keygenerator of sufficient size - _is(m.key, kf.outkey) - && _is(kf.keyspec, sks) - && ( - (_has_value(sks.len) && sks.len >= 128) - || (!(_has_value(sks.len)) && _has_value(sks.key) && _length(sks.key) >= 16) - ) - onfail - InsufficientCMACKeyLength -} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - HMAC minimum key length - */ -rule ID_5_3_02_HMAC_Keygen { - using - Mac as m, - KeyGenerator as kg - when - m.algorithm in ["HMACSHA256", "HMACSHA512/256", "HMACSHA384", "HMACSHA512", "HMACSHA3-256", "HMACSHA3-384", "HMACSHA3-512"] - && _is(m.key, kg.key) - ensure - // find a keygenerator of sufficient size - ( - _is(m.key, kg.key) - && kg.keysize >= 128 - ) - || ( - _is(m.key, kg.key) - && kg.algorithm == m.algorithm - ) - onfail - InsufficientHMACKeyLength -} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - HMAC minimum key length - */ -rule ID_5_3_02_HMAC_SecretKeyFactory { - using - Mac as m, - SecretKeySpec as sks, - SecretKeyFactory as kf - when - m.algorithm in ["HMACSHA256", "HMACSHA512/256", "HMACSHA384", "HMACSHA512", "HMACSHA3-256", "HMACSHA3-384", "HMACSHA3-512"] - && _is(m.key, kf.outkey) - && _is(kf.keyspec, sks) - ensure - ( - _is(m.key, kf.outkey) - && _is(kf.keyspec, sks) - && ( - (_has_value(sks.len) && sks.len >= 128) - || (!(_has_value(sks.len)) && _has_value(sks.key) && _length(sks.key) >= 16) - ) - ) - onfail - InsufficientHMACKeyLength -} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - GMAC minimum key length - */ -rule ID_5_3_02_GMAC { - using - Mac as m, - KeyGenerator as kg, - SecretKeySpec as sks, - SecretKeyFactory as kf - when - m.algorithm in ["AES-GMAC"] && ( _is(m.key, kg.key) - || ( _is(m.key, kf.outkey) && _is(kf.keyspec, sks) ) ) - ensure - // find a keygenerator of sufficient size - kg.keysize >= 128 - || (_has_value(sks.len) && sks.len >= 128) - || (!(_has_value(sks.len)) && _has_value(sks.key) && _length(sks.key) >= 16) - onfail - InsufficientGMACKeyLength -} - - -/** - * BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - MAC minimum length of authentication tag - */ -rule ID_5_3_03_CMAC { - using - Mac as m - when - m.algorithm in ["AESCMAC"] - ensure - // TODO check in future releases of Bouncy Castle - true // Bouncy Castle implementation uses block size of AES in bits (128) by default - onfail - InsufficientCMACTagLength -} - -/** -* BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - HMAC minimum length of authentication tag - */ -rule ID_5_3_03_HMAC { - using - Mac as m, - HMACParameterSpec as spec - when - m.algorithm in ["HMACSHA256", "HMACSHA512/256", "HMACSHA384", "HMACSHA512", "HMACSHA3-256", "HMACSHA3-384", "HMACSHA3-512"] - && _is(m.params, spec) - ensure - _is(m.params, spec) - && spec.outputLength >= 96 - onfail - InsufficientHMACTagLength -} - -/** -* BSI TR-02102-1 (Version 2019-01), 5.3. Message Authentication Code (MAC) - * - GMAC minimum length of authentication tag - */ -rule ID_5_3_03_GMAC { - using - Mac as m - when - m.algorithm in ["AES-GMAC"] - ensure - // TODO check in future releases of Bouncy Castle - true // Bouncy Castle uses 128 bits by default - onfail - InsufficientGMACTagLength -} - diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_MessageDigest.mark b/src/test/resources/Mark/bouncycastle/RulesTR_MessageDigest.mark deleted file mode 100644 index 86d407b58807b78cf02b5ca49fe7284e63ee669f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_MessageDigest.mark +++ /dev/null @@ -1,17 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 4. Hashfunktionen - * - hash functions - */ -rule ID_4_01 { - using - MessageDigest as md - ensure - md.algorithm in [ - "SHA-256", "SHA-512/256", "SHA-384", "SHA-512", // SHA-2 - "SHA3-256", "SHA3-384", "SHA3-512" // SHA-3 - ] - onfail - InvalidHashFunction -} diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_PRNG.txt b/src/test/resources/Mark/bouncycastle/RulesTR_PRNG.txt deleted file mode 100644 index bd975b50740cd3213c58e9730d2b216c87dc7de8..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_PRNG.txt +++ /dev/null @@ -1,10 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 9. Zufallszahlengeneratoren - * - * Note: - * - requirement ID 9.2.1.01 fulfiled by Bouncy Castle implementation - * - requirement ID 9.2.2.01 does not apply to Bouncy Castle - * - requirement ID 9.5.1.01 and ID 9.5.2.1 not checked because it is internal to the implementation of Bouncy Castle - */ \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/RulesTR_Signature.mark b/src/test/resources/Mark/bouncycastle/RulesTR_Signature.mark deleted file mode 100644 index 3de17561a7ad052843d09364f1e3b80fb70b2fa7..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/RulesTR_Signature.mark +++ /dev/null @@ -1,81 +0,0 @@ -package rules.bsi.tr_02102_1.v2019_01 - -/** - * BSI TR-02102-1 (Version 2019-01), 5.4.1. RSA und 5.4.2. Digital Signature Algorithm (DSA) und 5.4.3 DSA-Varianten basierend auf elliptischen Kurven - * - signature algorithms - * - * Note: - * - includes requirements ID 5.4.1.01 and ID 5.4.3.01 - */ -rule ID_5_4 { - using - Signature as s - ensure - s.algorithm in [ - "SHA256WITHRSAANDMGF1", "SHA512(256)WITHRSAANDMGF1", "SHA384WITHRSAANDMGF1", "SHA512WITHRSAANDMGF1", "SHA3-256WITHRSAANDMGF1", "SHA3-384WITHRSAANDMGF1", "SHA3-512WITHRSAANDMGF1", // RSA, EMSA-PSS - "SHA256WITHRSA/ISO9796-2", "SHA512(256)WITHRSA/ISO9796-2", "SHA384WITHRSA/ISO9796-2", "SHA512WITHRSA/ISO9796-2", // RSA, Digital Signature Scheme (DS) 2 und 3 - "SHA256WITHDSA", "SHA384WITHDSA", "SHA512WITHDSA", "SHA3-256WITHDSA", "SHA3-384WITHDSA", "SHA3-512WITHDSA", // DSA - "SHA256WITHECDSA", "SHA384WITHECDSA", "SHA512WITHECDSA", "SHA3-256WITHECDSA", "SHA3-384WITHECDSA", "SHA3-512WITHECDSA" // ECDSA - ] - onfail - InvalidSignatureAlgorithm -} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.4.1. RSA - * - RSA modulus length of key in bits - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the supplied RSA private key - */ -//rule ID_5_4_1_02 { -// using -// Signature as s, -// KeyFactory as kf, -// RSAPrivateKeySpec as rsaprivkey -// ensure -// // TODO valid until 2022 -// _is(s.privateKey, kf.prikey) -// && _is(kf.keyspec, rsaprivkey) -// && _bit_length(rsaprivkey.modulus) >= 2000 -// onfail -// InsufficientRSAKeyLength -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.4.2. Digital Signature Algorithm (DSA) - * - DSA modulus length of key in bits - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the supplied DSA private key - */ -//rule ID_5_4_2_01 { -// using -// Signature as s, -// KeyFactory as kf, -// DSAPrivateKeySpec as dsaprivkey -// ensure -// // TODO valid until 2022 -// _is(s.privateKey, kf.prikey) -// && _is(kf.keyspec, dsaprivkey) -// && _bit_length(dsaprivkey.modulus) >= 2000 -// onfail -// InsufficientDSAKeyLength -//} - -/** - * BSI TR-02102-1 (Version 2019-01), 5.4.3. DSA-Varianten basierend auf elliptischen Kurven - * - EC[K/G]DSA size of order q - * - * Note: - * - seems to be not checkable. We cannot sufficiently reason about the supplied EC private key - */ -//rule ID_5_4_3_02 { -// using -// Signature as s -// ensure -// // TODO valid until 2022 -// false -// onfail -// InsufficientECOrderQSize -//} diff --git a/src/test/resources/Mark/bouncycastle/Rules_BouncyCastleJCA.mark b/src/test/resources/Mark/bouncycastle/Rules_BouncyCastleJCA.mark deleted file mode 100644 index 607197c8480e716d3d9ebd5107313dc30f76ed04..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/Rules_BouncyCastleJCA.mark +++ /dev/null @@ -1,223 +0,0 @@ -package java.bcjca - -rule BouncyCastleProvider_AlgorithmParameterGenerator { - using - AlgorithmParameterGenerator as apg - ensure - _has_value(apg.provider) - && ( - apg.provider == "BC" - || _is_instance(apg.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_AlgorithmParameterGenerator -} - -rule BouncyCastleProvider_AlgorithmParameters { - using - AlgorithmParameters as ap - ensure - _has_value(ap.provider) - && ( - ap.provider == "BC" - || _is_instance(ap.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_AlgorithmParameters -} - -rule BouncyCastleProvider_CertificateFactory { - using - CertificateFactory as cf - ensure - _has_value(cf.provider) - && ( - cf.provider == "BC" - || _is_instance(cf.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_CertificateFactory -} - -rule BouncyCastleProvider_CertPathBuilder { - using - CertPathBuilder as cpb - ensure - _has_value(cpb.provider) - && ( - cpb.provider == "BC" - || _is_instance(cpb.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_CertPathBuilder -} - -rule BouncyCastleProvider_CertPathValidator { - using - CertPathValidator as cpv - ensure - _has_value(cpv.provider) - && ( - cpv.provider == "BC" - || _is_instance(cpv.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_CertPathValidator -} - -rule BouncyCastleProvider_CertStore { - using - CertStore as cs - ensure - _has_value(cs.provider) - && ( - cs.provider == "BC" - || _is_instance(cs.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_CertStore -} - -rule BouncyCastleProvider_Cipher { - using - Cipher as c - ensure - _has_value(c.provider) - && ( - c.provider == "BC" - || _is_instance(c.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_Cipher -} - -rule BouncyCastleProvider_KeyAgreement { - using - KeyAgreement as ka - ensure - _has_value(ka.provider) - && ( - ka.provider == "BC" - || _is_instance(ka.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_KeyAgreement -} - - -rule BouncyCastleProvider_KeyFactory { - using - KeyFactory as kf - ensure - _has_value(kf.provider) - && ( - kf.provider == "BC" - || _is_instance(kf.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_KeyFactory -} - -rule BouncyCastleProvider_KeyGenerator { - using - KeyGenerator as kg - ensure - _has_value(kg.provider) - && ( - kg.provider == "BC" - || _is_instance(kg.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_KeyGenerator -} - -rule BouncyCastleProvider_KeyPairGenerator { - using - KeyPairGenerator as kpg - ensure - _has_value(kpg.provider) - && ( - kpg.provider == "BC" - || _is_instance(kpg.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_KeyPairGenerator -} - -rule BouncyCastleProvider_KeyStore { - using - KeyStore as ks - ensure - _has_value(ks.provider) - && ( - ks.provider == "BC" - || _is_instance(ks.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_KeyStore -} - -rule BouncyCastleProvider_Mac { - using - Mac as m - ensure - _has_value(m.provider) - && ( - m.provider == "BC" - || _is_instance(m.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_Mac -} - -rule BouncyCastleProvider_MessageDigest { - using - MessageDigest as md - ensure - _has_value(md.provider) - && ( - md.provider == "BC" - || _is_instance(md.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_MessageDigest -} - -rule BouncyCastleProvider_SecretKeyFactory { - using - SecretKeyFactory as skf - ensure - _has_value(skf.provider) - && ( - skf.provider == "BC" - || _is_instance(skf.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_SecretKeyFactory -} - -rule BouncyCastleProvider_SecureRandom { - using - SecureRandom as sr - ensure - _has_value(sr.provider) - && ( - sr.provider == "BC" - || _is_instance(sr.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_SecureRandom -} - -rule BouncyCastleProvider_Signature { - using - Signature as s - ensure - _has_value(s.provider) - && ( - s.provider == "BC" - || _is_instance(s.provider, "org.bouncycastle.jce.provider.BouncyCastleProvider") - ) - onfail - InvalidProvider_Signature -} diff --git a/src/test/resources/Mark/bouncycastle/SecretKey.mark b/src/test/resources/Mark/bouncycastle/SecretKey.mark deleted file mode 100644 index 2a42d0459a9b1cb5762b02a93b266b37e8a1e46f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/SecretKey.mark +++ /dev/null @@ -1,5 +0,0 @@ -package java.jca - -entity SecretKey { - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/SecretKeyFactory.mark b/src/test/resources/Mark/bouncycastle/SecretKeyFactory.mark deleted file mode 100644 index 8703ea34004570688a3f5d68d9872711df3b898f..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/SecretKeyFactory.mark +++ /dev/null @@ -1,35 +0,0 @@ -package java.jca - -entity SecretKeyFactory { - - var algorithm; - var provider; - - var keyspec; - - var inkey; - var outkey; - - - op instantiate { - javax.crypto.SecretKeyFactory.getInstance( - algorithm : java.lang.String - ); - javax.crypto.SecretKeyFactory.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op generate { - outkey = javax.crypto.SecretKeyFactory.generateSecret( - keyspec: java.security.spec.KeySpec - ); - } - - op translate { - outkey = javax.crypto.SecretKeyFactory.translateKey( - inkey : javax.crypto.SecretKey - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/SecretKeySpec.mark b/src/test/resources/Mark/bouncycastle/SecretKeySpec.mark deleted file mode 100644 index fbcb922741fc90bd040dde4205fe18f3928e7104..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/SecretKeySpec.mark +++ /dev/null @@ -1,23 +0,0 @@ -package java.jca - -entity SecretKeySpec { - - var key; - var offset; - var len; - var algorithm; - - op instantiate { - javax.crypto.spec.SecretKeySpec( - key : byte[], - offset : int, - len : int, - algorithm : java.lang.String - ); - javax.crypto.spec.SecretKeySpec( - key : byte[], - algorithm : java.lang.String - ); - } - -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/Signature.mark b/src/test/resources/Mark/bouncycastle/Signature.mark deleted file mode 100644 index effa3213ef5746d2917ad91d422a6a419231bc45..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/Signature.mark +++ /dev/null @@ -1,89 +0,0 @@ -package java.jca - -entity Signature { - - var algorithm; - var provider; - - var privateKey; - var random; - - var certificate; - var publicKey; - - var b; - var data; - var off; - var len; - - var outbuf; - var offset; - var len; - - var signature; - var offset; - var length; - - op instantiate { - java.security.Signature.getInstance( - algorithm : java.lang.String - ); - java.security.Signature.getInstance( - algorithm : java.lang.String, - provider : java.lang.String | java.security.Provider - ); - } - - op initsign { - java.security.Signature.initSign( - privateKey : java.security.PrivateKey - ); - java.security.Signature.initSign( - privateKey : java.security.PrivateKey, - random : java.security.SecureRandom - ); - } - - op initverify { - java.security.Signature.initVerify( - certificate : java.security.cert.Certificate - ); - java.security.Signature.initVerify( - publicKey : java.security.PublicKey - ); - } - - op update { - java.security.Signature.update( - b : byte - ); - java.security.Signature.update( - data : byte[] | java.nio.ByteBuffer - ); - java.security.Signature.update( - data : byte[], - off : int, - len : int - ); - } - - op sign { - signature = java.security.Signature.sign(); - java.security.Signature.sign( - outbuf : byte[], - offseet : int, - len : int - ); - } - - op verify { - java.security.Signature.verify( - signature : byte[] - ); - java.security.Signature.verify( - signature : byte[], - offset : int, - length : int - ); - } -} diff --git a/src/test/resources/Mark/bouncycastle/X509EncodedKeySpec.mark b/src/test/resources/Mark/bouncycastle/X509EncodedKeySpec.mark deleted file mode 100644 index 298ef704a2f45f1082c6fe848a3e603c230f9f42..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/X509EncodedKeySpec.mark +++ /dev/null @@ -1,18 +0,0 @@ -package java.jca - -entity X509EncodedKeySpec { - - var encodedKey; - var algorithm; - - - op instantiate { - java.security.spec.X509EncodedKeySpec( - encodedKey : byte[] - ); - java.security.spec.X509EncodedKeySpec( - encodedKey : byte[], - algorithm : java.lang.String - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/XECPrivateKeySpec.mark b/src/test/resources/Mark/bouncycastle/XECPrivateKeySpec.mark deleted file mode 100644 index d093427f480e329ef8a3154cf45082c8412b9d81..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/XECPrivateKeySpec.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity XECPrivateKeySpec { - - var params; - var scalar; - - op instantiate { - java.security.spec.XECPrivateKeySpec( - params : java.security.spec.AlgorithmParameterSpec, - scalar : byte[] - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/XECPublicKeySpec.mark b/src/test/resources/Mark/bouncycastle/XECPublicKeySpec.mark deleted file mode 100644 index e228310529ff6c728e4059ed8d90c6f121af52c2..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/bouncycastle/XECPublicKeySpec.mark +++ /dev/null @@ -1,14 +0,0 @@ -package java.jca - -entity XECPublicKeySpec { - - var params; - var u; - - op instantiate { - java.security.spec.XECPublicKeySpec( - params : java.security.spec.AlgorithmParameterSpec, - u : java.math.BigInteger - ); - } -} \ No newline at end of file diff --git a/src/test/resources/Mark/exampleMapping.yaml b/src/test/resources/Mark/exampleMapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7358b83b3039400efe321af4df7de77d4aabbb2b --- /dev/null +++ b/src/test/resources/Mark/exampleMapping.yaml @@ -0,0 +1,20 @@ +metrics: + - name: "TestMetric1" + rules: + - "WrongUseOfBotan_CipherMode" + configuration: + default: false + operator: "=" + type: NUMBER + target: + - "1.23" + - "3.14" + - name: "TestMetric2" + rules: + - "VariableNotInitialized" + configuration: + default: false + operator: "=" + type: BOOLEAN + target: + - "true" \ No newline at end of file diff --git a/src/test/resources/Mark/jackson/ObjectMapper.mark b/src/test/resources/Mark/jackson/ObjectMapper.mark deleted file mode 100644 index dad5edebbd8b5158873e3e9fdc83ddb465cd1c82..0000000000000000000000000000000000000000 --- a/src/test/resources/Mark/jackson/ObjectMapper.mark +++ /dev/null @@ -1,12 +0,0 @@ -entity ObjectMapper { - op instantiate { - com.fasterxml.jackson.databind.ObjectMapper(); - } - - /** - * Default typing has lead to various problems with Jackson in the past and should be avoided. - */ - op enableDefaultTyping { - forbidden com.fasterxml.jackson.databind.ObjectMapper.enableDefaultTyping(); - } -} \ No newline at end of file diff --git a/src/test/resources/codyze.yaml b/src/test/resources/codyze.yaml index 66a79acffe705fa71b3e2fa3db5e45b11fdf78da..2a2abb4e81349df1376175c6f2889ae83b42518b 100644 --- a/src/test/resources/codyze.yaml +++ b/src/test/resources/codyze.yaml @@ -7,12 +7,16 @@ orchestrator: username: "clouditor" password: "clouditor" -source: "src/test/resources/exampleFiles/TLSServer/" +id: "default" +source: + - exampleFiles/2_1_2_1_02.cpp output: "codyze.sarif" -sarif: true +rules: mark/medina.yaml + +mark: + builtin: + - bc-jsse/ codyze: - mark: - - "src/test/resources/mark/demo/" no-good-findings: false pedantic: false diff --git a/src/test/resources/demos/bcjsse/TlsServer.java b/src/test/resources/demos/bcjsse/TlsServer.java deleted file mode 100644 index f71cad44e280da432554b7bb2df9d4be522558da..0000000000000000000000000000000000000000 --- a/src/test/resources/demos/bcjsse/TlsServer.java +++ /dev/null @@ -1,146 +0,0 @@ -package de.fraunhofer.aisec.codyze.medina.demo.jsse; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; - -import javax.net.ssl.*; -import java.io.*; -import java.net.Socket; -import java.nio.file.Paths; -import java.security.*; -import java.security.cert.CertificateException; -import java.util.Arrays; - -public class TlsServer { - - private static final boolean DEBUG = true; - - private SSLServerSocket socket; - - private void configure(int port, String keystore, String keystorePwd) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException, NoSuchProviderException { - // overview of functionality provided by BCJSSE - if (DEBUG) { - System.out.println("Services provided by BCJSSE security Provider"); - - for (Provider.Service s : Security.getProvider("BCJSSE").getServices()) { - System.out.println(s); - } - System.out.println(); - } - - // get default from most prioritized securtiy provider -> BCJSSE - SSLContext sslCtx = SSLContext.getInstance("TLS", "BCJSSE"); - - // initialize sslContext with a KeyManager and no TrustManager - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load(new FileInputStream(keystore), keystorePwd.toCharArray()); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, keystorePwd.toCharArray()); - sslCtx.init(kmf.getKeyManagers(), null, null); - - // verify correct selection - if(DEBUG) { - System.out.println("Current provider:"); - System.out.println(sslCtx.getProvider()); - System.out.println(); - - System.out.println("Selected protocol:"); - System.out.println(sslCtx.getProtocol()); - System.out.println(); - - SSLParameters sslParams = sslCtx.getSupportedSSLParameters(); - - System.out.println("Protocols:"); - Arrays.stream(sslParams.getProtocols()).forEach(System.out::println); - System.out.println(); - - System.out.println("Use cipher suites order: "); - System.out.println(sslParams.getUseCipherSuitesOrder()); - System.out.println(); - - System.out.println("Cipher suites:"); - Arrays.stream(sslParams.getCipherSuites()).forEach(System.out::println); - System.out.println(); - } - - // create the SSLServerSocket - SSLServerSocketFactory socketFactory = sslCtx.getServerSocketFactory(); - socket = (SSLServerSocket) socketFactory.createServerSocket(port); - - // set protocol versions and cipher suites - socket.setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"}); - socket.setEnabledCipherSuites(new String[]{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_AES_128_CCM_SHA256", "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"}); - - if (DEBUG) { - System.out.println("Enabled by the Socket:"); - - System.out.println("Protocols:"); - Arrays.stream(socket.getEnabledProtocols()).forEach(System.out::println); - System.out.println(); - - System.out.println("Cipher suites:"); - Arrays.stream(socket.getEnabledCipherSuites()).forEach(System.out::println); - System.out.println(); - } - } - - private void start() { - while(true) { - try (Socket sock = socket.accept()) { - BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); - BufferedWriter out = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream())); - while (in.readLine() != null) { - out.write("ack"); - out.flush(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public static void main(String[] args) { - // ensure Bouncy Castle is first provider queried when retrieving implementations - Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); - Security.insertProviderAt(new BouncyCastleProvider(), 2); - - if (DEBUG) { - System.out.println("Registered security providers:"); - - Provider[] ps = Security.getProviders(); - for (int i = 0; i < ps.length; i++) { - System.out.println(i + " : " + ps[i]); - } - System.out.println(); - } - - // try to get the path of the needed Keystore: - File jks = null; - // 1: absolute path via program argument - if (args.length != 0) { - jks = new File(args[0]); - } - // 2: absolute path via environment variable - if (args.length == 0 || !jks.exists()) { - String env = System.getenv("KEYSTORE_PATH"); - if (env != null) - jks = new File(env); - //3: load via class loader - if (env == null || !jks.exists()) { - // Throws a NPE if Keystore could not be found up until this point - jks = new File(TlsServer.class.getClassLoader().getResource("keystore.jks").getPath()); - } - } - - // create TLS server - TlsServer server = new TlsServer(); - try { - // the keystore is expected to be generated with the script in the resource folder - server.configure(2200, jks.getAbsolutePath(), "demo-password"); - } catch (Exception e) { - e.getLocalizedMessage(); - System.exit(1); - } - server.start(); - } -} diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml deleted file mode 100644 index a7748a1a6a34bcd5c8fa94e7d462a952b0904b39..0000000000000000000000000000000000000000 --- a/src/test/resources/log4j2.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<configuration> - <appenders> - <console name="stdout" target="SYSTEM_OUT"> - <patternLayout pattern="%d{ABSOLUTE} %5p %c{1}:%L - %m%n"/> - </console> - - <file name="fileout" fileName="medina-codyze.log"> - <patternLayout pattern="%d{ABSOLUTE} %5p %c{1}:%L - %m%n"/> - </file> - </appenders> - - <loggers> - <Logger level="ALL" name="de.fraunhofer.aisec.codyze"/> - <root level="TRACE"> - <appenderRef ref="stdout"/> - </root> - </loggers> -</configuration> \ No newline at end of file diff --git a/src/test/resources/mappingTreeTestStructure/botan/bt1/block_ciphers.mark b/src/test/resources/mappingTreeTestStructure/botan/bt1/block_ciphers.mark new file mode 100644 index 0000000000000000000000000000000000000000..117cc7140b942d1afda5d978ba78b80544d898ff --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/botan/bt1/block_ciphers.mark @@ -0,0 +1,20 @@ +package botan + +/* + * From Botan Handbook: + * "In general a bare block cipher is not what you should be using. You probably want a cipher mode instead (see Cipher Modes)" + */ + +entity Botan.Forbidden.BlockCipher { + op forbid { + forbidden Botan::get_block_cipher(); + forbidden Botan::get_block_cipher(...); + forbidden Botan::get_block_cipher_providers(); + forbidden Botan::get_block_cipher_providers(...); + + forbidden Botan::BlockCipher::create(); + forbidden Botan::BlockCipher::create(...); + forbidden Botan::BlockCipher::create_or_throw(); + forbidden Botan::BlockCipher::create_or_throw(...); + } +} \ No newline at end of file diff --git a/src/test/resources/mappingTreeTestStructure/botan/bt1/mapping.yaml b/src/test/resources/mappingTreeTestStructure/botan/bt1/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..de12ff7a3fa477761d1435921d0e58db235197de --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/botan/bt1/mapping.yaml @@ -0,0 +1,11 @@ +metrics: + - name: "bt1" + rules: + - "block_ciphers" + configuration: + default: false + operator: "=" + type: NUMBER + target: + - "1.23" + - "3.14" \ No newline at end of file diff --git a/src/test/resources/mappingTreeTestStructure/botan/bt2/hash.mark b/src/test/resources/mappingTreeTestStructure/botan/bt2/hash.mark new file mode 100644 index 0000000000000000000000000000000000000000..f987874caf7d51f2f12c690d64dfbb795caa9620 --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/botan/bt2/hash.mark @@ -0,0 +1,69 @@ +package botan + +entity Botan.HashFunction { + var alg; + var data; + var len; + var hash_output; + + op create { + Botan::HashFunction::create(alg); + Botan::HashFunction::create(alg, _); + Botan::HashFunction::create_or_throw(alg); + Botan::HashFunction::create_or_throw(alg, _); + } + + op update { + Botan::HashFunction::update(data); + Botan::HashFunction::update(data: uint8_t[], length); + } + + op finalize { + hash_output = Botan::HashFunction::final(); + hash_output = Botan::HashFunction::final_stdvec(); + Botan::HashFunction::final(hash_output); + } + + op process { + hash_output = Botan::HashFunction::process(data); + hash_output = Botan::HashFunction::process(data: uint8_t[], length); + } +} + +rule HashOrder { + using Botan.HashFunction as hf + ensure order + hf.create(), + ( + (hf.update()*, hf.finalize()) + | hf.process() + ) + onfail HashOrder + +} + + + + /* + * For use of hashes in Filter/Pipes + * correct order is defined in pipe.mark + */ + entity Botan.Hash_Filter { + var hash_function: Botan.HashFunction; + var request; + + op create { + Botan::Hash_Filter(request: std::string, len); + Botan::Hash_Filter(request: std::string, len); + Botan::Hash_Filter(hash_function: Botan::HashFunction); + Botan::Hash_Filter(hash_function: Botan::HashFunction, len); + } + } + + rule _4_01_HashFilter { + using Botan.Hash_Filter as hf + when _has_value(hf.request) + ensure hf.request in ["SHA-256", "SHA-512-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-384", "SHA3-512"] + onfail _01_HashFilter +} + \ No newline at end of file diff --git a/src/test/resources/mappingTreeTestStructure/botan/bt2/mapping.yaml b/src/test/resources/mappingTreeTestStructure/botan/bt2/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bda4ddf5f351e4ed563ebe8a225e5a9ddb1fedda --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/botan/bt2/mapping.yaml @@ -0,0 +1,11 @@ +metrics: + - name: "bt2" + rules: + - "hash" + configuration: + default: false + operator: "=" + type: NUMBER + target: + - "1.23" + - "3.14" \ No newline at end of file diff --git a/src/test/resources/mappingTreeTestStructure/botan/cipher_mode.mark b/src/test/resources/mappingTreeTestStructure/botan/cipher_mode.mark new file mode 100644 index 0000000000000000000000000000000000000000..b1b29a1100fdf08b2b06fd46a88b2638558202c1 --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/botan/cipher_mode.mark @@ -0,0 +1,107 @@ +package botan + +entity Botan.Cipher_Mode { + + var algorithm; + var symkey : Botan.SymmetricKey; + var iv : Botan.InitializationVector; + var iv_length; + var direction; + + var input; + var input_length; + + var inout; + + var aead_data; + var aead_data_len; + + /* + Note: allows creating objects of Type Botan::Keyed_Filter and Botan::Cipher_Mode, therefore all other ops should consider member functions of these two classes + + Botan::OctetString might be allowed for key and IV, but we will not accept it because Botan::SymmetricKey and Botan::InitializationVector carry more semantics, which should increase safety and maintainability. + We allow secure vector because real world examples seem to use it + */ + + /* Note: there is also the possibility to create a cipher from ECIES_System_Params. Maybe forbid that? */ + + op create_uninit { + Botan::get_cipher_mode(algorithm, direction); + Botan::get_cipher_mode(algorithm, direction, ...); + Botan::get_cipher(algorithm, direction); + } + + op create_key_init { + Botan::get_cipher( + algorithm, + iv: Botan::InitializationVector, + direction + ); + } + + op create_key_iv_init { + Botan::get_cipher( + algorithm, + symkey: Botan::SymmetricKey, + iv: Botan::InitializationVector, + direction + ); + } + + op set_key { + Botan::Cipher_Mode::set_key(symkey: Botan::SymmetricKey | Botan::secure_vector<uint8_t>); + forbidden Botan::Cipher_Mode::set_key(_, _); + Botan::Keyed_Filter::set_key(symkey: Botan::SymmetricKey | Botan::secure_vector<uint8_t>); + } + + op set_iv { + Botan::Keyed_Filter::set_iv(iv: Botan::InitializationVector); + } + + op start_no_iv { + Botan::Cipher_Mode::start(); + Botan::Keyed_Filter::start_msg(); + } + + op start_iv { + Botan::Cipher_Mode::start(iv); + Botan::Cipher_Mode::start(iv, iv_length); + Botan::Cipher_Mode::start_msg(iv, iv_length); + } + + op process { + Botan::Cipher_Mode::process(input, input_length); + Botan::Cipher_Mode::update(inout); + Botan::Cipher_Mode::update(inout, _); + + Botan::Keyed_Filter::write(input, input_length); + } + + op finish { + Botan::Cipher_Mode::finish(inout); + Botan::Cipher_Mode::finish(inout, _); + + Botan::Keyed_Filter::end_msg(); + } + + op reset { + Botan::Cipher_Mode::reset(); + } + + op assoc_data { + Botan::AEAD_Filter::set_associated_data(aead_data, aead_data_len); + } + +} + + +rule Cipher_Mode_Order { + using Botan.Cipher_Mode as cm + ensure order + ((cm.create_uninit(), cm.set_key()) | cm.create_key_init()), // key is set here + ((cm.set_iv(), cm.start_no_iv()) | cm.start_iv()), + cm.assoc_data()*, + cm.process()*, + cm.finish() + onfail Cipher_Mode_Order +} \ No newline at end of file diff --git a/src/test/resources/mappingTreeTestStructure/botan/mapping.yaml b/src/test/resources/mappingTreeTestStructure/botan/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d86e377b832b72182becae5a6becf522eef18c59 --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/botan/mapping.yaml @@ -0,0 +1,13 @@ +metrics: + - name: "botan" + rules: + - "hash" + - "block_ciphers" + - "cipher_mode" + configuration: + default: false + operator: "=" + type: NUMBER + target: + - "1.23" + - "3.14" \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/BouncyCastleProvider.mark b/src/test/resources/mappingTreeTestStructure/bouncycastle/bc1/BouncyCastleProvider.mark similarity index 100% rename from src/test/resources/Mark/bouncycastle/BouncyCastleProvider.mark rename to src/test/resources/mappingTreeTestStructure/bouncycastle/bc1/BouncyCastleProvider.mark diff --git a/src/test/resources/mappingTreeTestStructure/bouncycastle/bc1/mapping.yaml b/src/test/resources/mappingTreeTestStructure/bouncycastle/bc1/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d54470d7a1531e19dd64ad67ddde60ed8b7c6447 --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/bouncycastle/bc1/mapping.yaml @@ -0,0 +1,11 @@ +metrics: + - name: "bc1" + rules: + - "BouncyCastleProvider" + configuration: + default: false + operator: "=" + type: NUMBER + target: + - "1.23" + - "3.14" \ No newline at end of file diff --git a/src/test/resources/Mark/bouncycastle/Cipher.mark b/src/test/resources/mappingTreeTestStructure/bouncycastle/bc2/Cipher.mark similarity index 100% rename from src/test/resources/Mark/bouncycastle/Cipher.mark rename to src/test/resources/mappingTreeTestStructure/bouncycastle/bc2/Cipher.mark diff --git a/src/test/resources/mappingTreeTestStructure/bouncycastle/bc2/mapping.yaml b/src/test/resources/mappingTreeTestStructure/bouncycastle/bc2/mapping.yaml new file mode 100644 index 0000000000000000000000000000000000000000..da14598b0570cd0037aad79b1d34ea86d1c348c5 --- /dev/null +++ b/src/test/resources/mappingTreeTestStructure/bouncycastle/bc2/mapping.yaml @@ -0,0 +1,11 @@ +metrics: + - name: "bc2" + rules: + - "Cipher" + configuration: + default: false + operator: "=" + type: NUMBER + target: + - "1.23" + - "3.14" \ No newline at end of file diff --git a/templates/codyze-medina-metrics.yaml b/templates/codyze-medina-metrics.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cd2d6cde71c244764912175b298ff3e4789e3f5b --- /dev/null +++ b/templates/codyze-medina-metrics.yaml @@ -0,0 +1,16 @@ +metrics: + - name: "CodeSignoff" + target: + - "<Name>" + - "<Name>" + - name: "SignedCommits" + target: + - "<16-Digit Key-Id>" + - name: "SignedSignoff" + target: + - name: "<Name>" + email: "<E-Mail>" + pub-key-id: "<16-Digit Base-64 Signing-Key-Id>" + - name: "ApprovedCommitAuthor" + target: + - "<Name>" diff --git a/templates/codyze-medina.yaml b/templates/codyze-medina.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3051555b0d22b92438c0cf95374abf5290963f53 --- /dev/null +++ b/templates/codyze-medina.yaml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=schema/codyze-config-schema.json +# For more explanation regarding the parameters, please refer to the README. + +orchestrator: + required: <BOOLEAN> # Optional value, defaults to true + endpoint: "<URL>" # Required value + auth: + oauth-endpoint: "<URL>" # Required value + username: "<STRING>" # Required value + password: "<STRING>" # Required value, can also be injected via program arguments or environment variables + +id: "<UUID>" # Required value +source: # Required value + - <FILEPATH> + - ... + +ci: "<NONE | GITLAB | GITHUB | JENKINS>" # Optional value, automatically inferred by default +rules: <FILEPATH> # Optional value, defaults to codyze-medina-metrics.yaml +medina-output: <FILEPATH> # Optional value, defaults to codyze-medina.sarif +combined-output: <BOOLEAN> # Optional value, defaults to true +key-location: <PATH> # Optional value, defaults to public-keys/ + +mark: # While technically optional, MARK rules are required to generate analysis results + builtin: # Refers to MARK modules included in the "mark/" directory of the release package + - <MODULE_NAME> + - ... + project: # Refers to additional MARK modules in other places + - <PATH> + - ... + +# All additional parameters will be directly passed to the underlying Codyze implementation. +# Core Codyze parameters are documented at https://www.codyze.io/