Skip to content

Commit 1606811

Browse files
committed
Implement the new retrieve endpoint and start on tests
1 parent 0a4b5ec commit 1606811

18 files changed

+400
-468
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ gem('faraday')
66
gem('rbnacl')
77
gem('mysql2')
88
gem('grpc-tools')
9+
gem('rubyzip')

Gemfile.lock

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ GEM
1414
mysql2 (0.5.3)
1515
rbnacl (7.1.1)
1616
ffi
17+
rubyzip (2.3.0)
1718

1819
PLATFORMS
1920
ruby
@@ -26,6 +27,7 @@ DEPENDENCIES
2627
mocha
2728
mysql2
2829
rbnacl
30+
rubyzip
2931

3032
BUNDLED WITH
3133
2.1.4
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package main
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"crypto/x509"
8+
"encoding/hex"
9+
"fmt"
10+
)
11+
12+
func main() {
13+
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
14+
if err != nil {
15+
panic(err)
16+
}
17+
data, err := x509.MarshalECPrivateKey(privateKey)
18+
if err != nil {
19+
panic(err)
20+
}
21+
fmt.Println(hex.EncodeToString(data))
22+
}

dev.yml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ up:
2323
env:
2424
KEY_CLAIM_TOKEN: test=ON
2525
RETRIEVE_HMAC_KEY: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
26+
ECDSA_KEY: 30770201010420a6885a310b694b7bb4ba985459de1e79446dddcd1247c62ece925402b362a110a00a06082a8648ce3d030107a1440342000403eb64f714c4b4ed394331c26c31b7ce7156d00fb28982ad2679a87eaa1a3869802fbeb1d7ee28002762921929c3f7603672d535fcac3d24d57afbb4e2d97f5a
2627
DATABASE_URL: "root@tcp(covidshield.railgun)/covidshield"
2728
DB_HOST: covidshield.railgun
2829
DB_USER: root

examples/retrieval/app.rb

+25-65
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
require_relative('../../test/lib/protocol/covidshield_pb')
77

88
class Database
9-
Fetch = Struct.new(:date, :hour)
9+
Fetch = Struct.new(:period)
1010

1111
def initialize
1212
@fetches = []
@@ -17,21 +17,13 @@ def drop_old_data
1717
@fetches.reject! { |fetch| fetch.date < min_date }
1818
end
1919

20-
def has_all_hours?(date)
21-
(0..23).all? { |hour| has_hour?(date, hour) }
20+
def fetched?(period)
21+
@fetches.include?(Fetch.new(period))
2222
end
2323

24-
def has_hour?(date, hour)
25-
@fetches.include?(Fetch.new(date, hour))
26-
end
27-
28-
def mark_hours_fetched(date)
29-
(0..23).each { |hour| mark_hour_fetched(date, hour) }
30-
end
31-
32-
def mark_hour_fetched(date, hour)
33-
return if @fetches.include?(Fetch.new(date, hour))
34-
@fetches << Fetch.new(date, hour)
24+
def mark_fetched(period)
25+
return if @fetches.include?(Fetch.new(period))
26+
@fetches << Fetch.new(period)
3527
end
3628

3729
private
@@ -89,15 +81,10 @@ def fetch_new_keys
8981

9082
@database.drop_old_data
9183

92-
(1..14).to_a.reverse.each do |days_ago| # [1, 14] => 14,13,...,2,1
93-
date = today_utc.prev_day(days_ago)
94-
fetch_date(date) unless @database.has_all_hours?(date)
95-
end
96-
97-
# ... is exclusive range -- [0, hour)
98-
(0...current_hour_number_within_utc_day).each do |hour|
99-
date = today_utc
100-
fetch_hour(date, hour) unless @database.has_hour?(date, hour)
84+
curr = current_period
85+
168.times do |n|
86+
period = curr - (2 * (n + 1))
87+
fetch_period(period) unless @database.fetched?(period)
10188
end
10289
end
10390

@@ -108,40 +95,25 @@ def fetch_exposure_config(region)
10895
raise("failed") unless resp['Content-Type'] == 'application/json'
10996
end
11097

111-
def fetch_date(date)
112-
puts "Fetching date: #{date}"
113-
resp = Faraday.get(date_url(date))
98+
def fetch_period(period)
99+
puts "Fetching period: #{period}"
100+
resp = Faraday.get(period_url(period))
114101
raise("failed") unless resp.status == 200
115-
raise("failed") unless resp['Content-Type'] == 'application/x-protobuf; delimited=true'
102+
raise("failed") unless resp['Content-Type'] == 'application/zip'
116103
db_transaction do
117-
keys = parse_and_save_keys_from(resp)
118-
puts("retrieved #{keys.size} keys")
119-
@database.mark_hours_fetched(date)
104+
keys = send_to_framework(resp)
105+
puts("retrieved pack")
106+
@database.mark_fetched(period)
120107
end
121108
end
122109

123-
def fetch_hour(date, hour)
124-
puts "Fetching hour: #{date} // #{hour}"
125-
resp = Faraday.get(hour_url(date, hour))
126-
raise("failed") unless resp.status == 200
127-
raise("failed") unless resp['Content-Type'] == 'application/x-protobuf; delimited=true'
128-
db_transaction do
129-
keys = parse_and_save_keys_from(resp)
130-
puts("retrieved #{keys.size} keys")
131-
@database.mark_hour_fetched(date, hour)
132-
end
110+
def current_period
111+
(Time.now.to_i / 3600 / 2) * 2
133112
end
134113

135-
BIG_ENDIAN_UINT32 = 'N'
136-
137-
def parse_and_save_keys_from(resp)
138-
buf = resp.body.each_byte.to_a
139-
files = []
140-
until buf.empty?
141-
len = buf.shift(4).map(&:chr).join.unpack(BIG_ENDIAN_UINT32).first
142-
files << Covidshield::File.decode(buf.shift(len).map(&:chr).join)
143-
end
144-
files.flat_map(&:key)
114+
def send_to_framework(resp)
115+
# See retrieve_test.rb for a ruby example of how to load this format, but probably,
116+
# you'll just be feeding the response body to the EN framework.
145117
end
146118

147119
def db_transaction
@@ -152,23 +124,11 @@ def exposure_configuration_url(region)
152124
"#{KEY_RETRIEVAL_URL}/exposure-configuration/#{region}.json"
153125
end
154126

155-
def date_url(date)
156-
message = "#{date.iso8601}:#{format("%02d", hour_number)}"
157-
key = [ENV.fetch("RETRIEVE_HMAC_KEY")].pack("H*")
158-
hmac = OpenSSL::HMAC.hexdigest("SHA256", key, message)
159-
"#{KEY_RETRIEVAL_URL}/retrieve-day/#{date.iso8601}/#{hmac}"
160-
end
161-
162-
def hour_url(date, hour)
163-
hour = format("%02d", hour)
164-
message = "#{date.iso8601}:#{hour}:#{format("%02d", hour_number)}"
127+
def period_url(period)
128+
message = "#{period}:#{hour_number}"
165129
key = [ENV.fetch("RETRIEVE_HMAC_KEY")].pack("H*")
166130
hmac = OpenSSL::HMAC.hexdigest("SHA256", key, message)
167-
"#{KEY_RETRIEVAL_URL}/retrieve-hour/#{date.iso8601}/#{hour}/#{hmac}"
168-
end
169-
170-
def current_hour_number_within_utc_day
171-
(Time.now.to_i % 86400) / 3600
131+
"#{KEY_RETRIEVAL_URL}/retrieve/#{period}/#{hmac}"
172132
end
173133

174134
def hour_number(at = Time.now)

pkg/app/app.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,11 @@ func (a *AppBuilder) WithRetrieval() *AppBuilder {
5858

5959
a.defaultServerPort = defaultRetrievalServerPort
6060

61-
key, err := retrieval.DummyKey()
62-
if err != nil {
63-
panic(err)
64-
}
65-
signer := retrieval.NewSigner(key)
61+
a.components = append(a.components, newExpirationWorker(a.database))
6662

6763
a.servlets = append(a.servlets, server.NewConfigServlet())
68-
a.servlets = append(a.servlets, server.NewRetrieveServlet(a.database, retrieval.NewAuthenticator(), signer))
64+
a.servlets = append(a.servlets, server.NewRetrieveServlet(a.database, retrieval.NewAuthenticator(), retrieval.NewSigner()))
6965

70-
a.components = append(a.components, newExpirationWorker(a.database))
7166
return a
7267
}
7368

pkg/retrieval/authenticator.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ func (a *authenticator) Authenticate(requestedHour string, auth string) bool {
5252

5353
currentHour := int(timemath.HourNumber(time.Now()))
5454

55-
if validMAC([]byte(requestedHour+strconv.Itoa(currentHour)), dst, a.hmacKey) {
55+
if validMAC([]byte(requestedHour+":"+strconv.Itoa(currentHour)), dst, a.hmacKey) {
5656
return true
5757
}
58-
if validMAC([]byte(requestedHour+strconv.Itoa(currentHour-1)), dst, a.hmacKey) {
58+
if validMAC([]byte(requestedHour+":"+strconv.Itoa(currentHour-1)), dst, a.hmacKey) {
5959
return true
6060
}
61-
if validMAC([]byte(requestedHour+strconv.Itoa(currentHour+1)), dst, a.hmacKey) {
61+
if validMAC([]byte(requestedHour+":"+strconv.Itoa(currentHour+1)), dst, a.hmacKey) {
6262
return true
6363
}
6464
return false

pkg/retrieval/retrieval.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,24 @@ func SerializeTo(
104104
if err != nil {
105105
return err
106106
}
107-
if _, err := f.Write(exportBinData); err != nil {
107+
n, err := f.Write(exportBinData)
108+
if err != nil {
108109
return err
109110
}
111+
if n != len(exportBinData) {
112+
panic("len")
113+
}
110114
f, err = zipw.Create("export.sig")
111115
if err != nil {
112116
return err
113117
}
114-
if _, err := f.Write(exportSigData); err != nil {
118+
n, err = f.Write(exportSigData)
119+
if err != nil {
115120
return err
116121
}
122+
if n != len(exportSigData) {
123+
panic("len")
124+
}
117125

118126
return zipw.Close()
119127
}

pkg/retrieval/signer.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"crypto/elliptic"
66
"crypto/rand"
77
"crypto/sha256"
8+
"crypto/x509"
9+
"encoding/hex"
10+
"os"
811
)
912

1013
type Signer interface {
@@ -15,8 +18,22 @@ type signer struct {
1518
privateKey *ecdsa.PrivateKey
1619
}
1720

18-
func NewSigner(key *ecdsa.PrivateKey) Signer {
19-
return &signer{privateKey: key}
21+
func NewSigner() Signer {
22+
ecdsaKeyHex := os.Getenv("ECDSA_KEY")
23+
if ecdsaKeyHex == "" {
24+
panic("no ECDSA_KEY")
25+
}
26+
ecdsaKey, err := hex.DecodeString(ecdsaKeyHex)
27+
if err != nil {
28+
panic(err)
29+
}
30+
31+
priv, err := x509.ParseECPrivateKey(ecdsaKey)
32+
if err != nil {
33+
panic(err)
34+
}
35+
36+
return &signer{privateKey: priv}
2037
}
2138

2239
func (s *signer) Sign(data []byte) ([]byte, error) {
@@ -28,7 +45,3 @@ func (s *signer) Sign(data []byte) ([]byte, error) {
2845
signatureX962 := elliptic.Marshal(elliptic.P256(), a, b)
2946
return signatureX962, nil
3047
}
31-
32-
func DummyKey() (*ecdsa.PrivateKey, error) {
33-
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
34-
}

0 commit comments

Comments
 (0)