Skip to content
Merged
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
155 changes: 148 additions & 7 deletions commands/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ package commands

import (
"bytes"
"encoding/base64"
"fmt"
"os"
"regexp"
"sort"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)

func TestInspectCmdPrintf(t *testing.T) {
Expand Down Expand Up @@ -65,17 +71,145 @@ func TestInspectSSHCert(t *testing.T) {
require.NoError(t, err, "Unexpected error")

output := buf.String()
require.Contains(t, output, "--- SSH Certificate Information ---")
require.Contains(t, output, "[guest dev]")
require.Contains(t, output, "Provider Signature (OP) exists\n{\n \"alg\": \"RS256\",\n \"kid\": \"kid-")
require.Contains(t, output, "Client Signature (CIC) exists\n{\n \"alg\": \"")

// Verify all four section headers appear in order
sections := []string{
"--- SSH Certificate Information ---",
"--- PKToken Structure ---",
"--- Signature Information ---",
"--- Token Metadata ---",
}
lastIdx := -1
for _, section := range sections {
idx := strings.Index(output, section)
require.Greater(t, idx, lastIdx,
"section %q not found or out of order in output", section)
lastIdx = idx
}

// Split into lines for line-by-line verification
lines := strings.Split(output, "\n")

// --- Verify SSH Certificate Information section ---
requireLineEquals(t, lines, 0, "--- SSH Certificate Information ---")
requireLineMatches(t, lines, 1, `^Serial:\s+0$`)
requireLineMatches(t, lines, 2, `^Type:\s+User Certificate$`)
requireLineMatches(t, lines, 3, `^Key ID:\s+arthur\.aardvark@example\.com$`)
requireLineMatches(t, lines, 4, `^Principals:\s+\[guest dev\]$`)
requireLineMatches(t, lines, 5, `^Valid After:\s+Not set$`)
requireLineMatches(t, lines, 6, `^Valid Before:\s+Forever$`)
requireLineMatches(t, lines, 7, `^Critical Options:\s+map\[\]$`)
requireLineEquals(t, lines, 8, "Extensions:")

// Extensions are from a map so order is non-deterministic.
// Collect extension lines until we hit an empty line or section header.
extStart := 9
var extLines []string
for i := extStart; i < len(lines); i++ {
if !strings.HasPrefix(lines[i], " ") {
break
}
extLines = append(extLines, strings.TrimSpace(lines[i]))
}

// Sort for deterministic comparison
sort.Strings(extLines)

expectedExtNames := []string{
"openpubkey-pkt",
"permit-X11-forwarding",
"permit-agent-forwarding",
"permit-port-forwarding",
"permit-pty",
"permit-user-rc",
}
require.Len(t, extLines, len(expectedExtNames),
"expected %d extensions, got %d", len(expectedExtNames), len(extLines))

for i, extLine := range extLines {
name := expectedExtNames[i]
if name == "openpubkey-pkt" {
require.Regexp(t, `^openpubkey-pkt: \[PKToken data\] \d+ bytes$`, extLine)
} else {
// Permit extensions have empty values
require.Equal(t, name+":", extLine,
"extension line mismatch")
}
}

// --- Verify PKToken Structure section ---
require.Contains(t, output, "\n--- PKToken Structure ---\n")
require.Contains(t, output, "Payload:\n")

// Verify the PKToken payload contains expected claims
require.Contains(t, output, `"email": "arthur.aardvark@example.com"`)
require.Contains(t, output, `"iss":`)
require.Contains(t, output, `"sub":`)
require.Contains(t, output, `"aud":`)

// --- Verify Signature Information section ---
require.Contains(t, output, "\n--- Signature Information ---\n")
require.Contains(t, output, "Provider Signature (OP) exists\n")
require.Contains(t, output, `"alg": "RS256"`)
require.Contains(t, output, `"kid":`)
require.Contains(t, output, "Client Signature (CIC) exists\n")
require.Contains(t, output, `"alg":`)

// --- Verify Token Metadata section ---
require.Contains(t, output, "\n--- Token Metadata ---\n")

// Verify all metadata fields are printed with correct format
metadataSection := output[strings.Index(output, "--- Token Metadata ---"):]
requireLineInSection(t, metadataSection, `^Issuer:\s+.+$`)
requireLineInSection(t, metadataSection, `^Audience:\s+.+$`)
requireLineInSection(t, metadataSection, `^Subject:\s+.+$`)
requireLineInSection(t, metadataSection, `^Identity:\s+.+$`)
requireLineInSection(t, metadataSection, `^Token Hash:\s+.+$`)
requireLineInSection(t, metadataSection, `^Provider Algorithm:\s+RS256$`)
})
}
}

// requireLineEquals checks that the line at index idx exactly equals expected.
func requireLineEquals(t *testing.T, lines []string, idx int, expected string) {
t.Helper()
require.Greater(t, len(lines), idx,
"output has only %d lines, expected line at index %d", len(lines), idx)
require.Equal(t, expected, lines[idx],
"line %d mismatch", idx)
}

// requireLineMatches checks that the line at index idx matches the regexp pattern.
func requireLineMatches(t *testing.T, lines []string, idx int, pattern string) {
t.Helper()
require.Greater(t, len(lines), idx,
"output has only %d lines, expected line at index %d", len(lines), idx)
require.Regexp(t, regexp.MustCompile(pattern), lines[idx],
"line %d does not match pattern %q", idx, pattern)
}

// requireLineInSection checks that at least one line in the section matches
// the regexp pattern.
func requireLineInSection(t *testing.T, section string, pattern string) {
t.Helper()
re := regexp.MustCompile(pattern)
for _, line := range strings.Split(section, "\n") {
if re.MatchString(line) {
return
}
}
require.Fail(t, fmt.Sprintf("no line in section matches pattern %q", pattern))
}

func TestInspectKey(t *testing.T) {
dummyKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINlDR6KRBqBZ1/UL96ltcZWQC7QTgru/ckbCrA/i3RfI your_email@example.com"

// Compute expected fingerprint and marshal prefix from the key
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(dummyKey))
require.NoError(t, err)
expectedFingerprint := ssh.FingerprintSHA256(pubKey)
expectedMarshalPrefix := base64.StdEncoding.EncodeToString(pubKey.Marshal())[:20]

f, err := os.CreateTemp("", "opkssh")
require.NoError(t, err, "unable to create test file")

Expand All @@ -86,6 +220,14 @@ func TestInspectKey(t *testing.T) {

dummyFile := f.Name()

expectedOutput := fmt.Sprintf(
"--- SSH Public Key Information ---\n"+
"Type: ssh-ed25519\n"+
"Fingerprint: %s\n"+
"Marshal (base64): %s...\n",
expectedFingerprint, expectedMarshalPrefix,
)

tests := []struct {
name string
input string
Expand Down Expand Up @@ -126,9 +268,8 @@ func TestInspectKey(t *testing.T) {
require.NoError(t, err, "Unexpected error")

output := buf.String()
require.Contains(t, output, "--- SSH Public Key Information ---")
require.Contains(t, output, "Type: ssh-ed25519")
require.Contains(t, output, "AAAAC3NzaC")
require.Equal(t, expectedOutput, output,
"full printed output should match expected format exactly")
}
})
}
Expand Down
Loading