Skip to content

Commit

Permalink
Split encryption and signing keys for enclave
Browse files Browse the repository at this point in the history
  • Loading branch information
rdnt committed Jun 30, 2023
1 parent 27cd8f3 commit 76d04bc
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 61 deletions.
10 changes: 5 additions & 5 deletions src/client/application/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ type Enclave interface {
type Remote interface {
Address() string

Authenticate(username, password string) error
Register(username, password string, publicKey []byte) (user.User, error)
Authenticated() bool
CurrentUser() *user.User

CreateKeystore(k keystore.Keystore) (keystore.Keystore, error)
UpdateKeystore(k keystore.Keystore) (keystore.Keystore, error)
Keystores(privateKey []byte) (map[string]keystore.Keystore, error)
Expand All @@ -47,10 +52,5 @@ type Remote interface {
FinalizeInvitation(invitationId string, privateKey, inviteePublicKey []byte, k keystore.Keystore) (invitation.Invitation, error)
Invitations() (map[string]invitation.Invitation, error)

Authenticate(username, password string) error
Register(username, password string, publicKey []byte) (user.User, error)
Authenticated() bool
CurrentUser() *user.User

SharedSecret(privateKey []byte, userId string) ([]byte, error)
}
1 change: 0 additions & 1 deletion src/client/application/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ func (app *application) Authenticate(password string) ([]byte, error) {
return nil, ErrRemoteAddressMismatch
}

// TODO: do this in a goroutine on interval to keep JWT fresh
err = app.remote.Authenticate(rem.Username, rem.Password)
if err != nil {
return nil, errors.WithMessage(err, "failed to authenticate against remote")
Expand Down
5 changes: 3 additions & 2 deletions src/client/enclaverepo/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func enclaveToJSON(e *enclave) ([]byte, error) {
return b, nil
}

func enclaveFromJSON(b, salt []byte) (*enclave, error) {
func enclaveFromJSON(b, encSalt, signSalt []byte) (*enclave, error) {
e := &enclaveJSON{}

err := json.Unmarshal(b, e)
Expand Down Expand Up @@ -113,7 +113,8 @@ func enclaveFromJSON(b, salt []byte) (*enclave, error) {
return &enclave{
keystores: ks,
creds: rem,
salt: salt,
encSalt: encSalt,
signSalt: signSalt,
keys: e.Keys,
}, nil
}
Expand Down
34 changes: 21 additions & 13 deletions src/client/enclaverepo/enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ import (
)

type enclave struct {
salt []byte
encSalt []byte
signSalt []byte
keystores map[string]keystore.Keystore
keys map[string][]byte
creds *credentials.Credentials
}

func newEnclave(salt []byte, publicKey, privateKey []byte) *enclave {
func newEnclave(encSalt, signSalt, publicKey, privateKey []byte) *enclave {
return &enclave{
keystores: map[string]keystore.Keystore{},
keys: map[string][]byte{},
salt: salt,
encSalt: encSalt,
signSalt: signSalt,
creds: &credentials.Credentials{
PublicKey: publicKey,
PrivateKey: privateKey,
Expand Down Expand Up @@ -102,37 +104,43 @@ func (r *Repository) enclavePath() string {
return path.Join(r.path, "data.myst")
}

func (r *Repository) enclave(argon2idKey []byte) (*enclave, error) {
func (r *Repository) enclave(keypair []byte) (*enclave, error) {
b, err := os.ReadFile(r.enclavePath())
if err != nil {
return nil, errors.Wrap(err, "failed to read enclave file")
}

salt := getSaltFromData(b)

p := crypto.DefaultArgon2IdParams

mac := b[p.SaltLength : sha256.Size+p.SaltLength]
b = b[p.SaltLength+sha256.Size:]
encKey := keypair[0:p.KeyLength]
signKey := keypair[p.KeyLength : p.KeyLength*2]

encSalt, signSalt := getSaltsFromData(b)

valid := crypto.VerifyHMAC_SHA256(argon2idKey, mac, b)
mac := b[p.SaltLength*2 : sha256.Size+p.SaltLength*2]
b = b[p.SaltLength*2+sha256.Size:]

valid := crypto.VerifyHMAC_SHA256(signKey, mac, b)
if !valid {
return nil, application.ErrAuthenticationFailed
}

b, err = crypto.AES256CBC_Decrypt(argon2idKey, b)
b, err = crypto.AES256CBC_Decrypt(encKey, b)
if err != nil {
return nil, errors.WithMessage(err, "failed to decrypt enclave")
}

encJson, err := enclaveFromJSON(b, salt)
encJson, err := enclaveFromJSON(b, encSalt, signSalt)
if err != nil {
return nil, errors.WithMessage(err, "failed to create enclave from json")
}

return encJson, nil
}

func getSaltFromData(b []byte) []byte {
return b[:crypto.DefaultArgon2IdParams.SaltLength]
func getSaltsFromData(b []byte) (encSalt, signSalt []byte) {
encSaltLen := crypto.DefaultArgon2IdParams.SaltLength
signSaltLen := crypto.DefaultArgon2IdParams.SaltLength

return b[:encSaltLen], b[encSaltLen : encSaltLen+signSaltLen]
}
114 changes: 79 additions & 35 deletions src/client/enclaverepo/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"io/fs"
"os"
"sync"

"github.com/pkg/errors"

Expand All @@ -25,8 +26,8 @@ func New(path string) *Repository {
return r
}

func (r *Repository) Keystore(key []byte, id string) (keystore.Keystore, error) {
e, err := r.enclave(key)
func (r *Repository) Keystore(keypair []byte, id string) (keystore.Keystore, error) {
e, err := r.enclave(keypair)
if err != nil {
return keystore.Keystore{}, errors.WithMessage(err, "failed to get enclave")
}
Expand All @@ -39,15 +40,15 @@ func (r *Repository) Keystore(key []byte, id string) (keystore.Keystore, error)
return k, nil
}

func (r *Repository) Keystores(key []byte) (map[string]keystore.Keystore, error) {
func (r *Repository) Keystores(keypair []byte) (map[string]keystore.Keystore, error) {
_, err := os.ReadFile(r.enclavePath())
if errors.Is(err, os.ErrNotExist) {
return nil, application.ErrInitializationRequired
} else if err != nil {
return nil, errors.Wrap(err, "failed to read enclave")
}

e, err := r.enclave(key)
e, err := r.enclave(keypair)
if err != nil {
return nil, errors.WithMessage(err, "failed to get enclave")
}
Expand Down Expand Up @@ -84,8 +85,8 @@ func (r *Repository) enclaveExists() (bool, error) {
return true, nil
}

func (r *Repository) DeleteKeystore(key []byte, id string) error {
e, err := r.enclave(key)
func (r *Repository) DeleteKeystore(keypair []byte, id string) error {
e, err := r.enclave(keypair)
if err != nil {
return errors.WithMessage(err, "failed to get enclave")
}
Expand All @@ -95,16 +96,16 @@ func (r *Repository) DeleteKeystore(key []byte, id string) error {
return errors.WithMessage(err, "failed to delete keystore")
}

err = r.sealAndWrite(key, e)
err = r.sealAndWrite(keypair, e)
if err != nil {
return errors.WithMessage(err, "failed to seal enclave")
}

return nil
}

func (r *Repository) CreateKeystore(key []byte, k keystore.Keystore) (keystore.Keystore, error) {
e, err := r.enclave(key)
func (r *Repository) CreateKeystore(keypair []byte, k keystore.Keystore) (keystore.Keystore, error) {
e, err := r.enclave(keypair)
if err != nil {
return keystore.Keystore{}, errors.WithMessage(err, "failed to get enclave")
}
Expand All @@ -114,7 +115,7 @@ func (r *Repository) CreateKeystore(key []byte, k keystore.Keystore) (keystore.K
return keystore.Keystore{}, errors.WithMessage(err, "failed to add keystore")
}

err = r.sealAndWrite(key, e)
err = r.sealAndWrite(keypair, e)
if err != nil {
return keystore.Keystore{}, errors.WithMessage(err, "failed to seal enclave")
}
Expand All @@ -135,32 +136,53 @@ func (r *Repository) Initialize(password string) ([]byte, error) {

p := crypto.DefaultArgon2IdParams

salt, err := crypto.GenerateRandomBytes(uint(p.SaltLength))
encSalt, err := crypto.GenerateRandomBytes(uint(p.SaltLength))
if err != nil {
return nil, errors.WithMessage(err, "failed to generate salt")
}

key := crypto.Argon2Id([]byte(password), salt)
signSalt, err := crypto.GenerateRandomBytes(uint(p.SaltLength))
if err != nil {
return nil, errors.WithMessage(err, "failed to generate key")
return nil, errors.WithMessage(err, "failed to generate salt")
}

var encKey, signKey []byte

var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()

encKey = crypto.Argon2Id([]byte(password), encSalt)
}()

go func() {
defer wg.Done()

signKey = crypto.Argon2Id([]byte(password), signSalt)
}()

wg.Wait()

publicKey, privateKey, err := crypto.NewCurve25519Keypair()
if err != nil {
return nil, errors.WithMessage(err, "failed to generate keypair")
}

e := newEnclave(salt, publicKey, privateKey)
e := newEnclave(encSalt, signSalt, publicKey, privateKey)

keypair := append(encKey, signKey...)

err = r.sealAndWrite(key, e)
err = r.sealAndWrite(keypair, e)
if err != nil {
return nil, errors.WithMessage(err, "failed to seal enclave")
}

return key, nil
return keypair, nil
}

func (r *Repository) Authenticate(password string) ([]byte, error) {
func (r *Repository) Authenticate(password string) (keypair []byte, err error) {
// we don't defer unlock because hash computation is slow

b, err := os.ReadFile(r.enclavePath())
Expand All @@ -170,25 +192,42 @@ func (r *Repository) Authenticate(password string) ([]byte, error) {
return nil, errors.Wrap(err, "failed to read enclave")
}

salt := getSaltFromData(b)
encSalt, signSalt := getSaltsFromData(b)

key := crypto.Argon2Id([]byte(password), salt)
var encKey, signKey []byte

var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()

encKey = crypto.Argon2Id([]byte(password), encSalt)
}()

go func() {
defer wg.Done()

signKey = crypto.Argon2Id([]byte(password), signSalt)
}()

wg.Wait()

p := crypto.DefaultArgon2IdParams

mac := b[p.SaltLength : sha256.Size+p.SaltLength]
b = b[p.SaltLength+sha256.Size:]
mac := b[p.SaltLength*2 : sha256.Size+p.SaltLength*2]
b = b[p.SaltLength*2+sha256.Size:]

valid := crypto.VerifyHMAC_SHA256(key, mac, b)
valid := crypto.VerifyHMAC_SHA256(signKey, mac, b)
if !valid {
return nil, application.ErrAuthenticationFailed
}

return key, nil
return append(encKey, signKey...), nil
}

func (r *Repository) UpdateKeystore(key []byte, k keystore.Keystore) (keystore.Keystore, error) {
e, err := r.enclave(key)
func (r *Repository) UpdateKeystore(keypair []byte, k keystore.Keystore) (keystore.Keystore, error) {
e, err := r.enclave(keypair)
if err != nil {
return keystore.Keystore{}, errors.WithMessage(err, "failed to get enclave")
}
Expand All @@ -198,30 +237,35 @@ func (r *Repository) UpdateKeystore(key []byte, k keystore.Keystore) (keystore.K
return keystore.Keystore{}, errors.WithMessage(err, "failed to update keystore")
}

err = r.sealAndWrite(key, e)
err = r.sealAndWrite(keypair, e)
if err != nil {
return keystore.Keystore{}, errors.WithMessage(err, "failed to seal enclave")
}

return k, nil
}

func (r *Repository) sealAndWrite(key []byte, e *enclave) error {
func (r *Repository) sealAndWrite(keypair []byte, e *enclave) error {
b, err := enclaveToJSON(e)
if err != nil {
return errors.WithMessage(err, "failed to marshal enclave")
}

b, err = crypto.AES256CBC_Encrypt(key, b)
p := crypto.DefaultArgon2IdParams

encKey := keypair[:p.KeyLength]
signKey := keypair[p.KeyLength:]

b, err = crypto.AES256CBC_Encrypt(encKey, b)
if err != nil {
return errors.WithMessage(err, "failed to encrypt enclave")
}

// authenticate
mac := crypto.HMAC_SHA256(key, b)
mac := crypto.HMAC_SHA256(signKey, b)

// prepend salt and mac to the ciphertext
b = append(e.salt, append(mac, b...)...)
b = append(e.encSalt, append(e.signSalt, append(mac, b...)...)...)

err = os.WriteFile(r.enclavePath(), b, 0600)
if err != nil {
Expand All @@ -231,24 +275,24 @@ func (r *Repository) sealAndWrite(key []byte, e *enclave) error {
return nil
}

func (r *Repository) UpdateCredentials(key []byte, creds credentials.Credentials) (credentials.Credentials, error) {
e, err := r.enclave(key)
func (r *Repository) UpdateCredentials(keypair []byte, creds credentials.Credentials) (credentials.Credentials, error) {
e, err := r.enclave(keypair)
if err != nil {
return credentials.Credentials{}, errors.WithMessage(err, "failed to get enclave")
}

e.creds = &creds

err = r.sealAndWrite(key, e)
err = r.sealAndWrite(keypair, e)
if err != nil {
return credentials.Credentials{}, errors.WithMessage(err, "failed to seal enclave")
}

return creds, nil
}

func (r *Repository) Credentials(key []byte) (credentials.Credentials, error) {
e, err := r.enclave(key)
func (r *Repository) Credentials(keypair []byte) (credentials.Credentials, error) {
e, err := r.enclave(keypair)
if err != nil {
return credentials.Credentials{}, errors.WithMessage(err, "failed to get enclave")
}
Expand Down
Loading

0 comments on commit 76d04bc

Please sign in to comment.