diff --git a/safestorage/decrypt.go b/safestorage/decrypt.go index e621957..ebc0a65 100644 --- a/safestorage/decrypt.go +++ b/safestorage/decrypt.go @@ -29,42 +29,42 @@ const ( keySize = 16 // AES-128 salt = "saltysalt" - keychainPrefix = "v10" - keychainIterations = 1003 + macosPrefix = "v10" + macosIterations = 1003 - libsecretPrefixV10 = "v10" - libsecretPrefixV11 = "v11" - libsecretIterations = 1 + linuxPrefixV10 = "v10" + linuxPrefixV11 = "v11" + linuxIterations = 1 ) func DecryptWithPassword(ciphertext, password []byte) ([]byte, error) { switch runtime.GOOS { case "darwin": - return decryptWithKeychainPassword(ciphertext, password) + return decryptWithMacosPassword(ciphertext, password) case "linux", "openbsd": - return decryptWithLibsecretPassword(ciphertext, password) + return decryptWithLinuxPassword(ciphertext, password) default: return nil, errors.New("not yet supported") } } -func decryptWithKeychainPassword(ciphertext, password []byte) ([]byte, error) { - if !bytes.HasPrefix(ciphertext, []byte(keychainPrefix)) { +func decryptWithMacosPassword(ciphertext, password []byte) ([]byte, error) { + if !bytes.HasPrefix(ciphertext, []byte(macosPrefix)) { return ciphertext, nil } - ciphertext = bytes.TrimPrefix(ciphertext, []byte(keychainPrefix)) - return decryptWithPassword(ciphertext, password, keychainIterations) + ciphertext = bytes.TrimPrefix(ciphertext, []byte(macosPrefix)) + return decryptWithPassword(ciphertext, password, macosIterations) } -func decryptWithLibsecretPassword(ciphertext, password []byte) ([]byte, error) { - if bytes.HasPrefix(ciphertext, []byte(libsecretPrefixV10)) { +func decryptWithLinuxPassword(ciphertext, password []byte) ([]byte, error) { + if bytes.HasPrefix(ciphertext, []byte(linuxPrefixV10)) { return nil, errors.New("unsupported encryption version prefix") } - if !bytes.HasPrefix(ciphertext, []byte(libsecretPrefixV11)) { + if !bytes.HasPrefix(ciphertext, []byte(linuxPrefixV11)) { return ciphertext, nil } - ciphertext = bytes.TrimPrefix(ciphertext, []byte(libsecretPrefixV11)) - return decryptWithPassword(ciphertext, password, libsecretIterations) + ciphertext = bytes.TrimPrefix(ciphertext, []byte(linuxPrefixV11)) + return decryptWithPassword(ciphertext, password, linuxIterations) } func decryptWithPassword(ciphertext, password []byte, iters int) ([]byte, error) { diff --git a/signal/open.go b/signal/open.go index 56067b1..a305eb5 100644 --- a/signal/open.go +++ b/signal/open.go @@ -35,22 +35,10 @@ type Context struct { } func Open(dir string) (*Context, error) { - key, err := databaseKey(dir) - if err != nil { - return nil, err - } - return open(dir, key) + return OpenWithPassword(dir, nil) } func OpenWithPassword(dir string, password []byte) (*Context, error) { - key, err := encryptedDatabaseKey(dir, password) - if err != nil { - return nil, err - } - return open(dir, key) -} - -func open(dir string, key []byte) (*Context, error) { dbFile := filepath.Join(dir, DatabaseFile) // SQLite/SQLCipher doesn't provide a useful error message if the @@ -66,6 +54,11 @@ func open(dir string, key []byte) (*Context, error) { return nil, err } + key, err := databaseKey(dir, password) + if err != nil { + return nil, err + } + // Format the key as an SQLite blob literal key = []byte(fmt.Sprintf("x'%s'", string(key))) @@ -104,57 +97,43 @@ func (c *Context) Close() { c.db.Close() } -type config struct { - Key *string `json:"key"` - EncryptedKey *string `json:"encryptedKey"` -} - -func databaseKey(dir string) ([]byte, error) { - config, err := parseConfigFile(dir) +func databaseKey(dir string, password []byte) ([]byte, error) { + configFile := filepath.Join(dir, ConfigFile) + data, err := os.ReadFile(configFile) if err != nil { return nil, err } - if config.Key == nil { - return nil, fmt.Errorf("legacy database key not found") + var config struct { + LegacyKey *string `json:"key"` + ModernKey *string `json:"encryptedKey"` + } + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("cannot parse %s: %w", configFile, err) } - return []byte(*config.Key), nil -} - -func encryptedDatabaseKey(dir string, password []byte) ([]byte, error) { - config, err := parseConfigFile(dir) - if err != nil { - return nil, err + if password == nil && config.LegacyKey != nil { + return []byte(*config.LegacyKey), nil } - if config.EncryptedKey == nil { + if config.ModernKey == nil { return nil, fmt.Errorf("encrypted database key not found") } - encKey, err := hex.DecodeString(*config.EncryptedKey) + key, err := hex.DecodeString(*config.ModernKey) if err != nil { return nil, fmt.Errorf("invalid encrypted database key: %w", err) } - key, err := safestorage.DecryptWithPassword(encKey, password) - if err != nil { - return nil, fmt.Errorf("cannot decrypt database key: %w", err) + var dbKey []byte + if password != nil { + dbKey, err = safestorage.DecryptWithPassword(key, password) + } else { + err = fmt.Errorf("not yet supported") } - - return key, nil -} - -func parseConfigFile(dir string) (config, error) { - configFile := filepath.Join(dir, ConfigFile) - data, err := os.ReadFile(configFile) if err != nil { - return config{}, err + return nil, fmt.Errorf("cannot decrypt database key: %w", err) } - var config config - if err := json.Unmarshal(data, &config); err != nil { - return config, fmt.Errorf("cannot parse %s: %w", configFile, err) - } - return config, nil + return dbKey, nil }