...

Source file src/github.com/sassoftware/relic/lib/signjar/verify.go

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

     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 signjar
    18  
    19  import (
    20  	"archive/zip"
    21  	"bytes"
    22  	"crypto"
    23  	"encoding/base64"
    24  	"fmt"
    25  	"hash"
    26  	"io"
    27  	"io/ioutil"
    28  	"net/http"
    29  	"path"
    30  	"strings"
    31  
    32  	"github.com/pkg/errors"
    33  	"github.com/sassoftware/relic/lib/pkcs7"
    34  	"github.com/sassoftware/relic/lib/pkcs9"
    35  	"github.com/sassoftware/relic/lib/x509tools"
    36  	"github.com/sassoftware/relic/signers/sigerrors"
    37  )
    38  
    39  var errNoDigests = errors.New("no recognized digests found")
    40  
    41  type JarSignature struct {
    42  	pkcs9.TimestampedSignature
    43  	SignatureHeader http.Header
    44  	Hash            crypto.Hash
    45  }
    46  
    47  func Verify(inz *zip.Reader, skipDigests bool) ([]*JarSignature, error) {
    48  	var manifest []byte
    49  	sigfiles := make(map[string][]byte)
    50  	sigblobs := make(map[string][]byte)
    51  	for _, f := range inz.File {
    52  		dir, name := path.Split(strings.ToUpper(f.Name))
    53  		if dir != "META-INF/" || name == "" {
    54  			continue
    55  		}
    56  		i := strings.LastIndex(name, ".")
    57  		if i < 0 {
    58  			continue
    59  		}
    60  		base, ext := name[:i], name[i:]
    61  		r2, err := f.Open()
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		contents, err := ioutil.ReadAll(r2)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  		if err := r2.Close(); err != nil {
    70  			return nil, err
    71  		}
    72  		if name == "MANIFEST.MF" {
    73  			manifest = contents
    74  		} else if ext == ".SF" {
    75  			sigfiles[base] = contents
    76  		} else if ext == ".RSA" || ext == ".DSA" || ext == ".EC" || strings.HasPrefix(name, "SIG-") {
    77  			sigblobs[base] = contents
    78  		}
    79  	}
    80  	if manifest == nil {
    81  		return nil, errors.New("JAR contains no META-INF/MANIFEST.MF")
    82  	} else if len(sigfiles) == 0 {
    83  		return nil, sigerrors.NotSignedError{Type: "JAR"}
    84  	}
    85  	sigs := make([]*JarSignature, 0, len(sigfiles))
    86  	for base, sigfile := range sigfiles {
    87  		pkcs := sigblobs[base]
    88  		if pkcs == nil {
    89  			return nil, fmt.Errorf("JAR contains sigfile META-INF/%s.SF with no matching signature", base)
    90  		}
    91  		psd, err := pkcs7.Unmarshal(pkcs)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  		sig, err := psd.Content.Verify(sigfile, false)
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  		ts, err := pkcs9.VerifyOptionalTimestamp(sig)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		hdr, err := verifySigFile(sigfile, manifest)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		hash, _ := x509tools.PkixDigestToHash(ts.SignerInfo.DigestAlgorithm)
   108  		sigs = append(sigs, &JarSignature{
   109  			TimestampedSignature: ts,
   110  			Hash:                 hash,
   111  			SignatureHeader:      hdr,
   112  		})
   113  	}
   114  	if !skipDigests {
   115  		if err := verifyManifest(inz, manifest); err != nil {
   116  			return nil, err
   117  		}
   118  	}
   119  	return sigs, nil
   120  }
   121  
   122  // Verify all digests in MANIFEST.MF
   123  func verifyManifest(inz *zip.Reader, manifest []byte) error {
   124  	parsed, err := ParseManifest(manifest)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	zipfiles := make(map[string]*zip.File, len(inz.File))
   129  	for _, fh := range inz.File {
   130  		zipfiles[fh.Name] = fh
   131  	}
   132  	for filename, keys := range parsed.Files {
   133  		if keys.Get("Magic") != "" {
   134  			continue
   135  		}
   136  		fh := zipfiles[filename]
   137  		if fh == nil {
   138  			return fmt.Errorf("file %s is in manifest but not JAR", filename)
   139  		}
   140  		r, err := fh.Open()
   141  		if err != nil {
   142  			return err
   143  		}
   144  		if err := hashFile(keys, r, ""); err != nil {
   145  			return errors.Wrapf(err, "file \"%s\" in MANIFEST.MF", filename)
   146  		}
   147  		if err := r.Close(); err != nil {
   148  			return err
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  type digester struct {
   155  	key, value string
   156  	hash       hash.Hash
   157  }
   158  
   159  // Verify any hashes present in a single manifest section against the given content.
   160  func hashFile(keys http.Header, content io.Reader, suffix string) error {
   161  	digesters := make([]digester, 0)
   162  	suffix = "-Digest" + suffix
   163  	for key, value := range keys {
   164  		if !strings.HasSuffix(key, suffix) {
   165  			continue
   166  		}
   167  		hashName := strings.ToUpper(key[:len(key)-len(suffix)])
   168  		hash := x509tools.HashByName(hashName)
   169  		if !hash.Available() {
   170  			return fmt.Errorf("unknown digest key in manifest: %s", hashName)
   171  		}
   172  		digesters = append(digesters, digester{key, value[0], hash.New()})
   173  	}
   174  	if len(digesters) == 0 {
   175  		return errNoDigests
   176  	}
   177  	buf := make([]byte, 32*1024)
   178  	for {
   179  		n, err := content.Read(buf)
   180  		if n > 0 {
   181  			for _, digester := range digesters {
   182  				digester.hash.Write(buf[:n])
   183  			}
   184  		}
   185  		if err == io.EOF {
   186  			break
   187  		} else if err != nil {
   188  			return err
   189  		}
   190  	}
   191  	for _, digester := range digesters {
   192  		calculated := base64.StdEncoding.EncodeToString(digester.hash.Sum(nil))
   193  		if calculated != digester.value {
   194  			return fmt.Errorf("%s mismatch: manifest %s != calculated %s", digester.key, digester.value, calculated)
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  func verifySigFile(sigfile, manifest []byte) (http.Header, error) {
   201  	sfParsed, err := ParseManifest(sigfile)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	if err := hashFile(sfParsed.Main, bytes.NewReader(manifest), "-Manifest"); err != nil {
   206  		if err != errNoDigests {
   207  			return nil, errors.Wrap(err, "manifest signature")
   208  		}
   209  		// fall through and verify all the section digests
   210  	} else {
   211  		// if the whole-file digest passed then skip the sections
   212  		return sfParsed.Main, nil
   213  	}
   214  	sections, err := splitManifest(manifest)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	sectionMap := make(map[string][]byte, len(sections)-1)
   219  	for i, section := range sections {
   220  		if i == 0 {
   221  			if err := hashFile(sfParsed.Main, bytes.NewReader(sections[0]), "-Manifest-Main-Attributes"); err != nil {
   222  				return nil, errors.Wrap(err, "manifest main attributes signature")
   223  			}
   224  		} else {
   225  			hdr, err := parseSection(section)
   226  			if err != nil {
   227  				return nil, err
   228  			}
   229  			name := hdr.Get("Name")
   230  			if name == "" {
   231  				return nil, errors.New("manifest has section with no \"Name\" attribute")
   232  			}
   233  			sectionMap[name] = section
   234  		}
   235  	}
   236  	for name, keys := range sfParsed.Files {
   237  		section := sectionMap[name]
   238  		if section == nil {
   239  			return nil, fmt.Errorf("manifest is missing signed section \"%s\"", name)
   240  		}
   241  		if err := hashFile(keys, bytes.NewReader(section), ""); err != nil {
   242  			return nil, errors.Wrapf(err, "manifest signature over section \"%s\"", name)
   243  		}
   244  	}
   245  	return sfParsed.Main, nil
   246  }
   247  

View as plain text