mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 02:11:40 +00:00
feature/ipfs vault allocation (#8)
* refactor: move constants to genesis.proto * feat: add ipfs_active flag to genesis state * feat: add IPFS connection initialization to keeper * feat: add testnet process-compose * refactor: rename sonr-testnet docker image to sonr-runner * refactor: update docker-vm-release workflow to use 'latest' tag * feat: add permission to workflows * feat: add new service chain execution * feat: add abstract vault class to pkl * feat: use jetpackio/devbox image for runner * feat: introduce dwn for local service worker * refactor: remove unnecessary dockerfile layers * refactor(deploy): Update Dockerfile to copy go.mod and go.sum from the parent directory * build: move Dockerfile to root directory * build: Add Dockerfile for deployment * feat: Update Dockerfile to work with Go project in parent directory * build: Update docker-compose.yaml to use relative paths * feat: Update docker-compose to work with new image and parent git directory * refactor: remove unnecessary test script * <no value> * feat: add test_node script for running node tests * feat: add IPFS cluster to testnet * feat: add docker image for sonr-runner * fix: typo in export path * feat(did): Add Localhost Registration Enabled Genesis Option * feat: add support for Sqlite DB in vault * feat: improve vault model JSON serialization * feat: support querying HTMX endpoint for DID * feat: Add primary key, unique, default, not null, auto increment, and foreign key field types * feat: Add PublicKey model in pkl/vault.pkl * feat: add frontend server * refactor: move dwn.wasm to vfs directory * feat(frontend): remove frontend server implementation * feat: Add a frontend server and web auth protocol * feat: implement new key types for MPC and ZK proofs * fix: Update enum types and DefaultKeyInfos * fix: correct typo in KeyAlgorithm enum * feat(did): add attestation format validation * feat: Add x/did/builder/extractor.go * feat: Update JWK parsing in x/did/builder/extractor.go * feat: Use github.com/onsonr/sonr/x/did/types package * feat: Extract and format public keys from WebAuthn credentials * feat: Introduce a new `mapToJWK` function to convert a map to a `types.JWK` struct * feat: add support for extracting JWK public keys * feat: remove VerificationMethod struct * refactor: extract public key extraction logic * feat: add helper functions to map COSECurveID to JWK curve names * feat: pin initial vault
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"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) {
|
||||
curve, err := GetCOSECurveName(key.Curve)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jwkMap := map[string]interface{}{
|
||||
"kty": "EC",
|
||||
"crv": curve,
|
||||
"x": base64.RawURLEncoding.EncodeToString(key.XCoord),
|
||||
"y": base64.RawURLEncoding.EncodeToString(key.YCoord),
|
||||
}
|
||||
|
||||
return MapToJWK(jwkMap)
|
||||
}
|
||||
|
||||
func FormatRSAPublicKey(key *webauthncose.RSAPublicKeyData) (*types.JWK, error) {
|
||||
jwkMap := map[string]interface{}{
|
||||
"kty": "RSA",
|
||||
"n": base64.RawURLEncoding.EncodeToString(key.Modulus),
|
||||
"e": base64.RawURLEncoding.EncodeToString(key.Exponent),
|
||||
}
|
||||
|
||||
return MapToJWK(jwkMap)
|
||||
}
|
||||
|
||||
func FormatOKPPublicKey(key *webauthncose.OKPPublicKeyData) (*types.JWK, error) {
|
||||
curve, err := getOKPCurveName(key.Curve)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jwkMap := map[string]interface{}{
|
||||
"kty": "OKP",
|
||||
"crv": curve,
|
||||
"x": base64.RawURLEncoding.EncodeToString(key.XCoord),
|
||||
}
|
||||
|
||||
return MapToJWK(jwkMap)
|
||||
}
|
||||
|
||||
func MapToJWK(m map[string]interface{}) (*types.JWK, error) {
|
||||
jwk := &types.JWK{}
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case "kty":
|
||||
jwk.Kty = v.(string)
|
||||
case "crv":
|
||||
jwk.Crv = v.(string)
|
||||
case "x":
|
||||
jwk.X = v.(string)
|
||||
case "y":
|
||||
jwk.Y = v.(string)
|
||||
case "n":
|
||||
jwk.N = v.(string)
|
||||
case "e":
|
||||
jwk.E = v.(string)
|
||||
}
|
||||
}
|
||||
return jwk, nil
|
||||
}
|
||||
|
||||
func GetCOSECurveName(curveID int64) (string, error) {
|
||||
switch curveID {
|
||||
case int64(webauthncose.P256):
|
||||
return "P-256", nil
|
||||
case int64(webauthncose.P384):
|
||||
return "P-384", nil
|
||||
case int64(webauthncose.P521):
|
||||
return "P-521", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown curve ID: %d", curveID)
|
||||
}
|
||||
}
|
||||
|
||||
func getOKPCurveName(curveID int64) (string, error) {
|
||||
switch curveID {
|
||||
case int64(webauthncose.Ed25519):
|
||||
return "Ed25519", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown OKP curve ID: %d", curveID)
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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 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 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 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 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/onsonr/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// ComputeAccountPublicKey computes the public key of a child key given the extended public key, chain code, and index.
|
||||
func computeBip32AccountPublicKey(extPubKey PublicKey, chainCode types.ChainCode, index int) (*types.PubKey, error) {
|
||||
// Check if the index is a hardened child key
|
||||
if chainCode&0x80000000 != 0 && index < 0 {
|
||||
return nil, errors.New("invalid index")
|
||||
}
|
||||
|
||||
// Serialize the public key
|
||||
pubKey, err := btcec.ParsePubKey(extPubKey.GetRaw())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKeyBytes := pubKey.SerializeCompressed()
|
||||
|
||||
// Serialize the index
|
||||
indexBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(indexBytes, uint32(index))
|
||||
|
||||
// Compute the HMAC-SHA512
|
||||
mac := hmac.New(sha512.New, []byte{byte(chainCode)})
|
||||
mac.Write(pubKeyBytes)
|
||||
mac.Write(indexBytes)
|
||||
I := mac.Sum(nil)
|
||||
|
||||
// Split I into two 32-byte sequences
|
||||
IL := I[:32]
|
||||
|
||||
// Convert IL to a big integer
|
||||
ilNum := new(big.Int).SetBytes(IL)
|
||||
|
||||
// Check if parse256(IL) >= n
|
||||
curve := btcec.S256()
|
||||
if ilNum.Cmp(curve.N) >= 0 {
|
||||
return nil, errors.New("invalid child key")
|
||||
}
|
||||
|
||||
// Compute the child public key
|
||||
ilx, ily := curve.ScalarBaseMult(IL)
|
||||
childX, childY := curve.Add(ilx, ily, pubKey.X(), pubKey.Y())
|
||||
lx := newBigIntFieldVal(childX)
|
||||
ly := newBigIntFieldVal(childY)
|
||||
|
||||
// Create the child public key
|
||||
childPubKey := btcec.NewPublicKey(lx, ly)
|
||||
pk, err := types.NewPublicKey(childPubKey.SerializeCompressed(), types.ChainCodeKeyInfos[chainCode])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// newBigIntFieldVal creates a new field value from a big integer.
|
||||
func newBigIntFieldVal(val *big.Int) *btcec.FieldVal {
|
||||
lx := new(btcec.FieldVal)
|
||||
lx.SetByteSlice(val.Bytes())
|
||||
return lx
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"github.com/onsonr/sonr/x/did/types"
|
||||
)
|
||||
|
||||
type (
|
||||
AuthenticatorAttachment string
|
||||
AuthenticatorTransport string
|
||||
)
|
||||
|
||||
const (
|
||||
// Platform represents a platform authenticator is attached using a client device-specific transport, called
|
||||
// platform attachment, and is usually not removable from the client device. A public key credential bound to a
|
||||
// platform authenticator is called a platform credential.
|
||||
Platform AuthenticatorAttachment = "platform"
|
||||
|
||||
// CrossPlatform represents a roaming authenticator is attached using cross-platform transports, called
|
||||
// cross-platform attachment. Authenticators of this class are removable from, and can "roam" among, client devices.
|
||||
// A public key credential bound to a roaming authenticator is called a roaming credential.
|
||||
CrossPlatform AuthenticatorAttachment = "cross-platform"
|
||||
)
|
||||
|
||||
func ParseAuthenticatorAttachment(s string) AuthenticatorAttachment {
|
||||
switch s {
|
||||
case "platform":
|
||||
return Platform
|
||||
default:
|
||||
return CrossPlatform
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// USB indicates the respective authenticator can be contacted over removable USB.
|
||||
USB AuthenticatorTransport = "usb"
|
||||
|
||||
// NFC indicates the respective authenticator can be contacted over Near Field Communication (NFC).
|
||||
NFC AuthenticatorTransport = "nfc"
|
||||
|
||||
// BLE indicates the respective authenticator can be contacted over Bluetooth Smart (Bluetooth Low Energy / BLE).
|
||||
BLE AuthenticatorTransport = "ble"
|
||||
|
||||
// SmartCard indicates the respective authenticator can be contacted over ISO/IEC 7816 smart card with contacts.
|
||||
//
|
||||
// WebAuthn Level 3.
|
||||
SmartCard AuthenticatorTransport = "smart-card"
|
||||
|
||||
// Hybrid indicates the respective authenticator can be contacted using a combination of (often separate)
|
||||
// data-transport and proximity mechanisms. This supports, for example, authentication on a desktop computer using
|
||||
// a smartphone.
|
||||
//
|
||||
// WebAuthn Level 3.
|
||||
Hybrid AuthenticatorTransport = "hybrid"
|
||||
|
||||
// Internal indicates the respective authenticator is contacted using a client device-specific transport, i.e., it
|
||||
// is a platform authenticator. These authenticators are not removable from the client device.
|
||||
Internal AuthenticatorTransport = "internal"
|
||||
)
|
||||
|
||||
func ParseAuthenticatorTransport(s string) AuthenticatorTransport {
|
||||
switch s {
|
||||
case "usb":
|
||||
return USB
|
||||
case "nfc":
|
||||
return NFC
|
||||
case "ble":
|
||||
return BLE
|
||||
case "smart-card":
|
||||
return SmartCard
|
||||
case "hybrid":
|
||||
return Hybrid
|
||||
default:
|
||||
return Internal
|
||||
}
|
||||
}
|
||||
|
||||
type AuthenticatorFlags byte
|
||||
|
||||
const (
|
||||
// FlagUserPresent Bit 00000001 in the byte sequence. Tells us if user is present. Also referred to as the UP flag.
|
||||
FlagUserPresent AuthenticatorFlags = 1 << iota // Referred to as UP
|
||||
|
||||
// FlagRFU1 is a reserved for future use flag.
|
||||
FlagRFU1
|
||||
|
||||
// FlagUserVerified Bit 00000100 in the byte sequence. Tells us if user is verified
|
||||
// by the authenticator using a biometric or PIN. Also referred to as the UV flag.
|
||||
FlagUserVerified
|
||||
|
||||
// FlagBackupEligible Bit 00001000 in the byte sequence. Tells us if a backup is eligible for device. Also referred
|
||||
// to as the BE flag.
|
||||
FlagBackupEligible // Referred to as BE
|
||||
|
||||
// FlagBackupState Bit 00010000 in the byte sequence. Tells us if a backup state for device. Also referred to as the
|
||||
// BS flag.
|
||||
FlagBackupState
|
||||
|
||||
// FlagRFU2 is a reserved for future use flag.
|
||||
FlagRFU2
|
||||
|
||||
// FlagAttestedCredentialData Bit 01000000 in the byte sequence. Indicates whether
|
||||
// the authenticator added attested credential data. Also referred to as the AT flag.
|
||||
FlagAttestedCredentialData
|
||||
|
||||
// FlagHasExtensions Bit 10000000 in the byte sequence. Indicates if the authenticator data has extensions. Also
|
||||
// referred to as the ED flag.
|
||||
FlagHasExtensions
|
||||
)
|
||||
|
||||
type AttestationFormat string
|
||||
|
||||
const (
|
||||
// AttestationFormatPacked is the "packed" attestation statement format is a WebAuthn-optimized format for
|
||||
// attestation. It uses a very compact but still extensible encoding method. This format is implementable by
|
||||
// authenticators with limited resources (e.g., secure elements).
|
||||
AttestationFormatPacked AttestationFormat = "packed"
|
||||
|
||||
// AttestationFormatTPM is the TPM attestation statement format returns an attestation statement in the same format
|
||||
// as the packed attestation statement format, although the rawData and signature fields are computed differently.
|
||||
AttestationFormatTPM AttestationFormat = "tpm"
|
||||
|
||||
// AttestationFormatAndroidKey is the attestation statement format for platform authenticators on versions "N", and
|
||||
// later, which may provide this proprietary "hardware attestation" statement.
|
||||
AttestationFormatAndroidKey AttestationFormat = "android-key"
|
||||
|
||||
// AttestationFormatAndroidSafetyNet is the attestation statement format that Android-based platform authenticators
|
||||
// MAY produce an attestation statement based on the Android SafetyNet API.
|
||||
AttestationFormatAndroidSafetyNet AttestationFormat = "android-safetynet"
|
||||
|
||||
// AttestationFormatFIDOUniversalSecondFactor is the attestation statement format that is used with FIDO U2F
|
||||
// authenticators.
|
||||
AttestationFormatFIDOUniversalSecondFactor AttestationFormat = "fido-u2f"
|
||||
|
||||
// AttestationFormatApple is the attestation statement format that is used with Apple devices' platform
|
||||
// authenticators.
|
||||
AttestationFormatApple AttestationFormat = "apple"
|
||||
|
||||
// AttestationFormatNone is the attestation statement format that is used to replace any authenticator-provided
|
||||
// attestation statement when a WebAuthn Relying Party indicates it does not wish to receive attestation information.
|
||||
AttestationFormatNone AttestationFormat = "none"
|
||||
)
|
||||
|
||||
func ExtractAttestationFormats(p *types.Params) []AttestationFormat {
|
||||
var formats []AttestationFormat
|
||||
for _, v := range p.AttestationFormats {
|
||||
formats = append(formats, parseAttestationFormat(v))
|
||||
}
|
||||
return formats
|
||||
}
|
||||
|
||||
func parseAttestationFormat(s string) AttestationFormat {
|
||||
switch s {
|
||||
case "packed":
|
||||
return AttestationFormatPacked
|
||||
case "tpm":
|
||||
return AttestationFormatTPM
|
||||
case "android-key":
|
||||
return AttestationFormatAndroidKey
|
||||
case "android-safetynet":
|
||||
return AttestationFormatAndroidSafetyNet
|
||||
case "fido-u2f":
|
||||
return AttestationFormatFIDOUniversalSecondFactor
|
||||
case "apple":
|
||||
return AttestationFormatApple
|
||||
case "none":
|
||||
return AttestationFormatNone
|
||||
default:
|
||||
return AttestationFormatPacked
|
||||
}
|
||||
}
|
||||
|
||||
type CredentialType string
|
||||
|
||||
const (
|
||||
CredentialTypePublicKeyCredential CredentialType = "public-key"
|
||||
)
|
||||
|
||||
type ConveyancePreference string
|
||||
|
||||
const (
|
||||
// PreferNoAttestation is a ConveyancePreference value.
|
||||
//
|
||||
// This value indicates that the Relying Party is not interested in authenticator attestation. For example, in order
|
||||
// to potentially avoid having to obtain user consent to relay identifying information to the Relying Party, or to
|
||||
// save a round trip to an Attestation CA or Anonymization CA.
|
||||
//
|
||||
// This is the default value.
|
||||
//
|
||||
// Specification: §5.4.7. Attestation Conveyance Preference Enumeration (https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-none)
|
||||
PreferNoAttestation ConveyancePreference = "none"
|
||||
|
||||
// PreferIndirectAttestation is a ConveyancePreference value.
|
||||
//
|
||||
// This value indicates that the Relying Party prefers an attestation conveyance yielding verifiable attestation
|
||||
// statements, but allows the client to decide how to obtain such attestation statements. The client MAY replace the
|
||||
// authenticator-generated attestation statements with attestation statements generated by an Anonymization CA, in
|
||||
// order to protect the user’s privacy, or to assist Relying Parties with attestation verification in a
|
||||
// heterogeneous ecosystem.
|
||||
//
|
||||
// Note: There is no guarantee that the Relying Party will obtain a verifiable attestation statement in this case.
|
||||
// For example, in the case that the authenticator employs self attestation.
|
||||
//
|
||||
// Specification: §5.4.7. Attestation Conveyance Preference Enumeration (https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-indirect)
|
||||
PreferIndirectAttestation ConveyancePreference = "indirect"
|
||||
|
||||
// PreferDirectAttestation is a ConveyancePreference value.
|
||||
//
|
||||
// This value indicates that the Relying Party wants to receive the attestation statement as generated by the
|
||||
// authenticator.
|
||||
//
|
||||
// Specification: §5.4.7. Attestation Conveyance Preference Enumeration (https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-direct)
|
||||
PreferDirectAttestation ConveyancePreference = "direct"
|
||||
|
||||
// PreferEnterpriseAttestation is a ConveyancePreference value.
|
||||
//
|
||||
// This value indicates that the Relying Party wants to receive an attestation statement that may include uniquely
|
||||
// identifying information. This is intended for controlled deployments within an enterprise where the organization
|
||||
// wishes to tie registrations to specific authenticators. User agents MUST NOT provide such an attestation unless
|
||||
// the user agent or authenticator configuration permits it for the requested RP ID.
|
||||
//
|
||||
// If permitted, the user agent SHOULD signal to the authenticator (at invocation time) that enterprise
|
||||
// attestation is requested, and convey the resulting AAGUID and attestation statement, unaltered, to the Relying
|
||||
// Party.
|
||||
//
|
||||
// Specification: §5.4.7. Attestation Conveyance Preference Enumeration (https://www.w3.org/TR/webauthn/#dom-attestationconveyancepreference-enterprise)
|
||||
PreferEnterpriseAttestation ConveyancePreference = "enterprise"
|
||||
)
|
||||
|
||||
func ExtractConveyancePreference(p *types.Params) ConveyancePreference {
|
||||
switch p.ConveyancePreference {
|
||||
case "none":
|
||||
return PreferNoAttestation
|
||||
case "indirect":
|
||||
return PreferIndirectAttestation
|
||||
case "direct":
|
||||
return PreferDirectAttestation
|
||||
case "enterprise":
|
||||
return PreferEnterpriseAttestation
|
||||
default:
|
||||
return PreferNoAttestation
|
||||
}
|
||||
}
|
||||
|
||||
type PublicKeyCredentialHints string
|
||||
|
||||
const (
|
||||
// PublicKeyCredentialHintSecurityKey is a PublicKeyCredentialHint that indicates that the Relying Party believes
|
||||
// that users will satisfy this request with a physical security key. For example, an enterprise Relying Party may
|
||||
// set this hint if they have issued security keys to their employees and will only accept those authenticators for
|
||||
// registration and authentication.
|
||||
//
|
||||
// For compatibility with older user agents, when this hint is used in PublicKeyCredentialCreationOptions, the
|
||||
// authenticatorAttachment SHOULD be set to cross-platform.
|
||||
PublicKeyCredentialHintSecurityKey PublicKeyCredentialHints = "security-key"
|
||||
|
||||
// PublicKeyCredentialHintClientDevice is a PublicKeyCredentialHint that indicates that the Relying Party believes
|
||||
// that users will satisfy this request with a platform authenticator attached to the client device.
|
||||
//
|
||||
// For compatibility with older user agents, when this hint is used in PublicKeyCredentialCreationOptions, the
|
||||
// authenticatorAttachment SHOULD be set to platform.
|
||||
PublicKeyCredentialHintClientDevice PublicKeyCredentialHints = "client-device"
|
||||
|
||||
// PublicKeyCredentialHintHybrid is a PublicKeyCredentialHint that indicates that the Relying Party believes that
|
||||
// users will satisfy this request with general-purpose authenticators such as smartphones. For example, a consumer
|
||||
// Relying Party may believe that only a small fraction of their customers possesses dedicated security keys. This
|
||||
// option also implies that the local platform authenticator should not be promoted in the UI.
|
||||
//
|
||||
// For compatibility with older user agents, when this hint is used in PublicKeyCredentialCreationOptions, the
|
||||
// authenticatorAttachment SHOULD be set to cross-platform.
|
||||
PublicKeyCredentialHintHybrid PublicKeyCredentialHints = "hybrid"
|
||||
)
|
||||
|
||||
func ParsePublicKeyCredentialHints(s string) PublicKeyCredentialHints {
|
||||
switch s {
|
||||
case "security-key":
|
||||
return PublicKeyCredentialHintSecurityKey
|
||||
case "client-device":
|
||||
return PublicKeyCredentialHintClientDevice
|
||||
case "hybrid":
|
||||
return PublicKeyCredentialHintHybrid
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type AttestedCredentialData struct {
|
||||
AAGUID []byte `json:"aaguid"`
|
||||
CredentialID []byte `json:"credential_id"`
|
||||
|
||||
// The raw credential public key bytes received from the attestation data.
|
||||
CredentialPublicKey []byte `json:"public_key"`
|
||||
}
|
||||
|
||||
type ResidentKeyRequirement string
|
||||
|
||||
const (
|
||||
// ResidentKeyRequirementDiscouraged indicates the Relying Party prefers creating a server-side credential, but will
|
||||
// accept a client-side discoverable credential. This is the default.
|
||||
ResidentKeyRequirementDiscouraged ResidentKeyRequirement = "discouraged"
|
||||
|
||||
// ResidentKeyRequirementPreferred indicates to the client we would prefer a discoverable credential.
|
||||
ResidentKeyRequirementPreferred ResidentKeyRequirement = "preferred"
|
||||
|
||||
// ResidentKeyRequirementRequired indicates the Relying Party requires a client-side discoverable credential, and is
|
||||
// prepared to receive an error if a client-side discoverable credential cannot be created.
|
||||
ResidentKeyRequirementRequired ResidentKeyRequirement = "required"
|
||||
)
|
||||
|
||||
func ParseResidentKeyRequirement(s string) ResidentKeyRequirement {
|
||||
switch s {
|
||||
case "discouraged":
|
||||
return ResidentKeyRequirementDiscouraged
|
||||
case "preferred":
|
||||
return ResidentKeyRequirementPreferred
|
||||
default:
|
||||
return ResidentKeyRequirementRequired
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
AuthenticationExtensions map[string]any
|
||||
UserVerificationRequirement string
|
||||
)
|
||||
|
||||
const (
|
||||
// VerificationRequired User verification is required to create/release a credential
|
||||
VerificationRequired UserVerificationRequirement = "required"
|
||||
|
||||
// VerificationPreferred User verification is preferred to create/release a credential
|
||||
VerificationPreferred UserVerificationRequirement = "preferred" // This is the default
|
||||
|
||||
// VerificationDiscouraged The authenticator should not verify the user for the credential
|
||||
VerificationDiscouraged UserVerificationRequirement = "discouraged"
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/onsonr/sonr/x/did/types"
|
||||
)
|
||||
|
||||
type AuthenticatorResponse struct {
|
||||
// From the spec https://www.w3.org/TR/webauthn/#dom-authenticatorresponse-clientdatajson
|
||||
// This attribute contains a JSON serialization of the client data passed to the authenticator
|
||||
// by the client in its call to either create() or get().
|
||||
ClientDataJSON URLEncodedBase64 `json:"clientDataJSON"`
|
||||
}
|
||||
|
||||
type AuthenticatorAttestationResponse struct {
|
||||
// The byte slice of clientDataJSON, which becomes CollectedClientData
|
||||
AuthenticatorResponse
|
||||
|
||||
Transports []string `json:"transports,omitempty"`
|
||||
|
||||
AuthenticatorData URLEncodedBase64 `json:"authenticatorData"`
|
||||
|
||||
PublicKey URLEncodedBase64 `json:"publicKey"`
|
||||
|
||||
PublicKeyAlgorithm int64 `json:"publicKeyAlgorithm"`
|
||||
|
||||
// AttestationObject is the byte slice version of attestationObject.
|
||||
// This attribute contains an attestation object, which is opaque to, and
|
||||
// cryptographically protected against tampering by, the client. The
|
||||
// attestation object contains both authenticator data and an attestation
|
||||
// statement. The former contains the AAGUID, a unique credential ID, and
|
||||
// the credential public key. The contents of the attestation statement are
|
||||
// determined by the attestation statement format used by the authenticator.
|
||||
// It also contains any additional information that the Relying Party's server
|
||||
// requires to validate the attestation statement, as well as to decode and
|
||||
// validate the authenticator data along with the JSON-serialized client data.
|
||||
AttestationObject URLEncodedBase64 `json:"attestationObject"`
|
||||
}
|
||||
|
||||
type PublicKeyCredentialCreationOptions struct {
|
||||
RelyingParty RelyingPartyEntity `json:"rp"`
|
||||
User UserEntity `json:"user"`
|
||||
Challenge URLEncodedBase64 `json:"challenge"`
|
||||
Parameters []CredentialParameter `json:"pubKeyCredParams,omitempty"`
|
||||
Timeout int `json:"timeout,omitempty"`
|
||||
CredentialExcludeList []CredentialDescriptor `json:"excludeCredentials,omitempty"`
|
||||
AuthenticatorSelection AuthenticatorSelection `json:"authenticatorSelection,omitempty"`
|
||||
Hints []PublicKeyCredentialHints `json:"hints,omitempty"`
|
||||
Attestation ConveyancePreference `json:"attestation,omitempty"`
|
||||
AttestationFormats []AttestationFormat `json:"attestationFormats,omitempty"`
|
||||
Extensions AuthenticationExtensions `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
func NewRegistrationOptions(origin string, subject string, vaultCID string, params *types.Params) (*PublicKeyCredentialCreationOptions, error) {
|
||||
chal, err := CreateChallenge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PublicKeyCredentialCreationOptions{
|
||||
RelyingParty: NewRelayingParty(origin, subject),
|
||||
User: NewUserEntity(subject, subject, vaultCID),
|
||||
Parameters: ExtractCredentialParameters(params),
|
||||
Timeout: 20,
|
||||
CredentialExcludeList: nil,
|
||||
Challenge: chal,
|
||||
AuthenticatorSelection: AuthenticatorSelection{},
|
||||
Hints: nil,
|
||||
Attestation: ExtractConveyancePreference(params),
|
||||
AttestationFormats: ExtractAttestationFormats(params),
|
||||
Extensions: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UnmarshalAuthenticatorResponse(data []byte) (*AuthenticatorResponse, error) {
|
||||
var ar AuthenticatorResponse
|
||||
err := json.Unmarshal(data, &ar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ar, nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/onsonr/sonr/x/did/types"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol/webauthncose"
|
||||
)
|
||||
|
||||
// PublicKey is an interface for a public key
|
||||
type PublicKey interface {
|
||||
cryptotypes.PubKey
|
||||
Clone() cryptotypes.PubKey
|
||||
GetRaw() []byte
|
||||
GetRole() types.KeyRole
|
||||
GetAlgorithm() types.KeyAlgorithm
|
||||
GetEncoding() types.KeyEncoding
|
||||
GetCurve() types.KeyCurve
|
||||
GetKeyType() types.KeyType
|
||||
}
|
||||
|
||||
// CreateAuthnVerification creates a new verification method for an authn method
|
||||
func CreateAuthnVerification(namespace types.DIDNamespace, issuer string, controller string, pubkey *types.PubKey, identifier string) *types.VerificationMethod {
|
||||
return &types.VerificationMethod{
|
||||
Method: namespace,
|
||||
Controller: controller,
|
||||
PublicKey: pubkey,
|
||||
Id: identifier,
|
||||
Issuer: issuer,
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
Controller: controller,
|
||||
PublicKey: pubkey,
|
||||
Id: identifier,
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractWebAuthnPublicKey parses the raw public key bytes and returns a JWK representation
|
||||
func ExtractWebAuthnPublicKey(keyBytes []byte) (*types.JWK, error) {
|
||||
key, err := webauthncose.ParsePublicKey(keyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
switch k := key.(type) {
|
||||
case *webauthncose.EC2PublicKeyData:
|
||||
return FormatEC2PublicKey(k)
|
||||
case *webauthncose.RSAPublicKeyData:
|
||||
return FormatRSAPublicKey(k)
|
||||
case *webauthncose.OKPPublicKeyData:
|
||||
return FormatOKPPublicKey(k)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
for method, chain := range types.InitialChainCodes {
|
||||
nk, err := computeBip32AccountPublicKey(pubkey, chain, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := chain.FormatAddress(nk)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
verificationMethods = append(verificationMethods, CreateWalletVerification(method, controller, nk, method.FormatDID(addr)))
|
||||
}
|
||||
return verificationMethods, nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/onsonr/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// ChallengeLength - Length of bytes to generate for a challenge.
|
||||
const ChallengeLength = 32
|
||||
|
||||
// CreateChallenge creates a new challenge that should be signed and returned by the authenticator. The spec recommends
|
||||
// using at least 16 bytes with 100 bits of entropy. We use 32 bytes.
|
||||
func CreateChallenge() (challenge URLEncodedBase64, err error) {
|
||||
challenge = make([]byte, ChallengeLength)
|
||||
|
||||
if _, err = rand.Read(challenge); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return challenge, nil
|
||||
}
|
||||
|
||||
type CredentialEntity struct {
|
||||
// A human-palatable name for the entity. Its function depends on what the PublicKeyCredentialEntity represents:
|
||||
//
|
||||
// When inherited by PublicKeyCredentialRpEntity it is a human-palatable identifier for the Relying Party,
|
||||
// intended only for display. For example, "ACME Corporation", "Wonderful Widgets, Inc." or "ОАО Примертех".
|
||||
//
|
||||
// When inherited by PublicKeyCredentialUserEntity, it is a human-palatable identifier for a user account. It is
|
||||
// intended only for display, i.e., aiding the user in determining the difference between user accounts with similar
|
||||
// displayNames. For example, "alexm", "alex.p.mueller@example.com" or "+14255551234".
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func NewCredentialEntity(name string) CredentialEntity {
|
||||
return CredentialEntity{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
type CredentialParameter struct {
|
||||
Type CredentialType `json:"type"`
|
||||
Algorithm types.COSEAlgorithmIdentifier `json:"alg"`
|
||||
}
|
||||
|
||||
func NewCredentialParameter(ki *types.KeyInfo) CredentialParameter {
|
||||
return CredentialParameter{
|
||||
Type: CredentialTypePublicKeyCredential,
|
||||
Algorithm: ki.Algorithm.CoseIdentifier(),
|
||||
}
|
||||
}
|
||||
|
||||
func ExtractCredentialParameters(p *types.Params) []CredentialParameter {
|
||||
var keys []*types.KeyInfo
|
||||
for _, v := range p.AllowedPublicKeys {
|
||||
if v.Role == types.KeyRole_KEY_ROLE_AUTHENTICATION {
|
||||
keys = append(keys, v)
|
||||
}
|
||||
}
|
||||
var cparams []CredentialParameter
|
||||
for _, ki := range keys {
|
||||
cparams = append(cparams, NewCredentialParameter(ki))
|
||||
}
|
||||
return cparams
|
||||
}
|
||||
|
||||
type RelyingPartyEntity struct {
|
||||
CredentialEntity
|
||||
|
||||
// A unique identifier for the Relying Party entity, which sets the RP ID.
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func NewRelayingParty(name string, origin string) RelyingPartyEntity {
|
||||
return RelyingPartyEntity{
|
||||
CredentialEntity: NewCredentialEntity(origin),
|
||||
ID: origin,
|
||||
}
|
||||
}
|
||||
|
||||
type UserEntity struct {
|
||||
CredentialEntity
|
||||
// A human-palatable name for the user account, intended only for display.
|
||||
// For example, "Alex P. Müller" or "田中 倫". The Relying Party SHOULD let
|
||||
// the user choose this, and SHOULD NOT restrict the choice more than necessary.
|
||||
DisplayName string `json:"displayName"`
|
||||
|
||||
// ID is the user handle of the user account entity. To ensure secure operation,
|
||||
// authentication and authorization decisions MUST be made on the basis of this id
|
||||
// member, not the displayName nor name members. See Section 6.1 of
|
||||
// [RFC8266](https://www.w3.org/TR/webauthn/#biblio-rfc8266).
|
||||
ID any `json:"id"`
|
||||
}
|
||||
|
||||
func NewUserEntity(name string, subject string, cid string) UserEntity {
|
||||
return UserEntity{
|
||||
CredentialEntity: NewCredentialEntity(name),
|
||||
DisplayName: subject,
|
||||
ID: cid,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type CredentialDescriptor struct {
|
||||
// The valid credential types.
|
||||
Type CredentialType `json:"type"`
|
||||
|
||||
// CredentialID The ID of a credential to allow/disallow.
|
||||
CredentialID URLEncodedBase64 `json:"id"`
|
||||
|
||||
// The authenticator transports that can be used.
|
||||
Transport []AuthenticatorTransport `json:"transports,omitempty"`
|
||||
|
||||
// The AttestationType from the Credential. Used internally only.
|
||||
AttestationType string `json:"-"`
|
||||
}
|
||||
|
||||
func NewCredentialDescriptor(credentialID string, transports []AuthenticatorTransport, attestationType string) *CredentialDescriptor {
|
||||
return &CredentialDescriptor{
|
||||
CredentialID: URLEncodedBase64(credentialID),
|
||||
Transport: transports,
|
||||
AttestationType: attestationType,
|
||||
Type: CredentialTypePublicKeyCredential,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthenticatorSelection struct {
|
||||
// AuthenticatorAttachment If this member is present, eligible authenticators are filtered to only
|
||||
// authenticators attached with the specified AuthenticatorAttachment enum.
|
||||
AuthenticatorAttachment AuthenticatorAttachment `json:"authenticatorAttachment,omitempty"`
|
||||
|
||||
// RequireResidentKey this member describes the Relying Party's requirements regarding resident
|
||||
// credentials. If the parameter is set to true, the authenticator MUST create a client-side-resident
|
||||
// public key credential source when creating a public key credential.
|
||||
RequireResidentKey *bool `json:"requireResidentKey,omitempty"`
|
||||
|
||||
// ResidentKey this member describes the Relying Party's requirements regarding resident
|
||||
// credentials per Webauthn Level 2.
|
||||
ResidentKey ResidentKeyRequirement `json:"residentKey,omitempty"`
|
||||
|
||||
// UserVerification This member describes the Relying Party's requirements regarding user verification for
|
||||
// the create() operation. Eligible authenticators are filtered to only those capable of satisfying this
|
||||
// requirement.
|
||||
UserVerification UserVerificationRequirement `json:"userVerification,omitempty"`
|
||||
}
|
||||
|
||||
type AuthenticatorData struct {
|
||||
RPIDHash []byte `json:"rpid"`
|
||||
Flags AuthenticatorFlags `json:"flags"`
|
||||
Counter uint32 `json:"sign_count"`
|
||||
AttData AttestedCredentialData `json:"att_data"`
|
||||
ExtData []byte `json:"ext_data"`
|
||||
}
|
||||
|
||||
type AttestationObject struct {
|
||||
// The authenticator data, including the newly created public key. See AuthenticatorData for more info
|
||||
AuthData AuthenticatorData
|
||||
|
||||
// The byteform version of the authenticator data, used in part for signature validation
|
||||
RawAuthData []byte `json:"authData"`
|
||||
|
||||
// The format of the Attestation data.
|
||||
Format string `json:"fmt"`
|
||||
|
||||
// The attestation statement data sent back if attestation is requested.
|
||||
AttStatement map[string]any `json:"attStmt,omitempty"`
|
||||
}
|
||||
|
||||
type URLEncodedBase64 []byte
|
||||
|
||||
func (e URLEncodedBase64) String() string {
|
||||
return base64.RawURLEncoding.EncodeToString(e)
|
||||
}
|
||||
|
||||
// UnmarshalJSON base64 decodes a URL-encoded value, storing the result in the
|
||||
// provided byte slice.
|
||||
func (e *URLEncodedBase64) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, []byte("null")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Trim the leading spaces.
|
||||
data = bytes.Trim(data, "\"")
|
||||
|
||||
// Trim the trailing equal characters.
|
||||
data = bytes.TrimRight(data, "=")
|
||||
|
||||
out := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
|
||||
|
||||
n, err := base64.RawURLEncoding.Decode(out, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(e).Elem()
|
||||
v.SetBytes(out[:n])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON base64 encodes a non URL-encoded value, storing the result in the
|
||||
// provided byte slice.
|
||||
func (e URLEncodedBase64) MarshalJSON() ([]byte, error) {
|
||||
if e == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
return []byte(`"` + base64.RawURLEncoding.EncodeToString(e) + `"`), nil
|
||||
}
|
||||
Reference in New Issue
Block a user