Commit aacc0c23 authored by masala's avatar masala

Merge nonvoting/voting internal s11n

parent 098da078
// descriptor.go - Katzenpost Non-voting authority descriptor s11n.
// Copyright (C) 2017 Yawning Angel.
// descriptor.go - Katzenpost authority descriptor s11n.
// Copyright (C) 2017, 2018 Yawning Angel, masala, David Stainton
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
......@@ -25,24 +25,24 @@ import (
"time"
"github.com/katzenpost/core/crypto/cert"
"github.com/katzenpost/core/epochtime"
"github.com/katzenpost/core/pki"
"github.com/katzenpost/core/epochtime"
"github.com/katzenpost/core/sphinx/constants"
"github.com/ugorji/go/codec"
"golang.org/x/net/idna"
)
const (
nodeDescriptorVersion = "nonvoting-v0"
nodeDescriptorVersion = "v0"
)
var (
certificateExpiration = (epochtime.Period * 3) + (time.Minute * 10)
// CertificateExpiration is the time a descriptor certificate will be valid for.
CertificateExpiration = (epochtime.Period * 3) + (time.Minute * 10)
)
type nodeDescriptor struct {
// Version uniquely identifies the descriptor format as being for the
// non-voting authority so that it can be rejected when unexpectedly
// posted to, or received from an authority, or if the version changes.
// specified version so that it can be rejected if the format changes.
Version string
pki.MixDescriptor
......@@ -63,7 +63,7 @@ func SignDescriptor(signer cert.Signer, base *pki.MixDescriptor) ([]byte, error)
}
// Sign the descriptor.
expiration := time.Now().Add(certificateExpiration).Unix()
expiration := time.Now().Add(CertificateExpiration).Unix()
signed, err := cert.Sign(signer, payload, expiration)
if err != nil {
return nil, err
......@@ -94,7 +94,7 @@ func GetVerifierFromDescriptor(rawDesc []byte) (cert.Verifier, error) {
func VerifyAndParseDescriptor(verifier cert.Verifier, b []byte, epoch uint64) (*pki.MixDescriptor, error) {
signatures, err := cert.GetSignatures(b)
if len(signatures) != 1 {
return nil, fmt.Errorf("nonvoting: Expected 1 signature, got: %v", len(signatures))
return nil, fmt.Errorf("Expected 1 signature, got: %v", len(signatures))
}
// Verify that the descriptor is signed by the verifier.
......@@ -112,7 +112,7 @@ func VerifyAndParseDescriptor(verifier cert.Verifier, b []byte, epoch uint64) (*
// Ensure the descriptor is well formed.
if d.Version != nodeDescriptorVersion {
return nil, fmt.Errorf("nonvoting: Invalid Descriptor Version: '%v'", d.Version)
return nil, fmt.Errorf("Invalid Descriptor Version: '%v'", d.Version)
}
if err = IsDescriptorWellFormed(&d.MixDescriptor, epoch); err != nil {
return nil, err
......@@ -125,38 +125,38 @@ func VerifyAndParseDescriptor(verifier cert.Verifier, b []byte, epoch uint64) (*
// a PKI Document.
func IsDescriptorWellFormed(d *pki.MixDescriptor, epoch uint64) error {
if d.Name == "" {
return fmt.Errorf("nonvoting: Descriptor missing Name")
return fmt.Errorf("Descriptor missing Name")
}
if len(d.Name) > constants.NodeIDLength {
return fmt.Errorf("nonvoting: Descriptor Name '%v' exceeds max length", d.Name)
return fmt.Errorf("Descriptor Name '%v' exceeds max length", d.Name)
}
if d.LinkKey == nil {
return fmt.Errorf("nonvoting: Descriptor missing LinkKey")
return fmt.Errorf("Descriptor missing LinkKey")
}
if d.IdentityKey == nil {
return fmt.Errorf("nonvoting: Descriptor missing IdentityKey")
return fmt.Errorf("Descriptor missing IdentityKey")
}
if d.MixKeys[epoch] == nil {
return fmt.Errorf("nonvoting: Descriptor missing MixKey[%v]", epoch)
return fmt.Errorf("Descriptor missing MixKey[%v]", epoch)
}
for e := range d.MixKeys {
// TODO: Should this check that the epochs in MixKey are sequential?
if e < epoch || e >= epoch+3 {
return fmt.Errorf("nonvoting: Descriptor contains MixKey for invalid epoch: %v", d)
return fmt.Errorf("Descriptor contains MixKey for invalid epoch: %v", d)
}
}
if len(d.Addresses) == 0 {
return fmt.Errorf("nonvoting: Descriptor missing Addresses")
return fmt.Errorf("Descriptor missing Addresses")
}
for transport, addrs := range d.Addresses {
if len(addrs) == 0 {
return fmt.Errorf("nonvoting: Descriptor contains empty Address list for transport '%v'", transport)
return fmt.Errorf("Descriptor contains empty Address list for transport '%v'", transport)
}
var expectedIPVer int
switch transport {
case pki.TransportInvalid:
return fmt.Errorf("nonvoting: Descriptor contains invalid Transport")
return fmt.Errorf("Descriptor contains invalid Transport")
case pki.TransportTCPv4:
expectedIPVer = 4
case pki.TransportTCPv6:
......@@ -165,7 +165,7 @@ func IsDescriptorWellFormed(d *pki.MixDescriptor, epoch uint64) error {
// Unknown transports are only supported between the client and
// provider.
if d.Layer != pki.LayerProvider {
return fmt.Errorf("nonvoting: Non-provider published Transport '%v'", transport)
return fmt.Errorf("Non-provider published Transport '%v'", transport)
}
if transport != pki.TransportTCP {
// Ignore transports that don't have validation logic.
......@@ -177,47 +177,47 @@ func IsDescriptorWellFormed(d *pki.MixDescriptor, epoch uint64) error {
for _, v := range addrs {
h, p, err := net.SplitHostPort(v)
if err != nil {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
}
if len(h) == 0 {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v'", transport, v)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v'", transport, v)
}
if port, err := strconv.ParseUint(p, 10, 16); err != nil {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
} else if port == 0 {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v': port is 0", transport, v)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v': port is 0", transport, v)
}
switch expectedIPVer {
case 4, 6:
if ver, err := getIPVer(h); err != nil {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
} else if ver != expectedIPVer {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v': IP version mismatch", transport, v)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v': IP version mismatch", transport, v)
}
default:
// This must be TransportTCP or something else that supports
// "sensible" DNS style hostnames. Validate that they are
// at least somewhat well formed.
if _, err := idna.Lookup.ToASCII(h); err != nil {
return fmt.Errorf("nonvoting: Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
return fmt.Errorf("Descriptor contains invalid address ['%v']'%v': %v", transport, v, err)
}
}
}
}
if len(d.Addresses[pki.TransportTCPv4]) == 0 {
return fmt.Errorf("nonvoting: Descriptor contains no TCPv4 addresses")
return fmt.Errorf("Descriptor contains no TCPv4 addresses")
}
switch d.Layer {
case 0:
if d.Kaetzchen != nil {
return fmt.Errorf("nonvoting: Descriptor contains Kaetzchen when a mix")
return fmt.Errorf("Descriptor contains Kaetzchen when a mix")
}
case pki.LayerProvider:
if err := validateKaetzchen(d.Kaetzchen); err != nil {
return fmt.Errorf("nonvoting: Descriptor contains invalid Kaetzchen block: %v", err)
return fmt.Errorf("Descriptor contains invalid Kaetzchen block: %v", err)
}
default:
return fmt.Errorf("nonvoting: Descriptor self-assigned Layer: '%v'", d.Layer)
return fmt.Errorf("Descriptor self-assigned Layer: '%v'", d.Layer)
}
return nil
}
......
// document.go - Katzenpost Non-voting authority document s11n.
// Copyright (C) 2017 Yawning Angel.
// document.go - Katzenpost authority document s11n.
// Copyright (C) 2017, 2018 Yawning Angel, masala, David Stainton
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
......@@ -17,21 +17,30 @@
package s11n
import (
"encoding/binary"
"errors"
"fmt"
"time"
"github.com/katzenpost/core/crypto/cert"
"github.com/katzenpost/core/epochtime"
"github.com/katzenpost/core/pki"
"github.com/ugorji/go/codec"
)
const documentVersion = "nonvoting-document-v0"
const (
// DocumentVersion is the string identifying the format of the Document
DocumentVersion = "document-v0"
// SharedRandomLength is the length in bytes of a SharedRandomCommit.
SharedRandomLength = 40
// SharedRandomValueLength is the length in bytes of a SharedRandomValue.
SharedRandomValueLength = 32
)
var (
// ErrInvalidEpoch is the error to return when the document epoch is
// invalid.
ErrInvalidEpoch = errors.New("nonvoting: invalid document epoch")
ErrInvalidEpoch = errors.New("invalid document epoch")
jsonHandle *codec.JsonHandle
)
......@@ -39,8 +48,7 @@ var (
// Document is the on-the-wire representation of a PKI Document.
type Document struct {
// Version uniquely identifies the document format as being for the
// non-voting authority so that it can be rejected when unexpectedly
// received or if the version changes.
// specified version so that it can be rejected if the format changes.
Version string
Epoch uint64
......@@ -59,11 +67,28 @@ type Document struct {
Topology [][][]byte
Providers [][]byte
SharedRandomCommit []byte
SharedRandomValue []byte
}
// FromPayload deserializes, then verifies a Document, and returns the Document or error.
func FromPayload(verifier cert.Verifier, payload []byte) (*Document, error) {
verified, err := cert.Verify(verifier, payload)
if err != nil {
return nil, err
}
dec := codec.NewDecoderBytes(verified, jsonHandle)
d := new(Document)
if err := dec.Decode(d); err != nil {
return nil, err
}
return d, nil
}
// SignDocument signs and serializes the document with the provided signing key.
func SignDocument(signer cert.Signer, d *Document) ([]byte, error) {
d.Version = documentVersion
d.Version = DocumentVersion
// Serialize the document.
var payload []byte
......@@ -73,18 +98,43 @@ func SignDocument(signer cert.Signer, d *Document) ([]byte, error) {
}
// Sign the document.
expiration := time.Now().Add(certificateExpiration).Unix()
expiration := time.Now().Add(CertificateExpiration).Unix()
return cert.Sign(signer, payload, expiration)
}
// MultiSignDocument signs and serializes the document with the provided signing key, adding the signature to the existing signatures.
func MultiSignDocument(signer cert.Signer, peerSignatures []*cert.Signature, verifiers map[string]cert.Verifier, d *Document) ([]byte, error) {
d.Version = DocumentVersion
// Serialize the document.
var payload []byte
enc := codec.NewEncoderBytes(&payload, jsonHandle)
if err := enc.Encode(d); err != nil {
return nil, err
}
// Sign the document.
expiration := time.Now().Add(3*epochtime.Period).Unix()
signed, err := cert.Sign(signer, payload, expiration)
if err != nil {
return nil, err
}
// attach peer signatures
for _, signature := range peerSignatures {
s := string(signature.Identity)
verifier := verifiers[s]
signed, err = cert.AddSignature(verifier, *signature, signed)
if err != nil {
return nil, err
}
}
return signed, nil
}
// VerifyAndParseDocument verifies the signautre and deserializes the document.
func VerifyAndParseDocument(b []byte, verifier cert.Verifier) (*pki.Document, error) {
// Sanity check the number of signatures, and
// validate the signature with the provided public key.
signatures, err := cert.GetSignatures(b)
if len(signatures) != 1 {
return nil, fmt.Errorf("nonvoting: Expected 1 signature, got: %v", len(signatures))
}
payload, err := cert.Verify(verifier, b)
if err != nil {
return nil, err
......@@ -98,13 +148,39 @@ func VerifyAndParseDocument(b []byte, verifier cert.Verifier) (*pki.Document, er
}
// Ensure the document is well formed.
if d.Version != documentVersion {
return nil, fmt.Errorf("nonvoting: Invalid Document Version: '%v'", d.Version)
if d.Version != DocumentVersion {
return nil, fmt.Errorf("Invalid Document Version: '%v'", d.Version)
}
// Convert from the wire representation to a Document, and validate
// everything.
// If there is a SharedRandomCommit, verify the Epoch contained in SharedRandomCommit matches the Epoch in the Document.
if len(d.SharedRandomCommit) == SharedRandomLength {
srvEpoch := binary.BigEndian.Uint64(d.SharedRandomCommit[0:8])
if srvEpoch != d.Epoch {
return nil, fmt.Errorf("Document with invalid Epoch in SharedRandomCommit")
}
}
if len(d.SharedRandomValue) != SharedRandomValueLength {
if len(d.SharedRandomValue) != 0 {
return nil, fmt.Errorf("Document has invalid SharedRandomValue")
} else if len(d.SharedRandomCommit) != SharedRandomLength {
return nil, fmt.Errorf("Document has invalid SharedRandomCommit")
}
}
if len(d.SharedRandomCommit) != SharedRandomLength {
if len(d.SharedRandomCommit) != 0 {
return nil, fmt.Errorf("Document has invalid SharedRandomCommit")
} else if len(d.SharedRandomValue) != SharedRandomValueLength {
return nil, fmt.Errorf("Document has invalid SharedRandomValue")
}
}
doc := new(pki.Document)
doc.SharedRandomCommit = d.SharedRandomCommit
doc.SharedRandomValue = d.SharedRandomValue
doc.Epoch = d.Epoch
doc.SendRatePerMinute = d.SendRatePerMinute
doc.MixLambda = d.MixLambda
......@@ -163,11 +239,11 @@ func VerifyAndParseDocument(b []byte, verifier cert.Verifier) (*pki.Document, er
func IsDocumentWellFormed(d *pki.Document) error {
pks := make(map[string]bool)
if len(d.Topology) == 0 {
return fmt.Errorf("nonvoting: Document contains no Topology")
return fmt.Errorf("Document contains no Topology")
}
for layer, nodes := range d.Topology {
if len(nodes) == 0 {
return fmt.Errorf("nonvoting: Document Topology layer %d contains no nodes", layer)
return fmt.Errorf("Document Topology layer %d contains no nodes", layer)
}
for _, desc := range nodes {
if err := IsDescriptorWellFormed(desc, d.Epoch); err != nil {
......@@ -175,24 +251,24 @@ func IsDocumentWellFormed(d *pki.Document) error {
}
pk := string(desc.IdentityKey.Identity())
if _, ok := pks[pk]; ok {
return fmt.Errorf("nonvoting: Document contains multiple entries for %v", desc.IdentityKey)
return fmt.Errorf("Document contains multiple entries for %v", desc.IdentityKey)
}
pks[pk] = true
}
}
if len(d.Providers) == 0 {
return fmt.Errorf("nonvoting: Document contains no Providers")
return fmt.Errorf("Document contains no Providers")
}
for _, desc := range d.Providers {
if err := IsDescriptorWellFormed(desc, d.Epoch); err != nil {
return err
}
if desc.Layer != pki.LayerProvider {
return fmt.Errorf("nonvoting: Document lists %v as a Provider with layer %v", desc.IdentityKey, desc.Layer)
return fmt.Errorf("Document lists %v as a Provider with layer %v", desc.IdentityKey, desc.Layer)
}
pk := string(desc.IdentityKey.Identity())
if _, ok := pks[pk]; ok {
return fmt.Errorf("nonvoting: Document contains multiple entries for %v", desc.IdentityKey)
return fmt.Errorf("Document contains multiple entries for %v", desc.IdentityKey)
}
pks[pk] = true
}
......
// document_test.go - Document s11n tests.
// Copyright (C) 2017 Yawning Angel
// Copyright (C) 2017 Yawning Angel, masala, David Stainton
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
......@@ -18,6 +18,7 @@ package s11n
import (
"crypto/rand"
"encoding/binary"
"fmt"
"testing"
......@@ -73,6 +74,9 @@ func TestDocument(t *testing.T) {
require.NoError(err, "eddsa.NewKeypair()")
testSendRate := uint64(3)
sharedRandomCommit := make([]byte, SharedRandomLength)
binary.BigEndian.PutUint64(sharedRandomCommit[:8], debugTestEpoch)
// Generate a Document.
doc := &Document{
......@@ -83,6 +87,8 @@ func TestDocument(t *testing.T) {
MixMaxDelay: 23,
SendLambda: 0.69,
SendMaxInterval: 17,
SharedRandomCommit: sharedRandomCommit,
SharedRandomValue: make([]byte, SharedRandomValueLength),
}
idx := 1
for l := 0; l < 3; l++ {
......
......@@ -24,7 +24,7 @@ import (
"fmt"
"net"
"github.com/katzenpost/authority/nonvoting/internal/s11n"
"github.com/katzenpost/authority/internal/s11n"
"github.com/katzenpost/core/crypto/ecdh"
"github.com/katzenpost/core/crypto/eddsa"
"github.com/katzenpost/core/crypto/rand"
......
......@@ -25,7 +25,7 @@ import (
//mrand "math/rand"
"net"
"github.com/katzenpost/authority/voting/internal/s11n"
"github.com/katzenpost/authority/internal/s11n"
"github.com/katzenpost/authority/voting/server/config"
"github.com/katzenpost/core/crypto/cert"
"github.com/katzenpost/core/crypto/ecdh"
......@@ -104,6 +104,7 @@ type connector struct {
log *logging.Logger
}
// NewConnector returns a connector initialized from a Config.
func NewConnector(cfg *Config) *connector {
p := &connector{
cfg: cfg,
......@@ -174,7 +175,7 @@ func (p *connector) initSession(ctx context.Context, doneCh <-chan interface{},
}, nil
}
func (c *connector) roundTrip(s *wire.Session, cmd commands.Command) (commands.Command, error) {
func (p *connector) roundTrip(s *wire.Session, cmd commands.Command) (commands.Command, error) {
if err := s.SendCommand(cmd); err != nil {
return nil, err
}
......@@ -269,10 +270,8 @@ func (c *client) Post(ctx context.Context, epoch uint64, signingKey *eddsa.Priva
}
if len(errs) == 0 {
return nil
} else {
return fmt.Errorf("failure to Post to %d Directory Authorities", len(errs))
}
// NOTREACHED
return fmt.Errorf("failure to Post to %d Directory Authorities", len(errs))
}
// Get returns the PKI document along with the raw serialized form for the provided epoch.
......@@ -325,7 +324,7 @@ func (c *client) Get(ctx context.Context, epoch uint64) (*pki.Document, []byte,
if len(good) == len(c.cfg.Authorities) {
c.log.Notice("OK, received fully signed consensus document.")
}
doc, _, err = s11n.VerifyAndParseDocument(r.Payload, c.cfg.Authorities[0].IdentityPublicKey)
doc, err = s11n.VerifyAndParseDocument(r.Payload, c.cfg.Authorities[0].IdentityPublicKey)
if err != nil {
// XXX: somehow this returned a nil doc!
return nil, nil, err
......@@ -342,7 +341,7 @@ func (c *client) Get(ctx context.Context, epoch uint64) (*pki.Document, []byte,
// Deserialize returns PKI document given the raw bytes.
func (c *client) Deserialize(raw []byte) (*pki.Document, error) {
doc, _, err := s11n.VerifyAndParseDocument(raw, c.cfg.Authorities[0].IdentityPublicKey)
doc, err := s11n.VerifyAndParseDocument(raw, c.cfg.Authorities[0].IdentityPublicKey)
if err != nil {
fmt.Errorf("Deserialize failure: %s", err)
}
......
// client.go - Katzenpost non-voting authority client.
// client.go - Katzenpost voting authority client.
// Copyright (C) 2018 David Stainton
//
// This program is free software: you can redistribute it and/or modify
......@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package client implements the Katzenpost non-voting authority client.
// Package client implements the Katzenpost voting authority client.
package client
import (
......@@ -26,7 +26,7 @@ import (
"testing"
"time"
"github.com/katzenpost/authority/voting/internal/s11n"
"github.com/katzenpost/authority/internal/s11n"
"github.com/katzenpost/authority/voting/server/config"
"github.com/katzenpost/core/crypto/cert"
"github.com/katzenpost/core/crypto/ecdh"
......@@ -207,7 +207,6 @@ func generateMixnet(numMixes, numProviders int, epoch uint64) (*s11n.Document, e
MixLambda: 0.25,
MixMaxDelay: 4000,
SendLambda: 1.2,
SendShift: 3,
SendMaxInterval: 300,
Topology: topology,
Providers: providersRaw,
......
// descriptor.go - Katzenpost voting authority descriptor s11n.
// Copyright (C) 2017, 2018 Yawning Angel, masala, David Stainton
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package s11n implements serialization routines for the various PKI
// data structures.
package s11n
import (
"fmt"
"net"
"strconv"
"time"
"github.com/katzenpost/core/crypto/cert"
"github.com/katzenpost/core/pki"
"github.com/katzenpost/core/epochtime"
"github.com/katzenpost/core/sphinx/constants"
"github.com/ugorji/go/codec"
"golang.org/x/net/idna"
)
const (
nodeDescriptorVersion = "voting-v0"
// XXX: How many epochs should this be?
)
type nodeDescriptor struct {
// Version uniquely identifies the descriptor format as being for the
// voting authority so that it can be rejected when unexpectedly
// posted to, or received from an authority, or if the version changes.
Version string
pki.MixDescriptor
}
// SignDescriptor signs and serializes the descriptor with the provided signing
// key.
func SignDescriptor(signer cert.Signer, base *pki.MixDescriptor) ([]byte, error) {
d := new(nodeDescriptor)
d.MixDescriptor = *base
d.Version = nodeDescriptorVersion
// Serialize the descriptor.
var payload []byte
enc := codec.NewEncoderBytes(&payload, jsonHandle)
if err := enc.Encode(d); err != nil {
return nil, err
}
// Sign the descriptor.
expiration := time.Now().Add(3*epochtime.Period).Unix()
signed, err := cert.Sign(signer, payload, expiration)
if err != nil {
return nil, err
}
return signed, nil
}
// GetVerifierFromDescriptor returns a verifier for the given
// mix descriptor certificate.
func GetVerifierFromDescriptor(rawDesc []byte) (cert.Verifier, error) {
payload, err := cert.GetCertified(rawDesc)
if err != nil {
return nil, err
}
// Parse the payload.
d := new(nodeDescriptor)
dec := codec.NewDecoderBytes(payload, jsonHandle)
if err = dec.Decode(d); err != nil {
return nil, err
}
return d.IdentityKey, nil
}
// VerifyAndParseDescriptor verifies the signature and deserializes the
// descriptor. MixDescriptors returned from this routine are guaranteed
// to have been correctly self signed by the IdentityKey listed in the
// MixDescriptor.
func VerifyAndParseDescriptor(verifier cert.Verifier, b []byte, epoch uint64) (*pki.MixDescriptor, error) {
signatures, err := cert.GetSignatures(b)
if len(signatures) != 1 {
return nil, fmt.Errorf("voting: Expected 1 signature, got: %v", len(signatures))
}
// Verify that the descriptor is signed by the verifier.
payload, err := cert.Verify(verifier, b)
if err != nil {
return nil, err
}
// Parse the payload.
d := new(nodeDescriptor)
dec := codec.NewDecoderBytes(payload, jsonHandle)
if err = dec.Decode(d); err != nil {
return nil, err
}
// Ensure the descriptor is well formed.
if d.Version != nodeDescriptorVersion {
return nil, fmt.Errorf("voting: Invalid Descriptor Version: '%v'", d.Version)
}
if err = IsDescriptorWellFormed(&d.MixDescriptor, epoch); err != nil {
return nil, err
}
return &d.MixDescriptor, nil
}
// IsDescriptorWellFormed validates the descriptor and returns a descriptive
// error iff there are any problems that would make it unusable as part of
// a PKI Document.
func IsDescriptorWellFormed(d *pki.MixDescriptor, epoch uint64) error {
if d.Name == "" {
return fmt.Errorf("voting: Descriptor missing Name")
}
if len(d.Name) > constants.NodeIDLength {
return fmt.Errorf("voting: Descriptor Name '%v' exceeds max length", d.Name)
}
if d.LinkKey == nil {
return fmt.Errorf("voting: Descriptor missing LinkKey")
}
if d.IdentityKey == nil {
return fmt.Errorf("voting: Descriptor missing IdentityKey")
}
if d.MixKeys[epoch] == nil {
return fmt.Errorf("voting: Descriptor missing MixKey[%v]", epoch)
}
for e := range d.MixKeys {
// TODO: Should this check that the epochs in MixKey are sequential?
if e < epoch || e >= epoch+3 {
return fmt.Errorf("voting: Descriptor contains MixKey for invalid epoch: %v", d)
}
}
if len(d.Addresses) == 0 {
return fmt.Errorf("voting: Descriptor missing Addresses")
}
for transport, addrs := range d.Addresses {
if len(addrs) == 0 {
return fmt.Errorf("voting: Descriptor contains empty Address list for transport '%v'", transport)
}
var expectedIPVer int
switch transport {
case pki.TransportInvalid:
return fmt.Errorf("voting: Descriptor contains invalid Transport")
case pki.TransportTCPv4:
expectedIPVer = 4
case pki.TransportTCPv6:
expectedIPVer = 6
default: