// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package packet

import (
	"crypto"
	"crypto/rand"
	"io"
	"math/big"
	"time"

	"github.com/ProtonMail/go-crypto/openpgp/s2k"
)

var (
	defaultRejectPublicKeyAlgorithms = map[PublicKeyAlgorithm]bool{
		PubKeyAlgoElGamal: true,
		PubKeyAlgoDSA:     true,
	}
	defaultRejectHashAlgorithms = map[crypto.Hash]bool{
		crypto.MD5:       true,
		crypto.RIPEMD160: true,
	}
	defaultRejectMessageHashAlgorithms = map[crypto.Hash]bool{
		crypto.SHA1:      true,
		crypto.MD5:       true,
		crypto.RIPEMD160: true,
	}
	defaultRejectCurves = map[Curve]bool{
		CurveSecP256k1: true,
	}
)

// A global feature flag to indicate v5 support.
// Can be set via a build tag, e.g.: `go build -tags v5 ./...`
// If the build tag is missing config_v5.go will set it to true.
//
// Disables parsing of v5 keys and v5 signatures.
// These are non-standard entities, which in the crypto-refresh have been superseded
// by v6 keys, v6 signatures and SEIPDv2 encrypted data, respectively.
var V5Disabled = false

// Config collects a number of parameters along with sensible defaults.
// A nil *Config is valid and results in all default values.
type Config struct {
	// Rand provides the source of entropy.
	// If nil, the crypto/rand Reader is used.
	Rand io.Reader
	// DefaultHash is the default hash function to be used.
	// If zero, SHA-256 is used.
	DefaultHash crypto.Hash
	// DefaultCipher is the cipher to be used.
	// If zero, AES-128 is used.
	DefaultCipher CipherFunction
	// Time returns the current time as the number of seconds since the
	// epoch. If Time is nil, time.Now is used.
	Time func() time.Time
	// DefaultCompressionAlgo is the compression algorithm to be
	// applied to the plaintext before encryption. If zero, no
	// compression is done.
	DefaultCompressionAlgo CompressionAlgo
	// CompressionConfig configures the compression settings.
	CompressionConfig *CompressionConfig
	// S2K (String to Key) config, used for key derivation in the context of secret key encryption
	// and password-encrypted data.
	// If nil, the default configuration is used
	S2KConfig *s2k.Config
	// Iteration count for Iterated S2K (String to Key).
	// Only used if sk2.Mode is nil.
	// This value is duplicated here from s2k.Config for backwards compatibility.
	// It determines the strength of the passphrase stretching when
	// the said passphrase is hashed to produce a key. S2KCount
	// should be between 65536 and 65011712, inclusive. If Config
	// is nil or S2KCount is 0, the value 16777216 used. Not all
	// values in the above range can be represented. S2KCount will
	// be rounded up to the next representable value if it cannot
	// be encoded exactly. When set, it is strongly encrouraged to
	// use a value that is at least 65536. See RFC 4880 Section
	// 3.7.1.3.
	//
	// Deprecated: SK2Count should be configured in S2KConfig instead.
	S2KCount int
	// RSABits is the number of bits in new RSA keys made with NewEntity.
	// If zero, then 2048 bit keys are created.
	RSABits int
	// The public key algorithm to use - will always create a signing primary
	// key and encryption subkey.
	Algorithm PublicKeyAlgorithm
	// Some known primes that are optionally prepopulated by the caller
	RSAPrimes []*big.Int
	// Curve configures the desired packet.Curve if the Algorithm is PubKeyAlgoECDSA,
	// PubKeyAlgoEdDSA, or PubKeyAlgoECDH. If empty Curve25519 is used.
	Curve Curve
	// AEADConfig configures the use of the new AEAD Encrypted Data Packet,
	// defined in the draft of the next version of the OpenPGP specification.
	// If a non-nil AEADConfig is passed, usage of this packet is enabled. By
	// default, it is disabled. See the documentation of AEADConfig for more
	// configuration options related to AEAD.
	// **Note: using this option may break compatibility with other OpenPGP
	// implementations, as well as future versions of this library.**
	AEADConfig *AEADConfig
	// V6Keys configures version 6 key generation. If false, this package still
	// supports version 6 keys, but produces version 4 keys.
	V6Keys bool
	// Minimum RSA key size allowed for key generation and message signing, verification and encryption.
	MinRSABits uint16
	// Reject insecure algorithms, only works with v2 api
	RejectPublicKeyAlgorithms   map[PublicKeyAlgorithm]bool
	RejectHashAlgorithms        map[crypto.Hash]bool
	RejectMessageHashAlgorithms map[crypto.Hash]bool
	RejectCurves                map[Curve]bool
	// "The validity period of the key.  This is the number of seconds after
	// the key creation time that the key expires.  If this is not present
	// or has a value of zero, the key never expires.  This is found only on
	// a self-signature.""
	// https://tools.ietf.org/html/rfc4880#section-5.2.3.6
	KeyLifetimeSecs uint32
	// "The validity period of the signature.  This is the number of seconds
	// after the signature creation time that the signature expires.  If
	// this is not present or has a value of zero, it never expires."
	// https://tools.ietf.org/html/rfc4880#section-5.2.3.10
	SigLifetimeSecs uint32
	// SigningKeyId is used to specify the signing key to use (by Key ID).
	// By default, the signing key is selected automatically, preferring
	// signing subkeys if available.
	SigningKeyId uint64
	// SigningIdentity is used to specify a user ID (packet Signer's User ID, type 28)
	// when producing a generic certification signature onto an existing user ID.
	// The identity must be present in the signer Entity.
	SigningIdentity string
	// InsecureAllowUnauthenticatedMessages controls, whether it is tolerated to read
	// encrypted messages without Modification Detection Code (MDC).
	// MDC is mandated by the IETF OpenPGP Crypto Refresh draft and has long been implemented
	// in most OpenPGP implementations. Messages without MDC are considered unnecessarily
	// insecure and should be prevented whenever possible.
	// In case one needs to deal with messages from very old OpenPGP implementations, there
	// might be no other way than to tolerate the missing MDC. Setting this flag, allows this
	// mode of operation. It should be considered a measure of last resort.
	InsecureAllowUnauthenticatedMessages bool
	// InsecureAllowDecryptionWithSigningKeys allows decryption with keys marked as signing keys in the v2 API.
	// This setting is potentially insecure, but it is needed as some libraries
	// ignored key flags when selecting a key for encryption.
	// Not relevant for the v1 API, as all keys were allowed in decryption.
	InsecureAllowDecryptionWithSigningKeys bool
	// KnownNotations is a map of Notation Data names to bools, which controls
	// the notation names that are allowed to be present in critical Notation Data
	// signature subpackets.
	KnownNotations map[string]bool
	// SignatureNotations is a list of Notations to be added to any signatures.
	SignatureNotations []*Notation
	// CheckIntendedRecipients controls, whether the OpenPGP Intended Recipient Fingerprint feature
	// should be enabled for encryption and decryption.
	// (See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-12.html#name-intended-recipient-fingerpr).
	// When the flag is set, encryption produces Intended Recipient Fingerprint signature sub-packets and decryption
	// checks whether the key it was encrypted to is one of the included fingerprints in the signature.
	// If the flag is disabled, no Intended Recipient Fingerprint sub-packets are created or checked.
	// The default behavior, when the config or flag is nil, is to enable the feature.
	CheckIntendedRecipients *bool
	// CacheSessionKey controls if decryption should return the session key used for decryption.
	// If the flag is set, the session key is cached in the message details struct.
	CacheSessionKey bool
	// CheckPacketSequence is a flag that controls if the pgp message reader should strictly check
	// that the packet sequence conforms with the grammar mandated by rfc4880.
	// The default behavior, when the config or flag is nil, is to check the packet sequence.
	CheckPacketSequence *bool
	// NonDeterministicSignaturesViaNotation is a flag to enable randomization of signatures.
	// If true, a salt notation is used to randomize signatures generated by v4 and v5 keys
	// (v6 signatures are always non-deterministic, by design).
	// This protects EdDSA signatures from potentially leaking the secret key in case of faults (i.e. bitflips) which, in principle, could occur
	// during the signing computation. It is added to signatures of any algo for simplicity, and as it may also serve as protection in case of
	// weaknesses in the hash algo, potentially hindering e.g. some chosen-prefix attacks.
	// The default behavior, when the config or flag is nil, is to enable the feature.
	NonDeterministicSignaturesViaNotation *bool

	// InsecureAllowAllKeyFlagsWhenMissing determines how a key without valid key flags is handled.
	// When set to true, a key without flags is treated as if all flags are enabled.
	// This behavior is consistent with GPG.
	InsecureAllowAllKeyFlagsWhenMissing bool
	// InsecureGenerateNonCriticalKeyFlags causes the "Key Flags" signature subpacket
	// to be non-critical in newly generated signatures.
	// This may be needed for keys to be accepted by older clients who do not recognize
	// the subpacket.
	// For example, rpm 4.14.3-150400.59.3.1 in OpenSUSE Leap 15.4 does not recognize it.
	InsecureGenerateNonCriticalKeyFlags bool
	// InsecureGenerateNonCriticalSignatureCreationTime causes the "Signature Creation Time" signature subpacket
	// to be non-critical in newly generated signatures.
	// This may be needed for keys to be accepted by older clients who do not recognize
	// the subpacket.
	// For example, yum 3.4.3-168 in CentOS 7 and yum 3.4.3-158 in Amazon Linux 2 do not recognize it.
	InsecureGenerateNonCriticalSignatureCreationTime bool

	// MaxDecompressedMessageSize specifies the maximum number of bytes that can be
	// read from a compressed packet. This serves as an upper limit to prevent
	// excessively large decompressed messages.
	MaxDecompressedMessageSize *int64
}

func (c *Config) Random() io.Reader {
	if c == nil || c.Rand == nil {
		return rand.Reader
	}
	return c.Rand
}

func (c *Config) Hash() crypto.Hash {
	if c == nil || uint(c.DefaultHash) == 0 {
		return crypto.SHA256
	}
	return c.DefaultHash
}

func (c *Config) Cipher() CipherFunction {
	if c == nil || uint8(c.DefaultCipher) == 0 {
		return CipherAES128
	}
	return c.DefaultCipher
}

func (c *Config) Now() time.Time {
	if c == nil || c.Time == nil {
		return time.Now().Truncate(time.Second)
	}
	return c.Time().Truncate(time.Second)
}

// KeyLifetime returns the validity period of the key.
func (c *Config) KeyLifetime() uint32 {
	if c == nil {
		return 0
	}
	return c.KeyLifetimeSecs
}

// SigLifetime returns the validity period of the signature.
func (c *Config) SigLifetime() uint32 {
	if c == nil {
		return 0
	}
	return c.SigLifetimeSecs
}

func (c *Config) Compression() CompressionAlgo {
	if c == nil {
		return CompressionNone
	}
	return c.DefaultCompressionAlgo
}

func (c *Config) RSAModulusBits() int {
	if c == nil || c.RSABits == 0 {
		return 2048
	}
	return c.RSABits
}

func (c *Config) PublicKeyAlgorithm() PublicKeyAlgorithm {
	if c == nil || c.Algorithm == 0 {
		return PubKeyAlgoRSA
	}
	return c.Algorithm
}

func (c *Config) CurveName() Curve {
	if c == nil || c.Curve == "" {
		return Curve25519
	}
	return c.Curve
}

// Deprecated: The hash iterations should now be queried via the S2K() method.
func (c *Config) PasswordHashIterations() int {
	if c == nil || c.S2KCount == 0 {
		return 0
	}
	return c.S2KCount
}

func (c *Config) S2K() *s2k.Config {
	if c == nil {
		return nil
	}
	// for backwards compatibility
	if c.S2KCount > 0 && c.S2KConfig == nil {
		return &s2k.Config{
			S2KCount: c.S2KCount,
		}
	}
	return c.S2KConfig
}

func (c *Config) AEAD() *AEADConfig {
	if c == nil {
		return nil
	}
	return c.AEADConfig
}

func (c *Config) SigningKey() uint64 {
	if c == nil {
		return 0
	}
	return c.SigningKeyId
}

func (c *Config) SigningUserId() string {
	if c == nil {
		return ""
	}
	return c.SigningIdentity
}

func (c *Config) AllowUnauthenticatedMessages() bool {
	if c == nil {
		return false
	}
	return c.InsecureAllowUnauthenticatedMessages
}

func (c *Config) AllowDecryptionWithSigningKeys() bool {
	if c == nil {
		return false
	}
	return c.InsecureAllowDecryptionWithSigningKeys
}

func (c *Config) KnownNotation(notationName string) bool {
	if c == nil {
		return false
	}
	return c.KnownNotations[notationName]
}

func (c *Config) Notations() []*Notation {
	if c == nil {
		return nil
	}
	return c.SignatureNotations
}

func (c *Config) V6() bool {
	if c == nil {
		return false
	}
	return c.V6Keys
}

func (c *Config) IntendedRecipients() bool {
	if c == nil || c.CheckIntendedRecipients == nil {
		return true
	}
	return *c.CheckIntendedRecipients
}

func (c *Config) RetrieveSessionKey() bool {
	if c == nil {
		return false
	}
	return c.CacheSessionKey
}

func (c *Config) MinimumRSABits() uint16 {
	if c == nil || c.MinRSABits == 0 {
		return 2047
	}
	return c.MinRSABits
}

func (c *Config) RejectPublicKeyAlgorithm(alg PublicKeyAlgorithm) bool {
	var rejectedAlgorithms map[PublicKeyAlgorithm]bool
	if c == nil || c.RejectPublicKeyAlgorithms == nil {
		// Default
		rejectedAlgorithms = defaultRejectPublicKeyAlgorithms
	} else {
		rejectedAlgorithms = c.RejectPublicKeyAlgorithms
	}
	return rejectedAlgorithms[alg]
}

func (c *Config) RejectHashAlgorithm(hash crypto.Hash) bool {
	var rejectedAlgorithms map[crypto.Hash]bool
	if c == nil || c.RejectHashAlgorithms == nil {
		// Default
		rejectedAlgorithms = defaultRejectHashAlgorithms
	} else {
		rejectedAlgorithms = c.RejectHashAlgorithms
	}
	return rejectedAlgorithms[hash]
}

func (c *Config) RejectMessageHashAlgorithm(hash crypto.Hash) bool {
	var rejectedAlgorithms map[crypto.Hash]bool
	if c == nil || c.RejectMessageHashAlgorithms == nil {
		// Default
		rejectedAlgorithms = defaultRejectMessageHashAlgorithms
	} else {
		rejectedAlgorithms = c.RejectMessageHashAlgorithms
	}
	return rejectedAlgorithms[hash]
}

func (c *Config) RejectCurve(curve Curve) bool {
	var rejectedCurve map[Curve]bool
	if c == nil || c.RejectCurves == nil {
		// Default
		rejectedCurve = defaultRejectCurves
	} else {
		rejectedCurve = c.RejectCurves
	}
	return rejectedCurve[curve]
}

func (c *Config) StrictPacketSequence() bool {
	if c == nil || c.CheckPacketSequence == nil {
		return true
	}
	return *c.CheckPacketSequence
}

func (c *Config) RandomizeSignaturesViaNotation() bool {
	if c == nil || c.NonDeterministicSignaturesViaNotation == nil {
		return true
	}
	return *c.NonDeterministicSignaturesViaNotation
}

func (c *Config) AllowAllKeyFlagsWhenMissing() bool {
	if c == nil {
		return false
	}
	return c.InsecureAllowAllKeyFlagsWhenMissing
}

func (c *Config) GenerateNonCriticalKeyFlags() bool {
	if c == nil {
		return false
	}
	return c.InsecureGenerateNonCriticalKeyFlags
}

func (c *Config) GenerateNonCriticalSignatureCreationTime() bool {
	if c == nil {
		return false
	}
	return c.InsecureGenerateNonCriticalSignatureCreationTime
}

func (c *Config) DecompressedMessageSizeLimit() *int64 {
	if c == nil {
		return nil
	}
	return c.MaxDecompressedMessageSize
}

// BoolPointer is a helper function to set a boolean pointer in the Config.
// e.g., config.CheckPacketSequence = BoolPointer(true)
func BoolPointer(value bool) *bool {
	return &value
}
