001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2013-2015 ForgeRock AS. 015 */ 016 017package org.forgerock.json.jose.jws.handlers; 018 019import org.forgerock.json.jose.exceptions.JwsSigningException; 020import org.forgerock.json.jose.jws.JwsAlgorithm; 021import org.forgerock.json.jose.utils.Utils; 022import org.forgerock.util.Reject; 023 024import java.security.InvalidKeyException; 025import java.security.MessageDigest; 026import java.security.NoSuchAlgorithmException; 027import javax.crypto.Mac; 028import javax.crypto.SecretKey; 029import javax.crypto.spec.SecretKeySpec; 030 031/** 032 * An implementation of the SigningHandler which can sign and verify using algorithms from the HMAC family. 033 * 034 * @since 2.0.0 035 */ 036public class HmacSigningHandler implements SigningHandler { 037 038 private final byte[] sharedSecret; 039 040 /** 041 * Constructs a new HmacSigningHandler. 042 * 043 * @param sharedSecret The shared secret to use to sign the data. 044 */ 045 public HmacSigningHandler(byte[] sharedSecret) { 046 Reject.ifNull(sharedSecret, "Shared secret cannot be null."); 047 this.sharedSecret = sharedSecret.clone(); 048 } 049 050 /** 051 * {@inheritDoc} 052 */ 053 @Override 054 public byte[] sign(JwsAlgorithm algorithm, String data) { 055 return signWithHMAC(algorithm.getAlgorithm(), sharedSecret, data.getBytes(Utils.CHARSET)); 056 } 057 058 /** 059 * {@inheritDoc} 060 */ 061 @Override 062 public byte[] sign(final JwsAlgorithm algorithm, final byte[] data) { 063 return signWithHMAC(algorithm.getAlgorithm(), sharedSecret, data); 064 } 065 066 /** 067 * Performs the creation of the MAC for the data using the given Java Cryptographic algorithm. 068 * 069 * @param algorithm The Java Cryptographic algorithm. 070 * @param sharedSecret The shared secret to use to sign the data. 071 * @param data The data to sign. 072 * @return A byte array of the signature. 073 */ 074 private byte[] signWithHMAC(String algorithm, byte[] sharedSecret, byte[] data) { 075 try { 076 Mac mac = Mac.getInstance(algorithm); 077 SecretKey secretKey = new SecretKeySpec(sharedSecret, algorithm.toUpperCase()); 078 mac.init(secretKey); 079 return mac.doFinal(data); 080 } catch (NoSuchAlgorithmException e) { 081 throw new JwsSigningException("Unsupported Signing Algorithm, " + algorithm, e); 082 } catch (InvalidKeyException e) { 083 throw new JwsSigningException(e); 084 } 085 } 086 087 /** 088 * Verifies that the given signature is valid for the given data. 089 * <p> 090 * Uses the Java Cryptographic algorithm defined by the JwsAlgorithm and private key to create a new signature 091 * of the data to compare against the given signature to see if they are identical. 092 * 093 * This implementation avoids timing attacks by enforcing checking of each element of the 094 * array against one another. We do not rely on Arrays.equal or other methods which 095 * may return early upon discovering a mistake. 096 * 097 * @param algorithm The JwsAlgorithm defining the JavaCryptographic algorithm. 098 * @param data The data that was signed. 099 * @param signature The signature of the data. 100 * @return <code>true</code> if the signature is a valid signature of the data. 101 */ 102 @Override 103 public boolean verify(JwsAlgorithm algorithm, byte[] data, byte[] signature) { 104 byte[] signed = signWithHMAC(algorithm.getAlgorithm(), sharedSecret, data); 105 return MessageDigest.isEqual(signed, signature); 106 } 107}