diff options
| -rw-r--r-- | handler.go | 9 | ||||
| -rw-r--r-- | instance.go | 2 | ||||
| -rw-r--r-- | markup/api.md | 6 | ||||
| -rw-r--r-- | reqres.go | 99 | ||||
| -rwxr-xr-x | server/testdata/cmd/add-entry | 4 | ||||
| -rw-r--r-- | type.go | 16 | ||||
| -rw-r--r-- | x509.go | 68 | 
7 files changed, 127 insertions, 77 deletions
| @@ -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 @@ -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 |\ @@ -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, +	}  } @@ -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 +} | 
