diff --git a/.gitignore b/.gitignore index 78eb422..f328e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ bin/ ### OpenAPI ### legalconsenthub/.api-client +legalconsenthub/.api-client-middleware diff --git a/.run/local-middleware-dummy.run.xml b/.run/local-middleware-dummy.run.xml new file mode 100644 index 0000000..e7605cf --- /dev/null +++ b/.run/local-middleware-dummy.run.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/.run/local-middleware.run.xml b/.run/local-middleware.run.xml new file mode 100644 index 0000000..c63a2f3 --- /dev/null +++ b/.run/local-middleware.run.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.run/local-h2.run.xml b/.run/local-server-backend-h2.run.xml similarity index 77% rename from .run/local-h2.run.xml rename to .run/local-server-backend-h2.run.xml index 672ebab..0a60a7a 100644 --- a/.run/local-h2.run.xml +++ b/.run/local-server-backend-h2.run.xml @@ -1,5 +1,5 @@ - + - + \ No newline at end of file diff --git a/legalconsenthub-middleware/DUMMY_MODE.md b/legalconsenthub-middleware/DUMMY_MODE.md new file mode 100644 index 0000000..c273c9e --- /dev/null +++ b/legalconsenthub-middleware/DUMMY_MODE.md @@ -0,0 +1,125 @@ +# Dummy Mode Configuration + +This document explains how to use the dummy/mock mode in the legalconsenthub-middleware application. + +## Overview + +Dummy mode allows you to test the middleware API endpoints without requiring a physical smart card connected to your system. Instead of interacting with real smart card hardware via OpenSC, the application returns predefined dummy data. + +## How to Enable Dummy Mode + +### Option 1: Using Spring Profiles + +Start the application with the `dummy` profile: + +```bash +# Using Gradle +./gradlew bootRun --args='--spring.profiles.active=dummy' + +# Using JAR +java -jar build/libs/legalconsenthub-middleware-*.jar --spring.profiles.active=dummy + +# Using environment variable +export SPRING_PROFILES_ACTIVE=dummy +./gradlew bootRun +``` + +### Option 2: Using IntelliJ IDEA Run Configuration + +A pre-configured run configuration named `local-middleware-dummy` is available in the `.run` directory. Simply: + +1. Open the project in IntelliJ IDEA +2. Select "local-middleware-dummy" from the run configurations dropdown +3. Click the run button + +### Option 3: Manual Configuration + +You can also manually set the dummy mode property: + +```bash +./gradlew bootRun --args='--dummy.mode.enabled=true' +``` + +## What Gets Mocked + +### Smart Card Information +- **Smart Card Info**: Always returns a successful response with dummy smart card information +- **Certificates**: Always returns a successful response with two dummy certificates + +### Signature Operations +- **Sign PDF Hash**: Always returns a successful dummy signature +- **Verify Signature**: Always returns a successful verification result + +## Dummy Data + +### Smart Card Info +```json +{ + "isPresent": true, + "label": "DUMMY Smart Card", + "serialNumber": "12345678", + "manufacturer": "Dummy Corp", + "model": "DummyCard 2024" +} +``` + +### Available Certificates +1. **Certificate 1**: + - ID: `01` + - Subject: `CN=John Doe, O=Example Company, L=Berlin, C=DE` + - Issuer: `CN=Dummy CA, O=Dummy Corp, C=DE` + +2. **Certificate 2**: + - ID: `02` + - Subject: `CN=Jane Smith, O=Test Organization, L=Munich, C=DE` + - Issuer: `CN=Test CA, O=Test Corp, C=DE` + +## Testing Signature Verification + +The dummy implementation always returns successful responses: + +- **All signatures**: Any signature verification request will return `isValid: true` +- **Consistent behavior**: All requests return successful responses for predictable testing +- **No validation**: The dummy mode doesn't perform actual signature validation + +## API Endpoints + +All original API endpoints remain the same when running in dummy mode: + +- `GET /smart-card/info` - Returns dummy smart card information +- `GET /smart-card/certificates` - Returns dummy certificates +- `POST /sign-pdf-hash` - Creates dummy signatures +- `POST /verify-signature` - Verifies dummy signatures + +## Development Benefits + +Using dummy mode provides several advantages during development: + +1. **No Hardware Dependency**: Test frontend functionality without smart card hardware +2. **Consistent Data**: Predictable responses make testing easier +3. **Fast Development**: No waiting for smart card operations or PIN entry +4. **Error Testing**: Easily test error scenarios by using invalid certificate IDs +5. **CI/CD Integration**: Run automated tests without smart card hardware + +## Switching Back to Real Mode + +To disable dummy mode and use real smart card operations: + +1. Remove the `dummy` profile from `SPRING_PROFILES_ACTIVE` +2. Ensure your smart card is connected and OpenSC is properly configured +3. Start the application normally + +```bash +# Normal mode (default) +./gradlew bootRun + +# Or explicitly disable dummy mode +./gradlew bootRun --args='--dummy.mode.enabled=false' +``` + +## Notes + +- In dummy mode, all responses are successful and verification details are prefixed with "DUMMY:" to clearly indicate mock responses +- The dummy controllers return hardcoded successful responses without any actual processing +- All dummy responses include realistic data structures that match the real API responses +- Perfect for frontend testing where you need predictable successful responses \ No newline at end of file diff --git a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/DummySignatureController.kt b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/DummySignatureController.kt new file mode 100644 index 0000000..2df4caf --- /dev/null +++ b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/DummySignatureController.kt @@ -0,0 +1,53 @@ +package com.betriebsratkanzlei.legalconsenthub_middleware.signature + +import com.betriebsratkanzlei.legalconsenthub_middleware_api.api.SignatureApi +import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.VerifySignatureResponseDto +import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.CertificateDto +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile +import java.time.LocalDateTime +import java.util.* + +@RestController +@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "true") +class DummySignatureController : SignatureApi { + + override fun signPdfHash( + document: MultipartFile, + certificateId: String, + hashAlgorithm: String + ): ResponseEntity { + // Always return a successful dummy signature + val dummySignature = "dummySignature_${certificateId}_${System.currentTimeMillis()}" + val encodedSignature = Base64.getEncoder().encodeToString(dummySignature.toByteArray()) + return ResponseEntity.ok(encodedSignature) + } + + override fun verifySignature( + document: MultipartFile, + signature: String, + certificateId: String?, + hashAlgorithm: String + ): ResponseEntity { + // Always return successful verification with dummy certificate + val dummyCertificate = CertificateDto( + id = certificateId ?: "01", + subject = "CN=John Doe, O=Example Company, L=Berlin, C=DE", + issuer = "CN=Dummy CA, O=Dummy Corp, C=DE", + validFrom = LocalDateTime.now().minusYears(1), + validTo = LocalDateTime.now().plusYears(2), + keyUsage = listOf("digitalSignature", "keyEncipherment", "nonRepudiation"), + fingerprint = "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD" + ) + + val successfulResponse = VerifySignatureResponseDto( + isValid = true, + certificateInfo = dummyCertificate, + verificationDetails = "DUMMY: Signature verified successfully using ${hashAlgorithm} algorithm" + ) + + return ResponseEntity.ok(successfulResponse) + } +} \ No newline at end of file diff --git a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureController.kt b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureController.kt index 3fc62dc..a278dd4 100644 --- a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureController.kt +++ b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureController.kt @@ -2,11 +2,13 @@ package com.betriebsratkanzlei.legalconsenthub_middleware.signature import com.betriebsratkanzlei.legalconsenthub_middleware_api.api.SignatureApi import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.VerifySignatureResponseDto +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile @RestController +@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "false", matchIfMissing = true) class SignatureController( private val signatureService: SignatureService ) : SignatureApi { diff --git a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureService.kt b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureService.kt index bac06c6..ef8a051 100644 --- a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureService.kt +++ b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/signature/SignatureService.kt @@ -3,10 +3,12 @@ package com.betriebsratkanzlei.legalconsenthub_middleware.signature import com.betriebsratkanzlei.legalconsenthub_middleware.smartcard.SmartCardService import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.VerifySignatureResponseDto import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.CertificateDto +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Service import java.util.* @Service +@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "false", matchIfMissing = true) class SignatureService( private val smartCardService: SmartCardService ) { diff --git a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/DummySmartCardController.kt b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/DummySmartCardController.kt new file mode 100644 index 0000000..c5e7078 --- /dev/null +++ b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/DummySmartCardController.kt @@ -0,0 +1,51 @@ +package com.betriebsratkanzlei.legalconsenthub_middleware.smartcard + +import com.betriebsratkanzlei.legalconsenthub_middleware_api.api.SmartCardApi +import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.CertificateDto +import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.SmartCardInfoDto +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RestController +import java.time.LocalDateTime + +@RestController +@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "true") +class DummySmartCardController : SmartCardApi { + + override fun getSmartCardInfo(): ResponseEntity { + // Always return successful smart card info + val dummySmartCardInfo = SmartCardInfoDto( + isPresent = true, + label = "DUMMY Smart Card", + serialNumber = "12345678", + manufacturer = "Dummy Corp", + model = "DummyCard 2024" + ) + return ResponseEntity.ok(dummySmartCardInfo) + } + + override fun getSmartCardCertificates(): ResponseEntity> { + // Always return successful list of dummy certificates + val dummyCertificates = listOf( + CertificateDto( + id = "01", + subject = "CN=John Doe, O=Example Company, L=Berlin, C=DE", + issuer = "CN=Dummy CA, O=Dummy Corp, C=DE", + validFrom = LocalDateTime.now().minusYears(1), + validTo = LocalDateTime.now().plusYears(2), + keyUsage = listOf("digitalSignature", "keyEncipherment", "nonRepudiation"), + fingerprint = "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD" + ), + CertificateDto( + id = "02", + subject = "CN=Jane Smith, O=Test Organization, L=Munich, C=DE", + issuer = "CN=Test CA, O=Test Corp, C=DE", + validFrom = LocalDateTime.now().minusMonths(6), + validTo = LocalDateTime.now().plusYears(3), + keyUsage = listOf("digitalSignature", "nonRepudiation"), + fingerprint = "11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44" + ) + ) + return ResponseEntity.ok(dummyCertificates) + } +} \ No newline at end of file diff --git a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardController.kt b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardController.kt index fbcf9db..5cc4c50 100644 --- a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardController.kt +++ b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardController.kt @@ -3,11 +3,13 @@ package com.betriebsratkanzlei.legalconsenthub_middleware.smartcard import com.betriebsratkanzlei.legalconsenthub_middleware_api.api.SmartCardApi import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.CertificateDto import com.betriebsratkanzlei.legalconsenthub_middleware_api.model.SmartCardInfoDto +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RestController import java.time.LocalDateTime @RestController +@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "false", matchIfMissing = true) class SmartCardController( private val smartCardService: SmartCardService ) : SmartCardApi { diff --git a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardService.kt b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardService.kt index d807dfa..2b4d23e 100644 --- a/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardService.kt +++ b/legalconsenthub-middleware/src/main/kotlin/com/betriebsratkanzlei/legalconsenthub_middleware/smartcard/SmartCardService.kt @@ -7,6 +7,7 @@ import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor import org.apache.commons.exec.PumpStreamHandler import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.core.io.ResourceLoader import org.springframework.stereotype.Service import java.io.ByteArrayOutputStream @@ -17,6 +18,7 @@ import java.nio.file.Files import java.nio.file.StandardCopyOption @Service +@ConditionalOnProperty(name = ["dummy.mode.enabled"], havingValue = "false", matchIfMissing = true) class SmartCardService( @Value("\${opensc.pkcs11.library.path}") private val openscPkcs11LibPath: String, private val resourceLoader: ResourceLoader diff --git a/legalconsenthub-middleware/src/main/resources/application-dummy.yaml b/legalconsenthub-middleware/src/main/resources/application-dummy.yaml new file mode 100644 index 0000000..d5b14ad --- /dev/null +++ b/legalconsenthub-middleware/src/main/resources/application-dummy.yaml @@ -0,0 +1,28 @@ +spring: + application: + name: legalconsenthub-middleware + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + +server: + port: 8081 + servlet: + context-path: / + +logging: + level: + com.betriebsratkanzlei.legalconsenthub_middleware: DEBUG + org.springframework.security: DEBUG + +# Dummy mode configuration +dummy: + mode: + enabled: true + +# OpenSC configuration (not used in dummy mode but kept for consistency) +opensc: + pkcs11: + library: + path: classpath:binaries/opensc-pkcs11.so \ No newline at end of file diff --git a/legalconsenthub/composables/middleware/useMiddleware.ts b/legalconsenthub/composables/middleware/useMiddleware.ts new file mode 100644 index 0000000..d096012 --- /dev/null +++ b/legalconsenthub/composables/middleware/useMiddleware.ts @@ -0,0 +1,38 @@ +import type { + VerifySignatureHashAlgorithmEnum, + VerifySignatureResponseDto, + SignPdfHashHashAlgorithmEnum +} from '~/.api-client-middleware' +import { useMiddlewareApi } from '~/composables/middleware/useMiddlewareApi' + +export function useMiddleware() { + const middlewareApi = useMiddlewareApi() + + async function signPdfHash(document: Blob, certificateId: string, hashAlgorithm?: SignPdfHashHashAlgorithmEnum) { + try { + return middlewareApi.signPdfHash(document, certificateId, hashAlgorithm) + } catch (e: unknown) { + console.error('Failed signing PDF hash:', e) + return Promise.reject(e) + } + } + + async function verifySignature( + document: Blob, + signature: string, + certificateId?: string, + hashAlgorithm?: VerifySignatureHashAlgorithmEnum + ): Promise { + try { + return await middlewareApi.verifySignature(document, signature, certificateId, hashAlgorithm) + } catch (e: unknown) { + console.error('Failed verifying signature:', e) + return Promise.reject(e) + } + } + + return { + signPdfHash, + verifySignature + } +} diff --git a/legalconsenthub/composables/middleware/useMiddlewareApi.ts b/legalconsenthub/composables/middleware/useMiddlewareApi.ts new file mode 100644 index 0000000..e6508d2 --- /dev/null +++ b/legalconsenthub/composables/middleware/useMiddlewareApi.ts @@ -0,0 +1,45 @@ +import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo' +import { + SmartCardApi, + SignatureApi, + Configuration, + type VerifySignatureHashAlgorithmEnum, + type VerifySignatureResponseDto, + type SignPdfHashHashAlgorithmEnum +} from '~/.api-client-middleware' + +export function useMiddlewareApi() { + const appBaseUrl = useRuntimeConfig().app.baseURL + const { serverApiBaseUrl, serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public + const { jwt } = useAuth() + + const basePath = withoutTrailingSlash( + cleanDoubleSlashes(import.meta.client ? appBaseUrl + clientProxyBasePath : serverApiBaseUrl + serverApiBasePath) + ) + + const smartCardApiClient = new SmartCardApi( + new Configuration({ basePath, headers: { Authorization: jwt.value ? `Bearer ${jwt.value}` : '' } }) + ) + + const signatureApiClient = new SignatureApi( + new Configuration({ basePath, headers: { Authorization: jwt.value ? `Bearer ${jwt.value}` : '' } }) + ) + + async function signPdfHash(document: Blob, certificateId: string, hashAlgorithm?: SignPdfHashHashAlgorithmEnum) { + return signatureApiClient.signPdfHash({ document, certificateId, hashAlgorithm }) + } + + async function verifySignature( + document: Blob, + signature: string, + certificateId?: string, + hashAlgorithm?: VerifySignatureHashAlgorithmEnum + ): Promise { + return signatureApiClient.verifySignature({ document, signature, certificateId, hashAlgorithm }) + } + + return { + signPdfHash, + verifySignature + } +}