diff options
| author | Joe Richey <joerichey@google.com> | 2017-03-02 14:01:20 -0800 |
|---|---|---|
| committer | Joe Richey joerichey@google.com <joerichey@google.com> | 2017-05-02 13:39:18 -0700 |
| commit | ee10adc91e79bca395a6b069797a99863fc957dd (patch) | |
| tree | fc07fbeb9432cb66c74561961f8b47f5d9bed4f5 /crypto/recovery_test.go | |
| parent | 8128b35375dfc4846dd1573dda55ef232ffd2d66 (diff) | |
crypto: reading and writing recovery keys
This commit adds in the concept of recovery codes: human-readable
strings that contain the necessary information to rederive a
cryptographic key. These keys look like:
73PZBXVP-DKJX7SKV-NNTFIC7A-QEGRPZUX-4K5ORRH2-MTKMKP3B-HFCA====
They are input or output directly to a io.Reader or io.Writer
respectively. This prevents the data from passing through unsecured
memory before it gets to its destination. Of course, if the provided
io.Reader or io.Writer is insecure, there is nothing we can do. In most
cases the provided io.Reader or io.Writer will be stdin or stdout. In
some rare cases you might want to pipe the output to another key.
This commit also adds tests and benchmarks for encoding/decoding
recovery codes. It also tests that encoding/decoding will fail in the
correct situations. A benchmark is also added to measure the effect of
locking the keys in memory.
Change-Id: Ifa0bc4c08582789785cf1cdd9a4acfe76c79534f
Diffstat (limited to 'crypto/recovery_test.go')
| -rw-r--r-- | crypto/recovery_test.go | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/crypto/recovery_test.go b/crypto/recovery_test.go new file mode 100644 index 0000000..2ee18f0 --- /dev/null +++ b/crypto/recovery_test.go @@ -0,0 +1,215 @@ +/* + * recovery_test.go - tests for recovery codes in the crypto package + * tests key wrapping/unwrapping and key generation + * + * Copyright 2017 Google Inc. + * Author: Joe Richey (joerichey@google.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package crypto + +import ( + "bytes" + "fmt" + "testing" +) + +const fakeSecretRecoveryCode = "EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTCMJRG-EYTA====" + +var fakeSecretKey, _ = makeKey(38, InternalKeyLen) + +// Note that this function is INSECURE. FOR TESTING ONLY +func getRecoveryCodeFromKey(key *Key) ([]byte, error) { + var buf bytes.Buffer + if err := WriteRecoveryCode(key, &buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func getRandomRecoveryCodeBuffer() ([]byte, error) { + key, err := NewRandomKey(InternalKeyLen) + if err != nil { + return nil, err + } + return getRecoveryCodeFromKey(key) +} + +func getKeyFromRecoveryCode(buf []byte) (*Key, error) { + return ReadRecoveryCode(bytes.NewReader(buf)) +} + +// Given a key, make a recovery code from that key, use that code to rederive +// another key and check if they are the same. +func testKeyEncodeDecode(key *Key) error { + buf, err := getRecoveryCodeFromKey(key) + if err != nil { + return err + } + + key2, err := getKeyFromRecoveryCode(buf) + if err != nil { + return err + } + + if !bytes.Equal(key.data, key2.data) { + return fmt.Errorf("encoding then decoding %x didn't yield the same key", key.data) + } + return nil +} + +// Given a recovery code, make a key from that recovery code, use that key to +// rederive another recovery code and check if they are the same. +func testRecoveryDecodeEncode(buf []byte) error { + key, err := getKeyFromRecoveryCode(buf) + if err != nil { + return err + } + + buf2, err := getRecoveryCodeFromKey(key) + if err != nil { + return err + } + + if !bytes.Equal(buf, buf2) { + return fmt.Errorf("decoding then encoding %x didn't yield the same key", buf) + } + return nil +} + +func TestGetRandomRecoveryString(t *testing.T) { + b, err := getRandomRecoveryCodeBuffer() + if err != nil { + t.Fatal(err) + } + + t.Log(string(b)) + // t.Fail() // Uncomment to see an example random recovery code +} + +func TestFakeSecretKey(t *testing.T) { + buf, err := getRecoveryCodeFromKey(fakeSecretKey) + if err != nil { + t.Fatal(err) + } + + recoveryCode := string(buf) + if recoveryCode != fakeSecretRecoveryCode { + t.Errorf("got '%s' instead of '%s'", recoveryCode, fakeSecretRecoveryCode) + } +} + +func TestEncodeDecode(t *testing.T) { + key, err := NewRandomKey(InternalKeyLen) + if err != nil { + t.Fatal(err) + } + + if err = testKeyEncodeDecode(key); err != nil { + t.Error(err) + } +} + +func TestDecodeEncode(t *testing.T) { + buf, err := getRandomRecoveryCodeBuffer() + if err != nil { + t.Fatal(err) + } + + if err = testRecoveryDecodeEncode(buf); err != nil { + t.Error(err) + } +} + +func TestWrongLengthError(t *testing.T) { + key, err := NewRandomKey(InternalKeyLen - 1) + if err != nil { + t.Fatal(err) + } + + if _, err = getRecoveryCodeFromKey(key); err == nil { + t.Error("key with wrong length should have failed to encode") + } +} + +func TestBadCharacterError(t *testing.T) { + buf, err := getRandomRecoveryCodeBuffer() + // Lowercase letters not allowed + buf[3] = 'k' + if _, err = getKeyFromRecoveryCode(buf); err == nil { + t.Error("lowercase letters should make decoding fail") + } +} + +func TestBadEndCharacterError(t *testing.T) { + buf, err := getRandomRecoveryCodeBuffer() + // Separator must be '-' + buf[blockSize] = '_' + if _, err = getKeyFromRecoveryCode(buf); err == nil { + t.Error("any separator that isn't '-' should make decoding fail") + } +} + +func BenchmarkEncode(b *testing.B) { + key, err := NewRandomKey(InternalKeyLen) + if err != nil { + b.Fatal(err) + } + + for n := 0; n < b.N; n++ { + if _, err = getRecoveryCodeFromKey(key); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecode(b *testing.B) { + buf, err := getRandomRecoveryCodeBuffer() + if err != nil { + b.Fatal(err) + } + + for n := 0; n < b.N; n++ { + if _, err = getKeyFromRecoveryCode(buf); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeDecode(b *testing.B) { + key, err := NewRandomKey(InternalKeyLen) + if err != nil { + b.Fatal(err) + } + + for n := 0; n < b.N; n++ { + if err = testKeyEncodeDecode(key); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecodeEncode(b *testing.B) { + buf, err := getRandomRecoveryCodeBuffer() + if err != nil { + b.Fatal(err) + } + + for n := 0; n < b.N; n++ { + if err = testRecoveryDecodeEncode(buf); err != nil { + b.Fatal(err) + } + } +} |