// Copyright 2021 The Sigstore Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !pkcs11keydisabled && softhsm
// +build !pkcs11keydisabled,softhsm

// DANGER
// This test requires SoftHSMv2 to be installed. An initialized token should already exist.
// This test will import an RSA key pair, using the specified token label.
// By default, the test assumes the following :
//	- The SoftHSMv2 library is located at "/usr/local/lib/softhsm/libsofthsm2.so"
//	- The initialized token has the label "My Token"
//	- The initialized token has the pin "1234"
//	- The test will import the key pair using the key label "My Key"
// These values can be overriden using the following environment variable :
//	- SOFTHSM_LIB
// 	- SOFTHSM_TOKENLABEL
// 	- SOFTHSM_PIN
// 	- SOFTHSM_KEYLABEL
// By default, the test makes use of the following SoftHSMv2 configuration files :
//	- /etc/softhsm2.conf
// 	- /etc/softhsm.conf
// These values can be overriden using the following environment variable :
//	- SOFTHSM2_CONF
// 	- SOFTHSM_CONF

package test

import (
	"bytes"
	"context"
	"crypto/rsa"
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"io"
	"math/big"
	"os"
	"strings"
	"testing"

	// Import the functions directly for testing.

	"github.com/miekg/pkcs11"
	. "github.com/sigstore/cosign/v3/cmd/cosign/cli/pkcs11cli"
	"github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key"
	"github.com/stretchr/testify/require"
)

var (
	modulePath = "/usr/local/lib/softhsm/libsofthsm2.so"
	tokenLabel = "My Token"
	pin        = "1234"
	keyLabel   = "My Key"

	keyID = "355d2d0b569a2a0169e46b82e172cf99aca41400"
	uri   = ""
)

func init() {
	if x := os.Getenv("SOFTHSM_LIB"); x != "" {
		modulePath = x
	}
	if x := os.Getenv("SOFTHSM_TOKENLABEL"); x != "" {
		tokenLabel = x
	}
	if x := os.Getenv("SOFTHSM_PIN"); x != "" {
		pin = x
	}
	if x := os.Getenv("SOFTHSM_KEYLABEL"); x != "" {
		keyLabel = x
	}
	if x := os.Getenv("SOFTHSM_CONF"); x == "" {
		os.Setenv("SOFTHSM_CONF", "/etc/softhsm.conf")
	}
	if x := os.Getenv("SOFTHSM2_CONF"); x == "" {
		os.Setenv("SOFTHSM2_CONF", "/etc/softhsm2.conf")
	}

	keyIDBytes, _ := hex.DecodeString(keyID)
	pkcs11UriConfig := pkcs11key.NewPkcs11UriConfigFromInput(modulePath, nil, tokenLabel, []byte(keyLabel), keyIDBytes, pin)
	uri, _ = pkcs11UriConfig.Construct()
}

func TestParsePKCS11URI(t *testing.T) {
	_ = context.Background()

	uriString := "pkcs11:"
	uriString += "library-manufacturer=manufacturer;library-description=description;library-version=1;"
	uriString += "slot-manufacturer=manufacturer;slot-description=description;slot-id=1;"
	uriString += "manufacturer=manufacturer;model=model;serial=12345678;token=token%20label;"
	uriString += "type=private;object=key%20label;id=%6b%65%79%5f%69%64"
	uriString += "?"
	uriString += "module-path=/path/to/some%20folder/libmodule.so&module-name=libmodule.so&"
	uriString += "pin-value=1234&pin-source=/path/to/pinfile"

	pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig()
	must(pkcs11UriConfig.Parse(uriString), t)
	require.Equal(t, pkcs11UriConfig.KeyID, []byte("key_id"))
	require.Equal(t, pkcs11UriConfig.KeyLabel, []byte("key label"))
	require.Equal(t, pkcs11UriConfig.ModulePath, "/path/to/some folder/libmodule.so")
	require.Equal(t, pkcs11UriConfig.Pin, "1234")
	require.Equal(t, *pkcs11UriConfig.SlotID, 1)
	require.Equal(t, pkcs11UriConfig.TokenLabel, "token label")
}

func TestConstructPKCS11URI(t *testing.T) {
	_ = context.Background()

	uri := "pkcs11:token=token%20label;slot-id=1;id=%6b%65%79%5f%69%64;object=key%20label"
	uri += "?"
	uri += "module-path=/path/to/some%20folder/libmodule.so&pin-value=1234"

	slotID := 1
	pkcs11UriConfig := pkcs11key.NewPkcs11UriConfigFromInput("/path/to/some folder/libmodule.so", &slotID, "token label", []byte("key label"), []byte("key_id"), "1234")
	uriString, err := pkcs11UriConfig.Construct()
	require.NoError(t, err)
	require.Equal(t, uri, uriString)
}

func TestListTokensCmd(t *testing.T) {
	ctx := context.Background()

	tokens, err := GetTokens(ctx, modulePath)
	if err != nil {
		t.Fatal(err)
	}

	bTokenFound := false
	for _, token := range tokens {
		if token.TokenInfo.Label == tokenLabel {
			bTokenFound = true
			break
		}
	}

	if !bTokenFound {
		t.Fatalf("token with label '%s' not found", tokenLabel)
	}
}

func TestListKeysUrisCmd(t *testing.T) {
	ctx := context.Background()

	tokens, err := GetTokens(ctx, modulePath)
	if err != nil {
		t.Fatal(err)
	}

	bTokenFound := false
	var slotID uint
	for _, token := range tokens {
		if token.TokenInfo.Label == tokenLabel {
			bTokenFound = true
			slotID = token.Slot
			break
		}
	}
	if !bTokenFound {
		t.Fatalf("token with label '%s' not found", tokenLabel)
	}

	err = importKey(slotID)
	if err != nil {
		t.Fatal(err)
	}
	defer deleteKey(slotID)

	keysInfo, err := GetKeysInfo(ctx, modulePath, slotID, pin)
	if err != nil {
		t.Fatal(err)
	}

	bKeyFound := false
	for _, keyInfo := range keysInfo {
		if hex.EncodeToString(keyInfo.KeyID) == keyID && string(keyInfo.KeyLabel) == keyLabel {
			foundUriConfig := pkcs11key.NewPkcs11UriConfig()
			err = foundUriConfig.Parse(keyInfo.KeyURI)
			if err != nil {
				t.Fatal(err)
			}

			uriConfig := pkcs11key.NewPkcs11UriConfig()
			err = uriConfig.Parse(uri)
			if err != nil {
				t.Fatal(err)
			}

			if foundUriConfig.TokenLabel == uriConfig.TokenLabel &&
				bytes.Compare(foundUriConfig.KeyID, uriConfig.KeyID) == 0 &&
				bytes.Compare(foundUriConfig.KeyLabel, uriConfig.KeyLabel) == 0 &&
				foundUriConfig.ModulePath == uriConfig.ModulePath &&
				foundUriConfig.Pin == uriConfig.Pin {
				bKeyFound = true
			}

			break
		}
	}

	if !bKeyFound {
		t.Fatalf("key not found")
	}
}

func TestCertificateIgnored(t *testing.T) {
	ctx := context.Background()

	tokens, err := GetTokens(ctx, modulePath)
	if err != nil {
		t.Fatal(err)
	}

	bTokenFound := false
	var slotID uint
	for _, token := range tokens {
		if token.TokenInfo.Label == tokenLabel {
			bTokenFound = true
			slotID = token.Slot
			break
		}
	}
	if !bTokenFound {
		t.Fatalf("token with label '%s' not found", tokenLabel)
	}

	err = importKey(slotID)
	if err != nil {
		t.Fatal(err)
	}
	defer deleteKey(slotID)

	pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig()
	err = pkcs11UriConfig.Parse(uri)
	if err != nil {
		t.Fatal(err)
	}

	const envvar = "COSIGN_PKCS11_IGNORE_CERTIFICATE"

	if err := os.Setenv(envvar, "1"); err != nil {
		t.Fatal(err)
	}

	defer os.Setenv(envvar, "")

	sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, true)
	if err != nil {
		t.Fatal(err)
	}

	defer sk.Close()

	cert, err := sk.Certificate()
	if err != nil {
		t.Fatal(err)
	}

	if cert != nil {
		t.Fatalf("expected certificate to be ignored while loading")
	}
}

func TestSignAndVerify(t *testing.T) {
	ctx := context.Background()

	tokens, err := GetTokens(ctx, modulePath)
	if err != nil {
		t.Fatal(err)
	}

	bTokenFound := false
	var slotID uint
	for _, token := range tokens {
		if token.TokenInfo.Label == tokenLabel {
			bTokenFound = true
			slotID = token.Slot
			break
		}
	}
	if !bTokenFound {
		t.Fatalf("token with label '%s' not found", tokenLabel)
	}

	err = importKey(slotID)
	if err != nil {
		t.Fatal(err)
	}
	defer deleteKey(slotID)

	pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig()
	err = pkcs11UriConfig.Parse(uri)
	if err != nil {
		t.Fatal(err)
	}

	sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, true)
	if err != nil {
		t.Fatal(err)
	}
	defer sk.Close()

	sv, err := sk.SignerVerifier()
	if err != nil {
		t.Fatal(err)
	}

	v, err := sk.Verifier()
	if err != nil {
		t.Fatal(err)
	}

	sig, err := sv.SignMessage(bytes.NewReader([]byte("hello, world!")))
	if err != nil {
		t.Fatal(err)
	}

	err = v.VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("hello, world!")))
	if err != nil {
		t.Fatal(err)
	}
}

var newPublicKeyAttrs = []*pkcs11.Attribute{
	pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
	pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
	pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
	pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
}

var newPrivateKeyAttrs = []*pkcs11.Attribute{
	pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
	pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
	pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
	pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
	pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
	pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
}

func rsaImportAttrs(priv *rsa.PrivateKey) (pubAttrs, privAttrs []*pkcs11.Attribute) {
	pubAttrs = []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(priv.E)).Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_MODULUS, priv.N.Bytes()),
	}
	privAttrs = []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(priv.E)).Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_MODULUS, priv.N.Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_PRIVATE_EXPONENT, priv.D.Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_PRIME_1, priv.Primes[0].Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_PRIME_2, priv.Primes[1].Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_1, priv.Precomputed.Dp.Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_2, priv.Precomputed.Dq.Bytes()),
		pkcs11.NewAttribute(pkcs11.CKA_COEFFICIENT, priv.Precomputed.Qinv.Bytes()),
	}
	return
}

func attrConcat(attrSets ...[]*pkcs11.Attribute) []*pkcs11.Attribute {
	ret := make([]*pkcs11.Attribute, 0)
	for _, attrs := range attrSets {
		ret = append(ret, attrs...)
	}
	return ret
}

func initPKCS11(modulePath string) (*pkcs11.Ctx, error) {
	ctx := pkcs11.New(modulePath)
	if ctx == nil {
		return nil, fmt.Errorf("unable to load PKCS#11 module")
	}

	err := ctx.Initialize()
	if err != nil {
		return nil, fmt.Errorf("unable to initialize PKCS#11 module")
	}

	return ctx, nil
}

func importKey(slotID uint) error {
	var pemBytes []byte
	var priv interface{}

	ctx, err := initPKCS11(modulePath)
	if err != nil {
		return err
	}
	defer func() {
		ctx.Finalize()
		ctx.Destroy()
	}()

	keyIDBytes, err := hex.DecodeString(keyID)
	if err != nil {
		return err
	}
	keyLabelBytes := []byte(keyLabel)

	r := strings.NewReader(rsaPrivKey)
	pemBytes, err = io.ReadAll(r)
	if err != nil {
		return fmt.Errorf("unable to read pem")
	}
	block, _ := pem.Decode(pemBytes)
	if block == nil {
		return fmt.Errorf("unable to decode pem")
	}
	priv, err = x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("unable to parse pem")
	}
	privKey, ok := priv.(*rsa.PrivateKey)
	if !ok {
		return fmt.Errorf("unable to load key")
	}

	session, err := ctx.OpenSession(slotID, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
	if err != nil {
		return fmt.Errorf("unable to open session")
	}
	defer ctx.CloseSession(session)
	err = ctx.Login(session, pkcs11.CKU_USER, pin)
	if err != nil {
		return fmt.Errorf("unable to login")
	}
	defer ctx.Logout(session)

	keyType := pkcs11.CKK_RSA
	pubTypeAttrs, privTypeAttrs := rsaImportAttrs(privKey)
	commonAttrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, keyType),
		pkcs11.NewAttribute(pkcs11.CKA_ID, keyIDBytes),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, keyLabelBytes),
	}
	pubAttrs := attrConcat(commonAttrs, newPublicKeyAttrs, pubTypeAttrs)
	privAttrs := attrConcat(commonAttrs, newPrivateKeyAttrs, privTypeAttrs)
	pubHandle, err := ctx.CreateObject(session, pubAttrs)
	if err != nil {
		return fmt.Errorf("unable to create public key")
	}
	_, err = ctx.CreateObject(session, privAttrs)
	if err != nil {
		ctx.DestroyObject(session, pubHandle)
		return fmt.Errorf("unable to create private key")
	}

	return nil
}

func deleteKey(slotID uint) error {
	var handles []pkcs11.ObjectHandle

	ctx, err := initPKCS11(modulePath)
	if err != nil {
		return err
	}
	defer func() {
		ctx.Finalize()
		ctx.Destroy()
	}()

	keyIDBytes, err := hex.DecodeString(keyID)
	if err != nil {
		return err
	}
	keyLabelBytes := []byte(keyLabel)

	session, err := ctx.OpenSession(slotID, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
	if err != nil {
		return fmt.Errorf("unable to open session")
	}
	defer ctx.CloseSession(session)
	err = ctx.Login(session, pkcs11.CKU_USER, pin)
	if err != nil {
		return fmt.Errorf("unable to login")
	}
	defer ctx.Logout(session)

	maxHandlePerFind := 20
	publicAttrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_ID, keyIDBytes),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, keyLabelBytes),
	}
	if err = ctx.FindObjectsInit(session, publicAttrs); err != nil {
		return fmt.Errorf("unable to initialize find objects")
	}
	handles, _, err = ctx.FindObjects(session, maxHandlePerFind)
	if err != nil {
		return fmt.Errorf("unable to find objects")
	}
	err = ctx.FindObjectsFinal(session)
	if err != nil {
		return fmt.Errorf("unable to finalize find objects")
	}
	if len(handles) == 1 {
		ctx.DestroyObject(session, handles[0])
		if err != nil {
			return fmt.Errorf("unable to destroy public key")
		}
	}

	privAttrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_ID, keyIDBytes),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, keyLabelBytes),
	}
	if err = ctx.FindObjectsInit(session, privAttrs); err != nil {
		return fmt.Errorf("unable to initialize find objects")
	}
	handles, _, err = ctx.FindObjects(session, maxHandlePerFind)
	if err != nil {
		return fmt.Errorf("unable to find objects")
	}
	err = ctx.FindObjectsFinal(session)
	if err != nil {
		return fmt.Errorf("unable to finalize find objects")
	}
	if len(handles) == 1 {
		ctx.DestroyObject(session, handles[0])
		if err != nil {
			return fmt.Errorf("unable to destroy private key")
		}
	}

	return nil
}

func must(err error, t *testing.T) {
	t.Helper()
	if err != nil {
		t.Fatal(err)
	}
}

func mustErr(err error, t *testing.T) {
	t.Helper()
	if err == nil {
		t.Fatal("expected error")
	}
}

const rsaPrivKey = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZJZ44vB04D2wm
xz+3upmuWelrTWcceVC2v6fkBo9dIR9IejolFY+CsMF1Rc5LGXG3XStQHRrbmq1w
UxC8jsIOK7gI2xI9IOwCgyaQun3J+1VQc6eZxHLGQfTTNq7Vx67VOG8V8d3RhN7L
BvAMT5U55254bUgH0KVx5C1ybLcX6BdGaABCunh7tV+thgwEZSbr2/t0Tf8QneMr
eHojTKZp7/d90TH8KF+/FiPWJWWv5OVhjpZPwTWqUgL+6pgrMKUSWD/92JSDIZe6
UjxASE4JgnJWMQUhkerJ7j5P16gjdAwJAAt5m3L6wdfVQG2aZ9CJzUowk12ly4Dc
Dx73/UufAgMBAAECggEAF8iA/eHMqXk29UBZgDwV3PzIDhKaOoonBv0S3GzDgwW/
sWaBu9ISt9O4PKn6oEsXI2g2+D1X1bmpSWYvrRdNtdOgAohMBRn3/4Zx0OQ8JsU6
YOdp8fOMRp6uu/t/RrbqNTxLHnIxQ2N0K3SFEjQdOgxZEyOVAhYeKM0/FQtHOnzj
WoyZHT8pV3mr6WnxBw/4u/1Ahfau7fs6aVJLECc9jGF/6e7aQeb+yEeLrHayml8e
sbBx4l/1LqU/2S7SQrWtQ+fi+/MlgxvLh0XC7tTPP6I3cTetyMZime9EwwDiPebX
PLUgo8Kf/sHzd/25G9M3Yz+UCLemcPSMUjBUQTPtYQKBgQD8lnpjekyeOjNCdRVP
5w6h1wGN4aC4bCksZ89HKpHc44+3AjDT/aVviory+CyOj05qbXDdpNnNh+jl5llM
yDw15WIvSsXFx3UQ467VVrBKm7vr+k1LGgLJ2fSFbZUTyLvwW4NpP26KDW6SitZ8
B9lkepTZ0G4Eao51VgidHsulKQKBgQDcFJNAIctqUWDli4tA5L0G5tiypcAA7iIZ
0h2YK+7eOU2f3r8aaywbPhcRn+cKlrf3iV4BCZAv59WEJqq1HOlzU92jkmZspYPq
8kSZLaaiDIBw+vwV4prHDSdZFEY+hHq5eULPIgVm/M474JcghetkVt8pG3ee+Dml
o6zUrZr7hwKBgQDCiXbrpObbuoF+PsTSTGfFl923k74ALDWt4KoQ6qV61bz7O3G1
5BYFiVOo/CD9Dzxa1b1mx6+ED5f9cOL4MwPEks2DFPircgoknucpomGWpMkgXyAm
pnrdUcN0/Egj+6db4G+eoN8W7m9p6Ap3bmgtbge0lkYVmqfrkP6DXJOFuQKBgHA/
hkMFeYyGaRdqruGwSMEGaKvlYiKXUok8459DeReavn61y16cHujeKEHy/pImATqd
s3Zv/DyS0BIQ7qxlTKRnt/m/p8HuQXRJkLdX009/dNsrB/vZkfvIN7N1ZcZpJ3cF
5A9lWMAIXN+pUythYofQzw1WVxKbpDtZWcM3sH5tAoGBAMHgZdtmIyllx/1BbYSg
Emxj3LekvZL0e7afeod9f977ZETt/imaejnJNnGOPeSbtLSPfhwonLEp+5XmICzt
lJZAF8iP2m1n9h8sZga5rZQ0JgiwVNFNwde4sp1pD5UcFrYepHRxKPo50eJi3rhR
SwNAKWa96qm5o8BaQu/aRMRu
-----END PRIVATE KEY-----`

const rsaCert = `-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUL7BdF7HSUwEAdqElJjVLQYd2OekwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTExMDIxNzM3MzJaFw0zMTEw
MzExNzM3MzJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDZJZ44vB04D2wmxz+3upmuWelrTWcceVC2v6fkBo9d
IR9IejolFY+CsMF1Rc5LGXG3XStQHRrbmq1wUxC8jsIOK7gI2xI9IOwCgyaQun3J
+1VQc6eZxHLGQfTTNq7Vx67VOG8V8d3RhN7LBvAMT5U55254bUgH0KVx5C1ybLcX
6BdGaABCunh7tV+thgwEZSbr2/t0Tf8QneMreHojTKZp7/d90TH8KF+/FiPWJWWv
5OVhjpZPwTWqUgL+6pgrMKUSWD/92JSDIZe6UjxASE4JgnJWMQUhkerJ7j5P16gj
dAwJAAt5m3L6wdfVQG2aZ9CJzUowk12ly4DcDx73/UufAgMBAAGjUzBRMB0GA1Ud
DgQWBBRokgD44sdsSGQEQcbJ3vrCrXTIcTAfBgNVHSMEGDAWgBRokgD44sdsSGQE
QcbJ3vrCrXTIcTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA8
+CpbIGi4ycCcSeomBzGVXsTFgFutqqvh3BFQ1u6bPlV7hIlFd11zzgWBeKKxREJn
z3SipT1qGX+uP4iVhUux94f2rQCV25mJNRKft2phAUylMr+laiO7IkHFB1zzJTfz
Bi9gm55HGvGCIdSWFkLZ/MUNCMj3WtPrUYl5jqFgDDmCpLctmPoN4vxSa0of3apv
ILH8jSsN5XbL8G1hsT/IGlRRbzoiLCKgCp6e6TjZSq/Y+JWGyw/+sZJMI8Mg4Mje
054uJhD29xmbfxdYxrMWLAFb6yoWVbDJPdECFf9uwOXyDZ8bGd48frTdUU3Rb+m3
5Hue2g5US98p2jnJiv75
-----END CERTIFICATE-----`
