From 7eadc6a7875b800d4ed5b3aec9ba6403498b3ca2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:50:49 +0000 Subject: [PATCH 1/4] Initial plan From 6ca311a70d2ab38b1c3cec65de22fa652e4fc00c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:54:28 +0000 Subject: [PATCH 2/4] Fix binary junk output when downloading GPG keys Co-authored-by: ericsuh <382805+ericsuh@users.noreply.github.com> --- armor/main.go | 6 ++-- armor/main_test.go | 15 +++++++++ main.go | 20 ++++++++++- main_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) diff --git a/armor/main.go b/armor/main.go index a44f7d8..be8f70e 100644 --- a/armor/main.go +++ b/armor/main.go @@ -49,7 +49,8 @@ scanning: } else if line == "" { continue } else { - err = fmt.Errorf("no header line found. Found '%v'", line) + // Don't include the line content in error to avoid printing binary junk + err = fmt.Errorf("no header line found") return } case ARMOR_PARSER_STATE_HEADER: @@ -59,7 +60,8 @@ scanning: state = ARMOR_PARSER_STATE_BODY continue } else { - err = fmt.Errorf("no end of header line. Found '%v'", line) + // Don't include the line content in error to avoid printing binary junk + err = fmt.Errorf("no end of header line") return } case ARMOR_PARSER_STATE_BODY: diff --git a/armor/main_test.go b/armor/main_test.go index bcf51a1..7d077f0 100644 --- a/armor/main_test.go +++ b/armor/main_test.go @@ -77,3 +77,18 @@ func TestBodyDecode(t *testing.T) { require.NoError(t, err) require.Equal(t, expected, actual) } + +func TestParseBinaryDataDoesNotPrintJunk(t *testing.T) { + // Test that binary data doesn't get included in error messages + binaryData, err := os.ReadFile("../test_data/key.gpg") + require.NoError(t, err) + + _, err = Parse(bytes.NewReader(binaryData)) + require.Error(t, err) + + // Check that the error message doesn't contain binary junk + // The error should be a clean message + require.Contains(t, err.Error(), "no header line found") + require.NotContains(t, err.Error(), string(binaryData[:10])) +} + diff --git a/main.go b/main.go index ae06141..6f2dc0d 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,12 @@ package main import ( + "bytes" "flag" "fmt" "github.com/ericsuh/adapt/aptfile" "github.com/ericsuh/adapt/armor" + "io" "log" "net/http" "os" @@ -322,10 +324,26 @@ func downloadGPGKey(url, destPath string) error { log.Printf("Error closing response body: %v", err2) } }() - dearm, err := armor.Parse(resp.Body) + + // Read the entire body first so we can check if it's binary or ASCII-armored + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return err } + + // Try to parse as ASCII-armored format + dearm, err := armor.Parse(bytes.NewReader(bodyBytes)) + if err != nil { + // If parsing fails, check if it's already in binary format + // GPG binary files start with specific byte sequences + if len(bodyBytes) > 0 && (bodyBytes[0] == 0x99 || bodyBytes[0] == 0x95) { + // Appears to be binary GPG format, use it directly + err = os.WriteFile(destPath, bodyBytes, 0644) + return err + } + // Not ASCII-armored and not recognized binary format + return fmt.Errorf("failed to parse GPG key: %w", err) + } err = os.WriteFile(destPath, dearm, 0644) return err } diff --git a/main_test.go b/main_test.go index 62ccb7c..a380c1a 100644 --- a/main_test.go +++ b/main_test.go @@ -1,6 +1,12 @@ package main import ( + "bytes" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" "testing" ) @@ -51,3 +57,80 @@ func TestSanitizeFilename(t *testing.T) { }) } } + +func TestDownloadGPGKeyHandlesBinary(t *testing.T) { + // Read the binary GPG key from test data + binaryKey, err := os.ReadFile("test_data/key.gpg") + if err != nil { + t.Fatalf("Failed to read test key: %v", err) + } + + // Create a test HTTP server that returns binary GPG key + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/octet-stream") + _, _ = io.Copy(w, bytes.NewReader(binaryKey)) + })) + defer ts.Close() + + // Create a temp file for destination + tempDir := t.TempDir() + destPath := filepath.Join(tempDir, "test.gpg") + + // Download the key + err = downloadGPGKey(ts.URL, destPath) + if err != nil { + t.Fatalf("downloadGPGKey failed: %v", err) + } + + // Verify the downloaded file matches the original + downloaded, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("Failed to read downloaded file: %v", err) + } + + if !bytes.Equal(downloaded, binaryKey) { + t.Errorf("Downloaded key doesn't match original") + } +} + +func TestDownloadGPGKeyHandlesAsciiArmored(t *testing.T) { + // Read the ASCII-armored GPG key from test data + armoredKey, err := os.ReadFile("test_data/key.gpg.asc") + if err != nil { + t.Fatalf("Failed to read test key: %v", err) + } + + // Read expected binary output + expectedBinary, err := os.ReadFile("test_data/key.gpg") + if err != nil { + t.Fatalf("Failed to read expected binary: %v", err) + } + + // Create a test HTTP server that returns ASCII-armored GPG key + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + _, _ = io.Copy(w, bytes.NewReader(armoredKey)) + })) + defer ts.Close() + + // Create a temp file for destination + tempDir := t.TempDir() + destPath := filepath.Join(tempDir, "test.gpg") + + // Download the key + err = downloadGPGKey(ts.URL, destPath) + if err != nil { + t.Fatalf("downloadGPGKey failed: %v", err) + } + + // Verify the downloaded file matches the expected binary + downloaded, err := os.ReadFile(destPath) + if err != nil { + t.Fatalf("Failed to read downloaded file: %v", err) + } + + if !bytes.Equal(downloaded, expectedBinary) { + t.Errorf("Downloaded key doesn't match expected binary") + } +} + From 729c6966781b7648490d88d1f71afe61312bb0ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:56:45 +0000 Subject: [PATCH 3/4] Format code with gofmt Co-authored-by: ericsuh <382805+ericsuh@users.noreply.github.com> --- armor/main_test.go | 3 +-- main.go | 4 ++-- main_test.go | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/armor/main_test.go b/armor/main_test.go index 7d077f0..97661e8 100644 --- a/armor/main_test.go +++ b/armor/main_test.go @@ -85,10 +85,9 @@ func TestParseBinaryDataDoesNotPrintJunk(t *testing.T) { _, err = Parse(bytes.NewReader(binaryData)) require.Error(t, err) - + // Check that the error message doesn't contain binary junk // The error should be a clean message require.Contains(t, err.Error(), "no header line found") require.NotContains(t, err.Error(), string(binaryData[:10])) } - diff --git a/main.go b/main.go index 6f2dc0d..0452d25 100644 --- a/main.go +++ b/main.go @@ -324,13 +324,13 @@ func downloadGPGKey(url, destPath string) error { log.Printf("Error closing response body: %v", err2) } }() - + // Read the entire body first so we can check if it's binary or ASCII-armored bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return err } - + // Try to parse as ASCII-armored format dearm, err := armor.Parse(bytes.NewReader(bodyBytes)) if err != nil { diff --git a/main_test.go b/main_test.go index a380c1a..a52b2d6 100644 --- a/main_test.go +++ b/main_test.go @@ -133,4 +133,3 @@ func TestDownloadGPGKeyHandlesAsciiArmored(t *testing.T) { t.Errorf("Downloaded key doesn't match expected binary") } } - From 71e8a6efdb48df3dfec5ae3bce15d171a18f827a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:57:55 +0000 Subject: [PATCH 4/4] Address code review: use named constants for GPG packet tags and document permissions Co-authored-by: ericsuh <382805+ericsuh@users.noreply.github.com> --- main.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 0452d25..fa840df 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,13 @@ import ( "strings" ) +const ( + // GPG packet tag for Public-Key Encrypted Session Key Packet (old format) + gpgPacketTagOldFormat = 0x95 + // GPG packet tag for Public-Key Encrypted Session Key Packet (new format) + gpgPacketTagNewFormat = 0x99 +) + var ensuredAddAptRepository bool = false var needsUpdate bool = true @@ -335,9 +342,10 @@ func downloadGPGKey(url, destPath string) error { dearm, err := armor.Parse(bytes.NewReader(bodyBytes)) if err != nil { // If parsing fails, check if it's already in binary format - // GPG binary files start with specific byte sequences - if len(bodyBytes) > 0 && (bodyBytes[0] == 0x99 || bodyBytes[0] == 0x95) { + // GPG binary files start with packet tags 0x95 (old format) or 0x99 (new format) + if len(bodyBytes) > 0 && (bodyBytes[0] == gpgPacketTagOldFormat || bodyBytes[0] == gpgPacketTagNewFormat) { // Appears to be binary GPG format, use it directly + // Note: 0644 permissions are appropriate for public keys in /usr/share/keyrings/ err = os.WriteFile(destPath, bodyBytes, 0644) return err }