// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package transit

import (
	"context"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"strconv"

	"github.com/hashicorp/vault/sdk/framework"
	"github.com/hashicorp/vault/sdk/helper/keysutil"
	"github.com/hashicorp/vault/sdk/logical"
)

const WrappingKeyName = "wrapping-key"

func (b *backend) pathWrappingKey() *framework.Path {
	return &framework.Path{
		Pattern: "wrapping_key",
		DisplayAttrs: &framework.DisplayAttributes{
			OperationPrefix: operationPrefixTransit,
			OperationSuffix: "wrapping-key",
		},
		Callbacks: map[logical.Operation]framework.OperationFunc{
			logical.ReadOperation: b.pathWrappingKeyRead,
		},
		HelpSynopsis:    pathWrappingKeyHelpSyn,
		HelpDescription: pathWrappingKeyHelpDesc,
	}
}

func (b *backend) pathWrappingKeyRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
	p, err := b.getWrappingKey(ctx, req.Storage)
	if err != nil {
		return nil, err
	}
	wrappingKey := p.Keys[strconv.Itoa(p.LatestVersion)]

	derBytes, err := x509.MarshalPKIXPublicKey(wrappingKey.RSAKey.Public())
	if err != nil {
		return nil, fmt.Errorf("error marshaling RSA public key: %w", err)
	}
	pemBlock := &pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: derBytes,
	}
	pemBytes := pem.EncodeToMemory(pemBlock)
	if pemBytes == nil || len(pemBytes) == 0 {
		return nil, fmt.Errorf("failed to PEM-encode RSA public key")
	}

	publicKeyString := string(pemBytes)

	resp := &logical.Response{
		Data: map[string]interface{}{
			"public_key": publicKeyString,
		},
	}

	return resp, nil
}

func (b *backend) getWrappingKey(ctx context.Context, storage logical.Storage) (*keysutil.Policy, error) {
	polReq := keysutil.PolicyRequest{
		Upsert:               true,
		Storage:              storage,
		Name:                 fmt.Sprintf("import/%s", WrappingKeyName),
		KeyType:              keysutil.KeyType_RSA4096,
		Derived:              false,
		Convergent:           false,
		Exportable:           false,
		AllowPlaintextBackup: false,
		AutoRotatePeriod:     0,
	}
	p, _, err := b.GetPolicy(ctx, polReq, b.GetRandomReader())
	if err != nil {
		return nil, err
	}
	if p == nil {
		return nil, fmt.Errorf("error retrieving wrapping key: returned policy was nil")
	}
	if b.System().CachingDisabled() {
		p.Unlock()
	}

	return p, nil
}

const (
	pathWrappingKeyHelpSyn  = "Returns the public key to use for wrapping imported keys"
	pathWrappingKeyHelpDesc = "This path is used to retrieve the RSA-4096 wrapping key " +
		"for wrapping keys that are being imported into transit."
)
