...

Source file src/github.com/sassoftware/relic/lib/signjar/manifest.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  	"bytes"
    21  	"crypto"
    22  	"encoding/base64"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"strings"
    28  
    29  	"github.com/sassoftware/relic/config"
    30  	"github.com/sassoftware/relic/lib/x509tools"
    31  )
    32  
    33  // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JAR_Manifest
    34  
    35  const (
    36  	metaInf      = "META-INF/"
    37  	manifestName = metaInf + "MANIFEST.MF"
    38  )
    39  
    40  type FilesMap struct {
    41  	Main  http.Header
    42  	Order []string
    43  	Files map[string]http.Header
    44  }
    45  
    46  func ParseManifest(manifest []byte) (files *FilesMap, err error) {
    47  	sections, err := splitManifest(manifest)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	files = &FilesMap{
    52  		Order: make([]string, 0, len(sections)-1),
    53  		Files: make(map[string]http.Header, len(sections)-1),
    54  	}
    55  	for i, section := range sections {
    56  		if i > 0 && len(section) == 0 {
    57  			continue
    58  		}
    59  		hdr, err := parseSection(section)
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  		if i == 0 {
    64  			files.Main = hdr
    65  		} else {
    66  			name := hdr.Get("Name")
    67  			if name == "" {
    68  				return nil, errors.New("manifest has section with no \"Name\" attribute")
    69  			}
    70  			files.Order = append(files.Order, name)
    71  			files.Files[name] = hdr
    72  		}
    73  	}
    74  	return files, nil
    75  }
    76  
    77  func (m *FilesMap) Dump() []byte {
    78  	var out bytes.Buffer
    79  	writeSection(&out, m.Main, "Manifest-Version")
    80  	for _, name := range m.Order {
    81  		section := m.Files[name]
    82  		if section != nil {
    83  			writeSection(&out, section, "Name")
    84  		}
    85  	}
    86  	return out.Bytes()
    87  }
    88  
    89  func splitManifest(manifest []byte) ([][]byte, error) {
    90  	sections := make([][]byte, 0)
    91  	for len(manifest) != 0 {
    92  		i1 := bytes.Index(manifest, []byte("\r\n\r\n"))
    93  		i2 := bytes.Index(manifest, []byte("\n\n"))
    94  		var idx int
    95  		if i1 < 0 {
    96  			if i2 < 0 {
    97  				return nil, errors.New("trailing bytes after last newline")
    98  			}
    99  			idx = i2 + 2
   100  		} else {
   101  			idx = i1 + 4
   102  		}
   103  		section := manifest[:idx]
   104  		manifest = manifest[idx:]
   105  		sections = append(sections, section)
   106  	}
   107  	return sections, nil
   108  }
   109  
   110  func parseSection(section []byte) (http.Header, error) {
   111  	section = bytes.Replace(section, []byte("\r\n"), []byte{'\n'}, -1)
   112  	section = bytes.Replace(section, []byte("\n "), []byte{}, -1)
   113  	keys := bytes.Split(section, []byte{'\n'})
   114  	hdr := make(http.Header)
   115  	for _, line := range keys {
   116  		if len(line) == 0 {
   117  			continue
   118  		}
   119  		idx := bytes.IndexRune(line, ':')
   120  		if idx < 0 {
   121  			return nil, errors.New("jar manifest is malformed")
   122  		}
   123  		key := strings.TrimSpace(string(line[:idx]))
   124  		value := strings.TrimSpace(string(line[idx+1:]))
   125  		hdr.Set(key, value)
   126  	}
   127  	return hdr, nil
   128  }
   129  
   130  func hashSection(hash crypto.Hash, section []byte) string {
   131  	d := hash.New()
   132  	d.Write(section)
   133  	return base64.StdEncoding.EncodeToString(d.Sum(nil))
   134  }
   135  
   136  // Transform a MANIFEST.MF into a *.SF by digesting each section with the
   137  // specified hash
   138  func DigestManifest(manifest []byte, hash crypto.Hash, sectionsOnly, apkV2 bool) ([]byte, error) {
   139  	sections, err := splitManifest(manifest)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	hashName := x509tools.HashNames[hash]
   144  	if hashName == "" {
   145  		return nil, errors.New("unsupported hash type")
   146  	}
   147  	var output bytes.Buffer
   148  	writeAttribute(&output, "Signature-Version", "1.0")
   149  	writeAttribute(&output, hashName+"-Digest-Manifest-Main-Attributes", hashSection(hash, sections[0]))
   150  	if !sectionsOnly {
   151  		writeAttribute(&output, hashName+"-Digest-Manifest", hashSection(hash, manifest))
   152  	}
   153  	writeAttribute(&output, "Created-By", fmt.Sprintf("%s (%s)", config.UserAgent, config.Author))
   154  	if apkV2 {
   155  		writeAttribute(&output, "X-Android-APK-Signed", "2")
   156  	}
   157  	output.WriteString("\r\n")
   158  	for _, section := range sections[1:] {
   159  		hdr, err := parseSection(section)
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		name := hdr.Get("Name")
   164  		if name == "" {
   165  			return nil, errors.New("File section was missing Name attribute")
   166  		}
   167  		writeAttribute(&output, "Name", name)
   168  		writeAttribute(&output, hashName+"-Digest", hashSection(hash, section))
   169  		output.WriteString("\r\n")
   170  	}
   171  	return output.Bytes(), nil
   172  }
   173  
   174  const maxLineLength = 70
   175  
   176  // Write a key-value pair, wrapping long lines as necessary
   177  func writeAttribute(out io.Writer, key, value string) {
   178  	line := []byte(fmt.Sprintf("%s: %s", key, value))
   179  	for i := 0; i < len(line); i += maxLineLength {
   180  		j := i + maxLineLength
   181  		if j > len(line) {
   182  			j = len(line)
   183  		}
   184  		if i != 0 {
   185  			out.Write([]byte{' '})
   186  		}
   187  		out.Write(line[i:j])
   188  		out.Write([]byte("\r\n"))
   189  	}
   190  }
   191  
   192  func writeSection(out io.Writer, hdr http.Header, first string) {
   193  	value := hdr.Get(first)
   194  	if value != "" {
   195  		writeAttribute(out, first, value)
   196  	}
   197  	for key, values := range hdr {
   198  		if key == first {
   199  			continue
   200  		}
   201  		for _, value := range values {
   202  			writeAttribute(out, key, value)
   203  		}
   204  	}
   205  	out.Write([]byte("\r\n"))
   206  }
   207  

View as plain text