diff --git a/loglist/helpers.go b/loglist/helpers.go new file mode 100644 index 0000000..8f1a0f6 --- /dev/null +++ b/loglist/helpers.go @@ -0,0 +1,37 @@ +// Copyright (C) 2020 Opsmate, Inc. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License, v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This software is distributed WITHOUT A WARRANTY OF ANY KIND. +// See the Mozilla Public License for details. + +package loglist + +import ( + "encoding/base64" + "time" +) + +func (list *List) AllLogs() []*Log { + logs := []*Log{} + for operator := range list.Operators { + for log := range list.Operators[operator].Logs { + logs = append(logs, &list.Operators[operator].Logs[log]) + } + } + return logs +} + +func (log *Log) LogIDString() string { + return base64.StdEncoding.EncodeToString(log.LogID) +} + +func (log *Log) AcceptsExpiration(expiration time.Time) bool { + return log.TemporalInterval == nil || withinInterval(expiration, log.TemporalInterval.StartInclusive, log.TemporalInterval.EndExclusive) +} + +func withinInterval(expiration, startInclusive, endExclusive time.Time) bool { + return !expiration.Before(startInclusive) && expiration.Before(endExclusive) +} diff --git a/loglist/load.go b/loglist/load.go new file mode 100644 index 0000000..75b59dc --- /dev/null +++ b/loglist/load.go @@ -0,0 +1,58 @@ +// Copyright (C) 2020 Opsmate, Inc. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License, v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This software is distributed WITHOUT A WARRANTY OF ANY KIND. +// See the Mozilla Public License for details. + +package loglist + +import ( + "encoding/json" + "fmt" + "net/http" + "io/ioutil" + "strings" +) + +func Load(urlOrFile string) (*List, error) { + if strings.HasPrefix(urlOrFile, "https://") { + return Fetch(urlOrFile) + } else { + return ReadFile(urlOrFile) + } +} + +func Fetch(url string) (*List, error) { + response, err := http.Get(url) + if err != nil { + return nil, err + } + content, err := ioutil.ReadAll(response.Body) + response.Body.Close() + if err != nil { + return nil, err + } + if response.StatusCode != 200 { + return nil, fmt.Errorf("%s: %s", url, response.Status) + } + return unmarshal(content) +} + +func ReadFile(filename string) (*List, error) { + content, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + return unmarshal(content) +} + +func unmarshal(jsonBytes []byte) (*List, error) { + list := new(List) + if err := json.Unmarshal(jsonBytes, list); err != nil { + return nil, err + } + return list, nil +} diff --git a/loglist/schema.go b/loglist/schema.go new file mode 100644 index 0000000..aa128ab --- /dev/null +++ b/loglist/schema.go @@ -0,0 +1,77 @@ +// Copyright (C) 2020 Opsmate, Inc. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License, v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This software is distributed WITHOUT A WARRANTY OF ANY KIND. +// See the Mozilla Public License for details. + +package loglist + +import ( + "time" +) + +type List struct { + Version string `json:"version"` + Operators []Operator `json:"operators"` +} + +type Operator struct { + Name string `json:"name"` + Email []string `json:"email"` + Logs []Log `json:"logs"` +} + +type Log struct { + Key []byte `json:"key"` + LogID []byte `json:"log_id"` + MMD int `json:"mmd"` + URL string `json:"url"` + Description string `json:"description"` + State State `json:"state"` + DNS string `json:"dns"` + LogType LogType `json:"log_type"` + TemporalInterval *struct { + StartInclusive time.Time `json:"start_inclusive"` + EndExclusive time.Time `json:"end_exclusive"` + } `json:"temporal_interval"` +} + +type State struct { + Pending *struct { + Timestamp time.Time `json:"timestamp"` + } `json:"pending"` + + Qualified *struct { + Timestamp time.Time `json:"timestamp"` + } `json:"qualified"` + + Usable *struct { + Timestamp time.Time `json:"timestamp"` + } `json:"usable"` + + Readonly *struct { + Timestamp time.Time `json:"timestamp"` + FinalTreeHead struct { + TreeSize int64 `json:"tree_size"` + SHA256RootHash []byte `json:"sha256_root_hash"` + } `json:"final_tree_head"` + } `json:"readonly"` + + Retired *struct { + Timestamp time.Time `json:"timestamp"` + } `json:"retired"` + + Rejected *struct { + Timestamp time.Time `json:"timestamp"` + } `json:"rejected"` +} + +type LogType string + +const ( + LogTypeProd = "prod" + LogTypeTest = "test" +) diff --git a/loglist/validate.go b/loglist/validate.go new file mode 100644 index 0000000..f03d2e9 --- /dev/null +++ b/loglist/validate.go @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Opsmate, Inc. +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License, v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// This software is distributed WITHOUT A WARRANTY OF ANY KIND. +// See the Mozilla Public License for details. + +package loglist + +import ( + "bytes" + "crypto/sha256" + "fmt" +) + +func (list *List) Validate() error { + for i := range list.Operators { + if err := list.Operators[i].Validate(); err != nil { + return fmt.Errorf("problem with %dth operator (%s): %w", i, list.Operators[i].Name, err) + } + } + return nil +} + +func (operator *Operator) Validate() error { + for i := range operator.Logs { + if err := operator.Logs[i].Validate(); err != nil { + return fmt.Errorf("problem with %dth log (%s): %w", i, operator.Logs[i].LogIDString(), err) + } + } + return nil +} + +func (log *Log) Validate() error { + realLogID := sha256.Sum256(log.Key) + if !bytes.Equal(log.LogID, realLogID[:]) { + return fmt.Errorf("log ID does not match log key") + } + return nil +}