| 1 | package operator |
| 2 | |
| 3 | import ( |
| 4 | "encoding/base64" |
| 5 | "encoding/hex" |
| 6 | "time" |
| 7 | |
| 8 | family "amadan.net/rastrillo/rastrillo/crypto" |
| 9 | ) |
| 10 | |
| 11 | // Private v2 wire types cannot enter the v1 engine or journal. The future |
| 12 | // verified adapter must also match these pins against the embedded catalog. |
| 13 | type schemaAuthorizationV2 struct { |
| 14 | Authorization |
| 15 | SchemaProfile string `json:"schema_profile"` |
| 16 | SchemaManifestDigest string `json:"schema_manifest_digest"` |
| 17 | } |
| 18 | type schemaInspectV2 struct { |
| 19 | InspectChallenge |
| 20 | SchemaProfile string `json:"schema_profile"` |
| 21 | SchemaManifestDigest string `json:"schema_manifest_digest"` |
| 22 | } |
| 23 | type schemaVerifierV2 struct { |
| 24 | base *Verifier |
| 25 | profile, manifestDigest string |
| 26 | } |
| 27 | type schemaRequestV2 struct { |
| 28 | authorization schemaAuthorizationV2 |
| 29 | input []byte |
| 30 | head string |
| 31 | } |
| 32 | |
| 33 | func validSchemaPinV2(role, profile, manifestDigest string) bool { |
| 34 | sum, err := hex.DecodeString(manifestDigest) |
| 35 | if err != nil || len(sum) != 32 || hex.EncodeToString(sum) != manifestDigest { |
| 36 | return false |
| 37 | } |
| 38 | return role == "credential" && profile == "credential-current-v1" || |
| 39 | role == "directory" && (profile == "directory-current-v1" || profile == "directory-historical-empty-v1") |
| 40 | } |
| 41 | func newSchemaVerifierV2(p Pins, profile, manifestDigest string) (*schemaVerifierV2, error) { |
| 42 | if !validSchemaPinV2(p.Role, profile, manifestDigest) { |
| 43 | return nil, ErrRefused |
| 44 | } |
| 45 | base, err := NewVerifier(p) |
| 46 | if err != nil { |
| 47 | return nil, err |
| 48 | } |
| 49 | return &schemaVerifierV2{base, profile, manifestDigest}, nil |
| 50 | } |
| 51 | func validSchemaAuthorizationV2(a schemaAuthorizationV2) bool { |
| 52 | if a.Version != 2 || !validSchemaPinV2(a.Role, a.SchemaProfile, a.SchemaManifestDigest) { |
| 53 | return false |
| 54 | } |
| 55 | base := a.Authorization |
| 56 | base.Version = 1 |
| 57 | return base.valid() |
| 58 | } |
| 59 | func validSchemaInspectV2(c schemaInspectV2) bool { |
| 60 | if c.Version != 2 || !validSchemaPinV2(c.Role, c.SchemaProfile, c.SchemaManifestDigest) { |
| 61 | return false |
| 62 | } |
| 63 | base := c.InspectChallenge |
| 64 | base.Version = 1 |
| 65 | return base.valid() |
| 66 | } |
| 67 | func signSchemaRequestV2(k *family.Keypair, a schemaAuthorizationV2, input any) ([]byte, error) { |
| 68 | if !signingKey(k) { |
| 69 | return nil, ErrRefused |
| 70 | } |
| 71 | b, err := encode(input) |
| 72 | if err != nil || len(b) > InputLimit { |
| 73 | return nil, ErrRefused |
| 74 | } |
| 75 | a.InputDigest = digest(b) |
| 76 | if !validSchemaAuthorizationV2(a) { |
| 77 | return nil, ErrRefused |
| 78 | } |
| 79 | payload, err := encode(a) |
| 80 | if err != nil { |
| 81 | return nil, err |
| 82 | } |
| 83 | sig, err := family.Sign(k, InputContext, payload) |
| 84 | if err != nil { |
| 85 | return nil, ErrUnavailable |
| 86 | } |
| 87 | env, err := encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(sig)}) |
| 88 | if err != nil || len(env) > AuthorizationLimit { |
| 89 | return nil, ErrRefused |
| 90 | } |
| 91 | out, err := encode(wrapper{2, env, b}) |
| 92 | if err != nil || len(out) > BodyLimit { |
| 93 | return nil, ErrRefused |
| 94 | } |
| 95 | return out, nil |
| 96 | } |
| 97 | func verifySchemaRequestV2(v *schemaVerifierV2, raw []byte) (schemaRequestV2, error) { |
| 98 | fail := func() (schemaRequestV2, error) { return schemaRequestV2{}, ErrRefused } |
| 99 | if v == nil || v.base == nil || len(raw) == 0 || len(raw) > BodyLimit { |
| 100 | return fail() |
| 101 | } |
| 102 | var w wrapper |
| 103 | if strictCanonical(raw, &w) != nil || w.Version != 2 || len(w.Authorization) == 0 || len(w.Authorization) > AuthorizationLimit || len(w.Input) == 0 || len(w.Input) > InputLimit { |
| 104 | return fail() |
| 105 | } |
| 106 | var env envelope |
| 107 | if strictCanonical(w.Authorization, &env) != nil || env.Version != 1 || env.KeyID != v.base.kid { |
| 108 | return fail() |
| 109 | } |
| 110 | payload, ok := canonical(env.Payload, -1) |
| 111 | sig, ok2 := canonical(env.Signature, 64) |
| 112 | if !ok || !ok2 || len(payload) > AuthorizationLimit || !family.Verify(v.base.key, InputContext, payload, sig) { |
| 113 | return fail() |
| 114 | } |
| 115 | var a schemaAuthorizationV2 |
| 116 | if strictCanonical(payload, &a) != nil || !validSchemaAuthorizationV2(a) { |
| 117 | return fail() |
| 118 | } |
| 119 | p := v.base.pins |
| 120 | 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 { |
| 121 | return fail() |
| 122 | } |
| 123 | return schemaRequestV2{a, append([]byte(nil), w.Input...), digest(w.Authorization)}, nil |
| 124 | } |
| 125 | func signSchemaInspectV2(k *family.Keypair, c schemaInspectV2) ([]byte, error) { |
| 126 | if !signingKey(k) || !validSchemaInspectV2(c) { |
| 127 | return nil, ErrRefused |
| 128 | } |
| 129 | payload, err := encode(c) |
| 130 | if err != nil { |
| 131 | return nil, err |
| 132 | } |
| 133 | sig, err := family.Sign(k, InspectContext, payload) |
| 134 | if err != nil { |
| 135 | return nil, ErrUnavailable |
| 136 | } |
| 137 | return encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(sig)}) |
| 138 | } |
| 139 | func verifySchemaInspectV2(v *schemaVerifierV2, raw []byte, now time.Time) (schemaInspectV2, error) { |
| 140 | fail := func() (schemaInspectV2, error) { return schemaInspectV2{}, ErrRefused } |
| 141 | if v == nil || v.base == nil || len(raw) == 0 || len(raw) > AuthorizationLimit { |
| 142 | return fail() |
| 143 | } |
| 144 | var env envelope |
| 145 | if strictCanonical(raw, &env) != nil || env.Version != 1 || env.KeyID != v.base.kid { |
| 146 | return fail() |
| 147 | } |
| 148 | payload, ok := canonical(env.Payload, -1) |
| 149 | sig, ok2 := canonical(env.Signature, 64) |
| 150 | if !ok || !ok2 || len(payload) > AuthorizationLimit || !family.Verify(v.base.key, InspectContext, payload, sig) { |
| 151 | return fail() |
| 152 | } |
| 153 | var c schemaInspectV2 |
| 154 | if strictCanonical(payload, &c) != nil || !validSchemaInspectV2(c) { |
| 155 | return fail() |
| 156 | } |
| 157 | p := v.base.pins |
| 158 | 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() { |
| 159 | return fail() |
| 160 | } |
| 161 | return c, nil |
| 162 | } |
| 163 | |