From 3ee2ad56a8d33f8481fda6d028fe472a7cb3caca Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Tue, 24 Jan 2023 20:43:13 +1000 Subject: [PATCH 1/2] allow for no encryption --- cmd/msgtime/main.go | 9 ++------ go.mod | 6 +++-- go.sum | 11 +++++++++ historydecryptor/getkey.go | 3 ++- historydecryptor/historydecryptor.go | 28 +++++++++++++++++------ historydecryptor/historydecryptor_test.go | 8 +++---- historydecryptor/key.go | 6 +++-- main.go | 16 +++++++++---- 8 files changed, 59 insertions(+), 28 deletions(-) diff --git a/cmd/msgtime/main.go b/cmd/msgtime/main.go index 4f65698..94a23b1 100644 --- a/cmd/msgtime/main.go +++ b/cmd/msgtime/main.go @@ -4,7 +4,6 @@ package main import ( "fmt" "os" - "strconv" "time" "github.com/rusq/osx-callhistory-decryptor/historydecryptor" @@ -31,10 +30,6 @@ func main() { fmt.Printf("%s\n", date) } -func convert(floatOffset string) (time.Time, error) { - t, err := strconv.ParseFloat(floatOffset, 64) - if err != nil { - return time.Time{}, err - } - return historydecryptor.CalcCallTime(t), nil +func convert(offset string) (time.Time, error) { + return historydecryptor.CalcCallTime(offset), nil } diff --git a/go.mod b/go.mod index 204c803..ace3301 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/rusq/osx-callhistory-decryptor go 1.13 require ( - github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6 - github.com/mattn/go-sqlite3 v1.11.0 + github.com/keybase/go-keychain v0.0.0-20221221221913-9be78f6c498b + github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03 // indirect + github.com/mattn/go-sqlite3 v1.14.16 + gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index 891cf6d..2e154c3 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,22 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8= github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6 h1:Mj0fhP9dzHKPijsmli/XbXMDKe1/KWy5xKci8e3nmBg= github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6/go.mod h1:N83iQ9rnnzi2KZuTu+0xBcD1JNWn1jSN140ggAF7HeE= +github.com/keybase/go-keychain v0.0.0-20221221221913-9be78f6c498b h1:k2ZvAPXrDB1Q7fGRdUane+T08K+UaL96qH47Setr/7k= +github.com/keybase/go-keychain v0.0.0-20221221221913-9be78f6c498b/go.mod h1:TXh6wFVZNh4iuqbymzy4r3obmj8hTPDlLNnoKNIbvJE= github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -16,3 +25,5 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/historydecryptor/getkey.go b/historydecryptor/getkey.go index e7e2012..0049782 100644 --- a/historydecryptor/getkey.go +++ b/historydecryptor/getkey.go @@ -1,3 +1,4 @@ +//go:build !darwin // +build !darwin package historydecryptor @@ -8,7 +9,7 @@ import ( func GetByteKey(keyStr string) ([]byte, error) { if len(keyStr) == 0 { - return nil, fmt.Errorf("Use -k parameter to supply the key.") + return nil, ErrNoKey } key, err := DecodeB64Key([]byte(keyStr)) if err != nil { diff --git a/historydecryptor/historydecryptor.go b/historydecryptor/historydecryptor.go index b60ab06..f7b314c 100644 --- a/historydecryptor/historydecryptor.go +++ b/historydecryptor/historydecryptor.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "database/sql" "encoding/csv" + "errors" "io" "log" "strconv" @@ -99,16 +100,25 @@ func DecipherHistory(database string, key []byte, output io.Writer, opts ...Opti blob = make([]byte, 255) ) - err = rows.Scan(&unparsedCallOffset, &answered, &originated, &calltype, &country, &blob) - if err != nil { + if err = rows.Scan(&unparsedCallOffset, &answered, &originated, &calltype, &country, &blob); err != nil { return 0, err } callTime := CalcCallTime(unparsedCallOffset) - address, err := Decipher(blob, key) - if err != nil { - return 0, err + var address []byte + if isEncrypted(blob) { + if len(key) == 0 { + return 0, errors.New("your database is encrypted, but you didn't provide a key") + } + address, err = Decipher(blob, key) + if err != nil { + return 0, err + } + } else { + // this indicates that the row might not be encrypted at all. + address = blob } + csvOut.Write([]string{callTime.Format(s.timeFmt), answered, originated, calltype, country.String, string(address)}) @@ -121,7 +131,11 @@ func DecipherHistory(database string, key []byte, output io.Writer, opts ...Opti return numRecords, nil } -//Decipher deciphers ZADDRESS from OS X call history. +func isEncrypted(blob []byte) bool { + return len(blob) >= TagSz+NonceSz +} + +// Decipher deciphers ZADDRESS from OS X call history. func Decipher(data, key []byte) ([]byte, error) { if len(data) == 0 { return nil, nil @@ -152,7 +166,7 @@ func Decipher(data, key []byte) ([]byte, error) { return pt, nil } -//Cipher text conforming to ZADDRESS encryption pattern +// Cipher text conforming to ZADDRESS encryption pattern func Cipher(text, key []byte) ([]byte, error) { if len(text) == 0 { return nil, nil diff --git a/historydecryptor/historydecryptor_test.go b/historydecryptor/historydecryptor_test.go index 28bf341..9e91757 100644 --- a/historydecryptor/historydecryptor_test.go +++ b/historydecryptor/historydecryptor_test.go @@ -126,16 +126,16 @@ func TestDecipherHistory(t *testing.T) { func Test_calcCallTime(t *testing.T) { type args struct { - callOffset float64 + callOffset string } tests := []struct { name string args args want time.Time }{ - {"zero offset", args{0.0}, time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC)}, - {"negative offset", args{-10.0}, time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC)}, - {"positive offset", args{1000.0}, time.Date(2001, time.January, 1, 0, 16, 40, 0, time.UTC)}, + {"zero offset", args{"0.0"}, time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC)}, + {"negative offset", args{"-10.0"}, time.Date(2000, time.December, 31, 23, 59, 50, 0, time.UTC)}, + {"positive offset", args{"1000.0"}, time.Date(2001, time.January, 1, 0, 16, 40, 0, time.UTC)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/historydecryptor/key.go b/historydecryptor/key.go index 699f857..64cede6 100644 --- a/historydecryptor/key.go +++ b/historydecryptor/key.go @@ -2,13 +2,15 @@ package historydecryptor import ( "encoding/base64" - "fmt" + "errors" ) +var ErrNoKey = errors.New("key is not supplied") + // DecodeB64Key decodes the provided key from base64 encoding func DecodeB64Key(key []byte) ([]byte, error) { if len(key) == 0 { - return nil, fmt.Errorf("key is not supplied") + return nil, ErrNoKey } ret := make([]byte, base64.StdEncoding.DecodedLen(len(key))) l, err := base64.StdEncoding.Decode(ret, key) diff --git a/main.go b/main.go index 5bd654a..bc08f04 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ var defFile = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", var ( strKey = flag.String("k", os.Getenv("KEY"), "Base64 key value from OS X keychain, on macOS may be omitted.") + omitDecryption = flag.Bool("no-key", false, "omit decryption") outputFilename = flag.String("o", "", "output csv filename. If not specified, result is output to stdout") versionOnly = flag.Bool("v", false, "print version and quit") timeFormat = flag.String("time-format", historydecryptor.DefTimeFmt, "CSV output time `format`") @@ -74,12 +75,12 @@ func main() { src = defFile } - if err := run(src, *outputFilename, *strKey); err != nil { + if err := run(src, *outputFilename, *strKey, *omitDecryption); err != nil { log.Fatal(err) } } -func run(src, dst string, strKey string) error { +func run(src, dst string, strKey string, omitDecryption bool) error { log.Printf("*** database filename: %q", src) dbfile, err := copytemp(src) if err != nil { @@ -88,9 +89,14 @@ func run(src, dst string, strKey string) error { defer os.Remove(dbfile) log.Printf("*** temporary file (will be removed): %q", dbfile) - key, err := historydecryptor.GetByteKey(strKey) - if err != nil { - return fmt.Errorf("%w: make sure you have supplied the key via -k or KEY env variable", err) + var key []byte + if omitDecryption { + key = []byte{} + } else { + key, err = historydecryptor.GetByteKey(strKey) + if err != nil { + return fmt.Errorf("%w: make sure you have supplied the key via -k or KEY env variable", err) + } } var output = os.Stdout From 60e86eb00b103a66e79673f782782fb340515d3a Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Tue, 24 Jan 2023 20:51:27 +1000 Subject: [PATCH 2/2] update README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index ae6c235..624da76 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ [![Build Status](https://travis-ci.org/rusq/osx-callhistory-decryptor.svg?branch=master)](https://travis-ci.org/rusq/osx-callhistory-decryptor) +> ℹ PLEASE NOTE +> +> since macOS 13 (Ventura) the Call History database appears to be not +> encrypted. You can run the program with `-no-key` flag to view the call +> history. The `-k` flag is no longer required, but still available for +> earlier versions of macOS. + Converts the MacOS X call history to CSV file format. This is the Golang implementation of the [n0fates'][1] [Call History