//
// Copyright (c) SAS Institute Inc.
//
// 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.
//

package p11token

import (
	"crypto/sha1"
	"crypto/x509"
	"errors"
	"fmt"

	"github.com/miekg/pkcs11"
	"github.com/sassoftware/relic/signers/sigerrors"
)

var newCertAttrs = []*pkcs11.Attribute{
	pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
	pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
	pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
	pkcs11.NewAttribute(pkcs11.CKA_CERTIFICATE_TYPE, pkcs11.CKC_X_509),
}

func (tk *Token) ImportCertificate(cert *x509.Certificate, labelBase string) error {
	fingerprint := sha1.Sum(cert.Raw)
	if labelBase == "" {
		return errors.New("label is required")
	}
	// make a label from the private key label plus the certificate fingerprint
	label := fmt.Sprintf("%s_chain_%x", labelBase, fingerprint[:8])
	tk.mutex.Lock()
	defer tk.mutex.Unlock()
	if err := tk.certExists(label); err != nil {
		return err
	}
	keyID := makeKeyID()
	if keyID == nil {
		return errors.New("failed to make key ID")
	}
	attrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
		pkcs11.NewAttribute(pkcs11.CKA_SUBJECT, cert.RawSubject),
		pkcs11.NewAttribute(pkcs11.CKA_ISSUER, cert.RawIssuer),
		pkcs11.NewAttribute(pkcs11.CKA_VALUE, cert.Raw),
	}
	attrs = append(attrs, newCertAttrs...)
	_, err := tk.ctx.CreateObject(tk.sh, attrs)
	return err
}

func (tk *Token) certExists(label string) error {
	attrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
	}
	objects, err := tk.findObject(attrs)
	if err != nil {
		return err
	} else if len(objects) != 0 {
		return sigerrors.ErrExist
	} else {
		return nil
	}
}

func (key *Key) ImportCertificate(cert *x509.Certificate) error {
	keyID, handle, err := key.findCertificate()
	if err != nil {
		return err
	} else if handle != 0 {
		return sigerrors.ErrExist
	}
	label := key.getLabel()
	if label == "" {
		return errors.New("label is required")
	}
	attrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
		pkcs11.NewAttribute(pkcs11.CKA_SUBJECT, cert.RawSubject),
		pkcs11.NewAttribute(pkcs11.CKA_ISSUER, cert.RawIssuer),
		pkcs11.NewAttribute(pkcs11.CKA_VALUE, cert.Raw),
	}
	attrs = append(attrs, newCertAttrs...)
	key.token.mutex.Lock()
	defer key.token.mutex.Unlock()
	_, err = key.token.ctx.CreateObject(key.token.sh, attrs)
	return err
}

func (key *Key) findCertificate() (keyID []byte, handle pkcs11.ObjectHandle, err error) {
	keyID = key.GetID()
	if len(keyID) == 0 {
		return nil, 0, errors.New("no keyID")
	}
	key.token.mutex.Lock()
	defer key.token.mutex.Unlock()
	attrs := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
		pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
	}
	objects, err := key.token.findObject(attrs)
	if err != nil {
		return nil, 0, err
	}
	if len(objects) == 0 {
		return keyID, 0, nil
	}
	return keyID, objects[0], nil
}
