// Copyright 2019 Canonical Ltd.
// Licensed under the LGPLv3 with static-linking exception.
// See LICENCE file for details.

package tpm2

import (
	"github.com/canonical/go-tpm2/mu"
)

// Section 18 - Attestation Commands

// Certify executes the TPM2_Certify command, which is used to prove that an object with a specific name is loaded in to the TPM.
// By producing an attestation, the TPM certifies that the object with a given name is loaded in to the TPM and consistent with a
// valid sensitive area.
//
// The objectContext parameter corresponds to the object for which to produce an attestation. The command requires authorization with
// the admin role for objectContext, with session based authorization provided via objectContextAuthSession.
//
// If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires
// authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession.
//
// If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error
// code of ErrorKey will be returned for handle index 2.
//
// If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be
// provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme
// will be returned for parameter index 2.
//
// If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If
// it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of
// ErrorScheme will be returned for parameter index 2.
//
// On successful, it returns an attestation structure detailing the name of the object associated with objectContext. If signContext
// is not nil, the attestation structure will be signed by the associated key and returned too.
func (t *TPMContext) Certify(objectContext, signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, objectContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (certifyInfo *Attest, signature *Signature, err error) {
	if inScheme == nil {
		inScheme = &SigScheme{Scheme: SigSchemeAlgNull}
	}

	if err := t.RunCommand(CommandCertify, sessions,
		ResourceContextWithSession{Context: objectContext, Session: objectContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter,
		qualifyingData, inScheme, Delimiter,
		Delimiter,
		mu.Sized(&certifyInfo), &signature); err != nil {
		return nil, nil, err
	}

	return certifyInfo, signature, nil
}

// CertifyCreation executes the TPM2_CertifyCreation command, which is used to prove the association between the object represented
// by objectContext and its creation data represented by creationHash. It does this by computing a ticket from creationHash and the
// name of the object represented by objectContext and then verifying that it matches the provided creationTicket, which was provided
// by the TPM at object creation time.
//
// If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires
// authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession.
//
// If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error
// code of ErrorKey will be returned for handle index 1.
//
// If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be
// provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme
// will be returned for parameter index 3.
//
// If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If
// it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of
// ErrorScheme will be returned for parameter index 3.
//
// If creationTicket corresponds to an invalid ticket, a *TPMParameterError error with an error code of ErrorTicket will be returned
// for parameter index 4.
//
// If the digest generated for signing is greater than or has a larger size than the modulus of the key associated with signContext, a
// *TPMError with an error code of ErrorValue will be returned.
//
// If successful, it returns an attestation structure. If signContext is not nil, the attestation structure will be signed by the
// associated key and returned too.
func (t *TPMContext) CertifyCreation(signContext, objectContext ResourceContext, qualifyingData Data, creationHash Digest, inScheme *SigScheme, creationTicket *TkCreation, signContextAuthSession SessionContext, sessions ...SessionContext) (certifyInfo *Attest, signature *Signature, err error) {
	if inScheme == nil {
		inScheme = &SigScheme{Scheme: SigSchemeAlgNull}
	}

	if err := t.RunCommand(CommandCertifyCreation, sessions,
		ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, objectContext, Delimiter,
		qualifyingData, creationHash, inScheme, creationTicket, Delimiter,
		Delimiter,
		mu.Sized(&certifyInfo), &signature); err != nil {
		return nil, nil, err
	}

	return certifyInfo, signature, nil
}

// Quote executes the TPM2_Quote command in order to quote a set of PCR values. The TPM will hash the set of PCRs specified by the
// pcrs parameter.
//
// If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires
// authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession.
//
// If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error
// code of ErrorKey will be returned for handle index 1.
//
// If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be
// provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme
// will be returned for parameter index 2.
//
// If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If
// it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of
// ErrorScheme will be returned for parameter index 2.
//
// On successful, it returns an attestation structure containing the hash of the PCRs selected by the pcrs parameter. If signContext
// is not nil, the attestation structure will be signed by the associated key and returned too.
func (t *TPMContext) Quote(signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, pcrs PCRSelectionList, signContextAuthSession SessionContext, sessions ...SessionContext) (quoted *Attest, signature *Signature, err error) {
	if inScheme == nil {
		inScheme = &SigScheme{Scheme: SigSchemeAlgNull}
	}

	if err := t.RunCommand(CommandQuote, sessions,
		ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter,
		qualifyingData, inScheme, pcrs, Delimiter,
		Delimiter,
		mu.Sized(&quoted), &signature); err != nil {
		return nil, nil, err
	}

	return quoted, signature, nil
}

// GetSessionAuditDigest executes the TPM2_GetSessionAuditDigest to obtain the current digest of the audit session corresponding to
// sessionContext.
//
// The privacyAdminContext argument must be a ResourceContext that corresponds to HandleEndorsement. This command requires authorization
// with the user auth role for privacyAdminContext, with session based authorization provided via privacyAdminContextAuthSession.
//
// If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires
// authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession.
//
// If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error
// code of ErrorKey will be returned for handle index 2.
//
// If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be
// provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme
// will be returned for parameter index 2.
//
// If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If
// it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of
// ErrorScheme will be returned for parameter index 2.
//
// On success, it returns an attestation structure detailing the current audit digest for sessionContext. If signContext is not nil,
// the attestation structure will be signed by the associated key and returned too.
func (t *TPMContext) GetSessionAuditDigest(privacyAdminContext, signContext ResourceContext, sessionContext SessionContext, qualifyingData Data, inScheme *SigScheme, privacyAdminContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (auditInfo *Attest, signature *Signature, err error) {
	if inScheme == nil {
		inScheme = &SigScheme{Scheme: SigSchemeAlgNull}
	}

	if err := t.RunCommand(CommandGetSessionAuditDigest, sessions,
		ResourceContextWithSession{Context: privacyAdminContext, Session: privacyAdminContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, sessionContext, Delimiter,
		qualifyingData, inScheme, Delimiter,
		Delimiter,
		mu.Sized(&auditInfo), &signature); err != nil {
		return nil, nil, err
	}

	return auditInfo, signature, nil
}

// GetCommandAuditDigest executes the TPM2_GetCommandAuditDigest command to obtain the current command audit digest, the current
// audit digest algorithm and a digest of the list of commands being audited.
//
// The privacyContext argument must be a ResourceContext corresponding to HandleEndorsement. This command requires authorization with
// the user auth role for privacyContext, with session based authorization provided via privacyContextAuthSession.
//
// If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires
// authorization with the user auth role for signContext, with session based authorization provided via provided via
// signContextAuthSession.
//
// If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error
// code of ErrorKey will be returned for handle index 2.
//
// If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be
// provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme
// will be returned for parameter index 2.
//
// If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If
// it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of
// ErrorScheme will be returned for parameter index 2.
//
// On success, it returns an attestation structure detailing the current command audit digest, digest algorithm and a digest of the
// list of commands being audited. If signContext is not nil, the attestation structure will be signed by the associated key and
// returned too.
func (t *TPMContext) GetCommandAuditDigest(privacyContext, signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, privacyContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (auditInfo *Attest, signature *Signature, err error) {
	if inScheme == nil {
		inScheme = &SigScheme{Scheme: SigSchemeAlgNull}
	}

	if err := t.RunCommand(CommandGetCommandAuditDigest, sessions,
		ResourceContextWithSession{Context: privacyContext, Session: privacyContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter,
		qualifyingData, inScheme, Delimiter,
		Delimiter,
		mu.Sized(&auditInfo), &signature); err != nil {
		return nil, nil, err
	}

	return auditInfo, signature, nil
}

// GetTime executes the TPM2_GetTime command in order to obtain the current values of time and clock.
//
// The privacyAdminContext argument must be a ResourceContext that corresponds to HandleEndorsement. The command requires authorization
// with the user auth role for privacyAdminContext, with session based authorization provided via privacyAdminContextAuthSession.
//
// If signContext is not nil, the returned attestation will be signed by the key associated with it. This command requires
// authorization with the user auth role for signContext, with session based authorization provided via signContextAuthSession.
//
// If signContext is not nil and the object associated with signContext is not a signing key, a *TPMHandleError error with an error
// code of ErrorKey will be returned for handle index 2.
//
// If signContext is not nil and if the scheme of the key associated with signContext is AsymSchemeNull, then inScheme must be
// provided to specify a valid signing scheme for the key. If it isn't, a *TPMParameterError error with an error code of ErrorScheme
// will be returned for parameter index 2.
//
// If signContext is not nil and the scheme of the key associated with signContext is not AsymSchemeNull, then inScheme may be nil. If
// it is provided, then the specified scheme must match that of the signing key, else a *TPMParameterError error with an error code of
// ErrorScheme will be returned for parameter index 2.
//
// On success, it returns an attestation structure detailing the current values of time and clock. If signContext is not nil, the
// attestation structure will be signed by the associated key and returned too.
func (t *TPMContext) GetTime(privacyAdminContext, signContext ResourceContext, qualifyingData Data, inScheme *SigScheme, privacyAdminContextAuthSession, signContextAuthSession SessionContext, sessions ...SessionContext) (timeInfo *Attest, signature *Signature, err error) {
	if inScheme == nil {
		inScheme = &SigScheme{Scheme: SigSchemeAlgNull}
	}

	if err := t.RunCommand(CommandGetTime, sessions,
		ResourceContextWithSession{Context: privacyAdminContext, Session: privacyAdminContextAuthSession}, ResourceContextWithSession{Context: signContext, Session: signContextAuthSession}, Delimiter,
		qualifyingData, inScheme, Delimiter,
		Delimiter,
		mu.Sized(&timeInfo), &signature); err != nil {
		return nil, nil, err
	}

	return timeInfo, signature, nil
}
