// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 siv

import (
	"crypto/aes"
	"crypto/cipher"
	"fmt"
)

func dup(d []byte) []byte {
	result := make([]byte, len(d))
	copy(result, d)
	return result
}

// Given a key and plaintext, encrypt the plaintext using the SIV mode of AES,
// as defined by RFC 5297, append the result (including both the synthetic
// initialization vector and the ciphertext) to dst, and return the updated
// slice. The output can later be fed to Decrypt to recover the plaintext.
//
// In addition to confidentiality, this function also offers authenticity. That
// is, without the secret key an attacker is unable to construct a byte string
// that Decrypt will accept.
//
// The supplied key must be 32, 48, or 64 bytes long.
//
// The supplied associated data, up to 126 strings, is also authenticated,
// though it is not included in the ciphertext. The user must supply the same
// associated data to Decrypt in order for the Decrypt call to succeed. If no
// associated data is desired, pass an empty slice.
//
// If the same key, plaintext, and associated data are supplied to this
// function multiple times, the output is guaranteed to be identical. As per
// RFC 5297 section 3, you may use this function for nonce-based authenticated
// encryption by passing a nonce as the last associated data element.
func Encrypt(dst, key, plaintext []byte, associated [][]byte) ([]byte, error) {
	keyLen := len(key)
	associatedLen := len(associated)

	// The output will consist of the current contents of dst, followed by the IV
	// generated by s2v, followed by the ciphertext (which is the same size as
	// the plaintext).
	//
	// Make sure dst is long enough, then carve it up.
	var iv []byte
	var ciphertext []byte
	{
		dstSize := len(dst)
		dstAndIVSize := dstSize + s2vSize
		outputSize := dstAndIVSize + len(plaintext)

		if cap(dst) < outputSize {
			tmp := make([]byte, dstSize, outputSize+outputSize/4)
			copy(tmp, dst)
			dst = tmp
		}

		dst = dst[:outputSize]
		iv = dst[dstSize:dstAndIVSize]
		ciphertext = dst[dstAndIVSize:outputSize]
	}

	// Make sure the key length is legal.
	switch keyLen {
	case 32, 48, 64:
	default:
		return nil, fmt.Errorf("SIV requires a 32-, 48-, or 64-byte key.")
	}

	// Make sure the number of associated data is legal, per RFC 5297 section 7.
	if associatedLen > 126 {
		return nil, fmt.Errorf("len(associated) may be no more than 126.")
	}

	// Derive subkeys.
	k1 := key[:keyLen/2]
	k2 := key[keyLen/2:]

	// Call S2V to derive the synthetic initialization vector. Use the ciphertext
	// output buffer as scratch space, since it's the same length as the final
	// string.
	s2vStrings := make([][]byte, associatedLen+1)
	copy(s2vStrings, associated)
	s2vStrings[associatedLen] = plaintext

	v := s2v(k1, s2vStrings, ciphertext)
	if len(v) != len(iv) {
		panic(fmt.Sprintf("Unexpected vector: %v", v))
	}

	copy(iv, v)

	// Create a CTR cipher using a version of v with the 31st and 63rd bits
	// zeroed out.
	q := dup(v)
	q[aes.BlockSize-4] &= 0x7f
	q[aes.BlockSize-8] &= 0x7f

	ciph, err := aes.NewCipher(k2)
	if err != nil {
		return nil, fmt.Errorf("aes.NewCipher: %v", err)
	}

	ctrCiph := cipher.NewCTR(ciph, q)

	// Fill in the ciphertext.
	ctrCiph.XORKeyStream(ciphertext, plaintext)

	return dst, nil
}
