// Copyright 2025 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 crypto_test

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"internal/testenv"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

type messageSignerOnly struct {
	k *rsa.PrivateKey
}

func (s *messageSignerOnly) Public() crypto.PublicKey {
	return s.k.Public()
}

func (s *messageSignerOnly) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
	return nil, errors.New("unimplemented")
}

func (s *messageSignerOnly) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
	h := opts.HashFunc().New()
	h.Write(msg)
	digest := h.Sum(nil)
	return s.k.Sign(rand, digest, opts)
}

func TestSignMessage(t *testing.T) {
	block, _ := pem.Decode([]byte(strings.ReplaceAll(
		`-----BEGIN RSA TESTING KEY-----
MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso
tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE
89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU
l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s
B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59
3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+
dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI
FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J
aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2
BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx
IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/
fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u
pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT
Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl
u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD
fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X
Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE
k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo
qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS
CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ
XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw
AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r
UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0
2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5
7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3
-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY")))
	k, _ := x509.ParsePKCS1PrivateKey(block.Bytes)

	msg := []byte("hello :)")

	h := crypto.SHA256.New()
	h.Write(msg)
	digest := h.Sum(nil)

	sig, err := crypto.SignMessage(k, rand.Reader, msg, &rsa.PSSOptions{Hash: crypto.SHA256})
	if err != nil {
		t.Fatalf("SignMessage failed with Signer: %s", err)
	}
	if err := rsa.VerifyPSS(&k.PublicKey, crypto.SHA256, digest, sig, nil); err != nil {
		t.Errorf("VerifyPSS failed for Signer signature: %s", err)
	}

	sig, err = crypto.SignMessage(&messageSignerOnly{k}, rand.Reader, msg, &rsa.PSSOptions{Hash: crypto.SHA256})
	if err != nil {
		t.Fatalf("SignMessage failed with MessageSigner: %s", err)
	}
	if err := rsa.VerifyPSS(&k.PublicKey, crypto.SHA256, digest, sig, nil); err != nil {
		t.Errorf("VerifyPSS failed for MessageSigner signature: %s", err)
	}
}

func TestDisallowedAssemblyInstructions(t *testing.T) {
	// This test enforces the cryptography assembly policy rule that we do not
	// use BYTE or WORD instructions, since these instructions can obscure what
	// the assembly is actually doing. If we do not support specific
	// instructions in the assembler, we should not be using them until we do.
	//
	// Instead of using the output of the 'go tool asm' tool, we take the simple
	// approach and just search the text of .s files for usage of BYTE and WORD.
	// We do this because the assembler itself will sometimes insert WORD
	// instructions for things like function preambles etc.

	boringSigPath := filepath.Join("internal", "boring", "sig")

	matcher, err := regexp.Compile(`(^|;)\s(BYTE|WORD)\s`)
	if err != nil {
		t.Fatal(err)
	}
	if err := filepath.WalkDir(filepath.Join(testenv.GOROOT(t), "src/crypto"), func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			t.Fatal(err)
		}
		if d.IsDir() || !strings.HasSuffix(path, ".s") {
			return nil
		}
		if strings.Contains(path, boringSigPath) {
			return nil
		}

		f, err := os.ReadFile(path)
		if err != nil {
			t.Fatal(err)
		}

		i := 1
		for line := range bytes.Lines(f) {
			if matcher.Match(line) {
				t.Errorf("%s:%d assembly contains BYTE or WORD instruction (%q)", path, i, string(line))
			}
			i++
		}

		return nil
	}); err != nil {
		t.Fatal(err)
	}
}

func TestRegisterHashLimits(t *testing.T) {
	// maxHash is not exported, so we just use its value. If maxHash ever changes
	// this will need to be updated.
	for _, h := range []crypto.Hash{0, 21, crypto.MLDSAMu} {
		t.Run(fmt.Sprintf("h=%d", h), func(t *testing.T) {
			defer func() {
				if r := recover(); r == nil {
					t.Errorf("RegisterHash did not panic for %v", h)
				}
			}()
			crypto.RegisterHash(h, sha256.New)
		})
	}
}

func TestMLDSAMu(t *testing.T) {
	h := crypto.MLDSAMu
	if got := h.Size(); got != 64 {
		t.Errorf("MLDSAMu.Size() = %d, want 64", got)
	}
	if got := h.String(); got != "ML-DSA μ message representative" {
		t.Errorf("MLDSAMu.String() = %q", got)
	}
	if h.Available() {
		t.Errorf("MLDSAMu.Available() = true, want false")
	}
	if got := h.HashFunc(); got != h {
		t.Errorf("MLDSAMu.HashFunc() = %v, want itself", got)
	}
	func() {
		defer func() {
			if r := recover(); r == nil {
				t.Errorf("MLDSAMu.New() did not panic")
			}
		}()
		_ = h.New()
	}()
}
