| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "context" |
| 6 | "crypto/sha256" |
| 7 | "encoding/hex" |
| 8 | "encoding/json" |
| 9 | "github.com/carlosframework/platform/internal/hostbox" |
| 10 | "github.com/carlosframework/platform/internal/hostclaim" |
| 11 | "github.com/carlosframework/platform/internal/hostevidence" |
| 12 | "github.com/carlosframework/platform/internal/park" |
| 13 | "strconv" |
| 14 | ) |
| 15 | |
| 16 | // A root-signed stop receipt is an alternative to the target's own release journal. |
| 17 | func (h claimCrossBoxHandover) recoveryStopReceipt(ctx context.Context, o hostclaim.Observed, p hostclaim.AdmissionRecord, key, digest string) error { |
| 18 | raw, _, err := h.Evidence.Get(ctx, key) |
| 19 | if err != nil { |
| 20 | return err |
| 21 | } |
| 22 | sum := sha256.Sum256(raw) |
| 23 | if hex.EncodeToString(sum[:]) != digest { |
| 24 | return ErrClaimStopReceipt |
| 25 | } |
| 26 | var r StopReceipt |
| 27 | if json.Unmarshal(raw, &r) != nil { |
| 28 | return ErrClaimStopReceipt |
| 29 | } |
| 30 | canonical, err := json.Marshal(r) |
| 31 | if err != nil || !bytes.Equal(raw, canonical) { |
| 32 | return ErrClaimStopReceipt |
| 33 | } |
| 34 | c := o.Claim |
| 35 | stamp, err := strconv.ParseUint(c.Op.Journal[hostclaim.JournalTargetStamp], 10, 64) |
| 36 | if err != nil || stamp == 0 || r.V != 1 || r.Kind != "stopped" || r.Host != o.Host || r.Box != p.Box || r.OperationID != c.Op.ID || |
| 37 | r.Incarnation != c.Holder.Incarnation || r.WriterSession != p.Session || r.InstallationStamp != stamp || |
| 38 | !park.SameRegion(c.Op.Intent["home_region"], r.Region, c.Op.Intent["to"]) || r.Enrollment != c.Op.Intent["target_enrollment"] || r.Phase != hostclaim.PhaseReleasing || |
| 39 | key != stopEvidenceKey(o.Host, c.Op.ID, p.Box, r.ID) || r.FenceID == "" || r.ObligationID == "" || r.ClaimGen == 0 || |
| 40 | r.EvidenceAt.IsZero() || r.RequestLimitNS != int64(hostbox.RequestLimit) || len(r.CgroupIdentities) == 0 || |
| 41 | !stopDigest(r.InventorySHA256) || !stopDigest(r.LocalJournalSHA256) || !stopDigest(r.ProducerDrainDigest) { |
| 42 | return ErrClaimStopReceipt |
| 43 | } |
| 44 | roster, err := h.Directory.Read(ctx) |
| 45 | if err != nil { |
| 46 | return err |
| 47 | } |
| 48 | box, ok := roster.Roster.Boxes[p.Box] |
| 49 | if (c.Op.Intent["home_region"] != "" && !roster.Roster.MatchesHome(c.Op.Intent["home_region"])) || !ok || box.Enrollment != r.Enrollment || !park.SameRegion("", box.Region, r.Region) { |
| 50 | return ErrClaimStopIdentity |
| 51 | } |
| 52 | sig, _, err := h.Evidence.Get(ctx, key+".sig") |
| 53 | if err != nil { |
| 54 | return err |
| 55 | } |
| 56 | return hostevidence.Verify(box.CensusPublicKey, key, raw, sig) |
| 57 | } |
| 58 | |