aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRasmus Dahlberg <rasmus.dahlberg@kau.se>2020-10-27 19:16:10 +0100
committerRasmus Dahlberg <rasmus.dahlberg@kau.se>2020-10-27 19:16:10 +0100
commite7801b268c97c6b72bfcd76549ce5fd50ab0b1b5 (patch)
tree1eecf16a6b263750b0d480c3d966dff2f3072cfd
parent13dd306e69b26ab8b7aedcd6ed915df4b6672a01 (diff)
added ed25519 signing and SDIs
-rw-r--r--handler.go13
-rw-r--r--instance.go20
-rw-r--r--reqres.go16
-rw-r--r--server/main.go3
-rw-r--r--server/testdata/chain/stfe.key3
-rwxr-xr-xserver/testdata/cmd/add-entry2
-rw-r--r--type.go28
-rw-r--r--x509.go45
8 files changed, 126 insertions, 4 deletions
diff --git a/handler.go b/handler.go
index 6e5fe49..2a23dbb 100644
--- a/handler.go
+++ b/handler.go
@@ -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
}
diff --git a/reqres.go b/reqres.go
index e1f97ee..fe79d51 100644
--- a/reqres.go
+++ b/reqres.go
@@ -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
diff --git a/type.go b/type.go
index a629259..726b215 100644
--- a/type.go
+++ b/type.go
@@ -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))
}
diff --git a/x509.go b/x509.go
index 4e5a4d6..1e443a1 100644
--- a/x509.go
+++ b/x509.go
@@ -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
+}