Output JSON
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -90,17 +91,30 @@ func main() {
|
|||||||
|
|
||||||
if *dumpAll {
|
if *dumpAll {
|
||||||
// Dump all entries in the tile
|
// Dump all entries in the tile
|
||||||
if err := utils.DumpAllEntries(tileData); err != nil {
|
result, err := utils.DumpAllEntries(tileData)
|
||||||
|
if err != nil {
|
||||||
fatal("%v", err)
|
fatal("%v", err)
|
||||||
}
|
}
|
||||||
|
printJSON(result)
|
||||||
} else {
|
} else {
|
||||||
// Dump only the specific entry at the position
|
// Dump only the specific entry at the position
|
||||||
if err := utils.DumpEntryAtPosition(tileData, int(positionInTile), leafIndex); err != nil {
|
entry, err := utils.DumpEntryAtPosition(tileData, int(positionInTile), leafIndex)
|
||||||
|
if err != nil {
|
||||||
fatal("%v", err)
|
fatal("%v", err)
|
||||||
}
|
}
|
||||||
|
printJSON(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printJSON(v interface{}) {
|
||||||
|
data, err := json.MarshalIndent(v, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fatal("failed to marshal JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
func fatal(format string, args ...any) {
|
func fatal(format string, args ...any) {
|
||||||
fmt.Fprintf(os.Stderr, "Error: "+format+"\n", args...)
|
fmt.Fprintf(os.Stderr, "Error: "+format+"\n", args...)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -63,9 +64,20 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, "Tile size: %d bytes\n\n", len(tileData))
|
fmt.Fprintf(os.Stderr, "Tile size: %d bytes\n\n", len(tileData))
|
||||||
|
|
||||||
// Dump all entries
|
// Dump all entries
|
||||||
if err := utils.DumpAllEntries(tileData); err != nil {
|
result, err := utils.DumpAllEntries(tileData)
|
||||||
|
if err != nil {
|
||||||
fatal("%v", err)
|
fatal("%v", err)
|
||||||
}
|
}
|
||||||
|
printJSON(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printJSON(v interface{}) {
|
||||||
|
data, err := json.MarshalIndent(v, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fatal("failed to marshal JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(format string, args ...any) {
|
func fatal(format string, args ...any) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -16,6 +17,32 @@ import (
|
|||||||
|
|
||||||
const maxCompressRatio = 100
|
const maxCompressRatio = 100
|
||||||
|
|
||||||
|
// Entry represents a CT log entry in JSON format.
|
||||||
|
type Entry struct {
|
||||||
|
EntryNumber int `json:"entry_number"`
|
||||||
|
LeafIndex int64 `json:"leaf_index"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
IsPrecert bool `json:"is_precert"`
|
||||||
|
IssuerKeyHash string `json:"issuer_key_hash,omitempty"`
|
||||||
|
CertificateSize int `json:"certificate_size"`
|
||||||
|
PreCertificateSize *int `json:"precertificate_size,omitempty"`
|
||||||
|
ChainFingerprints []string `json:"chain_fingerprints"`
|
||||||
|
ParsedCertInfo json.RawMessage `json:"parsed_cert_info,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashTileOutput represents a hash tile in JSON format.
|
||||||
|
type HashTileOutput struct {
|
||||||
|
NumHashes int `json:"num_hashes"`
|
||||||
|
Hashes []string `json:"hashes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpResult is the result of dumping entries or hashes from a tile.
|
||||||
|
type DumpResult struct {
|
||||||
|
Entries []Entry `json:"entries,omitempty"`
|
||||||
|
HashTile *HashTileOutput `json:"hash_tile,omitempty"`
|
||||||
|
TotalEntries int `json:"total_entries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// FetchURL fetches data from a URL.
|
// FetchURL fetches data from a URL.
|
||||||
func FetchURL(url string) ([]byte, error) {
|
func FetchURL(url string) ([]byte, error) {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
@@ -42,61 +69,70 @@ func Decompress(data []byte) ([]byte, error) {
|
|||||||
return io.ReadAll(io.LimitReader(r, maxSize))
|
return io.ReadAll(io.LimitReader(r, maxSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpAllEntries reads and dumps all entries from tile data.
|
// DumpAllEntries reads and returns all entries from tile data as JSON-serializable structures.
|
||||||
// Automatically detects if the tile is a data tile or hash tile.
|
// Automatically detects if the tile is a data tile or hash tile.
|
||||||
func DumpAllEntries(tileData []byte) error {
|
func DumpAllEntries(tileData []byte) (*DumpResult, error) {
|
||||||
// Try to read as data tile first
|
// Try to read as data tile first
|
||||||
if err := dumpDataTile(tileData); err != nil {
|
result, err := dumpDataTile(tileData)
|
||||||
|
if err != nil {
|
||||||
// If it fails, try as hash tile
|
// If it fails, try as hash tile
|
||||||
fmt.Fprintf(os.Stderr, "Not a data tile, trying as hash tile...\n")
|
fmt.Fprintf(os.Stderr, "Not a data tile, trying as hash tile...\n")
|
||||||
return dumpHashTile(tileData)
|
return dumpHashTile(tileData)
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpDataTile(tileData []byte) error {
|
func dumpDataTile(tileData []byte) (*DumpResult, error) {
|
||||||
entryNum := 0
|
entryNum := 0
|
||||||
|
var entries []Entry
|
||||||
for len(tileData) > 0 {
|
for len(tileData) > 0 {
|
||||||
e, remaining, err := sunlight.ReadTileLeaf(tileData)
|
e, remaining, err := sunlight.ReadTileLeaf(tileData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read entry %d: %w", entryNum, err)
|
return nil, fmt.Errorf("failed to read entry %d: %w", entryNum, err)
|
||||||
}
|
}
|
||||||
tileData = remaining
|
tileData = remaining
|
||||||
|
|
||||||
dumpEntry(e, entryNum)
|
entry := convertEntry(e, entryNum)
|
||||||
fmt.Println()
|
entries = append(entries, entry)
|
||||||
entryNum++
|
entryNum++
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Total entries: %d\n", entryNum)
|
return &DumpResult{
|
||||||
return nil
|
Entries: entries,
|
||||||
|
TotalEntries: entryNum,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpHashTile(tileData []byte) error {
|
func dumpHashTile(tileData []byte) (*DumpResult, error) {
|
||||||
const hashSize = 32 // SHA-256 hash size
|
const hashSize = 32 // SHA-256 hash size
|
||||||
|
|
||||||
if len(tileData)%hashSize != 0 {
|
if len(tileData)%hashSize != 0 {
|
||||||
return fmt.Errorf("invalid hash tile: size %d is not a multiple of %d", len(tileData), hashSize)
|
return nil, fmt.Errorf("invalid hash tile: size %d is not a multiple of %d", len(tileData), hashSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
numHashes := len(tileData) / hashSize
|
numHashes := len(tileData) / hashSize
|
||||||
fmt.Printf("Hash tile with %d hashes:\n\n", numHashes)
|
hashes := make([]string, numHashes)
|
||||||
|
|
||||||
for i := 0; i < numHashes; i++ {
|
for i := 0; i < numHashes; i++ {
|
||||||
hash := tileData[i*hashSize : (i+1)*hashSize]
|
hash := tileData[i*hashSize : (i+1)*hashSize]
|
||||||
fmt.Printf("Hash %d: %x\n", i, hash)
|
hashes[i] = hex.EncodeToString(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return &DumpResult{
|
||||||
|
HashTile: &HashTileOutput{
|
||||||
|
NumHashes: numHashes,
|
||||||
|
Hashes: hashes,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpEntryAtPosition reads and dumps a specific entry at the given position.
|
// DumpEntryAtPosition reads and returns a specific entry at the given position.
|
||||||
func DumpEntryAtPosition(tileData []byte, position int, expectedIndex int64) error {
|
func DumpEntryAtPosition(tileData []byte, position int, expectedIndex int64) (*Entry, error) {
|
||||||
entryNum := 0
|
entryNum := 0
|
||||||
for len(tileData) > 0 {
|
for len(tileData) > 0 {
|
||||||
e, remaining, err := sunlight.ReadTileLeaf(tileData)
|
e, remaining, err := sunlight.ReadTileLeaf(tileData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read entry %d: %w", entryNum, err)
|
return nil, fmt.Errorf("failed to read entry %d: %w", entryNum, err)
|
||||||
}
|
}
|
||||||
tileData = remaining
|
tileData = remaining
|
||||||
|
|
||||||
@@ -105,39 +141,45 @@ func DumpEntryAtPosition(tileData []byte, position int, expectedIndex int64) err
|
|||||||
fmt.Fprintf(os.Stderr, "WARNING: Expected leaf index %d but found %d at position %d\n",
|
fmt.Fprintf(os.Stderr, "WARNING: Expected leaf index %d but found %d at position %d\n",
|
||||||
expectedIndex, e.LeafIndex, position)
|
expectedIndex, e.LeafIndex, position)
|
||||||
}
|
}
|
||||||
dumpEntry(e, entryNum)
|
entry := convertEntry(e, entryNum)
|
||||||
return nil
|
return &entry, nil
|
||||||
}
|
}
|
||||||
entryNum++
|
entryNum++
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("position %d not found in tile (only %d entries)", position, entryNum)
|
return nil, fmt.Errorf("position %d not found in tile (only %d entries)", position, entryNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpEntry(e *sunlight.LogEntry, entryNum int) {
|
func convertEntry(e *sunlight.LogEntry, entryNum int) Entry {
|
||||||
fmt.Printf("=== Entry %d ===\n", entryNum)
|
entry := Entry{
|
||||||
fmt.Printf("Leaf Index: %d\n", e.LeafIndex)
|
EntryNumber: entryNum,
|
||||||
fmt.Printf("Timestamp: %d\n", e.Timestamp)
|
LeafIndex: e.LeafIndex,
|
||||||
fmt.Printf("Is Precert: %v\n", e.IsPrecert)
|
Timestamp: e.Timestamp,
|
||||||
|
IsPrecert: e.IsPrecert,
|
||||||
|
CertificateSize: len(e.Certificate),
|
||||||
|
}
|
||||||
|
|
||||||
if e.IsPrecert {
|
if e.IsPrecert {
|
||||||
fmt.Printf("Issuer Key Hash: %x\n", e.IssuerKeyHash)
|
entry.IssuerKeyHash = hex.EncodeToString(e.IssuerKeyHash[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Certificate: %d bytes\n", len(e.Certificate))
|
|
||||||
if e.PreCertificate != nil {
|
if e.PreCertificate != nil {
|
||||||
fmt.Printf("PreCertificate: %d bytes\n", len(e.PreCertificate))
|
size := len(e.PreCertificate)
|
||||||
|
entry.PreCertificateSize = &size
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Chain Fingerprints: %d entries\n", len(e.ChainFingerprints))
|
// Convert chain fingerprints to hex strings
|
||||||
|
entry.ChainFingerprints = make([]string, len(e.ChainFingerprints))
|
||||||
for i, fp := range e.ChainFingerprints {
|
for i, fp := range e.ChainFingerprints {
|
||||||
fmt.Printf(" [%d]: %x\n", i, fp)
|
entry.ChainFingerprints[i] = hex.EncodeToString(fp[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to extract parsed certificate info
|
// Try to extract parsed certificate info
|
||||||
if trimmed, err := e.TrimmedEntry(); err == nil {
|
if trimmed, err := e.TrimmedEntry(); err == nil {
|
||||||
if data, err := json.MarshalIndent(trimmed, " ", " "); err == nil {
|
if data, err := json.Marshal(trimmed); err == nil {
|
||||||
fmt.Printf("Parsed Certificate Info:\n %s\n", data)
|
entry.ParsedCertInfo = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user