hancock

Check-in [5c379c0bb1]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:model: refactor testimony key introduce TestimonyKey, for store and lookup of testimony introduce Key Identifier (KID) derived from Content Identifier (CID)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 5c379c0bb19115913099540690cb5497249e0c45403cda5e1d439229666762ab
User & Date: dnc 2019-10-07 01:09:23
Context
2019-11-03
11:58
model: prefer model.Authority (to string) for type safety check-in: 99cee0289a user: dnc tags: trunk
2019-10-07
01:09
model: refactor testimony key introduce TestimonyKey, for store and lookup of testimony introduce Key Identifier (KID) derived from Content Identifier (CID) check-in: 5c379c0bb1 user: dnc tags: trunk
2019-09-26
14:28
minor changes to variable names check-in: ff157b9a48 user: dnc tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to model/cid.go.

13
14
15
16
17
18
19

20

21
22
23
24
25
26
27
28














29
30
31
32
33

34
35
36
37
38
39
40
41
42
43
44
45
46



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66




// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package model

import (
	"crypto/sha256"

	"hash"


	"github.com/mr-tron/base58"
)

// Content Identifier (encoded, IPFS style)
type CID string

func (this CID) String() string { return string(this) }















// Simple Content Identifier implementation, which is compatible with
// IPFS and IPLD CID's.  But to keep things simple, only supporting a
// limited set.


type Sha256CID struct {
	hash.Hash
}

func NewSha256CID(data []byte) Sha256CID {
	this := Sha256CID{sha256.New()}
	if data != nil {
		// note that sha256.Sum256() also calls Write() without checking for error
		this.Write(data)
	}
	return this
}




func (this Sha256CID) Bytes() []byte {
	//
	prefix := []byte{
		1,    // version (varint)
		0x55, // multicodec 0x55 == Raw
		18,   // multihash 18 == sha2-256
		32,   // length of sha2-256 sum
	}
	sum := this.Sum(nil)
	return append(prefix, sum...)
}

func (this Sha256CID) String() string {
	// `z` for base58btc encoding
	return `z` + base58.Encode(this.Bytes())
}

func (this Sha256CID) Encode() CID {
	return CID(this.String())
}











>

>








>
>
>
>
>
>
>
>
>
>
>
>
>
>





>













>
>
>
|







|



|




|


>
>
>
>
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package model

import (
	"crypto/sha256"
	"fmt"
	"hash"
	"strings"

	"github.com/mr-tron/base58"
)

// Content Identifier (encoded, IPFS style)
type CID string

func (this CID) String() string { return string(this) }

func (this CID) Decode() ([]byte, error) {
	if !strings.HasPrefix(string(this), "z") {
		return nil, fmt.Errorf("unsupported CID encoding (%q)", string(this))
	}
	b, err := base58.Decode(strings.TrimPrefix(string(this), "z"))
	if err != nil {
		return nil, fmt.Errorf("bad CID (%q): %w", string(this), err)
	}
	if b[0] != 1 || b[1] != 0x55 || b[2] != 18 || b[3] != 32 {
		return nil, fmt.Errorf("unsupported CID format (%q)", string(this))
	}
	return b[4:], nil
}

// Simple Content Identifier implementation, which is compatible with
// IPFS and IPLD CID's.  But to keep things simple, only supporting a
// limited set.

// A Content Identifier based on Sha256 hash.
type Sha256CID struct {
	hash.Hash
}

func NewSha256CID(data []byte) Sha256CID {
	this := Sha256CID{sha256.New()}
	if data != nil {
		// note that sha256.Sum256() also calls Write() without checking for error
		this.Write(data)
	}
	return this
}

// Bytes provides a CID in byte array format. This function returns
// prefix bytes followed by the hash sum.  Use Sum() to get only the
// hash bytes.
func (this *Sha256CID) Bytes() []byte {
	//
	prefix := []byte{
		1,    // version (varint)
		0x55, // multicodec 0x55 == Raw
		18,   // multihash 18 == sha2-256
		32,   // length of sha2-256 sum
	}
	sum := this.Sum()
	return append(prefix, sum...)
}

func (this *Sha256CID) String() string {
	// `z` for base58btc encoding
	return `z` + base58.Encode(this.Bytes())
}

func (this *Sha256CID) Encode() CID {
	return CID(this.String())
}

func (this *Sha256CID) Sum() []byte {
	return this.Hash.Sum(nil)
}

Changes to model/cid_test.go.

48
49
50
51
52
53
54
55
56
57
58
		got := cid.String()
		if got != want {
			t.Errorf("Test: %q, got: %q, wanted %q", v, got, want)
			b, err := base58.Decode(want[1:]) // strip leading "z"
			if err != nil {
				t.Error(err)
			}
			t.Logf("(hex) Test: %q, got \n\t %x, wanted \n\t %x", v, cid.Sum(nil), b)
		}
	}
}







|



48
49
50
51
52
53
54
55
56
57
58
		got := cid.String()
		if got != want {
			t.Errorf("Test: %q, got: %q, wanted %q", v, got, want)
			b, err := base58.Decode(want[1:]) // strip leading "z"
			if err != nil {
				t.Error(err)
			}
			t.Logf("(hex) Test: %q, got \n\t %x, wanted \n\t %x", v, cid.Sum(), b)
		}
	}
}

Changes to model/index.go.

16
17
18
19
20
21
22

23
24
25



































26
27
28
29
30
31
32
// commands may create and inspect indexes, so we define the common
// conventions in this package.
package model

import (
	"encoding/hex"
	"fmt"


	"golang.org/x/crypto/ssh"
)




































func IndexKey(source CID, authority interface{}) (key []string, err error) {
	var public ssh.PublicKey
	switch pub := authority.(type) {
	case string:
		// use hex of ssh encoding of public key
		public, _, _, _, err = ssh.ParseAuthorizedKey([]byte(pub))







>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// commands may create and inspect indexes, so we define the common
// conventions in this package.
package model

import (
	"encoding/hex"
	"fmt"
	"log"

	"golang.org/x/crypto/ssh"
)

// We use Keys to store/lookup content.  A Key is really a special
// case of Content, so each Key has its own CID.  We define a KID type
// so that our source code can be explicit and avoid confusion when
// working with Keys.
type KID CID

type Sha256KID Sha256CID

func (this *Sha256KID) Sum() []byte { return (*Sha256CID)(this).Sum() }

// TestimonyKey refers to tesimony, for lookup and storage in a key/value store or index.
type TestimonyKey struct {
	// Public Key of the signer.
	Authority Authority `json:"authority"`

	// Identifies the object of testimony.
	Content CID `json:"content"`

	kid *Sha256KID // cache
}

// KID calculates an IPFS-style content identifier for a testimony key.
func (this *TestimonyKey) KID() *Sha256KID {
	if this.kid != nil {
		return this.kid
	}
	enc, err := Encode(this)
	if err != nil {
		log.Panicf("failed to encode %T: %s", this, err)
	}
	id := Sha256KID(NewSha256CID(enc))
	this.kid = &id
	return this.kid
}

func IndexKey(source CID, authority interface{}) (key []string, err error) {
	var public ssh.PublicKey
	switch pub := authority.(type) {
	case string:
		// use hex of ssh encoding of public key
		public, _, _, _, err = ssh.ParseAuthorizedKey([]byte(pub))

Changes to model/manifest.go.

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
	_, err := io.Copy(cid, f)
	if err != nil {
		return nil, err
	}

	return &FileManifest{
		Time: time.Now().Unix(),
		CID:  cid.Encode(),
	}, nil
}

// Enforce limitations on manifest data.
func (this FileManifest) Check() error {
	const messageCount = 8
	const messageLength = 128







|







128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
	_, err := io.Copy(cid, f)
	if err != nil {
		return nil, err
	}

	return &FileManifest{
		Time: time.Now().Unix(),
		CID:  cid.Encode(), // TODO(dnc): avoid unnecessary encodes/decodes, for performance
	}, nil
}

// Enforce limitations on manifest data.
func (this FileManifest) Check() error {
	const messageCount = 8
	const messageLength = 128

Changes to model/testimony.go.

17
18
19
20
21
22
23

24
25
26



27
28
29
30
31
32



33
34
35
36
37
38
39
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65







//
// Testimony is an attestation by an authority regarding the status of
// a source file.
package model

import (
	"errors"


	"golang.org/x/crypto/ssh"
)




// Type Testimony, when produced by a trusted authority, allows a
// verifier to authenticate a source file.
type Testimony struct {
	// The signer's public key.
	Public string `json:"public"`




	// The encoded bytes that were signed.  May be decoded into a manifest.
	Encoded []byte `json:"encoded"`

	// Signature of Encoded bytes.
	Signature ssh.Signature `json:"signature"`

................................................................................
	publicKey ssh.PublicKey
}

// Verify returns nil when the manifest, signature, and public key are
// consistent.  This checks only the tag data, and does not check that
// the key corresponds to an authorized entity.
func (this *Testimony) Verify() error {
	if this.Public == "" {
		return errors.New("testimony without public key")
	}
	public, err := this.PublicKey()
	if err != nil {
		return err
	}
	err = public.Verify(this.Encoded, &this.Signature)
	return err
}

func (this *Testimony) PublicKey() (ssh.PublicKey, error) {
	var err error
	if this.publicKey == nil {
		this.publicKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(this.Public))
	}
	return this.publicKey, err
}














>



>
>
>





|
>
>
>







 







|













|



>
>
>
>
>
>
>
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//
// Testimony is an attestation by an authority regarding the status of
// a source file.
package model

import (
	"errors"
	"log"

	"golang.org/x/crypto/ssh"
)

// An Authority is public key, encoded as a string (see ssh.ParseAuthorizedKey)
type Authority string

// Type Testimony, when produced by a trusted authority, allows a
// verifier to authenticate a source file.
type Testimony struct {
	// The signer's public key.
	Authority Authority `json:"authority"`

	// The data or source file which is the object of attestation.
	Content CID `json:"content,omitempty"`

	// The encoded bytes that were signed.  May be decoded into a manifest.
	Encoded []byte `json:"encoded"`

	// Signature of Encoded bytes.
	Signature ssh.Signature `json:"signature"`

................................................................................
	publicKey ssh.PublicKey
}

// Verify returns nil when the manifest, signature, and public key are
// consistent.  This checks only the tag data, and does not check that
// the key corresponds to an authorized entity.
func (this *Testimony) Verify() error {
	if this.Authority == "" {
		return errors.New("testimony without public key")
	}
	public, err := this.PublicKey()
	if err != nil {
		return err
	}
	err = public.Verify(this.Encoded, &this.Signature)
	return err
}

func (this *Testimony) PublicKey() (ssh.PublicKey, error) {
	var err error
	if this.publicKey == nil {
		this.publicKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(this.Authority))
	}
	return this.publicKey, err
}

func (this *Testimony) Key() *TestimonyKey {
	if this.Authority == "" {
		log.Panic("testimony not initialized, missing authority")
	}
	return &TestimonyKey{Authority: this.Authority, Content: this.Content}
}