├── .gitcookies.enc ├── .travis.yml ├── AUTHORS ├── CONTRIBUTORS ├── LICENSE.md ├── README.md ├── cnclient └── client.go ├── concurrent ├── oneshot-pubsub.go ├── oneshot-pubsub_test.go ├── pubsub.go ├── pubsub_test.go ├── sequence-broadcast.go └── sequence-broadcast_test.go ├── doc └── reconfiguration-mess.md ├── ed25519 ├── ed25519.go ├── ed25519_test.go ├── edwards25519 │ ├── const.go │ ├── edwards25519.go │ └── edwards25519_test.go ├── extra25519 │ ├── extra25519.go │ └── extra25519_test.go ├── marshal_test.go └── testdata │ └── sign.input.gz ├── hkpfront └── hkpfront.go ├── httpfront └── httpfront.go ├── keyserver ├── coname-server │ └── main.go ├── deploy │ ├── README │ └── make_config.go ├── dkim │ ├── dkim.go │ └── dkim_test.go ├── kv │ ├── kv.go │ ├── leveldbkv │ │ └── leveldbkv.go │ ├── logkv │ │ └── logkv.go │ ├── range.go │ └── tracekv │ │ └── tracekv.go ├── lookup.go ├── main.go ├── merkletree │ ├── merkletree.go │ ├── merkletree_test.go │ └── notes.txt ├── oidc.go ├── oidc │ ├── token.go │ ├── token_test.go │ └── verify.go ├── oidc_test.go ├── readme.md ├── replication │ ├── kvlog │ │ ├── kvlog.go │ │ └── leveldblog_test.go │ ├── raftlog │ │ ├── nettestutil │ │ │ └── nettestutil.go │ │ ├── proto │ │ │ ├── clean.sh │ │ │ ├── generate.sh │ │ │ ├── raftrpc.pb.go │ │ │ └── raftrpc.proto │ │ ├── raftlog.go │ │ └── raftlog_test.go │ └── replication.go ├── saml.go ├── saml │ ├── saml.go │ ├── saml_test.go │ ├── test.crt │ └── test.key ├── saml_test.go ├── server.go ├── server_test.go ├── table.go ├── update.go └── verify.go ├── lookup.go ├── merkle.go ├── policy.go ├── proto ├── AuthorizationPolicy.pr.go ├── AuthorizationPolicypr_test.go ├── Entry.pr.go ├── Entrypr_test.go ├── EpochHead.pr.go ├── EpochHeadpr_test.go ├── Profile.pr.go ├── Profilepr_test.go ├── SignedEntryUpdate.pr.go ├── SignedEntryUpdatepr_test.go ├── TimestampedEpochHead.pr.go ├── TimestampedEpochHeadpr_test.go ├── benchmarks.txt ├── clean.sh ├── client.pb.go ├── client.proto ├── clientpb_test.go ├── config.pb.go ├── config.proto ├── configpb_test.go ├── duration.go ├── duration.pb.go ├── duration.proto ├── duration_test.go ├── durationpb_test.go ├── encoded.go.template ├── encoded_test.go.template ├── generate.sh ├── keyid.go ├── keyid_test.go ├── keyserverconfig.pb.go ├── keyserverconfig.proto ├── keyserverconfigpb_test.go ├── keyserverlocal.pb.go ├── keyserverlocal.proto ├── keyserverlocalpb_test.go ├── marshal.go ├── protoc-gen-coname │ └── main.go ├── prune.sh ├── readme.md ├── replication.pb.go ├── replication.proto ├── replicationpb_test.go ├── time.go ├── time_test.go ├── timestamp.pb.go ├── timestamp.proto ├── timestamppb_test.go ├── tlsconfig.go ├── tlsconfig.pb.go ├── tlsconfig.proto ├── tlsconfig_test.go ├── tlsconfigpb_test.go ├── verifier.pb.go ├── verifier.proto ├── verifierconfig.pb.go ├── verifierconfig.proto ├── verifierconfigpb_test.go ├── verifierlocal.pb.go ├── verifierlocal.proto ├── verifierlocalpb_test.go └── verifierpb_test.go ├── verifier ├── deploy │ ├── gen-config │ │ └── main.go │ ├── gen-csr-id │ │ └── main.go │ └── sign-csr │ │ └── main.go ├── table.go ├── verifier.go └── verifierserver │ └── main.go └── vrf ├── prof.svg ├── vrf.go └── vrf_test.go /.gitcookies.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YahooArchive/coname/84592ddf867362896ad1183709ac0804516f776d/.gitcookies.enc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | #before_script: 4 | # - go vet ./... 5 | before_install: 6 | # Decrypts .gitcookies.sh which authenticates the user 7 | # for git to use when cloning from googlesource.com to 8 | # bypass "bandwidth rate limit exceeded" error. 9 | # See https://github.com/golang/go/issues/12933 for details 10 | # Skip for Pull Requests to avoid Travis failure. 11 | - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && openssl aes-256-cbc -K $encrypted_139df71bd95f_key -iv $encrypted_139df71bd95f_iv -in .gitcookies.enc -out .gitcookies -d && bash .gitcookies.sh || true' 12 | go: 13 | - 1.6.3 14 | - 1.7.3 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of End-to-End Keyserver authors for 2 | # copyright purposes. This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | # 9 | # Please keep the list sorted. 10 | 11 | Andres Erbsen 12 | Daniel Ziegler 13 | Google Inc. 14 | Yahoo Inc. 15 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Aditya Mahendrakar 13 | Adonis Fung 14 | Andres Erbsen 15 | Daniel Ziegler 16 | Dmitry Savintsev 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### coname [![Build Status](https://travis-ci.org/yahoo/coname.svg?branch=master)](https://travis-ci.org/yahoo/coname) [![GoDoc](https://godoc.org/github.com/yahoo/coname?status.svg)](http://godoc.org/github.com/yahoo/coname) 2 | 3 | 4 | This repository contains a WORK-IN-PROGRESS implementation of an EXPERIMENTAL 5 | cooperative keyserver design based on ideas from `dename` 6 | ([readme, code](https://github.com/andres-erbsen/dename), 7 | [talk](https://media.ccc.de/browse/congress/2014/31c3_-_6597_-_en_-_saal_2_-_201412301600_-_now_i_sprinkle_thee_with_crypto_dust_-_ryan_lackey_-_andres_erbsen_-_jurre_van_bergen_-_ladar_levison_-_equinox.html#video)) and 8 | CONIKS ([paper](https://eprint.iacr.org/2014/1004.pdf), 9 | [code](https://github.com/coniks-sys)). NO STABILITY is offered: things that are 10 | very likely going to change include the network protocol, the implementation, 11 | the internal interfaces, the import path, and the name. Sometime in the future 12 | this implementation might reach feature (and performance) parity with `dename`, 13 | along with a CONIKS-like username privacy layer and high-availability curated 14 | namespaces. 15 | 16 | ### development 17 | 18 | You need a [Golang development 19 | environment](https://golang.org/doc/install#download), a protocol buffer schema 20 | parser (`protoc`) that [understands 21 | protobuf3](https://github.com/google/protobuf) , [Go 22 | protobuf3](http://www.grpc.io/docs/installation/go.html) libraries, the 23 | [`gogoprotobuf`](https://github.com/gogo/protobuf#getting-started-give-me-the-speed-i-dont-care-about-the-rest) 24 | code generation tool and [grpc](http://www.grpc.io/) [for 25 | Go](https://github.com/grpc/grpc-go). On Arch Linux this comes down to `pacman 26 | -S go`, `aura -Ak protobuf3`, `go get github.com/yahoo/coname/... 27 | github.com/andres-erbsen/tlstestutil`. 28 | 29 | ### disclaimer 30 | 31 | As this project includes code (from `dename`) that I wrote and released as open 32 | source when I was employed by Google, here is a little disclaimer that I was 33 | asked to attach to the code: `This is not a Google project.` 34 | -------------------------------------------------------------------------------- /concurrent/oneshot-pubsub.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import "sync" 4 | 5 | // OneShotPubSub is used to return replies to the clients whose requests are 6 | // handled through the replication log. It's a sort of one-shot asynchronous 7 | // publish-subscribe system: Only one subscriber can wait on a particular uid, 8 | // and only one message can be sent on any given uid. Once the message is sent, 9 | // the subscriber is notified asynchronously and the channel associated with 10 | // that uid closes. Any further Notify calls on the same uid will return false 11 | // and have no effect. A uid should never be reused. 12 | type OneShotPubSub struct { 13 | sync.Mutex 14 | waiters map[uint64]chan<- interface{} 15 | } 16 | 17 | // NewOneShotPubSub initializes a OneShotPubSub. 18 | func NewOneShotPubSub() *OneShotPubSub { 19 | return &OneShotPubSub{waiters: make(map[uint64]chan<- interface{})} 20 | } 21 | 22 | // Wait waits for a value to be sent by Notify. 23 | func (p *OneShotPubSub) Wait(uid uint64) <-chan interface{} { 24 | p.Lock() 25 | defer p.Unlock() 26 | ch := make(chan interface{}, 1) 27 | p.waiters[uid] = ch 28 | return ch 29 | } 30 | 31 | // Notify tries to send v to the waiter uid and returns whether it did. 32 | func (p *OneShotPubSub) Notify(uid uint64, v interface{}) bool { 33 | p.Lock() 34 | defer p.Unlock() 35 | ch, ok := p.waiters[uid] 36 | if !ok { 37 | return false 38 | } 39 | ch <- v // never blocks because the waiter channels have a buffer of 1 40 | close(ch) 41 | delete(p.waiters, uid) 42 | return true 43 | } 44 | -------------------------------------------------------------------------------- /concurrent/oneshot-pubsub_test.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func TestOneShotPubSubSameID(t *testing.T) { 9 | d := "hello" 10 | w := NewOneShotPubSub() 11 | ch := w.Wait(10) 12 | defer func() { 13 | if v, ok := <-ch; !ok { 14 | t.Fatal("OneShotPubSub.Notify did not return anything") 15 | } else { 16 | if got, want := v, d; got != want { 17 | t.Fatalf("OneShotPubSub.Notify() got %q, want %q", got, want) 18 | } 19 | } 20 | }() 21 | if !w.Notify(10, d) { 22 | t.Fatal("OneShotPubSub.Notify() got false, wanted true") 23 | } 24 | } 25 | 26 | func TestOneShotPubSubInvalidID(t *testing.T) { 27 | d := "hello" 28 | w := NewOneShotPubSub() 29 | w.Wait(10) 30 | if w.Notify(11, d) { 31 | t.Fatal("OneShotPubSub.Notify() got true, wanted false") 32 | } 33 | } 34 | 35 | func TestOneShotPubSubMultpleIDs(t *testing.T) { 36 | ids := []uint64{1, 2, 5, 20} 37 | w := NewOneShotPubSub() 38 | var ch [4]<-chan interface{} 39 | for i, id := range ids { 40 | ch[i] = w.Wait(id) 41 | 42 | } 43 | defer func() { 44 | for i, id := range ids { 45 | if v, ok := <-ch[i]; !ok { 46 | t.Fatal("OneShotPubSub.Notify did not return anything") 47 | } else { 48 | d := "hello" + strconv.Itoa(int(id)) 49 | if got, want := v, d; got != want { 50 | t.Fatalf("OneShotPubSub.Notify() got %q, want %q", got, want) 51 | } 52 | } 53 | } 54 | }() 55 | for _, id := range ids { 56 | d := "hello" + strconv.Itoa(int(id)) 57 | if !w.Notify(id, d) { 58 | t.Fatal("OneShotPubSub.Notify() got false, wanted true") 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /concurrent/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package concurrent 16 | 17 | // PublishSubscribe implements the publish-subscribe pattern where messages have 18 | // uint64 "tags" (which could be thought of as "channels"). At any time, 19 | // messages can be published with Publish, and subscriptions to messages with a 20 | // particular tag can be created and stopped with Subscribe and Unsubscribe. 21 | // PublishSubscribe is somewhat asynchronous: Publish calls do not wait for the 22 | // value to be sent to subscribers. However, if a send to a subscribed channel 23 | // blocks, the entire state machine blocks; thus, subscribers should either 24 | // buffer or guarantee quick processing. Because of this, PublishSubscribe can 25 | // guarantee that subscriptions will receive all values published after the 26 | // Subscribe call and before the Unsubscribe call (with "after" and "before" 27 | // defined according to Go's memory model [https://golang.org/ref/mem]). 28 | type PublishSubscribe struct { 29 | messages chan broadcastMessage 30 | subscribers map[uint64]map[chan<- interface{}]struct{} 31 | 32 | subscribe chan subscription 33 | unsubscribe chan subscription 34 | stop chan struct{} 35 | } 36 | 37 | type broadcastMessage struct { 38 | tag uint64 39 | value interface{} 40 | } 41 | 42 | type subscription struct { 43 | tag uint64 44 | channel chan<- interface{} 45 | } 46 | 47 | func NewPublishSubscribe() *PublishSubscribe { 48 | p := &PublishSubscribe{ 49 | messages: make(chan broadcastMessage), 50 | subscribers: make(map[uint64]map[chan<- interface{}]struct{}), 51 | subscribe: make(chan subscription), 52 | unsubscribe: make(chan subscription), 53 | stop: make(chan struct{}), 54 | } 55 | go p.run() 56 | return p 57 | } 58 | 59 | func (p *PublishSubscribe) run() { 60 | defer close(p.messages) 61 | defer close(p.subscribe) 62 | defer close(p.unsubscribe) 63 | for { 64 | select { 65 | case m := <-p.messages: 66 | if subs, ok := p.subscribers[m.tag]; ok { 67 | for ch := range subs { 68 | ch <- m.value 69 | } 70 | } 71 | case s := <-p.subscribe: 72 | if _, ok := p.subscribers[s.tag]; !ok { 73 | p.subscribers[s.tag] = make(map[chan<- interface{}]struct{}) 74 | } 75 | p.subscribers[s.tag][s.channel] = struct{}{} 76 | case s := <-p.unsubscribe: 77 | delete(p.subscribers[s.tag], s.channel) 78 | if len(p.subscribers[s.tag]) == 0 { 79 | delete(p.subscribers, s.tag) 80 | } 81 | close(s.channel) 82 | case <-p.stop: 83 | for _, subs := range p.subscribers { 84 | for ch := range subs { 85 | close(ch) 86 | } 87 | } 88 | return 89 | } 90 | } 91 | } 92 | 93 | func (p *PublishSubscribe) Publish(tag uint64, value interface{}) { 94 | p.messages <- broadcastMessage{tag, value} 95 | } 96 | 97 | // Starts listening with the channel, which is guaranteed to receive all values 98 | // published with that tag after the Subscribe() call. 99 | func (p *PublishSubscribe) Subscribe(tag uint64, ch chan<- interface{}) { 100 | p.subscribe <- subscription{tag, ch} 101 | } 102 | 103 | // Stops listening with the channel. At some point after the Unsubscribe() call 104 | // begins, the channel will stop receiving values and be closed. Subscribers 105 | // are guaranteed to receive all values published with that tag before the 106 | // Unsubscribe() call. 107 | func (p *PublishSubscribe) Unsubscribe(tag uint64, ch chan<- interface{}) { 108 | p.unsubscribe <- subscription{tag, ch} 109 | } 110 | 111 | // No calls may be made on the broadcaster after Stop() is initiated. 112 | func (p *PublishSubscribe) Stop() { 113 | close(p.stop) 114 | } 115 | -------------------------------------------------------------------------------- /concurrent/pubsub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package concurrent 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestPublishWithoutSubscribers(t *testing.T) { 22 | b := NewPublishSubscribe() 23 | defer b.Stop() 24 | b.Publish(0, "hello") 25 | } 26 | 27 | func TestPublishOnDifferentChannel(t *testing.T) { 28 | b := NewPublishSubscribe() 29 | ch := make(chan interface{}) 30 | b.Subscribe(1, ch) 31 | b.Publish(2, "hi") 32 | b.Stop() 33 | if _, ok := <-ch; ok { 34 | t.Fatal("Got a value on the wrong channel") 35 | } 36 | } 37 | 38 | func TestPublishOnSameChannel(t *testing.T) { 39 | b := NewPublishSubscribe() 40 | ch := make(chan interface{}) 41 | b.Subscribe(1, ch) 42 | values := []interface{}{"hey", 3, "sup"} 43 | go func() { 44 | for _, v := range values { 45 | b.Publish(1, v) 46 | } 47 | b.Unsubscribe(1, ch) 48 | }() 49 | for _, want := range values { 50 | if v, ok := <-ch; !ok || v != want { 51 | if !ok { 52 | t.Fatal("Didn't get a value") 53 | } else { 54 | t.Fatalf("Wrong value: wanted %s, got %s", want, v) 55 | } 56 | } 57 | } 58 | if _, ok := <-ch; ok { 59 | t.Fatal("Channel didn't close") 60 | } 61 | b.Stop() 62 | } 63 | 64 | func TestPublishToMultipleSubscribers(t *testing.T) { 65 | const nSubs = 3 66 | b := NewPublishSubscribe() 67 | chs := []chan interface{}{} 68 | values := []interface{}{"hey", 3, "sup"} 69 | for i := 0; i < nSubs; i++ { 70 | ch := make(chan interface{}, len(values)) 71 | b.Subscribe(1, ch) 72 | chs = append(chs, ch) 73 | } 74 | for _, v := range values { 75 | b.Publish(1, v) 76 | } 77 | for _, ch := range chs { 78 | b.Unsubscribe(1, ch) 79 | } 80 | for _, ch := range chs { 81 | for _, want := range values { 82 | if v, ok := <-ch; !ok || v != want { 83 | if !ok { 84 | t.Fatal("Didn't get a value") 85 | } else { 86 | t.Fatalf("Wrong value: wanted %s, got %s", want, v) 87 | } 88 | } 89 | } 90 | if _, ok := <-ch; ok { 91 | t.Fatal("Channel didn't close") 92 | } 93 | } 94 | b.Stop() 95 | } 96 | -------------------------------------------------------------------------------- /concurrent/sequence-broadcast.go: -------------------------------------------------------------------------------- 1 | package concurrent 2 | 3 | import ( 4 | "container/list" 5 | "log" 6 | "sync" 7 | ) 8 | 9 | const sequenceBroadcastBufferSize = 1 10 | 11 | // SequenceBroadcast acts as an asynchronous message broker for verifiers 12 | // waiting for updates. It manages an ordered stream of messages, where each 13 | // message is assigned a uint64 index, but does not remember old messages. 14 | // Subscribers request to listen for messages in a particular range of indices, 15 | // but if those indices have already gone by, the subscription fails, and 16 | // clients must find the messages elsewhere. Also, subscription channels can be 17 | // closed at any time if the receivers block, so clients must be capable of 18 | // retrying. This enables Send to be non-blocking: it never waits for receivers 19 | // and is guaranteed not to block indefinitely. 20 | // The keyserver main calls Send each time a new log entry is available for 21 | // verifiers. A grpc handler would call Read with the verifier's request data 22 | // and use the returned channel to keep the verifier up to date. 23 | type SequenceBroadcast struct { 24 | sync.Mutex 25 | nextIndex uint64 26 | subscribers list.List // value type: sequenceSubscription 27 | } 28 | type sequenceSubscription struct { 29 | ch chan<- interface{} 30 | start, limit uint64 // [start, limit) 31 | } 32 | 33 | // NewSequenceBroadcast initializes a sequenceBroadcast such that the next 34 | // call to send will be interpreted as the value of sequenceLog[nextIndex]. 35 | func NewSequenceBroadcast(nextIndex uint64) *SequenceBroadcast { 36 | return &SequenceBroadcast{nextIndex: nextIndex} 37 | } 38 | 39 | // Send broadcasts m to all subscribers registered with sb. Send is a network 40 | // boundary: all inputs that are required to reproduce m MUST be synced to 41 | // stable storage before Send(m) is called. 42 | func (sb *SequenceBroadcast) Send(m interface{}) { 43 | sb.Lock() 44 | defer sb.Unlock() 45 | idx := sb.nextIndex 46 | sb.nextIndex++ 47 | for e := sb.subscribers.Front(); e != nil; { 48 | s := (e.Value).(sequenceSubscription) 49 | if idx < s.start { 50 | e = e.Next() 51 | continue 52 | } 53 | remove := false 54 | select { 55 | case s.ch <- m: 56 | remove = s.limit == sb.nextIndex 57 | default: 58 | // No channel in sb.subscribers is allowed to block. 59 | // Slow clients should just resubscribe. 60 | remove = true 61 | } 62 | // Advance first, then remove (or else e.Next() will always be nil) 63 | prev := e 64 | e = e.Next() 65 | if remove { 66 | close(s.ch) 67 | sb.subscribers.Remove(prev) 68 | } 69 | } 70 | } 71 | 72 | // Receive requests access to broadcasts for indexes [start, limit). If the 73 | // broadcast for start has already been sent, Receive returns nil. When there 74 | // are no more broadcasts left in the subscription (after limit-1 in the common 75 | // case), the channel returned by Receive is closed. The caller is expected to 76 | // consume the values from the returned channel quickly, if it blocks, the 77 | // channel may be closed before the limit is reached. 78 | func (sb *SequenceBroadcast) Receive(start, limit uint64) <-chan interface{} { 79 | if start > limit { 80 | log.Panicf("sb.Receive(%d, %d) (start > limit", start, limit) 81 | } 82 | ch := make(chan interface{}, sequenceBroadcastBufferSize) 83 | if start == limit { 84 | close(ch) 85 | return ch 86 | } 87 | 88 | sb.Lock() 89 | defer sb.Unlock() 90 | if start < sb.nextIndex { 91 | return nil // already broadcast, will never be sent again 92 | } 93 | sb.subscribers.PushBack(sequenceSubscription{ch: ch, start: start, limit: limit}) 94 | return ch 95 | } 96 | -------------------------------------------------------------------------------- /doc/reconfiguration-mess.md: -------------------------------------------------------------------------------- 1 | `etcd/raft` is sufficiently different from the "dissertation" raft that 2 | reconfiguration algorithms do not trivially carry over. For example, `etcd/raft` 3 | requires that a new replica being added must know the exact state of the cluster 4 | at the moment it is added. Similarly, replicas who are not yet aware of a recent 5 | reconfigurations are not able to receive commands from the new nodes: this means 6 | that a new node serving as a leader cannot help those replicas to catch up. 7 | Nodes not in the cluster cannot catch up with the cluster before being added -- 8 | and adding them would reduce availability. 9 | 10 | I am not aware of any way to achieve optimal-availability replication in this 11 | model ("optimal" is in with respect to CAP theorem: replicas in configuration 12 | c make progress if a majority of c is available). 13 | 14 | As a compromise, I propose mirroring `etcd`-s strategy even though its 15 | availability properties are suboptimal. Later (possibly after 16 | "dissertation"-style configuration change have been formally verified), we may 17 | choose to use that instead. The compromise should have no more issues than 18 | `etcd`. 19 | 20 | An `etcd`-style reconfiguration protocol for a keyserver cluster would go as 21 | follows (first see 22 | ): 23 | 24 | 1. Important invariants: 25 | 1. Reconfigurations only happen during epoch delimiters; each epoch 26 | delimiter is potentially a configuration change. 27 | 2. Every epoch is signed by the configuration as of *right after* its epoch 28 | delimiter. The public keys of the new replicas will be included in that 29 | epoch (in the form of an `AuthorizationPolicy`). 30 | 3. Each individual replicas only signs epochs according to what it has been 31 | told directly, ignoring raft state. However, an epoch is published when 32 | a simple majority of its configuration has signed it, allowing for lagging 33 | replicas to catch up without blocking signing of epochs. 34 | 4. An epoch delimiter will include a configuration change only if at least 35 | a simple majority of the previous configuration has indicated that they 36 | would sign epochs under the new configuration. This *approval* of a new 37 | configuration is signaled using a special log entry. If an epoch delimiter 38 | is proposed before the approvals are applied, it is handled as it was 39 | committed (without a configuration change), and the next epoch delimiter is 40 | proposed with the configuration change. 41 | 2. Operations flow for adding a replica 42 | 1. Inform at least a majority of the existing replicas of the new replica. Wait until 43 | this majority has performed a configuration change. 44 | 2. Seed the new replica with the exact configuration after step 1 as its 45 | initial Raftlog configuration, but keep let it have the true initial 46 | replicas for the Keyserver. 47 | 3. Start the new replica. 48 | 4. Wait for the new replica to catch up (after this, the RaftLog 49 | replicas must be the same as the Keyserver replicas). 50 | 5. Now it is safe to start a new configuration change. 51 | 3. Relevant RPC-s each replica implements and that are called from 2.1 (similar to `membersHandler` case for `POST`): 52 | 1. `AddReplica` 53 | 2. `RemoveReplica` 54 | 4. Relevant log messages: 55 | 1. EpochDelimiter: includes a configuration change (usually a NOP) 56 | 2. ApproveConfigurationChange indicates that a single replica would accept a new configuration. 57 | 5. Replica-local state: 58 | 1. map[replica]\*ApproveConfigurationChange -- the single (latest) 59 | configuration change that each replica has endorsed on the log. 60 | 2. bool -- approveCurrentConfiguration: set to true when transitioning into 61 | the configuration that the current replica approves of, or when told to move 62 | into that configuration (2.1) after some other majority has already accepted 63 | it. 64 | 65 | The code that implements a similar flow (as seen by by the members of the 66 | existing cluster) in `etcd` can be found in the following files: 67 | 1. `AddMember` (this is ends up being called when `etcdctl` issues an add). 68 | 2. `ApplyConfChange` gets called when that add has been committed to the log. 69 | 3. `configure` (called from `AddMember`) makes sure to wait with returning to `etcdctl` until the add has been committed and applied. 70 | 71 | The code that implements a similar flow (as seen by a new replica being added) 72 | in `etcd` can be found in the following files: 73 | 1. `NewServer` case for `!cfg.NewCluster`. 74 | 2. `ValidateClusterAndAssignIDs`. 75 | 3. `startNode` 76 | 77 | This is far very far from an ideal scenario, but I would like to stress that 78 | there are two very different kinds of awkwardness here. Having each replica only 79 | sign what it is told directly is a security property, and maintaining it 80 | requires a round of approval votes before switching to a new configuration. 81 | However, needing to provide each new replica with the exact cluster state as of 82 | when it was added (and risking outage otherwise) is an `etcd/raft` limitation. 83 | Using "dissertation" raft, one could simply have a replica catch up first and 84 | then issue the approvals, after which it would atomically be made a voting 85 | replica. 86 | 87 | All of this looks fragile enough that I wouldn't risk having something automatic 88 | manage this cluster... 89 | -------------------------------------------------------------------------------- /ed25519/ed25519.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package ed25519 implements the Ed25519 signature algorithm. See 6 | // http://ed25519.cr.yp.to/. 7 | package ed25519 8 | 9 | // This code is a port of the public domain, "ref10" implementation of ed25519 10 | // from SUPERCOP. 11 | 12 | import ( 13 | "crypto/sha512" 14 | "crypto/subtle" 15 | "io" 16 | 17 | "github.com/yahoo/coname/ed25519/edwards25519" 18 | ) 19 | 20 | const ( 21 | PublicKeySize = 32 22 | PrivateKeySize = 64 23 | SignatureSize = 64 24 | ) 25 | 26 | // GenerateKey generates a public/private key pair using randomness from rand. 27 | func GenerateKey(rand io.Reader) (publicKey *[PublicKeySize]byte, privateKey *[PrivateKeySize]byte, err error) { 28 | privateKey = new([64]byte) 29 | publicKey = new([32]byte) 30 | _, err = io.ReadFull(rand, privateKey[:32]) 31 | if err != nil { 32 | return nil, nil, err 33 | } 34 | 35 | h := sha512.New() 36 | h.Write(privateKey[:32]) 37 | digest := h.Sum(nil) 38 | 39 | digest[0] &= 248 40 | digest[31] &= 127 41 | digest[31] |= 64 42 | 43 | var A edwards25519.ExtendedGroupElement 44 | var hBytes [32]byte 45 | copy(hBytes[:], digest) 46 | edwards25519.GeScalarMultBase(&A, &hBytes) 47 | A.ToBytes(publicKey) 48 | 49 | copy(privateKey[32:], publicKey[:]) 50 | return 51 | } 52 | 53 | // Sign signs the message with privateKey and returns a signature. 54 | func Sign(privateKey *[PrivateKeySize]byte, message []byte) *[SignatureSize]byte { 55 | h := sha512.New() 56 | h.Write(privateKey[:32]) 57 | 58 | var digest1, messageDigest, hramDigest [64]byte 59 | var expandedSecretKey [32]byte 60 | h.Sum(digest1[:0]) 61 | copy(expandedSecretKey[:], digest1[:]) 62 | expandedSecretKey[0] &= 248 63 | expandedSecretKey[31] &= 63 64 | expandedSecretKey[31] |= 64 65 | 66 | h.Reset() 67 | h.Write(digest1[32:]) 68 | h.Write(message) 69 | h.Sum(messageDigest[:0]) 70 | 71 | var messageDigestReduced [32]byte 72 | edwards25519.ScReduce(&messageDigestReduced, &messageDigest) 73 | var R edwards25519.ExtendedGroupElement 74 | edwards25519.GeScalarMultBase(&R, &messageDigestReduced) 75 | 76 | var encodedR [32]byte 77 | R.ToBytes(&encodedR) 78 | 79 | h.Reset() 80 | h.Write(encodedR[:]) 81 | h.Write(privateKey[32:]) 82 | h.Write(message) 83 | h.Sum(hramDigest[:0]) 84 | var hramDigestReduced [32]byte 85 | edwards25519.ScReduce(&hramDigestReduced, &hramDigest) 86 | 87 | var s [32]byte 88 | edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced) 89 | 90 | signature := new([64]byte) 91 | copy(signature[:], encodedR[:]) 92 | copy(signature[32:], s[:]) 93 | return signature 94 | } 95 | 96 | // Verify returns true iff sig is a valid signature of message by publicKey. 97 | func Verify(publicKey *[PublicKeySize]byte, message []byte, sig *[SignatureSize]byte) bool { 98 | if sig[63]&224 != 0 { 99 | return false 100 | } 101 | 102 | var A edwards25519.ExtendedGroupElement 103 | if !A.FromBytes(publicKey) { 104 | return false 105 | } 106 | edwards25519.FeNeg(&A.X, &A.X) 107 | edwards25519.FeNeg(&A.T, &A.T) 108 | 109 | h := sha512.New() 110 | h.Write(sig[:32]) 111 | h.Write(publicKey[:]) 112 | h.Write(message) 113 | var digest [64]byte 114 | h.Sum(digest[:0]) 115 | 116 | var hReduced [32]byte 117 | edwards25519.ScReduce(&hReduced, &digest) 118 | 119 | var R edwards25519.ProjectiveGroupElement 120 | var b [32]byte 121 | copy(b[:], sig[32:]) 122 | edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b) 123 | 124 | var checkR [32]byte 125 | R.ToBytes(&checkR) 126 | return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1 127 | } 128 | -------------------------------------------------------------------------------- /ed25519/ed25519_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ed25519 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "compress/gzip" 11 | "encoding/hex" 12 | "io" 13 | "os" 14 | "strings" 15 | "testing" 16 | ) 17 | 18 | type zeroReader struct{} 19 | 20 | func (zeroReader) Read(buf []byte) (int, error) { 21 | for i := range buf { 22 | buf[i] = 0 23 | } 24 | return len(buf), nil 25 | } 26 | 27 | func TestSignVerify(t *testing.T) { 28 | var zero zeroReader 29 | public, private, _ := GenerateKey(zero) 30 | 31 | message := []byte("test message") 32 | sig := Sign(private, message) 33 | if !Verify(public, message, sig) { 34 | t.Errorf("valid signature rejected") 35 | } 36 | 37 | wrongMessage := []byte("wrong message") 38 | if Verify(public, wrongMessage, sig) { 39 | t.Errorf("signature of different message accepted") 40 | } 41 | } 42 | 43 | func TestGolden(t *testing.T) { 44 | // sign.input.gz is a selection of test cases from 45 | // http://ed25519.cr.yp.to/python/sign.input 46 | testDataZ, err := os.Open("testdata/sign.input.gz") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | defer testDataZ.Close() 51 | testData, err := gzip.NewReader(testDataZ) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | defer testData.Close() 56 | 57 | in := bufio.NewReaderSize(testData, 1<<12) 58 | lineNo := 0 59 | for { 60 | lineNo++ 61 | lineBytes, isPrefix, err := in.ReadLine() 62 | if isPrefix { 63 | t.Fatal("bufio buffer too small") 64 | } 65 | if err != nil { 66 | if err == io.EOF { 67 | break 68 | } 69 | t.Fatalf("error reading test data: %s", err) 70 | } 71 | 72 | line := string(lineBytes) 73 | parts := strings.Split(line, ":") 74 | if len(parts) != 5 { 75 | t.Fatalf("bad number of parts on line %d", lineNo) 76 | } 77 | 78 | privBytes, _ := hex.DecodeString(parts[0]) 79 | pubKeyBytes, _ := hex.DecodeString(parts[1]) 80 | msg, _ := hex.DecodeString(parts[2]) 81 | sig, _ := hex.DecodeString(parts[3]) 82 | // The signatures in the test vectors also include the message 83 | // at the end, but we just want R and S. 84 | sig = sig[:SignatureSize] 85 | 86 | if l := len(pubKeyBytes); l != PublicKeySize { 87 | t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l) 88 | } 89 | 90 | var priv [PrivateKeySize]byte 91 | copy(priv[:], privBytes) 92 | copy(priv[32:], pubKeyBytes) 93 | 94 | sig2 := Sign(&priv, msg) 95 | if !bytes.Equal(sig, sig2[:]) { 96 | t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2) 97 | } 98 | 99 | var pubKey [PublicKeySize]byte 100 | copy(pubKey[:], pubKeyBytes) 101 | if !Verify(&pubKey, msg, sig2) { 102 | t.Errorf("signature failed to verify on line %d", lineNo) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ed25519/extra25519/extra25519_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package extra25519 6 | 7 | import ( 8 | "bytes" 9 | "crypto/rand" 10 | "crypto/sha512" 11 | "testing" 12 | 13 | "github.com/yahoo/coname/ed25519" 14 | "github.com/yahoo/coname/ed25519/edwards25519" 15 | "golang.org/x/crypto/curve25519" 16 | ) 17 | 18 | func TestCurve25519Conversion(t *testing.T) { 19 | public, private, _ := ed25519.GenerateKey(rand.Reader) 20 | 21 | var curve25519Public, curve25519Public2, curve25519Private [32]byte 22 | PrivateKeyToCurve25519(&curve25519Private, private) 23 | curve25519.ScalarBaseMult(&curve25519Public, &curve25519Private) 24 | 25 | if !PublicKeyToCurve25519(&curve25519Public2, public) { 26 | t.Fatalf("PublicKeyToCurve25519 failed") 27 | } 28 | 29 | if !bytes.Equal(curve25519Public[:], curve25519Public2[:]) { 30 | t.Errorf("Values didn't match: curve25519 produced %x, conversion produced %x", curve25519Public[:], curve25519Public2[:]) 31 | } 32 | } 33 | 34 | func TestHashNoCollisions(t *testing.T) { 35 | type intpair struct { 36 | i int 37 | j uint 38 | } 39 | rainbow := make(map[[32]byte]intpair) 40 | N := 25 41 | if testing.Short() { 42 | N = 3 43 | } 44 | var h [32]byte 45 | // NOTE: hash values 0b100000000000... and 0b00000000000... both map to 46 | // the identity. this is a core part of the elligator function and not a 47 | // collision we need to worry about because an attacker would need to find 48 | // the preimages of these hashes to exploit it. 49 | h[0] = 1 50 | for i := 0; i < N; i++ { 51 | for j := uint(0); j < 257; j++ { 52 | if j < 256 { 53 | h[j>>3] ^= byte(1) << (j & 7) 54 | } 55 | 56 | var P edwards25519.ExtendedGroupElement 57 | HashToEdwards(&P, &h) 58 | var p [32]byte 59 | P.ToBytes(&p) 60 | if c, ok := rainbow[p]; ok { 61 | t.Fatalf("found collision: (%d, %d) and (%d, %d)", i, j, c.i, c.j) 62 | } 63 | rainbow[p] = intpair{i, j} 64 | 65 | if j < 256 { 66 | h[j>>3] ^= byte(1) << (j & 7) 67 | } 68 | } 69 | hh := sha512.Sum512(h[:]) // this package already imports sha512 70 | copy(h[:], hh[:]) 71 | } 72 | } 73 | 74 | func TestElligator(t *testing.T) { 75 | var publicKey, publicKey2, publicKey3, representative, privateKey [32]byte 76 | 77 | for i := 0; i < 1000; i++ { 78 | rand.Reader.Read(privateKey[:]) 79 | 80 | if !ScalarBaseMult(&publicKey, &representative, &privateKey) { 81 | continue 82 | } 83 | RepresentativeToPublicKey(&publicKey2, &representative) 84 | if !bytes.Equal(publicKey[:], publicKey2[:]) { 85 | t.Fatal("The resulting public key doesn't match the initial one.") 86 | } 87 | 88 | curve25519.ScalarBaseMult(&publicKey3, &privateKey) 89 | if !bytes.Equal(publicKey[:], publicKey3[:]) { 90 | t.Fatal("The public key doesn't match the value that curve25519 produced.") 91 | } 92 | } 93 | } 94 | 95 | func BenchmarkKeyGeneration(b *testing.B) { 96 | var publicKey, representative, privateKey [32]byte 97 | 98 | // Find the private key that results in a point that's in the image of the map. 99 | for { 100 | rand.Reader.Read(privateKey[:]) 101 | if ScalarBaseMult(&publicKey, &representative, &privateKey) { 102 | break 103 | } 104 | } 105 | 106 | b.ResetTimer() 107 | for i := 0; i < b.N; i++ { 108 | ScalarBaseMult(&publicKey, &representative, &privateKey) 109 | } 110 | } 111 | 112 | func BenchmarkMap(b *testing.B) { 113 | var publicKey, representative [32]byte 114 | rand.Reader.Read(representative[:]) 115 | 116 | b.ResetTimer() 117 | for i := 0; i < b.N; i++ { 118 | RepresentativeToPublicKey(&publicKey, &representative) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ed25519/marshal_test.go: -------------------------------------------------------------------------------- 1 | package ed25519 2 | 3 | import ( 4 | "crypto/rand" 5 | "testing" 6 | 7 | "github.com/yahoo/coname/ed25519/edwards25519" 8 | ) 9 | 10 | func TestUnmarshalMarshal(t *testing.T) { 11 | pk, _, _ := GenerateKey(rand.Reader) 12 | 13 | var A edwards25519.ExtendedGroupElement 14 | ret := A.FromBytes(pk) 15 | 16 | var pk2 [32]byte 17 | A.ToBytes(&pk2) 18 | 19 | if *pk != pk2 { 20 | _ = ret 21 | t.Errorf("FromBytes(%v)->ToBytes not idempotent:\n%x\nbytes:\n\t%x\n\t%x\ndelta: %x\n", ret, A, *pk, pk2, int(pk[31])-int(pk2[31])) 22 | } 23 | } 24 | 25 | func TestUnmarshalMarshalTwice(t *testing.T) { 26 | pk, _, _ := GenerateKey(rand.Reader) 27 | 28 | var A edwards25519.ExtendedGroupElement 29 | A.FromBytes(pk) 30 | 31 | var pk2 [32]byte 32 | A.ToBytes(&pk2) 33 | 34 | var B edwards25519.ExtendedGroupElement 35 | ret := B.FromBytes(&pk2) 36 | 37 | var pk3 [32]byte 38 | B.ToBytes(&pk3) 39 | 40 | if *pk != pk3 { 41 | t.Errorf("FromBytes(%v)->ToBytes not idempotent:\n%x\nbytes:\n\t%x\n\t%x\ndelta: %x\n", ret, A, *pk, pk3, int(pk[31])-int(pk2[31])) 42 | } 43 | } 44 | 45 | func TestUnmarshalMarshalNegative(t *testing.T) { 46 | pk, _, _ := GenerateKey(rand.Reader) 47 | 48 | var A edwards25519.ExtendedGroupElement 49 | ret := A.FromBytes(pk) 50 | 51 | var pk2 [32]byte 52 | A.ToBytes(&pk2) 53 | pk2[31] ^= 0x80 54 | 55 | if *pk == pk2 { 56 | t.Errorf("FromBytes(%v)->flipping sign did not change public key:\n%x\nbytes:\n\t%x\n\t%x\ndelta: %x\n", ret, A, *pk, pk2, int(pk[31])-int(pk2[31])) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ed25519/testdata/sign.input.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YahooArchive/coname/84592ddf867362896ad1183709ac0804516f776d/ed25519/testdata/sign.input.gz -------------------------------------------------------------------------------- /hkpfront/hkpfront.go: -------------------------------------------------------------------------------- 1 | package hkpfront 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | "sync" 8 | "time" 9 | 10 | "github.com/andres-erbsen/clock" 11 | "github.com/yahoo/coname" 12 | "github.com/yahoo/coname/proto" 13 | "golang.org/x/crypto/openpgp/armor" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | // HKPFront implements a unverified GnuPG-compatible HKP frontend for the 18 | // verified keyserver. 19 | type HKPFront struct { 20 | Lookup func(context.Context, *proto.LookupRequest) (*proto.LookupProof, error) 21 | Clk clock.Clock 22 | 23 | InsecureSkipVerify bool 24 | // Config must be set and valid if InsecureSkipVerify is not set 25 | Config *proto.Config 26 | 27 | // this is needed due to https://github.com/golang/go/issues/14374 28 | TLSConfig *tls.Config 29 | 30 | ln net.Listener 31 | sr http.Server 32 | 33 | connStateMu sync.Mutex 34 | connState map[net.Conn]http.ConnState 35 | 36 | stopOnce sync.Once 37 | stop chan struct{} 38 | waitStop sync.WaitGroup // server + all open connections 39 | } 40 | 41 | func (h *HKPFront) Start(ln net.Listener) { 42 | h.stop = make(chan struct{}) 43 | h.connState = make(map[net.Conn]http.ConnState) 44 | h.sr = http.Server{ 45 | Addr: ln.Addr().String(), 46 | Handler: h, 47 | ReadTimeout: 10 * time.Second, 48 | WriteTimeout: 10 * time.Second, 49 | MaxHeaderBytes: 4096, 50 | ConnState: h.updateConnState, 51 | TLSConfig: h.TLSConfig, 52 | } 53 | h.ln = ln 54 | h.waitStop.Add(1) 55 | go h.run() 56 | } 57 | 58 | func (h *HKPFront) run() { 59 | defer h.waitStop.Done() 60 | h.sr.Serve(h.ln) 61 | } 62 | 63 | func (h *HKPFront) Stop() { 64 | h.stopOnce.Do(func() { 65 | close(h.stop) 66 | h.sr.SetKeepAlivesEnabled(false) 67 | h.ln.Close() 68 | 69 | h.connStateMu.Lock() 70 | for c, s := range h.connState { 71 | if s == http.StateIdle { 72 | c.Close() 73 | } 74 | } 75 | h.connStateMu.Unlock() 76 | 77 | h.waitStop.Wait() 78 | }) 79 | } 80 | 81 | func (h *HKPFront) updateConnState(c net.Conn, s http.ConnState) { 82 | h.connStateMu.Lock() 83 | defer h.connStateMu.Unlock() 84 | h.connState[c] = s 85 | switch s { 86 | case http.StateNew: 87 | h.waitStop.Add(1) 88 | case http.StateIdle: 89 | select { 90 | case <-h.stop: 91 | c.Close() 92 | default: 93 | } 94 | case http.StateClosed, http.StateHijacked: 95 | h.waitStop.Done() 96 | delete(h.connState, c) 97 | } 98 | } 99 | 100 | func (h *HKPFront) ServeHTTP(w http.ResponseWriter, r *http.Request) { 101 | q := r.URL.Query() 102 | if r.Method != "GET" || r.URL.Path != "/pks/lookup" || len(q["op"]) != 1 || q["op"][0] != "get" || len(q["search"]) != 1 { 103 | http.Error(w, `this server only supports queries of the form "/pks/lookup?op=get&search="`, 501) 104 | return 105 | } 106 | user := q["search"][0] 107 | ctx := context.Background() 108 | 109 | var requiredSignatures *proto.QuorumExpr 110 | if !h.InsecureSkipVerify { 111 | realm, err := coname.GetRealmByUser(h.Config, user) 112 | if err != nil { 113 | http.Error(w, err.Error(), 400) 114 | return 115 | } 116 | switch realm.VerificationPolicy.PolicyType.(type) { 117 | case *proto.AuthorizationPolicy_Quorum: 118 | requiredSignatures = realm.VerificationPolicy.PolicyType.(*proto.AuthorizationPolicy_Quorum).Quorum 119 | default: 120 | } 121 | } 122 | 123 | pf, err := h.Lookup(ctx, &proto.LookupRequest{UserId: user, QuorumRequirement: requiredSignatures}) 124 | if err != nil { 125 | http.Error(w, err.Error(), 503) 126 | return 127 | } 128 | 129 | if !h.InsecureSkipVerify { 130 | coname.VerifyLookup(h.Config, user, pf, h.Clk.Now()) 131 | } 132 | 133 | if pf.Profile == nil || pf.Profile.Keys == nil { 134 | http.Error(w, `No results found: No keys found: unknown email`, 404) 135 | return 136 | } 137 | pgpKey, present := pf.Profile.Keys["pgp"] 138 | if !present { 139 | http.Error(w, `No results found: No keys found: the email is known to the keyserver, but the profile does not include an OpenPGP key`, 404) 140 | return 141 | } 142 | 143 | if _, mr := q["mr"]; mr { 144 | w.Header().Set("Content-Type", "application/pgp-keys") 145 | } 146 | aw, err := armor.Encode(w, "PGP PUBLIC KEY BLOCK", nil) 147 | if err != nil { 148 | http.Error(w, err.Error(), 500) 149 | return 150 | } 151 | _, err = aw.Write(pgpKey) 152 | if err != nil { 153 | http.Error(w, err.Error(), 500) 154 | return 155 | } 156 | if err := aw.Close(); err != nil { 157 | http.Error(w, err.Error(), 500) 158 | return 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /httpfront/httpfront.go: -------------------------------------------------------------------------------- 1 | package httpfront 2 | 3 | import ( 4 | //"errors" 5 | "crypto/tls" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "sync" 11 | "time" 12 | 13 | "github.com/maditya/protobuf/jsonpb" 14 | "github.com/yahoo/coname/proto" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | // HTTPFront implements a dumb http proxy for the keyserver grpc interface 19 | type HTTPFront struct { 20 | Lookup func(context.Context, *proto.LookupRequest) (*proto.LookupProof, error) 21 | Update func(context.Context, *proto.UpdateRequest) (*proto.LookupProof, error) 22 | SAMLRequest func() (string, error) 23 | OIDCRequest func(string, string) (string, error) 24 | InRotation func() bool 25 | IsAuthExpired func(error) bool 26 | 27 | // this is needed due to https://github.com/golang/go/issues/14374 28 | TLSConfig *tls.Config 29 | 30 | ln net.Listener 31 | sr http.Server 32 | 33 | connStateMu sync.Mutex 34 | connState map[net.Conn]http.ConnState 35 | 36 | stopOnce sync.Once 37 | stop chan struct{} 38 | waitStop sync.WaitGroup // server + all open connections 39 | } 40 | 41 | func (h *HTTPFront) Start(ln net.Listener) { 42 | h.stop = make(chan struct{}) 43 | h.connState = make(map[net.Conn]http.ConnState) 44 | h.sr = http.Server{ 45 | Addr: ln.Addr().String(), 46 | Handler: h, 47 | ReadTimeout: 10 * time.Second, 48 | WriteTimeout: 10 * time.Second, 49 | MaxHeaderBytes: 4096, 50 | ConnState: h.updateConnState, 51 | TLSConfig: h.TLSConfig, 52 | } 53 | h.ln = ln 54 | h.waitStop.Add(1) 55 | go h.run() 56 | } 57 | 58 | func (h *HTTPFront) run() { 59 | defer h.waitStop.Done() 60 | h.sr.Serve(h.ln) 61 | } 62 | 63 | func (h *HTTPFront) Stop() { 64 | h.stopOnce.Do(func() { 65 | close(h.stop) 66 | h.sr.SetKeepAlivesEnabled(false) 67 | h.ln.Close() 68 | 69 | h.connStateMu.Lock() 70 | for c, s := range h.connState { 71 | if s == http.StateIdle { 72 | c.Close() 73 | } 74 | } 75 | h.connStateMu.Unlock() 76 | 77 | h.waitStop.Wait() 78 | }) 79 | } 80 | 81 | func (h *HTTPFront) updateConnState(c net.Conn, s http.ConnState) { 82 | h.connStateMu.Lock() 83 | defer h.connStateMu.Unlock() 84 | h.connState[c] = s 85 | switch s { 86 | case http.StateNew: 87 | h.waitStop.Add(1) 88 | case http.StateIdle: 89 | select { 90 | case <-h.stop: 91 | c.Close() 92 | default: 93 | } 94 | case http.StateClosed, http.StateHijacked: 95 | h.waitStop.Done() 96 | delete(h.connState, c) 97 | } 98 | } 99 | 100 | func (h *HTTPFront) doLookup(b io.Reader, ctx context.Context) (*proto.LookupProof, error) { 101 | lr := &proto.LookupRequest{} 102 | err := jsonpb.Unmarshal(b, lr) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | pf, err := h.Lookup(ctx, lr) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return pf, nil 112 | 113 | } 114 | 115 | func (h *HTTPFront) doUpdate(b io.Reader, ctx context.Context) (*proto.LookupProof, error) { 116 | ur := &proto.UpdateRequest{} 117 | err := jsonpb.Unmarshal(b, ur) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | pf, err := h.Update(ctx, ur) 123 | if err != nil { 124 | return nil, err 125 | } 126 | return pf, nil 127 | 128 | } 129 | 130 | func (h *HTTPFront) ServeHTTP(w http.ResponseWriter, r *http.Request) { 131 | path := r.URL.Path 132 | method := r.Method 133 | 134 | // service healthcheck 135 | if (method == "HEAD" || method == "GET") && (path == "/status" || path == "/lb") { 136 | if !h.InRotation() { 137 | http.Error(w, `server out of rotation`, http.StatusNotFound) 138 | return 139 | } 140 | if method == "GET" { 141 | w.Write([]byte("OK")) 142 | } 143 | return 144 | } 145 | 146 | if method == "GET" && path == "/saml" { 147 | url, err := h.SAMLRequest() 148 | if err != nil { 149 | http.Error(w, err.Error(), http.StatusNotFound) 150 | return 151 | } 152 | http.Redirect(w, r, url, http.StatusFound) 153 | return 154 | } 155 | if method == "GET" && path == "/oidc" { 156 | u, err := url.ParseQuery(r.URL.RawQuery) 157 | if err != nil { 158 | http.Error(w, `error parsing query string`, http.StatusBadRequest) 159 | return 160 | } 161 | d := u.Get("domain") 162 | if d == "" { 163 | http.Error(w, `domain not found`, http.StatusBadRequest) 164 | return 165 | } 166 | 167 | url, err := h.OIDCRequest(d, "https://"+r.Host+"/oidcsso") 168 | if err != nil { 169 | http.Error(w, err.Error(), http.StatusNotFound) 170 | return 171 | } 172 | http.Redirect(w, r, url, http.StatusFound) 173 | } 174 | 175 | if method != "POST" || (path != "/lookup" && path != "/update") { 176 | http.Error(w, `this server only supports queries of the POST /lookup or POST /update`, http.StatusNotFound) 177 | return 178 | } 179 | pf := &proto.LookupProof{} 180 | var err error 181 | ctx := context.Background() 182 | if path == "/lookup" { 183 | pf, err = h.doLookup(r.Body, ctx) 184 | if err != nil { 185 | http.Error(w, err.Error(), http.StatusBadRequest) 186 | return 187 | } 188 | } else if path == "/update" { 189 | pf, err = h.doUpdate(r.Body, ctx) 190 | if err != nil { 191 | status := http.StatusBadRequest 192 | if h.IsAuthExpired(err) { 193 | status = http.StatusUnauthorized 194 | } 195 | http.Error(w, err.Error(), status) 196 | return 197 | } 198 | } 199 | // preserve the original field name 200 | marshaler := jsonpb.Marshaler{OrigName: true} 201 | err = marshaler.Marshal(w, pf) 202 | if err != nil { 203 | http.Error(w, `Internal server error`, http.StatusInternalServerError) 204 | return 205 | } 206 | w.Header().Set("Content-Type", "application/json") 207 | return 208 | } 209 | -------------------------------------------------------------------------------- /keyserver/coname-server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "log" 20 | "os" 21 | 22 | "github.com/maditya/protobuf/jsonpb" 23 | 24 | "github.com/yahoo/coname/keyserver" 25 | "github.com/yahoo/coname/proto" 26 | ) 27 | 28 | func main() { 29 | configPathPtr := flag.String("config", "config.json", "path to config file") 30 | flag.Parse() 31 | 32 | configReader, err := os.Open(*configPathPtr) 33 | if err != nil { 34 | log.Fatalf("Failed to open configuration file: %s", err) 35 | } 36 | cfg := &proto.ReplicaConfig{} 37 | err = jsonpb.Unmarshal(configReader, cfg) 38 | if err != nil { 39 | log.Fatalf("Failed to parse configuration file: %s", err) 40 | } 41 | keyserver.RunWithConfig(cfg) 42 | } 43 | -------------------------------------------------------------------------------- /keyserver/deploy/README: -------------------------------------------------------------------------------- 1 | Steps to set up a running (test) keyserver instance: 2 | 0. Get the certificate and private key for the CA that will be signing the 3 | keyserver's certificates. For testing purposes, it's easy to generate a CA 4 | using github.com/andres-erbsen/dename/utils/mkauthority (just "go get" it): 5 | mkauthority > ca.crt.pem 2> ca.key.pem 6 | 1. go run make_config.go ca.crt.pem ca.key.pem host0 host1 host2... 7 | where host0 and so on are the hostnames of the keyserver replicas. The 8 | hostnames are what 9 | a) the replicas bind to to listen for clients, verifiers, and other replicas, 10 | on the ports specified in the top of make_config.go 11 | b) the replicas dial to reach other replicas (on the Raft port) 12 | c) the clients try to reach (on the client port) 13 | Using different hostnames/IPs for different purposes is supported by the 14 | configuration files and server code; make_config.go just has to be changed. 15 | make_config.go outputs the following files: 16 | - vrf.vrfpublic: The raw 32-byte VRF public key 17 | - clientconfig.json: A JSON-serialized proto.Config which clients can use as 18 | their configuration file. The test client (cnclient/client.go) uses this. 19 | - client.key.pem: The PEM-encoded ECDSA TLS private key for a single client 20 | certificate generated and inserted into clientconfig.json for accessing the 21 | server. This is only to restrict access for testing purposes; one we reach 22 | actual production, we will only require client certificates for verifiers. 23 | - For each hostname, in a folder named after the host: 24 | - tls.key.pem: The PEM-encoded ECDSA TLS private key for the replica 25 | - vrf.vrfsecret: The raw 64-byte VRF secret key (shared across replicas) 26 | - signing.ed25519secret: The raw 64-byte ed25519 signing key for the replica 27 | - config.json: A JSON-serialized proto.ReplicaConfig configuration file for 28 | the server, which references the three secret key files above. 29 | 2. Upload the files in each hosts's directory to the host 30 | 3. Build keyserver/main and deploy it to each host 31 | 4. Start keyserver/main on each host 32 | -------------------------------------------------------------------------------- /keyserver/dkim/dkim.go: -------------------------------------------------------------------------------- 1 | package dkim 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/mail" 7 | "strings" 8 | "time" 9 | 10 | dkim "github.com/andres-erbsen/go-dkim" 11 | ) 12 | 13 | func CheckEmailProof(dkimMail []byte, toAddr, subjectPrefix string, LookupTXT func(string) ([]string, error), now func() time.Time) (email, binding string, err error) { 14 | dkimHeader, err := dkim.Verify(dkimMail, LookupTXT, now) 15 | if err != nil { 16 | return "", "", err 17 | } 18 | 19 | fromHeaderSigned := false 20 | toHeaderSigned := false 21 | subjectHeaderSigned := false 22 | for _, hdrName := range dkimHeader.Headers { 23 | switch strings.ToLower(hdrName) { 24 | case "to": 25 | toHeaderSigned = true 26 | case "from": 27 | fromHeaderSigned = true 28 | case "subject": 29 | subjectHeaderSigned = true 30 | } 31 | } 32 | if !toHeaderSigned { 33 | return "", "", fmt.Errorf("the To header is not signed") 34 | } 35 | if !fromHeaderSigned { 36 | return "", "", fmt.Errorf("the From header is not signed") 37 | } 38 | if !subjectHeaderSigned { 39 | return "", "", fmt.Errorf("the Subject header is not signed") 40 | } 41 | 42 | msg, err := mail.ReadMessage(bytes.NewReader(dkimMail)) 43 | if err != nil { 44 | return "", "", err 45 | } 46 | fromAddrs, err := msg.Header.AddressList("from") 47 | if err != nil { 48 | return "", "", err 49 | } 50 | if len(fromAddrs) != 1 { 51 | return "", "", fmt.Errorf("multiple from addresses") 52 | } 53 | email = fromAddrs[0].Address 54 | if !strings.HasSuffix(email, "@"+dkimHeader.Domain) { 55 | return "", "", fmt.Errorf("from address is not within the DKIM domain") 56 | } 57 | 58 | toAddrs, err := msg.Header.AddressList("to") 59 | if err != nil { 60 | return "", "", err 61 | } 62 | for _, dst := range toAddrs { 63 | addr := dst.Address 64 | switch { 65 | case addr == email: 66 | case addr == toAddr: 67 | default: 68 | return "", "", fmt.Errorf("unknown address %q on To line", addr) 69 | } 70 | } 71 | 72 | subject := msg.Header.Get("subject") 73 | if !strings.HasPrefix(subject, subjectPrefix) { 74 | return "", "", fmt.Errorf("subject line does not match the required format") 75 | } 76 | return email, subject[len(subjectPrefix):], nil 77 | } 78 | -------------------------------------------------------------------------------- /keyserver/kv/kv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package kv contains a generic interface for key-value databases with support 16 | // for batch writes. All operations are safe for concurrent use, atomic and 17 | // synchronously persistent. 18 | package kv 19 | 20 | // DB is an abstract ordered key-value store. All operations are assumed to be 21 | // synchronous, atomic and linearizable. This includes the following guarantee: 22 | // After Put(k, v) has returned, and as long as no other Put(k, ?) has been 23 | // called happened, Get(k) MUST return always v, regardless of whether the 24 | // process or the entire system has been reset in the meantime or very little 25 | // time has passed. To amortize the overhead of synchronous writes, DB offers 26 | // batch operations: Write(...) performs a series of Put-s atomically (and 27 | // possibly almost as fast as a single Put). 28 | type DB interface { 29 | Get(key []byte) ([]byte, error) 30 | Put(key, value []byte) error 31 | Delete(key []byte) error 32 | NewBatch() Batch 33 | Write(Batch) error 34 | NewIterator(*Range) Iterator 35 | 36 | ErrNotFound() error 37 | } 38 | 39 | // A Batch contains a sequence of Put-s waiting to be Write-n to a DB. 40 | type Batch interface { 41 | Reset() 42 | Put(key, value []byte) 43 | Delete(key []byte) 44 | } 45 | 46 | // Iterator is an abstract pointer to a DB entry. It must be valid to call 47 | // Error() after release. The boolean return values indicate whether the 48 | // requested entry exists. 49 | type Iterator interface { 50 | Key() []byte 51 | Value() []byte 52 | First() bool 53 | Next() bool 54 | Last() bool 55 | Release() 56 | Error() error 57 | } 58 | -------------------------------------------------------------------------------- /keyserver/kv/leveldbkv/leveldbkv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package leveldbkv implements the kv interface using leveldb 16 | package leveldbkv 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/syndtr/goleveldb/leveldb" 22 | "github.com/syndtr/goleveldb/leveldb/opt" 23 | "github.com/syndtr/goleveldb/leveldb/util" 24 | "github.com/yahoo/coname/keyserver/kv" 25 | ) 26 | 27 | type leveldbkv leveldb.DB 28 | 29 | // Wrap uses a leveldb.DB as a kv.DB the obvious way (and with Sync:true). 30 | func Wrap(db *leveldb.DB) kv.DB { 31 | return (*leveldbkv)(db) 32 | } 33 | 34 | func (db *leveldbkv) Get(key []byte) ([]byte, error) { 35 | return (*leveldb.DB)(db).Get(key, nil) 36 | } 37 | 38 | func (db *leveldbkv) Put(key, value []byte) error { 39 | return (*leveldb.DB)(db).Put(key, value, &opt.WriteOptions{Sync: true}) 40 | } 41 | 42 | func (db *leveldbkv) Delete(key []byte) error { 43 | return (*leveldb.DB)(db).Delete(key, &opt.WriteOptions{Sync: true}) 44 | } 45 | 46 | func (db *leveldbkv) NewBatch() kv.Batch { 47 | return new(leveldb.Batch) 48 | } 49 | 50 | func (db *leveldbkv) Write(b kv.Batch) error { 51 | wb, ok := b.(*leveldb.Batch) 52 | if !ok { 53 | return fmt.Errorf("leveldbkv.Write: expected *leveldb.Batch, got %T", b) 54 | } 55 | return (*leveldb.DB)(db).Write(wb, &opt.WriteOptions{Sync: true}) 56 | } 57 | 58 | func (db *leveldbkv) NewIterator(rg *kv.Range) kv.Iterator { 59 | return (*leveldb.DB)(db).NewIterator(&util.Range{Start: rg.Start, Limit: rg.Limit}, nil) 60 | } 61 | 62 | func (db *leveldbkv) ErrNotFound() error { 63 | return leveldb.ErrNotFound 64 | } 65 | -------------------------------------------------------------------------------- /keyserver/kv/logkv/logkv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package logkv 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "os" 21 | "strings" 22 | 23 | "github.com/yahoo/coname/keyserver/kv" 24 | "github.com/yahoo/coname/keyserver/kv/tracekv" 25 | ) 26 | 27 | type traceLogger log.Logger 28 | 29 | func toString(p tracekv.Update) string { 30 | if p.IsDeletion { 31 | return fmt.Sprintf("delete %q", p.Key) 32 | } else { 33 | return fmt.Sprintf("put %q = %q", p.Key, p.Value) 34 | } 35 | } 36 | 37 | func (l *traceLogger) put(p tracekv.Update) { 38 | (*log.Logger)(l).Print(toString(p)) 39 | } 40 | 41 | func (l *traceLogger) batch(ps []tracekv.Update) { 42 | var ss []string 43 | for _, p := range ps { 44 | ss = append(ss, toString(p)) 45 | } 46 | (*log.Logger)(l).Printf("batch{%s}", strings.Join(ss, "; ")) 47 | } 48 | 49 | // WithDefaultLogging logs all Update-s and Write-s to db to os.Stdout with 50 | // log.LstdFlags. 51 | func WithDefaultLogging(db kv.DB) kv.DB { 52 | return WithLogging(db, log.New(os.Stdout, "", log.LstdFlags)) 53 | } 54 | 55 | // WithLogging logs all Update-s and Write-s to db to l. 56 | func WithLogging(db kv.DB, l *log.Logger) kv.DB { 57 | trace := (*traceLogger)(l) 58 | return tracekv.WithTracing(db, trace.put, trace.batch) 59 | } 60 | -------------------------------------------------------------------------------- /keyserver/kv/range.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Copyright 2012 Suryandaru Triandana 16 | // Modified in 2015 by Andres Erbsen 17 | // All rights reserved. 18 | // 19 | // Redistribution and use in source and binary forms, with or without 20 | // modification, are permitted provided that the following conditions are 21 | // met: 22 | // 23 | // * Redistributions of source code must retain the above copyright 24 | // notice, this list of conditions and the following disclaimer. 25 | // * Redistributions in binary form must reproduce the above copyright 26 | // notice, this list of conditions and the following disclaimer in the 27 | // documentation and/or other materials provided with the distribution. 28 | // 29 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | 41 | package kv 42 | 43 | // Range is a key range. 44 | type Range struct { 45 | // Start of the key range, included in the range. 46 | Start []byte 47 | 48 | // Limit of the key range, not included in the range. nil indicates no limit. 49 | Limit []byte 50 | } 51 | 52 | // IncrementKey returns the lexicographically first DB key which is greater 53 | // than all keys prefixed by "prefix". Following the Range.Limit convention, 54 | // IncrementKey may return nil, a sentinel value that is to be interpreted as 55 | // greater than all keys. 56 | func IncrementKey(prefix []byte) []byte { 57 | for i := len(prefix) - 1; i >= 0; i-- { 58 | c := prefix[i] 59 | if c < 0xff { 60 | limit := make([]byte, i+1) 61 | copy(limit, prefix) 62 | limit[i] = c + 1 63 | return limit 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | // BytesPrefix returns key range that satisfy the given prefix. 70 | func BytesPrefix(prefix []byte) *Range { 71 | return &Range{prefix, IncrementKey(prefix)} 72 | } 73 | -------------------------------------------------------------------------------- /keyserver/kv/tracekv/tracekv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | // Package tracekv implements a tracing wrapper for kv.DB 16 | package tracekv 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/yahoo/coname/keyserver/kv" 22 | ) 23 | 24 | type tracekv struct { 25 | kv.DB 26 | traceUpdate func(Update) 27 | traceBatch func([]Update) 28 | } 29 | 30 | // WithTracing returns a kv.DB that calls traceUpdate on every Put or Delete 31 | // and traceBatch on every Write. 32 | func WithTracing(db kv.DB, traceUpdate func(Update), traceBatch func([]Update)) kv.DB { 33 | return tracekv{db, traceUpdate, traceBatch} 34 | } 35 | 36 | func mapUpdate(f func(Update)) func([]Update) { 37 | return func(ps []Update) { 38 | for _, p := range ps { 39 | f(p) 40 | } 41 | } 42 | } 43 | 44 | // WithSimpleTracing returns a kv.DB that calls traceUpdate on every Put 45 | // (regardless of whether it is issued by itself or during a Batch Write). 46 | func WithSimpleTracing(db kv.DB, traceUpdate func(Update)) kv.DB { 47 | return WithTracing(db, traceUpdate, mapUpdate(traceUpdate)) 48 | } 49 | 50 | func (db tracekv) Get(key []byte) ([]byte, error) { 51 | return db.DB.Get(key) 52 | } 53 | 54 | func (db tracekv) Put(key, value []byte) error { 55 | err := db.DB.Put(key, value) 56 | db.traceUpdate(Update{key, value, false}) 57 | return err 58 | } 59 | 60 | func (db tracekv) Delete(key []byte) error { 61 | err := db.DB.Delete(key) 62 | db.traceUpdate(Update{key, nil, true}) 63 | return err 64 | } 65 | 66 | func (db tracekv) NewBatch() kv.Batch { 67 | return &traceBatch{nil, db.DB.NewBatch()} 68 | } 69 | 70 | func (db tracekv) Write(b kv.Batch) error { 71 | wb, ok := b.(*traceBatch) 72 | if !ok { 73 | return fmt.Errorf("tracekv.Write: expected *tracekv.traceBatch, got %T", b) 74 | } 75 | err := db.DB.Write(wb.b) 76 | db.traceBatch(wb.operations) 77 | return err 78 | } 79 | 80 | func (db tracekv) NewIterator(rg *kv.Range) kv.Iterator { 81 | return db.DB.NewIterator(rg) 82 | } 83 | 84 | func (db tracekv) ErrNotFound() error { 85 | return db.DB.ErrNotFound() 86 | } 87 | 88 | type traceBatch struct { 89 | operations []Update 90 | b kv.Batch 91 | } 92 | 93 | // Update represents a single change to a kv.DB. 94 | type Update struct { 95 | Key, Value []byte 96 | IsDeletion bool 97 | } 98 | 99 | func (lb *traceBatch) Reset() { 100 | lb.operations = nil 101 | lb.b.Reset() 102 | } 103 | 104 | func (lb *traceBatch) Put(key, value []byte) { 105 | lb.operations = append(lb.operations, Update{key, value, false}) 106 | lb.b.Put(key, value) 107 | } 108 | 109 | func (lb *traceBatch) Delete(key []byte) { 110 | lb.operations = append(lb.operations, Update{key, nil, true}) 111 | lb.b.Delete(key) 112 | } 113 | -------------------------------------------------------------------------------- /keyserver/merkletree/notes.txt: -------------------------------------------------------------------------------- 1 | CoW scheme 2 | - Snapshots are just pointers to root nodes 3 | - Conceptually, nodes are addressed by an arbitrary node ID (which counts up sequentially) 4 | - For locality, we prepend IDs by the index in the tree 5 | - We build up the tree for a new epoch in-memory: 6 | - We lazy-load nodes that are accessed 7 | - Set() only touches changed nodes (nodes store childHashes so sibling nodes are not accessed), so all in-memory nodes are considered dirty 8 | - Flush() writes out loaded nodes and returns the new snapshot number 9 | 10 | 11 | Questions 12 | - Implement in purely functional style? (Probably still don't want to expose API 13 | that way) 14 | - Due to inertia I won't right now 15 | - Only set node IDs upon flush? 16 | - Cache nodes in-memory? Currently we do not (but LevelDB caches) 17 | - Would have to add a dirty bit so flush() doesn't write out all the cached nodes 18 | - Use binary encoding library rather than hand-serialization? 19 | - Support deletion? 20 | - Currently the lazy-loading logic breaks this 21 | 22 | Comments 23 | - Would be even nicer if you couldn't get access to a new snapshot number until 24 | the writebatch was flushed 25 | - Confusion about nodes being nil because they're not loaded vs they're actually 26 | not present 27 | - Confusion about when what is nil in proto.TreeProof 28 | - Trace construction very brittle (magically depends on lookup code) 29 | 30 | Hashing 31 | 0. Take CONIKS scheme 32 | 1. Add identifier for internal nodes (and maybe path, like Joe suggested?) 33 | 2. Commitment -> just hash of Entry (Profile includes nonce; our VRF hashes in 34 | the name so we don't have to worry about collisions) 35 | -------------------------------------------------------------------------------- /keyserver/oidc.go: -------------------------------------------------------------------------------- 1 | package keyserver 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | func (ks *Keyserver) OIDCRequest(domain string, uri string) (string, error) { 10 | 11 | for _, oc := range ks.oidcProofConfig { 12 | if _, ok := oc.allowedDomains[domain]; !ok { 13 | continue 14 | } 15 | v := url.Values{} 16 | v.Set("client_id", oc.oidcClient.ClientID) 17 | v.Set("response_type", "id_token") 18 | v.Set("redirect_uri", uri) 19 | v.Set("scope", oc.scope) 20 | // TODO: if this is absolutely required, generate a random one and validate it 21 | v.Set("nonce", "foobar") 22 | // replace '+' with '%20' due to https://github.com/golang/go/issues/4013 23 | return oc.oidcClient.Issuer + "/oauth2/request_auth?" + strings.Replace(v.Encode(), "+", "%20", -1), nil 24 | 25 | } 26 | return "", fmt.Errorf("domain %q NOT configured for OIDC auth", domain) 27 | } 28 | -------------------------------------------------------------------------------- /keyserver/oidc/token.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rsa" 7 | "encoding/base64" 8 | "encoding/binary" 9 | "encoding/json" 10 | "fmt" 11 | "math/big" 12 | "net/http" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // Client represents an openid connect client 18 | type Client struct { 19 | pubKeys map[string]interface{} 20 | Issuer string 21 | ClientID string 22 | DiscoveryURL string 23 | Validity time.Duration 24 | } 25 | 26 | // AddECDSAKey adds an ECDSA public key to the Client object 27 | func (c *Client) AddECDSAKey(curve elliptic.Curve, x, y *big.Int, kid string) { 28 | if c.pubKeys == nil { 29 | c.pubKeys = make(map[string]interface{}) 30 | } 31 | key := &ecdsa.PublicKey{ 32 | Curve: curve, 33 | X: x, 34 | Y: y, 35 | } 36 | c.pubKeys[kid] = key 37 | } 38 | 39 | // AddRSAKey adds a RSA public key to the Client object 40 | func (c *Client) AddRSAKey(n *big.Int, e int, kid string) { 41 | if c.pubKeys == nil { 42 | c.pubKeys = make(map[string]interface{}) 43 | } 44 | key := &rsa.PublicKey{ 45 | N: n, 46 | E: e, 47 | } 48 | c.pubKeys[kid] = key 49 | } 50 | 51 | // FetchPubKeys gets JWKS URI from the discovery document 52 | // Provider public keys are then fetched from JWKS URI 53 | // This could potentially be a goroutine running periodically 54 | // and syncing cached public keys 55 | func (c *Client) FetchPubKeys() error { 56 | 57 | type discoveryResp struct { 58 | Issuer string `json:"issuer"` 59 | JWKSURI string `json:"jwks_uri"` 60 | // other fields are ignored 61 | } 62 | 63 | type keysResp struct { 64 | Keys []struct { 65 | Kty string `json:"kty"` 66 | Alg string `json:"alg"` 67 | Use string `json:"use"` 68 | Kid string `json:"kid"` 69 | N string `json:"n"` 70 | E string `json:"e"` 71 | Crv string `json:"crv"` 72 | X string `json:"x"` 73 | Y string `json:"y"` 74 | } `json:"keys"` 75 | } 76 | dr, err := http.Get(c.DiscoveryURL) 77 | if err != nil { 78 | return err 79 | } 80 | defer dr.Body.Close() 81 | dj := discoveryResp{} 82 | json.NewDecoder(dr.Body).Decode(&dj) 83 | if dj.JWKSURI == "" { 84 | return fmt.Errorf("JWKS uri not found at %s", c.DiscoveryURL) 85 | } 86 | 87 | c.Issuer = dj.Issuer 88 | kr, err := http.Get(dj.JWKSURI) 89 | if err != nil { 90 | return err 91 | } 92 | defer kr.Body.Close() 93 | kj := keysResp{} 94 | json.NewDecoder(kr.Body).Decode(&kj) 95 | if len(kj.Keys) == 0 { 96 | return fmt.Errorf("No keys available at JWKS URI") 97 | } 98 | for _, obj := range kj.Keys { 99 | if obj.Kty == "RSA" { 100 | n := addOptionalPadding(obj.N) 101 | dn, err := base64.URLEncoding.DecodeString(n) 102 | if err != nil { 103 | return fmt.Errorf("failed to decode \"n\" value %s", obj.N) 104 | } 105 | nInt := (&big.Int{}).SetBytes(dn) 106 | e := addOptionalPadding(obj.E) 107 | de, err := base64.URLEncoding.DecodeString(e) 108 | if err != nil { 109 | return fmt.Errorf("failed to decode \"e\" value %s", obj.E) 110 | } 111 | if len(de) > 8 { 112 | return fmt.Errorf("Invalid length of decoded \"e\", expected <= 8 , got %d", len(de)) 113 | } 114 | deBytes := make([]byte, 8-len(de), 8) 115 | deBytes = append(deBytes, de...) 116 | eInt := int(binary.BigEndian.Uint64(deBytes)) 117 | c.AddRSAKey(nInt, eInt, obj.Kid) 118 | 119 | } else if obj.Kty == "EC" { 120 | x := addOptionalPadding(obj.X) 121 | dx, err := base64.URLEncoding.DecodeString(x) 122 | if err != nil { 123 | return fmt.Errorf("failed to decode \"X\" value %s", obj.X) 124 | } 125 | 126 | y := addOptionalPadding(obj.Y) 127 | dy, err := base64.URLEncoding.DecodeString(y) 128 | if err != nil { 129 | return fmt.Errorf("failed to decode \"Y\" value %s", obj.Y) 130 | } 131 | 132 | xInt := (&big.Int{}).SetBytes(dx) 133 | yInt := (&big.Int{}).SetBytes(dy) 134 | if obj.Crv == "P-256" { 135 | c.AddECDSAKey(elliptic.P256(), xInt, yInt, obj.Kid) 136 | } 137 | //TODO: add other curves 138 | } 139 | } 140 | return nil 141 | } 142 | 143 | // addOptionalPadding is a workaround for https://github.com/golang/go/issues/4237 144 | func addOptionalPadding(s string) string { 145 | if l := len(s) % 4; l > 0 { 146 | s += strings.Repeat("=", 4-l) 147 | } 148 | return s 149 | } 150 | -------------------------------------------------------------------------------- /keyserver/oidc/verify.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/dgrijalva/jwt-go" 8 | ) 9 | 10 | type ErrExpired struct { 11 | err error 12 | } 13 | 14 | func (e ErrExpired) Error() string { 15 | return e.err.Error() 16 | } 17 | 18 | // VerifyIDToken parses and validates the ID token received from the provider 19 | // Apart from the signature validation, we care about the following fields: 20 | // exp - token must not be expired 21 | // iat - token must not be older than a duration(specified in the config) 22 | // iss - must match issuer specified in the config 23 | // aud - must match the clientID specified in the config 24 | // email_verified - must be true 25 | // nonce - must be validated by the client 26 | func (c *Client) VerifyIDToken(token string) (email string, err error) { 27 | 28 | tok, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { 29 | kid, ok := t.Header["kid"].(string) 30 | if !ok { 31 | return nil, fmt.Errorf("\"kid\" not a string") 32 | } 33 | if c.pubKeys == nil { 34 | return nil, fmt.Errorf("No public key found to verify token") 35 | } 36 | return c.pubKeys[kid], nil 37 | }) 38 | if err != nil { 39 | if ve, ok := err.(*jwt.ValidationError); ok { 40 | if ve.Errors&jwt.ValidationErrorExpired != 0 { 41 | return "", &ErrExpired{err: err} 42 | } 43 | } 44 | return "", err 45 | } 46 | if tok.Valid { 47 | claims := tok.Claims.(jwt.MapClaims) 48 | aud, ok := claims["aud"].(string) 49 | if !ok { 50 | return "", fmt.Errorf("\"aud\" not a string") 51 | } 52 | if aud != c.ClientID { 53 | return "", fmt.Errorf("ClientID invalid - got (%s) wanted (%s)", aud, c.ClientID) 54 | } 55 | 56 | iss, ok := claims["iss"].(string) 57 | if !ok { 58 | return "", fmt.Errorf("\"iss\" not a string") 59 | } 60 | if iss != c.Issuer { 61 | return "", fmt.Errorf("iss invalid - got (%s) wanted (%s)", iss, c.Issuer) 62 | } 63 | 64 | iat, ok := claims["iat"].(float64) 65 | if !ok { 66 | return "", fmt.Errorf("\"iat\" not an integer") 67 | } 68 | 69 | if c.Validity != 0 { // skip this check if Validity=0 70 | tm := time.Unix(int64(iat), 0) 71 | if time.Now().Sub(tm) > c.Validity { 72 | return "", fmt.Errorf("\"iat\" too old") 73 | } 74 | } 75 | emailVrf, ok := claims["email_verified"].(bool) 76 | if !ok { 77 | return "", fmt.Errorf("\"email_verified\" missing or invalid type") 78 | } 79 | if !emailVrf { 80 | return "", fmt.Errorf("email not verified") 81 | } 82 | return claims["email"].(string), err 83 | } 84 | return "", fmt.Errorf("Invalid token") 85 | } 86 | -------------------------------------------------------------------------------- /keyserver/oidc_test.go: -------------------------------------------------------------------------------- 1 | package keyserver 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/yahoo/coname/keyserver/oidc" 7 | ) 8 | 9 | func TestOIDCRequest(t *testing.T) { 10 | c := "foo" 11 | i := "https://bar.com" 12 | d := "https://example.com" 13 | s := "read" 14 | expURL := "https://bar.com/oauth2/request_auth?client_id=foo&nonce=foobar&redirect_uri=ks.com&response_type=id_token&scope=read" 15 | o := &oidc.Client{ClientID: c, Issuer: i, DiscoveryURL: d} 16 | oc := OIDCConfig{oidcClient: o, scope: s} 17 | oc.allowedDomains = make(map[string]struct{}) 18 | for _, d := range []string{"foomail.com", "barmail.com"} { 19 | oc.allowedDomains[d] = struct{}{} 20 | } 21 | ks := Keyserver{oidcProofConfig: []OIDCConfig{oc}} 22 | url, err := ks.OIDCRequest("foomail.com", "ks.com") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | if got, want := url, expURL; got != want { 27 | t.Fatalf("url: got %q but wanted %q", got, want) 28 | } 29 | 30 | url, err = ks.OIDCRequest("foomailinvalid.com", "ks.com") 31 | if err == nil { 32 | t.Fatalf("OIDCRequest expected to fail, but got a url %q", url) 33 | } 34 | if got, want := err.Error(), "domain \"foomailinvalid.com\" NOT configured for OIDC auth"; got != want { 35 | t.Fatalf("OIDCRequest expected to fail with err %q , got %q", want, got) 36 | } 37 | 38 | url, err = ks.OIDCRequest("", "ks.com") 39 | if err == nil { 40 | t.Fatalf("OIDCRequest expected to fail, but got a url %q", url) 41 | } 42 | if got, want := err.Error(), "domain \"\" NOT configured for OIDC auth"; got != want { 43 | t.Fatalf("OIDCRequest expected to fail with err %q , got %q", want, got) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /keyserver/readme.md: -------------------------------------------------------------------------------- 1 | # End-to-End keyserver implementation 2 | 3 | The keyserver implementation is structured as a log-driven statemachine to 4 | support linearizable high-availability replication. The log in `replication` 5 | supports two main operations: Propose, which possibly appends an entry, and 6 | `WaitCommitted`, which returns new entries that are appended. All *guarantees* the server 7 | provides are provided through the log: for example, no new epoch is signed 8 | before all information about it has been committed. However, there are also 9 | *nice-to-haves*, for example reducing the load on the log, which are handled by 10 | sometimes holding back calls to `Propose` when it is likely that the appended 11 | value would be ignored anyway. 12 | 13 | The lifecycle of an update is as follows: 14 | 15 | 1. The client calls the update RPC 16 | 2. The update is `Propose`-d and appended to the log 17 | 3. `run` gets the update from `WaitCommitted`, calls `step` with it. `step` 18 | prepares an atomic change (`kv.Batch`) to the local database, and world-visible 19 | actions to be taken after the state change has been persisted (`deferredIO`). 20 | 4. `run` gets an epoch delimiter from the log (how it gets there will be 21 | described later) and calls `step` on it; `step` composes and signs a summary of 22 | the new keyserver state and pushes it into the log (the "epoch head"). 23 | 5. `run` gets receives the signatures on the new epoch head from a majority of replicas and combines them to form the keyserver signature. 24 | 6. The updates and the ratified state summary are made available to verifiers. 25 | 7. Verifiers push ratifications, the ratifications get proposed to the log. 26 | 8. `run` receives verifier ratifications from `WaitCommitted`, stores them in the local database. 27 | 28 | Epoch delimiters are inserted equivalentlry to the following pseudocode: 29 | 30 | for { 31 | if we are ALL 32 | likely the the leader of the cluster 33 | AND at least RetryEpochInterval has passed since the last time we `Propose`d an epoch delimiter 34 | AND either 35 | at least MinEpochInterval has passed since last ratification AND a new update has happened since the last delimiter 36 | at least MaxEpochInteval has passed since the last ratification 37 | then { 38 | `Propose` new EpochDelimiter with the next unused seq number and current time 39 | } 40 | } 41 | 42 | In the implementation, this busy-loop is turned into a non-blocking state 43 | machine by tracking changes to all inputs using channels. This implementation 44 | pattern is known as "sensitivity list" (in Verilog, for example) and is used for 45 | all proposals that need to be retried (most do!). 46 | 47 | A key-value database is used for local storage, its key space is split into 48 | tables based on the first byte of the key as specified in `tables.go`. 49 | Big-endian unsigned integers are used to preserve sorting order for range 50 | traversals and cache locality. 51 | 52 | The database interface also serves as a testing output (we really care that 53 | database writes happen before network requests, for example). 54 | -------------------------------------------------------------------------------- /keyserver/replication/kvlog/kvlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package kvlog 16 | 17 | import ( 18 | "encoding/binary" 19 | "sync" 20 | 21 | "github.com/yahoo/coname/keyserver/kv" 22 | "github.com/yahoo/coname/keyserver/replication" 23 | "golang.org/x/net/context" 24 | ) 25 | 26 | // kvLog implements replication.LogReplicator using a NOT NECESSARILY 27 | // REPLICATED persistent key-value database. 28 | type kvLog struct { 29 | db kv.DB 30 | prefix []byte 31 | nextIndex uint64 32 | skipBefore uint64 33 | 34 | leaderHintSet chan bool 35 | propose chan replication.LogEntry 36 | waitCommitted chan replication.LogEntry 37 | 38 | stopOnce sync.Once 39 | stop chan struct{} 40 | stopped chan struct{} 41 | } 42 | 43 | var _ replication.LogReplicator = (*kvLog)(nil) 44 | 45 | // New initializes a replication.LogReplicator using an already open kv.DB. 46 | func New(db kv.DB, prefix []byte) (replication.LogReplicator, error) { 47 | nextIndex := uint64(0) 48 | iter := db.NewIterator(kv.BytesPrefix(prefix)) 49 | if hasEntries := iter.Last(); hasEntries { 50 | nextIndex = binary.BigEndian.Uint64(iter.Key()[len(prefix):]) + 1 51 | } 52 | iter.Release() 53 | if err := iter.Error(); err != nil { 54 | return nil, err 55 | } 56 | 57 | leaderHintSet := make(chan bool, 1) 58 | leaderHintSet <- true 59 | return &kvLog{ 60 | db: db, 61 | prefix: prefix, 62 | nextIndex: nextIndex, 63 | propose: make(chan replication.LogEntry, 100), 64 | leaderHintSet: leaderHintSet, 65 | waitCommitted: make(chan replication.LogEntry), 66 | stop: make(chan struct{}), 67 | stopped: make(chan struct{}), 68 | }, nil 69 | } 70 | 71 | // Start implements replication.LogReplicator 72 | func (l *kvLog) Start(lo uint64) error { 73 | l.skipBefore = lo 74 | go l.run() 75 | return nil 76 | } 77 | 78 | // Stop implements replication.LogReplicator 79 | func (l *kvLog) Stop() error { 80 | l.stopOnce.Do(func() { 81 | close(l.stop) 82 | <-l.stopped 83 | }) 84 | return nil 85 | } 86 | 87 | // ApplyConfChange implements replication.LogReplicator 88 | func (l *kvLog) ApplyConfChange(*replication.ConfChange) { 89 | panic("ApplyConfChange: cannot reconfigure kvlog") 90 | } 91 | 92 | // Propose implements replication.LogReplicator 93 | // The following is true for kvLog.Propose but not necessarilty for other 94 | // implementations of replication.LogReplicator: If Propose(x) returns, then 95 | // after some amount of time without crashes, WaitCommitted returns x. 96 | func (l *kvLog) Propose(ctx context.Context, en replication.LogEntry) { 97 | if en.ConfChange != nil && en.ConfChange.Operation != replication.ConfChangeNOP { 98 | panic("Propose: cannot reconfigure kvlog") 99 | } 100 | select { 101 | case l.propose <- replication.LogEntry{Data: en.Data}: 102 | case <-l.stop: 103 | } 104 | } 105 | 106 | // WaitCommitted implements replication.LogReplicator 107 | func (l *kvLog) WaitCommitted() <-chan replication.LogEntry { 108 | return l.waitCommitted 109 | } 110 | 111 | // WaitCommitted implements replication.LogReplicator 112 | func (l *kvLog) LeaderHintSet() <-chan bool { 113 | return l.leaderHintSet 114 | } 115 | 116 | // GetCommitted implements replication.LogReplicator 117 | func (l *kvLog) GetCommitted(lo, hi, maxSize uint64) (ret []replication.LogEntry, err error) { 118 | size := uint64(0) 119 | for i := lo; i < hi; i++ { 120 | var v replication.LogEntry 121 | v, err = l.get(i) 122 | if err != nil { 123 | if err == l.db.ErrNotFound() { 124 | return ret, nil 125 | } 126 | return nil, err 127 | } 128 | if len(ret) != 0 && size+uint64(len(v.Data)) > maxSize { 129 | return 130 | } 131 | ret = append(ret, v) 132 | size += uint64(len(v.Data)) 133 | if size >= maxSize { 134 | return 135 | } 136 | } 137 | return 138 | } 139 | 140 | // get returns entry number i from l.db 141 | func (l *kvLog) get(i uint64) (le replication.LogEntry, err error) { 142 | dbkey := make([]byte, len(l.prefix)+8) 143 | copy(dbkey, l.prefix) 144 | binary.BigEndian.PutUint64(dbkey[len(l.prefix):], i) 145 | entryBytes, err := l.db.Get(dbkey[:]) 146 | if err != nil { 147 | return le, err 148 | } 149 | return replication.LogEntry{Data: entryBytes}, nil 150 | } 151 | 152 | // run is the CSP-style main of kvLog, all local struct fields (except 153 | // channels) belong exclusively to run while it is running. Method invocations 154 | // are signaled through channels. 155 | func (l *kvLog) run() { 156 | defer close(l.stopped) 157 | defer close(l.waitCommitted) 158 | for i := l.skipBefore; i < l.nextIndex; i++ { 159 | v, err := l.get(i) 160 | if err != nil { 161 | panic(err) 162 | } 163 | select { 164 | case <-l.stop: 165 | return 166 | case l.waitCommitted <- v: 167 | } 168 | } 169 | dbkey := make([]byte, len(l.prefix)+8) 170 | copy(dbkey, l.prefix) 171 | for { 172 | select { 173 | case <-l.stop: 174 | return 175 | case prop := <-l.propose: 176 | binary.BigEndian.PutUint64(dbkey[len(l.prefix):], l.nextIndex) 177 | l.nextIndex++ 178 | l.db.Put(dbkey[:], prop.Data) 179 | select { 180 | case <-l.stop: 181 | return 182 | case l.waitCommitted <- prop: 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /keyserver/replication/raftlog/nettestutil/nettestutil.go: -------------------------------------------------------------------------------- 1 | package nettestutil 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync/atomic" 7 | ) 8 | 9 | func New(n int) *Network { 10 | valve := make([][]uint32, n) 11 | for i := 0; i < n; i++ { 12 | valve[i] = make([]uint32, n) 13 | } 14 | return &Network{n: n, valve: valve} 15 | } 16 | 17 | type Network struct { 18 | n int 19 | valve [][]uint32 // 1=closed 20 | } 21 | 22 | func (n *Network) SetValve(i, j int, v bool) { 23 | var b uint32 24 | if v { 25 | b = 1 26 | } 27 | atomic.StoreUint32(&n.valve[i][j], b) 28 | } 29 | 30 | func (n *Network) GetValve(i, j int) bool { 31 | return atomic.LoadUint32(&n.valve[i][j]) == 1 32 | } 33 | 34 | // Partition partitions the network according to the following rules: 35 | // - nodes in the same slice are in the same partition 36 | // - nodes not in any slice are not connected to any node 37 | // - passing no slices represents a fully connected network 38 | func (n *Network) Partition(partitions ...[]int) { 39 | part := make(map[int]int, n.n) 40 | for p, members := range partitions { 41 | for _, i := range members { 42 | part[i] = p + 1 43 | } 44 | } 45 | for i := 0; i < n.n; i++ { 46 | for j := 0; j < n.n; j++ { 47 | li, _ := part[i] 48 | lj, _ := part[j] 49 | connected := len(partitions) == 0 || li == lj && li != 0 50 | n.SetValve(i, j, !connected) 51 | } 52 | } 53 | } 54 | 55 | func (n *Network) Wrap(c net.Conn, i, j int) net.Conn { 56 | return &mockConn{c, 0, &n.valve[i][j]} 57 | } 58 | 59 | type mockConn struct { 60 | net.Conn 61 | dead uint32 62 | kill *uint32 63 | } 64 | 65 | func (m *mockConn) Write(b []byte) (int, error) { 66 | dead := atomic.LoadUint32(&m.dead) 67 | kill := atomic.LoadUint32(m.kill) 68 | switch { 69 | case dead == 0 && kill == 0: 70 | return m.Conn.Write(b) 71 | case dead == 0 && kill == 1: 72 | atomic.StoreUint32(&m.dead, 1) 73 | return len(b), nil 74 | case dead == 1 && kill == 1: 75 | return len(b), nil 76 | case dead == 1 && kill == 0: 77 | m.Close() 78 | return 0, fmt.Errorf("networktestutil: this connection died long ago") 79 | } 80 | panic("unreachable") 81 | } 82 | 83 | var _ net.Conn = (*mockConn)(nil) 84 | -------------------------------------------------------------------------------- /keyserver/replication/raftlog/proto/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014-2015 The Dename Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | set -euo pipefail 17 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 18 | cd "$DIR" 19 | 20 | rm *.pb.go || true 21 | -------------------------------------------------------------------------------- /keyserver/replication/raftlog/proto/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014-2015 The Dename Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | set -euo pipefail 17 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 18 | cd "$DIR" 19 | 20 | protoc --gogofast_out=plugins=grpc:. --proto_path=${GOPATH}/src:. *.proto 21 | -------------------------------------------------------------------------------- /keyserver/replication/raftlog/proto/raftrpc.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/coreos/etcd/raft/raftpb/raft.proto"; 18 | 19 | service Raft { 20 | rpc Step(raftpb.Message) returns (Nothing); 21 | } 22 | 23 | message Nothing { 24 | } 25 | -------------------------------------------------------------------------------- /keyserver/replication/replication.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package replication 16 | 17 | import ( 18 | "golang.org/x/net/context" 19 | ) 20 | 21 | type ConfChangeType uint32 22 | 23 | const ( 24 | ConfChangeNOP ConfChangeType = 0 25 | ConfChangeAddNode ConfChangeType = 1 26 | ConfChangeRemoveNode ConfChangeType = 2 27 | ConfChangeUpdateNode ConfChangeType = 3 28 | ) 29 | 30 | type ConfChange struct { 31 | Operation ConfChangeType 32 | NodeID uint64 33 | } 34 | 35 | // LogEntry specifies an action and OPTIONALLY a change to the cluster 36 | // configuration. It is MANDATORY that all configuration changes are negotiated 37 | // using the ConfChange fields, and there must be a corresponding 38 | // ApplyConfChange for each ConfChange entry that gets committed. However, the 39 | // application may log-deterministically choose to apply a NOP conf change 40 | // instead of any other conf change. 41 | type LogEntry struct { 42 | Data []byte 43 | ConfChange *ConfChange 44 | } 45 | 46 | // LogReplicator is a generic interface to state-machine replication logs. The 47 | // log is a mapping from uint64 slot indices to []byte entries in which all 48 | // entries that have been committed are reliably persistent even throughout 49 | // machine crashes and data losses at a limited number of replicas. This is 50 | // achieved by trading off availability: proposing a new entry does not 51 | // necessarily mean it will be committed. One would use this interface 52 | // similarly to a local write-ahead log, except that this interface does not 53 | // support log compaction (it is intended for use when the entire log needs to 54 | // be kept around anyway). Returned nil entries should be ignored. 55 | // Start(lo) must be called exactly once before any other method is called, and 56 | // no methods must be called after Stop is called. The other three methods may 57 | // be called concurrently. 58 | type LogReplicator interface { 59 | // Start sets an internal field lo; later WaitCommitted will return entries 60 | // with indices >= lo. Start must be called before any other methods are. 61 | Start(lo uint64) error 62 | 63 | // Propose moves to append data to the log. It is not guaranteed that the 64 | // entry will get appended, though, due to node or network failures. 65 | // data.* : *mut // ownership of everything pointed to by the entry is transferred to LogReplicator 66 | Propose(ctx context.Context, data LogEntry) 67 | 68 | // ApplyConfChange MUST be called to notify LogReplicator that a committed 69 | // conf change has been processed (and thus a new one may be committed). 70 | ApplyConfChange(*ConfChange) 71 | 72 | // GetCommitted loads committed entries for post-replication distribution: 73 | // 1. The first returned entry corresponds to Index = lo 74 | // 2. All returned entries are consecutive 75 | // 3. No entry with Index >= hi is returned 76 | // 4. At least one entry is returned, if there is any. 77 | // 5. After that, no more than maxSize total bytes are returned (the first 78 | // entry counts towards the max size but is always returned) 79 | // ret: []&[]byte // All returned values are read-only for the caller. 80 | GetCommitted(lo, hi, maxSize uint64) ([]LogEntry, error) 81 | 82 | // WaitCommitted returns a channel that returns new committed entries, 83 | // starting with the index passed to Start. The caller MUST handler 84 | // .ConfChange if it is set on any returned entries. 85 | // All calls return the same channel. 86 | // ch : chan (&[]byte) // all read values are read-only to the caller 87 | WaitCommitted() <-chan LogEntry 88 | 89 | // LeaderHintSet returns a channel that reads true when it becomes likely 90 | // this replica is the leader of the cluster, and false when it is no onger 91 | // likely. Users MUST NOT rely on this for correctness. For example, it is 92 | // totally possible for two replicas have leaderHint=true at the same time. 93 | LeaderHintSet() <-chan bool 94 | 95 | // Stop cleanly stops logging requests. No calls to Propose or 96 | // GetCommitted must be started after Stop has been called (and the values 97 | // handed to ongoing Propose calls may not get committed). WaitCommitted 98 | // channel is closed. 99 | Stop() error 100 | } 101 | -------------------------------------------------------------------------------- /keyserver/saml.go: -------------------------------------------------------------------------------- 1 | package keyserver 2 | 3 | import ( 4 | "fmt" 5 | "github.com/yahoo/coname/keyserver/saml" 6 | ) 7 | 8 | type SAMLReq struct { 9 | Payload string 10 | IDPSSOURL string 11 | } 12 | 13 | // SAMLRequest constructs the redirect URL with SAMLRequest 14 | // as a query string parameter 15 | func (ks *Keyserver) SAMLRequest() (string, error) { 16 | if len(ks.samlProofAllowedDomains) == 0 { 17 | return "", fmt.Errorf("No domains configured for SAML auth") 18 | } 19 | payload, err := saml.GenerateSAMLRequest(ks.samlProofIDPCert, ks.samlProofSPKey, ks.samlProofConsumerServiceURL, ks.samlProofIDPSSOURL) 20 | if err != nil { 21 | return "", err 22 | } 23 | return ks.samlProofIDPSSOURL + "?SAMLRequest=" + payload, nil 24 | } 25 | -------------------------------------------------------------------------------- /keyserver/saml/saml.go: -------------------------------------------------------------------------------- 1 | package saml 2 | 3 | import ( 4 | "crypto" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "time" 11 | 12 | saml "github.com/maditya/go-saml" 13 | ) 14 | 15 | type ErrExpired struct { 16 | err error 17 | } 18 | 19 | func (e ErrExpired) Error() string { 20 | return e.err.Error() 21 | } 22 | 23 | func VerifySAMLResponse(payload string, idPCert *x509.Certificate, consumerServiceURL string, attributeName string, validity time.Duration) (string, error) { 24 | resp, err := saml.ParseEncodedResponse(payload) 25 | if err != nil { 26 | return "", err 27 | } 28 | err = resp.Validate(&saml.ServiceProviderConfig{ 29 | IDPCert: idPCert, 30 | AssertionConsumerServiceURL: consumerServiceURL, 31 | AssertionValidity: validity, 32 | }) 33 | if err != nil { 34 | if saml.IsExpired(err) { 35 | return "", &ErrExpired{err: err} 36 | } 37 | return "", err 38 | } 39 | email := resp.GetAttribute(attributeName) 40 | if email == "" { 41 | return "", fmt.Errorf("Attribute %q not found or value is empty in SAML response", attributeName) 42 | } 43 | return email, nil 44 | } 45 | 46 | func GenerateSAMLRequest(spCert *x509.Certificate, spKey crypto.PrivateKey, consumerServiceURL string, idPSSOURL string) (string, error) { 47 | sp := saml.ServiceProviderConfig{ 48 | Cert: spCert, 49 | AssertionConsumerServiceURL: consumerServiceURL, 50 | IDPSSOURL: idPSSOURL, 51 | PrivateKey: spKey, 52 | } 53 | authnRequest, err := sp.GetAuthnRequest() 54 | if err != nil { 55 | return "", err 56 | } 57 | b64SignedXML, err := authnRequest.CompressedEncodedSignedString(sp.PrivateKey) 58 | if err != nil { 59 | return "", err 60 | } 61 | 62 | // url encode the payload 63 | v := url.Values{} 64 | v.Set("", b64SignedXML) 65 | ue := v.Encode()[1:] // strip the leading "=" 66 | 67 | return ue, nil 68 | } 69 | 70 | func FetchIDPInfo(metadataURL string) (string, *x509.Certificate, error) { 71 | c := http.Client{Timeout: 10 * time.Second} 72 | url, err := url.Parse(metadataURL) 73 | if err != nil { 74 | return "", nil, err 75 | } 76 | resp, err := c.Do(&http.Request{URL: url}) 77 | if err != nil { 78 | return "", nil, err 79 | } 80 | defer resp.Body.Close() 81 | metadata, err := ioutil.ReadAll(resp.Body) 82 | if err != nil { 83 | return "", nil, err 84 | } 85 | return saml.ParseIDPMetadata(metadata) 86 | } 87 | -------------------------------------------------------------------------------- /keyserver/saml/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDKDCCAhACCQCe8LgISPCEWzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTEOMAwGA1UEChMFWWFo 4 | b28xFjAUBgNVBAMTDWlkcC55YWhvby5jb20wHhcNMTYwMzE1MTc1MjMxWhcNMTcw 5 | MzE1MTc1MjMxWjBWMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcT 6 | CVN1bm55dmFsZTEOMAwGA1UEChMFWWFob28xFjAUBgNVBAMTDWlkcC55YWhvby5j 7 | b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9pdVVP5NndPR1Tgkw 8 | U2kHLii/M+7bZHpgH7e08HUOTf/Vz4Yj/+lrpgxT+cM7r7HowWqv2XeM4P3b45Vm 9 | O6tjafuPT+uVWA085S4PoNLGyz5+rtU7c1LuyKLQfOsVATmno0YoS7E5hDOjik5J 10 | FGsE1ongvRwa/0FkGPLz7NWgY/K79WqKt5V51As9wASQGbt3Sy0vrT91eeIyIjzL 11 | Tf2oQmY5SHJRVA56i5kQKI9VLbBzKOSA6RraHK4msodGLqP9I50eFVcxH9XJkJEX 12 | f4EdB5b8omTkmkJRHGHdRez7W/sKNdwbv8COrvUBQ45BJK2y7J9m337o1qJpx8nk 13 | xTinAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAKjhfp62ciUoMusEw7LY/KaMOajV 14 | Rv0ign9uagQcdIoQ6GbIvAJErLDRMEw8q3P1jBDJ0tMU5CAozi4Axp4v2KYBv7j7 15 | 3sXnzJ9v4Krm+RzcGj4vQUDbQrFejg/oHHdQ+cyDzQvP2mk+pjbfJxgG0f0KsVSj 16 | z9p/hNX705q8hZHalQ0wWhLSiG13Lh3tkLHMg8IsFjZ4VG3JHI1KCITfRAanO1+K 17 | AurkIoD8QofRIQcCnXj/cZ3DyQrTjmQD185xpQ2BTFoAWhXob6QwQMQ5jZ1xhaUs 18 | WLBnCMVXW4W5zppjOvawcTpmelh1Sb2cc+zo3xZcdAfNhELf33NW4KA4Xc8= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /keyserver/saml/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAvaXVVT+TZ3T0dU4JMFNpBy4ovzPu22R6YB+3tPB1Dk3/1c+G 3 | I//pa6YMU/nDO6+x6MFqr9l3jOD92+OVZjurY2n7j0/rlVgNPOUuD6DSxss+fq7V 4 | O3NS7sii0HzrFQE5p6NGKEuxOYQzo4pOSRRrBNaJ4L0cGv9BZBjy8+zVoGPyu/Vq 5 | ireVedQLPcAEkBm7d0stL60/dXniMiI8y039qEJmOUhyUVQOeouZECiPVS2wcyjk 6 | gOka2hyuJrKHRi6j/SOdHhVXMR/VyZCRF3+BHQeW/KJk5JpCURxh3UXs+1v7CjXc 7 | G7/Ajq71AUOOQSStsuyfZt9+6NaiacfJ5MU4pwIDAQABAoIBAQCQhE+WH9VfEDO9 8 | NFMyPU2ayCUK1zNcgivzFhhyzI4W8X9UWkjZJAmnG6jnUTviPtevC70V9fzxIP6y 9 | UrIlbrEDF9837WoTqz6jOe2owR/9lyGvfeFF5XK8Pa48fATUQ9PAmF49FLkfoEuN 10 | gzZ24BVRYX8+AD3roLoe4hg5oy+DgXPlhsO9H0zQCGParD35nyQMuo0nawtEAWhv 11 | +I/p81xVjl0I5i+kaUWZ3WIcAWpvRMjSAw0r+aM90iJ99tyvbc+f4LX6dpOVcKPm 12 | lNqA5DlOGKbZSPj9v4gh8OjBFmy14JmB4kNLih+Z+kDklDQtj7br1vufrFEShrba 13 | QuFaRWRJAoGBAPsgRBP00MPrXXizsXXUUafOXxZD4QSBA8lr9/5Ogc/3GBPHyVVR 14 | Z4L8W1acBHLBsGcQN9kLEt+6jdF7Ku0iEd/vvUh+rjKN73bunh/ROaGrX48a5jeC 15 | ttyXmUDZo7phBzltaw6DShoqSyMJgprJt8hux1kHyRqbKqxixEDuyU7lAoGBAMFU 16 | G/QcD3VOcWKsAuXI51T99IXBtO8cEmxx033Ok7/d7CKKAMduwTbfFMdqkBre6weg 17 | HdMyV9eMJCbEPZVxsUxGB9UkXqjFgwa8wtDgaiTMOo8l9WSPgzZ5tpSnGjRimq3D 18 | DeBkzq7/jmCXOC8mebFeAxt+ab4fcjQzjN/DvWSbAoGBAI8kOSnW5hiG1GS0qHat 19 | a03nSDP8xQo4L9xj+puuTDgqdELyIkTLfeEfz0By9eYjmVVl3S+OLFfyklvRhQpd 20 | 3Sp0EMR90PVoChMUuHUSYXNRp8p+XrNNgsYCc4yPgc/+9os5X0iKdXasnN9/0D5C 21 | tMs+ucp5sZvEznIsaglJEt4xAoGAWkd4tknUPidfc4B36e4jeOEOqteGMpKdV97f 22 | sEQjYtmzR33CV181AvaxsV3l5WZrwqfWxaUguBtNVn0VdRAeh0u8pJ/TRGTXhvHm 23 | 17u0VXK5ddd3/Hm4AFxeEpTl+8iACtynQj5Q9g+I4To8lmgVM+p+8PvbTVlJ726T 24 | PnsdmjkCgYBqce0482au4mWJqbXiIFiy6kc8z/oBRtwCRH17Rr5k9xR28AAsoYhz 25 | xv9ptqipsCNiIrZ0EweuBYPh5IS0hr5RU99hCZUVNUT5wshx8EfWWgw3U9NkBFvi 26 | 1dFrhYevqluxwNH2GkhIHraJBmdQp5sQqzwBvlXVcqN4KjON5+e/WA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /keyserver/saml_test.go: -------------------------------------------------------------------------------- 1 | package keyserver 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/pem" 6 | "io/ioutil" 7 | "os/exec" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestSAMLRequest(t *testing.T) { 13 | ks := Keyserver{} 14 | _, err := ks.SAMLRequest() 15 | if err == nil { 16 | t.Fatal("expected SAMLRequest() to fail, but succeeded") 17 | } 18 | 19 | domains := make(map[string]struct{}) 20 | for _, d := range []string{"foomail.com", "barmail.com"} { 21 | domains[d] = struct{}{} 22 | } 23 | ks = Keyserver{samlProofAllowedDomains: domains} 24 | _, err = ks.SAMLRequest() 25 | if err == nil { 26 | t.Fatal("expected SAMLRequest() to fail, but succeeded") 27 | } 28 | k, err := ioutil.ReadFile("saml/test.key") 29 | if err != nil { 30 | t.Errorf(err.Error()) 31 | return 32 | } 33 | kd, _ := pem.Decode(k) 34 | privateKey, err := x509.ParsePKCS1PrivateKey(kd.Bytes) 35 | if err != nil { 36 | t.Errorf(err.Error()) 37 | return 38 | } 39 | 40 | certPem, err := ioutil.ReadFile("saml/test.crt") 41 | if err != nil { 42 | t.Errorf(err.Error()) 43 | return 44 | } 45 | 46 | certBlock, _ := pem.Decode(certPem) 47 | if certBlock == nil { 48 | t.Errorf("failed to PEM decode cert") 49 | return 50 | } 51 | 52 | cert, err := x509.ParseCertificate(certBlock.Bytes) 53 | if err != nil { 54 | t.Errorf(err.Error()) 55 | return 56 | } 57 | _, err = exec.LookPath("xmlsec1") 58 | if err != nil { 59 | t.Skip("skipping subsequent test since xmlsec1 is missing") 60 | } 61 | ks = Keyserver{samlProofAllowedDomains: domains, samlProofIDPCert: cert, samlProofSPKey: privateKey, samlProofConsumerServiceURL: "https://ks.alice.wonderland", samlProofIDPSSOURL: "https://idp.bob"} 62 | req, err := ks.SAMLRequest() 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | p := "https://idp.bob?SAMLRequest=" 67 | if !strings.HasPrefix(req, p) { 68 | t.Errorf("got request url %q, expected it to begin with %q", req, p) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /keyserver/table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package keyserver 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "github.com/yahoo/coname/vrf" 21 | ) 22 | 23 | // NOTE: All uint64 keys are big-endian encoded to make lexicographical order 24 | // correspond to numeric order. 25 | var ( 26 | tableReplicationLogPrefix byte = 'l' // index uint64 -> proto.KeyserverStep 27 | tableVerifierLogPrefix byte = 'v' // index uint64 -> proto.VerifierStep 28 | tableRatificationsPrefix byte = 'r' // epoch uint64, ratifier uint64 -> []byte (signature) 29 | tableEpochHeadsPrefix byte = 'e' // epoch uint64 -> proto.TimestampedEpochHead 30 | tableUpdateRequestsPrefix byte = 'u' // vrfidx [vrf.Size]byte -> epoch uint64 -> proto.UpdateRequest 31 | tableMerkleTreeSnapshotPrefix byte = 's' // epochNumber uint64 -> snapshotNumber uint64 32 | tableMerkleTreePrefix byte = 't' 33 | tableUpdatesPendingRatificationPrefix byte = 'p' // logIndex uint64 -> proto.SignedEntryUpdate 34 | 35 | tableReplicaState = []byte{'e'} // proto.ReplicaState 36 | ) 37 | 38 | func tableRatifications(epoch, ratifier uint64) []byte { 39 | ret := make([]byte, 1+8+8) 40 | ret[0] = tableRatificationsPrefix 41 | binary.BigEndian.PutUint64(ret[1:1+8], epoch) 42 | binary.BigEndian.PutUint64(ret[1+8:1+8+8], ratifier) 43 | return ret 44 | } 45 | 46 | func tableEpochHeads(epoch uint64) []byte { 47 | ret := make([]byte, 1+8) 48 | ret[0] = tableEpochHeadsPrefix 49 | binary.BigEndian.PutUint64(ret[1:1+8], epoch) 50 | return ret 51 | } 52 | 53 | func tableVerifierLog(index uint64) []byte { 54 | ret := make([]byte, 1+8) 55 | ret[0] = tableVerifierLogPrefix 56 | binary.BigEndian.PutUint64(ret[1:1+8], index) 57 | return ret 58 | } 59 | 60 | func tableUpdateRequests(vrfidx []byte, epoch uint64) []byte { 61 | ret := make([]byte, 1+vrf.Size+8) 62 | ret[0] = tableUpdateRequestsPrefix 63 | copy(ret[1:1+vrf.Size], vrfidx) 64 | binary.BigEndian.PutUint64(ret[1+vrf.Size:1+vrf.Size+8], epoch) 65 | return ret 66 | } 67 | 68 | func tableMerkleTreeSnapshot(epoch uint64) []byte { 69 | ret := make([]byte, 1+8) 70 | ret[0] = tableMerkleTreeSnapshotPrefix 71 | binary.BigEndian.PutUint64(ret[1:], epoch) 72 | return ret 73 | } 74 | 75 | func tableUpdatesPendingRatification(logIndex uint64) []byte { 76 | ret := make([]byte, 1+8) 77 | ret[0] = tableUpdatesPendingRatificationPrefix 78 | binary.BigEndian.PutUint64(ret[1:1+8], logIndex) 79 | return ret 80 | } 81 | -------------------------------------------------------------------------------- /merkle.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy 5 | // of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package coname 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | 22 | "golang.org/x/crypto/sha3" 23 | ) 24 | 25 | const ( 26 | HashBytes = 32 27 | IndexBytes = 32 28 | IndexBits = IndexBytes * 8 29 | ) 30 | 31 | type MerkleNode interface { 32 | IsEmpty() bool 33 | 34 | IsLeaf() bool 35 | Depth() int 36 | 37 | // For intermediate nodes 38 | ChildHash(rightChild bool) []byte 39 | Child(rightChild bool) (MerkleNode, error) 40 | 41 | // For leaves 42 | Index() []byte 43 | Value() []byte 44 | } 45 | 46 | // TreeLookup looks up the entry at a particular index in the snapshot. 47 | func TreeLookup(root MerkleNode, indexBytes []byte) (value []byte, err error) { 48 | if len(indexBytes) != IndexBytes { 49 | return nil, fmt.Errorf("Wrong index length") 50 | } 51 | if root.IsEmpty() { 52 | // Special case: The tree is empty. 53 | return nil, nil 54 | } 55 | n := root 56 | indexBits := ToBits(IndexBits, indexBytes) 57 | // Traverse down the tree, following either the left or right child depending on the next bit. 58 | for !n.IsLeaf() { 59 | descendingRight := indexBits[n.Depth()] 60 | n, err = n.Child(descendingRight) 61 | if err != nil { 62 | return nil, err 63 | } 64 | if n.IsEmpty() { 65 | // There's no leaf with this index. 66 | return nil, nil 67 | } 68 | } 69 | // Once a leaf node is reached, compare the entire index stored in the leaf node. 70 | if bytes.Equal(indexBytes, n.Index()) { 71 | // The leaf exists: we will simply return the value. 72 | return n.Value(), nil 73 | } else { 74 | // There is no leaf with the requested index. 75 | return nil, nil 76 | } 77 | } 78 | 79 | const ( 80 | InternalNodeIdentifier = 'I' 81 | LeafIdentifier = 'L' 82 | EmptyBranchIdentifier = 'E' 83 | ) 84 | 85 | // Differences from the CONIKS paper: 86 | // * Add an identifier byte at the beginning to make it impossible for this to collide with leaves 87 | // or empty branches. 88 | // * Add the prefix of the index, to protect against limited hash collisions or bugs. 89 | // This gives H(k_internal || h_child0 || h_child1 || prefix || depth) 90 | func HashInternalNode(prefixBits []bool, childHashes *[2][HashBytes]byte) []byte { 91 | h := sha3.NewShake256() 92 | h.Write([]byte{InternalNodeIdentifier}) 93 | h.Write(childHashes[0][:]) 94 | h.Write(childHashes[1][:]) 95 | h.Write(ToBytes(prefixBits)) 96 | buf := make([]byte, 4) 97 | binary.LittleEndian.PutUint32(buf, uint32(len(prefixBits))) 98 | h.Write(buf) 99 | var ret [HashBytes]byte 100 | h.Read(ret[:]) 101 | return ret[:] 102 | } 103 | 104 | // This is the same as in the CONIKS paper. 105 | // H(k_empty || nonce || prefix || depth) 106 | func HashEmptyBranch(treeNonce []byte, prefixBits []bool) []byte { 107 | h := sha3.NewShake256() 108 | h.Write([]byte{EmptyBranchIdentifier}) 109 | h.Write(treeNonce) 110 | h.Write(ToBytes(prefixBits)) 111 | buf := make([]byte, 4) 112 | binary.LittleEndian.PutUint32(buf, uint32(len(prefixBits))) 113 | h.Write(buf) 114 | var ret [HashBytes]byte 115 | h.Read(ret[:]) 116 | return ret[:] 117 | } 118 | 119 | // This is the same as in the CONIKS paper: H(k_leaf || nonce || index || depth || value) 120 | func HashLeaf(treeNonce []byte, indexBytes []byte, depth int, value []byte) []byte { 121 | h := sha3.NewShake256() 122 | h.Write([]byte{LeafIdentifier}) 123 | h.Write(treeNonce) 124 | h.Write(indexBytes) 125 | buf := make([]byte, 4) 126 | binary.LittleEndian.PutUint32(buf, uint32(depth)) 127 | h.Write(buf) 128 | h.Write(value) 129 | var ret [HashBytes]byte 130 | h.Read(ret[:]) 131 | return ret[:] 132 | } 133 | 134 | func BitToIndex(b bool) int { 135 | if b { 136 | return 1 137 | } else { 138 | return 0 139 | } 140 | } 141 | 142 | // In each byte, the bits are ordered MSB to LSB 143 | func ToBits(num int, bs []byte) []bool { 144 | bits := make([]bool, num) 145 | for i := 0; i < len(bits); i++ { 146 | bits[i] = (bs[i/8]< 0 147 | } 148 | return bits 149 | } 150 | 151 | // In each byte, the bits are ordered MSB to LSB 152 | func ToBytes(bits []bool) []byte { 153 | bs := make([]byte, (len(bits)+7)/8) 154 | for i := 0; i < len(bits); i++ { 155 | if bits[i] { 156 | bs[i/8] |= (1 << 7) >> uint(i%8) 157 | } 158 | } 159 | return bs 160 | } 161 | -------------------------------------------------------------------------------- /policy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package coname 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/agl/ed25519" 21 | "github.com/yahoo/coname/proto" 22 | ) 23 | 24 | // VerifyUpdate returns nil iff replacing entry current (nil if none) with next 25 | // is justified given the evidence in update. Globally deterministic. 26 | // current, update : &const // none of the inputs are modified 27 | func VerifyUpdate(current *proto.Entry, update *proto.SignedEntryUpdate) error { 28 | next := &update.NewEntry 29 | if current != nil { 30 | if current.UpdatePolicy == nil { 31 | return fmt.Errorf("VerifyUpdate: current.UpdatePolicy is nil") 32 | } 33 | if !VerifyPolicy(current.UpdatePolicy, update.NewEntry.Encoding, update.Signatures) { 34 | return fmt.Errorf("VerifyUpdate: replacing an entry requires authorization from the old key, but signature verification failed") 35 | } 36 | if next.Version <= current.Version { 37 | return fmt.Errorf("VerifyUpdate: entry version must increase (got %d <= %d)", next.Version, current.Version) 38 | } 39 | } else if next.Version != 0 { 40 | return fmt.Errorf("VerifyUpdate: registering a new entry must use version number 0 (got %d)", next.Version) 41 | } 42 | if next.UpdatePolicy == nil { 43 | return fmt.Errorf("VerifyUpdate: next.UpdatePolicy is nil") 44 | } 45 | if !VerifyPolicy(next.UpdatePolicy, update.NewEntry.Encoding, update.Signatures) { 46 | return fmt.Errorf("VerifyUpdate: update needs to be accepted by the new key, but signature verification failed") 47 | } 48 | return nil 49 | } 50 | 51 | // VerifyPolicy returns whether, by policy, action is justified by evidence. 52 | // Evidence is in the form of digital signatures denoting agreement, and the 53 | // policy contains public keys and a quorum rule. 54 | // policy, action, evidence : &const // none of the inputs are modified 55 | // NOTE: This does not work for verifier signatures on epoch heads because the 56 | // signed contents will differ in their timestamps. 57 | func VerifyPolicy(policy *proto.AuthorizationPolicy, action []byte, evidence map[uint64][]byte) bool { 58 | have := make(map[uint64]struct{}, len(evidence)) 59 | for id, pk := range policy.PublicKeys { 60 | if sig, ok := evidence[id]; ok && VerifySignature(pk, action, sig) { 61 | have[id] = struct{}{} 62 | } 63 | } 64 | switch policy.PolicyType.(type) { 65 | case *proto.AuthorizationPolicy_Quorum: 66 | return CheckQuorum(policy.PolicyType.(*proto.AuthorizationPolicy_Quorum).Quorum, have) 67 | default: // unknown policy 68 | return false 69 | } 70 | } 71 | 72 | // VerifySignature returns true iff sig is a valid signature of message by 73 | // verifier. 74 | // pk, message, sig : &const // none of the inputs are modified 75 | func VerifySignature(pk *proto.PublicKey, message []byte, sig []byte) bool { 76 | switch pk.PubkeyType.(type) { 77 | case *proto.PublicKey_Ed25519: 78 | var edpk [32]byte 79 | var edsig [64]byte 80 | copy(edpk[:], pk.PubkeyType.(*proto.PublicKey_Ed25519).Ed25519[:]) 81 | copy(edsig[:], sig) 82 | return ed25519.Verify(&edpk, message, &edsig) 83 | default: 84 | return false 85 | } 86 | } 87 | 88 | // CheckQuorum evaluates whether the quorum requirement want can be satisfied 89 | // by ratifications of the verifiers in have. 90 | // want, have : &const // none of the inputs are modified 91 | func CheckQuorum(want *proto.QuorumExpr, have map[uint64]struct{}) bool { 92 | if want == nil { 93 | return true // no requirements 94 | } 95 | var n uint32 96 | for _, verifier := range want.Candidates { 97 | if _, yes := have[verifier]; yes { 98 | n++ 99 | } 100 | } 101 | for _, e := range want.Subexpressions { 102 | if CheckQuorum(e, have) { 103 | n++ 104 | } 105 | } 106 | return n >= want.Threshold 107 | } 108 | 109 | // ListQuorum inserts all verifiers mentioned in e to out. If out is nil, a new 110 | // map is allocated. ListQuorum is NOT intended to be used for implementing 111 | // quorum verification, use CheckQuorum instead. 112 | // e : &const 113 | // out : *mut map mut // both the map and its contents can be modified arbitrarily 114 | func ListQuorum(e *proto.QuorumExpr, out map[uint64]struct{}) map[uint64]struct{} { 115 | if e == nil { 116 | return make(map[uint64]struct{}, 0) 117 | } 118 | if out == nil { 119 | var l int 120 | if e.Candidates != nil { 121 | l = len(e.Candidates) 122 | } 123 | out = make(map[uint64]struct{}, l) 124 | } 125 | for _, verifier := range e.Candidates { 126 | out[verifier] = struct{}{} 127 | } 128 | for _, e := range e.Subexpressions { 129 | ListQuorum(e, out) 130 | } 131 | return out 132 | } 133 | -------------------------------------------------------------------------------- /proto/AuthorizationPolicy.pr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedAuthorizationPolicy struct { 25 | AuthorizationPolicy 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedAuthorizationPolicy) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.AuthorizationPolicy) 31 | } 32 | 33 | func (m *EncodedAuthorizationPolicy) Reset() { 34 | *m = EncodedAuthorizationPolicy{} 35 | } 36 | 37 | func (m *EncodedAuthorizationPolicy) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedAuthorizationPolicy) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedAuthorizationPolicy) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedAuthorizationPolicy) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.AuthorizationPolicy) 58 | } 59 | 60 | func NewPopulatedEncodedAuthorizationPolicy(r randyClient, easy bool) *EncodedAuthorizationPolicy { 61 | this := &EncodedAuthorizationPolicy{AuthorizationPolicy: *NewPopulatedAuthorizationPolicy(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedAuthorizationPolicy) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedAuthorizationPolicy); ok { 68 | return this.AuthorizationPolicy.VerboseEqual(&thatP.AuthorizationPolicy) 69 | } 70 | if thatP, ok := that.(EncodedAuthorizationPolicy); ok { 71 | return this.AuthorizationPolicy.VerboseEqual(&thatP.AuthorizationPolicy) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedAuthorizationPolicy", that) 74 | } 75 | 76 | func (this *EncodedAuthorizationPolicy) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedAuthorizationPolicy); ok { 78 | return this.AuthorizationPolicy.Equal(&thatP.AuthorizationPolicy) 79 | } 80 | if thatP, ok := that.(EncodedAuthorizationPolicy); ok { 81 | return this.AuthorizationPolicy.Equal(&thatP.AuthorizationPolicy) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedAuthorizationPolicy) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedAuthorizationPolicy{AuthorizationPolicy: ` + this.AuthorizationPolicy.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedAuthorizationPolicy) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedAuthorizationPolicy{AuthorizationPolicy: ` + this.AuthorizationPolicy.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedAuthorizationPolicy) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedAuthorizationPolicy) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedAuthorizationPolicy)(nil) 121 | var _ json.Unmarshaler = (*EncodedAuthorizationPolicy)(nil) 122 | -------------------------------------------------------------------------------- /proto/AuthorizationPolicypr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 19 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedAuthorizationPolicyProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedAuthorizationPolicy(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedAuthorizationPolicy{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedAuthorizationPolicyJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedAuthorizationPolicy(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedAuthorizationPolicy{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/Entry.pr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedEntry struct { 25 | Entry 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedEntry) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.Entry) 31 | } 32 | 33 | func (m *EncodedEntry) Reset() { 34 | *m = EncodedEntry{} 35 | } 36 | 37 | func (m *EncodedEntry) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedEntry) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedEntry) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedEntry) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.Entry) 58 | } 59 | 60 | func NewPopulatedEncodedEntry(r randyClient, easy bool) *EncodedEntry { 61 | this := &EncodedEntry{Entry: *NewPopulatedEntry(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedEntry) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedEntry); ok { 68 | return this.Entry.VerboseEqual(&thatP.Entry) 69 | } 70 | if thatP, ok := that.(EncodedEntry); ok { 71 | return this.Entry.VerboseEqual(&thatP.Entry) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedEntry", that) 74 | } 75 | 76 | func (this *EncodedEntry) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedEntry); ok { 78 | return this.Entry.Equal(&thatP.Entry) 79 | } 80 | if thatP, ok := that.(EncodedEntry); ok { 81 | return this.Entry.Equal(&thatP.Entry) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedEntry) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedEntry{Entry: ` + this.Entry.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedEntry) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedEntry{Entry: ` + this.Entry.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedEntry) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedEntry) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedEntry)(nil) 121 | var _ json.Unmarshaler = (*EncodedEntry)(nil) 122 | -------------------------------------------------------------------------------- /proto/Entrypr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 19 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedEntryProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedEntry(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedEntry{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedEntryJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedEntry(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedEntry{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/EpochHead.pr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedEpochHead struct { 25 | EpochHead 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedEpochHead) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.EpochHead) 31 | } 32 | 33 | func (m *EncodedEpochHead) Reset() { 34 | *m = EncodedEpochHead{} 35 | } 36 | 37 | func (m *EncodedEpochHead) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedEpochHead) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedEpochHead) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedEpochHead) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.EpochHead) 58 | } 59 | 60 | func NewPopulatedEncodedEpochHead(r randyClient, easy bool) *EncodedEpochHead { 61 | this := &EncodedEpochHead{EpochHead: *NewPopulatedEpochHead(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedEpochHead) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedEpochHead); ok { 68 | return this.EpochHead.VerboseEqual(&thatP.EpochHead) 69 | } 70 | if thatP, ok := that.(EncodedEpochHead); ok { 71 | return this.EpochHead.VerboseEqual(&thatP.EpochHead) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedEpochHead", that) 74 | } 75 | 76 | func (this *EncodedEpochHead) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedEpochHead); ok { 78 | return this.EpochHead.Equal(&thatP.EpochHead) 79 | } 80 | if thatP, ok := that.(EncodedEpochHead); ok { 81 | return this.EpochHead.Equal(&thatP.EpochHead) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedEpochHead) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedEpochHead{EpochHead: ` + this.EpochHead.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedEpochHead) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedEpochHead{EpochHead: ` + this.EpochHead.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedEpochHead) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedEpochHead) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedEpochHead)(nil) 121 | var _ json.Unmarshaler = (*EncodedEpochHead)(nil) 122 | -------------------------------------------------------------------------------- /proto/EpochHeadpr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 19 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedEpochHeadProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedEpochHead(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedEpochHead{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedEpochHeadJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedEpochHead(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedEpochHead{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/Profile.pr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedProfile struct { 25 | Profile 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedProfile) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.Profile) 31 | } 32 | 33 | func (m *EncodedProfile) Reset() { 34 | *m = EncodedProfile{} 35 | } 36 | 37 | func (m *EncodedProfile) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedProfile) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedProfile) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedProfile) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.Profile) 58 | } 59 | 60 | func NewPopulatedEncodedProfile(r randyClient, easy bool) *EncodedProfile { 61 | this := &EncodedProfile{Profile: *NewPopulatedProfile(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedProfile) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedProfile); ok { 68 | return this.Profile.VerboseEqual(&thatP.Profile) 69 | } 70 | if thatP, ok := that.(EncodedProfile); ok { 71 | return this.Profile.VerboseEqual(&thatP.Profile) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedProfile", that) 74 | } 75 | 76 | func (this *EncodedProfile) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedProfile); ok { 78 | return this.Profile.Equal(&thatP.Profile) 79 | } 80 | if thatP, ok := that.(EncodedProfile); ok { 81 | return this.Profile.Equal(&thatP.Profile) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedProfile) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedProfile{Profile: ` + this.Profile.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedProfile) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedProfile{Profile: ` + this.Profile.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedProfile) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedProfile) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedProfile)(nil) 121 | var _ json.Unmarshaler = (*EncodedProfile)(nil) 122 | -------------------------------------------------------------------------------- /proto/Profilepr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 19 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedProfileProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedProfile(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedProfile{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedProfileJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedProfile(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedProfile{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/SignedEntryUpdate.pr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedSignedEntryUpdate struct { 25 | SignedEntryUpdate 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedSignedEntryUpdate) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.SignedEntryUpdate) 31 | } 32 | 33 | func (m *EncodedSignedEntryUpdate) Reset() { 34 | *m = EncodedSignedEntryUpdate{} 35 | } 36 | 37 | func (m *EncodedSignedEntryUpdate) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedSignedEntryUpdate) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedSignedEntryUpdate) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedSignedEntryUpdate) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.SignedEntryUpdate) 58 | } 59 | 60 | func NewPopulatedEncodedSignedEntryUpdate(r randyClient, easy bool) *EncodedSignedEntryUpdate { 61 | this := &EncodedSignedEntryUpdate{SignedEntryUpdate: *NewPopulatedSignedEntryUpdate(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedSignedEntryUpdate) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedSignedEntryUpdate); ok { 68 | return this.SignedEntryUpdate.VerboseEqual(&thatP.SignedEntryUpdate) 69 | } 70 | if thatP, ok := that.(EncodedSignedEntryUpdate); ok { 71 | return this.SignedEntryUpdate.VerboseEqual(&thatP.SignedEntryUpdate) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedSignedEntryUpdate", that) 74 | } 75 | 76 | func (this *EncodedSignedEntryUpdate) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedSignedEntryUpdate); ok { 78 | return this.SignedEntryUpdate.Equal(&thatP.SignedEntryUpdate) 79 | } 80 | if thatP, ok := that.(EncodedSignedEntryUpdate); ok { 81 | return this.SignedEntryUpdate.Equal(&thatP.SignedEntryUpdate) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedSignedEntryUpdate) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedSignedEntryUpdate{SignedEntryUpdate: ` + this.SignedEntryUpdate.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedSignedEntryUpdate) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedSignedEntryUpdate{SignedEntryUpdate: ` + this.SignedEntryUpdate.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedSignedEntryUpdate) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedSignedEntryUpdate) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedSignedEntryUpdate)(nil) 121 | var _ json.Unmarshaler = (*EncodedSignedEntryUpdate)(nil) 122 | -------------------------------------------------------------------------------- /proto/SignedEntryUpdatepr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 19 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedSignedEntryUpdateProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedSignedEntryUpdate(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedSignedEntryUpdate{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedSignedEntryUpdateJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedSignedEntryUpdate(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedSignedEntryUpdate{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/TimestampedEpochHead.pr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedTimestampedEpochHead struct { 25 | TimestampedEpochHead 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedTimestampedEpochHead) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.TimestampedEpochHead) 31 | } 32 | 33 | func (m *EncodedTimestampedEpochHead) Reset() { 34 | *m = EncodedTimestampedEpochHead{} 35 | } 36 | 37 | func (m *EncodedTimestampedEpochHead) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedTimestampedEpochHead) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedTimestampedEpochHead) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedTimestampedEpochHead) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.TimestampedEpochHead) 58 | } 59 | 60 | func NewPopulatedEncodedTimestampedEpochHead(r randyClient, easy bool) *EncodedTimestampedEpochHead { 61 | this := &EncodedTimestampedEpochHead{TimestampedEpochHead: *NewPopulatedTimestampedEpochHead(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedTimestampedEpochHead) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedTimestampedEpochHead); ok { 68 | return this.TimestampedEpochHead.VerboseEqual(&thatP.TimestampedEpochHead) 69 | } 70 | if thatP, ok := that.(EncodedTimestampedEpochHead); ok { 71 | return this.TimestampedEpochHead.VerboseEqual(&thatP.TimestampedEpochHead) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedTimestampedEpochHead", that) 74 | } 75 | 76 | func (this *EncodedTimestampedEpochHead) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedTimestampedEpochHead); ok { 78 | return this.TimestampedEpochHead.Equal(&thatP.TimestampedEpochHead) 79 | } 80 | if thatP, ok := that.(EncodedTimestampedEpochHead); ok { 81 | return this.TimestampedEpochHead.Equal(&thatP.TimestampedEpochHead) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedTimestampedEpochHead) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedTimestampedEpochHead{TimestampedEpochHead: ` + this.TimestampedEpochHead.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedTimestampedEpochHead) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedTimestampedEpochHead{TimestampedEpochHead: ` + this.TimestampedEpochHead.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedTimestampedEpochHead) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedTimestampedEpochHead) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedTimestampedEpochHead)(nil) 121 | var _ json.Unmarshaler = (*EncodedTimestampedEpochHead)(nil) 122 | -------------------------------------------------------------------------------- /proto/TimestampedEpochHeadpr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 19 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedTimestampedEpochHeadProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedTimestampedEpochHead(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedTimestampedEpochHead{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedTimestampedEpochHeadJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedTimestampedEpochHead(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedTimestampedEpochHead{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/benchmarks.txt: -------------------------------------------------------------------------------- 1 | PASS 2 | BenchmarkLookupRequestProtoMarshal 500000 2779 ns/op 217.33 MB/s 3 | BenchmarkLookupRequestProtoUnmarshal 500000 3413 ns/op 176.92 MB/s 4 | BenchmarkUpdateRequestProtoMarshal 200000 7641 ns/op 287.11 MB/s 5 | BenchmarkUpdateRequestProtoUnmarshal 200000 11728 ns/op 187.06 MB/s 6 | BenchmarkLookupProofProtoMarshal 200000 13231 ns/op 233.45 MB/s 7 | BenchmarkLookupProofProtoUnmarshal 100000 13567 ns/op 227.68 MB/s 8 | BenchmarkEntryProtoMarshal 500000 3537 ns/op 246.52 MB/s 9 | BenchmarkEntryProtoUnmarshal 300000 5600 ns/op 155.69 MB/s 10 | BenchmarkSignedEntryUpdateProtoMarshal 500000 2860 ns/op 473.72 MB/s 11 | BenchmarkSignedEntryUpdateProtoUnmarshal 200000 7850 ns/op 172.59 MB/s 12 | BenchmarkProfileProtoMarshal 1000000 1786 ns/op 267.03 MB/s 13 | BenchmarkProfileProtoUnmarshal 1000000 1701 ns/op 280.29 MB/s 14 | BenchmarkSignedEpochHeadProtoMarshal 1000000 1489 ns/op 283.99 MB/s 15 | BenchmarkSignedEpochHeadProtoUnmarshal 1000000 2043 ns/op 207.01 MB/s 16 | BenchmarkTimestampedEpochHeadProtoMarshal 5000000 416 ns/op 441.97 MB/s 17 | BenchmarkTimestampedEpochHeadProtoUnmarshal 2000000 809 ns/op 227.21 MB/s 18 | BenchmarkEpochHeadProtoMarshal 5000000 340 ns/op 469.39 MB/s 19 | BenchmarkEpochHeadProtoUnmarshal 3000000 456 ns/op 350.42 MB/s 20 | BenchmarkPublicKeyProtoMarshal 500000 3254 ns/op 257.47 MB/s 21 | BenchmarkPublicKeyProtoUnmarshal 300000 5776 ns/op 145.07 MB/s 22 | BenchmarkQuorumPublicKeyProtoMarshal 500000 2879 ns/op 305.23 MB/s 23 | BenchmarkQuorumPublicKeyProtoUnmarshal 200000 6089 ns/op 144.36 MB/s 24 | BenchmarkQuorumExprProtoMarshal 500000 2357 ns/op 233.25 MB/s 25 | BenchmarkQuorumExprProtoUnmarshal 500000 3780 ns/op 145.49 MB/s 26 | BenchmarkLookupRequestSize 3000000 550 ns/op 1077.76 MB/s 27 | BenchmarkUpdateRequestSize 1000000 1208 ns/op 1806.00 MB/s 28 | BenchmarkLookupProofSize 1000000 1797 ns/op 1696.71 MB/s 29 | BenchmarkEntrySize 3000000 507 ns/op 1679.16 MB/s 30 | BenchmarkSignedEntryUpdateSize 3000000 396 ns/op 3451.83 MB/s 31 | BenchmarkProfileSize 10000000 209 ns/op 2259.98 MB/s 32 | BenchmarkSignedEpochHeadSize 10000000 192 ns/op 2186.20 MB/s 33 | BenchmarkTimestampedEpochHeadSize 50000000 39.0 ns/op 4667.83 MB/s 34 | BenchmarkEpochHeadSize 100000000 19.1 ns/op 8427.59 MB/s 35 | BenchmarkPublicKeySize 3000000 541 ns/op 1552.20 MB/s 36 | BenchmarkQuorumPublicKeySize 3000000 554 ns/op 1570.10 MB/s 37 | BenchmarkQuorumExprSize 3000000 607 ns/op 902.77 MB/s 38 | BenchmarkConfigProtoMarshal 10000 104838 ns/op 164.11 MB/s 39 | BenchmarkConfigProtoUnmarshal 10000 101512 ns/op 169.49 MB/s 40 | BenchmarkRealmConfigProtoMarshal 100000 21258 ns/op 202.41 MB/s 41 | BenchmarkRealmConfigProtoUnmarshal 50000 26077 ns/op 165.01 MB/s 42 | BenchmarkConfigSize 100000 13821 ns/op 1217.19 MB/s 43 | BenchmarkRealmConfigSize 500000 3358 ns/op 1256.26 MB/s 44 | BenchmarkReplicaStateProtoMarshal 3000000 502 ns/op 185.21 MB/s 45 | BenchmarkReplicaStateProtoUnmarshal 3000000 413 ns/op 225.11 MB/s 46 | BenchmarkVerifierStateProtoMarshal 5000000 297 ns/op 211.42 MB/s 47 | BenchmarkVerifierStateProtoUnmarshal 5000000 281 ns/op 223.46 MB/s 48 | BenchmarkReplicaStateSize 30000000 58.4 ns/op 1576.09 MB/s 49 | BenchmarkVerifierStateSize 100000000 17.3 ns/op 3631.68 MB/s 50 | BenchmarkKeyserverStepProtoMarshal 200000 11807 ns/op 236.21 MB/s 51 | BenchmarkKeyserverStepProtoUnmarshal 100000 14593 ns/op 191.12 MB/s 52 | BenchmarkEpochDelimiterProtoMarshal 5000000 289 ns/op 89.71 MB/s 53 | BenchmarkEpochDelimiterProtoUnmarshal 10000000 131 ns/op 198.24 MB/s 54 | BenchmarkKeyserverStepSize 1000000 1651 ns/op 1672.95 MB/s 55 | BenchmarkEpochDelimiterSize 30000000 39.9 ns/op 652.42 MB/s 56 | BenchmarkTimestampProtoMarshal 5000000 253 ns/op 70.96 MB/s 57 | BenchmarkTimestampProtoUnmarshal 20000000 98.0 ns/op 183.74 MB/s 58 | BenchmarkTimestampSize 50000000 27.4 ns/op 692.62 MB/s 59 | BenchmarkVerifierStreamRequestProtoMarshal 10000000 177 ns/op 61.94 MB/s 60 | BenchmarkVerifierStreamRequestProtoUnmarshal 20000000 68.4 ns/op 160.84 MB/s 61 | BenchmarkVerifierStepProtoMarshal 300000 4483 ns/op 363.74 MB/s 62 | BenchmarkVerifierStepProtoUnmarshal 200000 9863 ns/op 165.35 MB/s 63 | BenchmarkNothingProtoMarshal 20000000 69.0 ns/op 64 | BenchmarkNothingProtoUnmarshal 50000000 39.0 ns/op 65 | BenchmarkVerifierStreamRequestSize 100000000 15.1 ns/op 727.74 MB/s 66 | BenchmarkVerifierStepSize 3000000 541 ns/op 3077.74 MB/s 67 | BenchmarkNothingSize 2000000000 2.01 ns/op 68 | ok github.com/yahoo/coname/proto 177.010s 69 | -------------------------------------------------------------------------------- /proto/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014-2015 The Dename Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | set -euo pipefail 17 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 18 | cd "$DIR" 19 | 20 | rm *.pb.go *pb_test.go *.pr.go *pr_test.go || true 21 | -------------------------------------------------------------------------------- /proto/config.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 18 | import "client.proto"; 19 | import "duration.proto"; 20 | import "tlsconfig.proto"; 21 | 22 | message Config { 23 | repeated RealmConfig realms = 1; 24 | } 25 | 26 | message RealmConfig { 27 | // RealmName is the canonical name of the realm. It is signed by the 28 | // verifiers as a part of the epoch head. 29 | string RealmName = 1; 30 | // Domains specifies a list of domains that belong to this realm. 31 | // Configuring one domain to belong to multiple realms is considered an 32 | // error. 33 | // TODO: support TLS-style wildcards. 34 | repeated string domains = 2; 35 | // Addr is the TCP (host:port) address of the keyserver GRPC interface. 36 | string addr = 3; 37 | // URL is the location of the secondary, HTTP-based interface to the 38 | // keyserver. It is not necessarily on the same host as addr. 39 | string URL = 4; 40 | // VRFPublic is the public key of the verifiable random function used for 41 | // user id privacy. Here it is used to check that the anti-spam obfuscation 42 | // layer is properly used as a one-to-one mapping between real and 43 | // obfuscated usernames. 44 | bytes VRFPublic = 5; 45 | // VerificationPolicy specifies the conditions on how a lookup must be 46 | // verified for it to be accepted. Each verifier in VerificationPolicy MUST 47 | // have a NoOlderThan entry. 48 | AuthorizationPolicy verification_policy = 6; 49 | 50 | // EpochTimeToLive specifies the duration for which an epoch is valid after 51 | // it has been issued. A client that has access to a clock MUST NOT accept 52 | // epoch heads with IssueTime more than EpochTimeToLive in the past. 53 | Duration epoch_time_to_live = 7 [(gogoproto.nullable) = false]; 54 | 55 | // TreeNonce is the global nonce that is hashed into the Merkle tree nodes. 56 | bytes tree_nonce = 8; 57 | 58 | TLSConfig client_tls = 9 [(gogoproto.customname) = "ClientTLS"]; 59 | } 60 | -------------------------------------------------------------------------------- /proto/duration.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import "time" 4 | 5 | func DurationStamp(d time.Duration) Duration { 6 | return Duration{Seconds: int64(d / 1e9), Nanos: int32(d % 1e9)} 7 | } 8 | 9 | func (dt *Duration) Duration() time.Duration { 10 | return time.Duration(dt.Seconds*1e9 + int64(dt.Nanos)) 11 | } 12 | -------------------------------------------------------------------------------- /proto/duration.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package proto; 18 | 19 | 20 | // A Duration represents a signed, fixed-length span of time represented 21 | // as a count of seconds and fractions of seconds at nanosecond 22 | // resolution. It is independent of any calendar and concepts like "day" 23 | // or "month". It is related to Timestamp in that the difference between 24 | // two Timestamp values is a Duration and it can be added or subtracted 25 | // from a Timestamp. Range is approximately +-10,000 years. 26 | // 27 | // Example 1: Compute Duration from two Timestamps in pseudo code. 28 | // 29 | // Timestamp start = ...; 30 | // Timestamp end = ...; 31 | // Duration duration = ...; 32 | // 33 | // duration.seconds = end.seconds - start.seconds; 34 | // duration.nanos = end.nanos - start.nanos; 35 | // 36 | // if (duration.seconds < 0 && duration.nanos > 0) { 37 | // duration.seconds += 1; 38 | // duration.nanos -= 1000000000; 39 | // } else if (durations.seconds > 0 && duration.nanos < 0) { 40 | // duration.seconds -= 1; 41 | // duration.nanos += 1000000000; 42 | // } 43 | // 44 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 45 | // 46 | // Timestamp start = ...; 47 | // Duration duration = ...; 48 | // Timestamp end = ...; 49 | // 50 | // end.seconds = start.seconds + duration.seconds; 51 | // end.nanos = start.nanos + duration.nanos; 52 | // 53 | // if (end.nanos < 0) { 54 | // end.seconds -= 1; 55 | // end.nanos += 1000000000; 56 | // } else if (end.nanos >= 1000000000) { 57 | // end.seconds += 1; 58 | // end.nanos -= 1000000000; 59 | // } 60 | // 61 | message Duration { 62 | // Signed seconds of the span of time. Must be from -315,576,000,000 63 | // to +315,576,000,000 inclusive. 64 | int64 seconds = 1; 65 | 66 | // Signed fractions of a second at nanosecond resolution of the span 67 | // of time. Durations less than one second are represented with a 0 68 | // `seconds` field and a positive or negative `nanos` field. For durations 69 | // of one second or more, a non-zero value for the `nanos` field must be 70 | // of the same sign as the `seconds` field. Must be from -999,999,999 71 | // to +999,999,999 inclusive. 72 | int32 nanos = 2; 73 | } 74 | -------------------------------------------------------------------------------- /proto/duration_test.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestDurationstamp(t *testing.T) { 9 | d := 5*time.Second + 15*time.Nanosecond 10 | ds := DurationStamp(d) 11 | if got, want := ds.Seconds, int64(5); got != want { 12 | t.Fatalf("Duration to DurationStamp got: %q, want %q", got, want) 13 | } 14 | if got, want := ds.Nanos, int32(15); got != want { 15 | t.Fatalf("Duration to DurationStamp got: %q, want %q", got, want) 16 | } 17 | } 18 | 19 | func TestDuration(t *testing.T) { 20 | d := 5*time.Second + 15*time.Nanosecond 21 | ds := DurationStamp(d) 22 | if got, want := ds.Duration(), d; got != want { 23 | t.Fatalf("Timestamp to Time got %q, want %q", got, want) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /proto/encoded.go.template: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "fmt" 21 | "github.com/maditya/protobuf/proto" 22 | ) 23 | 24 | type EncodedThing struct { 25 | Thing 26 | Encoding []byte 27 | } 28 | 29 | func (m *EncodedThing) UpdateEncoding() { 30 | m.Encoding = MustMarshal(&m.Thing) 31 | } 32 | 33 | func (m *EncodedThing) Reset() { 34 | *m = EncodedThing{} 35 | } 36 | 37 | func (m *EncodedThing) Size() int { 38 | return len(m.Encoding) 39 | } 40 | 41 | func (m *EncodedThing) Marshal() ([]byte, error) { 42 | size := m.Size() 43 | data := make([]byte, size) 44 | n, err := m.MarshalTo(data) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return data[:n], nil 49 | } 50 | 51 | func (m *EncodedThing) MarshalTo(data []byte) (int, error) { 52 | return copy(data, m.Encoding), nil 53 | } 54 | 55 | func (m *EncodedThing) Unmarshal(data []byte) error { 56 | m.Encoding = append([]byte{}, data...) 57 | return proto.Unmarshal(data, &m.Thing) 58 | } 59 | 60 | func NewPopulatedEncodedThing(r randyClient, easy bool) *EncodedThing { 61 | this := &EncodedThing{Thing: *NewPopulatedThing(r, easy)} 62 | this.UpdateEncoding() 63 | return this 64 | } 65 | 66 | func (this *EncodedThing) VerboseEqual(that interface{}) error { 67 | if thatP, ok := that.(*EncodedThing); ok { 68 | return this.Thing.VerboseEqual(&thatP.Thing) 69 | } 70 | if thatP, ok := that.(EncodedThing); ok { 71 | return this.Thing.VerboseEqual(&thatP.Thing) 72 | } 73 | return fmt.Errorf("types don't match: %T != EncodedThing", that) 74 | } 75 | 76 | func (this *EncodedThing) Equal(that interface{}) bool { 77 | if thatP, ok := that.(*EncodedThing); ok { 78 | return this.Thing.Equal(&thatP.Thing) 79 | } 80 | if thatP, ok := that.(EncodedThing); ok { 81 | return this.Thing.Equal(&thatP.Thing) 82 | } 83 | return false 84 | } 85 | 86 | func (this *EncodedThing) GoString() string { 87 | if this == nil { 88 | return "nil" 89 | } 90 | return `proto.EncodedThing{Thing: ` + this.Thing.GoString() + `, Encoding: ` + fmt.Sprintf("%#v", this.Encoding) + `}` 91 | } 92 | 93 | func (this *EncodedThing) String() string { 94 | if this == nil { 95 | return "nil" 96 | } 97 | return `proto.EncodedThing{Thing: ` + this.Thing.String() + `, Encoding: ` + fmt.Sprintf("%v", this.Encoding) + `}` 98 | } 99 | 100 | func (m *EncodedThing) MarshalJSON() ([]byte, error) { 101 | ret := make([]byte, base64.StdEncoding.EncodedLen(len(m.Encoding))+2) 102 | ret[0] = '"' 103 | base64.StdEncoding.Encode(ret[1:len(ret)-1], m.Encoding) 104 | ret[len(ret)-1] = '"' 105 | return ret, nil 106 | } 107 | 108 | func (m *EncodedThing) UnmarshalJSON(s []byte) error { 109 | if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { 110 | return fmt.Errorf("not a JSON quoted string: %q", s) 111 | } 112 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s)-2)) 113 | n, err := base64.StdEncoding.Decode(b, s[1:len(s)-1]) 114 | if err != nil { 115 | return err 116 | } 117 | return m.Unmarshal(b[:n]) 118 | } 119 | 120 | var _ json.Marshaler = (*EncodedThing)(nil) 121 | var _ json.Unmarshaler = (*EncodedThing)(nil) 122 | -------------------------------------------------------------------------------- /proto/encoded_test.go.template: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | github_com_gogo_protobuf_proto "github.com/maditya/protobuf/proto" 19 | github_com_gogo_protobuf_jsonpb "github.com/maditya/protobuf/jsonpb" 20 | math_rand "math/rand" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestEncodedThingProto(t *testing.T) { 26 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 27 | p := NewPopulatedEncodedThing(popr, false) 28 | data, err := github_com_gogo_protobuf_proto.Marshal(p) 29 | if err != nil { 30 | panic(err) 31 | } 32 | msg := &EncodedThing{} 33 | if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil { 34 | panic(err) 35 | } 36 | for i := range data { 37 | data[i] = byte(popr.Intn(256)) 38 | } 39 | if err := p.VerboseEqual(msg); err != nil { 40 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 41 | } 42 | if !p.Equal(msg) { 43 | t.Fatalf("%#v !Proto %#v", msg, p) 44 | } 45 | } 46 | 47 | func TestEncodedThingJSON(t *testing.T) { 48 | popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 49 | p := NewPopulatedEncodedThing(popr, true) 50 | marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} 51 | jsondata, err := marshaler.MarshalToString(p) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | msg := &EncodedThing{} 56 | err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if err := p.VerboseEqual(msg); err != nil { 61 | t.Fatalf("%#v !VerboseProto %#v, since %v", msg, p, err) 62 | } 63 | if !p.Equal(msg) { 64 | t.Fatalf("%#v !Json Equal %#v", msg, p) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /proto/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014-2015 The Dename Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | set -euo pipefail 17 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 18 | cd "$DIR" 19 | 20 | # go install ./protoc-gen-coname 21 | 22 | protoc --coname_out=plugins=grpc:. --proto_path=.:${GOPATH}/src *.proto 23 | 24 | function generateEncodedType { 25 | sed "s/Thing/$1/g" < encoded.go.template > "$1.pr.go" 26 | sed "s/Thing/$1/g" < encoded_test.go.template > "$1pr_test.go" 27 | } 28 | 29 | generateEncodedType Profile 30 | generateEncodedType Entry 31 | generateEncodedType SignedEntryUpdate 32 | generateEncodedType TimestampedEpochHead 33 | generateEncodedType EpochHead 34 | generateEncodedType AuthorizationPolicy 35 | 36 | # enable LookupProof.Entry and LookupProof.Profile to be nullable despite using a custom type 37 | sed -i.bak -e 's/m.Entry = &Entry{}/m.Entry = \&EncodedEntry{}/' client.pb.go 38 | sed -i.bak -e 's/m.Profile = &Profile{}/m.Profile = \&EncodedProfile{}/' client.pb.go 39 | 40 | # skip the text format tests (we never use the text format) 41 | sed -i.bak -e '/Test.*Text.*testing/a\ 42 | t.Skip()' *_test.go 43 | 44 | rm *.pb.go.bak *_test.go.bak || true 45 | 46 | # bound the branching factor of quorum expressions to avoid infinite recursion. 47 | awk '{ 48 | if ( $0 ~ /^func NewPopulatedQuorumExpr/ ) {f = 1} 49 | if ( f == 1 && $0 ~ /:= r\.Intn\(10\)/) { f = 0; sub(10,2,$0) } 50 | print($0) 51 | }' < client.pb.go > client.pb.go.tmp && mv client.pb.go.tmp client.pb.go 52 | -------------------------------------------------------------------------------- /proto/keyid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/binary" 19 | 20 | "golang.org/x/crypto/sha3" 21 | ) 22 | 23 | // KeyID computes the ID of public key. 24 | func KeyID(sv *PublicKey) uint64 { 25 | var h [8]byte 26 | sha3.ShakeSum256(h[:], MustMarshal(sv)) 27 | return binary.LittleEndian.Uint64(h[:8]) 28 | } 29 | -------------------------------------------------------------------------------- /proto/keyid_test.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import "testing" 4 | 5 | func TestKeyID(t *testing.T) { 6 | 7 | pk := []byte{108, 121, 82, 46, 112, 133, 74, 243, 72, 208, 82, 162, 56, 223, 221, 115, 5, 228, 171, 34, 69, 211, 87, 96, 159, 119, 223, 186, 41, 220, 44, 62} 8 | ppk := &PublicKey{ 9 | PubkeyType: &PublicKey_Ed25519{Ed25519: pk[:]}, 10 | } 11 | id := KeyID(ppk) 12 | if got, want := id, uint64(15615436926645791512); got != want { 13 | t.Fatalf("KeyID() got %v, wanted %v", got, want) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /proto/keyserverlocal.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 18 | import "replication.proto"; 19 | 20 | // ReplicaState contains the persistent internal state of a single replica. 21 | // Additional on-disk state is descried in server/table.go. 22 | message ReplicaState { 23 | // cached values derived purely from the state of the log 24 | uint64 next_index_log = 1; // index of the next replicated state machine input to be processed 25 | uint64 next_index_verifier = 2; // index for the next RSM input to be given to verifiers (this MUST be deterministic because verifiers see it through new indices) 26 | bytes previous_summary_hash = 3; 27 | EpochDelimiter last_epoch_delimiter = 4 [(gogoproto.nullable) = false]; 28 | bool this_replica_needs_to_sign_last_epoch = 5; 29 | bool pending_updates = 6; // are there any updates after the last epoch delimiter? 30 | 31 | // local variables 32 | uint64 latest_tree_snapshot = 7; // NOTE: this might be deterministic, but we definitely shouldn't rely on that 33 | bool last_epoch_needs_ratification = 8; 34 | } 35 | -------------------------------------------------------------------------------- /proto/marshal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package proto 16 | 17 | // MustMarshal takes a marshalable and returns the []byte representation. This 18 | // function must be used exclusively when a marshaling error is fatal AND 19 | // indicative of a programming bug. 20 | func MustMarshal(m interface { 21 | Marshal() ([]byte, error) 22 | }) []byte { 23 | ret, err := m.Marshal() 24 | if err != nil { 25 | panic(err) 26 | } 27 | return ret 28 | } 29 | -------------------------------------------------------------------------------- /proto/protoc-gen-coname/main.go: -------------------------------------------------------------------------------- 1 | // Extensions for Protocol Buffers to create more go like structures. 2 | // 3 | // Copyright (c) 2015, Vastech SA (PTY) LTD. All rights reserved. 4 | // http://github.com/gogo/protobuf/gogoproto 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // * Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // * Redistributions in binary form must reproduce the above 13 | // copyright notice, this list of conditions and the following disclaimer 14 | // in the documentation and/or other materials provided with the 15 | // distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | package main 30 | 31 | import ( 32 | "github.com/maditya/protobuf/vanity" 33 | "github.com/maditya/protobuf/vanity/command" 34 | ) 35 | 36 | func main() { 37 | req := command.Read() 38 | files := req.GetProtoFile() 39 | 40 | vanity.ForEachFile(files, vanity.TurnOnMarshalerAll) 41 | vanity.ForEachFile(files, vanity.TurnOnSizerAll) 42 | vanity.ForEachFile(files, vanity.TurnOnUnmarshalerAll) 43 | 44 | vanity.ForEachFile(files, vanity.TurnOffGoEnumPrefixAll) 45 | vanity.ForEachFile(files, vanity.TurnOffGoEnumStringerAll) 46 | vanity.ForEachFile(files, vanity.TurnOnEnumStringerAll) 47 | 48 | vanity.ForEachFile(files, vanity.TurnOnEqualAll) 49 | vanity.ForEachFile(files, vanity.TurnOnVerboseEqualAll) 50 | vanity.ForEachFile(files, vanity.TurnOnGoStringAll) 51 | vanity.ForEachFile(files, vanity.TurnOffGoStringerAll) 52 | vanity.ForEachFile(files, vanity.TurnOnStringerAll) 53 | 54 | vanity.ForEachFile(files, vanity.TurnOnPopulateAll) 55 | vanity.ForEachFile(files, vanity.TurnOnTestGenAll) 56 | vanity.ForEachFile(files, vanity.TurnOnBenchGenAll) 57 | 58 | resp := command.Generate(req) 59 | command.Write(resp) 60 | } 61 | -------------------------------------------------------------------------------- /proto/prune.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2014-2015 The Dename Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | cpp -P -fpreprocessed | 17 | grep -v 'import "gogoproto/gogo.proto";' | 18 | sed -r 's:^(\s*)(\S+)(\s+\S+\s*=\s*\S+).*customtype.*:\1bytes\3; // encoded \2:g' | 19 | sed 's: \[.*(gogoproto.*\]::g' 20 | -------------------------------------------------------------------------------- /proto/readme.md: -------------------------------------------------------------------------------- 1 | # End-to-End keyserver protocol 2 | 3 | The protocol consists of exchanging `protobuf3` messages over `grpc`. The 4 | message descriptors are split into three files: 5 | 6 | - `client.proto` -- all messages that are required for operation of a 7 | lightweight (stateful or stateless) client. Keyserver entries and server 8 | signature structures. 9 | - `config.proto` -- the client configuration. 10 | - `verifier.proto` -- everything that a verifier needs to use but a client does 11 | not. Pushing signatures, downloading update logs, etc. 12 | - `replication.proto` -- service-provider internal protocol, included here as a 13 | part of the reference implementation. Handles high-availability replication 14 | and synchronization of updates to the keyserver state. 15 | - `local.proto` -- service-provider internal protocol, included here as a 16 | part of the reference implementation. The local structures are user for 17 | persisting local state at each replica of the service provider. 18 | -------------------------------------------------------------------------------- /proto/replication.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 18 | import "client.proto"; 19 | import "timestamp.proto"; 20 | 21 | // KeyserverStep denotes the input to a single step of the keyserver state 22 | // machine. Serializable high-availability replication is achieved by 23 | // replicating an in-order log of all steps and having each replica reproduce 24 | // the state from them. 25 | message KeyserverStep { 26 | fixed64 UID = 1; // optional, for matching replies to clients. TODO: fixed64? 27 | // TODO: should all fields in a oneof have their own types for extensibility? 28 | oneof type { 29 | // Update is appended to the log when it is received from a client and 30 | // has passed pre-validation. However, since pre-validation is not 31 | // final, "success" should not be returned to the client until after the 32 | // update has been applied and ratified. 33 | // update is applied to the keyserver state as soon as it has been 34 | // committed to the log. 35 | UpdateRequest update = 2; 36 | // EpochDelimiter is appended to the log by a leader (not necessarily 37 | // unique) node when at least the duration EPOCH_INTERVAL_MIN and at 38 | // most EPOCH_INTERVAL_MAX after the previous epoch_delimiter has passed. 39 | // Between these times, an epoch delimiter is appended as soon as an 40 | // update is committed. 41 | // As the leader requirement for appending an epoch_delimiter is soft, 42 | // it may happen that an epoch delimiter with a epoch number not higher 43 | // than the previous gets committed to the log. It must be ignored. 44 | EpochDelimiter epoch_delimiter = 3; 45 | // ReplicaSigned for the last epoch is appended to the log 46 | // when the epoch_delimiter is committed. 47 | // After some majority of the replicas has signed the next 48 | // TimestampedEpochHead; their signatures make up the keyserver 49 | // signature. As combining signatures is deterministic, no new log 50 | // entry is appended to record that. 51 | SignedEpochHead replica_signed = 4; 52 | // VerifierSigned is appended for each new SignedEpochHead received 53 | // from a verifier; these are used to provide proof of verification to 54 | // clients. 55 | SignedEpochHead verifier_signed = 5; 56 | } 57 | } 58 | 59 | message EpochDelimiter { 60 | uint64 epoch_number = 1; // epoch numbering starts at 1 61 | Timestamp timestamp = 2 [(gogoproto.nullable) = false]; 62 | } 63 | -------------------------------------------------------------------------------- /proto/time.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import "time" 4 | 5 | func Time(t time.Time) Timestamp { 6 | return Timestamp{Seconds: t.Unix(), Nanos: int32(t.UnixNano() % 1e9)} 7 | } 8 | 9 | func (tst *Timestamp) Time() time.Time { 10 | return time.Unix(tst.Seconds, int64(tst.Nanos)) 11 | } 12 | -------------------------------------------------------------------------------- /proto/time_test.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTimestamp(t *testing.T) { 9 | tm := time.Now() 10 | ts := Time(tm) 11 | if got, want := ts.Seconds, int64(tm.Unix()); got != want { 12 | t.Fatalf("Time to Timestamp got: %q, want %q", got, want) 13 | } 14 | if got, want := ts.Nanos, int32(tm.Nanosecond()); got != want { 15 | t.Fatalf("Time to Timestamp got: %q, want %q", got, want) 16 | } 17 | 18 | } 19 | 20 | func TestTime(t *testing.T) { 21 | tm := time.Now() 22 | ts := Time(tm) 23 | if got, want := ts.Time(), tm; got != want { 24 | t.Fatalf("Timestamp to Time got %q, want %q", got, want) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /proto/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // Modified by Andres Erbsen in 2015. 4 | // https://developers.google.com/protocol-buffers/ 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are 8 | // met: 9 | // 10 | // * Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // * Redistributions in binary form must reproduce the above 13 | // copyright notice, this list of conditions and the following disclaimer 14 | // in the documentation and/or other materials provided with the 15 | // distribution. 16 | // * Neither the name of Google Inc. nor the names of its 17 | // contributors may be used to endorse or promote products derived from 18 | // this software without specific prior written permission. 19 | // 20 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | syntax = "proto3"; 32 | 33 | package proto; 34 | 35 | 36 | // A Timestamp represents a point in time independent of any time zone 37 | // or calendar, represented as seconds and fractions of seconds at 38 | // nanosecond resolution in UTC Epoch time. It is encoded using the 39 | // Proleptic Gregorian Calendar which extends the Gregorian calendar 40 | // backwards to year one. It is encoded assuming all minutes are 60 41 | // seconds long, i.e. leap seconds are "smeared" so that no leap second 42 | // table is needed for interpretation. Range is from 43 | // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. 44 | // By restricting to that range, we ensure that we can convert to 45 | // and from RFC 3339 date strings. 46 | // See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). 47 | // 48 | // Example 1: Compute Timestamp from POSIX `time()`. 49 | // 50 | // Timestamp timestamp; 51 | // timestamp.set_seconds(time(NULL)); 52 | // timestamp.set_nanos(0); 53 | // 54 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 55 | // 56 | // struct timeval tv; 57 | // gettimeofday(&tv, NULL); 58 | // 59 | // Timestamp timestamp; 60 | // timestamp.set_seconds(tv.tv_sec); 61 | // timestamp.set_nanos(tv.tv_usec * 1000); 62 | // 63 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 64 | // 65 | // FILETIME ft; 66 | // GetSystemTimeAsFileTime(&ft); 67 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 68 | // 69 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 70 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 71 | // Timestamp timestamp; 72 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 73 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 74 | // 75 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 76 | // 77 | // long millis = System.currentTimeMillis(); 78 | // 79 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 80 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 81 | // 82 | // Example 5: Compute Timestamp from Python `datetime.datetime`. 83 | // 84 | // now = datetime.datetime.utcnow() 85 | // seconds = int(time.mktime(now.timetuple())) 86 | // nanos = now.microsecond * 1000 87 | // timestamp = Timestamp(seconds=seconds, nanos=nanos) 88 | // 89 | message Timestamp { 90 | // Represents seconds of UTC time since Unix epoch 91 | // 1970-01-01T00:00:00Z. Must be from from 0001-01-01T00:00:00Z to 92 | // 9999-12-31T23:59:59Z inclusive. 93 | int64 seconds = 1; 94 | 95 | // Non-negative fractions of a second at nanosecond resolution. Negative 96 | // second values with fractions must still have non-negative nanos values 97 | // that count forward in time. Must be from 0 to 999,999,999 98 | // inclusive. 99 | int32 nanos = 2; 100 | } 101 | -------------------------------------------------------------------------------- /proto/tlsconfig.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "crypto" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "fmt" 8 | ) 9 | 10 | func certPool(certs [][]byte) (*x509.CertPool, error) { 11 | ret := x509.NewCertPool() 12 | for _, der := range certs { 13 | crt, err := x509.ParseCertificate(der) 14 | if err != nil { 15 | return nil, err 16 | } 17 | ret.AddCert(crt) 18 | } 19 | return ret, nil 20 | } 21 | 22 | func (m *TLSConfig) Config(getKey func(string) (crypto.PrivateKey, error)) (cfg *tls.Config, err error) { 23 | if m == nil { 24 | return nil, nil 25 | } 26 | cfg = new(tls.Config) 27 | for _, t := range m.Certificates { 28 | key, err := getKey(t.KeyID) 29 | if err != nil { 30 | return nil, err 31 | } 32 | cfg.Certificates = append(cfg.Certificates, tls.Certificate{ 33 | Certificate: t.Certificate, 34 | PrivateKey: key, 35 | OCSPStaple: t.OCSPStaple, 36 | }) 37 | } 38 | cfg.RootCAs, err = certPool(m.RootCAs) 39 | if err != nil { 40 | return nil, err 41 | } 42 | cfg.NextProtos = m.NextProtos 43 | cfg.ServerName = m.ServerName 44 | cfg.ClientAuth = tls.ClientAuthType(m.ClientAuth) 45 | cfg.ClientCAs, err = certPool(m.ClientCAs) 46 | if err != nil { 47 | return nil, err 48 | } 49 | for _, cs := range m.CipherSuites { 50 | cfg.CipherSuites = append(cfg.CipherSuites, uint16(cs)) 51 | } 52 | cfg.PreferServerCipherSuites = m.PreferServerCipherSuites 53 | cfg.SessionTicketsDisabled = !m.SessionTicketsEnabled 54 | if m.SessionTicketKeyID != "" { 55 | stk, err := getKey(m.SessionTicketKeyID) 56 | if err != nil { 57 | return nil, err 58 | } 59 | stk32, ok := stk.([32]byte) 60 | if !ok { 61 | return nil, fmt.Errorf("SessionTicketKey must be [32]byte, got %T (%v)", stk, stk) 62 | } 63 | cfg.SessionTicketKey = stk32 64 | } 65 | cfg.MinVersion = uint16(m.MinVersion) 66 | cfg.MaxVersion = uint16(m.MaxVersion) 67 | for _, cid := range m.CurvePreferences { 68 | cfg.CurvePreferences = append(cfg.CurvePreferences, tls.CurveID(cid)) 69 | } 70 | return cfg, nil 71 | } 72 | -------------------------------------------------------------------------------- /proto/tlsconfig.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proto; 3 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 4 | //import "/gogo.proto"; 5 | 6 | // TLSConfig structure is used to configure a TLS client or server. 7 | message TLSConfig { 8 | // Certificates contains one or more certificate chains 9 | // to present to the other side of the connection. 10 | // Server configurations must include at least one certificate. 11 | repeated CertificateAndKeyID certificates = 1; 12 | 13 | // NameToCertificate maps from a certificate name to an element of 14 | // Certificates. Note that a certificate name can be of the form 15 | // '*.example.com' and so doesn't have to be a domain name as such. 16 | // See Config.BuildNameToCertificate 17 | // The nil value causes the first element of Certificates to be used 18 | // for all connections. 19 | // map name_to_certificate = 2; 20 | 21 | // RootCAs defines the set of root certificate authorities 22 | // that clients use when verifying server certificates. 23 | // If RootCAs is nil, TLS uses the host's root CA set. 24 | // The certificates are expected in DER format. 25 | repeated bytes root_cas = 3 [(gogoproto.customname) = "RootCAs"]; 26 | 27 | // NextProtos is a list of supported, application level protocols. 28 | repeated string next_protos = 4; 29 | 30 | // ServerName is used to verify the hostname on the returned 31 | // certificates. It is also included in the client's handshake to support 32 | // virtual hosting. 33 | string server_name = 5; 34 | 35 | // ClientAuth determines the server's policy for 36 | // TLS Client Authentication. The default is NoClientCert. 37 | ClientAuthType client_auth = 6; 38 | 39 | // ClientCAs defines the set of root certificate authorities that servers 40 | // use if required to verify a client certificate by the policy in 41 | // ClientAuth. The certificates are expected in DER format. 42 | repeated bytes client_cas = 7 [(gogoproto.customname) = "ClientCAs"]; 43 | 44 | // CipherSuites is a list of supported cipher suites. If CipherSuites 45 | // is nil, TLS uses a list of suites supported by the implementation. 46 | repeated CipherSuite cipher_suites = 8; 47 | 48 | // PreferServerCipherSuites controls whether the server selects the 49 | // client's most preferred ciphersuite, or the server's most preferred 50 | // ciphersuite. If true then the server's preference, as expressed in 51 | // the order of elements in CipherSuites, is used. 52 | bool prefer_server_cipher_suites = 9; 53 | 54 | // SessionTicketsEnabled may be set to true to enable session ticket 55 | // (resumption) support. Enabling session tickets limits forward secrecy to 56 | // until after the lifetime of the session ticket key (which, by default, 57 | // lives as long as the server process). 58 | bool session_tickets_enabled = 10; 59 | 60 | // SessionTicketKey (32 bytes) is used by TLS servers to provide session 61 | // resumption. See RFC 5077. If zero, it will be filled with random data 62 | // before the first server handshake. 63 | // 64 | // If multiple servers are terminating connections for the same host 65 | // they should all have the same SessionTicketKey. If the 66 | // SessionTicketKey leaks, previously recorded and future TLS 67 | // connections using that key are compromised. 68 | string session_ticket_key_id = 11 [(gogoproto.customname) = "SessionTicketKeyID"]; 69 | 70 | // MinVersion contains the minimum SSL/TLS version that is acceptable. 71 | // If zero, then SSLv3 is taken as the minimum. 72 | TLSVersion min_version = 12; 73 | 74 | // MaxVersion contains the maximum SSL/TLS version that is acceptable. 75 | // If zero, then the maximum version supported by this package is used, 76 | // which is currently TLS 1.2. 77 | TLSVersion max_version = 13; 78 | 79 | // CurvePreferences contains the elliptic curves that will be used in 80 | // an ECDHE handshake, in preference order. If empty, the default will 81 | // be used. 82 | repeated CurveID curve_preferences = 14; 83 | } 84 | 85 | message CertificateAndKeyID { 86 | // Certificate contains the public certificates in DER format, leaf first. 87 | repeated bytes certificate = 1; 88 | string key_id = 2 [(gogoproto.customname) = "KeyID"]; 89 | bytes OCSP_staple = 3; 90 | } 91 | 92 | enum TLSVersion { 93 | TLSVersion_UNSPECIFIED = 0; 94 | 95 | VERSION_SSL30 = 0x0300; 96 | VERSION_TLS10 = 0x0301; 97 | VERSION_TLS11 = 0x0302; 98 | VERSION_TLS12 = 0x0303; 99 | } 100 | 101 | enum ClientAuthType { 102 | NO_CLIENT_CERT = 0; 103 | REQUEST_CLIENT_CERT = 1; 104 | REQUIRE_ANY_CLIENT_CERT = 2; 105 | VERIFY_CLIENT_CERT_IF_GIVEN = 3; 106 | REQUIRE_AND_VERIFY_CLIENT_CERT = 4; 107 | } 108 | 109 | enum CipherSuite { 110 | CipherSuite_UNSPECIFIED = 0; 111 | 112 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xc02f; 113 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xc030; 114 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xc027; 115 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xc013; 116 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014; 117 | TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009c; 118 | TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009d; 119 | TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003c; 120 | TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f; 121 | TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035; 122 | 123 | TLS_RSA_WITH_RC4_128_SHA = 0x0005; 124 | TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a; 125 | TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xc007; 126 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xc009; 127 | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc00a; 128 | TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xc011; 129 | TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xc012; 130 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xc02b; 131 | 132 | TLS_FALLBACK_SCSV = 0x5600; 133 | } 134 | 135 | enum CurveID { 136 | CurveID_UNSPECIFIED = 0; 137 | 138 | P256 = 23; 139 | P384 = 24; 140 | P521 = 25; 141 | } 142 | -------------------------------------------------------------------------------- /proto/tlsconfig_test.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "crypto" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/andres-erbsen/tlstestutil" 9 | ) 10 | 11 | func TestConfig(t *testing.T) { 12 | 13 | caCert, _, caKey := tlstestutil.CA(t, nil) 14 | cert := tlstestutil.Cert(t, caCert, caKey, "127.0.0.1", nil) 15 | st := [32]byte{1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2} 16 | badST := [2]byte{1, 2} 17 | errGetKey := errors.New("test error") 18 | getKey := func(keyid string) (crypto.PrivateKey, error) { 19 | switch keyid { 20 | case "tls": 21 | return cert.PrivateKey, nil 22 | case "badTLS": 23 | return nil, errGetKey 24 | case "stk": 25 | return st, nil 26 | case "badst1": 27 | return badST, nil 28 | case "badst2": 29 | return nil, errGetKey 30 | case "badst3": 31 | return nil, nil 32 | default: 33 | panic("unknown key requested in tests [" + keyid + "]") 34 | } 35 | } 36 | 37 | pcerts := []*CertificateAndKeyID{{cert.Certificate, "tls", nil}} 38 | 39 | goodTLS := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}} 40 | _, err := goodTLS.Config(getKey) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | pcertsBad := []*CertificateAndKeyID{{cert.Certificate, "badTLS", nil}} 46 | cfgBadKey := &TLSConfig{Certificates: pcertsBad, RootCAs: [][]byte{caCert.Raw}} 47 | _, err = cfgBadKey.Config(getKey) 48 | if err != errGetKey { 49 | t.Errorf("expected error: %v, got %v", errGetKey, err) 50 | } 51 | 52 | cfgSessionTkt := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}, SessionTicketKeyID: "stk"} 53 | _, err = cfgSessionTkt.Config(getKey) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | cfgBadSessionTkt1 := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}, SessionTicketKeyID: "badst1"} 59 | _, err = cfgBadSessionTkt1.Config(getKey) 60 | if err == nil { 61 | t.Errorf("expected error %q, got nil", "SessionTicketKey must be a [32]byte...") 62 | } 63 | 64 | cfgBadSessionTkt2 := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}, SessionTicketKeyID: "badst2"} 65 | _, err = cfgBadSessionTkt2.Config(getKey) 66 | if err != errGetKey { 67 | t.Errorf("expected error: %v, got %v", errGetKey, err) 68 | } 69 | 70 | cfgBadSessionTkt3 := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}, SessionTicketKeyID: "badst3"} 71 | _, err = cfgBadSessionTkt3.Config(getKey) 72 | if err == nil { 73 | t.Errorf("expected error %q, got nil", "SessionTicketKey must be a [32]byte...") 74 | } 75 | 76 | cfgCipherSuites := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}, CipherSuites: []CipherSuite{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_RC4_128_SHA}} 77 | _, err = cfgCipherSuites.Config(getKey) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | cfgCurvePref := &TLSConfig{Certificates: pcerts, RootCAs: [][]byte{caCert.Raw}, CurvePreferences: []CurveID{P256, P384, P521}} 83 | _, err = cfgCurvePref.Config(getKey) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /proto/verifier.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 18 | import "client.proto"; 19 | 20 | service E2EKSVerification { 21 | // VerifierStream accesses the public inputs to a keyserver state machine. 22 | // The returned stream is given a limited view of the log which the keyserver 23 | // being verified uses to replicate its state internally. However, private 24 | // user data, such as usernames and profile details, is not included (the 25 | // relevant fields are set to nil). note: the keyserver implementation also 26 | // uses the same log to persist verifier ratifications, but as they do not 27 | // affect any username:profile mappings, they are excluded as well. 28 | rpc VerifierStream(VerifierStreamRequest) returns (stream VerifierStep); 29 | // PushRatification is called each time a verifier who has been 30 | // successfully replaying the log returned by VerifierStream interprets a 31 | // keyserver_ratified step and agrees that the keyserver state summarized 32 | // by it is the unique result of applying all the previous update steps. 33 | // The SignedRatification will be stored by the server and used to 34 | // argue the correctness of future lookups in front of clients. 35 | rpc PushRatification(SignedEpochHead) returns (Nothing); 36 | } 37 | 38 | // UpdateRequest streams a specified number of committed updates or 39 | // ratifications. See replication.GetCommitted and replication.WaitCommitted. 40 | message VerifierStreamRequest { 41 | // Start identifies the first epoch for which verifier steps should be 42 | // returned. 43 | uint64 start = 1; 44 | // PageSize specifies number of entries to be returned, MaxUint64 for 45 | // unlimited. 46 | uint64 page_size = 2; 47 | } 48 | 49 | // VerifierStep denotes the input to a single state transition of the verified 50 | // part of the keyserver state machine. 51 | message VerifierStep { 52 | oneof type { 53 | SignedEntryUpdate Update = 1; 54 | SignedEpochHead Epoch = 2; 55 | } 56 | } 57 | 58 | message Nothing { 59 | option (gogoproto.gostring) = false; 60 | } 61 | -------------------------------------------------------------------------------- /proto/verifierconfig.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 18 | import "tlsconfig.proto"; 19 | import "client.proto"; 20 | 21 | message VerifierConfig { 22 | uint64 id = 1 [(gogoproto.customname) = "ID"]; 23 | string signing_key_id = 2 [(gogoproto.customname) = "SigningKeyID"]; 24 | 25 | string realm = 3; 26 | TLSConfig tls = 4 [(gogoproto.customname) = "TLS"]; 27 | string keyserver_addr = 5; 28 | AuthorizationPolicy initial_keyserver_auth = 6 [(gogoproto.nullable) = false]; 29 | 30 | bytes tree_nonce = 7; 31 | // LevelDBPath specifies the directory in which the database is stored. 32 | // Nothing else should use this directory. 33 | string leveldb_path = 8 [(gogoproto.customname) = "LevelDBPath"]; 34 | } 35 | -------------------------------------------------------------------------------- /proto/verifierlocal.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | syntax = "proto3"; 16 | package proto; 17 | import "github.com/maditya/protobuf/gogoproto/gogo.proto"; 18 | import "client.proto"; 19 | 20 | // Verifier contains the persistent internal state of a verifier. 21 | // Additional on-disk state is described in verifier/table.go. 22 | message VerifierState { 23 | uint64 next_index = 1; 24 | uint64 next_epoch = 2; 25 | bytes previous_summary_hash = 3; 26 | uint64 latest_tree_snapshot = 4; 27 | AuthorizationPolicy keyserver_auth = 5; 28 | } 29 | -------------------------------------------------------------------------------- /verifier/deploy/gen-config/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/pem" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "strconv" 11 | 12 | "github.com/maditya/protobuf/jsonpb" 13 | "github.com/yahoo/coname/proto" 14 | ) 15 | 16 | func make_config(caCert *x509.Certificate, cert *x509.Certificate, id uint64, inputCfg *proto.VerifierConfig) { 17 | cfg := &proto.VerifierConfig{ 18 | ID: id, 19 | SigningKeyID: "signing.ed25519secret", 20 | Realm: "yahoo", 21 | TLS: &proto.TLSConfig{RootCAs: [][]byte{caCert.Raw}, Certificates: []*proto.CertificateAndKeyID{{[][]byte{cert.Raw}, "tls", nil}}}, 22 | InitialKeyserverAuth: inputCfg.InitialKeyserverAuth, 23 | KeyserverAddr: inputCfg.KeyserverAddr, 24 | } 25 | 26 | configF, err := os.OpenFile("config.json", os.O_WRONLY|os.O_CREATE, 0600) 27 | defer configF.Close() 28 | if err != nil { 29 | log.Panic(err) 30 | } 31 | err = new(jsonpb.Marshaler).Marshal(configF, cfg) 32 | if err != nil { 33 | log.Panic(err) 34 | } 35 | 36 | } 37 | 38 | func main() { 39 | // input config file contains initial_keyserver_auth and keyserver_addr 40 | // this file will be provided by keyserver admin 41 | if len(os.Args) != 5 { 42 | fmt.Printf("usage: %s cacert cert id inputcfg\n", os.Args[0]) 43 | return 44 | } 45 | caCertFile := os.Args[1] 46 | certFile := os.Args[2] 47 | cfgF := os.Args[4] 48 | id, err := strconv.ParseUint(os.Args[3], 10, 64) 49 | if err != nil { 50 | log.Fatalf("Failed to convert %v to uint: %v", os.Args[3], err) 51 | } 52 | caCertPem, err := ioutil.ReadFile(caCertFile) 53 | if err != nil { 54 | log.Fatalf("Failed to read from %v: %v", caCertFile, err) 55 | } 56 | caCertBlock, _ := pem.Decode(caCertPem) 57 | if caCertBlock == nil { 58 | log.Fatalf("Failed to parse PEM: %v", string(caCertPem)) 59 | } 60 | caCert, err := x509.ParseCertificate(caCertBlock.Bytes) 61 | if err != nil { 62 | log.Fatalf("Failed to parse X.509 cert: %v", err) 63 | } 64 | 65 | certPEM, err := ioutil.ReadFile(certFile) 66 | if err != nil { 67 | log.Fatalf("Failed to read from %v: %v", certFile, err) 68 | } 69 | 70 | certBlock, _ := pem.Decode(certPEM) 71 | if certBlock == nil { 72 | log.Fatalf("Failed to parse PEM: %v", string(certPEM)) 73 | } 74 | cert, err := x509.ParseCertificate(certBlock.Bytes) 75 | if err != nil { 76 | log.Fatalf("Failed to parse X.509 cert: %v", err) 77 | } 78 | 79 | configReader, err := os.Open(cfgF) 80 | if err != nil { 81 | log.Fatalf("Failed to open input configuration file %v: %v", cfgF, err) 82 | } 83 | cfg := &proto.VerifierConfig{} 84 | err = jsonpb.Unmarshal(configReader, cfg) 85 | if err != nil { 86 | log.Fatalf("Failed to parse input configuration file %v: %v", cfgF, err) 87 | } 88 | 89 | make_config(caCert, cert, uint64(id), cfg) 90 | } 91 | -------------------------------------------------------------------------------- /verifier/deploy/gen-csr-id/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "strconv" 14 | 15 | "github.com/agl/ed25519" 16 | "github.com/yahoo/coname/proto" 17 | ) 18 | 19 | func main() { 20 | // This script generates CSR, private key and a verifier ID. 21 | // Keyserver admin will sign the CSR and provide the cert 22 | privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 23 | if err != nil { 24 | panic(err) 25 | } 26 | pk, sk, err := ed25519.GenerateKey(rand.Reader) 27 | if err != nil { 28 | panic(err) 29 | } 30 | sv := &proto.PublicKey{PubkeyType: &proto.PublicKey_Ed25519{Ed25519: pk[:]}} 31 | hostname := fmt.Sprintf("verifier %x", proto.KeyID(sv)) 32 | fmt.Println("verifier ID: " + hostname + "\nID(uint): " + strconv.FormatUint(proto.KeyID(sv), 10)) 33 | err = ioutil.WriteFile("signing.ed25519secret", []byte(sk[:]), 0644) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | certTemplate := &x509.CertificateRequest{ 39 | Subject: pkix.Name{CommonName: hostname}, 40 | } 41 | csrDER, err := x509.CreateCertificateRequest(rand.Reader, certTemplate, privKey) 42 | if err != nil { 43 | panic(err) 44 | } 45 | csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}) 46 | // openssl req -in csr.pem -noout -text 47 | err = ioutil.WriteFile("csr.pem", csrPEM, 0644) 48 | if err != nil { 49 | panic(err) 50 | } 51 | keyF, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE, 0644) 52 | defer keyF.Close() 53 | if err != nil { 54 | panic(err) 55 | } 56 | skDer, err := x509.MarshalECPrivateKey(privKey) 57 | if err != nil { 58 | panic(err) 59 | } 60 | err = pem.Encode(keyF, &pem.Block{ 61 | Type: "EC PRIVATE KEY", 62 | Headers: make(map[string]string), 63 | Bytes: skDer, 64 | }) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /verifier/deploy/sign-csr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rand" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "math/big" 13 | "os" 14 | "time" 15 | ) 16 | 17 | func newSerial(rnd io.Reader) (*big.Int, error) { 18 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 19 | return rand.Int(rnd, serialNumberLimit) 20 | } 21 | 22 | func sign_csr(caKey *ecdsa.PrivateKey, caCert *x509.Certificate, csr *x509.CertificateRequest) { 23 | serial, err := newSerial(rand.Reader) 24 | if err != nil { 25 | panic(err) 26 | } 27 | certTemplate := &x509.Certificate{ 28 | Subject: csr.Subject, 29 | SerialNumber: serial, 30 | NotBefore: time.Now(), 31 | NotAfter: time.Now().AddDate(2 /* years */, 0, 0), 32 | KeyUsage: x509.KeyUsageDigitalSignature, 33 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 34 | } 35 | certDER, err := x509.CreateCertificate(rand.Reader, certTemplate, caCert, csr.PublicKey.(*ecdsa.PublicKey), caKey) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 41 | // openssl x509 -in cert.pem -noout -text 42 | err = ioutil.WriteFile("cert.pem", certPem, 0644) 43 | if err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | func main() { 49 | if len(os.Args) != 4 { 50 | fmt.Printf("usage: %s cacert cakey csr\n", os.Args[0]) 51 | return 52 | } 53 | caCertFile := os.Args[1] 54 | caKeyFile := os.Args[2] 55 | csrFile := os.Args[3] 56 | caCertPem, err := ioutil.ReadFile(caCertFile) 57 | if err != nil { 58 | log.Fatalf("Failed to read from %v: %v", caCertFile, err) 59 | } 60 | caCertBlock, _ := pem.Decode(caCertPem) 61 | if caCertBlock == nil { 62 | log.Fatalf("Failed to parse PEM: %v", string(caCertPem)) 63 | } 64 | caCert, err := x509.ParseCertificate(caCertBlock.Bytes) 65 | if err != nil { 66 | log.Fatalf("Failed to parse X.509 cert: %v", err) 67 | } 68 | 69 | caKeyPem, err := ioutil.ReadFile(caKeyFile) 70 | if err != nil { 71 | log.Fatalf("Failed to read from %v: %v", caKeyFile, err) 72 | } 73 | caKeyBlock, _ := pem.Decode(caKeyPem) 74 | if caKeyBlock == nil { 75 | log.Fatalf("Failed to parse PEM: %v", string(caKeyPem)) 76 | } 77 | caKey, err := x509.ParseECPrivateKey(caKeyBlock.Bytes) 78 | if err != nil { 79 | log.Fatalf("Failed to parse EC private key: %v", err) 80 | } 81 | csrPem, err := ioutil.ReadFile(csrFile) 82 | if err != nil { 83 | log.Fatalf("Failed to read from %v: %v", csrPem, err) 84 | } 85 | csrBlock, _ := pem.Decode(csrPem) 86 | if csrBlock == nil { 87 | log.Fatalf("Failed to parse PEM: %v", string(csrPem)) 88 | } 89 | csr, err := x509.ParseCertificateRequest(csrBlock.Bytes) 90 | if err != nil { 91 | log.Fatalf("Failed to parse CSR: %v", err) 92 | } 93 | sign_csr(caKey, caCert, csr) 94 | 95 | } 96 | -------------------------------------------------------------------------------- /verifier/table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Dename Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | package verifier 16 | 17 | import ( 18 | "encoding/binary" 19 | "github.com/yahoo/coname/vrf" 20 | ) 21 | 22 | var ( 23 | tableVerifierLogPrefix byte = 'v' // index uint64 -> proto.VerifierStep 24 | tableRatificationsPrefix byte = 'r' // epoch uint64, ratifier uint64 -> proto.SignedRatification 25 | tableMerkleTreePrefix byte = 't' 26 | tableEntriesPrefix byte = 'e' // vrfidx [vrf.Size]byte -> epoch uint64 -> proto.Entry 27 | 28 | tableVerifierState = []byte{'a'} // proto.VeriferState 29 | ) 30 | 31 | func tableRatifications(epoch, ratifier uint64) []byte { 32 | ret := make([]byte, 1+8+8) 33 | ret[0] = tableRatificationsPrefix 34 | binary.BigEndian.PutUint64(ret[1:1+8], epoch) 35 | binary.BigEndian.PutUint64(ret[1+8:1+8+8], ratifier) 36 | return ret 37 | } 38 | 39 | func tableVerifierLog(index uint64) []byte { 40 | ret := make([]byte, 1+8) 41 | ret[0] = tableVerifierLogPrefix 42 | binary.BigEndian.PutUint64(ret[1:1+8], index) 43 | return ret 44 | } 45 | 46 | func tableEntries(vrfidx []byte, epoch uint64) []byte { 47 | ret := make([]byte, 1+vrf.Size+8) 48 | ret[0] = tableEntriesPrefix 49 | copy(ret[1:1+vrf.Size], vrfidx) 50 | binary.BigEndian.PutUint64(ret[1+vrf.Size:1+vrf.Size+8], epoch) 51 | return ret 52 | } 53 | -------------------------------------------------------------------------------- /verifier/verifierserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "flag" 10 | "fmt" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | "os/signal" 15 | "path" 16 | "strings" 17 | 18 | "github.com/agl/ed25519" 19 | "github.com/maditya/protobuf/jsonpb" 20 | "github.com/syndtr/goleveldb/leveldb" 21 | 22 | "github.com/yahoo/coname/keyserver/kv/leveldbkv" 23 | "github.com/yahoo/coname/proto" 24 | "github.com/yahoo/coname/verifier" 25 | ) 26 | 27 | func main() { 28 | configPathPtr := flag.String("config", "config.json", "path to config file") 29 | flag.Parse() 30 | 31 | configReader, err := os.Open(*configPathPtr) 32 | if err != nil { 33 | log.Fatalf("Failed to open configuration file: %s", err) 34 | } 35 | cfg := &proto.VerifierConfig{} 36 | err = jsonpb.Unmarshal(configReader, cfg) 37 | if err != nil { 38 | log.Fatalf("Failed to parse configuration file: %s", err) 39 | } 40 | 41 | leveldb, err := leveldb.OpenFile(cfg.LevelDBPath, nil) 42 | if err != nil { 43 | log.Fatalf("Couldn't open DB in directory %s: %s", cfg.LevelDBPath, err) 44 | } 45 | db := leveldbkv.Wrap(leveldb) 46 | 47 | server, err := verifier.Start(cfg, db, getKey) 48 | if err != nil { 49 | panic(err) 50 | } 51 | defer server.Stop() 52 | 53 | ch := make(chan os.Signal, 1) 54 | signal.Notify(ch, os.Interrupt) 55 | <-ch 56 | } 57 | 58 | // This getKey interprets key IDs as paths, and loads private keys from the 59 | // specified file 60 | func getKey(keyid string) (crypto.PrivateKey, error) { 61 | fileContents, err := ioutil.ReadFile(keyid) 62 | if err != nil { 63 | return nil, err 64 | } 65 | if path.Ext(keyid) == ".ed25519secret" { 66 | if got, want := len(fileContents), ed25519.PrivateKeySize; got != want { 67 | return nil, fmt.Errorf("ed25519 private key %s has wrong size %d (want %d)", keyid, got, want) 68 | } 69 | var keyArray [ed25519.PrivateKeySize]uint8 70 | copy(keyArray[:], fileContents) 71 | return &keyArray, nil 72 | } else { 73 | keyPEM := fileContents 74 | var keyDER *pem.Block 75 | for { 76 | keyDER, keyPEM = pem.Decode(keyPEM) 77 | if keyDER == nil { 78 | return nil, fmt.Errorf("failed to parse key PEM in %s", keyid) 79 | } 80 | if keyDER.Type == "PRIVATE KEY" || strings.HasSuffix(keyDER.Type, " PRIVATE KEY") { 81 | break 82 | } 83 | } 84 | return parsePrivateKey(keyDER.Bytes) 85 | } 86 | } 87 | 88 | func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { 89 | // Copied from the parsePrivateKey function in crypto/tls 90 | if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { 91 | return key, nil 92 | } 93 | if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { 94 | switch key := key.(type) { 95 | case *rsa.PrivateKey, *ecdsa.PrivateKey: 96 | return key, nil 97 | default: 98 | return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping") 99 | } 100 | } 101 | if key, err := x509.ParseECPrivateKey(der); err == nil { 102 | return key, nil 103 | } 104 | 105 | return nil, fmt.Errorf("failed to parse private key") 106 | } 107 | --------------------------------------------------------------------------------