126 lines
4.8 KiB
Kotlin
126 lines
4.8 KiB
Kotlin
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package org.pgpainless.sop
|
|
|
|
import java.io.InputStream
|
|
import java.io.OutputStream
|
|
import org.bouncycastle.openpgp.PGPException
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
|
import org.bouncycastle.openpgp.PGPSignature
|
|
import org.bouncycastle.util.io.Streams
|
|
import org.pgpainless.PGPainless
|
|
import org.pgpainless.algorithm.DocumentSignatureType
|
|
import org.pgpainless.algorithm.HashAlgorithm
|
|
import org.pgpainless.bouncycastle.extensions.openPgpFingerprint
|
|
import org.pgpainless.encryption_signing.ProducerOptions
|
|
import org.pgpainless.encryption_signing.SigningOptions
|
|
import org.pgpainless.exception.KeyException.MissingSecretKeyException
|
|
import org.pgpainless.exception.KeyException.UnacceptableSigningKeyException
|
|
import org.pgpainless.util.ArmoredOutputStreamFactory
|
|
import org.pgpainless.util.Passphrase
|
|
import sop.MicAlg
|
|
import sop.ReadyWithResult
|
|
import sop.SigningResult
|
|
import sop.enums.SignAs
|
|
import sop.exception.SOPGPException
|
|
import sop.operation.DetachedSign
|
|
import sop.util.UTF8Util
|
|
|
|
/** Implementation of the `sign` operation using PGPainless. */
|
|
class DetachedSignImpl : DetachedSign {
|
|
|
|
private val signingOptions = SigningOptions.get()
|
|
private val protector = MatchMakingSecretKeyRingProtector()
|
|
private val signingKeys = mutableListOf<PGPSecretKeyRing>()
|
|
|
|
private var armor = true
|
|
private var mode = SignAs.binary
|
|
|
|
override fun data(data: InputStream): ReadyWithResult<SigningResult> {
|
|
signingKeys.forEach {
|
|
try {
|
|
signingOptions.addDetachedSignature(protector, it, modeToSigType(mode))
|
|
} catch (e: UnacceptableSigningKeyException) {
|
|
throw SOPGPException.KeyCannotSign("Key ${it.openPgpFingerprint} cannot sign.", e)
|
|
} catch (e: MissingSecretKeyException) {
|
|
throw SOPGPException.KeyCannotSign(
|
|
"Key ${it.openPgpFingerprint} cannot sign. Missing secret key.", e)
|
|
} catch (e: PGPException) {
|
|
throw SOPGPException.KeyIsProtected(
|
|
"Key ${it.openPgpFingerprint} cannot be unlocked.", e)
|
|
}
|
|
}
|
|
|
|
try {
|
|
val signingStream =
|
|
PGPainless.encryptAndOrSign()
|
|
.discardOutput()
|
|
.withOptions(ProducerOptions.sign(signingOptions).setAsciiArmor(armor))
|
|
|
|
return object : ReadyWithResult<SigningResult>() {
|
|
override fun writeTo(outputStream: OutputStream): SigningResult {
|
|
check(!signingStream.isClosed) { "The operation is a one-shot object." }
|
|
|
|
Streams.pipeAll(data, signingStream)
|
|
signingStream.close()
|
|
val result = signingStream.result
|
|
|
|
// forget passphrases
|
|
protector.clear()
|
|
|
|
val signatures = result.detachedSignatures.map { it.value }.flatten()
|
|
val out =
|
|
if (armor) ArmoredOutputStreamFactory.get(outputStream) else outputStream
|
|
|
|
signatures.forEach { it.encode(out) }
|
|
out.close()
|
|
outputStream.close()
|
|
|
|
return SigningResult.builder()
|
|
.setMicAlg(micAlgFromSignatures(signatures))
|
|
.build()
|
|
}
|
|
}
|
|
} catch (e: PGPException) {
|
|
throw RuntimeException(e)
|
|
}
|
|
}
|
|
|
|
override fun key(key: InputStream): DetachedSign = apply {
|
|
KeyReader.readSecretKeys(key, true).forEach {
|
|
val info = PGPainless.inspectKeyRing(it)
|
|
if (!info.isUsableForSigning) {
|
|
throw SOPGPException.KeyCannotSign(
|
|
"Key ${info.fingerprint} does not have valid, signing capable subkeys.")
|
|
}
|
|
protector.addSecretKey(it)
|
|
signingKeys.add(it)
|
|
}
|
|
}
|
|
|
|
override fun mode(mode: SignAs): DetachedSign = apply { this.mode = mode }
|
|
|
|
override fun noArmor(): DetachedSign = apply { armor = false }
|
|
|
|
override fun withKeyPassword(password: ByteArray): DetachedSign = apply {
|
|
protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8)))
|
|
}
|
|
|
|
private fun modeToSigType(mode: SignAs): DocumentSignatureType {
|
|
return when (mode) {
|
|
SignAs.binary -> DocumentSignatureType.BINARY_DOCUMENT
|
|
SignAs.text -> DocumentSignatureType.CANONICAL_TEXT_DOCUMENT
|
|
}
|
|
}
|
|
|
|
private fun micAlgFromSignatures(signatures: List<PGPSignature>): MicAlg =
|
|
signatures
|
|
.mapNotNull { HashAlgorithm.fromId(it.hashAlgorithm) }
|
|
.toSet()
|
|
.singleOrNull()
|
|
?.let { MicAlg.fromHashAlgorithmId(it.algorithmId) }
|
|
?: MicAlg.empty()
|
|
}
|