/*-
 * Copyright 2018 Square Inc.
 *
 * 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 jose

// OpaqueSigner is an interface that supports signing payloads with opaque
// private key(s). Private key operations performed by implementers may, for
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
// transparently to the user of this interface.
type OpaqueSigner interface {
	// Public returns the public key of the current signing key.
	Public() *JSONWebKey
	// Algs returns a list of supported signing algorithms.
	Algs() []SignatureAlgorithm
	// SignPayload signs a payload with the current signing key using the given
	// algorithm.
	SignPayload(payload []byte, alg SignatureAlgorithm) ([]byte, error)
}

type opaqueSigner struct {
	signer OpaqueSigner
}

func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigInfo, error) {
	var algSupported bool
	for _, salg := range signer.Algs() {
		if alg == salg {
			algSupported = true
			break
		}
	}
	if !algSupported {
		return recipientSigInfo{}, ErrUnsupportedAlgorithm
	}

	return recipientSigInfo{
		sigAlg:    alg,
		publicKey: signer.Public,
		signer: &opaqueSigner{
			signer: signer,
		},
	}, nil
}

func (o *opaqueSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
	out, err := o.signer.SignPayload(payload, alg)
	if err != nil {
		return Signature{}, err
	}

	return Signature{
		Signature: out,
		protected: &rawHeader{},
	}, nil
}

// OpaqueVerifier is an interface that supports verifying payloads with opaque
// public key(s). An OpaqueSigner may rotate signing keys transparently to the
// user of this interface.
type OpaqueVerifier interface {
	VerifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
}

type opaqueVerifier struct {
	verifier OpaqueVerifier
}

func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
	return o.verifier.VerifyPayload(payload, signature, alg)
}

// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
type OpaqueKeyEncrypter interface {
	// KeyID returns the kid
	KeyID() string
	// Algs returns a list of supported key encryption algorithms.
	Algs() []KeyAlgorithm
	// encryptKey encrypts the CEK using the given algorithm.
	encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error)
}

type opaqueKeyEncrypter struct {
	encrypter OpaqueKeyEncrypter
}

func newOpaqueKeyEncrypter(alg KeyAlgorithm, encrypter OpaqueKeyEncrypter) (recipientKeyInfo, error) {
	var algSupported bool
	for _, salg := range encrypter.Algs() {
		if alg == salg {
			algSupported = true
			break
		}
	}
	if !algSupported {
		return recipientKeyInfo{}, ErrUnsupportedAlgorithm
	}

	return recipientKeyInfo{
		keyID:  encrypter.KeyID(),
		keyAlg: alg,
		keyEncrypter: &opaqueKeyEncrypter{
			encrypter: encrypter,
		},
	}, nil
}

func (oke *opaqueKeyEncrypter) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
	return oke.encrypter.encryptKey(cek, alg)
}

//OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
type OpaqueKeyDecrypter interface {
	DecryptKey(encryptedKey []byte, header Header) ([]byte, error)
}

type opaqueKeyDecrypter struct {
	decrypter OpaqueKeyDecrypter
}

func (okd *opaqueKeyDecrypter) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
	mergedHeaders := rawHeader{}
	mergedHeaders.merge(&headers)
	mergedHeaders.merge(recipient.header)

	header, err := mergedHeaders.sanitized()
	if err != nil {
		return nil, err
	}

	return okd.decrypter.DecryptKey(recipient.encryptedKey, header)
}