Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions clients/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@ Trust comes only from verifier-local inputs:
- a local Key Registry V2 verifier descriptor when lifecycle evidence is
required;
- local client and server CA roots when certificate chains are used;
- a local canonical FISCO BCOS `TrustConfig` when the evidence contains a
FISCO BCOS anchor;
- the built-in, offline-verifiable anchor formats implemented by the SDK.

Descriptors, certificates, CRLs, validator sets, or checkpoints embedded in
the evidence file never become trust roots by carrying themselves. Missing or
mismatched local trust fails closed. The desktop client has no configurable
anchor policy or provider trust-root input: custom/provider-backed anchors and
the local-only `file`/`noop` sinks fail the anchor stage and cannot receive L5.
Selecting “skip anchor” explicitly limits verification to the available L1-L4
evidence instead.
mismatched local trust fails closed. The FISCO BCOS TrustConfig is read from a
local canonical CBOR file selected in Settings; it is used only to check the
chain, contract, validator set, and checkpoint carried by the evidence. The
desktop client never connects to a FISCO BCOS endpoint during verification.
Custom/provider-backed anchors and the local-only `file`/`noop` sinks fail the
anchor stage and cannot receive L5. Selecting “skip anchor” explicitly limits
verification to the available L1-L4 evidence instead.

## Development

Expand Down
6 changes: 6 additions & 0 deletions clients/desktop/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,14 @@ func (a *App) SaveSettings(s Settings) error {
s.ClientVerifierDescriptor = strings.TrimSpace(s.ClientVerifierDescriptor)
s.ServerVerifierDescriptor = strings.TrimSpace(s.ServerVerifierDescriptor)
s.RegistryVerifierDescriptor = strings.TrimSpace(s.RegistryVerifierDescriptor)
s.FISCOBCOSTrustConfigFile = strings.TrimSpace(s.FISCOBCOSTrustConfigFile)
s.ClientCertificateRoots = strings.TrimSpace(s.ClientCertificateRoots)
s.ServerCertificateRoots = strings.TrimSpace(s.ServerCertificateRoots)
if s.FISCOBCOSTrustConfigFile != "" {
if _, err := readDesktopFISCOBCOSTrustConfig(s.FISCOBCOSTrustConfigFile); err != nil {
return fmt.Errorf("FISCO BCOS trust config: %w", err)
}
}
if (s.ClientTLSCertFile == "") != (s.ClientTLSKeyFile == "") {
return errors.New("client TLS certificate and key must be configured together")
}
Expand Down
16 changes: 15 additions & 1 deletion clients/desktop/frontend/src/pages/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Button from '@/components/Button.vue'
import Input from '@/components/Input.vue'
import Field from '@/components/Field.vue'
import StatusDot from '@/components/StatusDot.vue'
import { Save, RotateCcw, PlugZap, Sparkles } from 'lucide-vue-next'
import { Save, RotateCcw, PlugZap, Sparkles, FolderOpen } from 'lucide-vue-next'

const settings = useSettings()
const toasts = useToasts()
Expand Down Expand Up @@ -36,6 +36,7 @@ const dirty = computed(() => {
form.client_verifier_descriptor !== settings.settings.client_verifier_descriptor ||
form.server_verifier_descriptor !== settings.settings.server_verifier_descriptor ||
form.registry_verifier_descriptor !== settings.settings.registry_verifier_descriptor ||
form.fisco_bcos_trust_config_file !== settings.settings.fisco_bcos_trust_config_file ||
form.client_certificate_roots !== settings.settings.client_certificate_roots ||
form.server_certificate_roots !== settings.settings.server_certificate_roots ||
form.require_identity_evidence !== settings.settings.require_identity_evidence ||
Expand All @@ -62,6 +63,11 @@ function revert() {
Object.assign(form, settings.settings)
}

async function pickFISCOBCOSTrustConfig() {
const path = await api.chooseOpenPath('选择 FISCO BCOS TrustConfig')
if (path) form.fisco_bcos_trust_config_file = path
}

// ----- Ping test (uses the NEW url from the form, not the saved one) -----
const pingLoading = ref(false)
const pingResult = ref<HealthStatus | null>(null)
Expand Down Expand Up @@ -212,6 +218,14 @@ function reopenOnboarding() {
<Field label="V2 registry verifier descriptor" hint="验证 key lifecycle evidence 的本地 registry 信任键">
<Input v-model="form.registry_verifier_descriptor" :mono="true" />
</Field>
<div class="sm:col-span-2">
<Field label="FISCO BCOS 锚定 TrustConfig" hint="canonical CBOR;只在本机验证链、合约、验证者和 checkpoint,不会连接 FISCO BCOS">
<div class="flex gap-2">
<Input v-model="form.fisco_bcos_trust_config_file" :mono="true" placeholder="/path/fisco-bcos-trust-config.cbor" />
<Button size="sm" variant="subtle" @click="pickFISCOBCOSTrustConfig"><FolderOpen :size="13" /></Button>
</div>
</Field>
</div>
<Field label="客户端 CA 根" hint="每行一个本地 PEM/DER 根文件">
<Input v-model="form.client_certificate_roots" multiline :rows="2" :mono="true" />
</Field>
Expand Down
9 changes: 7 additions & 2 deletions clients/desktop/frontend/src/pages/Verify.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { bytesToHex, formatTime, humanSize, nanoToDate } from '@/lib/format'
const settings = useSettings()
const toasts = useToasts()
type Mode = 'local' | 'remote'
type Picker = 'file' | 'single' | 'proof' | 'global' | 'anchor' | 'clientDesc' | 'serverDesc' | 'registry' | 'clientRoot' | 'serverRoot'
type Picker = 'file' | 'single' | 'proof' | 'global' | 'anchor' | 'clientDesc' | 'serverDesc' | 'registry' | 'clientRoot' | 'serverRoot' | 'fiscoConfig'

const mode = ref<Mode>('local')
const filePath = ref('')
Expand All @@ -35,6 +35,7 @@ const serverDescriptors = ref('')
const registryDescriptor = ref('')
const clientRoots = ref('')
const serverRoots = ref('')
const fiscoConfigPath = ref('')
const requireEvidence = ref(false)
const requireCertificateStatus = ref(false)
const showTrustOverrides = ref(false)
Expand All @@ -54,6 +55,7 @@ async function pick(target: Picker) {
registry: '选择 registry verifier descriptor',
clientRoot: '选择客户端本地 CA 根',
serverRoot: '选择服务器本地 CA 根',
fiscoConfig: '选择 FISCO BCOS TrustConfig',
}
const path = await api.chooseOpenPath(titles[target])
if (!path) return
Expand All @@ -67,6 +69,7 @@ async function pick(target: Picker) {
if (target === 'registry') registryDescriptor.value = path
if (target === 'clientRoot') clientRoots.value = path
if (target === 'serverRoot') serverRoots.value = path
if (target === 'fiscoConfig') fiscoConfigPath.value = path
}

const canVerify = computed(() =>
Expand Down Expand Up @@ -94,6 +97,7 @@ async function verify() {
registry_verifier_descriptor: registryDescriptor.value || undefined,
client_certificate_roots: clientRoots.value || undefined,
server_certificate_roots: serverRoots.value || undefined,
fisco_bcos_trust_config_file: fiscoConfigPath.value || undefined,
require_identity_evidence: requireEvidence.value,
require_certificate_status: requireCertificateStatus.value,
}
Expand Down Expand Up @@ -188,14 +192,15 @@ function stageClass(status: string) {
</button>
<div v-if="showTrustOverrides" class="mt-3 rounded-xl border border-white/10 bg-black/15 p-4 space-y-3">
<p class="text-[11.5px] text-warn">
留空使用 Settings 中的 verifier-local trust。下列路径只从本机读取;证据文件自身携带的证书不会填充这些字段。
留空使用 Settings 中的 verifier-local trust。下列路径只从本机读取;证据文件自身携带的证书不会填充这些字段。FISCO BCOS TrustConfig 只用于本机复算,不会连接链节点。
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<Field label="客户端 verifier descriptors"><div class="flex gap-2"><Input v-model="clientDescriptors" multiline :rows="2" /><Button size="sm" variant="subtle" @click="pick('clientDesc')"><FolderOpen :size="13" /></Button></div></Field>
<Field label="服务器 verifier descriptors"><div class="flex gap-2"><Input v-model="serverDescriptors" multiline :rows="2" /><Button size="sm" variant="subtle" @click="pick('serverDesc')"><FolderOpen :size="13" /></Button></div></Field>
<Field label="Registry verifier descriptor"><div class="flex gap-2"><Input v-model="registryDescriptor" /><Button size="sm" variant="subtle" @click="pick('registry')"><FolderOpen :size="13" /></Button></div></Field>
<Field label="客户端 CA 根"><div class="flex gap-2"><Input v-model="clientRoots" multiline :rows="2" /><Button size="sm" variant="subtle" @click="pick('clientRoot')"><FolderOpen :size="13" /></Button></div></Field>
<Field label="服务器 CA 根"><div class="flex gap-2"><Input v-model="serverRoots" multiline :rows="2" /><Button size="sm" variant="subtle" @click="pick('serverRoot')"><FolderOpen :size="13" /></Button></div></Field>
<Field label="FISCO BCOS TrustConfig" hint="canonical CBOR;用于链、合约、验证者和 checkpoint 校验"><div class="flex gap-2"><Input v-model="fiscoConfigPath" :mono="true" placeholder="/path/fisco-bcos-trust-config.cbor" /><Button size="sm" variant="subtle" @click="pick('fiscoConfig')"><FolderOpen :size="13" /></Button></div></Field>
<Field label="原始客户端公钥(单钥本地信任)"><Input v-model="clientPub" :mono="true" /></Field>
<Field label="原始服务器公钥(单钥本地信任)"><Input v-model="serverPub" :mono="true" /></Field>
</div>
Expand Down
1 change: 1 addition & 0 deletions clients/desktop/frontend/src/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const defaultSettings: Settings = {
client_verifier_descriptor: '',
server_verifier_descriptor: '',
registry_verifier_descriptor: '',
fisco_bcos_trust_config_file: '',
client_certificate_roots: '',
server_certificate_roots: '',
require_identity_evidence: false,
Expand Down
4 changes: 4 additions & 0 deletions clients/desktop/frontend/wailsjs/go/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ export namespace main {
client_verifier_descriptor: string;
server_verifier_descriptor: string;
registry_verifier_descriptor: string;
fisco_bcos_trust_config_file: string;
client_certificate_roots: string;
server_certificate_roots: string;
require_identity_evidence: boolean;
Expand Down Expand Up @@ -610,6 +611,7 @@ export namespace main {
this.client_verifier_descriptor = source["client_verifier_descriptor"];
this.server_verifier_descriptor = source["server_verifier_descriptor"];
this.registry_verifier_descriptor = source["registry_verifier_descriptor"];
this.fisco_bcos_trust_config_file = source["fisco_bcos_trust_config_file"];
this.client_certificate_roots = source["client_certificate_roots"];
this.server_certificate_roots = source["server_certificate_roots"];
this.require_identity_evidence = source["require_identity_evidence"];
Expand Down Expand Up @@ -671,6 +673,7 @@ export namespace main {
registry_verifier_descriptor?: string;
client_certificate_roots?: string;
server_certificate_roots?: string;
fisco_bcos_trust_config_file?: string;
require_identity_evidence?: boolean;
require_certificate_status?: boolean;

Expand All @@ -696,6 +699,7 @@ export namespace main {
this.registry_verifier_descriptor = source["registry_verifier_descriptor"];
this.client_certificate_roots = source["client_certificate_roots"];
this.server_certificate_roots = source["server_certificate_roots"];
this.fisco_bcos_trust_config_file = source["fisco_bcos_trust_config_file"];
this.require_identity_evidence = source["require_identity_evidence"];
this.require_certificate_status = source["require_certificate_status"];
}
Expand Down
2 changes: 1 addition & 1 deletion clients/desktop/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.26.5

require (
github.com/cockroachdb/pebble v1.1.5
github.com/ethereum/go-ethereum v1.17.0
github.com/wailsapp/wails/v2 v2.12.0
github.com/wowtrust/trustdb/v2 v2.0.0-00010101000000-000000000000
)
Expand Down Expand Up @@ -33,7 +34,6 @@ require (
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/emmansun/gmsm v0.44.0 // indirect
github.com/ethereum/go-ethereum v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions clients/desktop/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Settings struct {
ClientVerifierDescriptor string `json:"client_verifier_descriptor"`
ServerVerifierDescriptor string `json:"server_verifier_descriptor"`
RegistryVerifierDescriptor string `json:"registry_verifier_descriptor"`
FISCOBCOSTrustConfigFile string `json:"fisco_bcos_trust_config_file"`
ClientCertificateRoots string `json:"client_certificate_roots"`
ServerCertificateRoots string `json:"server_certificate_roots"`
RequireIdentityEvidence bool `json:"require_identity_evidence"`
Expand Down
50 changes: 49 additions & 1 deletion clients/desktop/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/wowtrust/trustdb/v2/internal/anchor/fiscobcos"
"github.com/wowtrust/trustdb/v2/internal/cborx"
"github.com/wowtrust/trustdb/v2/internal/cryptosuite"
"github.com/wowtrust/trustdb/v2/internal/keydescriptor"
Expand Down Expand Up @@ -43,6 +44,7 @@ type VerifyRequest struct {
RegistryVerifierDescriptor string `json:"registry_verifier_descriptor,omitempty"`
ClientCertificateRoots string `json:"client_certificate_roots,omitempty"`
ServerCertificateRoots string `json:"server_certificate_roots,omitempty"`
FISCOBCOSTrustConfigFile string `json:"fisco_bcos_trust_config_file,omitempty"`
RequireIdentityEvidence bool `json:"require_identity_evidence,omitempty"`
RequireCertificateStatus bool `json:"require_certificate_status,omitempty"`
}
Expand Down Expand Up @@ -226,6 +228,10 @@ func (a *App) desktopOfflineTrust(
if strings.TrimSpace(serverRootPaths) == "" {
serverRootPaths = settings.ServerCertificateRoots
}
fiscoConfigPath := req.FISCOBCOSTrustConfigFile
if strings.TrimSpace(fiscoConfigPath) == "" {
fiscoConfigPath = settings.FISCOBCOSTrustConfigFile
}

var clientKeys []sdk.KeyDescriptor
switch {
Expand Down Expand Up @@ -339,6 +345,14 @@ func (a *App) desktopOfflineTrust(
}
registryKey = keys[0]
}
var fiscoConfig *sdk.FISCOBCOSTrustConfig
if strings.TrimSpace(fiscoConfigPath) != "" {
config, configErr := readDesktopFISCOBCOSTrustConfig(fiscoConfigPath)
if configErr != nil {
return sdk.OfflineTrust{}, 0, fmt.Errorf("FISCO BCOS trust config: %w", configErr)
}
fiscoConfig = config
}
return sdk.OfflineTrust{
Proof: proofKeys,
Identity: sdk.OfflineIdentityTrust{
Expand All @@ -350,7 +364,41 @@ func (a *App) desktopOfflineTrust(
RequireEvidence: req.RequireIdentityEvidence || settings.RequireIdentityEvidence,
RequireCertificateStatus: req.RequireCertificateStatus || settings.RequireCertificateStatus,
},
}, len(clientRoots) + len(serverRoots), nil
FISCOBCOS: fiscoConfig,
}, len(clientRoots) + len(serverRoots) + boolInt(fiscoConfig != nil), nil
}

func boolInt(value bool) int {
if value {
return 1
}
return 0
}

// readDesktopFISCOBCOSTrustConfig accepts only the canonical public TrustConfig
// CBOR format. Endpoints and credentials in the config are never contacted by
// the desktop verifier; the SDK uses the config as local anchor trust only.
func readDesktopFISCOBCOSTrustConfig(path string) (*sdk.FISCOBCOSTrustConfig, error) {
path = strings.TrimSpace(path)
if path == "" {
return nil, errors.New("path is empty")
}
data, err := readCBORFileLimit(path, fiscobcos.MaxTrustConfigBytes)
if err != nil {
return nil, err
}
config, err := fiscobcos.UnmarshalTrustConfig(data)
if err != nil {
return nil, err
}
canonical, err := fiscobcos.MarshalTrustConfig(config)
if err != nil {
return nil, err
}
if !bytes.Equal(canonical, data) {
return nil, errors.New("file is not canonical TrustConfig CBOR")
}
return (*sdk.FISCOBCOSTrustConfig)(&config), nil
}

func readDesktopVerifierDescriptors(paths string, suite cryptosuite.ID) ([]sdk.KeyDescriptor, error) {
Expand Down
60 changes: 60 additions & 0 deletions clients/desktop/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"bytes"
"context"
"crypto/ed25519"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"

ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/wowtrust/trustdb/v2/internal/anchor/fiscobcos"
internalapp "github.com/wowtrust/trustdb/v2/internal/app"
"github.com/wowtrust/trustdb/v2/internal/cborx"
"github.com/wowtrust/trustdb/v2/internal/cryptosuite"
Expand All @@ -25,6 +28,63 @@ import (
"github.com/wowtrust/trustdb/v2/sdk"
)

func TestReadDesktopFISCOBCOSTrustConfigAcceptsCanonicalCBOR(t *testing.T) {
t.Parallel()
config, err := fiscobcos.NewTrustConfig(fiscobcos.CryptoModeStandard)
if err != nil {
t.Fatal(err)
}
config.ChainID = "local-chain"
config.GroupID = "group0"
config.GenesisHash = bytes.Repeat([]byte{0x11}, 32)
config.TrustedCheckpoint = fiscobcos.BlockCheckpoint{BlockNumber: 1, BlockHash: bytes.Repeat([]byte{0x22}, 32)}
config.Contract = fiscobcos.ContractBinding{
Address: bytes.Repeat([]byte{0x33}, 20),
CodeHash: bytes.Repeat([]byte{0x44}, 32),
ProtocolVersion: fiscobcos.TrustDBAnchorV1ProtocolVersion,
EventSignature: fiscobcos.TrustDBAnchorV1EventSignature,
}
config.Endpoints = []string{"tls://127.0.0.1:20200"}
config.ReadQuorum = 1
config.AccountProvider = fiscobcos.AccountProviderConfig{
Provider: "keydescriptor", KeyID: "account-1", KeyReference: "keys/account-1", Algorithm: fiscobcos.StandardAccountAlg,
}
config.Certificates = fiscobcos.CertificateConfig{
TransportMode: fiscobcos.StandardTransport,
TrustedCAReferences: []string{"certs/ca.pem"},
TrustedCACertificateHashes: [][]byte{bytes.Repeat([]byte{0x55}, 32)},
ClientSigningCertificateRef: "certs/client.pem",
ClientSigningKeyRef: "keys/client.key",
}
key, err := ethcrypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
public := ethcrypto.FromECDSAPub(&key.PublicKey)
config.Validators = []fiscobcos.ValidatorDescriptor{{
NodeID: "0x" + hex.EncodeToString(public[1:]),
Algorithm: fiscobcos.StandardAccountAlg,
PublicKeyEncoding: fiscobcos.StandardKeyEncoding,
PublicKey: public,
VoteWeight: 1,
}}
data, err := fiscobcos.MarshalTrustConfig(config)
if err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "trust-config.cbor")
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatal(err)
}
loaded, err := readDesktopFISCOBCOSTrustConfig(path)
if err != nil {
t.Fatalf("readDesktopFISCOBCOSTrustConfig() error = %v", err)
}
if loaded == nil || loaded.ChainID != config.ChainID || loaded.CryptoMode != config.CryptoMode {
t.Fatalf("loaded config = %+v, want chain=%q mode=%q", loaded, config.ChainID, config.CryptoMode)
}
}

func TestReadGlobalProofFileExplainsAnchorResultMixup(t *testing.T) {
t.Parallel()

Expand Down