Skip to content

Instantly share code, notes, and snippets.

@jswanner jswanner/canary.rb forked from mclosson/canary.rb
Last active Aug 29, 2015

Embed
What would you like to do?
# https://blog.spideroak.com/20140814060007-status-reports-transparency-overall-safety
# https://spideroak.com/canary
# https://en.wikipedia.org/wiki/Warrant_canary
# https://www.eff.org/deeplinks/2014/04/warrant-canary-faq
# https://en.wikipedia.org/wiki/National_security_letter
#
# SpiderOak now maintains a warrant canary so they can passively let their users know
# if they have been served a National Security Letter or other legal tool which
# prevents them from actively disclosing to their users that they are being coerced
# or forced into compromising the security or privacy of their userbase.
#
# If the canary has not been killed and you can still trust their service then you will
# be able to verify 3 valid PGP signatures against their canary.
#
# The signatures will be made every six months between:
# August 10 - August 15
# February 10 - February 15
#
# The default signing keys are listed below however they may be replaced or updated
# if one of the default signers goes rogue or leaves SpiderOak.
#
# Key ID: 0x1712D3E36F2F0403
# Primary key fingerprint: 0DAB 1518 36A3 CBBA 0362 FC17 1712 D3E3 6F2F 0403
#
# Key ID: 0xE435F15CA6B145F8
# Primary key fingerprint: B411 3438 B56B D51C D208 E17E E435 F15C A6B1 45F8
#
# Key ID: 0x132B9F2251131E5C
# Primary key fingerprint: DC1E FB15 4444 E4B5 2726 8430 132B 9F22 5113 1E5C
#
# Example output:
#
# Chip Black (SpiderOak Canary) <chip@spideroak.com> signed canary on August 11 2014 with key: 0DAB 1518 36A3 CBBA 0362 FC17 1712 D3E3 6F2F 0403
# Frank Sievertsen <frank@spideroak.com> signed canary on August 12 2014 with key: B411 3438 B56B D51C D208 E17E E435 F15C A6B1 45F8
# Tomas Touceda (SpiderOak canary) <tomas@spideroak.com> signed canary on August 13 2014 with key: DC1E FB15 4444 E4B5 2726 8430 132B 9F22 5113 1E5C
# SpiderOak's Canary is alive and well!
#
# NOTE: This needs to have error handling added in various places, communicating with the network
# running GPG commands, handling if you actually do trust the signing keys, and the date ranges
# for the signature checking needs to be made more generic and the code should be refactored
# but its a start.
require 'open3'
require 'open-uri'
require 'tempfile'
class SpiderOakCanary
CANARY_URL = 'https://spideroak.com/canary'
DELIMITER = "-----BEGIN PGP SIGNATURE-----\n"
DEFAULT_KEYS = [
'0DAB 1518 36A3 CBBA 0362 FC17 1712 D3E3 6F2F 0403',
'B411 3438 B56B D51C D208 E17E E435 F15C A6B1 45F8',
'DC1E FB15 4444 E4B5 2726 8430 132B 9F22 5113 1E5C'
]
def verify(out_stream)
lines = open(CANARY_URL).readlines
chunk_idx = 0
chunks = lines.chunk { |line|
DELIMITER == line ? chunk_idx+=1 : chunk_idx
}.map { |chunk|
chunk.last.join
}
canary, signatures = chunks.shift, chunks
expected_signatures = {}
canary_file = Tempfile.new('')
canary_file.write(canary)
canary_file.close
signatures.each do |signature|
command = "gpg --verify - #{canary_file.to_path}"
stdin, stdout, stderr = Open3.popen3(command)
stdin.puts signature
stdin.close
output = stderr.readlines
signature_date = DateTime.parse(output[0][/made (.*) using/, 1])
signer = output[1][/Good signature from \"(.*)\"/, 1]
primary_key = output.select { |line| line.include?('Primary key') }.first
key_fingerprint = primary_key[/fingerprint: (.*)/, 1]
expected_signatures[key_fingerprint] = signature_date
out_stream.puts "#{signer} signed canary on #{signature_date.strftime('%B %d %Y')} " +
"with key: #{key_fingerprint}"
end
valid_signatures = DEFAULT_KEYS.select do |key|
signature_date = expected_signatures[key]
#
# TODO: Automatically determine the appropriate date range of signatures
# to check based on the current date or a passed in date.
# Remove hardcoded date range below.
#
#current_month = Date.today.month
#current_year = Date.today.year
#start_date = DateTime.parse("10-#{current_month}-#{current_year}")
#start_date = DateTime.parse("16-#{current_month}-#{current_year}")
start_date = DateTime.parse("August 10, 2014")
end_date = DateTime.parse("August 16, 2014")
(start_date...end_date).cover? signature_date
end.count
if valid_signatures == 3
out_stream.puts "SpiderOak's Canary is alive and well!"
else
out_stream.puts "Hrmmm SpiderOak's Canary seems to have died, consider your options."
end
canary_file.unlink
end
end
# Run program if this file was executed,
# but not if file was just required
if $0 == __FILE__
SpiderOakCanary.new.verify(STDOUT)
end
require 'stringio'
require 'vcr'
require_relative 'canary'
VCR.configure do |c|
c.cassette_library_dir = '.'
c.hook_into :webmock
end
describe SpiderOakCanary, '#verify' do
let(:output) { <<-OUTPUT }
Chip Black (SpiderOak Canary) <chip@spideroak.com> signed canary on August 11 2014 with key: 0DAB 1518 36A3 CBBA 0362 FC17 1712 D3E3 6F2F 0403
Frank Sievertsen <frank@spideroak.com> signed canary on August 12 2014 with key: B411 3438 B56B D51C D208 E17E E435 F15C A6B1 45F8
Tomas Touceda (SpiderOak canary) <tomas@spideroak.com> signed canary on August 13 2014 with key: DC1E FB15 4444 E4B5 2726 8430 132B 9F22 5113 1E5C
SpiderOak's Canary is alive and well!
OUTPUT
it 'writes output' do
strio = StringIO.new
VCR.use_cassette('spideroak') { described_class.new.verify(strio) }
expect(strio.string).to eq output
end
end
---
http_interactions:
- request:
method: get
uri: https://spideroak.com/canary
body:
encoding: US-ASCII
string: ''
headers:
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
User-Agent:
- Ruby
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Wed, 10 Sep 2014 06:37:46 GMT
Content-Type:
- text/plain; charset=utf8
Content-Length:
- '2376'
Last-Modified:
- Sun, 07 Sep 2014 19:58:37 GMT
Connection:
- keep-alive
Expires:
- Wed, 24 Sep 2014 06:37:46 GMT
Cache-Control:
- max-age=1209600
Set-Cookie:
- uid=AAAAKlQP8bqZBDEMEIazAg==; expires=Thu, 10-Sep-15 06:37:46 GMT; path=/
Accept-Ranges:
- bytes
body:
encoding: ASCII-8BIT
string: !binary |-
ZW46IEV2ZXJ5dGhpbmcncyBnb2luZyBzbW9vdGhseSBzbyBmYXIuCmRlOiBC
aXNoZXIgbMOkdWZ0IGFsbGVzIHByb2JsZW1sb3MuCmVzOiBUb2RvIHZhIGJp
ZW4gcG9yIGFob3JhLgoKSGF3YWlpYW4gR292ZXJub3IgTG9zZXMgUHJpbWFy
eSBieSBXaWRlIE1hcmdpbjsgU2VuYXRlIFJhY2UgSXMgVW5kZWNpZGVkIC0g
QXVnLiAxMCwgMjAxNCAtIFRoZSBOZXcgWW9yayB0aW1lcwoKYXVndXN0IDEw
LCAyMDE0Ci0tLS0tQkVHSU4gUEdQIFNJR05BVFVSRS0tLS0tClZlcnNpb246
IEdudVBHIHYxLjQuMTMgKE9wZW5CU0QpCgppUUdjQkFBQkNnQUdCUUpUNk9m
ckFBb0pFQmNTMCtOdkx3UUR3RmtNQUk3MkFMWGswc3pxaUVhUlpiRGZIQ2l0
CkZ4V05xQzlpMHk1akQ3bnNGUmtHOEVZaDJVdW85bEZrMWRRbG5qeFlqTGlu
c3R2NHorcDU3c1JmUHllWlpHNHIKZTZxZ3dwcUxyZzQvMDVlcndseVY4a2Vw
ZHAwdm9MdzlRbk9kbHl2REd5TmgrNGIxQ3duNU1pa044R1lseE5jVQpjV1J6
VzI0SEJ0R2xHUlVhUVMxSUh6WEJhbjhHUU5GK2tNa1YzbnU0eVFoMXZxbFgw
K1NyeGtlUGJnbjJsOXVzClFmbGYxdnQza1JhRTBDRnZDZjU2Z2NWWTRRUzRL
ZERGVzFldllnbUdwMkRBblRXc2dLeC9pYU9STWt0ZkkxMEoKTzIvOGEvd2I4
aVhsdUdOZjFZRmt0VXZtNllKblUybVJRRWRaSHZQTVphckFFTjRYTG5yK1dM
WGNWK0w3YzV3dgpNYjMvOFN6RXdIZ0RFRjhNTXludldZcmMyLzBFT3l4cU5i
L1M5a0JGMFhyNzdoRDdqU2RNMFVXZU51V2lKMlNlCndiUzJtWEJ3UDFjaTh3
NDVxZlpNNXFNcWJDRlc4aExYcnMyb3dhYlRKZWc5OEp3em1QQ2pQblQ5dTJZ
RGttYTIKUEQ2RW0zemkzYlpiMHBnY1Nld2FEeFRUY1NDMlpCQlFBRnNXV3hR
R0NRPT0KPVRRQ3oKLS0tLS1FTkQgUEdQIFNJR05BVFVSRS0tLS0tCi0tLS0t
QkVHSU4gUEdQIFNJR05BVFVSRS0tLS0tClZlcnNpb246IEdudVBHIHYxCgpp
UUljQkFBQkNnQUdCUUpUNnBSQ0FBb0pFT1ExOFZ5bXNVWDRNZzRRQUk2VFFV
OGVUQW04VFRrKzhuOFFkVDUwCmZ6cXM2czNJaWp5SUU0UzR1Z1o4QjN2ZGIx
LysrR1VPL2ZXRGNZQXo0ZWFybGRUYTAxQWRxSTJuaUtDS0hqZEkKKzRMeTBs
dE9zZVN4cTJjeTFiWkl6Wlh3TEVuR3M4TXZObFBCd1pVeXlmYmFpeE16aGF0
cTZ6cHZPSW9yVnRqYwpDeUlwQ0xwVEYxWDM2cUlQbmNwTURRY3owMFFGNFBP
N3VTRnJld3ZiYWRBaldaT3dncW80OW1yNW8yRlJKK1ZUCkpZV1IwbHNuVEor
OGhhbFJtTTZISFl6cWtVay8xTWVtOXJNN01NQ2FLVlBRNXYyOWIwNG8xL0Nv
dzE4TDd4MkwKODlQTzdPb2o2eXVyYkdFVERpQURSTElZc0xXTGxCZzFBVDZk
Z3Z4WHY5UnJQTXltRW1kRWRTbzJPUzByTjJPMgpKc2JEajArTHArcWdoc2Z2
VlRBTVYrR3BsRXRWZzV1Q0grUFhoVG9mYmxLWEpERmtDK2dpR2hLTWNudlFD
aGM5Cm00c0wxUExxWTZzS3ozSUFwY0QwNnNBY0RNQzQzLzRZV1Axa081aEEz
bnBYcUFEZks0cnNxQnFuMWRSdUhxL0sKQTFVTzlqTEg2OVEwNHpyVUp3NlFS
WW0rWk1OK3BGTFpSREJiVFpUQm4yL3d4QkZCSkErVVBwbTVoSU1PalkvRwpi
dHFGYzI3cjZkWVZWOUYrZEVFOERNYnAvYjhLNUhySzJVbWZScDM1RUZaSUdT
ckk5dFlXSXhqQVIwVlk4eWVLCk9mTWFwRmsxaC9URlJ6cGpFTDNCcnNYUjNt
Slc1VGlndnQxV0xTTlQzMysvR1F0Qm9oRjBzQzRMdEgrZDBPUTEKZnJ6SzBk
L09xWWhmek1uZVVLeFcKPXUrMGcKLS0tLS1FTkQgUEdQIFNJR05BVFVSRS0t
LS0tCi0tLS0tQkVHSU4gUEdQIFNJR05BVFVSRS0tLS0tClZlcnNpb246IEdu
dVBHIHYxLjQuMTQgKERhcndpbikKCmlRR2NCQUFCQ2dBR0JRSlQ2MllTQUFv
SkVHUW10R1pxSnA4MXRwTUwvM3RkUXBCLzFWTVlGTTZDNFFLbC9RU0wKeXNs
ZWdLZXNhanc3S0dlMVFvS1B1enVobFp1QU1ETnYvSzQyZEVwNHR2N1BVbVlm
dFg4ZGpuYVN4bFQ4V1ZaSwp2ZWUvUmEycVV1UjBsbTZaUm94dDQwSDBiL2lO
ZGpwbHQzSEJSbUI4VUtMUTROWUZ4aStNazhBc3VGVFFyaGM3CktzTG11bzIz
anBONmU0dmVTTGwxWU1hWVI1TisvcEhTaDdCYVNSeVFEZitUdTZFSXR0ZExr
YzhuYlJ6RU9lbEMKQmtRWi9TQWN0YmR6elZyNG84TVVKaitZZzFZQ29tUlZF
TTVSUzc2WnNQZWRTUEJrWkUyMTRXYk9BMEZnTWx4NApGVkhxQkRTYndPUmdI
NW1EaFluaU43T1pxWXhta3pUVXdiaFA3eXVZekpUWi9GZUNGeG50TmRBSHdq
amFYWG54CjFLbm5NSmZ1Yk01WEtYbzJOTGxaZEFNRzVSYjVBcFV0b0hUQlN2
Rjg3Z3BjZTFtS2l5aS9RSG1Zem5jcG11aXQKbElGVzdTVXViRnRrajRWOFR5
NTlXNnB1TG5MbjhQZGFTSUk5RktXY3ZKMTU1UFBSTVNENG1SNlZ0QUJIQnAz
UgpZVWpPLy9paWFGMEJTOFErRXhUdUV5SmJLYlpUOGluWWI0b3hTeGxnM0E9
PQo9L1gzdAotLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS0K
http_version:
recorded_at: Wed, 10 Sep 2014 06:37:46 GMT
recorded_with: VCR 2.9.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.