package operator import ( "encoding/base64" "encoding/hex" "time" family "amadan.net/rastrillo/rastrillo/crypto" ) // Private v2 wire types cannot enter the v1 engine or journal. The future // verified adapter must also match these pins against the embedded catalog. type schemaAuthorizationV2 struct { Authorization SchemaProfile string `json:"schema_profile"` SchemaManifestDigest string `json:"schema_manifest_digest"` } type schemaInspectV2 struct { InspectChallenge SchemaProfile string `json:"schema_profile"` SchemaManifestDigest string `json:"schema_manifest_digest"` } type schemaVerifierV2 struct { base *Verifier profile, manifestDigest string } type schemaRequestV2 struct { authorization schemaAuthorizationV2 input []byte head string } func validSchemaPinV2(role, profile, manifestDigest string) bool { sum, err := hex.DecodeString(manifestDigest) if err != nil || len(sum) != 32 || hex.EncodeToString(sum) != manifestDigest { return false } return role == "credential" && profile == "credential-current-v1" || role == "directory" && (profile == "directory-current-v1" || profile == "directory-historical-empty-v1") } func newSchemaVerifierV2(p Pins, profile, manifestDigest string) (*schemaVerifierV2, error) { if !validSchemaPinV2(p.Role, profile, manifestDigest) { return nil, ErrRefused } base, err := NewVerifier(p) if err != nil { return nil, err } return &schemaVerifierV2{base, profile, manifestDigest}, nil } func validSchemaAuthorizationV2(a schemaAuthorizationV2) bool { if a.Version != 2 || !validSchemaPinV2(a.Role, a.SchemaProfile, a.SchemaManifestDigest) { return false } base := a.Authorization base.Version = 1 return base.valid() } func validSchemaInspectV2(c schemaInspectV2) bool { if c.Version != 2 || !validSchemaPinV2(c.Role, c.SchemaProfile, c.SchemaManifestDigest) { return false } base := c.InspectChallenge base.Version = 1 return base.valid() } func signSchemaRequestV2(k *family.Keypair, a schemaAuthorizationV2, input any) ([]byte, error) { if !signingKey(k) { return nil, ErrRefused } b, err := encode(input) if err != nil || len(b) > InputLimit { return nil, ErrRefused } a.InputDigest = digest(b) if !validSchemaAuthorizationV2(a) { return nil, ErrRefused } payload, err := encode(a) if err != nil { return nil, err } sig, err := family.Sign(k, InputContext, payload) if err != nil { return nil, ErrUnavailable } env, err := encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(sig)}) if err != nil || len(env) > AuthorizationLimit { return nil, ErrRefused } out, err := encode(wrapper{2, env, b}) if err != nil || len(out) > BodyLimit { return nil, ErrRefused } return out, nil } func verifySchemaRequestV2(v *schemaVerifierV2, raw []byte) (schemaRequestV2, error) { fail := func() (schemaRequestV2, error) { return schemaRequestV2{}, ErrRefused } if v == nil || v.base == nil || len(raw) == 0 || len(raw) > BodyLimit { return fail() } var w wrapper if strictCanonical(raw, &w) != nil || w.Version != 2 || len(w.Authorization) == 0 || len(w.Authorization) > AuthorizationLimit || len(w.Input) == 0 || len(w.Input) > InputLimit { return fail() } var env envelope if strictCanonical(w.Authorization, &env) != nil || env.Version != 1 || env.KeyID != v.base.kid { return fail() } payload, ok := canonical(env.Payload, -1) sig, ok2 := canonical(env.Signature, 64) if !ok || !ok2 || len(payload) > AuthorizationLimit || !family.Verify(v.base.key, InputContext, payload, sig) { return fail() } var a schemaAuthorizationV2 if strictCanonical(payload, &a) != nil || !validSchemaAuthorizationV2(a) { return fail() } p := v.base.pins if !v.base.actions[a.Action] || a.Role != p.Role || a.Origin != p.Origin || a.Installation != p.Installation || a.Registry != p.Registry || a.Device != p.Device || a.InputDigest != digest(w.Input) || a.SchemaProfile != v.profile || a.SchemaManifestDigest != v.manifestDigest { return fail() } return schemaRequestV2{a, append([]byte(nil), w.Input...), digest(w.Authorization)}, nil } func signSchemaInspectV2(k *family.Keypair, c schemaInspectV2) ([]byte, error) { if !signingKey(k) || !validSchemaInspectV2(c) { return nil, ErrRefused } payload, err := encode(c) if err != nil { return nil, err } sig, err := family.Sign(k, InspectContext, payload) if err != nil { return nil, ErrUnavailable } return encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(sig)}) } func verifySchemaInspectV2(v *schemaVerifierV2, raw []byte, now time.Time) (schemaInspectV2, error) { fail := func() (schemaInspectV2, error) { return schemaInspectV2{}, ErrRefused } if v == nil || v.base == nil || len(raw) == 0 || len(raw) > AuthorizationLimit { return fail() } var env envelope if strictCanonical(raw, &env) != nil || env.Version != 1 || env.KeyID != v.base.kid { return fail() } payload, ok := canonical(env.Payload, -1) sig, ok2 := canonical(env.Signature, 64) if !ok || !ok2 || len(payload) > AuthorizationLimit || !family.Verify(v.base.key, InspectContext, payload, sig) { return fail() } var c schemaInspectV2 if strictCanonical(payload, &c) != nil || !validSchemaInspectV2(c) { return fail() } p := v.base.pins if !v.base.actions["directory.inspect"] || c.Role != p.Role || c.Origin != p.Origin || c.Installation != p.Installation || c.Registry != p.Registry || c.Device != p.Device || c.SchemaProfile != v.profile || c.SchemaManifestDigest != v.manifestDigest || c.Issued > now.Unix() || c.Expires <= now.Unix() { return fail() } return c, nil }