aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--handler.go9
-rw-r--r--instance.go2
-rw-r--r--markup/api.md6
-rw-r--r--reqres.go99
-rwxr-xr-xserver/testdata/cmd/add-entry4
-rw-r--r--type.go16
-rw-r--r--x509.go68
7 files changed, 127 insertions, 77 deletions
diff --git a/handler.go b/handler.go
index c86281f..ae91bef 100644
--- a/handler.go
+++ b/handler.go
@@ -45,15 +45,10 @@ func (a appHandler) sendHTTPError(w http.ResponseWriter, statusCode int, err err
func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
glog.Info("in addEntry")
- request, err := NewAddEntryRequest(r)
+ leaf, appendix, err := NewAddEntryRequest(i.LogParameters, r)
if err != nil {
return http.StatusBadRequest, err
- } // request can be decoded
-
- leaf, appendix, err := VerifyAddEntryRequest(i.LogParameters, request)
- if err != nil {
- return http.StatusBadRequest, err
- } // valid add-entry request
+ } // request is well-formed, signed, and chains back to a trust anchor
trillianRequest := trillian.QueueLeafRequest{
LogId: i.LogParameters.TreeId,
diff --git a/instance.go b/instance.go
index 12415f0..6b67096 100644
--- a/instance.go
+++ b/instance.go
@@ -44,7 +44,7 @@ func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, deadline
return i, nil
}
-// NewLogParameters returns an initialized LogParameters
+// NewLogParameters returns initialized log parameters using only ed25519
func NewLogParameters(treeId int64, prefix string, anchorPath, keyPath string) (*LogParameters, error) {
anchorList, anchorPool, err := LoadTrustAnchors(anchorPath)
if err != nil {
diff --git a/markup/api.md b/markup/api.md
index 60658b5..d9286bf 100644
--- a/markup/api.md
+++ b/markup/api.md
@@ -136,12 +136,12 @@ POST https://<base url>/st/v1/add-entry
Input:
- item: an `StItem` that corresponds to a valid leaf type. Only
`checksum_v1` at this time.
-- signature_algorithm: decimal, possible values are defined in RFC 8446
+- signature_scheme: decimal, possible values are defined in RFC 8446
[§4.2.3](https://tools.ietf.org/html/rfc8446#section-4.2.3). The serialized
signature encoding follows from this.
- signature: covers the submitted item.
-- certificate: base-64 encoded X.509 certificate that is vouched for by a trust
-anchor and which produced the above signature.
+- chain: a list of base-64 encoded X.509 certificates that chain back to
+a trust anchor and which produced the above signature.
Output:
- an `StItem` structure of type `signed_debug_info_v1` that covers the added
diff --git a/reqres.go b/reqres.go
index 66e07b8..59709cf 100644
--- a/reqres.go
+++ b/reqres.go
@@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
+ stdtls "crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
@@ -16,9 +17,10 @@ import (
// AddEntryRequest is a collection of add-entry input parameters
type AddEntryRequest struct {
- Item string `json:"item"` // base64-encoded StItem
- Signature string `json:"signature"` // base64-encoded DigitallySigned
- Certificate string `json:"certificate"` // base64-encoded X.509 certificate
+ Item string `json:"item"` // base64-encoded StItem
+ Signature string `json:"signature"` // base64-encoded DigitallySigned
+ SignatureScheme uint16 `json:"signature_scheme"` // RFC 8446, §4.2.3
+ Chain []string `json:"chain"` // base64-encoded X.509 certificates
}
// GetEntriesRequest is a collection of get-entry input parameters
@@ -48,24 +50,46 @@ type GetEntryResponse struct {
}
// NewAddEntryRequest parses and sanitizes the JSON-encoded add-entry
-// parameters from an incoming HTTP post. The resulting AddEntryRequest is
-// well-formed, but not necessarily trusted (further sanitization is needed).
-func NewAddEntryRequest(r *http.Request) (AddEntryRequest, error) {
- var ret AddEntryRequest
- if err := UnpackJsonPost(r, &ret); err != nil {
- return ret, err
+// parameters from an incoming HTTP post. The serialized leaf value and
+// associated appendix is returned if the submitted data is valid: well-formed,
+// signed using a supported scheme, and chains back to a valid trust anchor.
+func NewAddEntryRequest(lp *LogParameters, r *http.Request) ([]byte, []byte, error) {
+ var entry AddEntryRequest
+ if err := UnpackJsonPost(r, &entry); err != nil {
+ return nil, nil, err
}
- item, err := StItemFromB64(ret.Item)
+ item, err := StItemFromB64(entry.Item)
if err != nil {
- return ret, fmt.Errorf("failed decoding StItem: %v", err)
+ return nil, nil, fmt.Errorf("StItem(%s): %v", item.Format, err)
}
if item.Format != StFormatChecksumV1 {
- return ret, fmt.Errorf("invalid StItem format: %s", item.Format)
+ return nil, nil, fmt.Errorf("invalid StItem format: %s", item.Format)
+ } // note that decode would have failed if invalid checksum/package length
+
+ chain, err := buildChainFromB64List(lp, entry.Chain)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid certificate chain: %v", err)
+ } // the final entry in chain is a valid trust anchor
+
+ signature, err := base64.StdEncoding.DecodeString(entry.Signature)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid signature encoding: %v", err)
+ }
+ serialized, err := tls.Marshal(item)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed tls marshaling StItem(%s): %v", item.Format, err)
+ }
+ if err := verifySignature(lp, chain[0], stdtls.SignatureScheme(entry.SignatureScheme), serialized, signature); err != nil {
+ return nil, nil, fmt.Errorf("invalid signature: %v", err)
+ }
+
+ extra, err := tls.Marshal(NewAppendix(chain, signature, entry.SignatureScheme))
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed tls marshaling appendix: %v", err)
}
- // TODO: verify that we got a checksum length
- // TODO: verify that we got a signature and certificate
- return ret, nil
+
+ return serialized, extra, nil
}
// NewGetEntriesRequest parses and sanitizes the URL-encoded get-entries
@@ -173,51 +197,6 @@ func NewGetAnchorsResponse(anchors []*x509.Certificate) []string {
return certificates
}
-// VerifyAddEntryRequest determines whether a well-formed AddEntryRequest should
-// be inserted into the log. The corresponding leaf and appendix is returned.
-func VerifyAddEntryRequest(ld *LogParameters, r AddEntryRequest) ([]byte, []byte, error) {
- item, err := StItemFromB64(r.Item)
- if err != nil {
- return nil, nil, fmt.Errorf("failed decoding StItem: %v", err)
- }
-
- leaf, err := tls.Marshal(item)
- if err != nil {
- return nil, nil, fmt.Errorf("failed tls marshaling StItem: %v", err)
- } // leaf is the serialized data that should be added to the tree
-
- c, err := base64.StdEncoding.DecodeString(r.Certificate)
- if err != nil {
- return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)
- }
- certificate, err := x509.ParseCertificate(c)
- if err != nil {
- return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)
- } // certificate is the end-entity certificate that signed leaf
-
- chain, err := VerifyChain(ld, certificate)
- if err != nil {
- return nil, nil, fmt.Errorf("chain verification failed: %v", err)
- } // chain is a valid path to some trust anchor
-
- signature, err := base64.StdEncoding.DecodeString(r.Signature)
- if err != nil {
- return nil, nil, fmt.Errorf("failed decoding signature: %v", err)
- }
- if err := VerifySignature(leaf, signature, certificate); err != nil {
- return nil, nil, fmt.Errorf("signature verification failed: %v", err)
- } // signature is valid for certificate
-
- // TODO: update doc of what signature "is", i.e., w/e x509 does
- // TODO: doc in markdown/api.md what signature schemes we expect
- appendix, err := tls.Marshal(NewAppendix(chain, signature))
- if err != nil {
- return nil, nil, fmt.Errorf("failed tls marshaling appendix: %v", err)
- }
-
- return leaf, appendix, nil
-}
-
// UnpackJsonPost unpacks a json-encoded HTTP POST request into `unpack`
func UnpackJsonPost(r *http.Request, unpack interface{}) error {
body, err := ioutil.ReadAll(r.Body)
diff --git a/server/testdata/cmd/add-entry b/server/testdata/cmd/add-entry
index d5b404d..0935614 100755
--- a/server/testdata/cmd/add-entry
+++ b/server/testdata/cmd/add-entry
@@ -4,6 +4,7 @@ set -eo pipefail
source config
algo=ecdsa
+signature_scheme=1027
key_path="../chain/rgdd-$algo.key"
cert_path="../chain/rgdd-$algo.pem"
name="foobar-1.2.3"
@@ -19,9 +20,10 @@ pushd ../entry >/dev/null
openssl dgst -sha256 -sign $key_path -out stitem/$name.sig stitem/$name
openssl base64 -A -in stitem/$name -out stitem/$name.b64
openssl base64 -A -in stitem/$name.sig -out stitem/$name.sig.b64
- json=$(printf '{"item":"%s","signature":"%s","certificate":"%s"}'\
+ json=$(printf '{"item":"%s","signature":"%s","signature_scheme":%s,"chain":["%s"]}'\
$(cat stitem/$name.b64)\
$(cat stitem/$name.sig.b64)\
+ $signature_scheme\
$(cat $cert_path |\
sed '1,1d;$ d' |\
xargs |\
diff --git a/type.go b/type.go
index 060ce30..5a32964 100644
--- a/type.go
+++ b/type.go
@@ -272,8 +272,9 @@ func StItemToB64(i StItem) (string, error) {
// Appendix is extra data that Trillian can store about a leaf
type Appendix struct {
- Signature []byte `tls:"minlen:0,maxlen:16383"`
- Chain []RawCertificate `tls:"minlen:0,maxlen:65535"`
+ Signature []byte `tls:"minlen:0,maxlen:16383"`
+ SignatureScheme uint16
+ Chain []RawCertificate `tls:"minlen:0,maxlen:65535"`
}
// RawCertificate is a serialized X.509 certificate
@@ -282,10 +283,15 @@ type RawCertificate struct {
}
// NewAppendix creates a new leaf Appendix for an X.509 chain and signature
-func NewAppendix(x509Chain []*x509.Certificate, signature []byte) Appendix {
- chain := make([]RawCertificate, 0, 2) // TODO: base length on config param
+func NewAppendix(x509Chain []*x509.Certificate, signature []byte, signatureScheme uint16) Appendix {
+ chain := make([]RawCertificate, 0, len(x509Chain))
for _, c := range x509Chain {
chain = append(chain, RawCertificate{c.Raw})
}
- return Appendix{Signature: signature, Chain: chain}
+
+ return Appendix{
+ Signature: signature,
+ Chain: chain,
+ SignatureScheme: signatureScheme,
+ }
}
diff --git a/x509.go b/x509.go
index 04b3534..b78e5e5 100644
--- a/x509.go
+++ b/x509.go
@@ -8,7 +8,9 @@ import (
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
+ stdtls "crypto/tls"
"crypto/x509"
+ "encoding/base64"
"encoding/pem"
"io/ioutil"
@@ -135,3 +137,69 @@ func GenV1STH(ld *LogParameters, th TreeHeadV1) (StItem, error) {
}
return NewSignedTreeHeadV1(th, ld.LogId, sig), nil
}
+
+// ParseB64Chain parses a list of base64 DER-encoded X.509 certificates, such
+// that the first (zero-index) string is interpretted as an end-entity
+// certificate and the remaining ones as the an intermediate CertPool.
+func ParseB64Chain(chain []string) (*x509.Certificate, *x509.CertPool, error) {
+ var certificate *x509.Certificate
+ intermediatePool := x509.NewCertPool()
+ for index, cert := range chain {
+ der, err := base64.StdEncoding.DecodeString(cert)
+ if err != nil {
+ return nil, nil, fmt.Errorf("certificate decoding failed: %v", err)
+ }
+ c, err := x509.ParseCertificate(der)
+ if err != nil {
+ return nil, nil, fmt.Errorf("certificate decoding failed: %v", err)
+ }
+
+ if index == 0 {
+ certificate = c
+ } else {
+ intermediatePool.AddCert(c)
+ }
+ }
+ if certificate == nil {
+ return nil, nil, fmt.Errorf("certificate chain is empty")
+ }
+ return certificate, intermediatePool, nil
+}
+
+func buildChainFromB64List(lp *LogParameters, b64chain []string) ([]*x509.Certificate, error) {
+ certificate, _, err := ParseB64Chain(b64chain) // TODO: use intermediatePool
+ if err != nil {
+ return nil, err
+ }
+
+ opts := x509.VerifyOptions{
+ Roots: lp.AnchorPool,
+ KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, // TODO: move to ld
+ }
+
+ chains, err := certificate.Verify(opts)
+ if err != nil {
+ return nil, fmt.Errorf("chain verification failed: %v", err)
+ }
+ if len(chains) == 0 {
+ return nil, fmt.Errorf("chain verification failed: no chain")
+ }
+
+ chain := chains[0] // if we found multiple paths just pick the first one
+ // TODO: check that len(chain) is OK
+
+ return chain, nil
+}
+
+// verifySignature checks if signature is valid for some serialized data. The
+// only supported signature scheme is ecdsa_secp256r1_sha256(0x0403), see §4.3.2
+// in RFC 8446. TODO: replace ECDSA with ed25519(0x0807)
+func verifySignature(_ *LogParameters, certificate *x509.Certificate, scheme stdtls.SignatureScheme, serialized, signature []byte) error {
+ if scheme != stdtls.ECDSAWithP256AndSHA256 {
+ return fmt.Errorf("unsupported signature scheme: %v", scheme)
+ }
+ if err := certificate.CheckSignature(x509.ECDSAWithSHA256, serialized, signature); err != nil {
+ return fmt.Errorf("invalid signature: %v", err)
+ }
+ return nil
+}