...

Source file src/github.com/sassoftware/relic/lib/appmanifest/signmanifest.go

Documentation: github.com/sassoftware/relic/lib/appmanifest

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package appmanifest
    18  
    19  import (
    20  	"bytes"
    21  	"crypto"
    22  	"encoding/base64"
    23  	"encoding/hex"
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/beevik/etree"
    28  	"github.com/sassoftware/relic/lib/certloader"
    29  	"github.com/sassoftware/relic/lib/pkcs7"
    30  	"github.com/sassoftware/relic/lib/pkcs9"
    31  	"github.com/sassoftware/relic/lib/xmldsig"
    32  )
    33  
    34  const (
    35  	NsMsRel        = "http://schemas.microsoft.com/windows/rel/2005/reldata"
    36  	NsMpeg21       = "urn:mpeg:mpeg21:2003:01-REL-R-NS"
    37  	NsAuthenticode = "http://schemas.microsoft.com/windows/pki/2005/Authenticode"
    38  )
    39  
    40  type SignedManifest struct {
    41  	ManifestSignature
    42  	Signed          []byte
    43  	EncryptedDigest []byte
    44  }
    45  
    46  // Sign an application manifest
    47  func Sign(manifest []byte, cert *certloader.Certificate, opts crypto.SignerOpts) (*SignedManifest, error) {
    48  	doc := etree.NewDocument()
    49  	if err := doc.ReadFromString(string(manifest)); err != nil {
    50  		return nil, err
    51  	}
    52  	root := doc.Root()
    53  	// Update signer-related attributes
    54  	asi, err := setAssemblyIdentity(root, cert)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	subjectName, err := setPublisherIdentity(root, cert)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	// Primary signature
    63  	sigopts := xmldsig.SignOptions{MsCompatHashNames: true, IncludeKeyValue: true}
    64  	if err := xmldsig.Sign(root, root, opts.HashFunc(), cert.Signer(), cert.Chain(), sigopts); err != nil {
    65  		return nil, err
    66  	}
    67  	sig, keyinfo := setSigIds(root, "StrongNameSignature", "StrongNameKeyInfo")
    68  	// Create authenticode structure
    69  	manifestHash := makeManifestHash(sig)
    70  	license, sigDestNode := makeLicense(asi, subjectName, manifestHash)
    71  	// Sign authenticode structure
    72  	sigopts.IncludeX509 = true
    73  	if err := xmldsig.Sign(license, sigDestNode, opts.HashFunc(), cert.Signer(), cert.Chain(), sigopts); err != nil {
    74  		return nil, err
    75  	}
    76  	aSig, _ := setSigIds(sigDestNode, "AuthenticodeSignature", "")
    77  	// Attach authenticode to the primary document
    78  	license.AddChild(sigDestNode)
    79  	reldata := keyinfo.CreateElement("msrel:RelData")
    80  	reldata.CreateAttr("xmlns:msrel", NsMsRel)
    81  	reldata.AddChild(license)
    82  	// Serialize
    83  	signed, err := doc.WriteToBytes()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	// Get authenticode signature value for timestamping
    88  	encryptedDigest, _ := base64.StdEncoding.DecodeString(aSig.SelectElement("SignatureValue").Text())
    89  	return &SignedManifest{
    90  		Signed:          signed,
    91  		EncryptedDigest: encryptedDigest,
    92  		ManifestSignature: ManifestSignature{
    93  			Signature: &pkcs9.TimestampedSignature{Signature: pkcs7.Signature{
    94  				Certificate:   cert.Leaf,
    95  				Intermediates: cert.Chain(),
    96  			}},
    97  			AssemblyName:    asi.SelectAttrValue("name", ""),
    98  			AssemblyVersion: asi.SelectAttrValue("version", ""),
    99  			Hash:            opts.HashFunc(),
   100  			PublicKeyToken:  asi.SelectAttrValue("publicKeyToken", ""),
   101  		}}, nil
   102  }
   103  
   104  // Update the assemblyIdentity element with the actual signer public key. Only
   105  // the top-level one is updated, not the ones underneath individual manifest
   106  // entries.
   107  func setAssemblyIdentity(root *etree.Element, cert *certloader.Certificate) (*etree.Element, error) {
   108  	token, err := PublicKeyToken(cert.Leaf.PublicKey)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	asi := root.SelectElement("assemblyIdentity")
   113  	if asi == nil {
   114  		return nil, errors.New("manifest has no top-level assemblyIdentity element")
   115  	}
   116  	asi.CreateAttr("publicKeyToken", token)
   117  	return asi, nil
   118  }
   119  
   120  // Add/replace the publisherIdentity element
   121  func setPublisherIdentity(root *etree.Element, cert *certloader.Certificate) (string, error) {
   122  	// add or replace publisherIdentity
   123  	subjectName, issuerKeyHash, err := PublisherIdentity(cert)
   124  	if err != nil {
   125  		return "", err
   126  	}
   127  	xmldsig.RemoveElements(root, "publisherIdentity")
   128  	ident := root.CreateElement("publisherIdentity")
   129  	ident.CreateAttr("name", subjectName)
   130  	ident.CreateAttr("issuerKeyHash", issuerKeyHash)
   131  	return subjectName, nil
   132  }
   133  
   134  // Create the "license" block that goes inside the inner signature
   135  func makeLicense(asi *etree.Element, subjectName, manifestHash string) (*etree.Element, *etree.Element) {
   136  	license := etree.NewElement("r:license")
   137  	license.CreateAttr("xmlns:r", NsMpeg21)
   138  	license.CreateAttr("xmlns:as", NsAuthenticode)
   139  
   140  	grant := license.CreateElement("r:grant")
   141  	minfo := grant.CreateElement("as:ManifestInformation")
   142  	minfo.CreateAttr("Hash", manifestHash)
   143  	minfo.CreateAttr("Description", "")
   144  	minfo.CreateAttr("Url", "")
   145  	massy := asi.Copy()
   146  	massy.Space = "as"
   147  	minfo.AddChild(massy)
   148  	grant.CreateElement("as:SignedBy")
   149  	grant.CreateElement("as:AuthenticodePublisher").CreateElement("as:X509SubjectName").SetText(subjectName)
   150  
   151  	issuer := license.CreateElement("r:issuer")
   152  	return license, issuer
   153  }
   154  
   155  // ManifestInformation contains a hash value which is, for some inane reason,
   156  // the same hash that the outer signature references but in reverse byte order.
   157  func makeManifestHash(sig *etree.Element) string {
   158  	dv := sig.FindElement(".//DigestValue")
   159  	blob, _ := base64.StdEncoding.DecodeString(dv.Text())
   160  	for i := 0; i < len(blob)/2; i++ {
   161  		j := len(blob) - i - 1
   162  		blob[i], blob[j] = blob[j], blob[i]
   163  	}
   164  	return hex.EncodeToString(blob)
   165  }
   166  
   167  // Set Id attributes on signature elements
   168  func setSigIds(root *etree.Element, sigName, keyinfoName string) (sig, keyinfo *etree.Element) {
   169  	sig = root.SelectElement("Signature")
   170  	if sigName != "" {
   171  		sig.CreateAttr("Id", sigName)
   172  	}
   173  	keyinfo = sig.SelectElement("KeyInfo")
   174  	if keyinfoName != "" {
   175  		keyinfo.CreateAttr("Id", keyinfoName)
   176  	}
   177  	return sig, keyinfo
   178  }
   179  
   180  // Attach a timestamp counter-signature
   181  func (m *SignedManifest) AddTimestamp(token *pkcs7.ContentInfoSignedData) error {
   182  	doc := etree.NewDocument()
   183  	if err := doc.ReadFromString(string(m.Signed)); err != nil {
   184  		return err
   185  	}
   186  	aSig := doc.Root().FindElement("Signature/KeyInfo/msrel:RelData/r:license/r:issuer/Signature")
   187  	if aSig == nil {
   188  		return errors.New("manifest has no authenticode signature")
   189  	}
   190  	siblob, err := token.Marshal()
   191  	if err != nil {
   192  		return err
   193  	}
   194  	lines := (47 + len(siblob)) / 48
   195  	buf := bytes.NewBuffer(make([]byte, 0, (64+2)*lines))
   196  	for len(siblob) > 0 {
   197  		n := len(siblob)
   198  		if n > 48 {
   199  			n = 48
   200  		}
   201  		chunk := siblob[:n]
   202  		siblob = siblob[n:]
   203  
   204  		buf.WriteString(base64.StdEncoding.EncodeToString(chunk))
   205  		buf.WriteString("\r\n")
   206  	}
   207  	aSig.CreateElement("Object").CreateElement("as:Timestamp").SetText(buf.String())
   208  	signed, err := doc.WriteToBytes()
   209  	if err != nil {
   210  		return err
   211  	}
   212  	cs, err := VerifyTimestamp(token, m.EncryptedDigest, m.Signature.Intermediates)
   213  	if err != nil {
   214  		return fmt.Errorf("failed to validate timestamp: %s", err)
   215  	}
   216  	m.Signed = signed
   217  	m.Signature.CounterSignature = cs
   218  	return nil
   219  }
   220  

View as plain text