//go:build integration

package integration

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"math/big"
	"testing"

	"github.com/eggsampler/acme/v3"

	"github.com/letsencrypt/boulder/test"
)

// TestFermat ensures that a certificate public key which can be factored using
// less than 100 rounds of Fermat's Algorithm is rejected.
func TestFermat(t *testing.T) {
	t.Parallel()

	type testCase struct {
		name string
		p    string
		q    string
	}

	testCases := []testCase{
		{
			name: "canon printer (2048 bit, 1 round)",
			p:    "155536235030272749691472293262418471207550926406427515178205576891522284497518443889075039382254334975506248481615035474816604875321501901699955105345417152355947783063521554077194367454070647740704883461064399268622437721385112646454393005862535727615809073410746393326688230040267160616554768771412289114449",
			q:    "155536235030272749691472293262418471207550926406427515178205576891522284497518443889075039382254334975506248481615035474816604875321501901699955105345417152355947783063521554077194367454070647740704883461064399268622437721385112646454393005862535727615809073410746393326688230040267160616554768771412289114113",
		},
		{
			name: "innsbruck printer (4096 bit, 1 round)",
			p:    "25868808535211632564072019392873831934145242707953960515208595626279836366691068618582894100813803673421320899654654938470888358089618966238341690624345530870988951109006149164192566967552401505863871260691612081236189439839963332690997129144163260418447718577834226720411404568398865166471102885763673744513186211985402019037772108416694793355840983833695882936201196462579254234744648546792097397517107797153785052856301942321429858537224127598198913168345965493941246097657533085617002572245972336841716321849601971924830462771411171570422802773095537171762650402420866468579928479284978914972383512240254605625661",
			q:    "25868808535211632564072019392873831934145242707953960515208595626279836366691068618582894100813803673421320899654654938470888358089618966238341690624345530870988951109006149164192566967552401505863871260691612081236189439839963332690997129144163260418447718577834226720411404568398865166471102885763673744513186211985402019037772108416694793355840983833695882936201196462579254234744648546792097397517107797153785052856301942321429858537224127598198913168345965493941246097657533085617002572245972336841716321849601971924830462771411171570422802773095537171762650402420866468579928479284978914972383512240254605624819",
		},
		// Ideally we'd have a 2408-bit, nearly-100-rounds test case, but it turns
		// out purposefully generating keys that require 1 < N < 100 rounds to be
		// factored is surprisingly tricky.
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			// Create a client and complete an HTTP-01 challenge for a fake domain.
			c, err := makeClient()
			test.AssertNotError(t, err, "creating acme client")

			domain := random_domain()

			order, err := c.Client.NewOrder(
				c.Account, []acme.Identifier{{Type: "dns", Value: domain}})
			test.AssertNotError(t, err, "creating new order")
			test.AssertEquals(t, len(order.Authorizations), 1)

			authUrl := order.Authorizations[0]

			auth, err := c.Client.FetchAuthorization(c.Account, authUrl)
			test.AssertNotError(t, err, "fetching authorization")

			chal, ok := auth.ChallengeMap[acme.ChallengeTypeHTTP01]
			test.Assert(t, ok, "getting HTTP-01 challenge")

			err = addHTTP01Response(chal.Token, chal.KeyAuthorization)
			defer delHTTP01Response(chal.Token)
			test.AssertNotError(t, err, "adding HTTP-01 response")

			chal, err = c.Client.UpdateChallenge(c.Account, chal)
			test.AssertNotError(t, err, "updating HTTP-01 challenge")

			// Reconstruct the public modulus N from the test case's prime factors.
			p, ok := new(big.Int).SetString(tc.p, 10)
			test.Assert(t, ok, "failed to create large prime")
			q, ok := new(big.Int).SetString(tc.q, 10)
			test.Assert(t, ok, "failed to create large prime")
			n := new(big.Int).Mul(p, q)

			// Reconstruct the private exponent D from the test case's prime factors.
			p_1 := new(big.Int).Sub(p, big.NewInt(1))
			q_1 := new(big.Int).Sub(q, big.NewInt(1))
			field := new(big.Int).Mul(p_1, q_1)
			d := new(big.Int).ModInverse(big.NewInt(65537), field)

			// Create a CSR containing the reconstructed pubkey and signed with the
			// reconstructed private key.
			pubkey := rsa.PublicKey{
				N: n,
				E: 65537,
			}

			privkey := rsa.PrivateKey{
				PublicKey: pubkey,
				D:         d,
				Primes:    []*big.Int{p, q},
			}

			csrDer, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
				SignatureAlgorithm: x509.SHA256WithRSA,
				PublicKeyAlgorithm: x509.RSA,
				PublicKey:          &pubkey,
				Subject:            pkix.Name{CommonName: domain},
				DNSNames:           []string{domain},
			}, &privkey)
			test.AssertNotError(t, err, "creating CSR")

			csr, err := x509.ParseCertificateRequest(csrDer)
			test.AssertNotError(t, err, "parsing CSR")

			// Finalizing the order should fail as we reject the public key.
			_, err = c.Client.FinalizeOrder(c.Account, order, csr)
			test.AssertError(t, err, "finalizing order")
			test.AssertContains(t, err.Error(), "urn:ietf:params:acme:error:badCSR")
			test.AssertContains(t, err.Error(), "key generated with factors too close together")
		})
	}
}