You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2023/01/27 18:11:51 UTC

[nifi] 02/02: NIFI-11087 Added VerifyContentMAC Processor

This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git

commit e4748e15f44268104c04152d914f128a0bb47918
Author: Ferenc Erdei <er...@gmail.com>
AuthorDate: Mon Jan 23 11:40:10 2023 +0100

    NIFI-11087 Added VerifyContentMAC Processor
    
    This closes #6877
    
    Signed-off-by: David Handermann <ex...@apache.org>
---
 .../nifi/processors/cipher/VerifyContentMAC.java   | 279 +++++++++++++++++++++
 .../services/org.apache.nifi.processor.Processor   |   1 +
 .../processors/cipher/VerifyContentMACTest.java    | 179 +++++++++++++
 3 files changed, 459 insertions(+)

diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/VerifyContentMAC.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/VerifyContentMAC.java
new file mode 100644
index 0000000000..0dc406dad0
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/java/org/apache/nifi/processors/cipher/VerifyContentMAC.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.processors.cipher;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.Encoding.BASE64;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.Encoding.HEXADECIMAL;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.Encoding.UTF8;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
+import org.apache.nifi.annotation.behavior.WritesAttribute;
+import org.apache.nifi.annotation.behavior.WritesAttributes;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnScheduled;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.bouncycastle.util.encoders.Hex;
+
+@SupportsBatching
+@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
+@Tags({"Authentication", "Signing", "MAC", "HMAC"})
+@CapabilityDescription("Calculates a Message Authentication Code using the provided Secret Key and compares it with the provided MAC property")
+@WritesAttributes({
+        @WritesAttribute(attribute = VerifyContentMAC.MAC_CALCULATED_ATTRIBUTE, description = "Calculated Message Authentication Code encoded by the selected encoding"),
+        @WritesAttribute(attribute = VerifyContentMAC.MAC_ENCODING_ATTRIBUTE, description = "The Encoding of the Hashed Message Authentication Code"),
+        @WritesAttribute(attribute = VerifyContentMAC.MAC_ALGORITHM_ATTRIBUTE, description = "Hashed Message Authentication Code Algorithm")
+})
+public class VerifyContentMAC extends AbstractProcessor {
+
+    protected static final String HMAC_SHA256 = "HmacSHA256";
+    protected static final String HMAC_SHA512 = "HmacSHA512";
+
+    protected static final PropertyDescriptor MAC_ALGORITHM = new PropertyDescriptor.Builder()
+            .name("mac-algorithm")
+            .displayName("Message Authentication Code Algorithm")
+            .description("Hashed Message Authentication Code Function")
+            .allowableValues(HMAC_SHA256, HMAC_SHA512)
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
+
+    protected static final PropertyDescriptor MAC_ENCODING = new PropertyDescriptor.Builder()
+            .name("message-authentication-code-encoding")
+            .displayName("Message Authentication Code Encoding")
+            .description("Encoding of the Message Authentication Code")
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .allowableValues(HEXADECIMAL.name(), BASE64.name())
+            .defaultValue(HEXADECIMAL.name())
+            .build();
+
+    protected static final PropertyDescriptor MAC = new PropertyDescriptor.Builder()
+            .name("message-authentication-code")
+            .displayName("Message Authentication Code")
+            .description("The MAC to compare with the calculated value")
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+            .build();
+
+    protected static final PropertyDescriptor SECRET_KEY_ENCODING = new PropertyDescriptor.Builder()
+            .name("secret-key-encoding")
+            .displayName("Secret Key Encoding")
+            .description("Encoding of the Secret Key")
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .allowableValues(UTF8.name(), HEXADECIMAL.name(), BASE64.name())
+            .defaultValue(HEXADECIMAL.name())
+            .build();
+
+    protected static final PropertyDescriptor SECRET_KEY = new PropertyDescriptor.Builder()
+            .name("secret-key")
+            .displayName("Secret Key")
+            .description("Secret key to calculate the hash")
+            .required(true)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .sensitive(true)
+            .build();
+
+    protected static final Relationship FAILURE = new Relationship.Builder()
+            .name("failure")
+            .description("Signature Verification Failed")
+            .build();
+
+    protected static final Relationship SUCCESS = new Relationship.Builder()
+            .name("success")
+            .description("Signature Verification Succeeded")
+            .build();
+
+    protected static final String MAC_CALCULATED_ATTRIBUTE = "mac.calculated";
+    protected static final String MAC_ALGORITHM_ATTRIBUTE = "mac.algorithm";
+    protected static final String MAC_ENCODING_ATTRIBUTE = "mac.encoding";
+    private static final List<PropertyDescriptor> PROPERTIES = Collections.unmodifiableList(
+            Arrays.asList(
+                    MAC_ALGORITHM,
+                    MAC_ENCODING,
+                    MAC,
+                    SECRET_KEY_ENCODING,
+                    SECRET_KEY
+            )
+    );
+    private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(SUCCESS, FAILURE)));
+    private static final int BUFFER_SIZE = 512000;
+
+    private SecretKeySpec secretKeySpec;
+    private String macAlgorithm;
+    private String macEncoding;
+
+    @OnScheduled
+    public void setUp(ProcessContext context) {
+        macAlgorithm = context.getProperty(MAC_ALGORITHM).getValue();
+        macEncoding = context.getProperty(MAC_ENCODING).getValue();
+        String secretKeyEncoding = context.getProperty(SECRET_KEY_ENCODING).getValue();
+        String inputSecretKey = context.getProperty(SECRET_KEY).getValue();
+
+        byte[] secretKey = Encoding.valueOf(secretKeyEncoding).decode(inputSecretKey);
+
+        secretKeySpec = new SecretKeySpec(secretKey, macAlgorithm);
+    }
+
+    @Override
+    public Set<Relationship> getRelationships() {
+        return RELATIONSHIPS;
+    }
+
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession session) {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final String macEncoded = context.getProperty(MAC).evaluateAttributeExpressions(flowFile).getValue();
+
+        try {
+            final byte[] macDecoded = Encoding.valueOf(macEncoding).decode(macEncoded);
+            final byte[] macCalculated = getCalculatedMac(session, flowFile);
+
+            flowFile = setFlowFileAttributes(session, flowFile, macCalculated);
+
+            if (MessageDigest.isEqual(macDecoded, macCalculated)) {
+                session.transfer(flowFile, SUCCESS);
+            } else {
+                getLogger().info("Verification Failed with Message Authentication Code Algorithm [{}]", macAlgorithm);
+                session.transfer(flowFile, FAILURE);
+            }
+        } catch (final Exception e) {
+            getLogger().error("Processing Failed with Message Authentication Code Algorithm [{}]", macAlgorithm, e);
+            session.transfer(flowFile, FAILURE);
+        }
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return PROPERTIES;
+    }
+
+    @Override
+    protected Collection<ValidationResult> customValidate(ValidationContext validationContext) {
+        final List<ValidationResult> results = new ArrayList<>(super.customValidate(validationContext));
+
+        final String secretKeyEncoding = validationContext.getProperty(SECRET_KEY_ENCODING).getValue();
+        final String encodedSecretKey = validationContext.getProperty(SECRET_KEY).getValue();
+
+        try {
+            Encoding.valueOf(secretKeyEncoding).decode(encodedSecretKey);
+        } catch (Exception e) {
+            results.add(new ValidationResult.Builder()
+                    .valid(false)
+                    .subject(SECRET_KEY.getDisplayName())
+                    .explanation("The provided Secret Key is not a valid " + secretKeyEncoding + " value")
+                    .build());
+        }
+
+        return results;
+    }
+
+    private FlowFile setFlowFileAttributes(ProcessSession session, FlowFile flowFile, byte[] calculatedMac) {
+        Map<String, String> attributes = new HashMap<>();
+        attributes.put(MAC_ALGORITHM_ATTRIBUTE, macAlgorithm);
+        attributes.put(MAC_ENCODING_ATTRIBUTE, macEncoding);
+        attributes.put(MAC_CALCULATED_ATTRIBUTE, Encoding.valueOf(macEncoding).encode(calculatedMac));
+        return session.putAllAttributes(flowFile, attributes);
+    }
+
+    private Mac getInitializedMac() {
+        try {
+            Mac mac = Mac.getInstance(macAlgorithm);
+            mac.init(secretKeySpec);
+            return mac;
+        } catch (final NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new ProcessException("HMAC initialization failed", e);
+        }
+    }
+
+    private byte[] getCalculatedMac(ProcessSession session, FlowFile flowFile) {
+        Mac mac = getInitializedMac();
+
+        byte[] contents = new byte[BUFFER_SIZE];
+        int readSize;
+
+        try (InputStream is = session.read(flowFile)) {
+            while ((readSize = is.read(contents)) != -1) {
+                mac.update(contents, 0, readSize);
+            }
+        } catch (final IOException e) {
+            throw new ProcessException("File processing failed", e);
+        }
+
+        return mac.doFinal();
+    }
+
+    enum Encoding {
+        HEXADECIMAL(Hex::toHexString, Hex::decode),
+        BASE64(value -> Base64.getEncoder().encodeToString(value), value -> Base64.getDecoder().decode(value)),
+        UTF8(value -> new String(value, UTF_8), value -> value.getBytes(UTF_8));
+
+        private final Function<byte[], String> encodeFunction;
+        private final Function<String, byte[]> decodeFunction;
+
+        Encoding(Function<byte[], String> encodeFunction, Function<String, byte[]> decodeFunction) {
+            this.decodeFunction = decodeFunction;
+            this.encodeFunction = encodeFunction;
+        }
+
+        public byte[] decode(String value) {
+            return decodeFunction.apply(value);
+        }
+
+        public String encode(byte[] value) {
+            return encodeFunction.apply(value);
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index 9340751c56..9952db3a9f 100644
--- a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -14,3 +14,4 @@
 # limitations under the License.
 org.apache.nifi.processors.cipher.DecryptContentCompatibility
 org.apache.nifi.processors.cipher.DecryptContent
+org.apache.nifi.processors.cipher.VerifyContentMAC
diff --git a/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/VerifyContentMACTest.java b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/VerifyContentMACTest.java
new file mode 100644
index 0000000000..38b35a232e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-cipher-bundle/nifi-cipher-processors/src/test/java/org/apache/nifi/processors/cipher/VerifyContentMACTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.processors.cipher;
+
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.FAILURE;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.HMAC_SHA256;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.HMAC_SHA512;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.MAC_ALGORITHM_ATTRIBUTE;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.MAC_CALCULATED_ATTRIBUTE;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.MAC_ENCODING_ATTRIBUTE;
+import static org.apache.nifi.processors.cipher.VerifyContentMAC.SUCCESS;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.nifi.processors.cipher.VerifyContentMAC.Encoding;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class VerifyContentMACTest {
+
+    private static final String INVALID = "invalid";
+    private static final String FLOW_CONTENT = "content";
+    private static final String CONTENT_HMAC_SHA256_HEXADECIMAL = "3c81220b10838ab972fd4f6796304dab1bb3ccdf65a0edc8c5ce7eef30191b6c";
+    private static final String CONTENT_HMAC_SHA512_HEXADECIMAL =
+        "1e85c66a5ade958b0282632c9e0654c9a9f5985170ee53fbf9b55c536dbab768ddf835fe1afe11c37aeaec5f9751ab3762852fd33ea3c279a21ca98db99b3c62";
+    private static final String CONTENT_HMAC_SHA256_BASE64 = "PIEiCxCDirly/U9nljBNqxuzzN9loO3Ixc5+7zAZG2w=";
+    private static final String CONTENT_HMAC_SHA512_BASE64 = "HoXGalrelYsCgmMsngZUyan1mFFw7lP7+bVcU226t2jd+DX+Gv4Rw3rq7F+XUas3YoUv0z6jwnmiHKmNuZs8Yg==";
+    private static final String TEST_SECRET_KEY_UTF8 = "test";
+    private static final String TEST_SECRET_KEY_HEXADECIMAL = "74657374";
+    private static final String TEST_SECRET_KEY_BASE64 = "dGVzdA==";
+
+    private TestRunner runner;
+
+    @BeforeEach
+    public void setRunner() {
+        runner = TestRunners.newTestRunner(new VerifyContentMAC());
+    }
+
+    @Test
+    void testNotValidWithoutMACAlgorithm() {
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, TEST_SECRET_KEY_HEXADECIMAL);
+        runner.setProperty(VerifyContentMAC.MAC, CONTENT_HMAC_SHA256_HEXADECIMAL);
+
+        runner.assertNotValid();
+    }
+
+    @Test
+    void testNotValidWithInvalidAlgorithm() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, INVALID);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, TEST_SECRET_KEY_HEXADECIMAL);
+        runner.setProperty(VerifyContentMAC.MAC, CONTENT_HMAC_SHA256_HEXADECIMAL);
+
+        runner.assertNotValid();
+    }
+
+    @Test
+    void testNotValidWithoutSecretKey() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, HMAC_SHA256);
+        runner.setProperty(VerifyContentMAC.MAC, CONTENT_HMAC_SHA256_HEXADECIMAL);
+
+        runner.assertNotValid();
+    }
+
+    @Test
+    void testNotValidWithoutProvidedMac() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, HMAC_SHA256);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, TEST_SECRET_KEY_HEXADECIMAL);
+
+        runner.assertNotValid();
+    }
+
+    @Test
+    void testNotValidWhenSecretKeyEncodingIsHexadecimalButProvidedKeyIsNotValidHexadecimal() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, HMAC_SHA256);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, "not_hexadecimal");
+        runner.setProperty(VerifyContentMAC.SECRET_KEY_ENCODING, Encoding.HEXADECIMAL.name());
+        runner.setProperty(VerifyContentMAC.MAC, CONTENT_HMAC_SHA256_HEXADECIMAL);
+
+        runner.assertNotValid();
+    }
+
+    @Test
+    void testNotValidWhenSecretKeyEncodingIsBase64ButProvidedKeyIsNotValidBase64() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, HMAC_SHA256);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, "not_base64");
+        runner.setProperty(VerifyContentMAC.SECRET_KEY_ENCODING, Encoding.BASE64.name());
+        runner.setProperty(VerifyContentMAC.MAC, CONTENT_HMAC_SHA256_HEXADECIMAL);
+
+        runner.assertNotValid();
+    }
+
+    @ParameterizedTest(name = "macAlgorithm={0} secretKeyEncoding={1} secretKey={2} inputMac={3}")
+    @MethodSource("invalidConstructorArguments")
+    void testFlowFileTransferredToSuccessWhenMacMatch(String macAlgorithm, Encoding secretKeyEncoding, String secretKey, String inputMac, Encoding macEncoding) {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, macAlgorithm);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, secretKey);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY_ENCODING, secretKeyEncoding.name());
+        runner.setProperty(VerifyContentMAC.MAC_ENCODING, macEncoding.name());
+        runner.setProperty(VerifyContentMAC.MAC, inputMac);
+
+        runner.enqueue(FLOW_CONTENT);
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(SUCCESS);
+        Map<String, String> expectedAttributes = new HashMap<>();
+        expectedAttributes.put(MAC_CALCULATED_ATTRIBUTE, inputMac);
+        expectedAttributes.put(MAC_ALGORITHM_ATTRIBUTE, macAlgorithm);
+        expectedAttributes.put(MAC_ENCODING_ATTRIBUTE, macEncoding.name());
+
+        runner.assertAttributes(SUCCESS, expectedAttributes.keySet(), Collections.singleton(expectedAttributes));
+    }
+
+    private static Stream<Arguments> invalidConstructorArguments() {
+        return Stream.of(
+            Arguments.of(HMAC_SHA256, Encoding.UTF8, TEST_SECRET_KEY_UTF8, CONTENT_HMAC_SHA256_HEXADECIMAL, Encoding.HEXADECIMAL),
+            Arguments.of(HMAC_SHA512, Encoding.UTF8, TEST_SECRET_KEY_UTF8, CONTENT_HMAC_SHA512_HEXADECIMAL, Encoding.HEXADECIMAL),
+            Arguments.of(HMAC_SHA256, Encoding.UTF8, TEST_SECRET_KEY_UTF8, CONTENT_HMAC_SHA256_BASE64, Encoding.BASE64),
+            Arguments.of(HMAC_SHA512, Encoding.UTF8, TEST_SECRET_KEY_UTF8, CONTENT_HMAC_SHA512_BASE64, Encoding.BASE64),
+            Arguments.of(HMAC_SHA256, Encoding.HEXADECIMAL, TEST_SECRET_KEY_HEXADECIMAL, CONTENT_HMAC_SHA256_HEXADECIMAL, Encoding.HEXADECIMAL),
+            Arguments.of(HMAC_SHA512, Encoding.HEXADECIMAL, TEST_SECRET_KEY_HEXADECIMAL, CONTENT_HMAC_SHA512_HEXADECIMAL, Encoding.HEXADECIMAL),
+            Arguments.of(HMAC_SHA256, Encoding.HEXADECIMAL, TEST_SECRET_KEY_HEXADECIMAL, CONTENT_HMAC_SHA256_BASE64, Encoding.BASE64),
+            Arguments.of(HMAC_SHA512, Encoding.HEXADECIMAL, TEST_SECRET_KEY_HEXADECIMAL, CONTENT_HMAC_SHA512_BASE64, Encoding.BASE64),
+            Arguments.of(HMAC_SHA256, Encoding.BASE64, TEST_SECRET_KEY_BASE64, CONTENT_HMAC_SHA256_HEXADECIMAL, Encoding.HEXADECIMAL),
+            Arguments.of(HMAC_SHA512, Encoding.BASE64, TEST_SECRET_KEY_BASE64, CONTENT_HMAC_SHA512_HEXADECIMAL, Encoding.HEXADECIMAL),
+            Arguments.of(HMAC_SHA256, Encoding.BASE64, TEST_SECRET_KEY_BASE64, CONTENT_HMAC_SHA256_BASE64, Encoding.BASE64),
+            Arguments.of(HMAC_SHA512, Encoding.BASE64, TEST_SECRET_KEY_BASE64, CONTENT_HMAC_SHA512_BASE64, Encoding.BASE64)
+        );
+    }
+
+    @Test
+    void testFlowFileTransferredToFailureInCaseOfVerificationFailure() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, HMAC_SHA256);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, TEST_SECRET_KEY_HEXADECIMAL);
+        runner.setProperty(VerifyContentMAC.MAC, CONTENT_HMAC_SHA512_HEXADECIMAL);
+
+        runner.enqueue(FLOW_CONTENT);
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(FAILURE);
+    }
+
+    @Test
+    void testFlowFileTransferredToFailureInCaseOfException() {
+        runner.setProperty(VerifyContentMAC.MAC_ALGORITHM, HMAC_SHA256);
+        runner.setProperty(VerifyContentMAC.SECRET_KEY, TEST_SECRET_KEY_HEXADECIMAL);
+        runner.setProperty(VerifyContentMAC.MAC, INVALID);
+
+        runner.enqueue(FLOW_CONTENT);
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(FAILURE);
+    }
+
+}
\ No newline at end of file