mirror of
https://codeberg.org/PGPainless/sop-java.git
synced 2024-11-29 18:42:08 +01:00
Implement validate-userid command
This commit is contained in:
parent
1945411712
commit
0ec2961cbe
8 changed files with 216 additions and 17 deletions
|
@ -30,6 +30,7 @@ import sop.exception.SOPGPException
|
||||||
UpdateKeyCmd::class,
|
UpdateKeyCmd::class,
|
||||||
MergeCertsCmd::class,
|
MergeCertsCmd::class,
|
||||||
CertifyUserIdCmd::class,
|
CertifyUserIdCmd::class,
|
||||||
|
ValidateUserIdCmd::class,
|
||||||
// Messaging subcommands
|
// Messaging subcommands
|
||||||
SignCmd::class,
|
SignCmd::class,
|
||||||
VerifyCmd::class,
|
VerifyCmd::class,
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package sop.cli.picocli.commands
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.Option
|
||||||
|
import picocli.CommandLine.Parameters
|
||||||
|
import sop.cli.picocli.SopCLI
|
||||||
|
import sop.exception.SOPGPException
|
||||||
|
import sop.util.HexUtil.Companion.bytesToHex
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = "validate-userid",
|
||||||
|
resourceBundle = "msg_validate-userid",
|
||||||
|
exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE)
|
||||||
|
class ValidateUserIdCmd : AbstractSopCmd() {
|
||||||
|
|
||||||
|
@Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false
|
||||||
|
|
||||||
|
@Parameters(index = "0", arity = "1", paramLabel = "USERID") lateinit var userId: String
|
||||||
|
|
||||||
|
@Parameters(index = "1..*", arity = "1..*", paramLabel = "CERTS")
|
||||||
|
var authorities: List<String> = listOf()
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val validateUserId =
|
||||||
|
throwIfUnsupportedSubcommand(SopCLI.getSop().validateUserId(), "validate-userid")
|
||||||
|
|
||||||
|
if (addrSpecOnly) {
|
||||||
|
validateUserId.addrSpecOnly()
|
||||||
|
}
|
||||||
|
|
||||||
|
validateUserId.userId(userId)
|
||||||
|
|
||||||
|
for (authority in authorities) {
|
||||||
|
try {
|
||||||
|
getInput(authority).use { validateUserId.authorities(it) }
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
} catch (b: SOPGPException.BadData) {
|
||||||
|
val errorMsg = getMsg("sop.error.input.not_a_certificate", authority)
|
||||||
|
throw SOPGPException.BadData(errorMsg, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val valid = validateUserId.subjects(System.`in`)
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
val errorMsg = getMsg("sop.error.runtime.any_cert_user_id_no_match", userId)
|
||||||
|
throw SOPGPException.CertUserIdNoMatch(errorMsg)
|
||||||
|
}
|
||||||
|
} catch (e: SOPGPException.CertUserIdNoMatch) {
|
||||||
|
val errorMsg =
|
||||||
|
if (e.fingerprint != null) {
|
||||||
|
getMsg(
|
||||||
|
"sop.error.runtime.cert_user_id_no_match",
|
||||||
|
bytesToHex(e.fingerprint!!),
|
||||||
|
userId)
|
||||||
|
} else {
|
||||||
|
getMsg("sop.error.runtime.any_cert_user_id_no_match", userId)
|
||||||
|
}
|
||||||
|
throw SOPGPException.CertUserIdNoMatch(errorMsg, e)
|
||||||
|
} catch (e: SOPGPException.BadData) {
|
||||||
|
val errorMsg = getMsg("sop.error.input.not_a_certificate", "STDIN")
|
||||||
|
throw SOPGPException.BadData(errorMsg, e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,6 +80,8 @@ sop.error.runtime.cert_cannot_encrypt=Certificate from input '%s' cannot encrypt
|
||||||
sop.error.runtime.no_session_key_extracted=Session key not extracted. Feature potentially not supported.
|
sop.error.runtime.no_session_key_extracted=Session key not extracted. Feature potentially not supported.
|
||||||
sop.error.runtime.no_verifiable_signature_found=No verifiable signature found.
|
sop.error.runtime.no_verifiable_signature_found=No verifiable signature found.
|
||||||
sop.error.runtime.cannot_decrypt_message=Message could not be decrypted.
|
sop.error.runtime.cannot_decrypt_message=Message could not be decrypted.
|
||||||
|
sop.error.runtime.cert_user_id_no_match=Certificate '%s' does not contain a valid binding for user id '%s'.
|
||||||
|
sop.error.runtime.any_cert_user_id_no_match=Any certificate does not contain a valid binding for user id '%s'.
|
||||||
## Usage errors
|
## Usage errors
|
||||||
sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption.
|
sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption.
|
||||||
sop.error.usage.argument_required=Argument '%s' is required.
|
sop.error.usage.argument_required=Argument '%s' is required.
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
usage.header=Validate a UserID in an OpenPGP certificate
|
||||||
|
addr-spec-only=Treat the USERID as an email address, match only against the email address part of each correctly bound UserID
|
||||||
|
USERID[0]=UserID
|
||||||
|
CERTS[1..*]=Authority OpenPGP certificates
|
||||||
|
|
||||||
|
standardInput=CERTS
|
||||||
|
standardInputDescription=OpenPGP certificates in which UserID bindings shall be validated
|
||||||
|
|
||||||
|
stacktrace=Print stacktrace
|
||||||
|
# Generic TODO: Remove when bumping picocli to 4.7.0
|
||||||
|
usage.parameterListHeading=%nParameters:%n
|
||||||
|
usage.synopsisHeading=Usage:\u0020
|
||||||
|
usage.commandListHeading=%nCommands:%n
|
||||||
|
usage.optionListHeading=%nOptions:%n
|
||||||
|
usage.footerHeading=Powered by picocli%n
|
|
@ -0,0 +1,18 @@
|
||||||
|
# SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
usage.header=Validiere eine UserID auf OpenPGP Zertifikaten
|
||||||
|
addr-spec-only=Behandle die USERID als E-Mail-Adresse, vergleiche sie nur mit dem E-Mail-Adressen-Teil jeder korrekten UserID
|
||||||
|
USERID[0]=UserID
|
||||||
|
CERTS[1..*]=Autoritäre OpenPGP Zertifikate
|
||||||
|
|
||||||
|
standardInput=CERTS
|
||||||
|
standardInputDescription=OpenPGP Zertifikate auf denen UserIDs validiert werden sollen
|
||||||
|
|
||||||
|
stacktrace=Print stacktrace
|
||||||
|
# Generic TODO: Remove when bumping picocli to 4.7.0
|
||||||
|
usage.parameterListHeading=%nParameter:%n
|
||||||
|
usage.synopsisHeading=Aufruf:\u0020
|
||||||
|
usage.commandListHeading=%nBefehle:%n
|
||||||
|
usage.optionListHeading=%nOptionen:%n
|
||||||
|
usage.footerHeading=Powered by Picocli%n
|
|
@ -60,18 +60,15 @@ interface SOP : SOPV {
|
||||||
/** Update a key's password. */
|
/** Update a key's password. */
|
||||||
fun changeKeyPassword(): ChangeKeyPassword
|
fun changeKeyPassword(): ChangeKeyPassword
|
||||||
|
|
||||||
/**
|
/** Keep a secret key up-to-date. */
|
||||||
* Keep a secret key up-to-date.
|
|
||||||
*/
|
|
||||||
fun updateKey(): UpdateKey
|
fun updateKey(): UpdateKey
|
||||||
|
|
||||||
/**
|
/** Merge OpenPGP certificates. */
|
||||||
* Merge OpenPGP certificates.
|
|
||||||
*/
|
|
||||||
fun mergeCerts(): MergeCerts
|
fun mergeCerts(): MergeCerts
|
||||||
|
|
||||||
/**
|
/** Certify OpenPGP Certificate User-IDs. */
|
||||||
* Certify OpenPGP Certificate User-IDs.
|
|
||||||
*/
|
|
||||||
fun certifyUserId(): CertifyUserId
|
fun certifyUserId(): CertifyUserId
|
||||||
|
|
||||||
|
/** Validate a UserID in an OpenPGP certificate. */
|
||||||
|
fun validateUserId(): ValidateUserId
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,9 +338,7 @@ abstract class SOPGPException : RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The primary key of a KEYS object is too weak or revoked. */
|
||||||
* The primary key of a KEYS object is too weak or revoked.
|
|
||||||
*/
|
|
||||||
class PrimaryKeyBad : SOPGPException {
|
class PrimaryKeyBad : SOPGPException {
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
|
|
||||||
|
@ -353,13 +351,26 @@ abstract class SOPGPException : RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The CERTS object has no matching User ID. */
|
||||||
* The CERTS object has no matching User ID.
|
|
||||||
*/
|
|
||||||
class CertUserIdNoMatch : SOPGPException {
|
class CertUserIdNoMatch : SOPGPException {
|
||||||
constructor() : super()
|
|
||||||
|
|
||||||
constructor(errorMsg: String) : super(errorMsg)
|
val fingerprint: ByteArray?
|
||||||
|
|
||||||
|
constructor() : super() {
|
||||||
|
fingerprint = null
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(fingerprint: ByteArray) : super() {
|
||||||
|
this.fingerprint = fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(errorMsg: String) : super(errorMsg) {
|
||||||
|
fingerprint = null
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(errorMsg: String, cause: Throwable) : super(errorMsg, cause) {
|
||||||
|
fingerprint = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun getExitCode(): Int = EXIT_CODE
|
override fun getExitCode(): Int = EXIT_CODE
|
||||||
|
|
||||||
|
|
78
sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt
Normal file
78
sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package sop.operation
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import sop.exception.SOPGPException
|
||||||
|
|
||||||
|
/** Subcommand to validate UserIDs on certificates. */
|
||||||
|
interface ValidateUserId {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is set, then the USERID is treated as an e-mail address, and matched only against the
|
||||||
|
* e-mail address part of each correctly bound User ID. The rest of each correctly bound User ID
|
||||||
|
* is ignored.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
@Throws(SOPGPException.UnsupportedOption::class) fun addrSpecOnly(): ValidateUserId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the UserID to validate. To match only the email address, call [addrSpecOnly].
|
||||||
|
*
|
||||||
|
* @param userId UserID or email address
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
fun userId(userId: String): ValidateUserId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add certificates, which act as authorities. The [userId] is only considered correctly bound,
|
||||||
|
* if it was bound by an authoritative certificate.
|
||||||
|
*
|
||||||
|
* @param certs authoritative certificates
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
@Throws(SOPGPException.BadData::class, IOException::class)
|
||||||
|
fun authorities(certs: InputStream): ValidateUserId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add certificates, which act as authorities. The [userId] is only considered correctly bound,
|
||||||
|
* if it was bound by an authoritative certificate.
|
||||||
|
*
|
||||||
|
* @param certs authoritative certificates
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
@Throws(SOPGPException.BadData::class, IOException::class)
|
||||||
|
fun authorities(certs: ByteArray): ValidateUserId = authorities(certs.inputStream())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add subject certificates, on which UserID bindings are validated.
|
||||||
|
*
|
||||||
|
* @param certs subject certificates
|
||||||
|
* @return true if all subject certificates have a correct binding to the UserID.
|
||||||
|
* @throws SOPGPException.BadData if the subject certificates are malformed
|
||||||
|
* @throws IOException if a parser exception happens
|
||||||
|
* @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly
|
||||||
|
* bound UserID that matches [userId].
|
||||||
|
*/
|
||||||
|
@Throws(
|
||||||
|
SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class)
|
||||||
|
fun subjects(certs: InputStream): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add subject certificates, on which UserID bindings are validated.
|
||||||
|
*
|
||||||
|
* @param certs subject certificates
|
||||||
|
* @return true if all subject certificates have a correct binding to the UserID.
|
||||||
|
* @throws SOPGPException.BadData if the subject certificates are malformed
|
||||||
|
* @throws IOException if a parser exception happens
|
||||||
|
* @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly
|
||||||
|
* bound UserID that matches [userId].
|
||||||
|
*/
|
||||||
|
@Throws(
|
||||||
|
SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class)
|
||||||
|
fun subjects(certs: ByteArray): Boolean = subjects(certs.inputStream())
|
||||||
|
}
|
Loading…
Reference in a new issue