Skip to content

Instantly share code, notes, and snippets.

@mclosson
Created September 9, 2014 14:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mclosson/68f4cdd80ae9d6baaf6c to your computer and use it in GitHub Desktop.
Save mclosson/68f4cdd80ae9d6baaf6c to your computer and use it in GitHub Desktop.
# 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 'date'
require 'open3'
require 'open-uri'
require 'tempfile'
class SpiderOakCanary
CANARY_URL = 'https://spideroak.com/canary'
DELIMITER = '-----BEGIN PGP SIGNATURE-----'
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 alive?
text = open(CANARY_URL).read
signatures = text.split(DELIMITER)
canary = signatures.shift
signatures = signatures.map { |signature| DELIMITER + signature }
expected_signatures = {}
canary_file = Tempfile.new('')
canary_file.write(canary)
canary_file.close
sig_files = signatures.map do |signature|
sig_file = Tempfile.new('')
sig_file.write(signature)
sig_file.close
sig_file
end
sig_files.each do |sig_file|
command = "gpg --verify #{sig_file.to_path} #{canary_file.to_path}"
stdin, stdout, stderr = Open3.popen3(command)
output = stderr.readlines
signature_date = DateTime.parse(output[0].match(/made (.*) using/)[1])
signer = output[1].match(/Good signature from \"(.*)\"/)[1]
primary_key = output.select { |line| line.include?('Primary key') }.first
key_fingerprint = primary_key.match(/fingerprint: (.*)/)[1]
expected_signatures[key_fingerprint] = signature_date
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")
signature_date && signature_date >= start_date && signature_date < end_date
end.count
if valid_signatures == 3
puts "SpiderOak's Canary is alive and well!"
else
puts "Hrmmm SpiderOak's Canary seems to have died, consider your options."
end
canary_file.unlink
sig_files.each { |sig_file| sig_file.unlink }
end
end
canary = SpiderOakCanary.new
canary.alive?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment