// Package cryptocore wraps OpenSSL and Go GCM crypto and provides
// a nonce generator.
package cryptocore

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha512"
	"fmt"
	"log"
	"runtime"

	"github.com/rfjakob/eme"

	"github.com/rfjakob/gocryptfs/internal/siv_aead"
	"github.com/rfjakob/gocryptfs/internal/stupidgcm"
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

// AEADTypeEnum indicates the type of AEAD backend in use.
type AEADTypeEnum int

const (
	// KeyLen is the cipher key length in bytes.  32 for AES-256.
	KeyLen = 32
	// AuthTagLen is the length of a GCM auth tag in bytes.
	AuthTagLen = 16

	_ = iota // Skip zero
	// BackendOpenSSL specifies the OpenSSL backend.
	BackendOpenSSL AEADTypeEnum = iota
	// BackendGoGCM specifies the Go based GCM backend.
	BackendGoGCM AEADTypeEnum = iota
	// BackendAESSIV specifies an AESSIV backend.
	BackendAESSIV AEADTypeEnum = iota
)

// CryptoCore is the low level crypto implementation.
type CryptoCore struct {
	// EME is used for filename encryption.
	EMECipher *eme.EMECipher
	// GCM or AES-SIV. This is used for content encryption.
	AEADCipher cipher.AEAD
	// Which backend is behind AEADCipher?
	AEADBackend AEADTypeEnum
	// GCM needs unique IVs (nonces)
	IVGenerator *nonceGenerator
	IVLen       int
}

// New returns a new CryptoCore object or panics.
//
// Even though the "GCMIV128" feature flag is now mandatory, we must still
// support 96-bit IVs here because they were used for encrypting the master
// key in gocryptfs.conf up to gocryptfs v1.2. v1.3 switched to 128 bits.
func New(key []byte, aeadType AEADTypeEnum, IVBitLen int, useHKDF bool, forceDecode bool) *CryptoCore {
	if len(key) != KeyLen {
		log.Panic(fmt.Sprintf("Unsupported key length %d", len(key)))
	}
	// We want the IV size in bytes
	IVLen := IVBitLen / 8

	// Initialize EME for filename encryption.
	var emeCipher *eme.EMECipher
	{
		emeKey := key
		if useHKDF {
			emeKey = hkdfDerive(key, hkdfInfoEMENames, KeyLen)
		}
		emeBlockCipher, err := aes.NewCipher(emeKey)
		if err != nil {
			log.Panic(err)
		}
		emeCipher = eme.New(emeBlockCipher)
	}

	// Initialize an AEAD cipher for file content encryption.
	var aeadCipher cipher.AEAD
	if aeadType == BackendOpenSSL || aeadType == BackendGoGCM {
		gcmKey := key
		if useHKDF {
			gcmKey = hkdfDerive(key, hkdfInfoGCMContent, KeyLen)
		}
		switch aeadType {
		case BackendOpenSSL:
			if IVLen != 16 {
				log.Panic("stupidgcm only supports 128-bit IVs")
			}
			// stupidgcm does not create a private copy of the key, so things
			// break when initFuseFrontend() overwrites it with zeros. Create
			// a copy here. This is unnecessary when useHKDF == true, but
			// does no harm.
			var stupidgcmKey []byte
			stupidgcmKey = append(stupidgcmKey, gcmKey...)
			aeadCipher = stupidgcm.New(stupidgcmKey, forceDecode)
		case BackendGoGCM:
			goGcmBlockCipher, err := aes.NewCipher(gcmKey)
			if err != nil {
				log.Panic(err)
			}
			aeadCipher, err = cipher.NewGCMWithNonceSize(goGcmBlockCipher, IVLen)
			if err != nil {
				log.Panic(err)
			}
		}
	} else if aeadType == BackendAESSIV {
		if IVLen != 16 {
			// SIV supports any nonce size, but we only use 16.
			log.Panic("AES-SIV must use 16-byte nonces")
		}
		var key64 []byte
		if useHKDF {
			key64 = hkdfDerive(key, hkdfInfoSIVContent, siv_aead.KeyLen)
		} else {
			// AES-SIV uses 1/2 of the key for authentication, 1/2 for
			// encryption, so we need a 64-bytes key for AES-256. Derive it from
			// the master key by hashing it with SHA-512.
			s := sha512.Sum512(key)
			key64 = s[:]
		}
		aeadCipher = siv_aead.New(key64)
	} else {
		log.Panic("unknown backend cipher")
	}

	return &CryptoCore{
		EMECipher:   emeCipher,
		AEADCipher:  aeadCipher,
		AEADBackend: aeadType,
		IVGenerator: &nonceGenerator{nonceLen: IVLen},
		IVLen:       IVLen,
	}
}

// Wipe tries to wipe secret keys from memory by overwriting them with zeros
// and/or setting references to nil.
//
// This is not bulletproof due to possible GC copies, but
// still raises to bar for extracting the key.
func (c *CryptoCore) Wipe() {
	if c.AEADBackend == BackendOpenSSL {
		tlog.Debug.Print("CryptoCore.Wipe: Wiping stupidgcm key")
		// We don't use "x, ok :=" because we *want* to crash loudly if the
		// type assertion fails (it should never fail).
		sgcm := c.AEADCipher.(*stupidgcm.StupidGCM)
		sgcm.Wipe()
	} else {
		tlog.Debug.Print("CryptoCore.Wipe: niling stdlib refs")
	}
	// We have no access to the keys (or key-equivalents) stored inside the
	// Go stdlib. Best we can is to nil the references and force a GC.
	c.AEADCipher = nil
	c.EMECipher = nil
	runtime.GC()
}