github.com/creachadair/ffs@v0.17.3/storage/codecs/encrypted/encrypted_test.go (about)

     1  // Copyright 2019 Michael J. Fromberger. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package encrypted_test
    16  
    17  import (
    18  	"bytes"
    19  	"crypto/aes"
    20  	"crypto/cipher"
    21  	"encoding/binary"
    22  	"testing"
    23  
    24  	"github.com/creachadair/ffs/storage/codecs/encrypted"
    25  	"github.com/creachadair/ffs/storage/encoded"
    26  )
    27  
    28  var _ encoded.Codec = (*encrypted.Codec)(nil)
    29  
    30  // A fake Keyring implementation with static keys.
    31  type keyring []string
    32  
    33  func (k *keyring) Has(id int) bool { return id > 0 && id-1 < len(*k) }
    34  
    35  func (k *keyring) Get(id int, buf []byte) []byte { return append(buf, (*k)[id-1]...) }
    36  
    37  func (k *keyring) GetActive(buf []byte) (int, []byte) {
    38  	return len(*k), append(buf, (*k)[len(*k)-1]...)
    39  }
    40  
    41  func (k *keyring) addKey(s string) { *k = append(*k, s) }
    42  
    43  func TestCodec(t *testing.T) {
    44  	newCipher := func(key []byte) (cipher.AEAD, error) {
    45  		blk, err := aes.NewCipher(key)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  		return cipher.NewGCM(blk)
    50  	}
    51  	kr := &keyring{"00000000000000000000000000000000"}
    52  	e := encrypted.New(newCipher, kr)
    53  
    54  	checkEncrypt := func(ptext string) []byte {
    55  		t.Helper()
    56  		var buf bytes.Buffer
    57  		if err := e.Encode(&buf, []byte(ptext)); err != nil {
    58  			t.Fatalf("Encode failed: %v", err)
    59  		}
    60  		return buf.Bytes()
    61  	}
    62  	checkDecrypt := func(ctext []byte, want string) {
    63  		t.Helper()
    64  		var buf bytes.Buffer
    65  		if err := e.Decode(&buf, ctext); err != nil {
    66  			t.Fatalf("Decode failed: %v", err)
    67  		}
    68  		if buf.String() != want {
    69  			t.Errorf("Decode:\ngot:  %s\nwant: %s", buf.String(), want)
    70  		}
    71  	}
    72  
    73  	const value = "some of what a fool thinks often remains"
    74  	t.Logf("Input (%d bytes): %q", len(value), value)
    75  
    76  	// Encode the test value through the encrypted codec.
    77  	// Log the stored block for debugging purposes.
    78  	enc := checkEncrypt(value)
    79  	t.Logf("Stored block 1 (%d bytes):\n%#q", len(enc), enc)
    80  	if id := int(binary.BigEndian.Uint32(enc[1:5])); id != 1 {
    81  		t.Errorf("Key ID is %d, want 1", id)
    82  	}
    83  
    84  	checkDecrypt(enc, value)
    85  
    86  	// Add a new key and verify that we can still decrypt the old block.
    87  	kr.addKey("11111111111111111111111111111111")
    88  	checkDecrypt(enc, value)
    89  
    90  	// Encrypt a new block, which should use the new key.
    91  	enc2 := checkEncrypt(value)
    92  	t.Logf("Stored block 2 (%d bytes):\n%#q", len(enc2), enc2)
    93  	if id := int(binary.BigEndian.Uint32(enc2[1:5])); id != 2 {
    94  		t.Errorf("Key ID is %d, want 2", id)
    95  	}
    96  
    97  	checkDecrypt(enc, value)
    98  	checkDecrypt(enc2, value)
    99  
   100  	// Verify that we can also handle the legacy block format without a key ID.
   101  	// This format always uses the first key version in the ring.
   102  	legacy := make([]byte, 0, len(enc)-4)
   103  	legacy = append(legacy, enc[0]&0x7f)
   104  	legacy = append(legacy, enc[5:]...)
   105  	t.Logf("Legacy block (%d bytes):\n%#q", len(legacy), legacy)
   106  
   107  	checkDecrypt(legacy, value)
   108  }