Skip to content
Merged
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
6 changes: 4 additions & 2 deletions armor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions armor/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,17 @@ 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]))
}
28 changes: 27 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -15,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

Expand Down Expand Up @@ -322,10 +331,27 @@ 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 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
}
// 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
}
Expand Down
82 changes: 82 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package main

import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)

Expand Down Expand Up @@ -51,3 +57,79 @@ 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")
}
}