diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1820a98 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.{kt,kts}] +kotlin_disabled_rules = no-wildcard-imports, import-ordering diff --git a/README.md b/README.md new file mode 100644 index 0000000..874c0ce --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# AusweisApp2 SDK Wrapper + +The SDK Wrapper is an android library for providing a convenient frontend to the AusweisApp2. + +## Installation + +For now you need to include the aars in tester/libs in your project. +You also have to make sure to add all dependencies found in tester/build.gradle to your project. + +## Usage + +You can use the app as a simple wrapper of the ausweisapp2 sdk via the WorkflowController: + + import de.governikus.ausweiapp2.sdkwrapper.SDKWrapper + + val tcTokenUrl: Uri = ... + SDKWrapper.workflowController.registerCallbacks(object : WorkflowCallbacks { + override fun onStarted() { + SDKWrapper.workflowController.startAuthentication(tcTokenUrl) + } + + override fun onAuthenticationCompleted(authResult: AuthResult) { + [...] + } + }) + + SDKWrapper.workflowController.start() + +## Contact + + Governikus GmbH & Co. KG. + Hochschulring 4 + 28359 Bremen + +## License + +Copyright (c) 2023 Governikus GmbH & Co. KG, Germany diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c7643a5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,44 @@ +buildscript { + ext.kotlin_version = '1.8.10' + ext.sdkwrapper_version = "1.26.3.0" + + repositories { + google() + maven { + url "https://plugins.gradle.org/m2/" + } + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jmailen.gradle:kotlinter-gradle:3.13.0" + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3" + } +} + +plugins { + id("org.sonarqube") version "3.5.0.2730" + id("java") +} + +version = sdkwrapper_version + +allprojects { + repositories { + google() + mavenCentral() + maven { url "$project.rootDir/libs" } + } +} + +task tarball(type: Tar) { + into ('/') { + from rootDir + include '**' + exclude ('**/build/**', 'tester', '.reviewboardrc', '.gradle', '.idea', 'jenkins') + } + + destinationDirectory = file('build/tar') + compression = Compression.GZIP +} diff --git a/gradle.properties b/gradle.properties new file mode 100755 index 0000000..0ddec36 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,9 @@ +org.gradle.jvmargs=-Xmx1536m +android.useAndroidX=true +android.enableJetifier=true +kotlin.code.style=official + +systemProp.sonar.host.url=https://sonar.govkg.de +systemProp.sonar.projectKey=SDKWrapper-Android +systemProp.sonar.sourceEncoding=UTF-8 +systemProp.sonar.language=kotlin diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ae04661 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..53a6b23 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lint.xml b/lint.xml new file mode 100644 index 0000000..c76b768 --- /dev/null +++ b/lint.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sdkwrapper/build.gradle b/sdkwrapper/build.gradle new file mode 100644 index 0000000..580279e --- /dev/null +++ b/sdkwrapper/build.gradle @@ -0,0 +1,137 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' +apply plugin: "org.jmailen.kotlinter" +apply plugin: 'maven-publish' + +android { + compileSdkVersion 33 + namespace "de.governikus.ausweisapp2.sdkwrapper" + + defaultConfig { + buildToolsVersion "30.0.3" + minSdkVersion 24 + targetSdkVersion 33 + versionCode 2 + versionName sdkwrapper_version + archivesBaseName = "sdkwrapper" + + consumerProguardFiles 'consumer-rules.pro' + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + testOptions.unitTests.includeAndroidResources = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + allWarningsAsErrors = true + freeCompilerArgs = ["-opt-in=kotlin.RequiresOptIn"] // Used for UnitTests, can be removed with kotlin 1.7.0 + } + + lintOptions { + setLintConfig(file("../lint.xml")) + checkDependencies false + warningsAsErrors true + } + + buildFeatures { + dataBinding = true + } + + buildTypes { + debug { + minifyEnabled false + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + libraryVariants.all { variant -> + variant.outputs.all { + outputFileName = "${archivesBaseName}-${variant.name}-${defaultConfig.versionName}.aar" + } + } + + publishing { + singleVariant("release") + } + + packagingOptions { + jniLibs { + useLegacyPackaging = true + } + } +} + +task sourceJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + archiveClassifier = "sources" + archiveVersion = android.defaultConfig.versionName +} + +afterEvaluate { + publishing { + repositories { + maven { + name = 'Local' + url "$project.rootDir/build/dist/libs" + } + maven { + name = 'Nexus' + credentials { + username System.getenv("NEXUS_USERNAME") + password System.getenv("NEXUS_PSW") + } + url = System.getenv("NEXUS_URL") + } + } + publications { + snapshot(MavenPublication) { + from components.release + groupId = 'de.governikus.ausweisapp2' + artifactId = 'sdkwrapper' + version = "${android.defaultConfig.versionName}-SNAPSHOT" + + artifact(sourceJar) + } + release(MavenPublication) { + from components.release + pom { + name = 'AusweisApp2 SDK Wrapper' + description = 'Convenient authentication SDK' + url = 'https://www.governikus.de' + } + groupId = 'de.governikus.ausweisapp2' + artifactId = 'sdkwrapper' + version = android.defaultConfig.versionName + + artifact(sourceJar) + } + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "com.governikus:ausweisapp:1.26.3" + implementation 'com.google.code.gson:gson:2.10.1' + + testImplementation "junit:junit:4.13.2" + testImplementation 'androidx.test:core:1.5.0' + testImplementation 'org.robolectric:robolectric:4.9.2' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' + + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' + +} diff --git a/sdkwrapper/consumer-rules.pro b/sdkwrapper/consumer-rules.pro new file mode 100644 index 0000000..3beb66c --- /dev/null +++ b/sdkwrapper/consumer-rules.pro @@ -0,0 +1,6 @@ +-keep class androidx.databinding.** { *; } + +-keep class de.governikus.ausweisapp2.sdkwrapper.DataBinderMapperImpl { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.DataBinderMapperImpl$** { *; } + +-keepclassmembers,allowoptimization class de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.** { *; } diff --git a/sdkwrapper/proguard-rules.pro b/sdkwrapper/proguard-rules.pro new file mode 100644 index 0000000..3067231 --- /dev/null +++ b/sdkwrapper/proguard-rules.pro @@ -0,0 +1,42 @@ +-dontobfuscate +-keep class kotlin.Metadata { *; } +-keepattributes RuntimeVisibleAnnotations +-keep class androidx.databinding.** { *; } + +-keep class de.governikus.ausweisapp2.sdkwrapper.DataBinderMapperImpl { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.DataBinderMapperImpl$** { *; } + +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.** { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.CertificateDescription { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.CertificateValidity { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.AccessRights { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.AuxiliaryData { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.Card { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.AuthResult { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.AuthResultData { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.WrapperError { *; } +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.ChangePinResult { *; } +-keep enum de.governikus.ausweisapp2.sdkwrapper.card.core.AccessRight { *; } +-keep enum de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowProgressType { *; } + +-keep class de.governikus.ausweisapp2.sdkwrapper.card.core.NfcForegroundDispatcher { *; } + +-keep public interface de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowCallbacks { *; } +-keep public class de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowController { + public ; + public ; +} + +-keep class de.governikus.ausweisapp2.sdkwrapper.SDKWrapper$** { + public ; +} +-keep class de.governikus.ausweisapp2.sdkwrapper.SDKWrapper { + public *; +} + +-assumenosideeffects class android.util.Log { + public static boolean isLoggable(java.lang.String, int); + public static int v(...); + public static int d(...); + public static int i(...); +} diff --git a/sdkwrapper/src/main/AndroidManifest.xml b/sdkwrapper/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3f76fcf --- /dev/null +++ b/sdkwrapper/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/SDKWrapper.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/SDKWrapper.kt new file mode 100644 index 0000000..eff4ec3 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/SDKWrapper.kt @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper + +import de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowController +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.AA2SdkConnection +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope + +object SDKWrapper : CoroutineScope by MainScope() { + val workflowController = WorkflowController(AA2SdkConnection()) +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/NfcForegroundDispatcher.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/NfcForegroundDispatcher.kt new file mode 100644 index 0000000..33a00c1 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/NfcForegroundDispatcher.kt @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core + +import android.app.Activity +import android.nfc.NfcAdapter.FLAG_READER_NFC_A +import android.nfc.NfcAdapter.FLAG_READER_NFC_B +import android.nfc.NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK +import android.nfc.NfcAdapter.getDefaultAdapter +import android.nfc.Tag +import android.nfc.tech.IsoDep + +/** + * ForegroundDispatcher used to detect ISO-DEP (ISO 14443-4) NFC tags, like an id card. + * + * Use it to detect and pass detected id cards to the [WorkflowController.onNfcTagDetected] function. + * + * @param activity Activity that the NFCAdapter will be attached to + * @param workflowController Workflow manager that is th receive the nfc tags + */ +class NfcForegroundDispatcher( + private val activity: Activity, + private val workflowController: WorkflowController +) { + private val nfcAdapter by lazy { + getDefaultAdapter(activity) + } + private var isStarted = false + private val nfcReaderFlags = + FLAG_READER_NFC_A or FLAG_READER_NFC_B or FLAG_READER_SKIP_NDEF_CHECK + private val nfcTechnology = IsoDep::class.java.name + + fun start() { + if (isStarted) { + return + } + isStarted = true + nfcAdapter.enableReaderMode( + activity, + { tag: Tag -> + if (tag.techList.contains(nfcTechnology)) { + workflowController.onNfcTagDetected(tag) + } + }, + nfcReaderFlags, + null + ) + } + + /** + * Stop the [NfcForegroundDispatcher] + * + * Must be called in [Activity.onPause], when the activity put into the background + */ + fun stop() { + if (!isStarted) { + return + } + isStarted = false + nfcAdapter.disableReaderMode(activity) + } +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowCallbacks.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowCallbacks.kt new file mode 100644 index 0000000..f8e0867 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowCallbacks.kt @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core + +/** + * Authentication workflow callbacks. + * + * You need to register them with the [WorkflowController] + * + * @see WorkflowController.registerCallbacks + */ +interface WorkflowCallbacks { + + /** + * [WorkflowController] has successfully been initialized. + */ + fun onStarted() + + /** + * An authentication has been started via [WorkflowController.startAuthentication]. + * + * The next callback should be [onAccessRights] or [onAuthenticationCompleted] if the authentication immediately results in an error. + */ + fun onAuthenticationStarted() + + /** + * An authentication could not be started. + * This is different from an authentication that was started but failed during the process. + * + * @param error Error message about why the authentication could not be started. + */ + fun onAuthenticationStartFailed(error: String) + + /** + * Indicates that the authentication workflow is completed. + * + * The [AuthResult] will contain a refresh url or in case of an error a communication error address. + * You can check the state of the authentication, by looking for the [AuthResult.result] field. + * + * @param authResult Result of the authentication + */ + fun onAuthenticationCompleted(authResult: AuthResult) + + /** + * A pin change has been started via [WorkflowController.startChangePin]. + */ + fun onChangePinStarted() + + /** + * Access rights requested in response to an authentication. + * + * This function will be called once the authentication is started by [WorkflowController.startAuthentication] + * and the SDK got the certificate from the service. + * + * Accept ([WorkflowController.accept]) the rights to continue with the workflow or completely + * abort the workflow with ([WorkflowController.cancel]). + * + * It is also possible to change the optional rights via [WorkflowController.setAccessRights]. + * + * @param error Optional error message if the call to [WorkflowController.setAccessRights] failed. + * @param accessRights Requested access rights. + */ + fun onAccessRights(error: String?, accessRights: AccessRights?) + + /** + * Provides information about the used certificate. + * + * Response of a call to [WorkflowController.getCertificate]. + * + * @param certificateDescription Requested certificate. + */ + fun onCertificate(certificateDescription: CertificateDescription) + + /** + * Indicates that the workflow now requires a card to continue. + * + * If your application receives this message it should show a hint to the user. + * After the user inserted a card the workflow will automatically continue, unless the eID functionality is disabled. + * In this case, the workflow will be paused until another card is inserted. + * If the user already inserted a card this function will not be called at all. + * + * @param error Optional detailed error message if the previous call to [WorkflowController.setCard] failed. + */ + fun onInsertCard(error: String?) + + /** + * A specific reader was recognized or has vanished. Also called as a response to [WorkflowController.getReader]. + * + * @param reader Recognized or vanished reader, might be null if an unknown reader was requested in [WorkflowController.getReader]. + */ + fun onReader(reader: Reader?) + + /** + * Called as a reponse to [WorkflowController.getReaderList]. + * + * @param readers Optional list of present readers (if any). + */ + fun onReaderList(readers: List?) + + /** + * Indicates that a PIN is required to continue the workflow. + * + * A PIN is needed to unlock the id card, provide it with [WorkflowController.setPin]. + * + * @param error Optional error message if the call to [WorkflowController.setPin] failed. + * @param reader Reader the PIN is requested for + */ + fun onEnterPin(error: String?, reader: Reader) + + /** + * Indicates that a new PIN is required to continue the workflow. + * + * A new PIN is needed fin response to a pin change, provide it with [WorkflowController.setNewPin]. + * + * @param error Optional error message if the call to [WorkflowController.setNewPin] failed. + * @param reader Reader the new PIN is requested for + */ + fun onEnterNewPin(error: String?, reader: Reader) + + /** + * Indicates that a PUK is required to continue the workflow. + * + * A PUK is needed to unlock the id card, provide it with [WorkflowController.setPuk]. + * + * @param error Optional error message if the call to [WorkflowController.setPuk] failed. + * @param reader Reader the PUK is requested for + */ + fun onEnterPuk(error: String?, reader: Reader) + + /** + * Indicates that a CAN is required to continue workflow. + * + * A CAN is needed to unlock the id card, provide it with [WorkflowController.setCan]. + * + * @param error Optional error message if the call to [WorkflowController.setCan] failed. + * @param reader Reader the CAN is requested for + */ + fun onEnterCan(error: String?, reader: Reader) + + /** + * Indicates that the pin change workflow is completed. + * + * @param changePinResult Result of the pin change + */ + fun onChangePinCompleted(changePinResult: ChangePinResult) + + /** + * Indicates that an error has occurred. + * + * This might be called if there was an error in the workflow. + * + * @param error Error + */ + fun onWrapperError(error: WrapperError) + + /** + * Provides information about the current workflow and state. This callback indicates if a + * workflow is in progress or the workflow is paused. This can occur if the AusweisApp2 needs + * additional data like ACCESS_RIGHTS or INSERT_CARD. + * + * @param workflowProgress Holds information about the current workflow progress. + */ + fun onStatus(workflowProgress: WorkflowProgress) + + /** + * Provides information about the supported API level of the employed AusweisApp2 + * + * Response to a call to WorkflowController.getApiLevel() and WorkflowController.setApiLevel(). + * + * @param error Optional error message if WorkflowController.setApiLevel() failed. + * @param apiLevel Contains information about the supported and employed API level. + */ + fun onApiLevel(error: String?, apiLevel: ApiLevel?) + + /** + * Provides information about the AusweisApp2 that is used in the SDK Wrapper. + * + * Response to a call to [WorkflowController.getInfo]. + * + * @param versionInfo Holds information about the currently utilized AusweisApp2. + */ + fun onInfo(versionInfo: VersionInfo) + + /** + * Called if an error within the AusweisApp2 SDK occurred. Please report this as it indicates a bug. + * + * @param error Information about the error. + */ + fun onInternalError(error: String) + + /** + * Called if the sent command is not allowed within the current workflow. + * + * @param error Error message which SDK command failed. + */ + fun onBadState(error: String) +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowController.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowController.kt new file mode 100644 index 0000000..959e910 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowController.kt @@ -0,0 +1,611 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core + +import android.content.Context +import android.net.Uri +import android.nfc.Tag +import android.nfc.tech.IsoDep +import android.util.Log +import de.governikus.ausweisapp2.sdkwrapper.SDKWrapper +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getAccessRights +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getApiLevel +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getAuthResult +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getCertificateDescription +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getReaderFromReaderMember +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getReaderFromRoot +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getReaderList +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getVersionInfo +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.getWorkflowProgress +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Accept +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Cancel +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Command +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetAccessRights +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetApiLevel +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetCertificate +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetInfo +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetReader +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetReaderList +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.GetStatus +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Message +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_ACCESS_RIGHTS +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_API_LEVEL +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_AUTH +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_BAD_STATE +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_CERTIFICATE +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_CHANGE_PIN +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_ENTER_CAN +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_ENTER_NEW_PIN +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_ENTER_PIN +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_ENTER_PUK +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_INFO +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_INSERT_CARD +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_INTERNAL_ERROR +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_INVALID +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_READER +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_READER_LIST +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_STATUS +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Messages.MSG_UNKNOWN_COMMAND +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.RunAuth +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.RunChangePin +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetAccessRights +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetApiLevel +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetCan +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetCard +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetNewPin +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetPin +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetPuk +import de.governikus.ausweisapp2.sdkwrapper.card.core.util.workflowSimulatorToCommandSimulator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * [WorkflowController] is used to control the authentication and pin change workflow + */ +class WorkflowController internal constructor(private val sdkConnection: SdkConnection) { + + internal interface SdkConnection { + val isConnected: Boolean + fun bind( + context: Context, + onConnected: (() -> Unit)? = null, + onConnectionFailed: (() -> Unit)? = null, + onMessageReceived: ((message: Message) -> Unit)? = null + ) + fun unbind() + fun updateNfcTag(tag: Tag): Boolean + fun send(command: T): Boolean + } + + private val workflowCallbacks = ArrayList() + + /** + * Indicates that the [WorkflowController] is ready to be used. + * When the [WorkflowController] is not in started state, other api calls will fail. + */ + val isStarted: Boolean + get() = sdkConnection.isConnected + + /** + * Initialize the [WorkflowController]. + * + * Before it is possible to use the [WorkflowController] it needs to be initialized. + * Make sure to call this function and wait for the [WorkflowCallbacks.onStarted] callback before using it. + * + * @param context Context + */ + fun start(context: Context) { + if (isStarted) { + Log.d(TAG, "WorkflowController already started") + return + } + + sdkConnection.bind( + context, + onConnected = { + callback { onStarted() } + }, + onConnectionFailed = { + val error = WrapperError("WorkflowController::start", "Connection failed") + callback { onWrapperError(error) } + }, + onMessageReceived = { messageJson -> + handleMessage(messageJson) + } + ) + } + + /** + * Stop the [WorkflowController]. + * + * When you no longer need the [WorkflowController] make sure to stop it to free up some + * resources. + */ + fun stop() { + if (!isStarted) { + Log.d(TAG, "WorkflowController not started") + return + } + + sdkConnection.unbind() + } + + /** + * Register callbacks with controller. + * + * @param callbacks Callbacks to register. + */ + fun registerCallbacks(callbacks: WorkflowCallbacks) { + workflowCallbacks.add(callbacks) + } + + /** + * Unregister callback from controller. + * + * @param callbacks Callbacks to unregister. + */ + fun unregisterCallbacks(callbacks: WorkflowCallbacks) { + workflowCallbacks.remove(callbacks) + } + + /** + * Starts an authentication workflow. + * + * The [WorkflowController] will call [WorkflowCallbacks.onAuthenticationStarted], + * when the authentication is started. If there was an error starting the authentication + * [WorkflowCallbacks.onAuthenticationStartFailed]. + * + * After calling this method, the expected minimal workflow is: + * [WorkflowCallbacks.onAuthenticationStarted] is called. + * When [WorkflowCallbacks.onAccessRights] is called, accept it via [accept]. + * [WorkflowCallbacks.onInsertCard] is called, when the user has not yet placed the phone on the card. + * When [WorkflowCallbacks.onEnterPin] is called, provide the pin via [setPin]. + * When the authentication workflow is finished [WorkflowCallbacks.onAuthenticationCompleted] is called. + * + * This command is allowed only if the SDK has no running workflow. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param tcTokenUrl URL to the TcToken. + * + * @param developerMode Enable "Developer Mode" for test cards and disable some security + * checks according to BSI TR-03124-1. + * + * @param status True to enable automatic STATUS messages, which are delivered by + * callbacks to [WorkflowCallbacks.onStatus]. + */ + fun startAuthentication(tcTokenUrl: Uri, developerMode: Boolean = false, status: Boolean = true) { + send(RunAuth(tcTokenUrl.toString(), developerMode, status)) + } + + /** + * Start a pin change workflow. + * + * The [WorkflowController] will call [WorkflowCallbacks.onChangePinStarted], + * when the pin change is started. + * + * After calling this method, the expected minimal workflow is: + * [WorkflowCallbacks.onChangePinStarted] is called. + * [WorkflowCallbacks.onInsertCard] is called, when the user has not yet placed the card on the reader. + * When [WorkflowCallbacks.onEnterPin] is called, provide the pin via [setPin]. + * When [WorkflowCallbacks.onEnterNewPin] is called, provide the new pin via [setNewPin]. + * When the pin workflow is finished, [WorkflowCallbacks.onChangePinCompleted] is called. + * + * This command is allowed only if the SDK has no running workflow. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param status True to enable automatic STATUS messages, which are delivered by + * callbacks to [WorkflowCallbacks.onStatus]. + */ + fun startChangePin(status: Boolean = true) { + send(RunChangePin(status)) + } + + /** + * Set optional access rights + * + * If the SDK asks for specific access rights in [WorkflowCallbacks.onAccessRights], + * you can modify the requested optional rights by setting a list of accepted optional rights here. + * When the command is successful you get a callback to [WorkflowCallbacks.onAccessRights] + * with the updated access rights. + * + * List of possible access rights are listed in [AccessRight] + * + * This command is allowed only if the SDK asked for a pin via [WorkflowCallbacks.onAccessRights]. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param accessRights List of enabled optional access rights. If the list is empty all optional access rights are disabled. + */ + fun setAccessRights(accessRights: List) { + send(SetAccessRights(accessRights.map { it.rawName })) + } + + /** + * Returns information about the requested access rights. + * This command is allowed only if the SDK Wrapper called [WorkflowCallbacks.onAccessRights] beforehand. + */ + fun getAccessRights() { + send(GetAccessRights()) + } + + /** + * Set supported API level of your application. + * + * If you initially develop your application against the SDK Wrapper you should check + * the highest supported level with getApiLevel() and set this value with this command + * when you connect to the SDK Wrapper. + * This will set the SDK Wrapper to act with the defined level even if a newer level is available. + * The SDK Wrapper will call [WorkflowCallbacks.onApiLevel] as an answer. + * + * @param level Supported API level of your app. + */ + fun setApiLevel(level: Int) { + send(SetApiLevel(level)) + } + + /** + * Returns information about the available and current API level. + * + * The SDK will call [WorkflowCallbacks.onApiLevel] as an answer. + */ + fun getApiLevel() { + send(GetApiLevel()) + } + + /** + * Provides information about the utilized AusweisApp2. + * + * The SDK Wrapper will call [WorkflowCallbacks.onInfo] as an answer. + */ + fun getInfo() { + send(GetInfo()) + } + + /** + * Dummy match for SDK Wrapper iOS method, does not actually do anything at the moment. + */ + fun interrupt() { + } + + /** + * Set PIN of inserted card. + * + * If the SDK calls [WorkflowCallbacks.onEnterPin] you need to call this function to unblock the card with the PIN. + * + * If your application provides an invalid PIN the SDK will call [WorkflowCallbacks.onEnterPin] + * again with a decreased retryCounter. + * + * If the value of retryCounter is 1 the SDK will initially call [WorkflowCallbacks.onEnterCan]. + * Once your application provides a correct CAN the SDK will call [WorkflowCallbacks.onEnterPin] + * again with a retryCounter of 1. + * If the value of retryCounter is 0 the SDK will initially call [WorkflowCallbacks.onEnterPuk]. + * Once your application provides a correct PUK the SDK will call [WorkflowCallbacks.onEnterPin] + * again with a retryCounter of 3. + * + * This command is allowed only if the SDK asked for a pin via [WorkflowCallbacks.onEnterPin]. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param pin The personal identification number (PIN) of the card. Must contain 5 (Transport PIN) or 6 digits. + */ + fun setPin(pin: String?) { + send(SetPin(pin)) + } + + /** + * Set new PIN for inserted card. + * + * If the SDK calls [WorkflowCallbacks.onEnterNewPin] you need to call this function to provide a new pin. + * + * This command is allowed only if the SDK asked for a new pin via [WorkflowCallbacks.onEnterNewPin]. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param newPin The new personal identification number (PIN) of the card. Must only contain 6 digits. + * Must be null if the current reader has a keypad. See [Reader]. + */ + fun setNewPin(newPin: String?) { + send(SetNewPin(newPin)) + } + + /** + * Set PUK of inserted card. + * + * If the SDK calls [WorkflowCallbacks.onEnterPuk] you need to call this function to unblock [setPin]. + * + * The workflow will automatically continue if the PUK was correct and the SDK will call [WorkflowCallbacks.onEnterPin]. + * If the correct PUK is entered the retryCounter will be set to 3. + * + * If your application provides an invalid PUK the SDK will call [WorkflowCallbacks.onEnterPuk] again. + * + * If the SDK calls [WorkflowCallbacks.onEnterPuk] with [Card.inoperative] set true it is not possible to unblock the PIN. + * You will have to show a message to the user that the card is inoperative and the user should + * contact the authority responsible for issuing the identification card to unblock the PIN. + * + * This command is allowed only if the SDK asked for a puk via [WorkflowCallbacks.onEnterPuk]. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param puk The personal unblocking key (PUK) of the card. Must only contain 10 digits. + * Must be null if the current reader has a keypad. See [Reader]. + */ + fun setPuk(puk: String?) { + send(SetPuk(puk)) + } + + /** + * Set CAN of inserted card. + * + * If the SDK calls [WorkflowCallbacks.onEnterCan] you need to call this function to unblock the last retry of [setPin]. + * + * The CAN is required to enable the last attempt of PIN input if the retryCounter is 1. + * The workflow continues automatically with the correct CAN and the SDK will call [WorkflowCallbacks.onEnterPin]. + * Despite the correct CAN being entered, the retryCounter remains at 1. + * The CAN is also required, if the authentication terminal has an approved “CAN allowed right”. + * This allows the workflow to continue without an additional PIN. + * + * If your application provides an invalid CAN the SDK will call [WorkflowCallbacks.onEnterCan] again. + * + * This command is allowed only if the SDK asked for a puk via [WorkflowCallbacks.onEnterCan]. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * @param can The card access number (CAN) of the card. Must only contain 6 digits. + * Must be null if the current reader has a keypad. See [Reader]. + */ + fun setCan(can: String?) { + send(SetCan(can)) + } + + /** + * Insert “virtual” card. + * + * @param name Name of [Reader] with a [Card] that shall be used. + * @param simulator Specific data for [Simulator]. (optional) files: Content of card Filesystem. + */ + fun setCard(name: String, simulator: Simulator?) { + send(SetCard(name, workflowSimulatorToCommandSimulator(simulator))) + } + + /** + * Accept the current state. + * + * If the SDK calls [WorkflowCallbacks.onAccessRights] the user needs to accept or deny them. + * The workflow is paused until your application sends this command to accept the requested information. + * If the user does not accept the requested information your application needs to call [cancel] to abort the whole workflow. + * + * This command is allowed only if the SDK asked for access rights via [WorkflowCallbacks.onAccessRights]. + * Otherwise you will get a callback to [WorkflowCallbacks.onBadState]. + * + * Note: This accepts the requested access rights as well as the provider's certificate since it is not possible to accept + * one without the other. + */ + fun accept() { + send(Accept()) + } + + /** + * Cancel the running workflow. + * + * If your application sends this command the SDK will cancel the workflow. + * You can send this command in any state of a running workflow to abort it. + */ + fun cancel() { + send(Cancel()) + } + + /** + * Request the certificate of current authentication. + * + * The SDK will call [WorkflowCallbacks.onCertificate] as an answer. + */ + fun getCertificate() { + send(GetCertificate()) + } + + /** + * Request information about the current workflow and state of SDK. + * The SDK will call [WorkflowCallbacks.onStatus] as an answer. + */ + fun getStatus() { + send(GetStatus()) + } + + /** + * Returns information about the requested reader. + * + * If you explicitly want to ask for information of a known reader name you can request it with this command. + * The SDK Wrapper will call [WorkflowCallbacks.onReader] as an answer. + * + * @param name Name of the reader. + */ + fun getReader(name: String) { + send(GetReader(name)) + } + + /** + * Returns information about all connected readers. + * + * If you explicitly want to ask for information of all connected readers you can request it with this command. + * The SDK Wrapper will call [WorkflowCallbacks.onReaderList] as an answer. + */ + fun getReaderList() { + send(GetReaderList()) + } + + /** + * Pass a detected nfc tag to the [WorkflowController] + * + * Since only a foreground application can detect nfc tags, + * you need to pass them to the SDK for it to handle detected id cards. + * + * @see NfcForegroundDispatcher + * + * @param tag Detected id card. ISO-DEP (ISO 14443-4) NFC tag + */ + fun onNfcTagDetected(tag: Tag) { + require(tag.techList.contains(IsoDep::class.java.name)) { "NFC tag isn't a ISO-DEP (ISO 14443-4) NFC tag" } + + SDKWrapper.launch(Dispatchers.IO) { + if (isStarted) { + sdkConnection.updateNfcTag(tag) + } else { + callback { onWrapperError(WrapperError("WorkflowController::onNfcTagDetected: isStarted", "Not started")) } + } + } + } + + private inline fun send(command: T) = SDKWrapper.launch(Dispatchers.IO) { + if (isStarted) { + sdkConnection.send(command) + } else { + callback { onWrapperError(WrapperError("WorkflowController::send: isStarted", "Not started")) } + } + } + + private fun callback(callback: WorkflowCallbacks.() -> Unit) = + SDKWrapper.launch(Dispatchers.Main) { + workflowCallbacks.forEach { + callback(it) + } + } + + private fun handleMessage(message: Message) { + when (message.msg) { + MSG_API_LEVEL -> { + callback { onApiLevel(message.error, message.getApiLevel()) } + } + MSG_INFO -> { + when (val info = message.getVersionInfo()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Parsing error")) } + } + else -> { + callback { onInfo(info) } + } + } + } + MSG_AUTH -> { + if (message.error != null) { + callback { onAuthenticationStartFailed(message.error) } + } + + when (val authResult = message.getAuthResult()) { + null -> { + // Everything is fine, the SDK started the authentication workflow and sent an empty AUTH message. + callback { onAuthenticationStarted() } + } + else -> { + callback { onAuthenticationCompleted(authResult) } + } + } + } + MSG_ACCESS_RIGHTS -> { + when (val accessRights = message.getAccessRights()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Missing access rights")) } + } + else -> { + callback { onAccessRights(message.error, accessRights) } + } + } + } + MSG_BAD_STATE -> { + callback { onWrapperError(WrapperError(message.msg, message.error ?: "Unknown bad state")) } + } + MSG_CHANGE_PIN -> { + when (message.success) { + null -> { + callback { onChangePinStarted() } + } + else -> { + callback { onChangePinCompleted(ChangePinResult(message.success)) } + } + } + } + MSG_ENTER_PIN -> { + when (val reader = message.getReaderFromReaderMember()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Missing reader")) } + } + else -> { + callback { onEnterPin(message.error, reader) } + } + } + } + MSG_ENTER_NEW_PIN -> { + when (val reader = message.getReaderFromReaderMember()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Missing reader")) } + } + else -> { + callback { onEnterNewPin(message.error, reader) } + } + } + } + MSG_ENTER_PUK -> { + when (val reader = message.getReaderFromReaderMember()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Missing reader")) } + } + else -> { + callback { onEnterPuk(message.error, reader) } + } + } + } + MSG_ENTER_CAN -> { + when (val reader = message.getReaderFromReaderMember()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Missing reader")) } + } + else -> { + callback { onEnterCan(message.error, reader) } + } + } + } + MSG_INSERT_CARD -> { + callback { onInsertCard(null) } + } + MSG_CERTIFICATE -> { + when (val certificateDescription = message.getCertificateDescription()) { + null -> { + callback { onWrapperError(WrapperError(message.msg, "Missing certificateDescription")) } + } + else -> { + callback { onCertificate(certificateDescription) } + } + } + } + MSG_READER -> { + callback { onReader(message.getReaderFromRoot()) } + } + MSG_READER_LIST -> { + callback { onReaderList(message.getReaderList()) } + } + MSG_INVALID -> { + val error = message.error ?: "Unknown SDK Wrapper error" + callback { onWrapperError(WrapperError(message.msg, error)) } + } + MSG_UNKNOWN_COMMAND -> { + val error = message.error ?: "Unknown SDK Wrapper error" + callback { onWrapperError(WrapperError(message.msg, error)) } + } + MSG_INTERNAL_ERROR -> { + val errorMessage = message.error ?: "Unknown internal error" + callback { onInternalError(errorMessage) } + } + MSG_STATUS -> { + callback { onStatus(message.getWorkflowProgress()) } + } + else -> { + Log.d(TAG, "Received unknown message ${message.msg}") + } + } + } + + companion object { + private val TAG = WorkflowController::class.java.simpleName + + const val PIN_LENGTH = 6 + const val TRANSPORT_PIN_LENGTH = 5 + const val PUK_LENGTH = 10 + const val CAN_LENGTH = 6 + } +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowData.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowData.kt new file mode 100644 index 0000000..a34d7fa --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/WorkflowData.kt @@ -0,0 +1,293 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core + +import android.net.Uri +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.util.* + +/** + * Detailed description of the certificate. + * + * @property issuerName Name of the certificate issuer. + * @property issuerUrl URL of the certificate issuer. + * @property subjectName Name of the certificate subject. + * @property subjectUrl URL of the certificate subject. + * @property termsOfUsage Raw certificate information about the terms of usage. + * @property purpose Parsed purpose of the terms of usage. + * @property validity Certificate validity + */ +@Parcelize +data class CertificateDescription( + val issuerName: String, + val issuerUrl: Uri?, + val purpose: String, + val subjectName: String, + val subjectUrl: Uri?, + val termsOfUsage: String, + val validity: CertificateValidity +) : Parcelable + +/** + * Validity dates of the certificate in UTC. + * + * @property effectiveDate Certificate is valid since this date. + * @property expirationDate Certificate is invalid after this date. + */ +@Parcelize +data class CertificateValidity( + val effectiveDate: Date, + val expirationDate: Date +) : Parcelable + +/** + * Access rights requested by the provider. + * + * @property requiredRights These rights are mandatory and cannot be disabled. + * @property optionalRights These rights are optional and can be enabled or disabled + * @property effectiveRights Indicates the enabled access rights of optional and required. + * @property transactionInfo Optional transaction information. + * @property auxiliaryData Optional auxiliary data of the provider. + */ +@Parcelize +data class AccessRights( + val requiredRights: List, + val optionalRights: List, + val effectiveRights: List, + val transactionInfo: String?, + val auxiliaryData: AuxiliaryData? +) : Parcelable + +/** + * Auxiliary data of the provider. + * + * @property ageVerificationDate Optional required date of birth for AgeVerification. + * @property requiredAge Optional required age for AgeVerification. It is calculated by the SDK on the basis of ageVerificationDate and current date. + * @property validityDate Optional validity date. + * @property communityId Optional id of community. + */ +@Parcelize +data class AuxiliaryData( + val ageVerificationDate: Date?, + val requiredAge: Int?, + val validityDate: Date?, + val communityId: String? +) : Parcelable + +/** + * Provides information about inserted card. + * + * @property inoperative True if PUK is inoperative and cannot unblock PIN, otherwise false. This can be recognized if user enters a correct PUK only. It is not possible to read this data before a user tries to unblock the PIN. + * @property deactivated True if eID functionality is deactivated, otherwise false. + * @property pinRetryCounter Count of possible retries for the PIN. If you enter a PIN it will be decreased if PIN was incorrect. + */ +@Parcelize +data class Card( + val deactivated: Boolean, + val inoperative: Boolean, + val pinRetryCounter: Int +) : Parcelable + +/** + * Optional definition of files for the Simulator reader. + * + * @property files List of Filesystem definitions. See [SimulatorFile]. + */ +@Parcelize +data class Simulator( + val files: List +) : Parcelable + +/** + * Filesystem for Simulator reader + * + * The content of the filesystem can be provided as a JSON array of objects. The fileId and shortFileId + * are specified on the corresponding technical guideline of the BSI and ISO. The content is an + * ASN.1 structure in DER encoding. + * + * All fields are hex encoded. + * + * @property fileId The fileId and shortFileId are specified on the corresponding technical guideline of the BSI and ISO. + * @property shortFileId The fileId just shorter. + * @property content The content is an ASN.1 structure in DER encoding. + */ +@Parcelize +data class SimulatorFile( + val fileId: String, + val shortFileId: String, + val content: String +) : Parcelable + +/** + * Provides information about the API Level of the underlying AusweisApp2. + * + * @property available List of all available API levels of the employed AusweisApp2. + * @property current The currently set API level. + */ +@Parcelize +data class ApiLevel( + val available: List?, + val current: Int +) : Parcelable + +/** + * Final result of an authentication. + * + * @property url Refresh url or an optional communication error address. + * @property result Contains information about the result of the authentication. See [AuthResultData]. + */ +@Parcelize +data class AuthResult( + val url: Uri?, + val result: AuthResultData? +) : Parcelable + +/** + * Final result of a PIN change. + * + * @property success True if a the PIN has been successfully set, else false + */ + +@Parcelize +data class ChangePinResult( + val success: Boolean +) : Parcelable + +/** + * Information about an authentication + * + * @property major Major error code. + * @property minor Minor error code. + * @property language Language of description and message. Language “en” is supported only at the moment. + * @property description Description of the error message. + * @property message The error message. + * @property reason Unique error code. + */ +@Parcelize +data class AuthResultData( + val major: String, + val minor: String?, + val language: String?, + val description: String?, + val message: String?, + val reason: String? +) : Parcelable + +/** + * Provides information about a reader. + * + * @property name Identifier of card reader. + * @property insertable Indicates whether a card can be inserted via setCard(). + * @property attached Indicates whether a card reader is connected or disconnected. + * @property keypad Indicates whether a card reader has a keypad. + * @property card Provides information about inserted card, otherwise null. + */ +@Parcelize +data class Reader( + val name: String, + val insertable: Boolean, + val attached: Boolean, + val keypad: Boolean, + val card: Card? +) : Parcelable + +/** + * Provides information about the underlying AusweisApp2. + * + * @property name Application name. + * @property implementationTitle Title of implementation. + * @property implementationVendor Vendor of implementation. + * @property implementationVersion Version of implementation. + * @property specificationTitle Títle of specification. + * @property specificationVendor Vendor of specification. + * @property specificationVersion Version of specification. + * + */ +@Parcelize +data class VersionInfo( + val name: String, + val implementationTitle: String, + val implementationVendor: String, + val implementationVersion: String, + val specificationTitle: String, + val specificationVendor: String, + val specificationVersion: String +) : Parcelable + +/** + * Provides information about an error. + * + * @property msg Message type in which the error occurred. + * @property error Error message. + */ +@Parcelize +data class WrapperError( + val msg: String, + val error: String +) : Parcelable + +/** + * Provides information about the workflow status + * + * @param workflow Type of the current workflow. If there is no workflow in progress this will + * be null. + * @param progress Percentage of workflow progress. If there is no workflow in progress this + * will be null. + * @param state Name of the current state if paused. If there is no workflow in progress or the + * workflow is not paused this will be null. + */ +@Parcelize +data class WorkflowProgress( + val workflow: WorkflowProgressType?, + val progress: Int?, + val state: String? +) : Parcelable + +/** + * List of all available access rights a provider might request. + */ +enum class AccessRight(val rawName: String) { + ADDRESS("Address"), + BIRTH_NAME("BirthName"), + FAMILY_NAME("FamilyName"), + GIVEN_NAMES("GivenNames"), + PLACE_OF_BIRTH("PlaceOfBirth"), + DATE_OF_BIRTH("DateOfBirth"), + DOCTORAL_DEGREE("DoctoralDegree"), + ARTISTIC_NAME("ArtisticName"), + PSEUDONYM("Pseudonym"), + VALID_UNTIL("ValidUntil"), + NATIONALITY("Nationality"), + ISSUING_COUNTRY("IssuingCountry"), + DOCUMENT_TYPE("DocumentType"), + RESIDENCE_PERMIT_I("ResidencePermitI"), + RESIDENCE_PERMIT_II("ResidencePermitII"), + COMMUNITY_ID("CommunityID"), + ADDRESS_VERIFICATION("AddressVerification"), + AGE_VERIFICATION("AgeVerification"), + WRITE_ADDRESS("WriteAddress"), + WRITE_COMMUNITY_ID("WriteCommunityID"), + WRITE_RESIDENCE_PERMIT_I("WriteResidencePermitI"), + WRITE_RESIDENCE_PERMIT_II("WriteResidencePermitII"), + CAN_ALLOWED("CanAllowed"), + PIN_MANAGEMENT("PinManagement"); + + companion object { + fun fromRawName(rawName: String) = values().firstOrNull { it.rawName == rawName } + } +} + +/** + * List of all types of WorkflowProgress + */ +enum class WorkflowProgressType(val rawName: String) { + AUTHENTICATION("AUTH"), + CHANGE_PIN("CHANGE_PIN"); + + companion object { + fun fromRawName(rawName: String?) = values().firstOrNull { it.rawName == rawName } + } +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/AA2SdkConnection.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/AA2SdkConnection.kt new file mode 100644 index 0000000..11a11fa --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/AA2SdkConnection.kt @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2 + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.nfc.Tag +import android.os.IBinder +import android.os.RemoteException +import android.util.Log +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import com.governikus.ausweisapp2.IAusweisApp2Sdk +import com.governikus.ausweisapp2.IAusweisApp2SdkCallback +import de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowController +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Command +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Message + +internal class AA2SdkConnection : WorkflowController.SdkConnection { + + private var context: Context? = null + private var sdkConnection: ServiceConnection? = null + private var sdk: IAusweisApp2Sdk? = null + private var sdkSessionId: String? = null + + private val gson = Gson() + + override val isConnected: Boolean + get() = sdk != null + + override fun bind( + context: Context, + onConnected: (() -> Unit)?, + onConnectionFailed: (() -> Unit)?, + onMessageReceived: ((message: Message) -> Unit)? + ) { + this.context = context.applicationContext + val serviceIntent = Intent("com.governikus.ausweisapp2.START_SERVICE") + serviceIntent.setPackage(context.applicationContext.packageName) + + val sdkCallback = object : IAusweisApp2SdkCallback.Stub() { + override fun sessionIdGenerated(sessionId: String, isSecureSessionId: Boolean) { + this@AA2SdkConnection.sdkSessionId = sessionId + } + + override fun receive(messageJson: String) { + Log.d(TAG, "Received message: $messageJson") + + try { + val message = gson.fromJson(messageJson, Message::class.java) + onMessageReceived?.invoke(message) + } catch (e: JsonSyntaxException) { + Log.e(TAG, "Could not parse json message", e) + } + } + + override fun sdkDisconnected() { + sdkSessionId = null + } + } + + sdkConnection = object : ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + try { + sdk = IAusweisApp2Sdk.Stub.asInterface(service) + sdk?.connectSdk(sdkCallback) + onConnected?.invoke() + } catch (e: RemoteException) { + Log.d(TAG, "Could not connect to ausweisapp2 sdk", e) + onConnectionFailed?.invoke() + } + } + + override fun onServiceDisconnected(className: ComponentName) { + Log.d(TAG, "Service disconnected") + sdk = null + sdkSessionId = null + } + }.apply { + try { + context.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE) + } catch (e: SecurityException) { + Log.d(TAG, "Could not bind service", e) + onConnectionFailed?.invoke() + } + } + } + + override fun unbind() { + sdkConnection?.apply { + context?.unbindService(this) + } + context = null + sdkConnection = null + sdk = null + sdkSessionId = null + } + + override fun send(command: T): Boolean { + val sdk = sdk ?: return false + val sessionId = sdkSessionId ?: return false + + return try { + val messageJson = gson.toJson(command) + Log.d(TAG, "Send message: $messageJson") + sdk.send(sessionId, messageJson) + } catch (e: RemoteException) { + Log.d(TAG, "Could not send command", e) + false + } + } + + override fun updateNfcTag(tag: Tag): Boolean { + val sdk = sdk ?: return false + val sessionId = sdkSessionId ?: return false + + return try { + sdk.updateNfcTag(sessionId, tag) + true + } catch (e: RemoteException) { + Log.d(TAG, "Could not update nfc tag", e) + false + } + } + + companion object { + private val TAG = ServiceConnection::class.java.simpleName + } +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/MessageExtension.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/MessageExtension.kt new file mode 100644 index 0000000..b4c1898 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/MessageExtension.kt @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2 + +import android.net.Uri +import de.governikus.ausweisapp2.sdkwrapper.card.core.AccessRight +import de.governikus.ausweisapp2.sdkwrapper.card.core.AccessRights +import de.governikus.ausweisapp2.sdkwrapper.card.core.ApiLevel +import de.governikus.ausweisapp2.sdkwrapper.card.core.AuthResult +import de.governikus.ausweisapp2.sdkwrapper.card.core.AuthResultData +import de.governikus.ausweisapp2.sdkwrapper.card.core.AuxiliaryData +import de.governikus.ausweisapp2.sdkwrapper.card.core.Card +import de.governikus.ausweisapp2.sdkwrapper.card.core.CertificateDescription +import de.governikus.ausweisapp2.sdkwrapper.card.core.CertificateValidity +import de.governikus.ausweisapp2.sdkwrapper.card.core.Reader +import de.governikus.ausweisapp2.sdkwrapper.card.core.VersionInfo +import de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowProgress +import de.governikus.ausweisapp2.sdkwrapper.card.core.WorkflowProgressType +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Message +import java.text.SimpleDateFormat +import java.util.Locale + +private val dateFormat: SimpleDateFormat + get() = SimpleDateFormat("yyyy-MM-dd", Locale.GERMANY) + +internal fun Message.getCertificateDescription(): CertificateDescription? { + val description = description ?: return null + val validity = validity ?: return null + val issueDate = dateFormat.parse(validity.effectiveDate) ?: return null + val expirationDate = dateFormat.parse(validity.expirationDate) ?: return null + + val issuerUrl = if (description.issuerUrl.isNotBlank()) Uri.parse(description.issuerUrl) else null + val subjectUrl = if (description.subjectUrl.isNotBlank()) Uri.parse(description.subjectUrl) else null + + return CertificateDescription( + description.issuerName, + issuerUrl, + description.purpose, + description.subjectName, + subjectUrl, + description.termsOfUsage, + CertificateValidity( + issueDate, + expirationDate + ) + ) +} + +internal fun Message.getCard(): Card? { + val card = card ?: reader?.card ?: return null + + return Card( + card.deactivated, + card.inoperative, + card.retryCounter + ) +} + +internal fun Message.getApiLevel(): ApiLevel? { + val current = current ?: return null + + return ApiLevel( + available, + current + ) +} + +internal fun Message.getReaderFromRoot(): Reader? { + val name = name ?: return null + val insertable = insertable ?: return null + val attached = attached ?: return null + val keypad = keypad ?: return null + + return Reader( + name, + insertable, + attached, + keypad, + getCard() + ) +} + +internal fun Message.getReaderFromReaderMember(): Reader? { + val reader = reader ?: return null + val name = reader.name + val insertable = reader.insertable + val keypad = reader.keypad + val attached = reader.attached + + return Reader( + name, + insertable, + attached, + keypad, + getCard() + ) +} + +internal fun Message.getReaderList(): List? { + val readers = readers ?: return null + + return readers.map { + val card = if (it.card == null) { + null + } else { + Card( + it.card.deactivated, + it.card.inoperative, + it.card.retryCounter + ) + } + Reader( + it.name, + it.insertable, + it.attached, + it.keypad, + card + ) + } +} + +internal fun Message.getVersionInfo(): VersionInfo? { + val info = versionInfo ?: return null + + return VersionInfo( + info.name, + info.implementationTitle, + info.implementationVendor, + info.implementationVersion, + info.specificationTitle, + info.specificationVendor, + info.specificationVersion + ) +} + +internal fun Message.getAccessRights(): AccessRights? { + val chat = chat ?: return null + + val auxiliaryData = aux?.run { + AuxiliaryData( + if (ageVerificationDate != null) dateFormat.parse(ageVerificationDate) else null, + requiredAge?.toInt(), + if (validityDate != null) dateFormat.parse(validityDate) else null, + communityId + ) + } + + val requiredRights = chat.required.mapNotNull { AccessRight.fromRawName(it) } + val optionalRights = chat.optional.mapNotNull { AccessRight.fromRawName(it) } + val effectiveRights = chat.effective.mapNotNull { AccessRight.fromRawName(it) } + + return AccessRights( + requiredRights, + optionalRights, + effectiveRights, + transactionInfo, + auxiliaryData + ) +} + +internal fun Message.getAuthResult(): AuthResult? { + val uri = if (url != null) Uri.parse(url) else null + val resultData = getAuthResultData() + + if (resultData != null || uri != null) { + return AuthResult(uri, resultData) + } + + return null +} + +internal fun Message.getAuthResultData(): AuthResultData? { + val result = result ?: return null + if (result.major == null) return null + + return AuthResultData( + result.major, + result.minor, + result.language, + result.description, + result.message, + result.reason + ) +} + +internal fun Message.getWorkflowProgress(): WorkflowProgress { + val workflowType = WorkflowProgressType.fromRawName(workflow) + return WorkflowProgress(workflowType, progress, state) +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/CommandData.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/CommandData.kt new file mode 100644 index 0000000..6dc17aa --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/CommandData.kt @@ -0,0 +1,11 @@ +package de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol + +internal data class Simulator( + val files: List +) + +internal data class SimulatorFile( + val fileId: String, + val shortFileId: String, + val content: String +) diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/Commands.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/Commands.kt new file mode 100644 index 0000000..344863a --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/Commands.kt @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol + +internal interface Command { + val cmd: String +} + +internal class Accept : Command { + override val cmd = "ACCEPT" +} + +internal class Cancel : Command { + override val cmd = "CANCEL" +} + +internal class SetApiLevel(val level: Int) : Command { + override val cmd = "SET_API_LEVEL" +} + +internal class GetApiLevel : Command { + override val cmd = "GET_API_LEVEL" +} + +internal class GetCertificate : Command { + override val cmd = "GET_CERTIFICATE" +} + +internal class RunAuth(val tcTokenURL: String, val developerMode: Boolean, val status: Boolean) : Command { + override val cmd = "RUN_AUTH" +} + +internal class RunChangePin(val status: Boolean) : Command { + override val cmd = "RUN_CHANGE_PIN" +} + +internal class GetAccessRights : Command { + override val cmd = "GET_ACCESS_RIGHTS" +} + +internal class SetAccessRights(val chat: List) : Command { + override val cmd = "SET_ACCESS_RIGHTS" +} + +internal class SetCan(val value: String?) : Command { + override val cmd = "SET_CAN" +} + +internal class SetCard(val name: String, val simulator: Simulator?) : Command { + override val cmd = "SET_CARD" +} + +internal class SetPin(val value: String?) : Command { + override val cmd = "SET_PIN" +} + +internal class SetNewPin(val value: String?) : Command { + override val cmd = "SET_NEW_PIN" +} + +internal class SetPuk(val value: String?) : Command { + override val cmd = "SET_PUK" +} + +internal class GetStatus : Command { + override val cmd = "GET_STATUS" +} + +internal class GetInfo : Command { + override val cmd = "GET_INFO" +} + +internal class GetReader(val name: String) : Command { + override val cmd = "GET_READER" +} + +internal class GetReaderList : Command { + override val cmd = "GET_READER_LIST" +} diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/Messages.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/Messages.kt new file mode 100644 index 0000000..9bdf024 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/ausweisapp2/protocol/Messages.kt @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol + +import com.google.gson.annotations.SerializedName + +internal object Messages { + const val MSG_ACCESS_RIGHTS = "ACCESS_RIGHTS" + const val MSG_API_LEVEL = "API_LEVEL" + const val MSG_AUTH = "AUTH" + const val MSG_BAD_STATE = "BAD_STATE" + const val MSG_CERTIFICATE = "CERTIFICATE" + const val MSG_CHANGE_PIN = "CHANGE_PIN" + const val MSG_ENTER_CAN = "ENTER_CAN" + const val MSG_ENTER_NEW_PIN = "ENTER_NEW_PIN" + const val MSG_INFO = "INFO" + const val MSG_ENTER_PIN = "ENTER_PIN" + const val MSG_ENTER_PUK = "ENTER_PUK" + const val MSG_INSERT_CARD = "INSERT_CARD" + const val MSG_INTERNAL_ERROR = "INTERNAL_ERROR" + const val MSG_INVALID = "INVALID" + const val MSG_READER = "READER" + const val MSG_READER_LIST = "READER_LIST" + const val MSG_STATUS = "STATUS" + const val MSG_UNKNOWN_COMMAND = "UNKNOWN_COMMAND" +} + +internal data class Message( + val attached: Boolean?, + val aux: Aux?, + val available: List?, + val card: Card?, + val chat: Chat?, + val current: Int?, + val description: Description?, + val error: String?, + val insertable: Boolean?, + val keypad: Boolean?, + val msg: String?, + val name: String?, + val progress: Int?, + val reader: Reader?, + val readers: List?, + val reason: String?, + val result: Result?, + val state: String?, + val success: Boolean?, + val transactionInfo: String?, + val url: String?, + val validity: Validity?, + @SerializedName("VersionInfo") + val versionInfo: VersionInfo?, + val workflow: String? +) + +internal data class Description( + val issuerName: String, + val issuerUrl: String, + val purpose: String, + val subjectName: String, + val subjectUrl: String, + val termsOfUsage: String +) + +internal data class Validity( + val effectiveDate: String, + val expirationDate: String +) + +internal data class Chat( + val effective: List, + val optional: List, + val required: List +) + +internal data class Aux( + val ageVerificationDate: String?, + val requiredAge: String?, + val validityDate: String?, + val communityId: String? +) + +internal data class Card( + val inoperative: Boolean, + val deactivated: Boolean, + val retryCounter: Int +) + +internal data class Reader( + val name: String, + val insertable: Boolean, + val attached: Boolean, + val keypad: Boolean, + val card: Card? +) + +internal data class Result( + val major: String?, + val minor: String?, + val language: String?, + val description: String?, + val message: String?, + val reason: String? +) + +internal data class VersionInfo( + @SerializedName("Name") + val name: String, + @SerializedName("Implementation-Title") + val implementationTitle: String, + @SerializedName("Implementation-Vendor") + val implementationVendor: String, + @SerializedName("Implementation-Version") + val implementationVersion: String, + @SerializedName("Specification-Title") + val specificationTitle: String, + @SerializedName("Specification-Vendor") + val specificationVendor: String, + @SerializedName("Specification-Version") + val specificationVersion: String +) diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/util/StringUtil.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/util/StringUtil.kt new file mode 100644 index 0000000..63dff41 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/util/StringUtil.kt @@ -0,0 +1,9 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core.util + +internal val NUMBER_REGEX = Regex("[0-9]+") + +fun String.isNumber() = matches(NUMBER_REGEX) diff --git a/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/util/WorkflowDataToCommandDataConversion.kt b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/util/WorkflowDataToCommandDataConversion.kt new file mode 100644 index 0000000..a33ee93 --- /dev/null +++ b/sdkwrapper/src/main/java/de/governikus/ausweisapp2/sdkwrapper/card/core/util/WorkflowDataToCommandDataConversion.kt @@ -0,0 +1,22 @@ +package de.governikus.ausweisapp2.sdkwrapper.card.core.util + +internal typealias WorkflowSimulator = de.governikus.ausweisapp2.sdkwrapper.card.core.Simulator +internal typealias WorkflowSimulatorFile = de.governikus.ausweisapp2.sdkwrapper.card.core.SimulatorFile + +internal typealias CommandSimulator = de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Simulator +internal typealias CommandSimulatorFile = de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SimulatorFile + +internal fun workflowSimulatorToCommandSimulator(simulator: WorkflowSimulator?): CommandSimulator? { + simulator ?: return null + return CommandSimulator( + simulator.files.map { workflowSimulatorFileToCommandSimulatorFile(it) } + ) +} + +internal fun workflowSimulatorFileToCommandSimulatorFile(file: WorkflowSimulatorFile): CommandSimulatorFile { + return CommandSimulatorFile( + file.fileId, + file.shortFileId, + file.content + ) +} diff --git a/sdkwrapper/src/test/java/de/governikus/idloneos/card/core/WorkflowControllerTest.kt b/sdkwrapper/src/test/java/de/governikus/idloneos/card/core/WorkflowControllerTest.kt new file mode 100644 index 0000000..83186f3 --- /dev/null +++ b/sdkwrapper/src/test/java/de/governikus/idloneos/card/core/WorkflowControllerTest.kt @@ -0,0 +1,350 @@ +/** + * Copyright (c) 2020-2023 Governikus GmbH & Co. KG, Germany + */ + +package de.governikus.ausweisapp2.sdkwrapper.card.core + +import android.content.Context +import android.net.Uri +import android.nfc.Tag +import com.google.gson.Gson +import de.governikus.ausweisapp2.sdkwrapper.SDKWrapper +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Accept +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Command +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.Message +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.RunAuth +import de.governikus.ausweisapp2.sdkwrapper.card.core.ausweisapp2.protocol.SetPin +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, sdk = [24]) +@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) +class WorkflowControllerTest { + + private var workflowController: WorkflowController? = null + private var connection: MockSdkConnection? = null + + @OptIn(DelicateCoroutinesApi::class) + private val mainThreadSurrogate = newSingleThreadContext("UI thread") + + @Before + fun setUp() { + Dispatchers.setMain(mainThreadSurrogate) + connection = MockSdkConnection() + workflowController = WorkflowController(connection!!) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + mainThreadSurrogate.close() + workflowController = null + connection = null + } + + @Test + fun testOnStarted() = runTest(dispatchTimeoutMs = 1000) { + assertNotNull(workflowController) + val workflowController = workflowController!! + + val completed = suspendCoroutine { + workflowController.registerCallbacks( + object : TestWorkflowCallbacks() { + override fun onStarted() { + it.resume(true) + } + } + ) + + workflowController.start(RuntimeEnvironment.getApplication()) + } + assert(completed) + + assert(workflowController.isStarted) + } + + @Test + fun testErrorNotStarted() = runTest(dispatchTimeoutMs = 1000) { + assertNotNull(workflowController) + val workflowController = workflowController!! + + assertFalse(workflowController.isStarted) + + val completed = suspendCoroutine { + workflowController.registerCallbacks( + object : TestWorkflowCallbacks() { + override fun onWrapperError(error: WrapperError) { + it.resume(true) + } + } + ) + + workflowController.startAuthentication(Uri.parse("https://test.test")) + } + assert(completed) + } + + @Test + fun testAuthenticationStarted() = runTest(dispatchTimeoutMs = 1000) { + assertNotNull(workflowController) + assertNotNull(connection) + val workflowController = workflowController!! + val connection = connection!! + + val testUrl = Uri.parse("https://test.test") + + connection.onCommandSend = { + val command = it as? RunAuth + + assertNotNull(command) + assertEquals(testUrl.toString(), command?.tcTokenURL) + + connection.receive("{\"msg\":\"AUTH\"}") + } + + val completed = suspendCoroutine { + workflowController.registerCallbacks( + object : TestWorkflowCallbacks() { + override fun onAuthenticationStarted() { + it.resume(true) + } + } + ) + + workflowController.start(RuntimeEnvironment.getApplication()) + workflowController.startAuthentication(testUrl) + } + + assert(completed) + } + + @Test + fun testFullAuthentication() = runTest(dispatchTimeoutMs = 1000) { + assertNotNull(workflowController) + assertNotNull(connection) + val workflowController = workflowController!! + val connection = connection!! + + val tcTokenUrl = Uri.parse("https://test.test") + val testPin = "123456" + + connection.onCommandSend = { command -> + when (command) { + is RunAuth -> { + assertNotNull(command) + assertEquals(tcTokenUrl.toString(), command.tcTokenURL) + + connection.receive("{\"msg\":\"AUTH\"}") + } + is Accept -> { + connection.receive("{\"msg\":\"INSERT_CARD\"}") + } + is SetPin -> { + assertEquals(testPin, command.value) + + connection.receive( + "{" + + " \"msg\": \"AUTH\"," + + " \"result\":" + + " {" + + " \"major\": \"http://www.bsi.bund.de/ecard/api/1.1/resultmajor#ok\"" + + " }," + + " \"url\": \"https://test.governikus-eid.de/gov_autent/async?refID=_123456789\"" + + "}" + ) + } + else -> { + } + } + } + + val completed = suspendCoroutine { + workflowController.registerCallbacks( + object : TestWorkflowCallbacks() { + override fun onAuthenticationStarted() { + connection.receive( + "{" + + " \"msg\": \"ACCESS_RIGHTS\"," + + " \"aux\":" + + " {" + + " \"ageVerificationDate\": \"1999-07-20\"," + + " \"requiredAge\": \"18\"," + + " \"validityDate\": \"2017-07-20\"," + + " \"communityId\": \"02760400110000\"" + + " }," + + " \"chat\":" + + " {" + + " \"effective\": [\"Address\", \"FamilyName\", \"GivenNames\", \"AgeVerification\"]," + + " \"optional\": [\"GivenNames\", \"AgeVerification\"]," + + " \"required\": [\"Address\", \"FamilyName\"]" + + " }," + + " \"transactionInfo\": \"this is an example\"" + + "}" + ) + } + + override fun onAccessRights(error: String?, accessRights: AccessRights?) { + workflowController.accept() + } + + override fun onInsertCard(error: String?) { + connection.receive( + "{" + + " \"msg\": \"READER\"," + + " \"name\": \"NFC\"," + + " \"attached\": true," + + " \"insertable\": true," + + " \"keypad\": false," + + " \"card\":" + + " {" + + " \"inoperative\": false," + + " \"deactivated\": false," + + " \"retryCounter\": 3" + + " }" + + "}" + ) + } + + override fun onReader(reader: Reader?) { + val card = reader?.card + assertNotNull(card) + assertEquals(3, card?.pinRetryCounter) + assertEquals(false, card?.inoperative) + assertEquals(false, card?.deactivated) + + connection.receive( + "{" + + " \"msg\": \"ENTER_PIN\"," + + " \"reader\":" + + " {" + + " \"name\": \"NFC\"," + + " \"attached\": true," + + " \"insertable\": true," + + " \"keypad\": false," + + " \"card\":" + + " {" + + " \"inoperative\": false," + + " \"deactivated\": false," + + " \"retryCounter\": 3" + + " }" + + " }" + + "}" + ) + } + + override fun onEnterPin(error: String?, reader: Reader) { + val card = reader.card + if (card != null) { + assertEquals(3, card.pinRetryCounter) + assertEquals(false, card.inoperative) + assertEquals(false, card.deactivated) + + workflowController.setPin(testPin) + } else { + assert(false) + } + } + + override fun onAuthenticationCompleted(authResult: AuthResult) { + assertNotNull(authResult.result) + assertNotNull(authResult.url) + assertEquals("http://www.bsi.bund.de/ecard/api/1.1/resultmajor#ok", authResult.result?.major) + + it.resume(true) + } + } + ) + + workflowController.start(RuntimeEnvironment.getApplication()) + workflowController.startAuthentication(tcTokenUrl) + } + + assert(completed) + } +} + +internal class MockSdkConnection : WorkflowController.SdkConnection { + + private var onMessageReceived: ((message: Message) -> Unit)? = null + + var onCommandSend: ((command: Command) -> Unit)? = null + + override var isConnected: Boolean = false + private set + + override fun bind( + context: Context, + onConnected: (() -> Unit)?, + onConnectionFailed: (() -> Unit)?, + onMessageReceived: ((message: Message) -> Unit)? + ) { + this.onMessageReceived = onMessageReceived + isConnected = true + onConnected?.invoke() + } + + override fun unbind() { + onMessageReceived = null + isConnected = false + } + + override fun updateNfcTag(tag: Tag): Boolean { + return false + } + + override fun send(command: T): Boolean { + SDKWrapper.launch { + onCommandSend?.invoke(command) + } + return isConnected + } + + fun receive(messageJson: String) { + SDKWrapper.launch { + val message = Gson().fromJson(messageJson, Message::class.java) + onMessageReceived?.invoke(message) + } + } +} + +internal open class TestWorkflowCallbacks : WorkflowCallbacks { + override fun onStarted() {} + override fun onAuthenticationStarted() {} + override fun onAuthenticationStartFailed(error: String) {} + override fun onChangePinStarted() {} + override fun onAccessRights(error: String?, accessRights: AccessRights?) {} + override fun onCertificate(certificateDescription: CertificateDescription) {} + override fun onInsertCard(error: String?) {} + override fun onReader(reader: Reader?) {} + override fun onReaderList(readers: List?) {} + override fun onEnterPin(error: String?, reader: Reader) {} + override fun onEnterNewPin(error: String?, reader: Reader) {} + override fun onEnterPuk(error: String?, reader: Reader) {} + override fun onEnterCan(error: String?, reader: Reader) {} + override fun onAuthenticationCompleted(authResult: AuthResult) {} + override fun onChangePinCompleted(changePinResult: ChangePinResult) {} + override fun onWrapperError(error: WrapperError) {} + override fun onStatus(workflowProgress: WorkflowProgress) {} + override fun onApiLevel(error: String?, apiLevel: ApiLevel?) {} + override fun onInfo(versionInfo: VersionInfo) {} + override fun onBadState(error: String) {} + override fun onInternalError(error: String) {} +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c2d2624 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':tester', ':sdkwrapper' diff --git a/third_party_licenses/ApacheLicense.md b/third_party_licenses/ApacheLicense.md new file mode 100644 index 0000000..7e310bd --- /dev/null +++ b/third_party_licenses/ApacheLicense.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/third_party_licenses/EUPL.md b/third_party_licenses/EUPL.md new file mode 100644 index 0000000..b76bd59 --- /dev/null +++ b/third_party_licenses/EUPL.md @@ -0,0 +1,282 @@ + OPEN-SOURCE-LIZENZ FÜR DIE EUROPÄISCHE UNION v. 1.2 + EUPL © Europäische Union 2007, 2016 + + +Diese Open-Source-Lizenz für die Europäische Union (»EUPL«) gilt für Werke (im Sinne der nachfolgenden Begriffsbestimmung), +die unter EUPL-Bedingungen zur Verfügung gestellt werden. Das Werk darf nur in der durch diese Lizenz +gestatteten Form genutzt werden (insoweit eine solche Nutzung dem Urheber vorbehalten ist). + +Das Werk wird unter den Bedingungen dieser Lizenz zur Verfügung gestellt, wenn der Lizenzgeber (im Sinne der +nachfolgenden Begriffsbestimmung) den folgenden Hinweis unmittelbar hinter dem Urheberrechtshinweis dieses Werks +anbringt: + +Lizenziert unter der EUPL + +oder in einer anderen Form zum Ausdruck bringt, dass er es unter der EUPL lizenzieren möchte. + + + 1. Begriffsbestimmungen + +Für diese Lizenz gelten folgende Begriffsbestimmungen: + +- »Lizenz«: diese Lizenz. + +- »Originalwerk«: das Werk oder die Software, die vom Lizenzgeber unter dieser Lizenz verbreitet oder zugänglich + gemacht wird, und zwar als Quellcode und gegebenenfalls auch als ausführbarer Code. + +- »Bearbeitungen«: die Werke oder Software, die der Lizenznehmer auf der Grundlage des Originalwerks oder seiner + Bearbeitungen schaffen kann. In dieser Lizenz wird nicht festgelegt, wie umfangreich die Änderung oder wie stark die + Abhängigkeit vom Originalwerk für eine Einstufung als Bearbeitung sein muss; dies bestimmt sich nach dem + Urheberrecht, das in dem unter Artikel 15 aufgeführten Land anwendbar ist. + +- »Werk«: das Originalwerk oder seine Bearbeitungen. + +- »Quellcode«: diejenige Form des Werkes, die zur Auffassung durch den Menschen bestimmt ist und die am besten + geeignet ist, um vom Menschen verstanden und verändert zu werden. + +- »Ausführbarer Code«: die - üblicherweise - kompilierte Form des Werks, die von einem Computer als Programm + ausgeführt werden soll. + +- »Lizenzgeber«: die natürliche oder juristische Person, die das Werk unter der Lizenz verbreitet oder zugänglich + macht. + +- »Bearbeiter«: jede natürliche oder juristische Person, die das Werk unter der Lizenz verändert oder auf andere Weise + zur Schaffung einer Bearbeitung beiträgt. + +- »Lizenznehmer« (»Sie«): jede natürliche oder juristische Person, die das Werk unter den Lizenzbedingungen nutzt. + +- »Verbreitung« oder »Zugänglichmachung«: alle Formen von Verkauf, Überlassung, Verleih, Vermietung, Verbreitung, + Weitergabe, Übermittlung oder anderweitiger Online- oder Offline-Bereitstellung von Vervielfältigungen des Werks + oder Zugänglichmachung seiner wesentlichen Funktionen für dritte natürliche oder juristische Personen. + + + 2. Umfang der Lizenzrechte + +Der Lizenzgeber erteilt Ihnen hiermit für die Gültigkeitsdauer der am Originalwerk bestehenden Urheberrechte eine +weltweite, unentgeltliche, nicht ausschließliche, unterlizenzierbare Lizenz, die Sie berechtigt: + +- das Werk uneingeschränkt zu nutzen, + +- das Werk zu vervielfältigen, + +- das Werk zu verändern und Bearbeitungen auf der Grundlage des Werks zu schaffen, + +- das Werk öffentlich zugänglich zu machen, was das Recht einschließt, das Werk oder Vervielfältigungsstücke davon + öffentlich bereitzustellen oder wahrnehmbar zu machen oder das Werk, soweit möglich, öffentlich aufzuführen, + +- das Werk oder Vervielfältigungen davon zu verbreiten, + +- das Werk oder Vervielfältigungen davon zu vermieten oder zu verleihen, + +- das Werk oder Vervielfältigungen davon weiter zu lizenzieren. + +Für die Wahrnehmung dieser Rechte können beliebige, derzeit bekannte oder künftige Medien, Träger und Formate +verwendet werden, soweit das geltende Recht dem nicht entgegensteht. + +Für die Länder, in denen Urheberpersönlichkeitsrechte an dem Werk bestehen, verzichtet der Lizenzgeber im gesetzlich +zulässigen Umfang auf seine Urheberpersönlichkeitsrechte, um die Lizenzierung der oben aufgeführten +Verwertungsrechte wirksam durchführen zu können. + +Der Lizenzgeber erteilt dem Lizenznehmer ein nicht ausschließliches, unentgeltliches Nutzungsrecht an seinen Patenten, +sofern dies zur Ausübung der durch die Lizenz erteilten Nutzungsrechte am Werk notwendig ist. + + + 3. Zugänglichmachung des Quellcodes + + +Der Lizenzgeber kann das Werk entweder als Quellcode oder als ausführbaren Code zur Verfügung stellen. Stellt er es als +ausführbaren Code zur Verfügung, so stellt er darüber hinaus eine maschinenlesbare Kopie des Quellcodes für jedes von +ihm verbreitete Vervielfältigungsstück des Werks zur Verfügung, oder er verweist in einem Vermerk im Anschluss an den +dem Werk beigefügten Urheberrechtshinweis auf einen Speicherort, an dem problemlos und unentgeltlich auf den +Quellcode zugegriffen werden kann, solange der Lizenzgeber das Werk verbreitet oder zugänglich macht. + + + 4. Einschränkungen des Urheberrechts + + +Es ist nicht Zweck dieser Lizenz, Ausnahmen oder Schranken der ausschließlichen Rechte des Urhebers am Werk, die +dem Lizenznehmer zugutekommen, einzuschränken. Auch die Erschöpfung dieser Rechte bleibt von dieser Lizenz +unberührt. + + + 5. Pflichten des Lizenznehmers + + +Die Einräumung der oben genannten Rechte ist an mehrere Beschränkungen und Pflichten für den Lizenznehmer +gebunden: + +Urheberrechtshinweis, Lizenztext, Nennung des Bearbeiters: Der Lizenznehmer muss alle Urheberrechts-, Patent- +oder Markenrechtshinweise und alle Hinweise auf die Lizenz und den Haftungsausschluss unverändert lassen. Jedem von +ihm verbreiteten oder zugänglich gemachten Vervielfältigungsstück des Werks muss der Lizenznehmer diese Hinweise +sowie diese Lizenz beifügen. Der Lizenznehmer muss auf jedem abgeleiteten Werk deutlich darauf hinweisen, dass das +Werk geändert wurde, und das Datum der Bearbeitung angeben. + +»Copyleft«-Klausel: Der Lizenznehmer darf Vervielfältigungen des Originalwerks oder Bearbeitungen nur unter den +Bedingungen dieser EUPL oder einer neueren Version dieser Lizenz verbreiten oder zugänglich machen, außer wenn das +Originalwerk ausdrücklich nur unter dieser Lizenzversion - z. B. mit der Angabe »Nur EUPL V. 1.2« - verbreitet +werden darf. Der Lizenznehmer (der zum Lizenzgeber wird) darf für das Werk oder die Bearbeitung keine zusätzlichen +Bedingungen anbieten oder vorschreiben, die die Bedingungen dieser Lizenz verändern oder einschränken. + +Kompatibilitäts-Klausel: Wenn der Lizenznehmer Bearbeitungen, die auf dem Werk und einem anderen Werk, das +unter einer kompatiblen Lizenz lizenziert wurde, basieren, oder die Kopien dieser Bearbeitungen verbreitet oder +zugänglich macht, kann dies unter den Bedingungen dieser kompatiblen Lizenz erfolgen. Unter »kompatibler Lizenz« ist +eine im Anhang dieser Lizenz angeführte Lizenz zu verstehen. Sollten die Verpflichtungen des Lizenznehmers aus der +kompatiblen Lizenz mit denjenigen aus der vorliegenden Lizenz (EUPL) in Konflikt stehen, werden die Verpflichtungen +aus der kompatiblen Lizenz Vorrang haben. + +Bereitstellung des Quellcodes: Wenn der Lizenznehmer Vervielfältigungsstücke des Werks verbreitet oder zugänglich +macht, muss er eine maschinenlesbare Fassung des Quellcodes mitliefern oder einen Speicherort angeben, über den +problemlos und unentgeltlich so lange auf diesen Quellcode zugegriffen werden kann, wie der Lizenznehmer das Werk +verbreitet oder zugänglich macht. + +Rechtsschutz: Diese Lizenz erlaubt nicht die Benutzung von Kennzeichen, Marken oder geschützten Namensrechten des +Lizenzgebers, soweit dies nicht für die angemessene und übliche Beschreibung der Herkunft des Werks und der +inhaltlichen Wiedergabe des Urheberrechtshinweises erforderlich ist. + + + 6. Urheber und Bearbeiter + +Der ursprüngliche Lizenzgeber gewährleistet, dass er das Urheberrecht am Originalwerk innehat oder dieses an ihn +lizenziert wurde und dass er befugt ist, diese Lizenz zu erteilen. + +Jeder Bearbeiter gewährleistet, dass er das Urheberrecht an den von ihm vorgenommenen Änderungen des Werks besitzt +und befugt ist, diese Lizenz zu erteilen. + +Jedes Mal, wenn Sie die Lizenz annehmen, erteilen Ihnen der ursprüngliche Lizenzgeber und alle folgenden Bearbeiter +eine Befugnis zur Nutzung ihrer Beiträge zum Werk unter den Bedingungen dieser Lizenz. + + + 7. Gewährleistungsausschluss + +Die Arbeit an diesem Werk wird laufend fortgeführt; es wird durch unzählige Bearbeiter ständig verbessert. Das Werk ist +nicht vollendet und kann daher Fehler (»bugs«) enthalten, die dieser Art der Entwicklung inhärent sind. + +Aus den genannten Gründen wird das Werk unter dieser Lizenz »so, wie es ist« ohne jegliche Gewährleistung zur +Verfügung gestellt. Dies gilt unter anderem - aber nicht ausschließlich - für Marktreife, Verwendbarkeit für einen +bestimmten Zweck, Mängelfreiheit, Richtigkeit sowie Nichtverletzung von anderen Immaterialgüterrechten als dem +Urheberrecht (vgl. dazu Artikel 6 dieser Lizenz). + +Dieser Gewährleistungsausschluss ist wesentlicher Bestandteil der Lizenz und Bedingung für die Einräumung von +Rechten an dem Werk. + + + 8. Haftungsausschluss/Haftungsbeschränkung + +Außer in Fällen von Vorsatz oder der Verursachung von Personenschäden haftet der Lizenzgeber nicht für direkte oder +indirekte, materielle oder immaterielle Schäden irgendwelcher Art, die aus der Lizenz oder der Benutzung des Werks +folgen; dies gilt unter anderem, aber nicht ausschließlich, für Firmenwertverluste, Produktionsausfall, Computerausfall +oder Computerfehler, Datenverlust oder wirtschaftliche Schäden, und zwar auch dann, wenn der Lizenzgeber auf die +Möglichkeit solcher Schäden hingewiesen wurde. Unabhängig davon haftet der Lizenzgeber im Rahmen der gesetzlichen +Produkthaftung, soweit die entsprechenden Regelungen auf das Werk anwendbar sind. + + + 9. Zusatzvereinbarungen + +Wenn Sie das Werk verbreiten, können Sie Zusatzvereinbarungen schließen, in denen Verpflichtungen oder +Dienstleistungen festgelegt werden, die mit dieser Lizenz vereinbar sind. Sie dürfen Verpflichtungen indessen nur in +Ihrem eigenen Namen und auf Ihre eigene Verantwortung eingehen, nicht jedoch im Namen des ursprünglichen +Lizenzgebers oder eines anderen Bearbeiters, und nur, wenn Sie sich gegenüber allen Bearbeitern verpflichten, sie zu +entschädigen, zu verteidigen und von der Haftung freizustellen, falls aufgrund der von Ihnen eingegangenen +Gewährleistungsverpflichtung oder Haftungsübernahme Forderungen gegen sie geltend gemacht werden oder eine +Haftungsverpflichtung entsteht. + + + 10. Annahme der Lizenz + +Sie können den Bestimmungen dieser Lizenz zustimmen, indem Sie das Symbol »Lizenz annehmen« unter dem Fenster +mit dem Lizenztext anklicken oder indem Sie Ihre Zustimmung auf vergleichbare Weise in einer nach anwendbarem +Recht zulässigen Form geben. Das Anklicken des Symbols gilt als Anzeichen Ihrer eindeutigen und unwiderruflichen +Annahme der Lizenz und der darin enthaltenen Klauseln und Bedingungen. + +In gleicher Weise gilt als Zeichen der eindeutigen und unwiderruflichen Zustimmung die Ausübung eines Rechtes, das in +Artikel 2 dieser Lizenz angeführt ist, wie das Erstellen einer Bearbeitung oder die Verbreitung oder Zugänglichmachung +des Werks oder dessen Vervielfältigungen. + + + 11. Informationspflichten + +Wenn Sie das Werk verbreiten oder zugänglich machen (beispielsweise, indem Sie es zum Herunterladen von einer +Website anbieten), müssen Sie über den Vertriebskanal oder das benutzte Verbreitungsmedium der Öffentlichkeit +zumindest jene Informationen bereitstellen, die nach dem anwendbaren Recht bezüglich der Lizenzgeber, der Lizenz und +ihrer Zugänglichkeit, des Abschlusses des Lizenzvertrags sowie darüber, wie die Lizenz durch den Lizenznehmer +gespeichert und vervielfältigt werden kann, erforderlich sind. + + + 12. Beendigung der Lizenz + +Die Lizenz und die damit eingeräumten Rechte erlöschen automatisch, wenn der Lizenznehmer gegen die Lizenzbedingungen +verstößt. + +Ein solches Erlöschen der Lizenz führt nicht zum Erlöschen der Lizenzen von Personen, denen das Werk vom +Lizenznehmer unter dieser Lizenz zur Verfügung gestellt worden ist, solange diese Personen die Lizenzbedingungen +erfüllen. + + 13. Sonstiges + +Unbeschadet des Artikels 9 stellt die Lizenz die vollständige Vereinbarung der Parteien über das Werk dar. + +Sind einzelne Bestimmungen der Lizenz nach geltendem Recht nichtig oder unwirksam, so berührt dies nicht die +Wirksamkeit oder Durchsetzbarkeit der Lizenz an sich. Solche Bestimmungen werden vielmehr so ausgelegt oder +modifiziert, dass sie wirksam und durchsetzbar sind. + +Die Europäische Kommission kann weitere Sprachfassungen oder neue Versionen dieser Lizenz oder aktualisierte +Fassungen des Anhangs veröffentlichen, soweit dies notwendig und angemessen ist, ohne den Umfang der Lizenzrechte +zu verringern. Neue Versionen werden mit einer eindeutigen Versionsnummer veröffentlicht. + +Alle von der Europäischen Kommission anerkannten Sprachfassungen dieser Lizenz sind gleichwertig. Die Parteien +können sich auf die Sprachfassung ihrer Wahl berufen. + + + 14. Gerichtsstand + +Unbeschadet besonderer Vereinbarungen zwischen den Parteien gilt Folgendes: + +- Für alle Streitigkeiten über die Auslegung dieser Lizenz zwischen den Organen, Einrichtungen und sonstigen Stellen + der Europäischen Union als Lizenzgeber und einem Lizenznehmer ist der Gerichtshof der Europäischen Union + gemäß Artikel 272 des Vertrags über die Arbeitsweise der Europäischen Union zuständig; + +- Gerichtsstand für Streitigkeiten zwischen anderen Parteien über die Auslegung dieser Lizenz ist allein der Ort, an + dem der Lizenzgeber seinen Wohnsitz oder den wirtschaftlichen Mittelpunkt seiner Tätigkeit hat. + + + 15. Anwendbares Recht + +Unbeschadet besonderer Vereinbarungen zwischen den Parteien gilt Folgendes: + +- Diese Lizenz unterliegt dem Recht des Mitgliedstaats der Europäischen Union, in dem der Lizenzgeber seinen Sitz, + Wohnsitz oder eingetragenen Sitz hat; + +- diese Lizenz unterliegt dem belgischen Recht, wenn der Lizenzgeber keinen Sitz, Wohnsitz oder eingetragenen Sitz in + einem Mitgliedstaat der Europäischen Union hat. + + + Anlage + +»Kompatible Lizenzen« nach Artikel 5 der EUPL sind: + + - GNU General Public License (GPL) v. 2, v. 3 + + - GNU Affero General Public License (AGPL) v. 3 + + - Open Software License (OSL) v. 2.1, v. 3.0 + + - Eclipse Public License (EPL) v. 1.0 + + - CeCILL v. 2.0, v. 2.1 + + - Mozilla Public Licence (MPL) v. 2 + + - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 + + - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) für andere Werke als Software + + - European Union Public Licence (EUPL) v. 1.1, v. 1.2 + + - Québec Free and Open-Source Licence - Reciprocity (LiLiQ-R) oder Strong Reciprocity (LiLiQ-R+) + +- Die Europäische Kommission kann diesen Anhang aktualisieren, um neuere Fassungen der obigen Lizenzen + aufzunehmen, ohne hierfür eine neue Fassung der EUPL auszuarbeiten, solange diese Lizenzen die in Artikel 2 + gewährten Rechte gewährleisten und den erfassten Quellcode vor ausschließlicher Aneignung schützen. + +- Alle sonstigen Änderungen oder Ergänzungen dieses Anhangs bedürfen der Ausarbeitung einer neuen Version der + EUPL. diff --git a/third_party_licenses/LICENSES.md b/third_party_licenses/LICENSES.md new file mode 100644 index 0000000..d64c691 --- /dev/null +++ b/third_party_licenses/LICENSES.md @@ -0,0 +1,10 @@ +Apache License - ApacheLicense.md +================================= + +com.google.code.gson:gson:2.10.1 + + +OPEN-SOURCE-LIZENZ FÜR DIE EUROPÄISCHE UNION - EUPL.md +====================================================== + +com.governikus:ausweisapp:1.26.3