//go:build linux package provisioner import ( "encoding/base64" "os" "strings" family "amadan.net/rastrillo/rastrillo/crypto" ) type RegistryInfo struct { Registry string `json:"registry"` Device string `json:"device"` PublicKey string `json:"public_key"` Sequence int64 `json:"sequence"` } // Initialize creates or resumes only the identity already recorded in these // independent directories. It never replaces a credential or existing floor. func Initialize(statePath, floorPath string, password []byte) (RegistryInfo, error) { if statePath == floorPath || strings.HasPrefix(statePath, floorPath+"/") || strings.HasPrefix(floorPath, statePath+"/") { return RegistryInfo{}, ErrRefused } state, e := openPrivateDirectory(statePath) if e != nil { return RegistryInfo{}, e } defer state.close() floorDir, e := openPrivateDirectory(floorPath) if e != nil { return RegistryInfo{}, e } defer floorDir.close() stateLock, e := state.lock() if e != nil { return RegistryInfo{}, e } defer stateLock.Close() floorLock, e := floorDir.lock() if e != nil { return RegistryInfo{}, e } defer floorLock.Close() names, e := directoryNames(state) if e != nil { return RegistryInfo{}, e } present := map[string]bool{} for _, name := range names { switch name { case "lock", "credential.age", "initializing.json", "registry.db", "registry.db-wal", "registry.db-shm": present[name] = true default: return RegistryInfo{}, ErrRefused } } floorNames, e := directoryNames(floorDir) if e != nil { return RegistryInfo{}, e } var id floorIdentity var key *family.Keypair if !present["credential.age"] { for name := range present { if name != "lock" { return RegistryInfo{}, ErrRefused } } for _, name := range floorNames { if name != "lock" { return RegistryInfo{}, ErrRefused } } key, e = family.Generate() if e != nil { return RegistryInfo{}, ErrUnavailable } registry, e := family.NewKey() if e != nil { return RegistryInfo{}, ErrUnavailable } device, e := family.NewKey() if e != nil { return RegistryInfo{}, ErrUnavailable } id = floorIdentity{base64.RawURLEncoding.EncodeToString(registry), base64.RawURLEncoding.EncodeToString(device), base64.RawURLEncoding.EncodeToString(key.SignPub())} wrapped, e := wrapCredential(key, id, password) if e != nil { return RegistryInfo{}, e } if e = state.create("credential.age", wrapped); e != nil { return RegistryInfo{}, e } } else { raw, e := state.read("credential.age", 128<<10) if e != nil { return RegistryInfo{}, e } id, key, e = unwrapCredential(raw, password) if e != nil { return RegistryInfo{}, e } } floor, e := newNativeFloor(floorDir, id) if e != nil { return RegistryInfo{}, e } if present["initializing.json"] { raw, e := state.read("initializing.json", 8192) if e != nil { return RegistryInfo{}, e } var signed floorEnvelope if closedJSON(raw, &signed) != nil || signed.Record.Version != 1 || signed.Record.Identity != id || signed.Record.Point != (floorPoint{}) || signed.Record.Previous != "" { return RegistryInfo{}, ErrRefused } body, e := canonicalJSON(signed.Record) sig, ok := tokenBytes(signed.Signature, 64) if e != nil || !ok || !family.Verify(floor.public, floorContext, body, sig) { return RegistryInfo{}, ErrRefused } } else { if present["registry.db"] { return RegistryInfo{}, ErrRefused } raw, e := floor.signed(key, floorPoint{}, "") if e != nil { return RegistryInfo{}, e } if e = state.create("initializing.json", raw); e != nil { return RegistryInfo{}, e } } hasFloor := false for _, name := range floorNames { if name != "lock" { hasFloor = true } } if !hasFloor { if present["registry.db"] { return RegistryInfo{}, ErrRefused } if e = floor.initialize(key); e != nil { return RegistryInfo{}, e } } else { if _, _, e = floor.current(); e != nil { return RegistryInfo{}, e } } j, e := openJournalWithLocks(state, floor, key, true, []*os.File{stateLock, floorLock}, true) if e != nil { return RegistryInfo{}, e } defer j.close() point, _, e := floor.current() if e != nil { return RegistryInfo{}, e } return RegistryInfo{id.Registry, id.Device, id.Public, point.Sequence}, nil }