diff options
Diffstat (limited to 'crypto')
| -rw-r--r-- | crypto/crypto.go | 46 | ||||
| -rw-r--r-- | crypto/key.go | 95 | ||||
| -rw-r--r-- | crypto/rand.go | 9 |
3 files changed, 74 insertions, 76 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go index c6d6619..967243d 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -46,9 +46,9 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/hex" - "errors" "unsafe" + "github.com/pkg/errors" "golang.org/x/crypto/hkdf" "fscrypt/metadata" @@ -57,34 +57,29 @@ import ( // Crypto error values var ( - ErrBadAuth = errors.New("key authentication check failed") - ErrNegitiveLength = errors.New("negative length requested for key") - ErrKeyAlloc = util.SystemError("could not allocate memory for key") - ErrKeyFree = util.SystemError("could not free memory of key") - ErrKeyringLocate = util.SystemError("could not locate the session keyring") - ErrKeyringInsert = util.SystemError("could not insert key into the session keyring") - ErrKeyringSearch = util.SystemError("could not find key in the session keyring") - ErrKeyringDelete = util.SystemError("could not delete key from the session keyring") - ErrRecoveryCode = errors.New("provided recovery code had incorrect format") - ErrLowEntropy = util.SystemError("insufficient entropy in pool to generate random bytes") - ErrRandNotSupported = util.SystemError("getrandom() not implemented; kernel must be v3.17 or later") - ErrRandFailed = util.SystemError("cannot get random bytes") + ErrBadAuth = errors.New("key authentication check failed") + ErrNegitiveLength = errors.New("keys cannot have negative lengths") + ErrRecoveryCode = errors.New("invalid recovery code") + ErrGetrandomFail = util.SystemError("getrandom() failed") + ErrKeyAlloc = util.SystemError("could not allocate memory for key") + ErrKeyFree = util.SystemError("could not free memory of key") + ErrKeyringLocate = util.SystemError("could not locate the session keyring") + ErrKeyringInsert = util.SystemError("could not insert key into the session keyring") + ErrKeyringSearch = errors.New("could not find key with descriptor") + ErrKeyringDelete = util.SystemError("could not delete key from the session keyring") ) // panicInputLength panics if "name" has invalid length (expected != actual) func panicInputLength(name string, expected, actual int) { - if expected != actual { - util.NeverError(util.InvalidLengthError(name, expected, actual)) + if err := util.CheckValidLength(expected, actual); err != nil { + panic(errors.Wrap(err, name)) } } // checkWrappingKey returns an error if the wrapping key has the wrong length func checkWrappingKey(wrappingKey *Key) error { - l := wrappingKey.Len() - if l != metadata.InternalKeyLen { - return util.InvalidLengthError("wrapping key", metadata.InternalKeyLen, l) - } - return nil + err := util.CheckValidLength(metadata.InternalKeyLen, wrappingKey.Len()) + return errors.Wrap(err, "wrapping key") } // stretchKey stretches a key of length KeyLen using unsalted HKDF to make two @@ -140,14 +135,14 @@ func getHMAC(key *Key, data ...[]byte) []byte { // and an HMAC to verify the wrapping key was correct. All of this is included // in the returned WrappedKeyData structure. func Wrap(wrappingKey, secretKey *Key) (*metadata.WrappedKeyData, error) { - err := checkWrappingKey(wrappingKey) - if err != nil { + if err := checkWrappingKey(wrappingKey); err != nil { return nil, err } data := &metadata.WrappedKeyData{EncryptedKey: make([]byte, secretKey.Len())} // Get random IV + var err error if data.IV, err = NewRandomBuffer(metadata.IVLen); err != nil { return nil, err } @@ -251,8 +246,11 @@ use it in "id" mode to provide extra protection against side-channel attacks. For more info see: https://github.com/P-H-C/phc-winner-argon2 */ func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) { - if len(salt) != metadata.SaltLen { - return nil, util.InvalidLengthError("salt", metadata.SaltLen, len(salt)) + if err := util.CheckValidLength(metadata.SaltLen, len(salt)); err != nil { + return nil, errors.Wrap(err, "passphrase hashing salt") + } + if err := costs.CheckValidity(); err != nil { + return nil, errors.Wrap(err, "passphrase hashing costs") } // This key will hold the hashing output diff --git a/crypto/key.go b/crypto/key.go index 852b213..2394eef 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -30,6 +30,8 @@ import ( "runtime" "unsafe" + "github.com/pkg/errors" + "golang.org/x/sys/unix" "fscrypt/metadata" @@ -98,8 +100,7 @@ func newBlankKey(length int) (*Key, error) { if length == 0 { return &Key{data: nil}, nil } else if length < 0 { - log.Printf("key length of %d is invalid", length) - return nil, ErrNegitiveLength + return nil, errors.Wrapf(ErrNegitiveLength, "length of %d requested", length) } flags := keyMmapFlags @@ -123,9 +124,11 @@ func newBlankKey(length int) (*Key, error) { // Wipe destroys a Key by zeroing and freeing the memory. The data is zeroed // even if Wipe returns an error, which occurs if we are unable to unlock or -// free the key memory. Calling Wipe() multiple times on a key has no effect. +// free the key memory. Wipe does nothing if the key is already wiped or is nil. func (key *Key) Wipe() error { - if key.data != nil { + // We do nothing if key or key.data is nil so that Wipe() is idempotent + // and so Wipe() can be called on keys which have already been cleared. + if key != nil && key.data != nil { data := key.data key.data = nil @@ -224,56 +227,50 @@ func NewFixedLengthKeyFromReader(reader io.Reader, length int) (*Key, error) { return key, nil } -// addPayloadToSessionKeyring adds the payload to the current session keyring as -// type logon, returning an error on failure. -func addPayloadToSessionKeyring(payload []byte, description string) error { - // We cannot add directly to KEY_SPEC_SESSION_KEYRING, as that will make - // a new session keyring if one does not exist, which will be garbage - // collected when the process terminates. Instead, we first get the ID - // of the KEY_SPEC_SESSION_KEYRING, which will return the user session - // keyring if a session keyring does not exist. +// getKeyring returns the id of the session keyring, or the id of the user +// session keyring if session keyring does not exist. We cannot directly use +// KEY_SPEC_SESSION_KEYRING, as that will make a new session keyring if one does +// not exist, which will be garbage collected when the process terminates. +func getKeyring() (int, error) { keyringID, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, false) log.Printf("unix.KeyctlGetKeyringID(KEY_SPEC_SESSION_KEYRING) = %d, %v", keyringID, err) if err != nil { - return ErrKeyringLocate + return 0, errors.Wrap(ErrKeyringLocate, err.Error()) } + return keyringID, nil +} - keyID, err := unix.AddKey(keyType, description, payload, keyringID) - log.Printf("unix.AddKey(%s, %s, <payload>, %d) = %d, %v", - keyType, description, keyringID, keyID, err) +// FindPolicyKey tries to locate a policy key in the kernel keyring with the +// provided descriptor and service. The keyring and key ids are returned if we +// can find the key. An error is returned if the key does not exist. +func FindPolicyKey(descriptor, service string) (keyringID, keyID int, err error) { + keyringID, err = getKeyring() if err != nil { - return ErrKeyringInsert + return } - return nil -} -// FindPolicyKey tries to locate a policy key in the kernel keyring with the -// provided descriptor and service. The key id is returned if we can find the -// key. An error is returned if the key does not exist. -func FindPolicyKey(descriptor, service string) (int, error) { description := service + descriptor - keyID, err := unix.KeyctlSearch(unix.KEY_SPEC_SESSION_KEYRING, keyType, description, 0) - log.Printf("unix.KeyctlSearch(KEY_SPEC_SESSION_KEYRING, %s, %s, 0) = %d, %v", - keyType, description, keyID, err) + keyID, err = unix.KeyctlSearch(keyringID, keyType, description, 0) + log.Printf("unix.KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, keyType, description, keyID, err) if err != nil { - return 0, ErrKeyringSearch + err = errors.Wrap(ErrKeyringSearch, err.Error()) } - return keyID, nil + return } // RemovePolicyKey tries to remove a policy key from the kernel keyring with the // provided descriptor and service. An error is returned if the key does not // exist. func RemovePolicyKey(descriptor, service string) error { - keyID, err := FindPolicyKey(descriptor, service) + keyringID, keyID, err := FindPolicyKey(descriptor, service) if err != nil { return err } - _, err = unix.KeyctlInt(unix.KEYCTL_UNLINK, keyID, unix.KEY_SPEC_SESSION_KEYRING, 0, 0) - log.Printf("unix.KeyctlUnlink(%d, KEY_SPEC_SESSION_KEYRING) = %v", keyID, err) + _, err = unix.KeyctlInt(unix.KEYCTL_UNLINK, keyID, keyringID, 0, 0) + log.Printf("unix.KeyctlUnlink(%d, %d) = %v", keyID, keyringID, err) if err != nil { - return ErrKeyringDelete + return errors.Wrap(ErrKeyringDelete, err.Error()) } return nil } @@ -282,12 +279,11 @@ func RemovePolicyKey(descriptor, service string) error { // provided descriptor, provided service prefix, and type logon. The key and // descriptor must have the appropriate lengths. func InsertPolicyKey(key *Key, descriptor, service string) error { - if key.Len() != metadata.PolicyKeyLen { - return util.InvalidLengthError("Policy Key", metadata.PolicyKeyLen, key.Len()) + if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { + return errors.Wrap(err, "policy key") } - - if len(descriptor) != metadata.DescriptorLen { - return util.InvalidLengthError("Descriptor", metadata.DescriptorLen, len(descriptor)) + if err := util.CheckValidLength(metadata.DescriptorLen, len(descriptor)); err != nil { + return errors.Wrap(err, "descriptor") } // Create our payload (containing an FscryptKey) @@ -304,10 +300,18 @@ func InsertPolicyKey(key *Key, descriptor, service string) error { fscryptKey.Size = metadata.PolicyKeyLen copy(fscryptKey.Raw[:], key.data) - if err := addPayloadToSessionKeyring(payload.data, service+descriptor); err != nil { + keyringID, err := getKeyring() + if err != nil { return err } + description := service + descriptor + keyID, err := unix.AddKey(keyType, description, payload.data, keyringID) + log.Printf("unix.AddKey(%s, %s, <payload>, %d) = %d, %v", + keyType, description, keyringID, keyID, err) + if err != nil { + return errors.Wrap(ErrKeyringInsert, err.Error()) + } return nil } @@ -326,8 +330,8 @@ var ( // WARNING: This recovery key is enough to derive the original key, so it must // be given the same level of protection as a raw cryptographic key. func WriteRecoveryCode(key *Key, writer io.Writer) error { - if key.Len() != metadata.PolicyKeyLen { - return util.InvalidLengthError("key", metadata.PolicyKeyLen, key.Len()) + if err := util.CheckValidLength(metadata.PolicyKeyLen, key.Len()); err != nil { + return errors.Wrap(err, "recovery key") } // We store the base32 encoded data (without separators) in a temp key @@ -374,8 +378,8 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { for blockStart := blockSize; blockStart < encodedLength; blockStart += blockSize { r.Read(inputSeparator) if r.Err() == nil && !bytes.Equal(separator, inputSeparator) { - log.Printf("separator of %q is invalid", inputSeparator) - return nil, ErrRecoveryCode + err := errors.Wrapf(ErrRecoveryCode, "invalid seperator %q", inputSeparator) + return nil, err } blockEnd := util.MinInt(blockStart+blockSize, encodedLength) @@ -384,8 +388,7 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { // If any reads have failed, return the error if r.Err() != nil { - log.Printf("error while reading recovery code: %v", r.Err()) - return nil, ErrRecoveryCode + return nil, errors.Wrapf(ErrRecoveryCode, "read error %v", r.Err()) } // Now we decode the key, resizing if necessary @@ -394,9 +397,7 @@ func ReadRecoveryCode(reader io.Reader) (*Key, error) { return nil, err } if _, err = encoding.Decode(decodedKey.data, encodedKey.data); err != nil { - decodedKey.Wipe() - log.Printf("error decoding recovery code: %v", err) - return nil, ErrRecoveryCode + return nil, errors.Wrap(ErrRecoveryCode, err.Error()) } return decodedKey.resize(metadata.PolicyKeyLen) } diff --git a/crypto/rand.go b/crypto/rand.go index d2948d0..0778ebd 100644 --- a/crypto/rand.go +++ b/crypto/rand.go @@ -21,8 +21,8 @@ package crypto import ( "io" - "log" + "github.com/pkg/errors" "golang.org/x/sys/unix" ) @@ -58,11 +58,10 @@ func (r randReader) Read(buffer []byte) (int, error) { case nil: return n, nil case unix.EAGAIN: - return 0, ErrLowEntropy + return 0, errors.Wrap(ErrGetrandomFail, "insufficient entropy in pool") case unix.ENOSYS: - return 0, ErrRandNotSupported + return 0, errors.Wrap(ErrGetrandomFail, "kernel must be v3.17 or later") default: - log.Printf("unix.Getrandom failed: %v", err) - return 0, ErrRandFailed + return 0, errors.Wrap(ErrGetrandomFail, err.Error()) } } |