From 217609679d4b63a84bfa6b8d9439159bc1b6c245 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 19 Feb 2021 21:22:25 +0100 Subject: [PATCH] Add SignatureValidationUtil and NotationRegistry classes --- .../SignatureValidationUtil.java | 101 ++++++++++++++++++ .../org/pgpainless/util/NotationRegistry.java | 54 ++++++++++ 2 files changed, 155 insertions(+) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java new file mode 100644 index 00000000..3cd1d5fe --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021 Paul Schaub. + * + * 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. + */ +package org.pgpainless.decryption_verification; + +import java.util.Date; + +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.openpgp.PGPDataValidationException; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.pgpainless.algorithm.SignatureSubpacket; +import org.pgpainless.util.NotationRegistry; + +/** + * Utility class that implements validation of signatures. + */ +public class SignatureValidationUtil { + + public static void validate(PGPSignature signature) throws PGPException { + validateHashedAreaHasSignatureCreationTime(signature); + validateSignatureCreationTimeIsNotInUnhashedArea(signature); + validateSignatureDoesNotContainCriticalUnknownSubpackets(signature); + validateSignatureDoesNotContainCriticalUnknownNotations(signature); + } + + public static void validateHashedAreaHasSignatureCreationTime(PGPSignature signature) throws PGPDataValidationException { + PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); + if (hashedSubpackets.getSignatureCreationTime() == null) { + throw new PGPDataValidationException("Hashed area of the signature MUST carry signature creation time subpacket."); + } + } + + public static void validateSignatureCreationTimeIsNotInUnhashedArea(PGPSignature signature) throws PGPDataValidationException { + PGPSignatureSubpacketVector unhashedSubpackets = signature.getUnhashedSubPackets(); + Date unhashedCreationTime = unhashedSubpackets.getSignatureCreationTime(); + if (unhashedCreationTime == null) { + return; + } + throw new PGPDataValidationException("Signature creation time MUST be in hashed area of the signature."); + } + + public static void validateSignatureDoesNotContainCriticalUnknownSubpackets(PGPSignature signature) throws PGPDataValidationException { + try { + throwIfContainsCriticalUnknownSubpacket(signature.getHashedSubPackets()); + } catch (PGPDataValidationException e) { + throw new PGPDataValidationException("Signature has unknown critical subpacket in hashed area.\n" + e.getMessage()); + } + try { + throwIfContainsCriticalUnknownSubpacket(signature.getHashedSubPackets()); + } catch (PGPDataValidationException e) { + throw new PGPDataValidationException("Signature has unknown critical subpacket in unhashed area.\n" + e.getMessage()); + } + } + + private static void throwIfContainsCriticalUnknownSubpacket(PGPSignatureSubpacketVector subpacketVector) throws PGPDataValidationException { + for (int critical : subpacketVector.getCriticalTags()) { + try { + SignatureSubpacket.fromCode(critical); + } catch (IllegalArgumentException e) { + throw new PGPDataValidationException("Unknown critical signature subpacket: " + Long.toHexString(critical)); + } + } + } + + public static void validateSignatureDoesNotContainCriticalUnknownNotations(PGPSignature signature) throws PGPDataValidationException { + PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); + try { + throwIfSubpacketsContainCriticalUnknownNotation(hashedSubpackets); + } catch (PGPDataValidationException e) { + throw new PGPDataValidationException("Signature contains unknown critical notation in hashed area:\n" + e.getMessage()); + } + PGPSignatureSubpacketVector unhashedSubpackets = signature.getUnhashedSubPackets(); + try { + throwIfSubpacketsContainCriticalUnknownNotation(unhashedSubpackets); + } catch (PGPDataValidationException e) { + throw new PGPDataValidationException("Signature contains unknown critical notation in unhashed area:\n" + e.getMessage()); + } + } + + private static void throwIfSubpacketsContainCriticalUnknownNotation(PGPSignatureSubpacketVector subpacketVector) throws PGPDataValidationException { + for (NotationData notation : subpacketVector.getNotationDataOccurrences()) { + if (notation.isCritical() && !NotationRegistry.getInstance().isKnownNotation(notation.getNotationName())) { + throw new PGPDataValidationException("Critical unknown notation encountered: " + notation.getNotationName()); + } + } + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java b/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java new file mode 100644 index 00000000..3366fc0a --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021 Paul Schaub. + * + * 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. + */ +package org.pgpainless.util; + +import java.util.HashSet; +import java.util.Set; + +/** + * Registry for known notations. + * Since signature verification must reject signatures with critical notations that are not known to the application, + * there must be some way to tell PGPainless which notations actually are known. + * + * To add a notation name, call {@link #addKnownNotation(String)}. + */ +public final class NotationRegistry { + + private static NotationRegistry INSTANCE; + private final Set knownNotations = new HashSet<>(); + + private NotationRegistry() { + + } + + public static NotationRegistry getInstance() { + if (INSTANCE == null) { + INSTANCE = new NotationRegistry(); + } + return INSTANCE; + } + + public void addKnownNotation(String notationName) { + if (notationName == null) { + throw new NullPointerException("Notation name MUST NOT be null."); + } + knownNotations.add(notationName); + } + + public boolean isKnownNotation(String notationName) { + return knownNotations.contains(notationName); + } +}