nuts-foundation/nuts-discovery

View on GitHub
src/main/kotlin/nl/nuts/discovery/api/CertificateApi.kt

Summary

Maintainability
A
1 hr
Test Coverage
/*
 *     Nuts discovery service for Corda network creation
 *     Copyright (C) 2019 Nuts community
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package nl.nuts.discovery.api

import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.X509Utilities
import nl.nuts.discovery.service.CertificateAndKeyService
import nl.nuts.discovery.service.NutsDiscoveryProperties
import nl.nuts.discovery.store.CertificateRepository
import nl.nuts.discovery.store.CertificateRequestRepository
import nl.nuts.discovery.store.entity.CertificateRequest
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

/**
 * Certificate API for handling Certificate Signing Requests and returning an answer when signed.
 */
@RestController
@RequestMapping("/doorman/certificate", produces = ["*/*"], consumes = arrayOf("*/*"))
class CertificateApi {
    val logger: Logger = LoggerFactory.getLogger(this.javaClass)

    @Autowired
    lateinit var certificateAndKeyService: CertificateAndKeyService

    @Autowired
    lateinit var certificateRequestRepository: CertificateRequestRepository

    @Autowired
    lateinit var certificateRepository: CertificateRepository

    @Autowired
    lateinit var nutsDiscoveryProperties: NutsDiscoveryProperties

    @RequestMapping("", method = arrayOf(RequestMethod.POST), produces = arrayOf("*/*"), consumes = arrayOf("*/*"))
    fun handleCertificateRequest(@RequestBody input: ByteArray,
                                 @RequestHeader("Platform-Version") platformVersion: String,
                                 @RequestHeader("Client-Version") clientVersion: String,
                                 @RequestHeader("Private-Network-Map", required = false) pnm: String?): ResponseEntity<String> {
        return try {
            val pkcs10Request = PKCS10CertificationRequest(input)

            logger.info("Received request for: ${pkcs10Request.subject}")
            logger.info("Platform-Version: $platformVersion")
            logger.info("Client-Version: $clientVersion")
            logger.info("Private-Network-Map: $pnm")

            val cr = CertificateRequest.fromPKCS10(pkcs10Request)

            logger.info("Saving request under: ${cr.name}")

            val req = certificateRequestRepository.save(cr)
            if (nutsDiscoveryProperties.autoAck) {
                certificateAndKeyService.signCertificate(req)
            }

            ResponseEntity.ok(pkcs10Request.subject.toString())
        } catch (e: Exception) {
            logger.error(e.message, e)
            ResponseEntity.badRequest().build()
        }
    }

    @RequestMapping("/{var}", method = arrayOf(RequestMethod.GET))
    fun downloadCertificate(@PathVariable("var") requestId: String): ResponseEntity<ByteArray> {
        try {
            logger.info("Received certificate download request for: $requestId")
            var x500Name = CordaX500Name.parse(requestId) // for sorting

            // certificate signed?
            val certificate = certificateRepository.findByName(x500Name.toString())
            // certificate pending?
                ?: return if (certificateRequestRepository.findByName(x500Name.toString()) != null) {
                    // try later
                    ResponseEntity.noContent().build()
                } else {
                    // nope, don't try again.
                    ResponseEntity.status(403).build()
                }

            val certPath = X509Utilities.buildCertPath(certificate.toX509(), certificateAndKeyService.intermediateCertificate(), certificateAndKeyService.cordaRootCertificate())

            val baos = ByteArrayOutputStream()
            ZipOutputStream(baos as OutputStream?).use { zip ->
                listOf(X509Utilities.CORDA_CLIENT_CA, X509Utilities.CORDA_INTERMEDIATE_CA, X509Utilities.CORDA_ROOT_CA).zip(certPath.certificates).forEach {
                    zip.putNextEntry(ZipEntry("${it.first}.cer"))
                    zip.write(it.second.encoded)
                    zip.closeEntry()
                }
            }
            return ResponseEntity
                .ok()
                //.contentType(MediaType.parseMediaType("application/zip"))
                .header("Content-Disposition", "attachment; filename=\"certificates.zip\"")
                .body(baos.toByteArray())

        } catch (e: Exception) {
            logger.error(e.message, e)
            return ResponseEntity.badRequest().build()
        }
    }
}