Skip to content

Commit

Permalink
Merge pull request #5 from lburgey/dev
Browse files Browse the repository at this point in the history
Rework the library
  • Loading branch information
zachmann authored Nov 25, 2021
2 parents 9ba3ab4 + 7857e0d commit dee5d67
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 179 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ applications.

Documentation can be found at
https://indigo-dc.gitbook.io/oidc-agent/api/api-go


## Tests
The testing the library requires a working oidc-agent setup:
```sh
oidc-add <account shortname>
export OIDC_AGENT_ACCOUNT=<account shortname>
export OIDC_AGENT_ISSUER=<issuer of the account>
go test -v
```
206 changes: 126 additions & 80 deletions comm.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,143 +13,200 @@ import (
"golang.org/x/crypto/nacl/box"
)

func communicateWithSock(c net.Conn, request string) (response []byte, err error) {
_, err = c.Write([]byte(request))
type keyring struct {
ClientPrivate *[32]byte
ClientPublic *[32]byte
ServerPublic *[32]byte
}

type agentConnection struct {
Conn net.Conn
Keyring *keyring
Remote bool
Socket *socket
}

const defaultRemotePort = 42424

type socket struct {
AddressEnvVar string
Type string // e.g.: "unix" or "tcp"
Remote bool
}

// we try these sockets in order
var sockets = []socket{
{
"OIDC_SOCK",
"unix",
false,
},
{
"OIDC_REMOTE_SOCK",
"tcp",
true,
},
}

func (c *agentConnection) openSocket() (err error) {
for i, socket := range sockets {
address, ok := os.LookupEnv(socket.AddressEnvVar)
if !ok {
err = fmt.Errorf("$%s not set", socket.AddressEnvVar)
continue
}

if socket.Remote {
if _, port, _ := net.SplitHostPort(address); port == "" {
address = fmt.Sprintf("%s:%d", address, defaultRemotePort)
}
}

c.Conn, err = net.Dial(socket.Type, address)
if err != nil {
err = fmt.Errorf("dialing socket: %s", err)
} else {
c.Socket = &sockets[i]
return
}
}
// err is not nil here
err = fmt.Errorf("no socket connection! last error was: %s", err)
return
}

func communicateWithSock(c net.Conn, request []byte) (response []byte, err error) {
_, err = c.Write(request)
if err != nil {
err = fmt.Errorf("Writing to socket: %s", err)
err = fmt.Errorf("writing to socket: %s", err)
return
}
bufSize := 4096
buffer := make([]byte, bufSize)
for {
buffer := make([]byte, 4096)
n, e := c.Read(buffer)
response = append(response, buffer[:n]...)
if n < 4096 || e != nil {
if n < bufSize || e != nil {
err = e
break
}
}
if err != nil {
err = fmt.Errorf("Reading from socket: %s", err)
err = fmt.Errorf("reading from socket: %s", err)
}
return
}

func initCommunication(remote bool) (c net.Conn, err error) {
envVar := "OIDC_SOCK"
sockType := "unix"
if remote {
envVar = "OIDC_REMOTE_SOCK"
sockType = "tcp"
}
socketValue, socketSet := os.LookupEnv(envVar)
if !socketSet {
err = fmt.Errorf("$%s not set", envVar)
// init opens a socket connection to oidc-agent.
// use with `defer conn.close()` in order to not leak the socket
func (c *agentConnection) init(encrypted bool) (err error) {
err = c.openSocket()
if err != nil {
return
}

if remote {
if _, port, _ := net.SplitHostPort(socketValue); port == "" {
socketValue = fmt.Sprintf("%s:%d", socketValue, 42424)
}
}

c, err = net.Dial(sockType, socketValue)
if err != nil {
err = fmt.Errorf("Dialing socket: %s", err)
return
if encrypted {
c.Keyring = new(keyring)
err = c.Keyring.init(c.Conn)
}
return
}

func communicatePlain(remote bool, request string) (response string, err error) {
c, err := initCommunication(remote)
if err != nil {
return
}
defer c.Close()

res, err := communicateWithSock(c, request)
response = string(res)
func newEncryptedConn() (c *agentConnection, err error) {
c = new(agentConnection)
err = c.init(true)
return
}

func communicateEncrypted(remote bool, request string) (response string, err error) {
c, err := initCommunication(remote)
if err != nil {
return
func (c *agentConnection) close() error {
return c.Conn.Close()
}

func (c *agentConnection) sendRequest(request []byte) (response []byte, err error) {
msg := request
if c.Keyring != nil {
msg, err = c.Keyring.encryptMessage(request)
if err != nil {
return
}
}
defer c.Close()

clientPrivateKey, _, serverPublicKey, err := initKeys(c)
response, err = communicateWithSock(c.Conn, msg)
if err != nil {
return
}

encryptedMsg, err := encryptMessage(request, serverPublicKey, clientPrivateKey)
if err != nil {
return
if c.Keyring != nil {
response, err = c.Keyring.decryptMessage(response)
}

encryptedResponse, err := communicateWithSock(c, encryptedMsg)
return
}

func (c *agentConnection) sendJSONRequest(req interface{}, resp interface{}) (err error) {
var reqMsg []byte
reqMsg, err = json.Marshal(req)
if err != nil {
return
}
encryptedResponseStr := string(encryptedResponse)
if isJSON(encryptedResponseStr) {
// response not encrypted
response = encryptedResponseStr
var respMsg []byte
respMsg, err = c.sendRequest(reqMsg)
if err != nil {
return
}

response, err = decryptMessage(encryptedResponseStr, serverPublicKey, clientPrivateKey)
err = json.Unmarshal(respMsg, resp)
return
}

func initKeys(c net.Conn) (clientPrivateKey, clientPublicKey, serverPublicKey *[32]byte, err error) {
clientPublicKey, clientPrivateKey, err = box.GenerateKey(rand.Reader)
func (k *keyring) init(c net.Conn) (err error) {
k.ClientPublic, k.ClientPrivate, err = box.GenerateKey(rand.Reader)
if err != nil {
return
}
clientPubKeyBase64 := base64.StdEncoding.EncodeToString(clientPublicKey[:])
clientPubKeyBase64 := []byte(base64.StdEncoding.EncodeToString(k.ClientPublic[:]))
serverPubKeyBase64, err := communicateWithSock(c, clientPubKeyBase64)
if err != nil {
return
}
serverPubKeyB, err := decodeBytes(serverPubKeyBase64)
var serverPubKeyBytes []byte
serverPubKeyBytes, err = base64.StdEncoding.DecodeString(string(serverPubKeyBase64))
if err != nil {
return
}
serverPublicKey = sliceToArray32(serverPubKeyB)
k.ServerPublic = sliceToArray32(serverPubKeyBytes)
return
}

func encryptMessage(message string, serverPublicKey, clientPrivateKey *[32]byte) (string, error) {
func (k *keyring) encryptMessage(message []byte) (encryptedMsg []byte, err error) {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
return "", err
_, err = io.ReadFull(rand.Reader, nonce[:])
if err != nil {
return
}
encrypted := box.Seal([]byte{}, []byte(message), &nonce, serverPublicKey, clientPrivateKey)
encrypted := box.Seal([]byte{}, message, &nonce, k.ServerPublic, k.ClientPrivate)
encryptedBase64 := base64.StdEncoding.EncodeToString(encrypted)
nonceBase64 := base64.StdEncoding.EncodeToString(nonce[:])
msgLen := len(message)
encryptedMsg := fmt.Sprintf("%d:%s:%s", msgLen, nonceBase64, encryptedBase64)
return encryptedMsg, nil
encryptedMsg = []byte(fmt.Sprintf("%d:%s:%s", msgLen, nonceBase64, encryptedBase64))
return
}

func decryptMessage(message string, serverPublicKey, clientPrivateKey *[32]byte) (decrypted string, err error) {
split := strings.Split(message, ":")
nonce, err := base64.StdEncoding.DecodeString(split[1])
func (k *keyring) decryptMessage(message []byte) (decrypted []byte, err error) {
split := strings.Split(string(message), ":")
var nonce []byte
nonce, err = base64.StdEncoding.DecodeString(split[1])
if err != nil {
return
}
encryptedRes, err := base64.StdEncoding.DecodeString(split[2])
var encryptedRes []byte
encryptedRes, err = base64.StdEncoding.DecodeString(split[2])
if err != nil {
return
}
res, ok := box.Open([]byte{}, encryptedRes, sliceToArray24(nonce), serverPublicKey, clientPrivateKey)
decrypted = string(res)
res, ok := box.Open([]byte{}, encryptedRes, sliceToArray24(nonce), k.ServerPublic, k.ClientPrivate)
decrypted = res
if !ok {
err = fmt.Errorf("Decryption error")
err = fmt.Errorf("decryption error")
}
return
}
Expand All @@ -165,14 +222,3 @@ func sliceToArray24(slice []byte) *[24]byte {
copy(arr[:], slice)
return &arr
}

func decodeBytes(src []byte) ([]byte, error) {
out := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
n, err := base64.StdEncoding.Decode(out, src)
return out[:n], err
}

func isJSON(s string) bool {
var js map[string]interface{}
return json.Unmarshal([]byte(s), &js) == nil
}
45 changes: 45 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package liboidcagent

import "fmt"

// OIDCAgentError is an error type used for returning errors
type OIDCAgentError struct {
err string
help string
remote bool
}

func (e OIDCAgentError) Error() string {
rem := ""
if e.remote {
rem = "(remote) "
}
return fmt.Sprintf("oidc-agent %serror: %s", rem, e.err)
}

// Help returns a help message if available. This help message helps the user to
// solve the problem. If a help message is available it SHOULD be displayed to
// the user. One can use ErrorWithHelp to obtain both.
func (e OIDCAgentError) Help() string {
return e.help
}

// ErrorWithHelp returns a string combining the error message and the help
// message (if available).
func (e OIDCAgentError) ErrorWithHelp() string {
help := e.Help()
err := e.Error()
if help != "" {
return fmt.Sprintf("%s\n%s", err, help)
}
return err
}

func oidcAgentErrorWrap(err error) error {
if err == nil {
return nil
}
return &OIDCAgentError{
err: err.Error(),
}
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ module github.com/indigo-dc/liboidcagent-go

go 1.11

require golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
require (
github.com/adrg/xdg v0.4.0
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
)
16 changes: 15 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit dee5d67

Please sign in to comment.