diff options
| author | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2020-10-27 19:16:10 +0100 | 
|---|---|---|
| committer | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2020-10-27 19:16:10 +0100 | 
| commit | e7801b268c97c6b72bfcd76549ce5fd50ab0b1b5 (patch) | |
| tree | 1eecf16a6b263750b0d480c3d966dff2f3072cfd | |
| parent | 13dd306e69b26ab8b7aedcd6ed915df4b6672a01 (diff) | |
added ed25519 signing and SDIs
| -rw-r--r-- | handler.go | 13 | ||||
| -rw-r--r-- | instance.go | 20 | ||||
| -rw-r--r-- | reqres.go | 16 | ||||
| -rw-r--r-- | server/main.go | 3 | ||||
| -rw-r--r-- | server/testdata/chain/stfe.key | 3 | ||||
| -rwxr-xr-x | server/testdata/cmd/add-entry | 2 | ||||
| -rw-r--r-- | type.go | 28 | ||||
| -rw-r--r-- | x509.go | 45 | 
8 files changed, 126 insertions, 4 deletions
| @@ -68,7 +68,18 @@ func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.R  	} // note: more detail could be provided here, see addChainInternal in ctfe  	glog.Infof("Queued leaf: %v", trillianResponse.QueuedLeaf.Leaf.LeafValue) -	// TODO: respond with an SDI +	sdi, err := GenV1SDI(i.LogParameters, trillianResponse.QueuedLeaf.Leaf.LeafValue) +	if err != nil { +		return http.StatusInternalServerError, fmt.Errorf("failed creating signed debug info: %v", err) +	} + +	response, err := NewAddEntryResponse(sdi) +	if err != nil { +		return http.StatusInternalServerError, fmt.Errorf("failed creating AddEntryResponse: %v", err) +	} +	if err := WriteJsonResponse(response, w); err != nil { +		return http.StatusInternalServerError, err +	}  	return http.StatusOK, nil  } diff --git a/instance.go b/instance.go index d4fc004..8914a4b 100644 --- a/instance.go +++ b/instance.go @@ -2,10 +2,12 @@ package stfe  import (  	"crypto" -	"crypto/x509"  	"fmt"  	"time" +	"crypto/x509" +	"crypto/sha256" +  	"encoding/base64"  	"net/http" @@ -42,18 +44,32 @@ func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, deadline  }  // NewLogParameters returns an initialized LogParameters -func NewLogParameters(logId []byte, treeId int64, prefix string, anchorPath string) (*LogParameters, error) { +func NewLogParameters(treeId int64, prefix string, anchorPath, keyPath string) (*LogParameters, error) {  	anchorList, anchorPool, err := LoadTrustAnchors(anchorPath)  	if err != nil {  		return nil, err  	} +	key, err := LoadEd25519SigningKey(keyPath) +	if err != nil { +		return nil, err +	} + +	pub, err := x509.MarshalPKIXPublicKey(key.Public()) +	if err != nil { +		return nil, fmt.Errorf("failed DER encoding SubjectPublicKeyInfo: %v", err) +	} +	hasher := sha256.New() +	hasher.Write(pub) +	logId := hasher.Sum(nil) +  	return &LogParameters{  		LogId:      logId,  		TreeId:     treeId,  		Prefix:     prefix,  		AnchorPool: anchorPool,  		AnchorList: anchorList, +		Signer:     key,  	}, nil  } @@ -33,6 +33,11 @@ type GetProofByHashRequest struct {  	TreeSize int64  `json:"tree_size"` // Tree head size to base proof on  } +// AddEntryResponse is an assembled add-entry response +type AddEntryResponse struct { +	SignedDebugInfo string `json:"sdi"` +} +  // GetEntryResponse is an assembled log entry and its associated appendix  type GetEntryResponse struct {  	Leaf      string   `json:"leaf"`      // base64-encoded StItem @@ -118,6 +123,17 @@ func NewGetProofByHashRequest(httpRequest *http.Request) (GetProofByHashRequest,  	return GetProofByHashRequest{TreeSize: treeSize, Hash: hash}, nil  } +// NewAddEntryResponse assembles an add-entry response from an SDI +func NewAddEntryResponse(sdi StItem) (AddEntryResponse, error) { +	b, err := tls.Marshal(sdi) +	if err != nil { +		return AddEntryResponse{}, fmt.Errorf("tls marshal failed: %v", err) +	} +	return AddEntryResponse{ +		SignedDebugInfo: base64.StdEncoding.EncodeToString(b), +	}, nil +} +  // NewGetEntryResponse assembles a log entry and its appendix  func NewGetEntryResponse(leaf, appendix []byte) (GetEntryResponse, error) {  	var app Appendix diff --git a/server/main.go b/server/main.go index 84d92ea..924cfc9 100644 --- a/server/main.go +++ b/server/main.go @@ -20,6 +20,7 @@ var (  	trillianID   = flag.Int64("trillian_id", 5991359069696313945, "log identifier in the Trillian database")  	rpcDeadline  = flag.Duration("rpc_deadline", time.Second*10, "deadline for backend RPC requests")  	anchorPath   = flag.String("anchor_path", "testdata/chain/rgdd-root.pem", "path to a file containing PEM-encoded X.509 root certificates") +	keyPath = flag.String("key_path", "testdata/chain/stfe.key", "path to a PEM-encoded ed25519 signing key")  )  func main() { @@ -37,7 +38,7 @@ func main() {  	mux := http.NewServeMux()  	http.Handle("/", mux) -	lp, err := stfe.NewLogParameters([]byte("rgdd"), *trillianID, *prefix, *anchorPath) +	lp, err := stfe.NewLogParameters(*trillianID, *prefix, *anchorPath, *keyPath)  	if err != nil {  		glog.Fatalf("failed setting up log parameters: %v", err)  	} diff --git a/server/testdata/chain/stfe.key b/server/testdata/chain/stfe.key new file mode 100644 index 0000000..ffc5df4 --- /dev/null +++ b/server/testdata/chain/stfe.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAhqlhKgY/TiEyTIe5BcZKLELGa2kODtJ3S+oMP4JwsA +-----END PRIVATE KEY----- diff --git a/server/testdata/cmd/add-entry b/server/testdata/cmd/add-entry index 9efd3bf..d5b404d 100755 --- a/server/testdata/cmd/add-entry +++ b/server/testdata/cmd/add-entry @@ -31,3 +31,5 @@ popd >/dev/null  info "sending add-entry request"  curl --header "application/json" --request POST --data $json $base_url/add-entry +newline +# TODO: try decoding and verifying signature @@ -25,6 +25,7 @@ const (  // StItem references a versioned item based on a given format specifier.  type StItem struct {  	Format           StFormat          `tls:"maxval:65535"` +	SignedDebugInfoV1 *SignedDebugInfoV1 `tls:"selector:Format,val:2"`  	InclusionProofV1 *InclusionProofV1 `tls:"selector:Format,val:4"`  	ChecksumV1       *ChecksumV1       `tls:"selector:Format,val:5"`  	// TODO: add more items @@ -44,11 +45,32 @@ type InclusionProofV1 struct {  	InclusionPath []NodeHash `tls:"minlen:1,maxlen:65535"`  } +// SignedDebugInfoV1 is a signed statement that we intend (but do not promise) +// to insert an entry into the log.  Only Ed25519 signatures are supported. +// TODO: double-check that crypto/ed25519 encodes signature as in RFC 8032 +// TODO: need to think about signature format, then update markdown/api.md +type SignedDebugInfoV1 struct { +	LogId []byte `tls:"minlen:32,maxlen:127"` +	Message []byte `tls:"minlen:0,maxlen:65535"` +	Signature []byte `tls:"minlen:0,maxlen:65535"` // defined in RFC 8032 +} +  // NodeHash is a hashed Merkle tree node, see RFC 6962/bis (ยง4.9)  type NodeHash struct {  	Data []byte `tls:"minlen:32,maxlen:255"`  } +func NewSignedDebugInfoV1(logId, message, signature []byte) StItem { +	return StItem{ +		Format: StFormatSignedDebugInfoV1, +		SignedDebugInfoV1: &SignedDebugInfoV1{ +			LogId: logId, +			Message: message, +			Signature: signature, +		}, +	} +} +  // NewChecksumV1 creates a new StItem of type checksum_v1  func NewChecksumV1(identifier []byte, checksum []byte) StItem {  	return StItem{ @@ -103,11 +125,17 @@ func (i StItem) String() string {  		return fmt.Sprintf("Format(%s): %s", i.Format, *i.ChecksumV1)  	case StFormatInclusionProofV1:  		return fmt.Sprintf("Format(%s): %s", i.Format, *i.InclusionProofV1) +	case StFormatSignedDebugInfoV1: +		return fmt.Sprintf("Format(%s): %s", i.Format, *i.SignedDebugInfoV1)  	default:  		return fmt.Sprintf("unknown StItem: %s", i.Format)  	}  } +func (i SignedDebugInfoV1) String() string { +	return fmt.Sprintf("LogId(%s) Message(%s) Signature(%s)", base64.StdEncoding.EncodeToString(i.LogId), string(i.Message), base64.StdEncoding.EncodeToString(i.Signature)) +} +  func (i ChecksumV1) String() string {  	return fmt.Sprintf("Package(%v) Checksum(%v)", string(i.Package), base64.StdEncoding.EncodeToString(i.Checksum))  } @@ -3,6 +3,9 @@ package stfe  import (  	"fmt" +	"crypto" +	"crypto/rand" +	"crypto/ed25519"  	"crypto/ecdsa"  	"crypto/rsa"  	"crypto/x509" @@ -44,6 +47,38 @@ func LoadTrustAnchors(path string) ([]*x509.Certificate, *x509.CertPool, error)  	return anchors, pool, nil  } + +func LoadEd25519SigningKey(path string) (ed25519.PrivateKey, error) { +	data, err := ioutil.ReadFile(path) +	if err != nil { +		return nil, fmt.Errorf("failed reading private key: %v", err) +	} + +	var block *pem.Block +	block, data = pem.Decode(data) +	if block == nil { +		return nil, fmt.Errorf("private key not loaded") +	} +	if block.Type != "PRIVATE KEY" { +		return nil, fmt.Errorf("unexpected PEM block type: %s", block.Type) +	} +	if len(data) != 0 { +		return nil, fmt.Errorf("trailing data found after key: %v", data) +	} + +	key, err := x509.ParsePKCS8PrivateKey(block.Bytes) +	if err != nil { +		return nil, fmt.Errorf("failed parsing signing key: %v", err) +	} + +	switch t := key.(type) { +	case ed25519.PrivateKey: +		return key.(ed25519.PrivateKey), nil +	default: +		return nil, fmt.Errorf("unexpected signing key type: %v", t) +	} +} +  func VerifyChain(ld *LogParameters, certificate *x509.Certificate) ([]*x509.Certificate, error) {  	opts := x509.VerifyOptions{  		Roots:     ld.AnchorPool, @@ -76,3 +111,13 @@ func VerifySignature(leaf, signature []byte, certificate *x509.Certificate) erro  	}  	return nil  } + + +func GenV1SDI(ld *LogParameters, leaf []byte) (StItem, error) { +	// Note that ed25519 does not use the passed io.Reader +	sig, err := ld.Signer.Sign(rand.Reader, leaf, crypto.Hash(0)) +	if err != nil { +		return StItem{}, fmt.Errorf("ed25519 signature failed: %v", err) +	} +	return NewSignedDebugInfoV1(ld.LogId, []byte("reserved"), sig), nil +} | 
