Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
Support key access in all services (#110)
Browse files Browse the repository at this point in the history
* utility wrappers

* init

* utilize key encryption

* config updatse

* temp

* did svc uses keystore pt 1

* using the keystore pt 2

* temp

* add storage, update manifest with some todos

* merge

* fix dwn tests

* bad rename

* pr comments
  • Loading branch information
Gabe authored Sep 28, 2022
1 parent ce425c4 commit 23ad921
Show file tree
Hide file tree
Showing 33 changed files with 695 additions and 401 deletions.
9 changes: 8 additions & 1 deletion config/compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ log_level = "info"
storage = "bolt"

# per-service configuration
[services.keystore]
name = "keystore"
password = "default-password"

[services.did]
name = "did"
methods = ["key"]
Expand All @@ -33,6 +37,9 @@ name = "schema"
[services.credential]
name = "credential"

[services.manifest]
name = "manifest"

[services.dwn]
name = "dwn"
dwn_endpoint = "http://localhost:4321"
dwn_endpoint = "http://localhost:4321"
49 changes: 40 additions & 9 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ type ServicesConfig struct {

// Embed all service-specific configs here. The order matters: from which should be instantiated first, to last

KeyStoreConfig KeyStoreServiceConfig `toml:"keystore,omitempty"`
DIDConfig DIDServiceConfig `toml:"did,omitempty"`
SchemaConfig SchemaServiceConfig `toml:"schema,omitempty"`
CredentialConfig CredentialServiceConfig `toml:"credential,omitempty"`
KeyStoreConfig KeyStoreServiceConfig `toml:"keystore,omitempty"`
ManifestConfig ManifestServiceConfig `toml:"manifest,omitempty"`
DWNConfig DWNServiceConfig `toml:"dwn,omitempty"`
}
Expand All @@ -62,6 +62,20 @@ type BaseServiceConfig struct {
Name string `toml:"name"`
}

type KeyStoreServiceConfig struct {
*BaseServiceConfig
// Service key password. Used by a KDF whose key is used by a symmetric cypher for key encryption.
// The password is salted before usage.
ServiceKeyPassword string `toml:"password"`
}

func (k *KeyStoreServiceConfig) IsEmpty() bool {
if k == nil {
return true
}
return reflect.DeepEqual(k, &KeyStoreServiceConfig{})
}

type DIDServiceConfig struct {
*BaseServiceConfig
Methods []string `toml:"methods"`
Expand Down Expand Up @@ -90,20 +104,34 @@ type CredentialServiceConfig struct {
// TODO(gabe) supported key and signature types
}

func (c *CredentialServiceConfig) IsEmpty() bool {
if c == nil {
return true
}
return reflect.DeepEqual(c, &CredentialServiceConfig{})
}

type ManifestServiceConfig struct {
*BaseServiceConfig
}

func (m *ManifestServiceConfig) IsEmpty() bool {
if m == nil {
return true
}
return reflect.DeepEqual(m, &ManifestServiceConfig{})
}

type DWNServiceConfig struct {
*BaseServiceConfig
DWNEndpoint string `toml:"dwn_endpoint"`
}

type KeyStoreServiceConfig struct {
*BaseServiceConfig
// Service key password. Used by a KDF whose key is used by a symmetric cypher for key encryption.
// The password is salted before usage.
ServiceKeyPassword string
func (d *DWNServiceConfig) IsEmpty() bool {
if d == nil {
return true
}
return reflect.DeepEqual(d, &DWNServiceConfig{})
}

// LoadConfig attempts to load a TOML config file from the given path, and coerce it into our object model.
Expand Down Expand Up @@ -149,6 +177,10 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
if defaultConfig {
config.Services = ServicesConfig{
StorageProvider: "bolt",
KeyStoreConfig: KeyStoreServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "keystore"},
ServiceKeyPassword: "default-password",
},
DIDConfig: DIDServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "did"},
Methods: []string{"key"},
Expand All @@ -159,9 +191,8 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
CredentialConfig: CredentialServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "credential"},
},
KeyStoreConfig: KeyStoreServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "keystore"},
ServiceKeyPassword: "default-password",
ManifestConfig: ManifestServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "manifest"},
},
DWNConfig: DWNServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "dwn"},
Expand Down
13 changes: 12 additions & 1 deletion config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ log_level = "debug"
storage = "bolt"

# per-service configuration
[services.keystore]
name = "keystore"
password = "default-password"

[services.did]
name = "did"
methods = ["key"]
Expand All @@ -29,4 +33,11 @@ methods = ["key"]
name = "schema"

[services.credential]
name = "credential"
name = "credential"

[services.manifest]
name = "manifest"

[services.dwn]
name = "dwn"
dwn_endpoint = "http://localhost:4321"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/BurntSushi/toml v1.2.0
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220923181557-56fc273d1dcd
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220921215642-f749c4d89b8b
github.com/ardanlabs/conf v1.5.0
github.com/dimfeld/httptreemux/v5 v5.4.0
github.com/go-playground/locales v0.14.0
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220915152202-18c69f474901 h1:7cT1hMDiWQlsQYNP2U0mxpUjc5AxKVElcAdvTt5amnM=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220915152202-18c69f474901/go.mod h1:uXrbtCwqgsbZvL/zM3+DpAOuuIax9qG2aeUhZrGPCck=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220919021815-57be087eca80 h1:1HBBtzkjsgjCYjMVWi6+785SO+Q7kSOOrA0Njw3JON0=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220919021815-57be087eca80/go.mod h1:ZUcjj/CHvtmz48c9GapcbavlTRsN9JZ9pKdT+co/EvY=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220920154030-cbf6b4665a6d h1:NW/9Qg2Z+Yhh6PtzgV/cFwgNz7Sgng3E25IYQiUV34c=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220920154030-cbf6b4665a6d/go.mod h1:jkbaZT2qU+g6weOHpjPdxuHdmfOApdT8CQg41GEyZhA=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220923181557-56fc273d1dcd h1:BTbS7+/QsnxvVhP0LwB3JJxnT/9qHC8msEtku2JF4QY=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220923181557-56fc273d1dcd/go.mod h1:jkbaZT2qU+g6weOHpjPdxuHdmfOApdT8CQg41GEyZhA=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220921215642-f749c4d89b8b h1:0IJJPN8qBGEINcNheyMN/VztIj/Wu9OLzoaEdwfKMWM=
github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220921215642-f749c4d89b8b/go.mod h1:jkbaZT2qU+g6weOHpjPdxuHdmfOApdT8CQg41GEyZhA=
github.com/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c=
github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down
10 changes: 8 additions & 2 deletions internal/keyaccess/dataintegrity.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ func NewDataIntegrityKeyAccess(kid string, key gocrypto.PrivateKey) (*DataIntegr
}, nil
}

func (ka DataIntegrityKeyAccess) Sign(payload cryptosuite.Provable) ([]byte, error) {
// DataIntegrityJSON represents a response from a DataIntegrityKeyAccess.Sign() call represented
// as a serialized JSON object
type DataIntegrityJSON struct {
Data []byte `json:"data" validate:"required"`
}

func (ka DataIntegrityKeyAccess) Sign(payload cryptosuite.Provable) (*DataIntegrityJSON, error) {
if payload == nil {
return nil, errors.New("payload cannot be nil")
}
Expand All @@ -58,7 +64,7 @@ func (ka DataIntegrityKeyAccess) Sign(payload cryptosuite.Provable) ([]byte, err
if err != nil {
return nil, errors.Wrap(err, "could not marshal signed payload")
}
return signedJSONBytes, nil
return &DataIntegrityJSON{Data: signedJSONBytes}, nil
}

func (ka DataIntegrityKeyAccess) Verify(payload cryptosuite.Provable) error {
Expand Down
6 changes: 3 additions & 3 deletions internal/keyaccess/dataintegrity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestDataIntegrityKeyAccessSignVerify(t *testing.T) {
assert.NotEmpty(tt, signedCred)

var cred credential.VerifiableCredential
err = json.Unmarshal(signedCred, &cred)
err = json.Unmarshal(signedCred.Data, &cred)
assert.NoError(tt, err)

// verify
Expand Down Expand Up @@ -104,11 +104,11 @@ func TestDataIntegrityKeyAccessSignVerify(t *testing.T) {
assert.NotEmpty(tt, signedPres)

var pres credential.VerifiablePresentation
err = json.Unmarshal(signedPres, &pres)
err = json.Unmarshal(signedPres.Data, &pres)
assert.NoError(tt, err)

// verify
err = ka.Verify(&pres)
assert.NoError(tt, err)
})
}
}
47 changes: 32 additions & 15 deletions internal/keyaccess/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,44 +42,61 @@ func NewJWKKeyAccess(kid string, key gocrypto.PrivateKey) (*JWKKeyAccess, error)
}, nil
}

func (ka JWKKeyAccess) Sign(payload map[string]interface{}) ([]byte, error) {
// A JWKToken is a utility that wraps a string representation of a JSON Web Token.
type JWKToken struct {
Token string `json:"token" validate:"required"`
}

func (ka JWKKeyAccess) Sign(payload map[string]interface{}) (*JWKToken, error) {
if payload == nil {
return nil, errors.New("payload cannot be nil")
}
return ka.SignJWT(payload)
tokenBytes, err := ka.SignJWT(payload)
if err != nil {
return nil, errors.Wrap(err, "could not sign payload")
}
return &JWKToken{Token: string(tokenBytes)}, nil
}

func (ka JWKKeyAccess) Verify(token string) error {
if token == "" {
func (ka JWKKeyAccess) Verify(token JWKToken) error {
if token.Token == "" {
return errors.New("token cannot be empty")
}
return ka.VerifyJWT(token)
return ka.VerifyJWT(token.Token)
}

func (ka JWKKeyAccess) SignVerifiableCredential(credential credential.VerifiableCredential) ([]byte, error) {
func (ka JWKKeyAccess) SignVerifiableCredential(credential credential.VerifiableCredential) (*JWKToken, error) {
if err := credential.IsValid(); err != nil {
return nil, errors.New("cannot sign invalid credential")
}
return signing.SignVerifiableCredentialJWT(ka.JWTSigner, credential)
tokenBytes, err := signing.SignVerifiableCredentialJWT(ka.JWTSigner, credential)
if err != nil {
return nil, errors.Wrap(err, "could not sign credential")
}
return &JWKToken{Token: string(tokenBytes)}, nil
}

func (ka JWKKeyAccess) VerifyVerifiableCredential(token string) (*credential.VerifiableCredential, error) {
if token == "" {
func (ka JWKKeyAccess) VerifyVerifiableCredential(token JWKToken) (*credential.VerifiableCredential, error) {
if token.Token == "" {
return nil, errors.New("token cannot be empty")
}
return signing.VerifyVerifiableCredentialJWT(ka.JWTVerifier, token)
return signing.VerifyVerifiableCredentialJWT(ka.JWTVerifier, token.Token)
}

func (ka JWKKeyAccess) SignVerifiablePresentation(presentation credential.VerifiablePresentation) ([]byte, error) {
func (ka JWKKeyAccess) SignVerifiablePresentation(presentation credential.VerifiablePresentation) (*JWKToken, error) {
if err := presentation.IsValid(); err != nil {
return nil, errors.New("cannot sign invalid presentation")
}
return signing.SignVerifiablePresentationJWT(ka.JWTSigner, presentation)
tokenBytes, err := signing.SignVerifiablePresentationJWT(ka.JWTSigner, presentation)
if err != nil {
return nil, errors.Wrap(err, "could not sign presentation")
}
return &JWKToken{Token: string(tokenBytes)}, nil
}

func (ka JWKKeyAccess) VerifyVerifiablePresentation(token string) (*credential.VerifiablePresentation, error) {
if token == "" {
func (ka JWKKeyAccess) VerifyVerifiablePresentation(token JWKToken) (*credential.VerifiablePresentation, error) {
if token.Token == "" {
return nil, errors.New("token cannot be empty")
}
return signing.VerifyVerifiablePresentationJWT(ka.JWTVerifier, token)
return signing.VerifyVerifiablePresentationJWT(ka.JWTVerifier, token.Token)
}
16 changes: 8 additions & 8 deletions internal/keyaccess/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ func TestJWKKeyAccessSignVerify(t *testing.T) {
data := map[string]interface{}{
"test": "test",
}
tokenBytes, err := ka.Sign(data)
token, err := ka.Sign(data)
assert.NoError(tt, err)
assert.NotEmpty(tt, tokenBytes)
assert.NotEmpty(tt, token)

err = ka.Verify(string(tokenBytes))
err = ka.Verify(*token)
assert.NoError(tt, err)
})

Expand All @@ -79,7 +79,7 @@ func TestJWKKeyAccessSignVerify(t *testing.T) {
assert.NoError(tt, err)
assert.NotEmpty(tt, ka)

err = ka.Verify("")
err = ka.Verify(JWKToken{})
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "token cannot be empty")
})
Expand All @@ -101,7 +101,7 @@ func TestJWKKeyAccessSignVerifyCredentials(t *testing.T) {
assert.NotEmpty(tt, signedCred)

// verify
verifiedCred, err := ka.VerifyVerifiableCredential(string(signedCred))
verifiedCred, err := ka.VerifyVerifiableCredential(*signedCred)
assert.NoError(tt, err)
assert.NotEmpty(tt, verifiedCred)

Expand Down Expand Up @@ -136,7 +136,7 @@ func TestJWKKeyAccessSignVerifyCredentials(t *testing.T) {
assert.NotEmpty(tt, ka)

// verify
_, err = ka.VerifyVerifiableCredential("bad")
_, err = ka.VerifyVerifiableCredential(JWKToken{"bad"})
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "could not verify JWT and its signature")
})
Expand All @@ -158,7 +158,7 @@ func TestJWKKeyAccessSignVerifyPresentations(t *testing.T) {
assert.NotEmpty(tt, signedPres)

// verify
verifiedPres, err := ka.VerifyVerifiablePresentation(string(signedPres))
verifiedPres, err := ka.VerifyVerifiablePresentation(*signedPres)
assert.NoError(tt, err)
assert.NotEmpty(tt, verifiedPres)

Expand Down Expand Up @@ -193,7 +193,7 @@ func TestJWKKeyAccessSignVerifyPresentations(t *testing.T) {
assert.NotEmpty(tt, ka)

// verify
_, err = ka.VerifyVerifiablePresentation("bad")
_, err = ka.VerifyVerifiablePresentation(JWKToken{"bad"})
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "could not verify JWT and its signature")
})
Expand Down
4 changes: 2 additions & 2 deletions internal/util/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ func XChaCha20Poly1305Decrypt(key, data []byte) ([]byte, error) {
nonce, ciphertext := data[:aead.NonceSize()], data[aead.NonceSize():]

// Decrypt the message and check it wasn't tampered with.
decyrpted, err := aead.Open(nil, nonce, ciphertext, nil)
decrypted, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, errors.Wrap(err, "could not decrypt data")
}
return decyrpted, nil
return decrypted, nil
}

// Argon2KeyGen returns an encoded string generation of a key generated using the go crypto argon2 impl
Expand Down
Loading

0 comments on commit 23ad921

Please sign in to comment.