oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "encoding/base64"
5 "errors"
6 "net"
7 "net/url"
8 "strings"
9
10 "amadan.net/oficina/home/handoff/seal"
11 "amadan.net/rastrillo/rastrillo/assertion"
12 json "github.com/go-json-experiment/json"
13)
14
15// HomeConfig explicitly enables Home authentication. The public key manifest
16// is StdBase64-encoded JSON mapping key IDs to StdBase64 P-256 public keys.
17// DEKBase64 is a dedicated handoff/storage key, never the document content key.
18type HomeConfig struct {
19 Issuer string
20 ConsumerID string
21 KeysBase64 string
22 DEKBase64 string
23}
24
25type homeSettings struct {
26 issuer string
27 consumerID string
28 keys map[string][]byte
29 dek []byte
30}
31
32var errHomeConfig = errors.New("docs: invalid Home configuration")
33
34// A nil config preserves standalone authentication; an incomplete config
35// cannot silently fall back to it. Errors never include supplied values.
36func validatedHomeConfig(cfg *HomeConfig, origin string) (*homeSettings, error) {
37 if cfg == nil {
38 return nil, nil
39 }
40 if !homeConsumerID(cfg.ConsumerID) {
41 return nil, errHomeConfig
42 }
43 dek, ok := homeConfigBase64(cfg.DEKBase64, 32)
44 if !ok {
45 return nil, errHomeConfig
46 }
47 if _, err := seal.New(dek, "oficina/docs"); err != nil {
48 return nil, errHomeConfig
49 }
50 manifest, ok := homeConfigBase64(cfg.KeysBase64, 4096)
51 if !ok {
52 return nil, errHomeConfig
53 }
54 var encoded map[string]string
55 // This decoder rejects duplicate names, invalid UTF-8 and trailing values.
56 // A map of strings also rejects wrapper objects, arrays and nested key data.
57 if json.Unmarshal(manifest, &encoded) != nil || len(encoded) == 0 || len(encoded) > 8 {
58 return nil, errHomeConfig
59 }
60 keys := make(map[string][]byte, len(encoded))
61 for id, encodedKey := range encoded {
62 key, ok := homeConfigBase64(encodedKey, 65)
63 if !ok {
64 return nil, errHomeConfig
65 }
66 keys[id] = key
67 }
68 if _, err := assertion.NewVerifier(cfg.Issuer, origin, keys); err != nil {
69 return nil, errHomeConfig
70 }
71 return &homeSettings{issuer: cfg.Issuer, consumerID: cfg.ConsumerID, keys: keys, dek: dek}, nil
72}
73
74func homeConsumerID(v string) bool {
75 if len(v) == 0 || len(v) > 64 {
76 return false
77 }
78 for _, b := range []byte(v) {
79 if !(b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' || b >= '0' && b <= '9' || b == '_' || b == '-') {
80 return false
81 }
82 }
83 return true
84}
85
86func homeConfigBase64(encoded string, limit int) ([]byte, bool) {
87 if encoded == "" || len(encoded) > base64.StdEncoding.EncodedLen(limit) {
88 return nil, false
89 }
90 decoded, err := base64.StdEncoding.Strict().DecodeString(encoded)
91 if err != nil || len(decoded) > limit || base64.StdEncoding.EncodeToString(decoded) != encoded {
92 return nil, false
93 }
94 return decoded, true
95}
96
97var errDirectoryConfig = errors.New("docs: invalid $HOME_DIRECTORY_ORIGIN")
98
99// validatedDirectory checks the hub directory origin the app switcher
100// links to, by Home's own originHost rules (home internal/home/origins.go):
101// https, a lower-case DNS name of at least two labels, no IP address, no
102// port, userinfo, path, query or fragment. Empty is allowed and means no
103// switcher. Docs reads it itself because it is not on home/connect, where
104// it would be connect.Credentials.Directory (spec §6.2); that move is R3.
105func validatedDirectory(origin string) (string, error) {
106 if origin == "" {
107 return "", nil
108 }
109 u, err := url.Parse(origin)
110 if err != nil || u.Scheme != "https" || u.User != nil || u.Host == "" || origin != "https://"+u.Host || u.Host != strings.ToLower(u.Host) || net.ParseIP(u.Host) != nil || len(u.Host) > 253 {
111 return "", errDirectoryConfig
112 }
113 labels := strings.Split(u.Host, ".")
114 if len(labels) < 2 {
115 return "", errDirectoryConfig
116 }
117 for _, label := range labels {
118 if len(label) == 0 || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' {
119 return "", errDirectoryConfig
120 }
121 for _, c := range label {
122 if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
123 return "", errDirectoryConfig
124 }
125 }
126 }
127 return origin, nil
128}
129