//
// 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 appmanifest

// Sign Microsoft ClickOnce application manifests and deployment manifests.
// These take the form of an XML file using XML DSIG signatures and, unlike all
// other Microsoft signatures, does not use an Authenticode PKCS#7 structure.

import (
	"fmt"
	"io"
	"io/ioutil"

	"github.com/sassoftware/relic/lib/appmanifest"
	"github.com/sassoftware/relic/lib/audit"
	"github.com/sassoftware/relic/lib/certloader"
	"github.com/sassoftware/relic/lib/magic"
	"github.com/sassoftware/relic/lib/pkcs9"
	"github.com/sassoftware/relic/signers"
)

var AppSigner = &signers.Signer{
	Name:         "appmanifest",
	Magic:        magic.FileTypeAppManifest,
	CertTypes:    signers.CertTypeX509,
	FormatLog:    formatLog,
	Sign:         sign,
	VerifyStream: verify,
}

func init() {
	AppSigner.Flags().Bool("rfc3161-timestamp", true, "(APPMANIFEST) Timestamp with RFC3161 server")
	signers.Register(AppSigner)
}

func formatLog(info *audit.Info) string {
	return fmt.Sprintf("assembly=%s version=%s publicKeyToken=%s",
		info.Attributes["assembly.name"],
		info.Attributes["assembly.version"],
		info.Attributes["assembly.publicKeyToken"],
	)
}

func sign(r io.Reader, cert *certloader.Certificate, opts signers.SignOpts) ([]byte, error) {
	blob, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	signed, err := appmanifest.Sign(blob, cert, opts.Hash)
	if err != nil {
		return nil, err
	}
	if cert.Timestamper != nil {
		tsreq := &pkcs9.Request{
			EncryptedDigest: signed.EncryptedDigest,
			Legacy:          !opts.Flags.GetBool("rfc3161-timestamp"),
			Hash:            opts.Hash,
		}

		token, err := cert.Timestamper.Timestamp(opts.Context(), tsreq)
		if err != nil {
			return nil, err
		}
		if err := signed.AddTimestamp(token); err != nil {
			return nil, err
		}
	}
	opts.Audit.SetMimeType("application/xml")
	opts.Audit.Attributes["assembly.name"] = signed.AssemblyName
	opts.Audit.Attributes["assembly.version"] = signed.AssemblyVersion
	opts.Audit.Attributes["assembly.publicKeyToken"] = signed.PublicKeyToken
	opts.Audit.SetCounterSignature(signed.Signature.CounterSignature)
	return signed.Signed, nil
}

func verify(r io.Reader, opts signers.VerifyOpts) ([]*signers.Signature, error) {
	blob, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	sig, err := appmanifest.Verify(blob)
	if err != nil {
		return nil, err
	}
	return []*signers.Signature{&signers.Signature{
		Package:       fmt.Sprintf("%s %s", sig.AssemblyName, sig.AssemblyVersion),
		Hash:          sig.Hash,
		X509Signature: sig.Signature,
	}}, nil
}
