Skip to content

Commit

Permalink
Merge pull request #7 from rossta/add_specs
Browse files Browse the repository at this point in the history
Add specs
  • Loading branch information
zaru committed May 14, 2016
2 parents 0ce0a88 + 8e36c65 commit f2c5e8e
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
task default: :spec
16 changes: 16 additions & 0 deletions bin/rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env ruby
#
# This file was generated by Bundler.
#
# The application 'rspec' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)

require "rubygems"
require "bundler/setup"

load Gem.bin_path("rspec-core", "rspec")
38 changes: 18 additions & 20 deletions lib/webpush.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,24 @@ def payload_send(message:, endpoint:, p256dh:, auth:, api_key: "")
private

def push_server_post(endpoint, payload, api_key = "")
begin
uri = URI.parse(endpoint)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
header = {
"Content-Type" => "application/octet-stream",
"Content-Encoding" => "aesgcm",
"Encryption" => "salt=#{Base64.urlsafe_encode64(payload[:salt]).delete('=')}",
"Crypto-Key" => "dh=#{Base64.urlsafe_encode64(payload[:server_public_key_bn]).delete('=')}",
"Ttl" => "2419200"
}
header["Authorization"] = "key=#{api_key}" unless api_key.empty?
req = Net::HTTP::Post.new(uri.request_uri, header)
req.body = payload[:ciphertext]
res = http.request(req)
return ("201" == res.code) ? true : false
rescue
return false
end
uri = URI.parse(endpoint)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
header = {
"Content-Type" => "application/octet-stream",
"Content-Encoding" => "aesgcm",
"Encryption" => "salt=#{Base64.urlsafe_encode64(payload[:salt]).delete('=')}",
"Crypto-Key" => "dh=#{Base64.urlsafe_encode64(payload[:server_public_key_bn]).delete('=')}",
"Ttl" => "2419200"
}
header["Authorization"] = "key=#{api_key}" unless api_key.nil? || api_key.empty?
req = Net::HTTP::Post.new(uri.request_uri, header)
req.body = payload[:ciphertext]
res = http.request(req)
res.code == "201"
rescue
false
end
end
end
24 changes: 18 additions & 6 deletions lib/webpush/encryption.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Encryption
extend self

def encrypt(message, p256dh, auth)
assert_arguments(message, p256dh, auth)

group_name = "prime256v1"
salt = Random.new.bytes(16)

Expand All @@ -18,15 +20,15 @@ def encrypt(message, p256dh, auth)

client_auth_token = Base64.urlsafe_decode64(auth)

prk = HKDF.new(shared_secret, :salt => client_auth_token, :algorithm => 'SHA256', :info => "Content-Encoding: auth\0").next_bytes(32)
prk = HKDF.new(shared_secret, salt: client_auth_token, algorithm: 'SHA256', info: "Content-Encoding: auth\0").next_bytes(32)

context = create_context(client_public_key_bn, server_public_key_bn)

content_encryption_key_info = create_info('aesgcm', context)
content_encryption_key = HKDF.new(prk, :salt => salt, :info => content_encryption_key_info).next_bytes(16)
content_encryption_key = HKDF.new(prk, salt: salt, info: content_encryption_key_info).next_bytes(16)

nonce_info = create_info('nonce', context)
nonce = HKDF.new(prk, :salt => salt, :info => nonce_info).next_bytes(12)
nonce = HKDF.new(prk, salt: salt, info: nonce_info).next_bytes(12)

ciphertext = encrypt_payload(message, content_encryption_key, nonce)

Expand All @@ -40,9 +42,9 @@ def encrypt(message, p256dh, auth)

private

def create_context(clientPublicKey, serverPublicKey)
c = convert16bit(clientPublicKey)
s = convert16bit(serverPublicKey)
def create_context(client_public_key, server_public_key)
c = convert16bit(client_public_key)
s = convert16bit(server_public_key)
context = "\0"
context += [c.bytesize].pack("n*")
context += c
Expand Down Expand Up @@ -77,5 +79,15 @@ def create_info(type, context)
def convert16bit(key)
[key.to_s(16)].pack("H*")
end

def assert_arguments(message, p256dh, auth)
raise ArgumentError, "message cannot be blank" if blank?(message)
raise ArgumentError, "p256dh cannot be blank" if blank?(p256dh)
raise ArgumentError, "auth cannot be blank" if blank?(auth)
end

def blank?(value)
value.nil? || value.empty?
end
end
end
15 changes: 15 additions & 0 deletions spec/webpush/encryption_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@
expect(decrypted_data).to eq("Hello World")
end

it 'returns error when message is blank' do
expect{Webpush::Encryption.encrypt(nil, p256dh, auth)}.to raise_error(ArgumentError)
expect{Webpush::Encryption.encrypt("", p256dh, auth)}.to raise_error(ArgumentError)
end

it 'returns error when p256dh is blank' do
expect{Webpush::Encryption.encrypt("Hello world", nil, auth)}.to raise_error(ArgumentError)
expect{Webpush::Encryption.encrypt("Hello world", "", auth)}.to raise_error(ArgumentError)
end

it 'returns error when auth is blank' do
expect{Webpush::Encryption.encrypt("Hello world", p256dh, "")}.to raise_error(ArgumentError)
expect{Webpush::Encryption.encrypt("Hello world", p256dh, nil)}.to raise_error(ArgumentError)
end

def generate_ecdh_key
group = "prime256v1"
curve = OpenSSL::PKey::EC.new(group)
Expand Down
43 changes: 42 additions & 1 deletion spec/webpush_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,58 @@
}
end

before do
allow(Webpush::Encryption).to receive(:encrypt).and_return(payload)
end

it 'calls the relevant service with the correct headers' do
expect(Webpush::Encryption).to receive(:encrypt).and_return(payload)

stub_request(:post, expected_endpoint).
with(body: expected_body, headers: expected_headers).
to_return(:status => 201, :body => "", :headers => {})
to_return(status: 201, body: "", headers: {})

result = Webpush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth)

expect(result).to be(true)
end

it 'returns false for unsuccessful status code by default' do
stub_request(:post, expected_endpoint).
to_return(status: 401, body: "", headers: {})

result = Webpush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth)

expect(result).to be(false)
end

it 'returns false on error by default' do
stub_request(:post, expected_endpoint).to_raise(StandardError)

result = Webpush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth)

expect(result).to be(false)
end

it 'inserts Authorization header when present' do
api_key = SecureRandom.hex(16)
expected_headers.merge!('Authorization' => "key=#{api_key}")

stub_request(:post, expected_endpoint).
with(body: expected_body, headers: expected_headers).
to_return(status: 201, body: "", headers: {})

Webpush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth, api_key: api_key)
end

it 'does not insert Authorization header when blank' do
stub_request(:post, expected_endpoint).
with(body: expected_body, headers: expected_headers).
to_return(status: 201, body: "", headers: {})

Webpush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth, api_key: "")
Webpush.payload_send(message: message, endpoint: endpoint, p256dh: p256dh, auth: auth, api_key: nil)
end
end

context 'chrome endpoint' do
Expand Down

0 comments on commit f2c5e8e

Please sign in to comment.