Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/sigstore/rekor/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# limitations under the License.

require "net/http"
require_relative "../version"

module Sigstore
module Rekor
Expand Down Expand Up @@ -66,7 +67,8 @@ def retrieve

def post(entry)
resp = @session.post2(@url.path.chomp("/"), entry.to_json,
{ "Content-Type" => "application/json", "Accept" => "application/json" })
{ "Content-Type" => "application/json", "Accept" => "application/json",
"User-Agent" => Sigstore::USER_AGENT })

unless resp.code == "201"
raise Error::FailedRekorPost,
Expand All @@ -89,7 +91,8 @@ def initialize(url, session:)
def post(expected_entry)
data = { entries: [expected_entry] }
resp = @session.post2(@url.path, data.to_json,
{ "Content-Type" => "application/json", "Accept" => "application/json" })
{ "Content-Type" => "application/json", "Accept" => "application/json",
"User-Agent" => Sigstore::USER_AGENT })

if resp.code != "200"
raise Error::FailedRekorLookup,
Expand Down
3 changes: 2 additions & 1 deletion lib/sigstore/signer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
require_relative "oidc"
require_relative "policy"
require_relative "verifier"
require_relative "version"

module Sigstore
class Signer
Expand Down Expand Up @@ -109,7 +110,7 @@ def fetch_cert(csr)
resp = Net::HTTP.post(
uri,
JSON.dump(csr),
{ "Content-Type" => "application/json" }
{ "Content-Type" => "application/json", "User-Agent" => Sigstore::USER_AGENT }
)

unless resp.code == "200"
Expand Down
5 changes: 3 additions & 2 deletions lib/sigstore/tuf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
require "uri"
require "net/http"
require "rubygems/remote_fetcher"
require_relative "version"

module Sigstore
module TUF
Expand Down Expand Up @@ -133,8 +134,8 @@ def fetch(uri)

fetcher = Gem::RemoteFetcher.fetcher
begin
response = fetcher.request(uri, Net::HTTP::Get, nil) do
nil
response = fetcher.request(uri, Net::HTTP::Get, nil) do |req|
req["User-Agent"] = Sigstore::USER_AGENT
end
response.uri = uri
case response
Expand Down
1 change: 1 addition & 0 deletions lib/sigstore/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@

module Sigstore
VERSION = "0.2.2"
USER_AGENT = "sigstore-ruby/#{VERSION}".freeze
end
155 changes: 155 additions & 0 deletions test/sigstore/user_agent_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# frozen_string_literal: true

# Copyright 2026 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "test_helper"
require "sigstore/rekor/client"
require "sigstore/signer"
require "sigstore/tuf"
require "sigstore/version"

class Sigstore::UserAgentTest < Test::Unit::TestCase
def test_user_agent_constant
assert_equal "sigstore-ruby/#{Sigstore::VERSION}", Sigstore::USER_AGENT
assert Sigstore::USER_AGENT.frozen?
end

def test_rekor_client_post_sends_user_agent
client = Sigstore::Rekor::Client.new(url: "https://rekor.sigstore.dev")
entries = client.log.entries

# Capture the headers sent to post2
captured_headers = nil
session = entries.instance_variable_get(:@session)
session.define_singleton_method(:post2) do |_path, _body, headers|
captured_headers = headers
# Return a mock response with proper base64-encoded body
encoded_body = Base64.strict_encode64({ "kind" => "hashedrekord", "apiVersion" => "0.0.1" }.to_json)
response = Object.new
response.define_singleton_method(:code) { "201" }
response.define_singleton_method(:content_type) { "application/json" }
response.define_singleton_method(:body) do
{ "abc123" => { "body" => encoded_body, "logIndex" => 1, "logID" => "abcd",
"integratedTime" => 1234 } }.to_json
end
response
end

entry = Object.new
def entry.to_json
"{}"
end

entries.post(entry)

assert_equal Sigstore::USER_AGENT, captured_headers["User-Agent"]
end

def test_rekor_client_retrieve_sends_user_agent
client = Sigstore::Rekor::Client.new(url: "https://rekor.sigstore.dev")
retrieve = client.log.entries.retrieve

# Capture the headers sent to post2
captured_headers = nil
session = retrieve.instance_variable_get(:@session)
session.define_singleton_method(:post2) do |_path, _body, headers|
captured_headers = headers
# Return a mock response with proper base64-encoded body
encoded_body = Base64.strict_encode64({ "kind" => "hashedrekord", "apiVersion" => "0.0.1" }.to_json)
response = Object.new
response.define_singleton_method(:code) { "200" }
response.define_singleton_method(:body) do
[{ "abc123" => { "body" => encoded_body, "logIndex" => 1, "logID" => "abcd",
"integratedTime" => 1234 } }].to_json
end
response
end

retrieve.post({})

assert_equal Sigstore::USER_AGENT, captured_headers["User-Agent"]
end

def test_signer_fetch_cert_sends_user_agent
# Stub Net::HTTP.post to capture headers
captured_headers = nil
original_post = Net::HTTP.method(:post)

Net::HTTP.define_singleton_method(:post) do |_uri, _body, headers|
captured_headers = headers
# Return a mock response
response = Object.new
response.define_singleton_method(:code) { "200" }
response.define_singleton_method(:body) do
{ signedCertificateEmbeddedSct: { chain: { certificates: ["cert"] } } }.to_json
end
response
end

begin
# Create a minimal signer to test fetch_cert directly
signer = Sigstore::Signer.allocate

# Create mock trusted_root with certificate_authority_for_signing
ca = Struct.new(:uri).new("https://fulcio.sigstore.dev")
trusted_root = Object.new
trusted_root.define_singleton_method(:certificate_authority_for_signing) { ca }
signer.instance_variable_set(:@trusted_root, trusted_root)

# Call fetch_cert - it will fail when parsing the cert, but the HTTP request will have been made
assert_raise(Sigstore::Error::InvalidCertificate) do
signer.send(:fetch_cert, { credentials: {}, certificateSigningRequest: "" })
end

assert_equal Sigstore::USER_AGENT, captured_headers["User-Agent"]
ensure
Net::HTTP.define_singleton_method(:post, &original_post)
end
end

def test_tuf_fetch_sends_user_agent
Dir.mktmpdir do |dir|
updater = Sigstore::TUF::TrustUpdater.new("https://tuf-repo-cdn.sigstore.dev", true,
metadata_dir: dir, targets_dir: dir)

user_agent_received = nil

mock_fetcher = Object.new
mock_fetcher.define_singleton_method(:request) do |uri, request_class, _last_modified, &block|
req = request_class.new(uri.respond_to?(:request_uri) ? uri.request_uri : uri.to_s)
block&.call(req)
user_agent_received = req["User-Agent"]

# Return a valid response
net_http_ok = defined?(Gem::Net::HTTPOK) ? Gem::Net::HTTPOK : Net::HTTPOK
response = net_http_ok.new("1.1", 200, "OK")
response.define_singleton_method(:body) { "{}" }
response.define_singleton_method(:uri=) { |u| @uri = u }
response.define_singleton_method(:uri) { @uri }
response
end

original_fetcher = Gem::RemoteFetcher.fetcher
Gem::RemoteFetcher.define_singleton_method(:fetcher) { mock_fetcher }

begin
updater.send(:fetch, "https://tuf-repo-cdn.sigstore.dev/root.json")
assert_equal Sigstore::USER_AGENT, user_agent_received
ensure
Gem::RemoteFetcher.define_singleton_method(:fetcher) { original_fetcher }
end
end
end
end
Loading