...

Source file src/github.com/sassoftware/relic/lib/signjar/digest.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  	"crypto"
    21  	"encoding/base64"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"net/http"
    27  	"strings"
    28  
    29  	"github.com/sassoftware/relic/lib/x509tools"
    30  	"github.com/sassoftware/relic/lib/zipslicer"
    31  )
    32  
    33  // found in the "extra" field of JAR files, not strictly required but it makes
    34  // `file` output actually say JAR
    35  var jarMagic = []byte{0xfe, 0xca, 0, 0}
    36  
    37  type JarDigest struct {
    38  	Digests  map[string]string
    39  	Manifest []byte
    40  	Hash     crypto.Hash
    41  	inz      *zipslicer.Directory
    42  }
    43  
    44  func DigestJarStream(r io.Reader, hash crypto.Hash) (*JarDigest, error) {
    45  	inz, err := zipslicer.ReadZipTar(r)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	return updateManifest(inz, hash)
    50  }
    51  
    52  // Digest all of the files in the JAR
    53  func digestFiles(jar *zipslicer.Directory, hash crypto.Hash) (*JarDigest, error) {
    54  	jd := &JarDigest{
    55  		Hash:    hash,
    56  		Digests: make(map[string]string),
    57  		inz:     jar,
    58  	}
    59  	for _, f := range jar.File {
    60  		if f.Name == manifestName {
    61  			r, err := f.Open()
    62  			if err != nil {
    63  				return nil, fmt.Errorf("failed to read JAR manifest: %s", err)
    64  			}
    65  			jd.Manifest, err = ioutil.ReadAll(r)
    66  			if err != nil {
    67  				return nil, fmt.Errorf("failed to read JAR manifest: %s", err)
    68  			}
    69  		} else if (len(f.Name) > 0 && f.Name[len(f.Name)-1] == '/') || strings.HasPrefix(f.Name, "META-INF/") {
    70  			// not hashing
    71  		} else {
    72  			r, err := f.Open()
    73  			if err != nil {
    74  				return nil, err
    75  			}
    76  			d := hash.New()
    77  			if _, err := io.Copy(d, r); err != nil {
    78  				return nil, fmt.Errorf("failed to digest JAR file %s: %s", f.Name, err)
    79  			}
    80  			if err := r.Close(); err != nil {
    81  				return nil, fmt.Errorf("failed to digest JAR file %s: %s", f.Name, err)
    82  			}
    83  			jd.Digests[f.Name] = base64.StdEncoding.EncodeToString(d.Sum(nil))
    84  		}
    85  		// Ensure we get a copy of the zip metadata even if the file isn't
    86  		// digested, because if we're reading from a stream we can't go back
    87  		// and get it later.
    88  		if _, err := f.GetDataDescriptor(); err != nil {
    89  			return nil, fmt.Errorf("failed to read JAR manifest: %s", err)
    90  		}
    91  	}
    92  	return jd, nil
    93  }
    94  
    95  // Check JAR contents against its manifest and adds digests if necessary
    96  func updateManifest(jar *zipslicer.Directory, hash crypto.Hash) (*JarDigest, error) {
    97  	jd, err := digestFiles(jar, hash)
    98  	if err != nil {
    99  		return nil, err
   100  	} else if jd.Manifest == nil {
   101  		return nil, errors.New("JAR did not contain a manifest")
   102  	}
   103  	files, err := ParseManifest(jd.Manifest)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	hashName := x509tools.HashNames[hash]
   109  	if hashName == "" {
   110  		return nil, errors.New("unsupported hash type")
   111  	}
   112  	hashName += "-Digest"
   113  	changed := false
   114  	for name, calculated := range jd.Digests {
   115  		// if the manifest has a matching digest, check it. otherwise add to the manifest.
   116  		attrs := files.Files[name]
   117  		if attrs == nil {
   118  			// file is not mentioned in the manifest at all
   119  			files.Files[name] = http.Header{
   120  				"Name":   []string{name},
   121  				hashName: []string{calculated},
   122  			}
   123  			files.Order = append(files.Order, name)
   124  			changed = true
   125  		} else if attrs.Get("Magic") != "" {
   126  			// magic means a special digester is required. hopefully it's already been digested.
   127  		} else if existing := attrs.Get(hashName); existing != "" {
   128  			// manifest has a digest already, check it
   129  			if existing != calculated {
   130  				return nil, fmt.Errorf("%s mismatch for JAR file %s: manifest %s != calculated %s", hashName, name, existing, calculated)
   131  			}
   132  		} else {
   133  			// file in manifest but no matching digest
   134  			attrs.Set(hashName, calculated)
   135  			changed = true
   136  		}
   137  	}
   138  	if changed {
   139  		jd.Manifest = files.Dump()
   140  	}
   141  	return jd, nil
   142  }
   143  

View as plain text