package pkcs7

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/base64"
	"encoding/pem"
	"math/big"
	"os"
	"os/exec"
	"testing"
	"time"

	"github.com/emmansun/gmsm/smx509"
)

func TestVerify(t *testing.T) {
	fixture := UnmarshalTestFixture(SignedTestFixture)
	p7, err := Parse(fixture.Input)
	if err != nil {
		t.Errorf("Parse encountered unexpected error: %v", err)
	}

	if err := p7.Verify(); err != nil {
		t.Errorf("Verify failed with error: %v", err)
	}
	expected := []byte("We the People")
	if !bytes.Equal(p7.Content, expected) {
		t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content)

	}
}

func TestInvalidSigningTime(t *testing.T) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		t.Fatalf("failed generating ECDSA key: %v", err)
	}

	// define certificate validity to a timeframe in the past, so that
	// the certificate itself is not valid at the time of signing.
	notBefore := time.Now().UTC().Round(time.Minute).Add(-2 * time.Hour)
	notAfter := time.Now().UTC().Round(time.Minute).Add(-1 * time.Hour)
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject: pkix.Name{
			CommonName: "TestInvalidSigningtime",
		},
		NotBefore:   notBefore,
		NotAfter:    notAfter,
		KeyUsage:    x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
	}

	der, err := smx509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
	if err != nil {
		t.Fatalf("failed creating certificate: %v", err)
	}

	cert, err := smx509.ParseCertificate(der)
	if err != nil {
		t.Fatalf("failed parsing certificate: %v", err)
	}

	toBeSignedData, err := NewSignedData([]byte("test-invalid-signing-time"))
	if err != nil {
		t.Fatalf("failed creating signed data: %v", err)
	}

	// add the signer cert, and add attributes, including the signing
	// time attribute, containing the current time
	if err := toBeSignedData.AddSigner(cert, key, SignerInfoConfig{}); err != nil {
		t.Fatalf("failed adding signer: %v", err)
	}

	// finalizes the signed data
	signedData, err := toBeSignedData.Finish()
	if err != nil {
		t.Fatalf("failed signing data: %v", err)
	}

	p7, err := Parse(signedData)
	if err != nil {
		t.Fatalf("failed parsing signed data: %v", err)
	}

	signerCert := p7.GetOnlySigner()
	if !bytes.Equal(cert.Signature, signerCert.Signature) {
		t.Fatal("unexpected signer certificate obtained from P7 data")
	}

	// verify without a chain (self-signed cert), at time.Now()
	now := time.Now()
	err = p7.VerifyWithChainAtTime(nil, &now)
	if err == nil {
		t.Fatal("expected verification error, but got nil")
	}

	signingTimeErr, ok := err.(*InvalidSigningTimeError)
	if !ok {
		t.Fatalf("expected *InvalidSigningTimeError, but got %T", err)
	}

	if signingTimeErr.NotBefore != notBefore {
		t.Errorf("expected notBefore to be %q, but got %q", notBefore, signingTimeErr.NotBefore)
	}

	if signingTimeErr.NotAfter != notAfter {
		t.Errorf("expected notAfter to be %q, but got %q", notAfter, signingTimeErr.NotAfter)
	}

	// verify without a chain (self-signed cert), but without specifying the time
	err = p7.VerifyWithChain(nil)
	if err == nil {
		t.Fatal("expected verification error, but got nil")
	}

	signingTimeErr, ok = err.(*InvalidSigningTimeError)
	if !ok {
		t.Fatalf("expected *InvalidSigningTimeError, but got %T", err)
	}

	if signingTimeErr.NotBefore != notBefore {
		t.Errorf("expected notBefore to be %q, but got %q", notBefore, signingTimeErr.NotBefore)
	}

	if signingTimeErr.NotAfter != notAfter {
		t.Errorf("expected notAfter to be %q, but got %q", notAfter, signingTimeErr.NotAfter)
	}
}

var SignedTestFixture = `
-----BEGIN PKCS7-----
MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B
BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG
SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh
cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB
Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP
J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF
a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw
DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8
zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+
L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy
axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD
bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI
hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4
LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG
SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX
iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM
FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS
-----END PKCS7-----
-----BEGIN CERTIFICATE-----
MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt
ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2
MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu
b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0
bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F
1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5
SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG
9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9
8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR
3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw
TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih
Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB
AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY
5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI
1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14
qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB
Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J
+t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ
4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg
ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF
JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3
BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g==
-----END PRIVATE KEY-----`

func TestVerifyAppStore(t *testing.T) {
	fixture := UnmarshalTestFixture(AppStoreReceiptFixture)
	p7, err := Parse(fixture.Input)
	if err != nil {
		t.Errorf("Parse encountered unexpected error: %v", err)
	}
	if err := p7.Verify(); err != nil {
		t.Errorf("Verify failed with error: %v", err)
	}
}

var AppStoreReceiptFixture = `
-----BEGIN PKCS7-----
[encoded data, 6825 characters]
4B85NkrgvQsWAQ==
-----END PKCS7-----`

func TestVerifyApkEcdsa(t *testing.T) {
	fixture := UnmarshalTestFixture(ApkEcdsaFixture)
	p7, err := Parse(fixture.Input)
	if err != nil {
		t.Errorf("Parse encountered unexpected error: %v", err)
	}
	p7.Content, err = base64.StdEncoding.DecodeString(ApkEcdsaContent)
	if err != nil {
		t.Errorf("Failed to decode base64 signature file: %v", err)
	}
	if err := p7.Verify(); err != nil {
		t.Errorf("Verify failed with error: %v", err)
	}
}

var ApkEcdsaFixture = `-----BEGIN PKCS7-----
MIIDAgYJKoZIhvcNAQcCoIIC8zCCAu8CAQExDzANBglghkgBZQMEAgMFADALBgkq
hkiG9w0BBwGgggH3MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAME
MBIxEDAOBgNVBAMMB2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUz
MTIyWjASMRAwDgYDVQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GG
AAQAYX95sSjPEQqgyLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9G
hAyIyQSnNrIg0zDlWQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCP
B0p+fEXDbq8QFDYvlh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1Ud
DgQWBBT/Ra3kz60gQ7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tY
k3byZckcLabt8TAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39
hYLsWk2H84oEw+HJqGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcD
j2VfNCHazuXO3kSxGA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJV
A/2uEoeqvnl7BsdkicyaOBNEADijuVdaPPIWzKClt9OaVxExgdAwgc0CAQEwHzAS
MRAwDgYDVQQDDAdlYy1wNTIxAgkA7Fd0Wy+bdiIwDQYJYIZIAWUDBAIDBQAwCgYI
KoZIzj0EAwQEgYswgYgCQgD1pVSNo7qTm9A6tpt3SU2yRa+xpJAnUbpZ+Gu36B71
JnQBUzRgTGevniqHpyagi7b2zjWh1uvfz9FfrITUwGMddgJCAPjiBRcl7rKpxmZn
V1MvcJOX41xRSJu1wmBiYXqaJarL+gQ/Wl7RYsMtqLjmNColvLaHNxCaWOO/8nAE
Hg0OMA60
-----END PKCS7-----`

var ApkEcdsaContent = `U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KU0hBLTUxMi1EaWdlc3QtTWFuaWZlc3Q6IFAvVDRqSWtTMjQvNzFxeFE2WW1MeEtNdkRPUUF0WjUxR090dFRzUU9yemhHRQ0KIEpaUGVpWUtyUzZYY090bStYaWlFVC9uS2tYdWVtUVBwZ2RBRzFKUzFnPT0NCkNyZWF0ZWQtQnk6IDEuMCAoQW5kcm9pZCBTaWduQXBrKQ0KDQpOYW1lOiBBbmRyb2lkTWFuaWZlc3QueG1sDQpTSEEtNTEyLURpZ2VzdDogcm9NbWVQZmllYUNQSjFJK2VzMVpsYis0anB2UXowNHZqRWVpL2U0dkN1ald0VVVWSHEzMkNXDQogMUxsOHZiZGMzMCtRc1FlN29ibld4dmhLdXN2K3c1a2c9PQ0KDQpOYW1lOiByZXNvdXJjZXMuYXJzYw0KU0hBLTUxMi1EaWdlc3Q6IG5aYW1aUzlPZTRBRW41cEZaaCtoQ1JFT3krb1N6a3hHdU5YZU0wUFF6WGVBVlVQV3hSVzFPYQ0KIGVLbThRbXdmTmhhaS9HOEcwRUhIbHZEQWdlcy9HUGtBPT0NCg0KTmFtZTogY2xhc3Nlcy5kZXgNClNIQS01MTItRGlnZXN0OiBlbWlDQld2bkVSb0g2N2lCa3EwcUgrdm5tMkpaZDlMWUNEV051N3RNYzJ3bTRtV0dYSUVpWmcNCiBWZkVPV083MFRlZnFjUVhldkNtN2hQMnRpT0U3Y0w5UT09DQoNCg==`

func TestVerifyFirefoxAddon(t *testing.T) {
	fixture := UnmarshalTestFixture(FirefoxAddonFixture)
	p7, err := Parse(fixture.Input)
	if err != nil {
		t.Errorf("Parse encountered unexpected error: %v", err)
	}
	p7.Content = FirefoxAddonContent
	certPool := smx509.NewCertPool()
	certPool.AppendCertsFromPEM(FirefoxAddonRootCert)
	// verifies at the signingTime authenticated attr
	if err := p7.VerifyWithChain(certPool); err != nil {
		t.Errorf("Verify failed with error: %v", err)
	}

	p7.Content = []byte("bad content")
	if err = p7.VerifyWithChain(certPool); err == nil {
		t.Errorf("Verify with incorrect content did not error")
	}
	p7.Content = FirefoxAddonContent

	// The chain has validity:
	//
	// EE:           2016-08-17 20:04:58 +0000 UTC 2021-08-16 20:04:58 +0000 UTC
	// Intermediate: 2015-03-17 23:52:42 +0000 UTC 2025-03-14 23:52:42 +0000 UTC
	// Root:         2015-03-17 22:53:57 +0000 UTC 2025-03-14 22:53:57 +0000 UTC
	validTime := time.Date(2021, 8, 16, 20, 0, 0, 0, time.UTC)
	if err = p7.VerifyWithChainAtTime(certPool, &validTime); err != nil {
		t.Errorf("Verify at UTC now failed with error: %v", err)
	}

	expiredTime := time.Date(2030, time.January, 1, 0, 0, 0, 0, time.UTC)
	if err = p7.VerifyWithChainAtTime(certPool, &expiredTime); err == nil {
		t.Errorf("Verify at expired time %s did not error", expiredTime)
	}
	notYetValidTime := time.Date(1999, time.July, 5, 0, 13, 0, 0, time.UTC)
	if err = p7.VerifyWithChainAtTime(certPool, &notYetValidTime); err == nil {
		t.Errorf("Verify at not yet valid time %s did not error", notYetValidTime)
	}

	// Verify the certificate chain to make sure the identified root
	// is the one we expect
	ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber)
	if ee == nil {
		t.Errorf("No end-entity certificate found for signer")
	}
	signingTime := mustParseTime("2017-02-23T09:06:16-05:00")
	chains, err := verifyCertChain(ee, p7.Certificates, certPool, signingTime)
	if err != nil {
		t.Error(err)
	}
	if len(chains) != 1 {
		t.Errorf("Expected to find one chain, but found %d", len(chains))
	}
	if len(chains[0]) != 3 {
		t.Errorf("Expected to find three certificates in chain, but found %d", len(chains[0]))
	}
	if chains[0][0].Subject.CommonName != "tabscope@xuldev.org" {
		t.Errorf("Expected to find EE certificate with subject 'tabscope@xuldev.org', but found '%s'", chains[0][0].Subject.CommonName)
	}
	if chains[0][1].Subject.CommonName != "production-signing-ca.addons.mozilla.org" {
		t.Errorf("Expected to find intermediate certificate with subject 'production-signing-ca.addons.mozilla.org', but found '%s'", chains[0][1].Subject.CommonName)
	}
	if chains[0][2].Subject.CommonName != "root-ca-production-amo" {
		t.Errorf("Expected to find root certificate with subject 'root-ca-production-amo', but found '%s'", chains[0][2].Subject.CommonName)
	}
}

func mustParseTime(s string) time.Time {
	t, err := time.Parse(time.RFC3339, s)
	if err != nil {
		panic(err)
	}
	return t
}

var FirefoxAddonContent = []byte(`Signature-Version: 1.0
MD5-Digest-Manifest: KjRavc6/KNpuT1QLcB/Gsg==
SHA1-Digest-Manifest: 5Md5nUg+U7hQ/UfzV+xGKWOruVI=

`)

var FirefoxAddonFixture = `
-----BEGIN PKCS7-----
[encoded data, 5655 characters]
-----END PKCS7-----`

var FirefoxAddonRootCert = []byte(`
-----BEGIN CERTIFICATE-----
MIIGYTCCBEmgAwIBAgIBATANBgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBB
TU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2Et
cHJvZHVjdGlvbi1hbW8wHhcNMTUwMzE3MjI1MzU3WhcNMjUwMzE0MjI1MzU3WjB9
MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0G
A1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAd
BgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW8wggIgMA0GCSqGSIb3DQEBAQUA
A4ICDQAwggIIAoICAQC0u2HXXbrwy36+MPeKf5jgoASMfMNz7mJWBecJgvlTf4hH
JbLzMPsIUauzI9GEpLfHdZ6wzSyFOb4AM+D1mxAWhuZJ3MDAJOf3B1Rs6QorHrl8
qqlNtPGqepnpNJcLo7JsSqqE3NUm72MgqIHRgTRsqUs+7LIPGe7262U+N/T0LPYV
Le4rZ2RDHoaZhYY7a9+49mHOI/g2YFB+9yZjE+XdplT2kBgA4P8db7i7I0tIi4b0
B0N6y9MhL+CRZJyxdFe2wBykJX14LsheKsM1azHjZO56SKNrW8VAJTLkpRxCmsiT
r08fnPyDKmaeZ0BtsugicdipcZpXriIGmsZbI12q5yuwjSELdkDV6Uajo2n+2ws5
uXrP342X71WiWhC/dF5dz1LKtjBdmUkxaQMOP/uhtXEKBrZo1ounDRQx1j7+SkQ4
BEwjB3SEtr7XDWGOcOIkoJZWPACfBLC3PJCBWjTAyBlud0C5n3Cy9regAAnOIqI1
t16GU2laRh7elJ7gPRNgQgwLXeZcFxw6wvyiEcmCjOEQ6PM8UQjthOsKlszMhlKw
vjyOGDoztkqSBy/v+Asx7OW2Q7rlVfKarL0mREZdSMfoy3zTgtMVCM0vhNl6zcvf
5HNNopoEdg5yuXo2chZ1p1J+q86b0G5yJRMeT2+iOVY2EQ37tHrqUURncCy4uwIB
A6OB7TCB6jAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8E
DDAKBggrBgEFBQcDAzCBkgYDVR0jBIGKMIGHoYGBpH8wfTELMAkGA1UEBhMCVVMx
HDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEg
QU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNh
LXByb2R1Y3Rpb24tYW1vggEBMB0GA1UdDgQWBBSzvOpYdKvhbngqsqucIx6oYyyX
tzANBgkqhkiG9w0BAQwFAAOCAgEAaNSRYAaECAePQFyfk12kl8UPLh8hBNidP2H6
KT6O0vCVBjxmMrwr8Aqz6NL+TgdPmGRPDDLPDpDJTdWzdj7khAjxqWYhutACTew5
eWEaAzyErbKQl+duKvtThhV2p6F6YHJ2vutu4KIciOMKB8dslIqIQr90IX2Usljq
8Ttdyf+GhUmazqLtoB0GOuESEqT4unX6X7vSGu1oLV20t7t5eCnMMYD67ZBn0YIU
/cm/+pan66hHrja+NeDGF8wabJxdqKItCS3p3GN1zUGuJKrLykxqbOp/21byAGog
Z1amhz6NHUcfE6jki7sM7LHjPostU5ZWs3PEfVVgha9fZUhOrIDsyXEpCWVa3481
LlAq3GiUMKZ5DVRh9/Nvm4NwrTfB3QkQQJCwfXvO9pwnPKtISYkZUqhEqvXk5nBg
QCkDSLDjXTx39naBBGIVIqBtKKuVTla9enngdq692xX/CgO6QJVrwpqdGjebj5P8
5fNZPABzTezG3Uls5Vp+4iIWVAEDkK23cUj3c/HhE+Oo7kxfUeu5Y1ZV3qr61+6t
ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL
26b24/tRam4SJjqpiq20lynhUrmTtt6hbG3E1Hpy3bmkt2DYnuMFwEx2gfXNcnbT
wNuvFqc=
-----END CERTIFICATE-----`)

// sign a document with openssl and verify the signature with pkcs7.
// this uses a chain of root, intermediate and signer cert, where the
// intermediate is added to the certs but the root isn't.
func TestSignWithOpenSSLAndVerify(t *testing.T) {
	content := []byte(`
A ship in port is safe,
but that's not what ships are built for.
-- Grace Hopper`)
	// write the content to a temp file
	tmpContentFile, err := os.CreateTemp("", "TestSignWithOpenSSLAndVerify_content")
	if err != nil {
		t.Fatal(err)
	}
	os.WriteFile(tmpContentFile.Name(), content, 0755)
	sigalgs := []x509.SignatureAlgorithm{
		x509.SHA1WithRSA,
		x509.SHA256WithRSA,
		x509.SHA512WithRSA,
		x509.ECDSAWithSHA1,
		x509.ECDSAWithSHA256,
		x509.ECDSAWithSHA384,
		x509.ECDSAWithSHA512,
	}
	for _, sigalgroot := range sigalgs {
		rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true)
		if err != nil {
			t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err)
		}
		truststore := smx509.NewCertPool()
		truststore.AddCert(rootCert.Certificate)
		for _, sigalginter := range sigalgs {
			interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true)
			if err != nil {
				t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err)
			}
			// write the intermediate cert to a temp file
			tmpInterCertFile, err := os.CreateTemp("", "TestSignWithOpenSSLAndVerify_intermediate")
			if err != nil {
				t.Fatal(err)
			}
			fd, err := os.OpenFile(tmpInterCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
			if err != nil {
				t.Fatal(err)
			}
			pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: interCert.Certificate.Raw})
			fd.Close()
			for _, sigalgsigner := range sigalgs {
				signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false)
				if err != nil {
					t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err)
				}

				// write the signer cert to a temp file
				tmpSignerCertFile, err := os.CreateTemp("", "TestSignWithOpenSSLAndVerify_signer")
				if err != nil {
					t.Fatal(err)
				}
				fd, err = os.OpenFile(tmpSignerCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
				if err != nil {
					t.Fatal(err)
				}
				pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Certificate.Raw})
				fd.Close()

				// write the signer key to a temp file
				tmpSignerKeyFile, err := os.CreateTemp("", "TestSignWithOpenSSLAndVerify_key")
				if err != nil {
					t.Fatal(err)
				}
				fd, err = os.OpenFile(tmpSignerKeyFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
				if err != nil {
					t.Fatal(err)
				}
				var derKey []byte
				priv := *signerCert.PrivateKey
				switch priv := priv.(type) {
				case *rsa.PrivateKey:
					derKey = x509.MarshalPKCS1PrivateKey(priv)
					pem.Encode(fd, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: derKey})
				case *ecdsa.PrivateKey:
					derKey, err = x509.MarshalECPrivateKey(priv)
					if err != nil {
						t.Fatal(err)
					}
					pem.Encode(fd, &pem.Block{Type: "EC PRIVATE KEY", Bytes: derKey})
				}
				fd.Close()

				// write the root cert to a temp file
				tmpSignedFile, err := os.CreateTemp("", "TestSignWithOpenSSLAndVerify_signature")
				if err != nil {
					t.Fatal(err)
				}
				// call openssl to sign the content
				opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach",
					"-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(),
					"-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(),
					"-certfile", tmpInterCertFile.Name(), "-outform", "PEM")
				out, err := opensslCMD.CombinedOutput()
				if err != nil {
					t.Fatalf("test %s/%s/%s: openssl command failed with %s: %s", sigalgroot, sigalginter, sigalgsigner, err, out)
				}

				// verify the signed content
				pemSignature, err := os.ReadFile(tmpSignedFile.Name())
				if err != nil {
					t.Fatal(err)
				}
				derBlock, _ := pem.Decode(pemSignature)
				if derBlock == nil {
					break
				}
				p7, err := Parse(derBlock.Bytes)
				if err != nil {
					t.Fatalf("Parse encountered unexpected error: %v", err)
				}
				if err := p7.VerifyWithChain(truststore); err != nil {
					t.Fatalf("Verify failed with error: %v", err)
				}
				// Verify the certificate chain to make sure the identified root
				// is the one we expect
				ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber)
				if ee == nil {
					t.Fatalf("No end-entity certificate found for signer")
				}
				chains, err := verifyCertChain(ee, p7.Certificates, truststore, time.Now())
				if err != nil {
					t.Fatal(err)
				}
				if len(chains) != 1 {
					t.Fatalf("Expected to find one chain, but found %d", len(chains))
				}
				if len(chains[0]) != 3 {
					t.Fatalf("Expected to find three certificates in chain, but found %d", len(chains[0]))
				}
				if chains[0][0].Subject.CommonName != "PKCS7 Test Signer Cert" {
					t.Fatalf("Expected to find EE certificate with subject 'PKCS7 Test Signer Cert', but found '%s'", chains[0][0].Subject.CommonName)
				}
				if chains[0][1].Subject.CommonName != "PKCS7 Test Intermediate Cert" {
					t.Fatalf("Expected to find intermediate certificate with subject 'PKCS7 Test Intermediate Cert', but found '%s'", chains[0][1].Subject.CommonName)
				}
				if chains[0][2].Subject.CommonName != "PKCS7 Test Root CA" {
					t.Fatalf("Expected to find root certificate with subject 'PKCS7 Test Root CA', but found '%s'", chains[0][2].Subject.CommonName)
				}
				os.Remove(tmpSignerCertFile.Name()) // clean up
				os.Remove(tmpSignerKeyFile.Name())  // clean up
			}
			os.Remove(tmpInterCertFile.Name()) // clean up
		}
	}
	os.Remove(tmpContentFile.Name()) // clean up
}

func TestGetSignatureAlgorithm(t *testing.T) {
	validtests := []struct {
		digestEncryption, digest asn1.ObjectIdentifier
		expected                 x509.SignatureAlgorithm
	}{
		{
			OIDDigestAlgorithmDSA,
			OIDDigestAlgorithmSHA1,
			x509.DSAWithSHA1,
		},
		{
			OIDDigestAlgorithmDSA,
			OIDDigestAlgorithmSHA256,
			x509.DSAWithSHA256,
		},
		{
			OIDEncryptionAlgorithmECDSAP256,
			OIDDigestAlgorithmSHA1,
			x509.ECDSAWithSHA1,
		},
		{
			OIDEncryptionAlgorithmECDSAP256,
			OIDDigestAlgorithmSHA256,
			x509.ECDSAWithSHA256,
		},
		{
			OIDEncryptionAlgorithmECDSAP256,
			OIDDigestAlgorithmSHA384,
			x509.ECDSAWithSHA384,
		},
		{
			OIDEncryptionAlgorithmECDSAP256,
			OIDDigestAlgorithmSHA512,
			x509.ECDSAWithSHA512,
		},
		{
			OIDEncryptionAlgorithmRSA,
			OIDDigestAlgorithmSHA384,
			x509.SHA384WithRSA,
		},
		{
			OIDEncryptionAlgorithmRSA,
			OIDDigestAlgorithmSHA512,
			x509.SHA512WithRSA,
		},
	}
	for _, test := range validtests {
		s, err := getSignatureAlgorithm(pkix.AlgorithmIdentifier{Algorithm: test.digestEncryption}, pkix.AlgorithmIdentifier{Algorithm: test.digest})
		if err != nil {
			t.Errorf("should return valid signature algorithm")
		}
		if s != test.expected {
			t.Errorf("expected %v, got %v", test.expected, s)
		}
	}
	invalidtests := []struct {
		digestEncryption, digest asn1.ObjectIdentifier
	}{
		{
			OIDEncryptionAlgorithmRSASHA256,
			OIDDigestAlgorithmSM3,
		},
		{
			OIDDigestAlgorithmDSA,
			OIDDigestAlgorithmSHA384,
		},
		{
			OIDEncryptionAlgorithmECDSAP256,
			OIDDigestAlgorithmSM3,
		},
		{
			OIDDigestAlgorithmSM9SM3,
			OIDDigestAlgorithmSHA384,
		},
	}
	for _, test := range invalidtests {
		_, err := getSignatureAlgorithm(pkix.AlgorithmIdentifier{Algorithm: test.digestEncryption}, pkix.AlgorithmIdentifier{Algorithm: test.digest})
		if err == nil {
			t.Errorf("should return error")
		}
	}
}
