Skip to content

Commit

Permalink
Opinionated and specific bakery refactoring.
Browse files Browse the repository at this point in the history
  • Loading branch information
rogpeppe committed Oct 30, 2014
1 parent 333d5d0 commit 6a75179
Show file tree
Hide file tree
Showing 15 changed files with 419 additions and 429 deletions.
147 changes: 147 additions & 0 deletions bakery/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package bakery

import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"

"code.google.com/p/go.crypto/nacl/box"
)

type caveatIdRecord struct {
RootKey []byte
Condition string
}

// caveatId defines the format of a third party caveat id.
type caveatId struct {
ThirdPartyPublicKey []byte
FirstPartyPublicKey []byte
Nonce []byte
Id string
}

// boxEncoder encodes caveat ids confidentially to a third-party service using
// authenticated public key encryption compatible with NaCl box.
type boxEncoder struct {
locator PublicKeyLocator
key *KeyPair
}

// newBoxEncoder creates a new boxEncoder with the given public key pair and
// third-party public key locator function.
func newBoxEncoder(locator PublicKeyLocator, key *KeyPair) *boxEncoder {
return &boxEncoder{
key: key,
locator: locator,
}
}

func (enc *boxEncoder) encodeCaveatId(cav Caveat, rootKey []byte) (string, error) {
if cav.Location == "" {
return "", fmt.Errorf("cannot make caveat id for first party caveat")
}
thirdPartyPub, err := enc.locator.PublicKeyForLocation(cav.Location)
if err != nil {
return "", err
}
id, err := enc.newCaveatId(cav, rootKey, thirdPartyPub)
if err != nil {
return "", err
}
data, err := json.Marshal(id)
if err != nil {
return "", fmt.Errorf("cannot marshal %#v: %v", id, err)
}
return base64.StdEncoding.EncodeToString(data), nil
}

func (enc *boxEncoder) newCaveatId(cav Caveat, rootKey []byte, thirdPartyPub *PublicKey) (*caveatId, error) {
var nonce [NonceLen]byte
if _, err := rand.Read(nonce[:]); err != nil {
return nil, fmt.Errorf("cannot generate random number for nonce: %v", err)
}
plain := caveatIdRecord{
RootKey: rootKey,
Condition: cav.Condition,
}
plainData, err := json.Marshal(&plain)
if err != nil {
return nil, fmt.Errorf("cannot marshal %#v: %v", &plain, err)
}
sealed := box.Seal(nil, plainData, &nonce, (*[32]byte)(thirdPartyPub), (*[32]byte)(enc.key.PrivateKey()))
return &caveatId{
ThirdPartyPublicKey: thirdPartyPub[:],
FirstPartyPublicKey: enc.key.PublicKey()[:],
Nonce: nonce[:],
Id: base64.StdEncoding.EncodeToString(sealed),
}, nil
}

// boxDecoder decodes caveat ids for third-party service that were encoded to
// the third-party with authenticated public key encryption compatible with
// NaCl box.
type boxDecoder struct {
key *KeyPair
}

// newBoxDecoder creates a new BoxDecoder using the given key pair.
func newBoxDecoder(key *KeyPair) *boxDecoder {
return &boxDecoder{
key: key,
}
}

func (d *boxDecoder) decodeCaveatId(id string) (rootKey []byte, condition string, err error) {
data, err := base64.StdEncoding.DecodeString(id)
if err != nil {
return nil, "", fmt.Errorf("cannot base64-decode caveat id: %v", err)
}
var tpid caveatId
if err := json.Unmarshal(data, &tpid); err != nil {
return nil, "", fmt.Errorf("cannot unmarshal caveat id %q: %v", data, err)
}
var recordData []byte

recordData, err = d.encryptedCaveatId(tpid)
if err != nil {
return nil, "", err
}
var record caveatIdRecord
if err := json.Unmarshal(recordData, &record); err != nil {
return nil, "", fmt.Errorf("cannot decode third party caveat record: %v", err)
}
return record.RootKey, record.Condition, nil
}

func (d *boxDecoder) encryptedCaveatId(id caveatId) ([]byte, error) {
if d.key == nil {
return nil, fmt.Errorf("no public key for caveat id decryption")
}
if !bytes.Equal(d.key.PublicKey()[:], id.ThirdPartyPublicKey) {
return nil, fmt.Errorf("public key mismatch")
}
var nonce [NonceLen]byte
if len(id.Nonce) != len(nonce) {
return nil, fmt.Errorf("bad nonce length")
}
copy(nonce[:], id.Nonce)

var firstPartyPublicKey [KeyLen]byte
if len(id.FirstPartyPublicKey) != len(firstPartyPublicKey) {
return nil, fmt.Errorf("bad public key length")
}
copy(firstPartyPublicKey[:], id.FirstPartyPublicKey)

sealed, err := base64.StdEncoding.DecodeString(id.Id)
if err != nil {
return nil, fmt.Errorf("cannot base64-decode encrypted caveat id: %v", err)
}
out, ok := box.Open(nil, sealed, &nonce, (*[KeyLen]byte)(&firstPartyPublicKey), (*[KeyLen]byte)(d.key.PrivateKey()))
if !ok {
return nil, fmt.Errorf("decryption of public-key encrypted caveat id %#v failed", id)
}
return out, nil
}
21 changes: 9 additions & 12 deletions bakery/discharge.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,22 @@ type Discharger struct {
// Checker is used to check the caveat's condition.
Checker ThirdPartyChecker

// Decoder is used to decode the caveat id.
Decoder CaveatIdDecoder

// Factory is used to create the macaroon.
// Note that *Service implements NewMacarooner.
Factory NewMacarooner

// boxDecoder is used to decode the caveat id.
decoder *boxDecoder
}

// Discharge creates a macaroon that discharges the third party
// caveat with the given id. The id should have been created
// earlier with a matching CaveatIdEncoder.
// The condition implicit in the id is checked for validity
// using d.Checker, and then if valid, a new macaroon
// is minted which discharges the caveat, and
// can eventually be associated with a client request using
// AddClientMacaroon.
// Discharge creates a macaroon that discharges the third party caveat with the
// given id. The id should have been created earlier by a Service. The
// condition implicit in the id is checked for validity using d.Checker, and
// then if valid, a new macaroon is minted which discharges the caveat, and can
// eventually be associated with a client request using AddClientMacaroon.
func (d *Discharger) Discharge(id string) (*macaroon.Macaroon, error) {
logf("server attempting to discharge %q", id)
rootKey, condition, err := d.Decoder.DecodeCaveatId(id)
rootKey, condition, err := d.decoder.decodeCaveatId(id)
if err != nil {
return nil, fmt.Errorf("discharger cannot decode caveat id: %v", err)
}
Expand Down
6 changes: 4 additions & 2 deletions bakery/example/authservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
// authService implements an authorization service,
// that can discharge third-party caveats added
// to other macaroons.
func authService(endpoint string) (http.Handler, error) {
svc, err := httpbakery.NewService(httpbakery.NewServiceParams{
func authService(endpoint string, key *bakery.KeyPair) (http.Handler, error) {
svc, err := httpbakery.NewService(bakery.NewServiceParams{
Location: endpoint,
Key: key,
Locator: bakery.NewPublicKeyRing(),
})
if err != nil {
return nil, err
Expand Down
28 changes: 19 additions & 9 deletions bakery/example/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,36 @@ import (
"testing"

gc "gopkg.in/check.v1"

"github.com/rogpeppe/macaroon/bakery"
)

func TestPackage(t *testing.T) {
gc.TestingT(t)
}

type exampleSuite struct{}
type exampleSuite struct {
authEndpoint string
authPublicKey *bakery.PublicKey
}

var _ = gc.Suite(&exampleSuite{})

func (*exampleSuite) TestExample(c *gc.C) {
authEndpoint, err := serve(authService)
func (s *exampleSuite) SetUpSuite(c *gc.C) {
key, err := bakery.GenerateKey()
c.Assert(err, gc.IsNil)
serverEndpoint, err := serve(func(endpoint string) (http.Handler, error) {
return targetService(endpoint, authEndpoint)
s.authPublicKey = key.PublicKey()
s.authEndpoint, err = serve(func(endpoint string) (http.Handler, error) {
return authService(endpoint, key)
})
c.Assert(err, gc.IsNil)
}

func (s *exampleSuite) TestExample(c *gc.C) {
serverEndpoint, err := serve(func(endpoint string) (http.Handler, error) {
return targetService(endpoint, s.authEndpoint, s.authPublicKey)
})
c.Assert(err, gc.IsNil)
c.Logf("gold request")
resp, err := clientRequest(serverEndpoint + "/gold")
c.Assert(err, gc.IsNil)
Expand All @@ -34,11 +46,9 @@ func (*exampleSuite) TestExample(c *gc.C) {
c.Assert(resp, gc.Equals, "every cloud has a silver lining")
}

func (*exampleSuite) BenchmarkExample(c *gc.C) {
authEndpoint, err := serve(authService)
c.Assert(err, gc.IsNil)
func (s *exampleSuite) BenchmarkExample(c *gc.C) {
serverEndpoint, err := serve(func(endpoint string) (http.Handler, error) {
return targetService(endpoint, authEndpoint)
return targetService(endpoint, s.authEndpoint, s.authPublicKey)
})
c.Assert(err, gc.IsNil)
c.ResetTimer()
Expand Down
2 changes: 1 addition & 1 deletion bakery/example/idservice/idservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type UserInfo struct {

// Params holds parameters for New.
type Params struct {
Service httpbakery.NewServiceParams
Service bakery.NewServiceParams
Users map[string]*UserInfo
}

Expand Down
7 changes: 4 additions & 3 deletions bakery/example/idservice/idservice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import (

type suite struct {
authEndpoint string
authPublicKey *[32]byte
authPublicKey *bakery.PublicKey
}

var _ = gc.Suite(&suite{})

func (s *suite) SetUpSuite(c *gc.C) {
key, err := httpbakery.GenerateKey()
key, err := bakery.GenerateKey()
c.Assert(err, gc.IsNil)
s.authPublicKey = key.PublicKey()
s.authEndpoint = serve(c, func(endpoint string) (http.Handler, error) {
Expand All @@ -42,10 +42,11 @@ func (s *suite) SetUpSuite(c *gc.C) {
},
},
},
Service: httpbakery.NewServiceParams{
Service: bakery.NewServiceParams{
Location: endpoint,
Store: bakery.NewMemStorage(),
Key: key,
Locator: bakery.NewPublicKeyRing(),
},
})
})
Expand Down
13 changes: 10 additions & 3 deletions bakery/example/idservice/targetservice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@ type targetServiceHandler struct {
// targetService implements a "target service", representing
// an arbitrary web service that wants to delegate authorization
// to third parties.
func targetService(endpoint, authEndpoint string, authPK *[32]byte) (http.Handler, error) {
svc, err := httpbakery.NewService(httpbakery.NewServiceParams{
func targetService(endpoint, authEndpoint string, authPK *bakery.PublicKey) (http.Handler, error) {
key, err := bakery.GenerateKey()
if err != nil {
return nil, err
}
pkLocator := bakery.NewPublicKeyRing()
svc, err := httpbakery.NewService(bakery.NewServiceParams{
Key: key,
Location: endpoint,
Locator: pkLocator,
})
if err != nil {
return nil, err
}
log.Printf("adding public key for location %s: %x", authEndpoint, authPK[:])
svc.AddPublicKeyForLocation(authEndpoint, true, authPK)
pkLocator.AddPublicKeyForLocation(authEndpoint, true, authPK)
mux := http.NewServeMux()
srv := &targetServiceHandler{
svc: svc,
Expand Down
13 changes: 11 additions & 2 deletions bakery/example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@ import (
"log"
"net"
"net/http"

"github.com/rogpeppe/macaroon/bakery"
)

func main() {
authEndpoint := mustServe(authService)
key, err := bakery.GenerateKey()
if err != nil {
log.Fatalf("cannot generate auth service key pair: %v", err)
}
authPublicKey := key.PublicKey()
authEndpoint := mustServe(func(endpoint string) (http.Handler, error) {
return authService(endpoint, key)
})
serverEndpoint := mustServe(func(endpoint string) (http.Handler, error) {
return targetService(endpoint, authEndpoint)
return targetService(endpoint, authEndpoint, authPublicKey)
})
resp, err := clientRequest(serverEndpoint)
if err != nil {
Expand Down
14 changes: 12 additions & 2 deletions bakery/example/targetservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"log"
"net"
"net/http"
"time"
Expand All @@ -22,13 +23,22 @@ type targetServiceHandler struct {
// an arbitrary web service that wants to delegate authorization
// to third parties.
//
func targetService(endpoint, authEndpoint string) (http.Handler, error) {
svc, err := httpbakery.NewService(httpbakery.NewServiceParams{
func targetService(endpoint, authEndpoint string, authPK *bakery.PublicKey) (http.Handler, error) {
key, err := bakery.GenerateKey()
if err != nil {
return nil, err
}
pkLocator := bakery.NewPublicKeyRing()
svc, err := httpbakery.NewService(bakery.NewServiceParams{
Key: key,
Location: endpoint,
Locator: pkLocator,
})
if err != nil {
return nil, err
}
log.Printf("adding public key for location %s: %x", authEndpoint, authPK[:])
pkLocator.AddPublicKeyForLocation(authEndpoint, true, authPK)
mux := http.NewServeMux()
srv := &targetServiceHandler{
svc: svc,
Expand Down
Loading

0 comments on commit 6a75179

Please sign in to comment.