All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.infinispan.persistence.dynamodb.ItemHMAC Maven / Gradle / Ivy

There is a newer version: 7.0
Show newest version
package com.nimbusds.infinispan.persistence.dynamodb;


import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.amazonaws.services.dynamodbv2.document.Item;
import net.jcip.annotations.Immutable;
import org.erdtman.jcs.JsonCanonicalizer;

import com.nimbusds.common.store.StoreException;


/**
 * HMAC SHA-256 facility for providing integrity and authenticity to DynamoDB
 * items.
 */
@Immutable
final class ItemHMAC {
	
	
	/**
	 * The attribute used to store the HMAC SHA-256 in binary format.
	 */
	public static final String ATTRIBUTE_NAME = "_hmac#s256";
	
	
	/**
	 * The HMAC key, {@code null} if disabled.
	 */
	private final SecretKey hmacKey;
	
	
	/**
	 * Creates a new HMAC SHA-256 facility.
	 *
	 * @param hmacKeyBase64Encoded The BASE 64 encoded HMAC SHA-256 key
	 *                             with at least 256 bits, {@code null} if
	 *                             disabled.
	 */
	public ItemHMAC(final String hmacKeyBase64Encoded)
		throws InvalidKeyException {
		
		this (hmacKeyBase64Encoded != null ? Base64.getDecoder().decode(hmacKeyBase64Encoded) : null);
	}
	
	
	/**
	 * Creates a new HMAC SHA-256 facility.
	 *
	 * @param hmacKeyBytes The HMAC SHA-256 key with at least 256 bits,
	 *                     {@code null} if disabled.
	 */
	public ItemHMAC(final byte[] hmacKeyBytes)
		throws InvalidKeyException {
		
		this(hmacKeyBytes != null ? new SecretKeySpec(hmacKeyBytes, "HmacSHA256") : null);
	}
	
	
	/**
	 * Creates a new HMAC SHA-256 facility.
	 *
	 * @param hmacKey The HMAC SHA-256 key with JCA algorithm "HmacSHA256"
	 *                and at least 256 bits, {@code null} if disabled.
	 */
	public ItemHMAC(final SecretKey hmacKey)
		throws InvalidKeyException {
		
		this.hmacKey = hmacKey;
		
		if (hmacKey == null) {
			return;
		}
		
		if (hmacKey.getEncoded().length < (256 / 8)) {
			throw new InvalidKeyException("The HMAC SHA-256 key must be at least 256 bits long");
		}
	}
	
	
	/**
	 * Computes the HMAC SHA-256 for the specified DynamoDB item.
	 *
	 * @param item The DynamoDB item.
	 *
	 * @return The HMAC SHA-256 bytes, {@code null} if HMAC is disabled.
	 */
	public byte[] compute(final Item item)
		throws NoSuchAlgorithmException, InvalidKeyException {
		
		if (hmacKey == null) {
			return null;
		}
		
		JsonCanonicalizer jc;
		try {
			jc = new JsonCanonicalizer(item.toJSON());
		} catch (IOException e) {
			throw new StoreException(e.getMessage(), e);
		}
		
		// https://tools.ietf.org/html/rfc8785
		byte[] hmacInput = jc.getEncodedString().getBytes(StandardCharsets.UTF_8);
		
		Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
		hmacSHA256.init(hmacKey);
		return hmacSHA256.doFinal(hmacInput);
	}
	
	
	/**
	 * Applies an optional HMAC SHA-256 to the specified DynamoDB item
	 * which is included as the {@link #ATTRIBUTE_NAME _hmac#S256}
	 * attribute.
	 *
	 * @param item The DynamoDB item.
	 *
	 * @return The DynamoDB item with included HMAC SHA-256, unmodified if
	 *         HMAC is disabled.
	 */
	public Item apply(final Item item)
		throws InvalidKeyException, NoSuchAlgorithmException {
		
		byte[] hmac = compute(item);
		
		if (hmac == null) {
			return item;
		}
		
		return item.withBinary(ATTRIBUTE_NAME, hmac);
	}
	
	
	/**
	 * Verifies the optional HMAC HMAC SHA-256 for the specified DynamoDB
	 * item.
	 *
	 * @param item The DynamoDB item, with optional HMAC SHA-256.
	 *
	 * @return The DynamoDB item, with removed HMAC SHA-256 attribute if
	 *         HMAC is enabled, unmodified if HMAC is disabled.
	 *
	 * @throws InvalidHMACException If the HMAC SHA-256 check failed.
	 */
	public Item verify(final Item item)
		throws InvalidHMACException, InvalidKeyException, NoSuchAlgorithmException {
		
		if (hmacKey == null) {
			return item;
		}
		
		if (! item.hasAttribute(ATTRIBUTE_NAME)) {
			throw new InvalidHMACException("Missing item HMAC attribute: " + ATTRIBUTE_NAME);
		}
		
		byte[] storedHMAC = item.getBinary(ATTRIBUTE_NAME);
		
		Item baseItem = item.removeAttribute(ATTRIBUTE_NAME);
		
		byte[] computedHMAC = compute(baseItem);
		
		if (! MessageDigest.isEqual(storedHMAC, computedHMAC)) {
			
			throw new InvalidHMACException(
				"Invalid item HMAC:" +
				" Stored: " + Base64.getEncoder().encodeToString(storedHMAC) +
				" Computed: " + Base64.getEncoder().encodeToString(computedHMAC) +
				" Item: " + item.toJSON()
			);
		}
		
		return baseItem;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy