github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/vfile/vfile_test.go (about)

     1  // Copyright 2020 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package vfile
     6  
     7  import (
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  
    18  	"golang.org/x/crypto/openpgp"
    19  	"golang.org/x/crypto/openpgp/errors"
    20  )
    21  
    22  type signedFile struct {
    23  	signers []*openpgp.Entity
    24  	content string
    25  }
    26  
    27  func (s signedFile) write(path string) error {
    28  	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
    29  	if err != nil {
    30  		return err
    31  	}
    32  	defer f.Close()
    33  
    34  	if _, err := f.Write([]byte(s.content)); err != nil {
    35  		return err
    36  	}
    37  
    38  	sigf, err := os.OpenFile(fmt.Sprintf("%s.sig", path), os.O_RDWR|os.O_CREATE, 0600)
    39  	if err != nil {
    40  		return err
    41  	}
    42  	defer sigf.Close()
    43  	for _, signer := range s.signers {
    44  		if err := openpgp.DetachSign(sigf, signer, strings.NewReader(s.content), nil); err != nil {
    45  			return err
    46  		}
    47  	}
    48  	return nil
    49  }
    50  
    51  type normalFile struct {
    52  	content string
    53  }
    54  
    55  func (n normalFile) write(path string) error {
    56  	return ioutil.WriteFile(path, []byte(n.content), 0600)
    57  }
    58  
    59  func writeHashedFile(path, content string) ([]byte, error) {
    60  	c := []byte(content)
    61  	if err := ioutil.WriteFile(path, c, 0600); err != nil {
    62  		return nil, err
    63  	}
    64  	hash := sha256.Sum256(c)
    65  	return hash[:], nil
    66  }
    67  
    68  func TestOpenSignedFile(t *testing.T) {
    69  	key, err := openpgp.NewEntity("goog", "goog", "goog@goog", nil)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	ring := openpgp.EntityList{key}
    74  
    75  	key2, err := openpgp.NewEntity("goog2", "goog2", "goog@goog", nil)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	dir, err := ioutil.TempDir("", "opensignedfile")
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  	defer os.RemoveAll(dir)
    85  
    86  	signed := signedFile{
    87  		signers: openpgp.EntityList{key},
    88  		content: "foo",
    89  	}
    90  	signedPath := filepath.Join(dir, "signed_by_key")
    91  	if err := signed.write(signedPath); err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	signed2 := signedFile{
    96  		signers: openpgp.EntityList{key2},
    97  		content: "foo",
    98  	}
    99  	signed2Path := filepath.Join(dir, "signed_by_key2")
   100  	if err := signed2.write(signed2Path); err != nil {
   101  		t.Fatal(err)
   102  	}
   103  
   104  	signed12 := signedFile{
   105  		signers: openpgp.EntityList{key, key2},
   106  		content: "foo",
   107  	}
   108  	signed12Path := filepath.Join(dir, "signed_by_both.sig")
   109  	if err := signed12.write(signed12Path); err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	normalPath := filepath.Join(dir, "unsigned")
   114  	if err := ioutil.WriteFile(normalPath, []byte("foo"), 0777); err != nil {
   115  		t.Fatal(err)
   116  	}
   117  
   118  	for _, tt := range []struct {
   119  		desc             string
   120  		path             string
   121  		keyring          openpgp.KeyRing
   122  		want             error
   123  		isSignatureValid bool
   124  	}{
   125  		{
   126  			desc:             "signed file",
   127  			keyring:          ring,
   128  			path:             signedPath,
   129  			want:             nil,
   130  			isSignatureValid: true,
   131  		},
   132  		{
   133  			desc:             "signed file w/ two signatures (key1 ring)",
   134  			keyring:          ring,
   135  			path:             signed12Path,
   136  			want:             nil,
   137  			isSignatureValid: true,
   138  		},
   139  		{
   140  			desc:             "signed file w/ two signatures (key2 ring)",
   141  			keyring:          openpgp.EntityList{key2},
   142  			path:             signed12Path,
   143  			want:             nil,
   144  			isSignatureValid: true,
   145  		},
   146  		{
   147  			desc:    "nil keyring",
   148  			keyring: nil,
   149  			path:    signed2Path,
   150  			want: ErrUnsigned{
   151  				Path: signed2Path,
   152  				Err:  ErrNoKeyRing,
   153  			},
   154  			isSignatureValid: false,
   155  		},
   156  		{
   157  			desc:    "non-nil empty keyring",
   158  			keyring: openpgp.EntityList{},
   159  			path:    signed2Path,
   160  			want: ErrUnsigned{
   161  				Path: signed2Path,
   162  				Err:  errors.ErrUnknownIssuer,
   163  			},
   164  			isSignatureValid: false,
   165  		},
   166  		{
   167  			desc:    "signed file does not match keyring",
   168  			keyring: openpgp.EntityList{key2},
   169  			path:    signedPath,
   170  			want: ErrUnsigned{
   171  				Path: signedPath,
   172  				Err:  errors.ErrUnknownIssuer,
   173  			},
   174  			isSignatureValid: false,
   175  		},
   176  		{
   177  			desc:    "unsigned file",
   178  			keyring: ring,
   179  			path:    normalPath,
   180  			want: ErrUnsigned{
   181  				Path: normalPath,
   182  				Err: &os.PathError{
   183  					Op:   "open",
   184  					Path: fmt.Sprintf("%s.sig", normalPath),
   185  					Err:  syscall.ENOENT,
   186  				},
   187  			},
   188  			isSignatureValid: false,
   189  		},
   190  		{
   191  			desc:    "file does not exist",
   192  			keyring: ring,
   193  			path:    filepath.Join(dir, "foo"),
   194  			want: &os.PathError{
   195  				Op:   "open",
   196  				Path: filepath.Join(dir, "foo"),
   197  				Err:  syscall.ENOENT,
   198  			},
   199  			isSignatureValid: false,
   200  		},
   201  	} {
   202  		t.Run(tt.desc, func(t *testing.T) {
   203  			f, gotErr := OpenSignedSigFile(tt.keyring, tt.path)
   204  			if !reflect.DeepEqual(gotErr, tt.want) {
   205  				t.Errorf("openSignedFile(%v, %q) = %v, want %v", tt.keyring, tt.path, gotErr, tt.want)
   206  			}
   207  
   208  			if isSignatureValid := (gotErr == nil); isSignatureValid != tt.isSignatureValid {
   209  				t.Errorf("isSignatureValid(%v) = %v, want %v", gotErr, isSignatureValid, tt.isSignatureValid)
   210  			}
   211  
   212  			// Make sure that the file is readable from position 0.
   213  			if f != nil {
   214  				content, err := ioutil.ReadAll(f)
   215  				if err != nil {
   216  					t.Errorf("Could not read content: %v", err)
   217  				}
   218  				if got := string(content); got != "foo" {
   219  					t.Errorf("ReadAll = %v, want \"foo\"", got)
   220  				}
   221  			}
   222  		})
   223  	}
   224  }
   225  
   226  func TestOpenHashedFile(t *testing.T) {
   227  	dir, err := ioutil.TempDir("", "openhashedfile")
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	defer os.RemoveAll(dir)
   232  
   233  	hashedPath := filepath.Join(dir, "hashed")
   234  	hash, err := writeHashedFile(hashedPath, "foo")
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	emptyPath := filepath.Join(dir, "empty")
   240  	emptyHash, err := writeHashedFile(emptyPath, "")
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	for _, tt := range []struct {
   246  		desc        string
   247  		path        string
   248  		hash        []byte
   249  		want        error
   250  		isHashValid bool
   251  		wantContent string
   252  	}{
   253  		{
   254  			desc:        "correct hash",
   255  			path:        hashedPath,
   256  			hash:        hash,
   257  			want:        nil,
   258  			isHashValid: true,
   259  			wantContent: "foo",
   260  		},
   261  		{
   262  			desc: "wrong hash",
   263  			path: hashedPath,
   264  			hash: []byte{0x99, 0x77},
   265  			want: ErrInvalidHash{
   266  				Path: hashedPath,
   267  				Err: ErrHashMismatch{
   268  					Got:  hash,
   269  					Want: []byte{0x99, 0x77},
   270  				},
   271  			},
   272  			isHashValid: false,
   273  			wantContent: "foo",
   274  		},
   275  		{
   276  			desc: "no hash",
   277  			path: hashedPath,
   278  			hash: []byte{},
   279  			want: ErrInvalidHash{
   280  				Path: hashedPath,
   281  				Err:  ErrNoExpectedHash,
   282  			},
   283  			isHashValid: false,
   284  			wantContent: "foo",
   285  		},
   286  		{
   287  			desc:        "empty file",
   288  			path:        emptyPath,
   289  			hash:        emptyHash,
   290  			want:        nil,
   291  			isHashValid: true,
   292  			wantContent: "",
   293  		},
   294  		{
   295  			desc: "nonexistent file",
   296  			path: filepath.Join(dir, "doesnotexist"),
   297  			hash: nil,
   298  			want: &os.PathError{
   299  				Op:   "open",
   300  				Path: filepath.Join(dir, "doesnotexist"),
   301  				Err:  syscall.ENOENT,
   302  			},
   303  			isHashValid: false,
   304  		},
   305  	} {
   306  		t.Run(tt.desc, func(t *testing.T) {
   307  			f, err := OpenHashedFile256(tt.path, tt.hash)
   308  			if !reflect.DeepEqual(err, tt.want) {
   309  				t.Errorf("OpenHashedFile256(%s, %x) = %v, want %v", tt.path, tt.hash, err, tt.want)
   310  			}
   311  
   312  			if isHashValid := (err == nil); isHashValid != tt.isHashValid {
   313  				t.Errorf("isHashValid(%v) = %v, want %v", err, isHashValid, tt.isHashValid)
   314  			}
   315  
   316  			// Make sure that the file is readable from position 0.
   317  			if f != nil {
   318  				content, err := ioutil.ReadAll(f)
   319  				if err != nil {
   320  					t.Errorf("Could not read content: %v", err)
   321  				}
   322  				if got := string(content); got != tt.wantContent {
   323  					t.Errorf("ReadAll = %v, want %s", got, tt.wantContent)
   324  				}
   325  			}
   326  		})
   327  	}
   328  }