//
// Copyright (c) SAS Institute 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 certloader

import (
	"bytes"
	"crypto"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"golang.org/x/crypto/openpgp/packet"

	"github.com/sassoftware/relic/v7/lib/passprompt"
)

// Parse and decrypt a private key. It can be a RSA or ECDA key in PKCS#1 or
// PKCS#8 format and DER or PEM encoding, or it can be a PGP private key. If
// the private key is encrypted then the given prompter will be invoked to ask
// for the passphrase, if provided.
func ParseAnyPrivateKey(blob []byte, prompt passprompt.PasswordGetter) (crypto.PrivateKey, error) {
	if bytes.HasPrefix(blob, []byte("-----BEGIN PGP")) {
		return parsePgpPrivateKey(blob, prompt)
	} else if bytes.HasPrefix(blob, []byte("-----BEGIN")) {
		var block *pem.Block
		for {
			block, blob = pem.Decode(blob)
			if block == nil {
				break
			} else if block.Type == "PRIVATE KEY" || strings.HasSuffix(block.Type, " PRIVATE KEY") {
				return parsePemPrivateKey(block, prompt)
			}
		}
		return nil, errors.New("failed to find any private keys in PEM data")
	} else if blob[0] == asn1Magic {
		return parsePrivateKey(blob)
	} else if blob[0]&0x80 != 0 {
		return parsePgpPrivateKey(blob, prompt)
	} else {
		return nil, errors.New("unrecognized private key format")
	}
}

func parsePemPrivateKey(block *pem.Block, prompt passprompt.PasswordGetter) (crypto.PrivateKey, error) {
	if !x509.IsEncryptedPEMBlock(block) { //nolint:staticcheck
		return parsePrivateKey(block.Bytes)
	}
	if prompt == nil {
		return nil, errors.New("private key is encrypted and no password was provided")
	}
	for {
		password, err := prompt.GetPasswd("Password for private key: ")
		if err != nil {
			return nil, err
		} else if password == "" {
			return nil, errors.New("aborted")
		}
		keyblob, err := x509.DecryptPEMBlock(block, []byte(password)) //nolint:staticcheck
		if err == x509.IncorrectPasswordError {
			continue
		} else if err != nil {
			return nil, err
		} else {
			return parsePrivateKey(keyblob)
		}
	}
}

func parsePgpPrivateKey(blob []byte, prompt passprompt.PasswordGetter) (crypto.PrivateKey, error) {
	var reader io.Reader = bytes.NewReader(blob)
	if blob[0] == '-' {
		block, err := armor.Decode(reader)
		if err != nil {
			return nil, err
		}
		reader = block.Body
	}
	entity, err := openpgp.ReadEntity(packet.NewReader(reader))
	if err != nil {
		return nil, err
	}
	if entity.PrivateKey == nil {
		return nil, errors.New("file does not contain a private key")
	}
	if entity.PrivateKey.Encrypted {
		fmt.Fprintln(os.Stderr, "Key fingerprint:", entity.PrimaryKey.KeyIdString())
		for name := range entity.Identities {
			fmt.Fprintln(os.Stderr, "UID:", name)
		}
		fmt.Fprintln(os.Stderr)
		for {
			password, err := prompt.GetPasswd("Passphrase for key: ")
			if err != nil {
				return nil, err
			} else if password == "" {
				return nil, errors.New("Aborted")
			}
			err = entity.PrivateKey.Decrypt([]byte(password))
			if err == nil {
				break
			}
		}
	}
	return entity.PrivateKey.PrivateKey, nil
}
