package org.jboss.resteasy.jose.jwe.crypto;


import org.jboss.resteasy.jose.Base64Url;
import org.jboss.resteasy.jose.jwe.Algorithm;
import org.jboss.resteasy.jose.jwe.CompressionAlgorithm;
import org.jboss.resteasy.jose.jwe.EncryptionMethod;

import javax.crypto.SecretKey;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;


/**
 * Direct encrypter with a
 * shared symmetric key. This class is thread-safe.
 * <p/>
 * <p>Supports the following JWE algorithms:
 * <p/>
 * <ul>
 * <li>DIR
 * </ul>
 * <p/>
 * <p>Supports the following encryption methods:
 * <p/>
 * <ul>
 * <li>A128CBC_HS256}
 * <li>A256CBC_HS512}
 * <li>A128GCM}
 * <li>A256GCM}
 * </ul>
 *
 * @author Vladimir Dzhuvinov
 * @version $version$ (2013-05-29)
 */
public class DirectEncrypter
{


   /**
    * Random byte generator.
    */
   private static SecureRandom randomGen;


   /**
    * Initialises the secure random byte generator.
    *
    * @throws RuntimeException If the secure random byte generator couldn't
    *                          be instantiated.
    */
   private static void initSecureRandom()
   {

      try
      {
         randomGen = SecureRandom.getInstance("SHA1PRNG");

      }
      catch (NoSuchAlgorithmException e)
      {

         throw new RuntimeException(e.getMessage(), e);
      }
   }


   public static String encrypt(EncryptionMethod enc, CompressionAlgorithm compressionAlgorithm, String encodedJWEHeader, final SecretKey key, final byte[] bytes)
   {

      if (randomGen == null) initSecureRandom();

      if (enc.getCekBitLength() != key.getEncoded().length * 8)
      {

         throw new RuntimeException("The Content Encryption Key (CEK) length must be " + enc.getCekBitLength() + " bits for " + enc + " encryption");
      }



      // Apply compression if instructed
      byte[] plainText = DeflateHelper.applyCompression(compressionAlgorithm, bytes);


      // Compose the AAD
      byte[] aad = encodedJWEHeader.getBytes(Charset.forName("UTF-8"));


      // Encrypt the plain text according to the JWE enc
      byte[] iv;
      AuthenticatedCipherText authCipherText;

      if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512))
      {

         iv = AESCBC.generateIV(randomGen);

         authCipherText = AESCBC.encryptAuthenticated(key, iv, plainText, aad);

      }
      else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM))
      {

         iv = AESGCM.generateIV(randomGen);

         authCipherText = AESGCM.encrypt(key, iv, plainText, aad);

      }
      else
      {

         throw new RuntimeException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM");
      }

      StringBuilder builder = new StringBuilder(encodedJWEHeader)
              .append('.')
              .append('.').append(Base64Url.encode(iv))
              .append('.').append(Base64Url.encode(authCipherText.getCipherText()))
              .append('.').append(Base64Url.encode(authCipherText.getAuthenticationTag()));

      return builder.toString();
   }
}