1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2025-11-30 21:32:30 +00:00

feat: add sha256 signatures to outgoing http requests (#2874)

هذا الالتزام موجود في:
Adam Cooke
2024-03-13 08:52:29 +00:00
ملتزم من قبل GitHub
الأصل 9982bb8c31
التزام 96d73653d7
11 ملفات معدلة مع 188 إضافات و10 حذوفات

عرض الملف

@@ -8,12 +8,12 @@ gem "chronic"
gem "domain_name"
gem "dotenv"
gem "dynamic_form"
gem "encrypto_signo"
gem "execjs", "~> 2.7", "< 2.8"
gem "gelf"
gem "haml"
gem "hashie"
gem "highline", require: false
gem "jwt"
gem "kaminari"
gem "klogger-logger"
gem "konfig-config", "~> 3.0"

عرض الملف

@@ -112,7 +112,6 @@ GEM
activemodel (> 5.2.0)
email_validator (2.2.4)
activemodel
encrypto_signo (1.0.0)
erubi (1.12.0)
execjs (2.7.0)
factory_bot (6.4.6)
@@ -152,6 +151,8 @@ GEM
bindata
faraday (~> 2.0)
faraday-follow_redirects
jwt (2.8.1)
base64
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
@@ -410,7 +411,6 @@ DEPENDENCIES
domain_name
dotenv
dynamic_form
encrypto_signo
execjs (~> 2.7, < 2.8)
factory_bot_rails
gelf
@@ -418,6 +418,7 @@ DEPENDENCIES
hashie
highline
jquery-rails
jwt
kaminari
klogger-logger
konfig-config (~> 3.0)

عرض الملف

@@ -0,0 +1,15 @@
# frozen_string_literal: true
class WellKnownController < ApplicationController
layout false
skip_before_action :set_browser_id
skip_before_action :login_required
skip_before_action :set_timezone
def jwks
render json: JWT::JWK::Set.new(Postal.signer.jwk).export.to_json
end
end

عرض الملف

@@ -9,7 +9,7 @@ class DKIMHeader
@dkim_identifier = domain.dkim_identifier
else
@domain_name = Postal::Config.dns.return_path_domain
@dkim_key = Postal.signing_key
@dkim_key = Postal.signer.private_key
@dkim_identifier = Postal::Config.dns.dkim_identifier
end
@domain = domain

66
app/lib/signer.rb Normal file
عرض الملف

@@ -0,0 +1,66 @@
# frozen_string_literal: true
require "base64"
class Signer
# Create a new Signer
#
# @param [OpenSSL::PKey::RSA] private_key The private key to use for signing
# @return [Signer]
def initialize(private_key)
@private_key = private_key
end
# Return the private key
#
# @return [OpenSSL::PKey::RSA]
attr_reader :private_key
# Return the public key for the private key
#
# @return [OpenSSL::PKey::RSA]
def public_key
@private_key.public_key
end
# Sign the given data
#
# @param [String] data The data to sign
# @return [String] The signature
def sign(data)
private_key.sign(OpenSSL::Digest.new("SHA256"), data)
end
# Sign the given data and return a Base64-encoded signature
#
# @param [String] data The data to sign
# @return [String] The Base64-encoded signature
def sign64(data)
Base64.strict_encode64(sign(data))
end
# Return a JWK for the private key
#
# @return [JWT::JWK] The JWK
def jwk
@jwk ||= JWT::JWK.new(private_key, { use: "sig", alg: "RS256" })
end
# Sign the given data using SHA1 (for legacy use)
#
# @param [String] data The data to sign
# @return [String] The signature
def sha1_sign(data)
private_key.sign(OpenSSL::Digest.new("SHA1"), data)
end
# Sign the given data using SHA1 (for legacy use) and return a Base64-encoded string
#
# @param [String] data The data to sign
# @return [String] The signature
def sha1_sign64(data)
Base64.strict_encode64(sha1_sign(data))
end
end

عرض الملف

@@ -89,6 +89,8 @@ Rails.application.routes.draw do
get "auth/oidc/callback", to: "sessions#create_from_oidc"
end
get ".well-known/jwks.json" => "well_known#jwks"
get "ip" => "sessions#ip"
root "organizations#index"

عرض الملف

@@ -98,12 +98,15 @@ module Postal
"#{locker_name} #{suffix}"
end
def signing_key
@signing_key ||= OpenSSL::PKey::RSA.new(File.read(Config.postal.signing_key_path))
def signer
@signer ||= begin
key = OpenSSL::PKey::RSA.new(File.read(Config.postal.signing_key_path))
Signer.new(key)
end
end
def rp_dkim_dns_record
public_key = signing_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "")
public_key = signer.private_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "")
"v=DKIM1; t=s; h=sha256; p=#{public_key};"
end

عرض الملف

@@ -40,8 +40,9 @@ module Postal
end
if options[:sign]
signature = EncryptoSigno.sign(Postal.signing_key, request.body.to_s).gsub("\n", "")
request.add_field "X-Postal-Signature", signature
request.add_field "X-Postal-Signature-KID", Postal.signer.jwk.kid
request.add_field "X-Postal-Signature", Postal.signer.sha1_sign64(request.body.to_s)
request.add_field "X-Postal-Signature-256", Postal.signer.sign64(request.body.to_s)
end
request["User-Agent"] = options[:user_agent] || "Postal/#{Postal.version}"

12
spec/lib/postal_spec.rb Normal file
عرض الملف

@@ -0,0 +1,12 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Postal do
describe "#signer" do
it "returns a signer with the installation's signing key" do
expect(Postal.signer).to be_a(Signer)
expect(Postal.signer.private_key.to_pem).to eq OpenSSL::PKey::RSA.new(File.read(Postal::Config.postal.signing_key_path)).to_pem
end
end
end

76
spec/lib/signer_spec.rb Normal file
عرض الملف

@@ -0,0 +1,76 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Signer do
STATIC_PRIVATE_KEY = OpenSSL::PKey::RSA.new(2048) # rubocop:disable Lint/ConstantDefinitionInBlock
subject(:signer) { described_class.new(STATIC_PRIVATE_KEY) }
describe "#private_key" do
it "returns the private key" do
expect(signer.private_key).to eq(STATIC_PRIVATE_KEY)
end
end
describe "#public_key" do
it "returns the public key" do
expect(signer.public_key.to_s).to eq(STATIC_PRIVATE_KEY.public_key.to_s)
end
end
describe "#sign" do
it "returns a valid signature" do
data = "hello world!"
signature = signer.sign(data)
expect(signature).to be_a(String)
verification = STATIC_PRIVATE_KEY.public_key.verify(OpenSSL::Digest.new("SHA256"),
signature,
data)
expect(verification).to be true
end
end
describe "#sign64" do
it "returns a valid Base64-encoded signature" do
data = "hello world!"
signature = signer.sign64(data)
expect(signature).to be_a(String)
verification = STATIC_PRIVATE_KEY.public_key.verify(OpenSSL::Digest.new("SHA256"),
Base64.strict_decode64(signature),
data)
expect(verification).to be true
end
end
describe "#jwk" do
it "returns a valid JWK" do
jwk = signer.jwk
expect(jwk).to be_a(JWT::JWK::RSA)
end
end
describe "#sha1_sign" do
it "returns a valid signature" do
data = "hello world!"
signature = signer.sha1_sign(data)
expect(signature).to be_a(String)
verification = STATIC_PRIVATE_KEY.public_key.verify(OpenSSL::Digest.new("SHA1"),
signature,
data)
expect(verification).to be true
end
end
describe "#sha1_sign64" do
it "returns a valid Base64-encoded signature" do
data = "hello world!"
signature = signer.sha1_sign64(data)
expect(signature).to be_a(String)
verification = STATIC_PRIVATE_KEY.public_key.verify(OpenSSL::Digest.new("SHA1"),
Base64.strict_decode64(signature),
data)
expect(verification).to be true
end
end
end

عرض الملف

@@ -28,7 +28,9 @@ RSpec.describe WebhookDeliveryService do
}.to_json,
headers: {
"Content-Type" => "application/json",
"X-Postal-Signature" => /\A[a-z0-9\/+]+=*\z/i
"X-Postal-Signature" => /\A[a-z0-9\/+]+=*\z/i,
"X-Postal-Signature-256" => /\A[a-z0-9\/+]+=*\z/i,
"X-Postal-Signature-KID" => /\A[a-f0-9\/+]{64}\z/i
}
})
end