feature/refactor did state (#10)

* feat(did): remove account types

* feat: Refactor Property to Proof in zkprop.go

* feat: add ZKP proof mechanism for verifications

* fix: return bool and error from pinInitialVault

* feat: implement KeyshareSet for managing user and validator keyshares

* feat: Update Credential type in protobuf

* feat: update credential schema with sign count

* feat: migrate  and  modules to middleware

* refactor: rename vault module to ORM

* chore(dwn): add service worker registration to index template

* feat: integrate service worker for offline functionality

* refactor(did): use DIDNamespace enum for verification method in proto reflection

* refactor: update protobuf definitions to support Keyshare

* feat: expose did keeper in app keepers

* Add Motr Web App

* refactor: rename motr/handlers/discovery.go to motr/handlers/openid.go

* refactor: move session related code to middleware

* feat: add database operations for managing assets, chains, and credentials

* feat: add htmx support for UI updates

* refactor: extract common helper scripts

* chore: remove unused storage GUI components

* refactor: Move frontend rendering to dedicated handlers

* refactor: rename  to

* refactor: move alert implementation to templ

* feat: add alert component with icon, title, and message

* feat: add new RequestHeaders struct to store request headers

* Feature/create home view (#9)

* refactor: move view logic to new htmx handler

* refactor: remove unnecessary dependencies

* refactor: remove unused dependencies

* feat(devbox): integrate air for local development

* feat: implement openid connect discovery document

* refactor: rename  to

* refactor(did): update service handling to support DNS discovery

* feat: add support for user and validator keyshares

* refactor: move keyshare signing logic to signer
This commit is contained in:
Prad Nukala
2024-09-11 15:10:54 -04:00
committed by GitHub
parent 4f2d342649
commit bbfe2a2329
197 changed files with 14668 additions and 27810 deletions
+148 -73
View File
@@ -4,12 +4,103 @@ import (
"encoding/base64"
"fmt"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
didv1 "github.com/onsonr/sonr/api/did/v1"
"github.com/onsonr/sonr/x/did/types"
)
func FormatEC2PublicKey(key *webauthncose.EC2PublicKeyData) (*types.JWK, error) {
func APIFormatDIDNamespace(namespace types.DIDNamespace) didv1.DIDNamespace {
return didv1.DIDNamespace(namespace)
}
func APIFormatDIDNamespaces(namespaces []types.DIDNamespace) []didv1.DIDNamespace {
var s []didv1.DIDNamespace
for _, namespace := range namespaces {
s = append(s, APIFormatDIDNamespace(namespace))
}
return s
}
func APIFormatKeyRole(role types.KeyRole) didv1.KeyRole {
return didv1.KeyRole(role)
}
func APIFormatKeyAlgorithm(algorithm types.KeyAlgorithm) didv1.KeyAlgorithm {
return didv1.KeyAlgorithm(algorithm)
}
func APIFormatKeyEncoding(encoding types.KeyEncoding) didv1.KeyEncoding {
return didv1.KeyEncoding(encoding)
}
func APIFormatKeyCurve(curve types.KeyCurve) didv1.KeyCurve {
return didv1.KeyCurve(curve)
}
func APIFormatKeyType(keyType types.KeyType) didv1.KeyType {
return didv1.KeyType(keyType)
}
func APIFormatPermissions(permissions *types.Permissions) *didv1.Permissions {
if permissions == nil {
return nil
}
p := didv1.Permissions{
Grants: APIFormatDIDNamespaces(permissions.Grants),
Scopes: APIFormatPermissionScopes(permissions.Scopes),
}
return &p
}
func APIFormatPermissionScope(scope types.PermissionScope) didv1.PermissionScope {
return didv1.PermissionScope(scope)
}
func APIFormatPermissionScopes(scopes []types.PermissionScope) []didv1.PermissionScope {
var s []didv1.PermissionScope
for _, scope := range scopes {
s = append(s, APIFormatPermissionScope(scope))
}
return s
}
func APIFormatServiceRecord(service *types.Service) *didv1.ServiceRecord {
return &didv1.ServiceRecord{
Id: service.Id,
ServiceType: service.ServiceType,
Authority: service.Authority,
Origin: service.Origin,
Description: service.Description,
ServiceEndpoints: service.ServiceEndpoints,
Permissions: APIFormatPermissions(service.Permissions),
}
}
func APIFormatPubKeyJWK(jwk *types.PubKey_JWK) *didv1.PubKey_JWK {
return &didv1.PubKey_JWK{
Kty: jwk.Kty,
Crv: jwk.Crv,
X: jwk.X,
Y: jwk.Y,
N: jwk.N,
E: jwk.E,
}
}
func APIFormatPubKey(key *types.PubKey) *didv1.PubKey {
return &didv1.PubKey{
Role: APIFormatKeyRole(key.GetRole()),
Algorithm: APIFormatKeyAlgorithm(key.GetAlgorithm()),
Encoding: APIFormatKeyEncoding(key.GetEncoding()),
Curve: APIFormatKeyCurve(key.GetCurve()),
KeyType: APIFormatKeyType(key.GetKeyType()),
Raw: key.GetRaw(),
}
}
func FormatEC2PublicKey(key *webauthncose.EC2PublicKeyData) (*types.PubKey_JWK, error) {
curve, err := GetCOSECurveName(key.Curve)
if err != nil {
return nil, err
@@ -25,7 +116,7 @@ func FormatEC2PublicKey(key *webauthncose.EC2PublicKeyData) (*types.JWK, error)
return MapToJWK(jwkMap)
}
func FormatRSAPublicKey(key *webauthncose.RSAPublicKeyData) (*types.JWK, error) {
func FormatRSAPublicKey(key *webauthncose.RSAPublicKeyData) (*types.PubKey_JWK, error) {
jwkMap := map[string]interface{}{
"kty": "RSA",
"n": base64.RawURLEncoding.EncodeToString(key.Modulus),
@@ -35,8 +126,8 @@ func FormatRSAPublicKey(key *webauthncose.RSAPublicKeyData) (*types.JWK, error)
return MapToJWK(jwkMap)
}
func FormatOKPPublicKey(key *webauthncose.OKPPublicKeyData) (*types.JWK, error) {
curve, err := getOKPCurveName(key.Curve)
func FormatOKPPublicKey(key *webauthncose.OKPPublicKeyData) (*types.PubKey_JWK, error) {
curve, err := GetOKPCurveName(key.Curve)
if err != nil {
return nil, err
}
@@ -50,8 +141,8 @@ func FormatOKPPublicKey(key *webauthncose.OKPPublicKeyData) (*types.JWK, error)
return MapToJWK(jwkMap)
}
func MapToJWK(m map[string]interface{}) (*types.JWK, error) {
jwk := &types.JWK{}
func MapToJWK(m map[string]interface{}) (*types.PubKey_JWK, error) {
jwk := &types.PubKey_JWK{}
for k, v := range m {
switch k {
case "kty":
@@ -84,7 +175,7 @@ func GetCOSECurveName(curveID int64) (string, error) {
}
}
func getOKPCurveName(curveID int64) (string, error) {
func GetOKPCurveName(curveID int64) (string, error) {
switch curveID {
case int64(webauthncose.Ed25519):
return "Ed25519", nil
@@ -93,84 +184,68 @@ func getOKPCurveName(curveID int64) (string, error) {
}
}
func ModulePubKeyToAPI(pk *types.PubKey) *didv1.PubKey {
return &didv1.PubKey{
Role: ModuleKeyRoleToAPI(pk.GetRole()),
Algorithm: ModuleKeyAlgorithmToAPI(pk.GetAlgorithm()),
Encoding: ModuleKeyEncodingToAPI(pk.GetEncoding()),
Curve: ModuleKeyCurveToAPI(pk.GetCurve()),
KeyType: ModuleKeyTypeToAPI(pk.GetKeyType()),
Raw: pk.GetRaw(),
// NormalizeTransports returns the transports as strings
func NormalizeTransports(transports []protocol.AuthenticatorTransport) []string {
tss := make([]string, len(transports))
for i, t := range transports {
tss[i] = string(t)
}
return tss
}
// GetTransports returns the protocol.AuthenticatorTransport
func ModuleTransportsToProtocol(transport []string) []protocol.AuthenticatorTransport {
tss := make([]protocol.AuthenticatorTransport, len(transport))
for i, t := range transport {
tss[i] = protocol.AuthenticatorTransport(t)
}
return tss
}
// ModuleFormatAPIServiceRecord formats a service record for the module
func ModuleFormatAPIServiceRecord(service *didv1.ServiceRecord) *types.Service {
return &types.Service{
Id: service.Id,
ServiceType: service.ServiceType,
Authority: service.Authority,
Origin: service.Origin,
Description: service.Description,
ServiceEndpoints: service.ServiceEndpoints,
Permissions: ModuleFormatAPIPermissions(service.Permissions),
}
}
func ModuleKeyRoleToAPI(role types.KeyRole) didv1.KeyRole {
switch role {
case types.KeyRole_KEY_ROLE_INVOCATION:
return didv1.KeyRole_KEY_ROLE_INVOCATION
case types.KeyRole_KEY_ROLE_ASSERTION:
return didv1.KeyRole_KEY_ROLE_ASSERTION
case types.KeyRole_KEY_ROLE_DELEGATION:
return didv1.KeyRole_KEY_ROLE_DELEGATION
default:
return didv1.KeyRole_KEY_ROLE_INVOCATION
func ModuleFormatAPIPermissions(permissions *didv1.Permissions) *types.Permissions {
if permissions == nil {
return nil
}
p := types.Permissions{
Grants: ModuleFormatAPIDIDNamespaces(permissions.Grants),
Scopes: ModuleFormatAPIPermissionScopes(permissions.Scopes),
}
return &p
}
func ModuleKeyAlgorithmToAPI(algorithm types.KeyAlgorithm) didv1.KeyAlgorithm {
switch algorithm {
case types.KeyAlgorithm_KEY_ALGORITHM_ES256K:
return didv1.KeyAlgorithm_KEY_ALGORITHM_ES256K
case types.KeyAlgorithm_KEY_ALGORITHM_ES256:
return didv1.KeyAlgorithm_KEY_ALGORITHM_ES256
case types.KeyAlgorithm_KEY_ALGORITHM_ES384:
return didv1.KeyAlgorithm_KEY_ALGORITHM_ES384
case types.KeyAlgorithm_KEY_ALGORITHM_ES512:
return didv1.KeyAlgorithm_KEY_ALGORITHM_ES512
case types.KeyAlgorithm_KEY_ALGORITHM_EDDSA:
return didv1.KeyAlgorithm_KEY_ALGORITHM_EDDSA
default:
return didv1.KeyAlgorithm_KEY_ALGORITHM_ES256K
}
func ModuleFormatAPIPermissionScope(scope didv1.PermissionScope) types.PermissionScope {
return types.PermissionScope(scope)
}
func ModuleKeyCurveToAPI(curve types.KeyCurve) didv1.KeyCurve {
switch curve {
case types.KeyCurve_KEY_CURVE_P256:
return didv1.KeyCurve_KEY_CURVE_P256
case types.KeyCurve_KEY_CURVE_SECP256K1:
return didv1.KeyCurve_KEY_CURVE_SECP256K1
case types.KeyCurve_KEY_CURVE_BLS12381:
return didv1.KeyCurve_KEY_CURVE_BLS12381
case types.KeyCurve_KEY_CURVE_KECCAK256:
return didv1.KeyCurve_KEY_CURVE_KECCAK256
default:
return didv1.KeyCurve_KEY_CURVE_P256
func ModuleFormatAPIPermissionScopes(scopes []didv1.PermissionScope) []types.PermissionScope {
var s []types.PermissionScope
for _, scope := range scopes {
s = append(s, ModuleFormatAPIPermissionScope(scope))
}
return s
}
func ModuleKeyEncodingToAPI(encoding types.KeyEncoding) didv1.KeyEncoding {
switch encoding {
case types.KeyEncoding_KEY_ENCODING_RAW:
return didv1.KeyEncoding_KEY_ENCODING_RAW
case types.KeyEncoding_KEY_ENCODING_HEX:
return didv1.KeyEncoding_KEY_ENCODING_HEX
case types.KeyEncoding_KEY_ENCODING_MULTIBASE:
return didv1.KeyEncoding_KEY_ENCODING_MULTIBASE
default:
return didv1.KeyEncoding_KEY_ENCODING_RAW
}
func ModuleFormatAPIDIDNamespace(namespace didv1.DIDNamespace) types.DIDNamespace {
return types.DIDNamespace(namespace)
}
func ModuleKeyTypeToAPI(keyType types.KeyType) didv1.KeyType {
switch keyType {
case types.KeyType_KEY_TYPE_BIP32:
return didv1.KeyType_KEY_TYPE_BIP32
case types.KeyType_KEY_TYPE_ZK:
return didv1.KeyType_KEY_TYPE_ZK
case types.KeyType_KEY_TYPE_WEBAUTHN:
return didv1.KeyType_KEY_TYPE_WEBAUTHN
default:
return didv1.KeyType_KEY_TYPE_BIP32
func ModuleFormatAPIDIDNamespaces(namespaces []didv1.DIDNamespace) []types.DIDNamespace {
var s []types.DIDNamespace
for _, namespace := range namespaces {
s = append(s, ModuleFormatAPIDIDNamespace(namespace))
}
return s
}
+48
View File
@@ -0,0 +1,48 @@
package builder
import (
"net/http"
"github.com/labstack/echo/v4"
"gopkg.in/macaroon.v2"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
)
var PermissionNamespace *checkers.Namespace
func ValidateMacaroonMiddleware(secretKey []byte, location string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Extract the macaroon from the Authorization header
auth := c.Request().Header.Get("Authorization")
if auth == "" {
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Missing Authorization header"})
}
// Decode the macaroon
mac, err := macaroon.Base64Decode([]byte(auth))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid macaroon encoding"})
}
token, err := macaroon.New(secretKey, mac, location, macaroon.LatestVersion)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid macaroon"})
}
// Verify the macaroon
err = token.Verify(secretKey, func(caveat string) error {
// Implement your caveat verification logic here
// For example, you might check if the caveat is still valid (e.g., not expired)
return nil // Return nil if the caveat is valid
}, nil)
if err != nil {
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid macaroon"})
}
// Macaroon is valid, proceed to the next handler
return next(c)
}
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ type PublicKeyCredentialCreationOptions struct {
Extensions AuthenticationExtensions `json:"extensions,omitempty"`
}
func NewRegistrationOptions(origin string, subject string, vaultCID string, params *types.Params) (*PublicKeyCredentialCreationOptions, error) {
func GetPublicKeyCredentialCreationOptions(origin string, subject string, vaultCID string, params *types.Params) (*PublicKeyCredentialCreationOptions, error) {
chal, err := CreateChallenge()
if err != nil {
return nil, err
+8 -7
View File
@@ -4,6 +4,7 @@ import (
"fmt"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
didv1 "github.com/onsonr/sonr/api/did/v1"
"github.com/onsonr/sonr/x/did/types"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
@@ -33,17 +34,17 @@ func CreateAuthnVerification(namespace types.DIDNamespace, issuer string, contro
}
// CreateWalletVerification creates a new verification method for a wallet
func CreateWalletVerification(namespace types.DIDNamespace, controller string, pubkey *types.PubKey, identifier string) *types.VerificationMethod {
return &types.VerificationMethod{
Method: namespace,
func CreateWalletVerification(namespace types.DIDNamespace, controller string, pubkey *types.PubKey, identifier string) *didv1.VerificationMethod {
return &didv1.VerificationMethod{
Method: APIFormatDIDNamespace(namespace),
Controller: controller,
PublicKey: pubkey,
PublicKey: APIFormatPubKey(pubkey),
Id: identifier,
}
}
// ExtractWebAuthnPublicKey parses the raw public key bytes and returns a JWK representation
func ExtractWebAuthnPublicKey(keyBytes []byte) (*types.JWK, error) {
func ExtractWebAuthnPublicKey(keyBytes []byte) (*types.PubKey_JWK, error) {
key, err := webauthncose.ParsePublicKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
@@ -62,8 +63,8 @@ func ExtractWebAuthnPublicKey(keyBytes []byte) (*types.JWK, error) {
}
// NewInitialWalletAccounts creates a new set of verification methods for a wallet
func NewInitialWalletAccounts(controller string, pubkey *types.PubKey) ([]*types.VerificationMethod, error) {
var verificationMethods []*types.VerificationMethod
func NewInitialWalletAccounts(controller string, pubkey *types.PubKey) ([]*didv1.VerificationMethod, error) {
var verificationMethods []*didv1.VerificationMethod
for method, chain := range types.InitialChainCodes {
nk, err := computeBip32AccountPublicKey(pubkey, chain, 0)
if err != nil {
+56
View File
@@ -0,0 +1,56 @@
package builder
import (
"github.com/onsonr/sonr/x/did/types"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
)
var (
GenericPermissionScopeStrings = [...]string{
"profile.name",
"identifiers.email",
"identifiers.phone",
"transactions.read",
"transactions.write",
"wallets.read",
"wallets.create",
"wallets.subscribe",
"wallets.update",
"transactions.verify",
"transactions.broadcast",
"admin.user",
"admin.validator",
}
StringToModulePermissionScope = map[string]types.PermissionScope{
"PERMISSION_SCOPE_UNSPECIFIED": types.PermissionScope_PERMISSION_SCOPE_UNSPECIFIED,
"PERMISSION_SCOPE_BASIC_INFO": types.PermissionScope_PERMISSION_SCOPE_BASIC_INFO,
"PERMISSION_SCOPE_IDENTIFIERS_EMAIL": types.PermissionScope_PERMISSION_SCOPE_PERMISSIONS_READ,
"PERMISSION_SCOPE_IDENTIFIERS_PHONE": types.PermissionScope_PERMISSION_SCOPE_PERMISSIONS_WRITE,
"PERMISSION_SCOPE_TRANSACTIONS_READ": types.PermissionScope_PERMISSION_SCOPE_TRANSACTIONS_READ,
"PERMISSION_SCOPE_TRANSACTIONS_WRITE": types.PermissionScope_PERMISSION_SCOPE_TRANSACTIONS_WRITE,
"PERMISSION_SCOPE_WALLETS_READ": types.PermissionScope_PERMISSION_SCOPE_WALLETS_READ,
"PERMISSION_SCOPE_WALLETS_CREATE": types.PermissionScope_PERMISSION_SCOPE_WALLETS_CREATE,
"PERMISSION_SCOPE_WALLETS_SUBSCRIBE": types.PermissionScope_PERMISSION_SCOPE_WALLETS_SUBSCRIBE,
"PERMISSION_SCOPE_WALLETS_UPDATE": types.PermissionScope_PERMISSION_SCOPE_WALLETS_UPDATE,
"PERMISSION_SCOPE_TRANSACTIONS_VERIFY": types.PermissionScope_PERMISSION_SCOPE_TRANSACTIONS_VERIFY,
"PERMISSION_SCOPE_TRANSACTIONS_BROADCAST": types.PermissionScope_PERMISSION_SCOPE_TRANSACTIONS_BROADCAST,
"PERMISSION_SCOPE_ADMIN_USER": types.PermissionScope_PERMISSION_SCOPE_ADMIN_USER,
"PERMISSION_SCOPE_ADMIN_VALIDATOR": types.PermissionScope_PERMISSION_SCOPE_ADMIN_VALIDATOR,
}
)
func ResolvePermissionScope(scope string) (types.PermissionScope, bool) {
uriToPrefix := make(map[string]string)
for _, scope := range GenericPermissionScopeStrings {
uriToPrefix["https://example.com/auth/"+scope] = scope
}
PermissionNamespace := checkers.NewNamespace(uriToPrefix)
prefix, ok := PermissionNamespace.Resolve("https://example.com/auth/" + scope)
if !ok {
return 0, false
}
permScope, ok := StringToModulePermissionScope[prefix]
return permScope, ok
}
+59
View File
@@ -0,0 +1,59 @@
package builder
import (
"github.com/onsonr/crypto"
"github.com/onsonr/sonr/x/did/types"
)
type Signer interface {
Sign(msg []byte) ([]byte, error)
Verify(msg []byte, sig []byte) error
PublicKey() []byte
}
type signer struct {
user *types.Keyshare
val *types.Keyshare
}
func (k signer) Sign(msg []byte) ([]byte, error) {
valSignFunc, err := crypto.GetSignFunc(k.val, msg)
if err != nil {
return nil, err
}
usrSignFunc, err := crypto.GetSignFunc(k.user, msg)
if err != nil {
return nil, err
}
sig, err := crypto.RunMPCSign(valSignFunc, usrSignFunc)
if err != nil {
return nil, err
}
return crypto.SerializeMPCSignature(sig)
}
func (k signer) Verify(msg []byte, sig []byte) error {
sigMpc, err := crypto.DeserializeMPCSignature(sig)
if err != nil {
return err
}
pk, err := crypto.GetECDSAPublicKey(k.val)
if err != nil {
return err
}
ok := crypto.VerifyMPCSignature(sigMpc, msg, pk)
if !ok {
return types.ErrInvalidSignature
}
return nil
}
func (k signer) PublicKey() []byte {
if k.user != nil {
return k.user.PublicKey
}
if k.val != nil {
return k.val.PublicKey
}
return nil
}
+55
View File
@@ -4,8 +4,63 @@ import (
"bytes"
"encoding/base64"
"reflect"
"github.com/go-webauthn/webauthn/protocol"
)
// Credential contains all needed information about a WebAuthn credential for storage.
type Credential struct {
Subject string `json:"handle"`
AttestationType string `json:"attestationType"`
Origin string `json:"origin"`
CredentialID []byte `json:"id"`
PublicKey []byte `json:"publicKey"`
Transport []string `json:"transport"`
SignCount uint32 `json:"signCount"`
UserPresent bool `json:"userPresent"`
UserVerified bool `json:"userVerified"`
BackupEligible bool `json:"backupEligible"`
BackupState bool `json:"backupState"`
CloneWarning bool `json:"cloneWarning"`
}
// NewCredential will return a credential pointer on successful validation of a registration response.
func NewCredential(c *protocol.ParsedCredentialCreationData, origin, handle string) *Credential {
return &Credential{
Subject: handle,
Origin: origin,
AttestationType: c.Response.AttestationObject.Format,
CredentialID: c.Response.AttestationObject.AuthData.AttData.CredentialID,
PublicKey: c.Response.AttestationObject.AuthData.AttData.CredentialPublicKey,
Transport: NormalizeTransports(c.Response.Transports),
SignCount: c.Response.AttestationObject.AuthData.Counter,
UserPresent: c.Response.AttestationObject.AuthData.Flags.HasUserPresent(),
UserVerified: c.Response.AttestationObject.AuthData.Flags.HasUserVerified(),
BackupEligible: c.Response.AttestationObject.AuthData.Flags.HasBackupEligible(),
BackupState: c.Response.AttestationObject.AuthData.Flags.HasAttestedCredentialData(),
}
}
// Descriptor converts a Credential into a protocol.CredentialDescriptor.
func (c *Credential) Descriptor() protocol.CredentialDescriptor {
return protocol.CredentialDescriptor{
Type: protocol.PublicKeyCredentialType,
CredentialID: c.CredentialID,
Transport: ModuleTransportsToProtocol(c.Transport),
AttestationType: c.AttestationType,
}
}
// This is a signal that the authenticator may be cloned, see CloneWarning above for more information.
func (a *Credential) UpdateCounter(authDataCount uint32) {
if authDataCount <= a.SignCount && (authDataCount != 0 || a.SignCount != 0) {
a.CloneWarning = true
return
}
a.SignCount = authDataCount
}
type CredentialDescriptor struct {
// The valid credential types.
Type CredentialType `json:"type"`