Skip to content

Commit

Permalink
Merge pull request #12 from rneatherway/windows-sup
Browse files Browse the repository at this point in the history
Add support for Windows
  • Loading branch information
rneatherway authored May 26, 2022
2 parents 9e565da + 7e1abcd commit fc0c463
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 74 deletions.
7 changes: 6 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ project_name: slack

builds:
- main: ./cmd/gh-slack
goos: [linux, darwin]
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
ignore:
- goos: linux
goarch: arm64
- goos: windows
goarch: arm64
ldflags:
- -X github.com/rneatherway/gh-slack/internal/version.version={{.Version}}
- -X github.com/rneatherway/gh-slack/internal/version.commit={{.Commit}}
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ module github.com/rneatherway/gh-slack
go 1.18

require (
github.com/billgraziano/dpapi v0.4.0
github.com/cli/go-gh v0.0.3
github.com/jessevdk/go-flags v1.5.0
github.com/zalando/go-keyring v0.2.1
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
modernc.org/sqlite v1.15.3
r00t2.io/gosecret v1.1.5
)
Expand All @@ -21,6 +22,7 @@ require (
github.com/henvic/httpretty v0.0.6 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
golang.org/x/mod v0.3.0 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/billgraziano/dpapi v0.4.0 h1:t39THI1Ld1hkkLVrhkOX6u5TUxwzRddOffq4jcwh2AE=
github.com/billgraziano/dpapi v0.4.0/go.mod h1:gi1Lin0jvovT53j0EXITkY6UPb3hTfI92POaZgj9JBA=
github.com/cli/go-gh v0.0.3 h1:GcVgUa7q0SeauIRbch3VSUXVij6+c49jtAHv7WuWj5c=
github.com/cli/go-gh v0.0.3/go.mod h1:J1eNgrPJYAUy7TwPKj7GW1ibqI+WCiMndtyzrCyZIiQ=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
Expand Down Expand Up @@ -36,6 +38,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
Expand All @@ -50,9 +54,8 @@ github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand All @@ -65,6 +68,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828161417-c663848e9a16/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
15 changes: 0 additions & 15 deletions internal/slackclient/cookie_password_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,10 @@ package slackclient

import (
"errors"
"os"
"path"

"r00t2.io/gosecret"
)

func slackConfigDirs() []string {
if xdgConfigDir, found := os.LookupEnv("XDG_CONFIG_DIR"); found {
return []string{xdgConfigDir}
}

home := os.Getenv("HOME")
return []string{path.Join(home, ".config")}
}

func cookiePassword() ([]byte, error) {
service, err := gosecret.NewService()
if err != nil {
Expand All @@ -46,7 +35,3 @@ func cookiePassword() ([]byte, error) {
return nil, errors.New("multiple items found")
}
}

func iterations() int {
return 1
}
15 changes: 0 additions & 15 deletions internal/slackclient/cookie_password_macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,9 @@
package slackclient

import (
"os"
"path"

"github.com/zalando/go-keyring"
)

func slackConfigDirs() []string {
home := os.Getenv("HOME")
return []string{
path.Join(home, "Library", "Application Support"),
path.Join(home, "Library", "Containers", "com.tinyspeck.slackmacgap", "Data", "Library", "Application Support"),
}
}

func cookiePassword() ([]byte, error) {
secret, err := keyring.Get("Slack Safe Storage", "Slack")
if err != nil {
Expand All @@ -26,7 +15,3 @@ func cookiePassword() ([]byte, error) {

return []byte(secret), nil
}

func iterations() int {
return 1003
}
51 changes: 51 additions & 0 deletions internal/slackclient/cookie_password_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//go:build windows
// +build windows

package slackclient

import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path"

"github.com/billgraziano/dpapi"
)

type EncryptedKey struct {
EncryptedKey string `json:"encrypted_key"`
}
type LocalState struct {
OSCrypt EncryptedKey `json:"os_crypt"`
}

func cookiePassword() ([]byte, error) {
bs, err := os.ReadFile(path.Join(slackConfigDir(), "Local State"))
if err != nil {
return nil, err
}

var localState LocalState
err = json.Unmarshal(bs, &localState)
if err != nil {
return nil, err
}

encryptedKey, err := base64.StdEncoding.DecodeString(localState.OSCrypt.EncryptedKey)
if err != nil {
return nil, err
}

encryptionMethod := encryptedKey[:5]
if string(encryptionMethod) != "DPAPI" {
return nil, fmt.Errorf("encryption method %q is not supported", encryptionMethod)
}

encryptedKey = encryptedKey[5:]
decryptedKey, err := dpapi.DecryptBytes(encryptedKey)
if err != nil {
return nil, err
}
return decryptedKey, nil
}
31 changes: 31 additions & 0 deletions internal/slackclient/slack_configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package slackclient

import (
"fmt"
"os"
"path"
"runtime"
)

func slackConfigDir() string {
switch runtime.GOOS {
case "windows":
return path.Join(os.Getenv("APPDATA"), "Slack")
case "darwin":
home := os.Getenv("HOME")
first := path.Join(home, "Library", "Application Support", "Slack")
second := path.Join(home, "Library", "Containers", "com.tinyspeck.slackmacgap", "Data", "Library", "Application Support", "Slack")

if _, err := os.Stat(first); err == nil {
return first
}
return second
case "linux":
if xdgConfigDir, found := os.LookupEnv("XDG_CONFIG_DIR"); found {
return path.Join(xdgConfigDir, "Slack")
}
return path.Join(os.Getenv("HOME"), ".config", "Slack")
default:
panic(fmt.Sprintf("Platform %q not supported", runtime.GOOS))
}
}
73 changes: 33 additions & 40 deletions internal/slackclient/token.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package slackclient

import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"database/sql"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"os"
"path"
"regexp"
"runtime"
"strings"

"golang.org/x/crypto/pbkdf2"

_ "modernc.org/sqlite"
)

Expand All @@ -29,25 +24,34 @@ type SlackAuth struct {
var stmt = "SELECT value, encrypted_value FROM cookies WHERE host_key=\".slack.com\" AND name=\"d\""

type CookieDecryptor interface {
Password() string
Decrypt(value, key []byte) ([]byte, error)
}

func decrypt(encryptedValue, key []byte) ([]byte, error) {
switch runtime.GOOS {
case "windows":
return WindowsDecryptor{}.Decrypt(encryptedValue, key)
case "darwin":
return UnixCookieDecryptor{rounds: 1003}.Decrypt(encryptedValue, key)
case "linux":
return UnixCookieDecryptor{rounds: 1}.Decrypt(encryptedValue, key)
default:
panic(fmt.Sprintf("platform %q not supported", runtime.GOOS))
}
}

func getCookie() (string, error) {
var cookieDBFile string

for _, config := range slackConfigDirs() {
cookieDBFile = path.Join(config, "Slack", "Cookies")
stat, err := os.Stat(cookieDBFile)
if errors.Is(err, fs.ErrNotExist) {
continue
}
if err != nil {
return "", fmt.Errorf("could not access Slack cookie database: %w", err)
}
if stat.IsDir() {
return "", fmt.Errorf("directory found at expected Slack cookie database location %q", cookieDBFile)
}
break
cookieDBFile := path.Join(slackConfigDir(), "Cookies")
if runtime.GOOS == "windows" {
cookieDBFile = path.Join(slackConfigDir(), "Network", "Cookies")
}

stat, err := os.Stat(cookieDBFile)
if err != nil {
return "", fmt.Errorf("could not access Slack cookie database: %w", err)
}
if stat.IsDir() {
return "", fmt.Errorf("directory found at expected Slack cookie database location %q", cookieDBFile)
}

if cookieDBFile == "" {
Expand All @@ -60,8 +64,8 @@ func getCookie() (string, error) {
}

var cookie string
var encrypted_value []byte
err = db.QueryRow(stmt).Scan(&cookie, &encrypted_value)
var encryptedValue []byte
err = db.QueryRow(stmt).Scan(&cookie, &encryptedValue)
if err != nil {
return "", err
}
Expand All @@ -70,32 +74,21 @@ func getCookie() (string, error) {
return cookie, nil
}

// We need to decrypt the cookie.
// Remove the version number e.g. v11
encryptedValue = encryptedValue[3:]

// We need to decrypt the cookie.
key, err := cookiePassword()
if err != nil {
return "", fmt.Errorf("failed to get cookie password: %w", err)
}
dk := pbkdf2.Key(key, []byte("saltysalt"), iterations(), 16, sha1.New)

block, err := aes.NewCipher(dk)
decryptedValue, err := decrypt(encryptedValue, key)
if err != nil {
return "", err
}

iv := make([]byte, 16)
for i := range iv {
iv[i] = ' '
}

mode := cipher.NewCBCDecrypter(block, iv)

encrypted_value = encrypted_value[3:]
mode.CryptBlocks(encrypted_value, encrypted_value)

bytesToStrip := int(encrypted_value[len(encrypted_value)-1])

return string(encrypted_value[:len(encrypted_value)-bytesToStrip]), nil
return string(decryptedValue), err
}

var apiTokenRE = regexp.MustCompile("\"api_token\":\"([^\"]+)\"")
Expand Down
35 changes: 35 additions & 0 deletions internal/slackclient/unix_decryptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package slackclient

import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"

"golang.org/x/crypto/pbkdf2"
)

type UnixCookieDecryptor struct {
rounds int
}

func (d UnixCookieDecryptor) Decrypt(value, key []byte) ([]byte, error) {
dk := pbkdf2.Key(key, []byte("saltysalt"), d.rounds, 16, sha1.New)

block, err := aes.NewCipher(dk)
if err != nil {
return nil, err
}

iv := make([]byte, 16)
for i := range iv {
iv[i] = ' '
}

mode := cipher.NewCBCDecrypter(block, iv)

mode.CryptBlocks(value, value)

bytesToStrip := int(value[len(value)-1])

return value[:len(value)-bytesToStrip], nil
}
Loading

0 comments on commit fc0c463

Please sign in to comment.