package trustgraph import ( "crypto/x509" "encoding/json" "io" "io/ioutil" "sort" "strings" "time" "github.com/docker/libtrust" ) type jsonGrant struct { Subject string `json:"subject"` Permission uint16 `json:"permission"` Grantee string `json:"grantee"` } type jsonRevocation struct { Subject string `json:"subject"` Revocation uint16 `json:"revocation"` Grantee string `json:"grantee"` } type jsonStatement struct { Revocations []*jsonRevocation `json:"revocations"` Grants []*jsonGrant `json:"grants"` Expiration time.Time `json:"expiration"` IssuedAt time.Time `json:"issuedAt"` } func (g *jsonGrant) Grant(statement *Statement) *Grant { return &Grant{ Subject: g.Subject, Permission: g.Permission, Grantee: g.Grantee, statement: statement, } } // Statement represents a set of grants made from a verifiable // authority. A statement has an expiration associated with it // set by the authority. type Statement struct { jsonStatement signature *libtrust.JSONSignature } // IsExpired returns whether the statement has expired func (s *Statement) IsExpired() bool { return s.Expiration.Before(time.Now().Add(-10 * time.Second)) } // Bytes returns an indented json representation of the statement // in a byte array. This value can be written to a file or stream // without alteration. func (s *Statement) Bytes() ([]byte, error) { return s.signature.PrettySignature("signatures") } // LoadStatement loads and verifies a statement from an input stream. func LoadStatement(r io.Reader, authority *x509.CertPool) (*Statement, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err } js, err := libtrust.ParsePrettySignature(b, "signatures") if err != nil { return nil, err } payload, err := js.Payload() if err != nil { return nil, err } var statement Statement err = json.Unmarshal(payload, &statement.jsonStatement) if err != nil { return nil, err } if authority == nil { _, err = js.Verify() if err != nil { return nil, err } } else { _, err = js.VerifyChains(authority) if err != nil { return nil, err } } statement.signature = js return &statement, nil } // CreateStatements creates and signs a statement from a stream of grants // and revocations in a JSON array. func CreateStatement(grants, revocations io.Reader, expiration time.Duration, key libtrust.PrivateKey, chain []*x509.Certificate) (*Statement, error) { var statement Statement err := json.NewDecoder(grants).Decode(&statement.jsonStatement.Grants) if err != nil { return nil, err } err = json.NewDecoder(revocations).Decode(&statement.jsonStatement.Revocations) if err != nil { return nil, err } statement.jsonStatement.Expiration = time.Now().UTC().Add(expiration) statement.jsonStatement.IssuedAt = time.Now().UTC() b, err := json.MarshalIndent(&statement.jsonStatement, "", " ") if err != nil { return nil, err } statement.signature, err = libtrust.NewJSONSignature(b) if err != nil { return nil, err } err = statement.signature.SignWithChain(key, chain) if err != nil { return nil, err } return &statement, nil } type statementList []*Statement func (s statementList) Len() int { return len(s) } func (s statementList) Less(i, j int) bool { return s[i].IssuedAt.Before(s[j].IssuedAt) } func (s statementList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // CollapseStatements returns a single list of the valid statements as well as the // time when the next grant will expire. func CollapseStatements(statements []*Statement, useExpired bool) ([]*Grant, time.Time, error) { sorted := make(statementList, 0, len(statements)) for _, statement := range statements { if useExpired || !statement.IsExpired() { sorted = append(sorted, statement) } } sort.Sort(sorted) var minExpired time.Time var grantCount int roots := map[string]*grantNode{} for i, statement := range sorted { if statement.Expiration.Before(minExpired) || i == 0 { minExpired = statement.Expiration } for _, grant := range statement.Grants { parts := strings.Split(grant.Grantee, "/") nodes := roots g := grant.Grant(statement) grantCount = grantCount + 1 for _, part := range parts { node, nodeOk := nodes[part] if !nodeOk { node = newGrantNode() nodes[part] = node } node.grants = append(node.grants, g) nodes = node.children } } for _, revocation := range statement.Revocations { parts := strings.Split(revocation.Grantee, "/") nodes := roots var node *grantNode var nodeOk bool for _, part := range parts { node, nodeOk = nodes[part] if !nodeOk { break } nodes = node.children } if node != nil { for _, grant := range node.grants { if isSubName(grant.Subject, revocation.Subject) { grant.Permission = grant.Permission &^ revocation.Revocation } } } } } retGrants := make([]*Grant, 0, grantCount) for _, rootNodes := range roots { retGrants = append(retGrants, rootNodes.grants...) } return retGrants, minExpired, nil } // FilterStatements filters the statements to statements including the given grants. func FilterStatements(grants []*Grant) ([]*Statement, error) { statements := map[*Statement]bool{} for _, grant := range grants { if grant.statement != nil { statements[grant.statement] = true } } retStatements := make([]*Statement, len(statements)) var i int for statement := range statements { retStatements[i] = statement i++ } return retStatements, nil }