// Copyright 2020 Google LLC
//
// 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 subtle implements the key wrapping primitive KWP defined in
// NIST SP 800 38f.
//
// The same encryption mode is also defined in RFC 5649. The NIST document is
// used here as a primary reference, since it contains a security analysis and
// further recommendations. In particular, Section 8 of NIST SP 800 38f
// suggests that the allowed key sizes may be restricted. The implementation in
// this package requires that the key sizes are in the range MinWrapSize and
// MaxWrapSize.
//
// The minimum of 16 bytes has been chosen, because 128 bit keys are the
// smallest key sizes used in tink. Additionally, wrapping short keys with KWP
// does not use the function W and hence prevents using security arguments
// based on the assumption that W is a strong pseudorandom. One consequence of
// using a strong pseudorandom permutation as an underlying function is that
// leaking partial information about decrypted bytes is not useful for an
// attack.
//
// The upper bound for the key size is somewhat arbitrary. Setting an upper
// bound is motivated by the analysis in section A.4 of NIST SP 800 38f:
// forgery of long messages is simpler than forgery of short messages.
package subtle

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/binary"
	"fmt"
	"math"

	// Placeholder for internal crypto/cipher allowlist, please ignore.
)

const (
	// MinWrapSize is the smallest key byte length that may be wrapped.
	MinWrapSize = 16
	// MaxWrapSize is the largest key byte length that may be wrapped.
	MaxWrapSize = 8192

	roundCount = 6
	ivPrefix   = uint32(0xA65959A6)
)

// KWP is an implementation of an AES-KWP key wrapping cipher.
type KWP struct {
	block cipher.Block
}

// NewKWP returns a KWP instance.
//
// The key argument should be the AES wrapping key, either 16 or 32 bytes
// to select AES-128 or AES-256.
func NewKWP(wrappingKey []byte) (*KWP, error) {
	switch len(wrappingKey) {
	default:
		return nil, fmt.Errorf("kwp: invalid AES key size; want 16 or 32, got %d", len(wrappingKey))
	case 16, 32:
		block, err := aes.NewCipher(wrappingKey)
		if err != nil {
			return nil, fmt.Errorf("kwp: error building AES cipher: %v", err)
		}
		return &KWP{block: block}, nil
	}
}

// wrappingSize computes the byte length of the ciphertext output for the
// provided plaintext input.
func wrappingSize(inputSize int) int {
	paddingSize := 7 - (inputSize+7)%8
	return inputSize + paddingSize + 8
}

// computeW computes the pseudorandom permutation W over the IV concatenated
// with zero-padded key material.
func (kwp *KWP) computeW(iv, key []byte) ([]byte, error) {
	// Checks the parameter sizes for which W is defined.
	// Note that the caller ensures stricter limits.
	if len(key) <= 8 || len(key) > math.MaxInt32-16 || len(iv) != 8 {
		return nil, fmt.Errorf("kwp: computeW called with invalid parameters")
	}

	data := make([]byte, wrappingSize(len(key)))
	copy(data, iv)
	copy(data[8:], key)
	blockCount := len(data)/8 - 1

	buf := make([]byte, 16)
	copy(buf, data[:8])

	for i := 0; i < roundCount; i++ {
		for j := 0; j < blockCount; j++ {

			copy(buf[8:], data[8*(j+1):])
			kwp.block.Encrypt(buf, buf)

			// xor the round constant in big endian order
			// to the left half of the buffer
			roundConst := uint(i*blockCount + j + 1)
			for b := 0; b < 4; b++ {
				buf[7-b] ^= byte(roundConst & 0xFF)
				roundConst >>= 8
			}

			copy(data[8*(j+1):], buf[8:])
		}
	}
	copy(data[:8], buf)
	return data, nil
}

// invertW computes the inverse of the pseudorandom permutation W. Note that
// invertW does not perform an integrity check.
func (kwp *KWP) invertW(wrapped []byte) ([]byte, error) {
	// Checks the input size for which invertW is defined.
	// Note that the caller ensures stricter limits.
	if len(wrapped) < 24 || len(wrapped)%8 != 0 {
		return nil, fmt.Errorf("kwp: incorrect data size")
	}

	data := make([]byte, len(wrapped))
	copy(data, wrapped)

	blockCount := len(data)/8 - 1

	buf := make([]byte, 16)
	copy(buf, data[:8])

	for i := roundCount - 1; i >= 0; i-- {
		for j := blockCount - 1; j >= 0; j-- {
			copy(buf[8:], data[8*(j+1):])

			// xor the round constant in big endian order
			// to the left half of the buffer
			roundConst := uint(i*blockCount + j + 1)
			for b := 0; b < 4; b++ {
				buf[7-b] ^= byte(roundConst & 0xFF)
				roundConst >>= 8
			}

			kwp.block.Decrypt(buf, buf)
			copy(data[8*(j+1):], buf[8:])
		}
	}

	copy(data, buf[:8])
	return data, nil
}

// Wrap wraps the provided key material.
func (kwp *KWP) Wrap(data []byte) ([]byte, error) {
	if len(data) < MinWrapSize {
		return nil, fmt.Errorf("kwp: key size to wrap too small")
	}
	if len(data) > MaxWrapSize {
		return nil, fmt.Errorf("kwp: key size to wrap too large")
	}

	iv := make([]byte, 8)
	binary.BigEndian.PutUint32(iv, ivPrefix)
	binary.BigEndian.PutUint32(iv[4:], uint32(len(data)))

	return kwp.computeW(iv, data)
}

var errIntegrity = fmt.Errorf("kwp: unwrap failed integrity check")

// Unwrap unwraps a wrapped key.
func (kwp *KWP) Unwrap(data []byte) ([]byte, error) {
	if len(data) < wrappingSize(MinWrapSize) {
		return nil, fmt.Errorf("kwp: wrapped key size too small")
	}
	if len(data) > wrappingSize(MaxWrapSize) {
		return nil, fmt.Errorf("kwp: wrapped key size too large")
	}
	if len(data)%8 != 0 {
		return nil, fmt.Errorf("kwp: wrapped key size must be a multiple of 8 bytes")
	}

	unwrapped, err := kwp.invertW(data)
	if err != nil {
		return nil, err
	}

	// Check the IV and padding.
	// W has been designed to be strong pseudorandom permutation, so
	// leaking information about improperly padded keys would not be a
	// vulnerability. This means we don't have to go to extra lengths to
	// ensure that the integrity checks run in constant time.

	if binary.BigEndian.Uint32(unwrapped) != ivPrefix {
		return nil, errIntegrity
	}

	encodedSize := int(binary.BigEndian.Uint32(unwrapped[4:]))
	if encodedSize < 0 || wrappingSize(encodedSize) != len(unwrapped) {
		return nil, errIntegrity
	}

	for i := 8 + encodedSize; i < len(unwrapped); i++ {
		if unwrapped[i] != 0 {
			return nil, errIntegrity
		}
	}

	return unwrapped[8 : 8+encodedSize], nil
}
